diff options
Diffstat (limited to 'wp-includes/js/dist/rich-text.js')
-rw-r--r-- | wp-includes/js/dist/rich-text.js | 1255 |
1 files changed, 481 insertions, 774 deletions
diff --git a/wp-includes/js/dist/rich-text.js b/wp-includes/js/dist/rich-text.js index e0aa83e..3e953e5 100644 --- a/wp-includes/js/dist/rich-text.js +++ b/wp-includes/js/dist/rich-text.js @@ -136,307 +136,9 @@ function formatTypes(state = {}, action) { formatTypes })); -;// CONCATENATED MODULE: ./node_modules/rememo/rememo.js - - -/** @typedef {(...args: any[]) => *[]} GetDependants */ - -/** @typedef {() => void} Clear */ - -/** - * @typedef {{ - * getDependants: GetDependants, - * clear: Clear - * }} EnhancedSelector - */ - -/** - * Internal cache entry. - * - * @typedef CacheNode - * - * @property {?CacheNode|undefined} [prev] Previous node. - * @property {?CacheNode|undefined} [next] Next node. - * @property {*[]} args Function arguments for cache entry. - * @property {*} val Function result. - */ - -/** - * @typedef Cache - * - * @property {Clear} clear Function to clear cache. - * @property {boolean} [isUniqueByDependants] Whether dependants are valid in - * considering cache uniqueness. A cache is unique if dependents are all arrays - * or objects. - * @property {CacheNode?} [head] Cache head. - * @property {*[]} [lastDependants] Dependants from previous invocation. - */ - -/** - * Arbitrary value used as key for referencing cache object in WeakMap tree. - * - * @type {{}} - */ -var LEAF_KEY = {}; - -/** - * Returns the first argument as the sole entry in an array. - * - * @template T - * - * @param {T} value Value to return. - * - * @return {[T]} Value returned as entry in array. - */ -function arrayOf(value) { - return [value]; -} - -/** - * Returns true if the value passed is object-like, or false otherwise. A value - * is object-like if it can support property assignment, e.g. object or array. - * - * @param {*} value Value to test. - * - * @return {boolean} Whether value is object-like. - */ -function isObjectLike(value) { - return !!value && 'object' === typeof value; -} - -/** - * Creates and returns a new cache object. - * - * @return {Cache} Cache object. - */ -function createCache() { - /** @type {Cache} */ - var cache = { - clear: function () { - cache.head = null; - }, - }; - - return cache; -} - -/** - * Returns true if entries within the two arrays are strictly equal by - * reference from a starting index. - * - * @param {*[]} a First array. - * @param {*[]} b Second array. - * @param {number} fromIndex Index from which to start comparison. - * - * @return {boolean} Whether arrays are shallowly equal. - */ -function isShallowEqual(a, b, fromIndex) { - var i; - - if (a.length !== b.length) { - return false; - } - - for (i = fromIndex; i < a.length; i++) { - if (a[i] !== b[i]) { - return false; - } - } - - return true; -} - -/** - * Returns a memoized selector function. The getDependants function argument is - * called before the memoized selector and is expected to return an immutable - * reference or array of references on which the selector depends for computing - * its own return value. The memoize cache is preserved only as long as those - * dependant references remain the same. If getDependants returns a different - * reference(s), the cache is cleared and the selector value regenerated. - * - * @template {(...args: *[]) => *} S - * - * @param {S} selector Selector function. - * @param {GetDependants=} getDependants Dependant getter returning an array of - * references used in cache bust consideration. - */ -/* harmony default export */ function rememo(selector, getDependants) { - /** @type {WeakMap<*,*>} */ - var rootCache; - - /** @type {GetDependants} */ - var normalizedGetDependants = getDependants ? getDependants : arrayOf; - - /** - * Returns the cache for a given dependants array. When possible, a WeakMap - * will be used to create a unique cache for each set of dependants. This - * is feasible due to the nature of WeakMap in allowing garbage collection - * to occur on entries where the key object is no longer referenced. Since - * WeakMap requires the key to be an object, this is only possible when the - * dependant is object-like. The root cache is created as a hierarchy where - * each top-level key is the first entry in a dependants set, the value a - * WeakMap where each key is the next dependant, and so on. This continues - * so long as the dependants are object-like. If no dependants are object- - * like, then the cache is shared across all invocations. - * - * @see isObjectLike - * - * @param {*[]} dependants Selector dependants. - * - * @return {Cache} Cache object. - */ - function getCache(dependants) { - var caches = rootCache, - isUniqueByDependants = true, - i, - dependant, - map, - cache; - - for (i = 0; i < dependants.length; i++) { - dependant = dependants[i]; - - // Can only compose WeakMap from object-like key. - if (!isObjectLike(dependant)) { - isUniqueByDependants = false; - break; - } - - // Does current segment of cache already have a WeakMap? - if (caches.has(dependant)) { - // Traverse into nested WeakMap. - caches = caches.get(dependant); - } else { - // Create, set, and traverse into a new one. - map = new WeakMap(); - caches.set(dependant, map); - caches = map; - } - } - - // We use an arbitrary (but consistent) object as key for the last item - // in the WeakMap to serve as our running cache. - if (!caches.has(LEAF_KEY)) { - cache = createCache(); - cache.isUniqueByDependants = isUniqueByDependants; - caches.set(LEAF_KEY, cache); - } - - return caches.get(LEAF_KEY); - } - - /** - * Resets root memoization cache. - */ - function clear() { - rootCache = new WeakMap(); - } - - /* eslint-disable jsdoc/check-param-names */ - /** - * The augmented selector call, considering first whether dependants have - * changed before passing it to underlying memoize function. - * - * @param {*} source Source object for derivation. - * @param {...*} extraArgs Additional arguments to pass to selector. - * - * @return {*} Selector result. - */ - /* eslint-enable jsdoc/check-param-names */ - function callSelector(/* source, ...extraArgs */) { - var len = arguments.length, - cache, - node, - i, - args, - dependants; - - // Create copy of arguments (avoid leaking deoptimization). - args = new Array(len); - for (i = 0; i < len; i++) { - args[i] = arguments[i]; - } - - dependants = normalizedGetDependants.apply(null, args); - cache = getCache(dependants); - - // If not guaranteed uniqueness by dependants (primitive type), shallow - // compare against last dependants and, if references have changed, - // destroy cache to recalculate result. - if (!cache.isUniqueByDependants) { - if ( - cache.lastDependants && - !isShallowEqual(dependants, cache.lastDependants, 0) - ) { - cache.clear(); - } - - cache.lastDependants = dependants; - } - - node = cache.head; - while (node) { - // Check whether node arguments match arguments - if (!isShallowEqual(node.args, args, 1)) { - node = node.next; - continue; - } - - // At this point we can assume we've found a match - - // Surface matched node to head if not already - if (node !== cache.head) { - // Adjust siblings to point to each other. - /** @type {CacheNode} */ (node.prev).next = node.next; - if (node.next) { - node.next.prev = node.prev; - } - - node.next = cache.head; - node.prev = null; - /** @type {CacheNode} */ (cache.head).prev = node; - cache.head = node; - } - - // Return immediately - return node.val; - } - - // No cached value found. Continue to insertion phase: - - node = /** @type {CacheNode} */ ({ - // Generate the result from original function - val: selector.apply(null, args), - }); - - // Avoid including the source object in the cache. - args[0] = null; - node.args = args; - - // Don't need to check whether node is already head, since it would - // have been returned above already if it was - - // Shift existing head down list - if (cache.head) { - cache.head.prev = node; - node.next = cache.head; - } - - cache.head = node; - - return node.val; - } - - callSelector.getDependants = normalizedGetDependants; - callSelector.clear = clear; - clear(); - - return /** @type {S & EnhancedSelector} */ (callSelector); -} - ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/store/selectors.js /** - * External dependencies + * WordPress dependencies */ @@ -473,7 +175,7 @@ function isShallowEqual(a, b, fromIndex) { * * @return {Array} Format types. */ -const getFormatTypes = rememo(state => Object.values(state.formatTypes), state => [state.formatTypes]); +const getFormatTypes = (0,external_wp_data_namespaceObject.createSelector)(state => Object.values(state.formatTypes), state => [state.formatTypes]); /** * Returns a format type by name. @@ -1185,7 +887,9 @@ function toTree({ } if (character === OBJECT_REPLACEMENT_CHARACTER) { const replacement = replacements[i]; - if (!replacement) continue; + if (!replacement) { + continue; + } const { type, attributes, @@ -1902,7 +1606,9 @@ function createFromElement({ }); continue; } - if (format) delete format.formatType; + if (format) { + delete format.formatType; + } const value = createFromElement({ element: node, range, @@ -3024,7 +2730,9 @@ function useAnchorRef({ } = settings; const activeFormat = name ? getActiveFormat(value, name) : undefined; return (0,external_wp_element_namespaceObject.useMemo)(() => { - if (!ref.current) return; + if (!ref.current) { + return; + } const { ownerDocument: { defaultView @@ -3088,9 +2796,15 @@ function getFormatElement(range, editableContentElement, tagName, className) { if (element.nodeType !== element.ELEMENT_NODE) { element = element.parentElement; } - if (!element) return; - if (element === editableContentElement) return; - if (!editableContentElement.contains(element)) return; + if (!element) { + return; + } + if (element === editableContentElement) { + return; + } + if (!editableContentElement.contains(element)) { + return; + } const selector = tagName + (className ? '.' + className : ''); // .closest( selector ), but with a boundary. Check if the element matches @@ -3143,7 +2857,9 @@ function createVirtualAnchorElement(range, editableContentElement) { * @return {HTMLElement|VirtualAnchorElement|undefined} The anchor. */ function getAnchor(editableContentElement, tagName, className) { - if (!editableContentElement) return; + if (!editableContentElement) { + return; + } const { ownerDocument } = editableContentElement; @@ -3151,12 +2867,20 @@ function getAnchor(editableContentElement, tagName, className) { defaultView } = ownerDocument; const selection = defaultView.getSelection(); - if (!selection) return; - if (!selection.rangeCount) return; + if (!selection) { + return; + } + if (!selection.rangeCount) { + return; + } const range = selection.getRangeAt(0); - if (!range || !range.startContainer) return; + if (!range || !range.startContainer) { + return; + } const formatElement = getFormatElement(range, editableContentElement, tagName, className); - if (formatElement) return formatElement; + if (formatElement) { + return formatElement; + } return createVirtualAnchorElement(range, editableContentElement); } @@ -3184,7 +2908,9 @@ function useAnchor({ const [anchor, setAnchor] = (0,external_wp_element_namespaceObject.useState)(() => getAnchor(editableContentElement, tagName, className)); const wasActive = (0,external_wp_compose_namespaceObject.usePrevious)(isActive); (0,external_wp_element_namespaceObject.useLayoutEffect)(() => { - if (!editableContentElement) return; + if (!editableContentElement) { + return; + } function callback() { setAnchor(getAnchor(editableContentElement, tagName, className)); } @@ -3253,7 +2979,9 @@ const whiteSpace = 'pre-wrap'; const minWidth = '1px'; function useDefaultStyle() { return (0,external_wp_element_namespaceObject.useCallback)(element => { - if (!element) return; + if (!element) { + return; + } element.style.whiteSpace = whiteSpace; element.style.minWidth = minWidth; }, []); @@ -3315,216 +3043,243 @@ function useBoundaryStyle({ return ref; } -;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-copy-handler.js +;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/copy-handler.js /** - * WordPress dependencies + * Internal dependencies */ -/** - * Internal dependencies - */ +/* harmony default export */ const copy_handler = (props => element => { + function onCopy(event) { + const { + record + } = props.current; + const { + ownerDocument + } = element; + if (isCollapsed(record.current) || !element.contains(ownerDocument.activeElement)) { + return; + } + const selectedRecord = slice(record.current); + const plainText = getTextContent(selectedRecord); + const html = toHTMLString({ + value: selectedRecord + }); + event.clipboardData.setData('text/plain', plainText); + event.clipboardData.setData('text/html', html); + event.clipboardData.setData('rich-text', 'true'); + event.preventDefault(); + if (event.type === 'cut') { + ownerDocument.execCommand('delete'); + } + } + const { + defaultView + } = element.ownerDocument; + defaultView.addEventListener('copy', onCopy); + defaultView.addEventListener('cut', onCopy); + return () => { + defaultView.removeEventListener('copy', onCopy); + defaultView.removeEventListener('cut', onCopy); + }; +}); +;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/select-object.js +/* harmony default export */ const select_object = (() => element => { + function onClick(event) { + const { + target + } = event; + // If the child element has no text content, it must be an object. + if (target === element || target.textContent && target.isContentEditable) { + return; + } + const { + ownerDocument + } = target; + const { + defaultView + } = ownerDocument; + const selection = defaultView.getSelection(); -function useCopyHandler(props) { - const propsRef = (0,external_wp_element_namespaceObject.useRef)(props); - propsRef.current = props; - return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { - function onCopy(event) { - const { - record - } = propsRef.current; - const { - ownerDocument - } = element; - if (isCollapsed(record.current) || !element.contains(ownerDocument.activeElement)) { - return; - } - const selectedRecord = slice(record.current); - const plainText = getTextContent(selectedRecord); - const html = toHTMLString({ - value: selectedRecord - }); - event.clipboardData.setData('text/plain', plainText); - event.clipboardData.setData('text/html', html); - event.clipboardData.setData('rich-text', 'true'); - event.preventDefault(); - if (event.type === 'cut') { - ownerDocument.execCommand('delete'); - } + // If it's already selected, do nothing and let default behavior happen. + // This means it's "click-through". + if (selection.containsNode(target)) { + return; } - element.addEventListener('copy', onCopy); - element.addEventListener('cut', onCopy); - return () => { - element.removeEventListener('copy', onCopy); - element.removeEventListener('cut', onCopy); - }; - }, []); -} + const range = ownerDocument.createRange(); + // If the target is within a non editable element, select the non + // editable element. + const nodeToSelect = target.isContentEditable ? target : target.closest('[contenteditable]'); + range.selectNode(nodeToSelect); + selection.removeAllRanges(); + selection.addRange(range); + event.preventDefault(); + } + function onFocusIn(event) { + // When there is incoming focus from a link, select the object. + if (event.relatedTarget && !element.contains(event.relatedTarget) && event.relatedTarget.tagName === 'A') { + onClick(event); + } + } + element.addEventListener('click', onClick); + element.addEventListener('focusin', onFocusIn); + return () => { + element.removeEventListener('click', onClick); + element.removeEventListener('focusin', onFocusIn); + }; +}); ;// CONCATENATED MODULE: external ["wp","keycodes"] const external_wp_keycodes_namespaceObject = window["wp"]["keycodes"]; -;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-format-boundaries.js +;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/format-boundaries.js /** * WordPress dependencies */ - - /** * Internal dependencies */ const EMPTY_ACTIVE_FORMATS = []; -function useFormatBoundaries(props) { - const [, forceRender] = (0,external_wp_element_namespaceObject.useReducer)(() => ({})); - const propsRef = (0,external_wp_element_namespaceObject.useRef)(props); - propsRef.current = props; - return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { - function onKeyDown(event) { - const { - keyCode, - shiftKey, - altKey, - metaKey, - ctrlKey - } = event; - if ( - // Only override left and right keys without modifiers pressed. - shiftKey || altKey || metaKey || ctrlKey || keyCode !== external_wp_keycodes_namespaceObject.LEFT && keyCode !== external_wp_keycodes_namespaceObject.RIGHT) { - return; - } - const { - record, - applyRecord - } = propsRef.current; - const { - text, - formats, - start, - end, - activeFormats: currentActiveFormats = [] - } = record.current; - const collapsed = isCollapsed(record.current); - const { - ownerDocument - } = element; - const { - defaultView - } = ownerDocument; - // To do: ideally, we should look at visual position instead. - const { - direction - } = defaultView.getComputedStyle(element); - const reverseKey = direction === 'rtl' ? external_wp_keycodes_namespaceObject.RIGHT : external_wp_keycodes_namespaceObject.LEFT; - const isReverse = event.keyCode === reverseKey; - - // If the selection is collapsed and at the very start, do nothing if - // navigating backward. - // If the selection is collapsed and at the very end, do nothing if - // navigating forward. - if (collapsed && currentActiveFormats.length === 0) { - if (start === 0 && isReverse) { - return; - } - if (end === text.length && !isReverse) { - return; - } - } - - // If the selection is not collapsed, let the browser handle collapsing - // the selection for now. Later we could expand this logic to set - // boundary positions if needed. - if (!collapsed) { +/* harmony default export */ const format_boundaries = (props => element => { + function onKeyDown(event) { + const { + keyCode, + shiftKey, + altKey, + metaKey, + ctrlKey + } = event; + if ( + // Only override left and right keys without modifiers pressed. + shiftKey || altKey || metaKey || ctrlKey || keyCode !== external_wp_keycodes_namespaceObject.LEFT && keyCode !== external_wp_keycodes_namespaceObject.RIGHT) { + return; + } + const { + record, + applyRecord, + forceRender + } = props.current; + const { + text, + formats, + start, + end, + activeFormats: currentActiveFormats = [] + } = record.current; + const collapsed = isCollapsed(record.current); + const { + ownerDocument + } = element; + const { + defaultView + } = ownerDocument; + // To do: ideally, we should look at visual position instead. + const { + direction + } = defaultView.getComputedStyle(element); + const reverseKey = direction === 'rtl' ? external_wp_keycodes_namespaceObject.RIGHT : external_wp_keycodes_namespaceObject.LEFT; + const isReverse = event.keyCode === reverseKey; + + // If the selection is collapsed and at the very start, do nothing if + // navigating backward. + // If the selection is collapsed and at the very end, do nothing if + // navigating forward. + if (collapsed && currentActiveFormats.length === 0) { + if (start === 0 && isReverse) { return; } - const formatsBefore = formats[start - 1] || EMPTY_ACTIVE_FORMATS; - const formatsAfter = formats[start] || EMPTY_ACTIVE_FORMATS; - const destination = isReverse ? formatsBefore : formatsAfter; - const isIncreasing = currentActiveFormats.every((format, index) => format === destination[index]); - let newActiveFormatsLength = currentActiveFormats.length; - if (!isIncreasing) { - newActiveFormatsLength--; - } else if (newActiveFormatsLength < destination.length) { - newActiveFormatsLength++; - } - if (newActiveFormatsLength === currentActiveFormats.length) { - record.current._newActiveFormats = destination; + if (end === text.length && !isReverse) { return; } - event.preventDefault(); - const origin = isReverse ? formatsAfter : formatsBefore; - const source = isIncreasing ? destination : origin; - const newActiveFormats = source.slice(0, newActiveFormatsLength); - const newValue = { - ...record.current, - activeFormats: newActiveFormats - }; - record.current = newValue; - applyRecord(newValue); - forceRender(); } - element.addEventListener('keydown', onKeyDown); - return () => { - element.removeEventListener('keydown', onKeyDown); + + // If the selection is not collapsed, let the browser handle collapsing + // the selection for now. Later we could expand this logic to set + // boundary positions if needed. + if (!collapsed) { + return; + } + const formatsBefore = formats[start - 1] || EMPTY_ACTIVE_FORMATS; + const formatsAfter = formats[start] || EMPTY_ACTIVE_FORMATS; + const destination = isReverse ? formatsBefore : formatsAfter; + const isIncreasing = currentActiveFormats.every((format, index) => format === destination[index]); + let newActiveFormatsLength = currentActiveFormats.length; + if (!isIncreasing) { + newActiveFormatsLength--; + } else if (newActiveFormatsLength < destination.length) { + newActiveFormatsLength++; + } + if (newActiveFormatsLength === currentActiveFormats.length) { + record.current._newActiveFormats = destination; + return; + } + event.preventDefault(); + const origin = isReverse ? formatsAfter : formatsBefore; + const source = isIncreasing ? destination : origin; + const newActiveFormats = source.slice(0, newActiveFormatsLength); + const newValue = { + ...record.current, + activeFormats: newActiveFormats }; - }, []); -} + record.current = newValue; + applyRecord(newValue); + forceRender(); + } + element.addEventListener('keydown', onKeyDown); + return () => { + element.removeEventListener('keydown', onKeyDown); + }; +}); -;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-select-object.js +;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/delete.js /** * WordPress dependencies */ -function useSelectObject() { - return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { - function onClick(event) { - const { - target - } = event; - // If the child element has no text content, it must be an object. - if (target === element || target.textContent && target.isContentEditable) { - return; - } - const { - ownerDocument - } = target; - const { - defaultView - } = ownerDocument; - const selection = defaultView.getSelection(); - - // If it's already selected, do nothing and let default behavior - // happen. This means it's "click-through". - if (selection.containsNode(target)) return; - const range = ownerDocument.createRange(); - // If the target is within a non editable element, select the non - // editable element. - const nodeToSelect = target.isContentEditable ? target : target.closest('[contenteditable]'); - range.selectNode(nodeToSelect); - selection.removeAllRanges(); - selection.addRange(range); - event.preventDefault(); +/** + * Internal dependencies + */ + +/* harmony default export */ const event_listeners_delete = (props => element => { + function onKeyDown(event) { + const { + keyCode + } = event; + const { + createRecord, + handleChange + } = props.current; + if (event.defaultPrevented) { + return; } - function onFocusIn(event) { - // When there is incoming focus from a link, select the object. - if (event.relatedTarget && !element.contains(event.relatedTarget) && event.relatedTarget.tagName === 'A') { - onClick(event); - } + if (keyCode !== external_wp_keycodes_namespaceObject.DELETE && keyCode !== external_wp_keycodes_namespaceObject.BACKSPACE) { + return; } - element.addEventListener('click', onClick); - element.addEventListener('focusin', onFocusIn); - return () => { - element.removeEventListener('click', onClick); - element.removeEventListener('focusin', onFocusIn); - }; - }, []); -} + const currentValue = createRecord(); + const { + start, + end, + text + } = currentValue; + + // Always handle full content deletion ourselves. + if (start === 0 && end !== 0 && end === text.length) { + handleChange(remove_remove(currentValue)); + event.preventDefault(); + } + } + element.addEventListener('keydown', onKeyDown); + return () => { + element.removeEventListener('keydown', onKeyDown); + }; +}); ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/update-formats.js /** @@ -3583,13 +3338,7 @@ function updateFormats({ return value; } -;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-input-and-selection.js -/** - * WordPress dependencies - */ - - - +;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/input-and-selection.js /** * Internal dependencies */ @@ -3604,7 +3353,7 @@ function updateFormats({ * @type {Set} */ const INSERTION_INPUT_TYPES_TO_IGNORE = new Set(['insertParagraph', 'insertOrderedList', 'insertUnorderedList', 'insertHorizontalRule', 'insertLink']); -const use_input_and_selection_EMPTY_ACTIVE_FORMATS = []; +const input_and_selection_EMPTY_ACTIVE_FORMATS = []; const PLACEHOLDER_ATTR_NAME = 'data-rich-text-placeholder'; /** @@ -3628,210 +3377,204 @@ function fixPlaceholderSelection(defaultView) { } selection.collapseToStart(); } -function useInputAndSelection(props) { - const propsRef = (0,external_wp_element_namespaceObject.useRef)(props); - propsRef.current = props; - return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { - const { - ownerDocument - } = element; +/* harmony default export */ const input_and_selection = (props => element => { + const { + ownerDocument + } = element; + const { + defaultView + } = ownerDocument; + let isComposing = false; + function onInput(event) { + // Do not trigger a change if characters are being composed. Browsers + // will usually emit a final `input` event when the characters are + // composed. As of December 2019, Safari doesn't support + // nativeEvent.isComposing. + if (isComposing) { + return; + } + let inputType; + if (event) { + inputType = event.inputType; + } const { - defaultView - } = ownerDocument; - let isComposing = false; - function onInput(event) { - // Do not trigger a change if characters are being composed. - // Browsers will usually emit a final `input` event when the - // characters are composed. - // As of December 2019, Safari doesn't support - // nativeEvent.isComposing. - if (isComposing) { - return; - } - let inputType; - if (event) { - inputType = event.inputType; - } - const { - record, - applyRecord, - createRecord, - handleChange - } = propsRef.current; - - // The browser formatted something or tried to insert HTML. - // Overwrite it. It will be handled later by the format library if - // needed. - if (inputType && (inputType.indexOf('format') === 0 || INSERTION_INPUT_TYPES_TO_IGNORE.has(inputType))) { - applyRecord(record.current); - return; - } - const currentValue = createRecord(); - const { - start, - activeFormats: oldActiveFormats = [] - } = record.current; - - // Update the formats between the last and new caret position. - const change = updateFormats({ - value: currentValue, - start, - end: currentValue.start, - formats: oldActiveFormats - }); - handleChange(change); + record, + applyRecord, + createRecord, + handleChange + } = props.current; + + // The browser formatted something or tried to insert HTML. Overwrite + // it. It will be handled later by the format library if needed. + if (inputType && (inputType.indexOf('format') === 0 || INSERTION_INPUT_TYPES_TO_IGNORE.has(inputType))) { + applyRecord(record.current); + return; } + const currentValue = createRecord(); + const { + start, + activeFormats: oldActiveFormats = [] + } = record.current; - /** - * Syncs the selection to local state. A callback for the - * `selectionchange` event. - */ - function handleSelectionChange() { - const { - record, - applyRecord, - createRecord, - onSelectionChange - } = propsRef.current; - - // Check if the implementor disabled editing. `contentEditable` - // does disable input, but not text selection, so we must ignore - // selection changes. - if (element.contentEditable !== 'true') { - return; - } - - // Ensure the active element is the rich text element. - if (ownerDocument.activeElement !== element) { - // If it is not, we can stop listening for selection changes. - // We resume listening when the element is focused. - ownerDocument.removeEventListener('selectionchange', handleSelectionChange); - return; - } - - // In case of a keyboard event, ignore selection changes during - // composition. - if (isComposing) { - return; - } - const { - start, - end, - text - } = createRecord(); - const oldRecord = record.current; - - // Fallback mechanism for IE11, which doesn't support the input event. - // Any input results in a selection change. - if (text !== oldRecord.text) { - onInput(); - return; - } - if (start === oldRecord.start && end === oldRecord.end) { - // Sometimes the browser may set the selection on the placeholder - // element, in which case the caret is not visible. We need to set - // the caret before the placeholder if that's the case. - if (oldRecord.text.length === 0 && start === 0) { - fixPlaceholderSelection(defaultView); - } - return; - } - const newValue = { - ...oldRecord, - start, - end, - // _newActiveFormats may be set on arrow key navigation to control - // the right boundary position. If undefined, getActiveFormats will - // give the active formats according to the browser. - activeFormats: oldRecord._newActiveFormats, - _newActiveFormats: undefined - }; - const newActiveFormats = getActiveFormats(newValue, use_input_and_selection_EMPTY_ACTIVE_FORMATS); - - // Update the value with the new active formats. - newValue.activeFormats = newActiveFormats; + // Update the formats between the last and new caret position. + const change = updateFormats({ + value: currentValue, + start, + end: currentValue.start, + formats: oldActiveFormats + }); + handleChange(change); + } - // It is important that the internal value is updated first, - // otherwise the value will be wrong on render! - record.current = newValue; - applyRecord(newValue, { - domOnly: true - }); - onSelectionChange(start, end); + /** + * Syncs the selection to local state. A callback for the `selectionchange` + * event. + */ + function handleSelectionChange() { + const { + record, + applyRecord, + createRecord, + onSelectionChange + } = props.current; + + // Check if the implementor disabled editing. `contentEditable` does + // disable input, but not text selection, so we must ignore selection + // changes. + if (element.contentEditable !== 'true') { + return; } - function onCompositionStart() { - isComposing = true; - // Do not update the selection when characters are being composed as - // this rerenders the component and might destroy internal browser - // editing state. + + // Ensure the active element is the rich text element. + if (ownerDocument.activeElement !== element) { + // If it is not, we can stop listening for selection changes. We + // resume listening when the element is focused. ownerDocument.removeEventListener('selectionchange', handleSelectionChange); - // Remove the placeholder. Since the rich text value doesn't update - // during composition, the placeholder doesn't get removed. There's - // no need to re-add it, when the value is updated on compositionend - // it will be re-added when the value is empty. - element.querySelector(`[${PLACEHOLDER_ATTR_NAME}]`)?.remove(); + return; } - function onCompositionEnd() { - isComposing = false; - // Ensure the value is up-to-date for browsers that don't emit a final - // input event after composition. - onInput({ - inputType: 'insertText' - }); - // Tracking selection changes can be resumed. - ownerDocument.addEventListener('selectionchange', handleSelectionChange); + + // In case of a keyboard event, ignore selection changes during + // composition. + if (isComposing) { + return; } - function onFocus() { - const { - record, - isSelected, - onSelectionChange, - applyRecord - } = propsRef.current; - - // When the whole editor is editable, let writing flow handle - // selection. - if (element.parentElement.closest('[contenteditable="true"]')) { - return; - } - if (!isSelected) { - // We know for certain that on focus, the old selection is invalid. - // It will be recalculated on the next mouseup, keyup, or touchend - // event. - const index = undefined; - record.current = { - ...record.current, - start: index, - end: index, - activeFormats: use_input_and_selection_EMPTY_ACTIVE_FORMATS - }; - } else { - applyRecord(record.current, { - domOnly: true - }); + const { + start, + end, + text + } = createRecord(); + const oldRecord = record.current; + + // Fallback mechanism for IE11, which doesn't support the input event. + // Any input results in a selection change. + if (text !== oldRecord.text) { + onInput(); + return; + } + if (start === oldRecord.start && end === oldRecord.end) { + // Sometimes the browser may set the selection on the placeholder + // element, in which case the caret is not visible. We need to set + // the caret before the placeholder if that's the case. + if (oldRecord.text.length === 0 && start === 0) { + fixPlaceholderSelection(defaultView); } - onSelectionChange(record.current.start, record.current.end); - ownerDocument.addEventListener('selectionchange', handleSelectionChange); + return; } - element.addEventListener('input', onInput); - element.addEventListener('compositionstart', onCompositionStart); - element.addEventListener('compositionend', onCompositionEnd); - element.addEventListener('focus', onFocus); - return () => { - element.removeEventListener('input', onInput); - element.removeEventListener('compositionstart', onCompositionStart); - element.removeEventListener('compositionend', onCompositionEnd); - element.removeEventListener('focus', onFocus); + const newValue = { + ...oldRecord, + start, + end, + // _newActiveFormats may be set on arrow key navigation to control + // the right boundary position. If undefined, getActiveFormats will + // give the active formats according to the browser. + activeFormats: oldRecord._newActiveFormats, + _newActiveFormats: undefined }; - }, []); -} + const newActiveFormats = getActiveFormats(newValue, input_and_selection_EMPTY_ACTIVE_FORMATS); -;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-selection-change-compat.js -/** - * WordPress dependencies - */ + // Update the value with the new active formats. + newValue.activeFormats = newActiveFormats; + // It is important that the internal value is updated first, + // otherwise the value will be wrong on render! + record.current = newValue; + applyRecord(newValue, { + domOnly: true + }); + onSelectionChange(start, end); + } + function onCompositionStart() { + isComposing = true; + // Do not update the selection when characters are being composed as + // this rerenders the component and might destroy internal browser + // editing state. + ownerDocument.removeEventListener('selectionchange', handleSelectionChange); + // Remove the placeholder. Since the rich text value doesn't update + // during composition, the placeholder doesn't get removed. There's no + // need to re-add it, when the value is updated on compositionend it + // will be re-added when the value is empty. + element.querySelector(`[${PLACEHOLDER_ATTR_NAME}]`)?.remove(); + } + function onCompositionEnd() { + isComposing = false; + // Ensure the value is up-to-date for browsers that don't emit a final + // input event after composition. + onInput({ + inputType: 'insertText' + }); + // Tracking selection changes can be resumed. + ownerDocument.addEventListener('selectionchange', handleSelectionChange); + } + function onFocus() { + const { + record, + isSelected, + onSelectionChange, + applyRecord + } = props.current; + + // When the whole editor is editable, let writing flow handle + // selection. + if (element.parentElement.closest('[contenteditable="true"]')) { + return; + } + if (!isSelected) { + // We know for certain that on focus, the old selection is invalid. + // It will be recalculated on the next mouseup, keyup, or touchend + // event. + const index = undefined; + record.current = { + ...record.current, + start: index, + end: index, + activeFormats: input_and_selection_EMPTY_ACTIVE_FORMATS + }; + } else { + applyRecord(record.current, { + domOnly: true + }); + } + onSelectionChange(record.current.start, record.current.end); + + // There is no selection change event when the element is focused, so + // we need to manually trigger it. The selection is also not available + // yet in this call stack. + window.queueMicrotask(handleSelectionChange); + ownerDocument.addEventListener('selectionchange', handleSelectionChange); + } + element.addEventListener('input', onInput); + element.addEventListener('compositionstart', onCompositionStart); + element.addEventListener('compositionend', onCompositionEnd); + element.addEventListener('focus', onFocus); + return () => { + element.removeEventListener('input', onInput); + element.removeEventListener('compositionstart', onCompositionStart); + element.removeEventListener('compositionend', onCompositionEnd); + element.removeEventListener('focus', onFocus); + }; +}); +;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/selection-change-compat.js /** * Internal dependencies */ @@ -3842,97 +3585,73 @@ function useInputAndSelection(props) { * changing the selection by mouse or keyboard. This hook makes sure that, if we * detect no `selectionchange` or `input` event between the up and down events, * we fire a `selectionchange` event. - * - * @return {import('@wordpress/compose').RefEffect} A ref effect attaching the - * listeners. */ -function useSelectionChangeCompat() { - return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { - const { - ownerDocument - } = element; - const { - defaultView - } = ownerDocument; - const selection = defaultView?.getSelection(); - let range; - function getRange() { - return selection.rangeCount ? selection.getRangeAt(0) : null; - } - function onDown(event) { - const type = event.type === 'keydown' ? 'keyup' : 'pointerup'; - function onCancel() { - ownerDocument.removeEventListener(type, onUp); - ownerDocument.removeEventListener('selectionchange', onCancel); - ownerDocument.removeEventListener('input', onCancel); - } - function onUp() { - onCancel(); - if (isRangeEqual(range, getRange())) return; - ownerDocument.dispatchEvent(new Event('selectionchange')); +/* harmony default export */ const selection_change_compat = (() => element => { + const { + ownerDocument + } = element; + const { + defaultView + } = ownerDocument; + const selection = defaultView?.getSelection(); + let range; + function getRange() { + return selection.rangeCount ? selection.getRangeAt(0) : null; + } + function onDown(event) { + const type = event.type === 'keydown' ? 'keyup' : 'pointerup'; + function onCancel() { + ownerDocument.removeEventListener(type, onUp); + ownerDocument.removeEventListener('selectionchange', onCancel); + ownerDocument.removeEventListener('input', onCancel); + } + function onUp() { + onCancel(); + if (isRangeEqual(range, getRange())) { + return; } - ownerDocument.addEventListener(type, onUp); - ownerDocument.addEventListener('selectionchange', onCancel); - ownerDocument.addEventListener('input', onCancel); - range = getRange(); - } - element.addEventListener('pointerdown', onDown); - element.addEventListener('keydown', onDown); - return () => { - element.removeEventListener('pointerdown', onDown); - element.removeEventListener('keydown', onDown); - }; - }, []); -} + ownerDocument.dispatchEvent(new Event('selectionchange')); + } + ownerDocument.addEventListener(type, onUp); + ownerDocument.addEventListener('selectionchange', onCancel); + ownerDocument.addEventListener('input', onCancel); + range = getRange(); + } + element.addEventListener('pointerdown', onDown); + element.addEventListener('keydown', onDown); + return () => { + element.removeEventListener('pointerdown', onDown); + element.removeEventListener('keydown', onDown); + }; +}); -;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-delete.js +;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/index.js /** * WordPress dependencies */ - /** * Internal dependencies */ -function useDelete(props) { + + + + + +const allEventListeners = [copy_handler, select_object, format_boundaries, event_listeners_delete, input_and_selection, selection_change_compat]; +function useEventListeners(props) { const propsRef = (0,external_wp_element_namespaceObject.useRef)(props); propsRef.current = props; + const refEffects = (0,external_wp_element_namespaceObject.useMemo)(() => allEventListeners.map(refEffect => refEffect(propsRef)), [propsRef]); return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { - function onKeyDown(event) { - const { - keyCode - } = event; - const { - createRecord, - handleChange - } = propsRef.current; - if (event.defaultPrevented) { - return; - } - if (keyCode !== external_wp_keycodes_namespaceObject.DELETE && keyCode !== external_wp_keycodes_namespaceObject.BACKSPACE) { - return; - } - const currentValue = createRecord(); - const { - start, - end, - text - } = currentValue; - - // Always handle full content deletion ourselves. - if (start === 0 && end !== 0 && end === text.length) { - handleChange(remove_remove(currentValue)); - event.preventDefault(); - } - } - element.addEventListener('keydown', onKeyDown); + const cleanups = refEffects.map(effect => effect(element)); return () => { - element.removeEventListener('keydown', onKeyDown); + cleanups.forEach(cleanup => cleanup()); }; - }, []); + }, [refEffects]); } ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/index.js @@ -3952,11 +3671,6 @@ function useDelete(props) { - - - - - function useRichText({ value = '', selectionStart, @@ -4114,22 +3828,15 @@ function useRichText({ }, [hadSelectionUpdate.current]); const mergedRefs = (0,external_wp_compose_namespaceObject.useMergeRefs)([ref, useDefaultStyle(), useBoundaryStyle({ record - }), useCopyHandler({ - record - }), useSelectObject(), useFormatBoundaries({ - record, - applyRecord - }), useDelete({ - createRecord, - handleChange - }), useInputAndSelection({ + }), useEventListeners({ record, + handleChange, applyRecord, createRecord, - handleChange, isSelected, - onSelectionChange - }), useSelectionChangeCompat(), (0,external_wp_compose_namespaceObject.useRefEffect)(() => { + onSelectionChange, + forceRender + }), (0,external_wp_compose_namespaceObject.useRefEffect)(() => { applyFromProps(); didMount.current = true; }, [placeholder, ...__unstableDependencies])]); |