PluginProbe
Gutenberg / 18.9.0
Gutenberg v18.9.0
24.0.0 23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 All 403 releases
gutenberg / build / dom / index.js

index.js in Gutenberg 18.9.0, at build/dom/index.js

2,014 lines 60.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /******/ (() => { // webpackBootstrap
2 /******/ "use strict";
3 /******/ // The require scope
4 /******/ var __webpack_require__ = {};
5 /******/
6 /************************************************************************/
7 /******/ /* webpack/runtime/compat get default export */
8 /******/ (() => {
9 /******/ // getDefaultExport function for compatibility with non-harmony modules
10 /******/ __webpack_require__.n = (module) => {
11 /******/ var getter = module && module.__esModule ?
12 /******/ () => (module['default']) :
13 /******/ () => (module);
14 /******/ __webpack_require__.d(getter, { a: getter });
15 /******/ return getter;
16 /******/ };
17 /******/ })();
18 /******/
19 /******/ /* webpack/runtime/define property getters */
20 /******/ (() => {
21 /******/ // define getter functions for harmony exports
22 /******/ __webpack_require__.d = (exports, definition) => {
23 /******/ for(var key in definition) {
24 /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
25 /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
26 /******/ }
27 /******/ }
28 /******/ };
29 /******/ })();
30 /******/
31 /******/ /* webpack/runtime/hasOwnProperty shorthand */
32 /******/ (() => {
33 /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
34 /******/ })();
35 /******/
36 /******/ /* webpack/runtime/make namespace object */
37 /******/ (() => {
38 /******/ // define __esModule on exports
39 /******/ __webpack_require__.r = (exports) => {
40 /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
41 /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
42 /******/ }
43 /******/ Object.defineProperty(exports, '__esModule', { value: true });
44 /******/ };
45 /******/ })();
46 /******/
47 /************************************************************************/
48 var __webpack_exports__ = {};
49 // ESM COMPAT FLAG
50 __webpack_require__.r(__webpack_exports__);
51
52 // EXPORTS
53 __webpack_require__.d(__webpack_exports__, {
54 __unstableStripHTML: () => (/* reexport */ stripHTML),
55 computeCaretRect: () => (/* reexport */ computeCaretRect),
56 documentHasSelection: () => (/* reexport */ documentHasSelection),
57 documentHasTextSelection: () => (/* reexport */ documentHasTextSelection),
58 documentHasUncollapsedSelection: () => (/* reexport */ documentHasUncollapsedSelection),
59 focus: () => (/* binding */ build_module_focus),
60 getFilesFromDataTransfer: () => (/* reexport */ getFilesFromDataTransfer),
61 getOffsetParent: () => (/* reexport */ getOffsetParent),
62 getPhrasingContentSchema: () => (/* reexport */ getPhrasingContentSchema),
63 getRectangleFromRange: () => (/* reexport */ getRectangleFromRange),
64 getScrollContainer: () => (/* reexport */ getScrollContainer),
65 insertAfter: () => (/* reexport */ insertAfter),
66 isEmpty: () => (/* reexport */ isEmpty),
67 isEntirelySelected: () => (/* reexport */ isEntirelySelected),
68 isFormElement: () => (/* reexport */ isFormElement),
69 isHorizontalEdge: () => (/* reexport */ isHorizontalEdge),
70 isNumberInput: () => (/* reexport */ isNumberInput),
71 isPhrasingContent: () => (/* reexport */ isPhrasingContent),
72 isRTL: () => (/* reexport */ isRTL),
73 isTextContent: () => (/* reexport */ isTextContent),
74 isTextField: () => (/* reexport */ isTextField),
75 isVerticalEdge: () => (/* reexport */ isVerticalEdge),
76 placeCaretAtHorizontalEdge: () => (/* reexport */ placeCaretAtHorizontalEdge),
77 placeCaretAtVerticalEdge: () => (/* reexport */ placeCaretAtVerticalEdge),
78 remove: () => (/* reexport */ remove),
79 removeInvalidHTML: () => (/* reexport */ removeInvalidHTML),
80 replace: () => (/* reexport */ replace),
81 replaceTag: () => (/* reexport */ replaceTag),
82 safeHTML: () => (/* reexport */ safeHTML),
83 unwrap: () => (/* reexport */ unwrap),
84 wrap: () => (/* reexport */ wrap)
85 });
86
87 // NAMESPACE OBJECT: ./packages/dom/build-module/focusable.js
88 var focusable_namespaceObject = {};
89 __webpack_require__.r(focusable_namespaceObject);
90 __webpack_require__.d(focusable_namespaceObject, {
91 find: () => (find)
92 });
93
94 // NAMESPACE OBJECT: ./packages/dom/build-module/tabbable.js
95 var tabbable_namespaceObject = {};
96 __webpack_require__.r(tabbable_namespaceObject);
97 __webpack_require__.d(tabbable_namespaceObject, {
98 find: () => (tabbable_find),
99 findNext: () => (findNext),
100 findPrevious: () => (findPrevious),
101 isTabbableIndex: () => (isTabbableIndex)
102 });
103
104 ;// CONCATENATED MODULE: ./packages/dom/build-module/focusable.js
105 /**
106 * References:
107 *
108 * Focusable:
109 * - https://www.w3.org/TR/html5/editing.html#focus-management
110 *
111 * Sequential focus navigation:
112 * - https://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute
113 *
114 * Disabled elements:
115 * - https://www.w3.org/TR/html5/disabled-elements.html#disabled-elements
116 *
117 * getClientRects algorithm (requiring layout box):
118 * - https://www.w3.org/TR/cssom-view-1/#extension-to-the-element-interface
119 *
120 * AREA elements associated with an IMG:
121 * - https://w3c.github.io/html/editing.html#data-model
122 */
123
124 /**
125 * Returns a CSS selector used to query for focusable elements.
126 *
127 * @param {boolean} sequential If set, only query elements that are sequentially
128 * focusable. Non-interactive elements with a
129 * negative `tabindex` are focusable but not
130 * sequentially focusable.
131 * https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute
132 *
133 * @return {string} CSS selector.
134 */
135 function buildSelector(sequential) {
136 return [sequential ? '[tabindex]:not([tabindex^="-"])' : '[tabindex]', 'a[href]', 'button:not([disabled])', 'input:not([type="hidden"]):not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'iframe:not([tabindex^="-"])', 'object', 'embed', 'area[href]', '[contenteditable]:not([contenteditable=false])'].join(',');
137 }
138
139 /**
140 * Returns true if the specified element is visible (i.e. neither display: none
141 * nor visibility: hidden).
142 *
143 * @param {HTMLElement} element DOM element to test.
144 *
145 * @return {boolean} Whether element is visible.
146 */
147 function isVisible(element) {
148 return element.offsetWidth > 0 || element.offsetHeight > 0 || element.getClientRects().length > 0;
149 }
150
151 /**
152 * Returns true if the specified area element is a valid focusable element, or
153 * false otherwise. Area is only focusable if within a map where a named map
154 * referenced by an image somewhere in the document.
155 *
156 * @param {HTMLAreaElement} element DOM area element to test.
157 *
158 * @return {boolean} Whether area element is valid for focus.
159 */
160 function isValidFocusableArea(element) {
161 /** @type {HTMLMapElement | null} */
162 const map = element.closest('map[name]');
163 if (!map) {
164 return false;
165 }
166
167 /** @type {HTMLImageElement | null} */
168 const img = element.ownerDocument.querySelector('img[usemap="#' + map.name + '"]');
169 return !!img && isVisible(img);
170 }
171
172 /**
173 * Returns all focusable elements within a given context.
174 *
175 * @param {Element} context Element in which to search.
176 * @param {Object} options
177 * @param {boolean} [options.sequential] If set, only return elements that are
178 * sequentially focusable.
179 * Non-interactive elements with a
180 * negative `tabindex` are focusable but
181 * not sequentially focusable.
182 * https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute
183 *
184 * @return {HTMLElement[]} Focusable elements.
185 */
186 function find(context, {
187 sequential = false
188 } = {}) {
189 /** @type {NodeListOf<HTMLElement>} */
190 const elements = context.querySelectorAll(buildSelector(sequential));
191 return Array.from(elements).filter(element => {
192 if (!isVisible(element)) {
193 return false;
194 }
195 const {
196 nodeName
197 } = element;
198 if ('AREA' === nodeName) {
199 return isValidFocusableArea( /** @type {HTMLAreaElement} */element);
200 }
201 return true;
202 });
203 }
204
205 ;// CONCATENATED MODULE: ./packages/dom/build-module/tabbable.js
206 /**
207 * Internal dependencies
208 */
209
210
211 /**
212 * Returns the tab index of the given element. In contrast with the tabIndex
213 * property, this normalizes the default (0) to avoid browser inconsistencies,
214 * operating under the assumption that this function is only ever called with a
215 * focusable node.
216 *
217 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1190261
218 *
219 * @param {Element} element Element from which to retrieve.
220 *
221 * @return {number} Tab index of element (default 0).
222 */
223 function getTabIndex(element) {
224 const tabIndex = element.getAttribute('tabindex');
225 return tabIndex === null ? 0 : parseInt(tabIndex, 10);
226 }
227
228 /**
229 * Returns true if the specified element is tabbable, or false otherwise.
230 *
231 * @param {Element} element Element to test.
232 *
233 * @return {boolean} Whether element is tabbable.
234 */
235 function isTabbableIndex(element) {
236 return getTabIndex(element) !== -1;
237 }
238
239 /** @typedef {HTMLElement & { type?: string, checked?: boolean, name?: string }} MaybeHTMLInputElement */
240
241 /**
242 * Returns a stateful reducer function which constructs a filtered array of
243 * tabbable elements, where at most one radio input is selected for a given
244 * name, giving priority to checked input, falling back to the first
245 * encountered.
246 *
247 * @return {(acc: MaybeHTMLInputElement[], el: MaybeHTMLInputElement) => MaybeHTMLInputElement[]} Radio group collapse reducer.
248 */
249 function createStatefulCollapseRadioGroup() {
250 /** @type {Record<string, MaybeHTMLInputElement>} */
251 const CHOSEN_RADIO_BY_NAME = {};
252 return function collapseRadioGroup( /** @type {MaybeHTMLInputElement[]} */result, /** @type {MaybeHTMLInputElement} */element) {
253 const {
254 nodeName,
255 type,
256 checked,
257 name
258 } = element;
259
260 // For all non-radio tabbables, construct to array by concatenating.
261 if (nodeName !== 'INPUT' || type !== 'radio' || !name) {
262 return result.concat(element);
263 }
264 const hasChosen = CHOSEN_RADIO_BY_NAME.hasOwnProperty(name);
265
266 // Omit by skipping concatenation if the radio element is not chosen.
267 const isChosen = checked || !hasChosen;
268 if (!isChosen) {
269 return result;
270 }
271
272 // At this point, if there had been a chosen element, the current
273 // element is checked and should take priority. Retroactively remove
274 // the element which had previously been considered the chosen one.
275 if (hasChosen) {
276 const hadChosenElement = CHOSEN_RADIO_BY_NAME[name];
277 result = result.filter(e => e !== hadChosenElement);
278 }
279 CHOSEN_RADIO_BY_NAME[name] = element;
280 return result.concat(element);
281 };
282 }
283
284 /**
285 * An array map callback, returning an object with the element value and its
286 * array index location as properties. This is used to emulate a proper stable
287 * sort where equal tabIndex should be left in order of their occurrence in the
288 * document.
289 *
290 * @param {HTMLElement} element Element.
291 * @param {number} index Array index of element.
292 *
293 * @return {{ element: HTMLElement, index: number }} Mapped object with element, index.
294 */
295 function mapElementToObjectTabbable(element, index) {
296 return {
297 element,
298 index
299 };
300 }
301
302 /**
303 * An array map callback, returning an element of the given mapped object's
304 * element value.
305 *
306 * @param {{ element: HTMLElement }} object Mapped object with element.
307 *
308 * @return {HTMLElement} Mapped object element.
309 */
310 function mapObjectTabbableToElement(object) {
311 return object.element;
312 }
313
314 /**
315 * A sort comparator function used in comparing two objects of mapped elements.
316 *
317 * @see mapElementToObjectTabbable
318 *
319 * @param {{ element: HTMLElement, index: number }} a First object to compare.
320 * @param {{ element: HTMLElement, index: number }} b Second object to compare.
321 *
322 * @return {number} Comparator result.
323 */
324 function compareObjectTabbables(a, b) {
325 const aTabIndex = getTabIndex(a.element);
326 const bTabIndex = getTabIndex(b.element);
327 if (aTabIndex === bTabIndex) {
328 return a.index - b.index;
329 }
330 return aTabIndex - bTabIndex;
331 }
332
333 /**
334 * Givin focusable elements, filters out tabbable element.
335 *
336 * @param {HTMLElement[]} focusables Focusable elements to filter.
337 *
338 * @return {HTMLElement[]} Tabbable elements.
339 */
340 function filterTabbable(focusables) {
341 return focusables.filter(isTabbableIndex).map(mapElementToObjectTabbable).sort(compareObjectTabbables).map(mapObjectTabbableToElement).reduce(createStatefulCollapseRadioGroup(), []);
342 }
343
344 /**
345 * @param {Element} context
346 * @return {HTMLElement[]} Tabbable elements within the context.
347 */
348 function tabbable_find(context) {
349 return filterTabbable(find(context));
350 }
351
352 /**
353 * Given a focusable element, find the preceding tabbable element.
354 *
355 * @param {Element} element The focusable element before which to look. Defaults
356 * to the active element.
357 *
358 * @return {HTMLElement|undefined} Preceding tabbable element.
359 */
360 function findPrevious(element) {
361 return filterTabbable(find(element.ownerDocument.body)).reverse().find(focusable =>
362 // eslint-disable-next-line no-bitwise
363 element.compareDocumentPosition(focusable) & element.DOCUMENT_POSITION_PRECEDING);
364 }
365
366 /**
367 * Given a focusable element, find the next tabbable element.
368 *
369 * @param {Element} element The focusable element after which to look. Defaults
370 * to the active element.
371 *
372 * @return {HTMLElement|undefined} Next tabbable element.
373 */
374 function findNext(element) {
375 return filterTabbable(find(element.ownerDocument.body)).find(focusable =>
376 // eslint-disable-next-line no-bitwise
377 element.compareDocumentPosition(focusable) & element.DOCUMENT_POSITION_FOLLOWING);
378 }
379
380 ;// CONCATENATED MODULE: ./packages/dom/build-module/utils/assert-is-defined.js
381 function assertIsDefined(val, name) {
382 if (false) {}
383 }
384
385 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/get-rectangle-from-range.js
386 /**
387 * Internal dependencies
388 */
389
390
391 /**
392 * Get the rectangle of a given Range. Returns `null` if no suitable rectangle
393 * can be found.
394 *
395 * @param {Range} range The range.
396 *
397 * @return {DOMRect?} The rectangle.
398 */
399 function getRectangleFromRange(range) {
400 // For uncollapsed ranges, get the rectangle that bounds the contents of the
401 // range; this a rectangle enclosing the union of the bounding rectangles
402 // for all the elements in the range.
403 if (!range.collapsed) {
404 const rects = Array.from(range.getClientRects());
405
406 // If there's just a single rect, return it.
407 if (rects.length === 1) {
408 return rects[0];
409 }
410
411 // Ignore tiny selection at the edge of a range.
412 const filteredRects = rects.filter(({
413 width
414 }) => width > 1);
415
416 // If it's full of tiny selections, return browser default.
417 if (filteredRects.length === 0) {
418 return range.getBoundingClientRect();
419 }
420 if (filteredRects.length === 1) {
421 return filteredRects[0];
422 }
423 let {
424 top: furthestTop,
425 bottom: furthestBottom,
426 left: furthestLeft,
427 right: furthestRight
428 } = filteredRects[0];
429 for (const {
430 top,
431 bottom,
432 left,
433 right
434 } of filteredRects) {
435 if (top < furthestTop) {
436 furthestTop = top;
437 }
438 if (bottom > furthestBottom) {
439 furthestBottom = bottom;
440 }
441 if (left < furthestLeft) {
442 furthestLeft = left;
443 }
444 if (right > furthestRight) {
445 furthestRight = right;
446 }
447 }
448 return new window.DOMRect(furthestLeft, furthestTop, furthestRight - furthestLeft, furthestBottom - furthestTop);
449 }
450 const {
451 startContainer
452 } = range;
453 const {
454 ownerDocument
455 } = startContainer;
456
457 // Correct invalid "BR" ranges. The cannot contain any children.
458 if (startContainer.nodeName === 'BR') {
459 const {
460 parentNode
461 } = startContainer;
462 assertIsDefined(parentNode, 'parentNode');
463 const index = /** @type {Node[]} */Array.from(parentNode.childNodes).indexOf(startContainer);
464 assertIsDefined(ownerDocument, 'ownerDocument');
465 range = ownerDocument.createRange();
466 range.setStart(parentNode, index);
467 range.setEnd(parentNode, index);
468 }
469 const rects = range.getClientRects();
470
471 // If we have multiple rectangles for a collapsed range, there's no way to
472 // know which it is, so don't return anything.
473 if (rects.length > 1) {
474 return null;
475 }
476 let rect = rects[0];
477
478 // If the collapsed range starts (and therefore ends) at an element node,
479 // `getClientRects` can be empty in some browsers. This can be resolved
480 // by adding a temporary text node with zero-width space to the range.
481 //
482 // See: https://stackoverflow.com/a/6847328/995445
483 if (!rect || rect.height === 0) {
484 assertIsDefined(ownerDocument, 'ownerDocument');
485 const padNode = ownerDocument.createTextNode('\u200b');
486 // Do not modify the live range.
487 range = range.cloneRange();
488 range.insertNode(padNode);
489 rect = range.getClientRects()[0];
490 assertIsDefined(padNode.parentNode, 'padNode.parentNode');
491 padNode.parentNode.removeChild(padNode);
492 }
493 return rect;
494 }
495
496 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/compute-caret-rect.js
497 /**
498 * Internal dependencies
499 */
500
501
502
503 /**
504 * Get the rectangle for the selection in a container.
505 *
506 * @param {Window} win The window of the selection.
507 *
508 * @return {DOMRect | null} The rectangle.
509 */
510 function computeCaretRect(win) {
511 const selection = win.getSelection();
512 assertIsDefined(selection, 'selection');
513 const range = selection.rangeCount ? selection.getRangeAt(0) : null;
514 if (!range) {
515 return null;
516 }
517 return getRectangleFromRange(range);
518 }
519
520 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/document-has-text-selection.js
521 /**
522 * Internal dependencies
523 */
524
525
526 /**
527 * Check whether the current document has selected text. This applies to ranges
528 * of text in the document, and not selection inside `<input>` and `<textarea>`
529 * elements.
530 *
531 * See: https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection#Related_objects.
532 *
533 * @param {Document} doc The document to check.
534 *
535 * @return {boolean} True if there is selection, false if not.
536 */
537 function documentHasTextSelection(doc) {
538 assertIsDefined(doc.defaultView, 'doc.defaultView');
539 const selection = doc.defaultView.getSelection();
540 assertIsDefined(selection, 'selection');
541 const range = selection.rangeCount ? selection.getRangeAt(0) : null;
542 return !!range && !range.collapsed;
543 }
544
545 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-html-input-element.js
546 /* eslint-disable jsdoc/valid-types */
547 /**
548 * @param {Node} node
549 * @return {node is HTMLInputElement} Whether the node is an HTMLInputElement.
550 */
551 function isHTMLInputElement(node) {
552 /* eslint-enable jsdoc/valid-types */
553 return node?.nodeName === 'INPUT';
554 }
555
556 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-text-field.js
557 /**
558 * Internal dependencies
559 */
560
561
562 /* eslint-disable jsdoc/valid-types */
563 /**
564 * Check whether the given element is a text field, where text field is defined
565 * by the ability to select within the input, or that it is contenteditable.
566 *
567 * See: https://html.spec.whatwg.org/#textFieldSelection
568 *
569 * @param {Node} node The HTML element.
570 * @return {node is HTMLElement} True if the element is an text field, false if not.
571 */
572 function isTextField(node) {
573 /* eslint-enable jsdoc/valid-types */
574 const nonTextInputs = ['button', 'checkbox', 'hidden', 'file', 'radio', 'image', 'range', 'reset', 'submit', 'number', 'email', 'time'];
575 return isHTMLInputElement(node) && node.type && !nonTextInputs.includes(node.type) || node.nodeName === 'TEXTAREA' || /** @type {HTMLElement} */node.contentEditable === 'true';
576 }
577
578 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/input-field-has-uncollapsed-selection.js
579 /**
580 * Internal dependencies
581 */
582
583
584
585 /**
586 * Check whether the given input field or textarea contains a (uncollapsed)
587 * selection of text.
588 *
589 * CAVEAT: Only specific text-based HTML inputs support the selection APIs
590 * needed to determine whether they have a collapsed or uncollapsed selection.
591 * This function defaults to returning `true` when the selection cannot be
592 * inspected, such as with `<input type="time">`. The rationale is that this
593 * should cause the block editor to defer to the browser's native selection
594 * handling (e.g. copying and pasting), thereby reducing friction for the user.
595 *
596 * See: https://html.spec.whatwg.org/multipage/input.html#do-not-apply
597 *
598 * @param {Element} element The HTML element.
599 *
600 * @return {boolean} Whether the input/textareaa element has some "selection".
601 */
602 function inputFieldHasUncollapsedSelection(element) {
603 if (!isHTMLInputElement(element) && !isTextField(element)) {
604 return false;
605 }
606
607 // Safari throws a type error when trying to get `selectionStart` and
608 // `selectionEnd` on non-text <input> elements, so a try/catch construct is
609 // necessary.
610 try {
611 const {
612 selectionStart,
613 selectionEnd
614 } = /** @type {HTMLInputElement | HTMLTextAreaElement} */element;
615 return (
616 // `null` means the input type doesn't implement selection, thus we
617 // cannot determine whether the selection is collapsed, so we
618 // default to true.
619 selectionStart === null ||
620 // when not null, compare the two points
621 selectionStart !== selectionEnd
622 );
623 } catch (error) {
624 // This is Safari's way of saying that the input type doesn't implement
625 // selection, so we default to true.
626 return true;
627 }
628 }
629
630 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/document-has-uncollapsed-selection.js
631 /**
632 * Internal dependencies
633 */
634
635
636
637 /**
638 * Check whether the current document has any sort of (uncollapsed) selection.
639 * This includes ranges of text across elements and any selection inside
640 * textual `<input>` and `<textarea>` elements.
641 *
642 * @param {Document} doc The document to check.
643 *
644 * @return {boolean} Whether there is any recognizable text selection in the document.
645 */
646 function documentHasUncollapsedSelection(doc) {
647 return documentHasTextSelection(doc) || !!doc.activeElement && inputFieldHasUncollapsedSelection(doc.activeElement);
648 }
649
650 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/document-has-selection.js
651 /**
652 * Internal dependencies
653 */
654
655
656
657
658 /**
659 * Check whether the current document has a selection. This includes focus in
660 * input fields, textareas, and general rich-text selection.
661 *
662 * @param {Document} doc The document to check.
663 *
664 * @return {boolean} True if there is selection, false if not.
665 */
666 function documentHasSelection(doc) {
667 return !!doc.activeElement && (isHTMLInputElement(doc.activeElement) || isTextField(doc.activeElement) || documentHasTextSelection(doc));
668 }
669
670 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/get-computed-style.js
671 /**
672 * Internal dependencies
673 */
674
675
676 /* eslint-disable jsdoc/valid-types */
677 /**
678 * @param {Element} element
679 * @return {ReturnType<Window['getComputedStyle']>} The computed style for the element.
680 */
681 function getComputedStyle(element) {
682 /* eslint-enable jsdoc/valid-types */
683 assertIsDefined(element.ownerDocument.defaultView, 'element.ownerDocument.defaultView');
684 return element.ownerDocument.defaultView.getComputedStyle(element);
685 }
686
687 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/get-scroll-container.js
688 /**
689 * Internal dependencies
690 */
691
692
693 /**
694 * Given a DOM node, finds the closest scrollable container node or the node
695 * itself, if scrollable.
696 *
697 * @param {Element | null} node Node from which to start.
698 * @param {?string} direction Direction of scrollable container to search for ('vertical', 'horizontal', 'all').
699 * Defaults to 'vertical'.
700 * @return {Element | undefined} Scrollable container node, if found.
701 */
702 function getScrollContainer(node, direction = 'vertical') {
703 if (!node) {
704 return undefined;
705 }
706 if (direction === 'vertical' || direction === 'all') {
707 // Scrollable if scrollable height exceeds displayed...
708 if (node.scrollHeight > node.clientHeight) {
709 // ...except when overflow is defined to be hidden or visible
710 const {
711 overflowY
712 } = getComputedStyle(node);
713 if (/(auto|scroll)/.test(overflowY)) {
714 return node;
715 }
716 }
717 }
718 if (direction === 'horizontal' || direction === 'all') {
719 // Scrollable if scrollable width exceeds displayed...
720 if (node.scrollWidth > node.clientWidth) {
721 // ...except when overflow is defined to be hidden or visible
722 const {
723 overflowX
724 } = getComputedStyle(node);
725 if (/(auto|scroll)/.test(overflowX)) {
726 return node;
727 }
728 }
729 }
730 if (node.ownerDocument === node.parentNode) {
731 return node;
732 }
733
734 // Continue traversing.
735 return getScrollContainer( /** @type {Element} */node.parentNode, direction);
736 }
737
738 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/get-offset-parent.js
739 /**
740 * Internal dependencies
741 */
742
743
744 /**
745 * Returns the closest positioned element, or null under any of the conditions
746 * of the offsetParent specification. Unlike offsetParent, this function is not
747 * limited to HTMLElement and accepts any Node (e.g. Node.TEXT_NODE).
748 *
749 * @see https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent
750 *
751 * @param {Node} node Node from which to find offset parent.
752 *
753 * @return {Node | null} Offset parent.
754 */
755 function getOffsetParent(node) {
756 // Cannot retrieve computed style or offset parent only anything other than
757 // an element node, so find the closest element node.
758 let closestElement;
759 while (closestElement = /** @type {Node} */node.parentNode) {
760 if (closestElement.nodeType === closestElement.ELEMENT_NODE) {
761 break;
762 }
763 }
764 if (!closestElement) {
765 return null;
766 }
767
768 // If the closest element is already positioned, return it, as offsetParent
769 // does not otherwise consider the node itself.
770 if (getComputedStyle( /** @type {Element} */closestElement).position !== 'static') {
771 return closestElement;
772 }
773
774 // offsetParent is undocumented/draft.
775 return /** @type {Node & { offsetParent: Node }} */closestElement.offsetParent;
776 }
777
778 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-input-or-text-area.js
779 /* eslint-disable jsdoc/valid-types */
780 /**
781 * @param {Element} element
782 * @return {element is HTMLInputElement | HTMLTextAreaElement} Whether the element is an input or textarea
783 */
784 function isInputOrTextArea(element) {
785 /* eslint-enable jsdoc/valid-types */
786 return element.tagName === 'INPUT' || element.tagName === 'TEXTAREA';
787 }
788
789 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-entirely-selected.js
790 /**
791 * Internal dependencies
792 */
793
794
795
796 /**
797 * Check whether the contents of the element have been entirely selected.
798 * Returns true if there is no possibility of selection.
799 *
800 * @param {HTMLElement} element The element to check.
801 *
802 * @return {boolean} True if entirely selected, false if not.
803 */
804 function isEntirelySelected(element) {
805 if (isInputOrTextArea(element)) {
806 return element.selectionStart === 0 && element.value.length === element.selectionEnd;
807 }
808 if (!element.isContentEditable) {
809 return true;
810 }
811 const {
812 ownerDocument
813 } = element;
814 const {
815 defaultView
816 } = ownerDocument;
817 assertIsDefined(defaultView, 'defaultView');
818 const selection = defaultView.getSelection();
819 assertIsDefined(selection, 'selection');
820 const range = selection.rangeCount ? selection.getRangeAt(0) : null;
821 if (!range) {
822 return true;
823 }
824 const {
825 startContainer,
826 endContainer,
827 startOffset,
828 endOffset
829 } = range;
830 if (startContainer === element && endContainer === element && startOffset === 0 && endOffset === element.childNodes.length) {
831 return true;
832 }
833 const lastChild = element.lastChild;
834 assertIsDefined(lastChild, 'lastChild');
835 const endContainerContentLength = endContainer.nodeType === endContainer.TEXT_NODE ? /** @type {Text} */endContainer.data.length : endContainer.childNodes.length;
836 return isDeepChild(startContainer, element, 'firstChild') && isDeepChild(endContainer, element, 'lastChild') && startOffset === 0 && endOffset === endContainerContentLength;
837 }
838
839 /**
840 * Check whether the contents of the element have been entirely selected.
841 * Returns true if there is no possibility of selection.
842 *
843 * @param {HTMLElement|Node} query The element to check.
844 * @param {HTMLElement} container The container that we suspect "query" may be a first or last child of.
845 * @param {"firstChild"|"lastChild"} propName "firstChild" or "lastChild"
846 *
847 * @return {boolean} True if query is a deep first/last child of container, false otherwise.
848 */
849 function isDeepChild(query, container, propName) {
850 /** @type {HTMLElement | ChildNode | null} */
851 let candidate = container;
852 do {
853 if (query === candidate) {
854 return true;
855 }
856 candidate = candidate[propName];
857 } while (candidate);
858 return false;
859 }
860
861 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-form-element.js
862 /**
863 * Internal dependencies
864 */
865
866
867 /**
868 *
869 * Detects if element is a form element.
870 *
871 * @param {Element} element The element to check.
872 *
873 * @return {boolean} True if form element and false otherwise.
874 */
875 function isFormElement(element) {
876 if (!element) {
877 return false;
878 }
879 const {
880 tagName
881 } = element;
882 const checkForInputTextarea = isInputOrTextArea(element);
883 return checkForInputTextarea || tagName === 'BUTTON' || tagName === 'SELECT';
884 }
885
886 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-rtl.js
887 /**
888 * Internal dependencies
889 */
890
891
892 /**
893 * Whether the element's text direction is right-to-left.
894 *
895 * @param {Element} element The element to check.
896 *
897 * @return {boolean} True if rtl, false if ltr.
898 */
899 function isRTL(element) {
900 return getComputedStyle(element).direction === 'rtl';
901 }
902
903 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/get-range-height.js
904 /**
905 * Gets the height of the range without ignoring zero width rectangles, which
906 * some browsers ignore when creating a union.
907 *
908 * @param {Range} range The range to check.
909 * @return {number | undefined} Height of the range or undefined if the range has no client rectangles.
910 */
911 function getRangeHeight(range) {
912 const rects = Array.from(range.getClientRects());
913 if (!rects.length) {
914 return;
915 }
916 const highestTop = Math.min(...rects.map(({
917 top
918 }) => top));
919 const lowestBottom = Math.max(...rects.map(({
920 bottom
921 }) => bottom));
922 return lowestBottom - highestTop;
923 }
924
925 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-selection-forward.js
926 /**
927 * Internal dependencies
928 */
929
930
931 /**
932 * Returns true if the given selection object is in the forward direction, or
933 * false otherwise.
934 *
935 * @see https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition
936 *
937 * @param {Selection} selection Selection object to check.
938 *
939 * @return {boolean} Whether the selection is forward.
940 */
941 function isSelectionForward(selection) {
942 const {
943 anchorNode,
944 focusNode,
945 anchorOffset,
946 focusOffset
947 } = selection;
948 assertIsDefined(anchorNode, 'anchorNode');
949 assertIsDefined(focusNode, 'focusNode');
950 const position = anchorNode.compareDocumentPosition(focusNode);
951
952 // Disable reason: `Node#compareDocumentPosition` returns a bitmask value,
953 // so bitwise operators are intended.
954 /* eslint-disable no-bitwise */
955 // Compare whether anchor node precedes focus node. If focus node (where
956 // end of selection occurs) is after the anchor node, it is forward.
957 if (position & anchorNode.DOCUMENT_POSITION_PRECEDING) {
958 return false;
959 }
960 if (position & anchorNode.DOCUMENT_POSITION_FOLLOWING) {
961 return true;
962 }
963 /* eslint-enable no-bitwise */
964
965 // `compareDocumentPosition` returns 0 when passed the same node, in which
966 // case compare offsets.
967 if (position === 0) {
968 return anchorOffset <= focusOffset;
969 }
970
971 // This should never be reached, but return true as default case.
972 return true;
973 }
974
975 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/caret-range-from-point.js
976 /**
977 * Polyfill.
978 * Get a collapsed range for a given point.
979 *
980 * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/caretRangeFromPoint
981 *
982 * @param {DocumentMaybeWithCaretPositionFromPoint} doc The document of the range.
983 * @param {number} x Horizontal position within the current viewport.
984 * @param {number} y Vertical position within the current viewport.
985 *
986 * @return {Range | null} The best range for the given point.
987 */
988 function caretRangeFromPoint(doc, x, y) {
989 if (doc.caretRangeFromPoint) {
990 return doc.caretRangeFromPoint(x, y);
991 }
992 if (!doc.caretPositionFromPoint) {
993 return null;
994 }
995 const point = doc.caretPositionFromPoint(x, y);
996
997 // If x or y are negative, outside viewport, or there is no text entry node.
998 // https://developer.mozilla.org/en-US/docs/Web/API/Document/caretRangeFromPoint
999 if (!point) {
1000 return null;
1001 }
1002 const range = doc.createRange();
1003 range.setStart(point.offsetNode, point.offset);
1004 range.collapse(true);
1005 return range;
1006 }
1007
1008 /**
1009 * @typedef {{caretPositionFromPoint?: (x: number, y: number)=> CaretPosition | null} & Document } DocumentMaybeWithCaretPositionFromPoint
1010 * @typedef {{ readonly offset: number; readonly offsetNode: Node; getClientRect(): DOMRect | null; }} CaretPosition
1011 */
1012
1013 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/hidden-caret-range-from-point.js
1014 /**
1015 * Internal dependencies
1016 */
1017
1018
1019
1020 /**
1021 * Get a collapsed range for a given point.
1022 * Gives the container a temporary high z-index (above any UI).
1023 * This is preferred over getting the UI nodes and set styles there.
1024 *
1025 * @param {Document} doc The document of the range.
1026 * @param {number} x Horizontal position within the current viewport.
1027 * @param {number} y Vertical position within the current viewport.
1028 * @param {HTMLElement} container Container in which the range is expected to be found.
1029 *
1030 * @return {?Range} The best range for the given point.
1031 */
1032 function hiddenCaretRangeFromPoint(doc, x, y, container) {
1033 const originalZIndex = container.style.zIndex;
1034 const originalPosition = container.style.position;
1035 const {
1036 position = 'static'
1037 } = getComputedStyle(container);
1038
1039 // A z-index only works if the element position is not static.
1040 if (position === 'static') {
1041 container.style.position = 'relative';
1042 }
1043 container.style.zIndex = '10000';
1044 const range = caretRangeFromPoint(doc, x, y);
1045 container.style.zIndex = originalZIndex;
1046 container.style.position = originalPosition;
1047 return range;
1048 }
1049
1050 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/scroll-if-no-range.js
1051 /**
1052 * If no range range can be created or it is outside the container, the element
1053 * may be out of view, so scroll it into view and try again.
1054 *
1055 * @param {HTMLElement} container The container to scroll.
1056 * @param {boolean} alignToTop True to align to top, false to bottom.
1057 * @param {Function} callback The callback to create the range.
1058 *
1059 * @return {?Range} The range returned by the callback.
1060 */
1061 function scrollIfNoRange(container, alignToTop, callback) {
1062 let range = callback();
1063
1064 // If no range range can be created or it is outside the container, the
1065 // element may be out of view.
1066 if (!range || !range.startContainer || !container.contains(range.startContainer)) {
1067 container.scrollIntoView(alignToTop);
1068 range = callback();
1069 if (!range || !range.startContainer || !container.contains(range.startContainer)) {
1070 return null;
1071 }
1072 }
1073 return range;
1074 }
1075
1076 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-edge.js
1077 /**
1078 * Internal dependencies
1079 */
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089 /**
1090 * Check whether the selection is at the edge of the container. Checks for
1091 * horizontal position by default. Set `onlyVertical` to true to check only
1092 * vertically.
1093 *
1094 * @param {HTMLElement} container Focusable element.
1095 * @param {boolean} isReverse Set to true to check left, false to check right.
1096 * @param {boolean} [onlyVertical=false] Set to true to check only vertical position.
1097 *
1098 * @return {boolean} True if at the edge, false if not.
1099 */
1100 function isEdge(container, isReverse, onlyVertical = false) {
1101 if (isInputOrTextArea(container) && typeof container.selectionStart === 'number') {
1102 if (container.selectionStart !== container.selectionEnd) {
1103 return false;
1104 }
1105 if (isReverse) {
1106 return container.selectionStart === 0;
1107 }
1108 return container.value.length === container.selectionStart;
1109 }
1110 if (!container.isContentEditable) {
1111 return true;
1112 }
1113 const {
1114 ownerDocument
1115 } = container;
1116 const {
1117 defaultView
1118 } = ownerDocument;
1119 assertIsDefined(defaultView, 'defaultView');
1120 const selection = defaultView.getSelection();
1121 if (!selection || !selection.rangeCount) {
1122 return false;
1123 }
1124 const range = selection.getRangeAt(0);
1125 const collapsedRange = range.cloneRange();
1126 const isForward = isSelectionForward(selection);
1127 const isCollapsed = selection.isCollapsed;
1128
1129 // Collapse in direction of selection.
1130 if (!isCollapsed) {
1131 collapsedRange.collapse(!isForward);
1132 }
1133 const collapsedRangeRect = getRectangleFromRange(collapsedRange);
1134 const rangeRect = getRectangleFromRange(range);
1135 if (!collapsedRangeRect || !rangeRect) {
1136 return false;
1137 }
1138
1139 // Only consider the multiline selection at the edge if the direction is
1140 // towards the edge. The selection is multiline if it is taller than the
1141 // collapsed selection.
1142 const rangeHeight = getRangeHeight(range);
1143 if (!isCollapsed && rangeHeight && rangeHeight > collapsedRangeRect.height && isForward === isReverse) {
1144 return false;
1145 }
1146
1147 // In the case of RTL scripts, the horizontal edge is at the opposite side.
1148 const isReverseDir = isRTL(container) ? !isReverse : isReverse;
1149 const containerRect = container.getBoundingClientRect();
1150
1151 // To check if a selection is at the edge, we insert a test selection at the
1152 // edge of the container and check if the selections have the same vertical
1153 // or horizontal position. If they do, the selection is at the edge.
1154 // This method proves to be better than a DOM-based calculation for the
1155 // horizontal edge, since it ignores empty textnodes and a trailing line
1156 // break element. In other words, we need to check visual positioning, not
1157 // DOM positioning.
1158 // It also proves better than using the computed style for the vertical
1159 // edge, because we cannot know the padding and line height reliably in
1160 // pixels. `getComputedStyle` may return a value with different units.
1161 const x = isReverseDir ? containerRect.left + 1 : containerRect.right - 1;
1162 const y = isReverse ? containerRect.top + 1 : containerRect.bottom - 1;
1163 const testRange = scrollIfNoRange(container, isReverse, () => hiddenCaretRangeFromPoint(ownerDocument, x, y, container));
1164 if (!testRange) {
1165 return false;
1166 }
1167 const testRect = getRectangleFromRange(testRange);
1168 if (!testRect) {
1169 return false;
1170 }
1171 const verticalSide = isReverse ? 'top' : 'bottom';
1172 const horizontalSide = isReverseDir ? 'left' : 'right';
1173 const verticalDiff = testRect[verticalSide] - rangeRect[verticalSide];
1174 const horizontalDiff = testRect[horizontalSide] - collapsedRangeRect[horizontalSide];
1175
1176 // Allow the position to be 1px off.
1177 const hasVerticalDiff = Math.abs(verticalDiff) <= 1;
1178 const hasHorizontalDiff = Math.abs(horizontalDiff) <= 1;
1179 return onlyVertical ? hasVerticalDiff : hasVerticalDiff && hasHorizontalDiff;
1180 }
1181
1182 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-horizontal-edge.js
1183 /**
1184 * Internal dependencies
1185 */
1186
1187
1188 /**
1189 * Check whether the selection is horizontally at the edge of the container.
1190 *
1191 * @param {HTMLElement} container Focusable element.
1192 * @param {boolean} isReverse Set to true to check left, false for right.
1193 *
1194 * @return {boolean} True if at the horizontal edge, false if not.
1195 */
1196 function isHorizontalEdge(container, isReverse) {
1197 return isEdge(container, isReverse);
1198 }
1199
1200 ;// CONCATENATED MODULE: external ["wp","deprecated"]
1201 const external_wp_deprecated_namespaceObject = window["wp"]["deprecated"];
1202 var external_wp_deprecated_default = /*#__PURE__*/__webpack_require__.n(external_wp_deprecated_namespaceObject);
1203 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-number-input.js
1204 /**
1205 * WordPress dependencies
1206 */
1207
1208
1209 /**
1210 * Internal dependencies
1211 */
1212
1213
1214 /* eslint-disable jsdoc/valid-types */
1215 /**
1216 * Check whether the given element is an input field of type number.
1217 *
1218 * @param {Node} node The HTML node.
1219 *
1220 * @return {node is HTMLInputElement} True if the node is number input.
1221 */
1222 function isNumberInput(node) {
1223 external_wp_deprecated_default()('wp.dom.isNumberInput', {
1224 since: '6.1',
1225 version: '6.5'
1226 });
1227 /* eslint-enable jsdoc/valid-types */
1228 return isHTMLInputElement(node) && node.type === 'number' && !isNaN(node.valueAsNumber);
1229 }
1230
1231 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-vertical-edge.js
1232 /**
1233 * Internal dependencies
1234 */
1235
1236
1237 /**
1238 * Check whether the selection is vertically at the edge of the container.
1239 *
1240 * @param {HTMLElement} container Focusable element.
1241 * @param {boolean} isReverse Set to true to check top, false for bottom.
1242 *
1243 * @return {boolean} True if at the vertical edge, false if not.
1244 */
1245 function isVerticalEdge(container, isReverse) {
1246 return isEdge(container, isReverse, true);
1247 }
1248
1249 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/place-caret-at-edge.js
1250 /**
1251 * Internal dependencies
1252 */
1253
1254
1255
1256
1257
1258
1259 /**
1260 * Gets the range to place.
1261 *
1262 * @param {HTMLElement} container Focusable element.
1263 * @param {boolean} isReverse True for end, false for start.
1264 * @param {number|undefined} x X coordinate to vertically position.
1265 *
1266 * @return {Range|null} The range to place.
1267 */
1268 function getRange(container, isReverse, x) {
1269 const {
1270 ownerDocument
1271 } = container;
1272 // In the case of RTL scripts, the horizontal edge is at the opposite side.
1273 const isReverseDir = isRTL(container) ? !isReverse : isReverse;
1274 const containerRect = container.getBoundingClientRect();
1275 // When placing at the end (isReverse), find the closest range to the bottom
1276 // right corner. When placing at the start, to the top left corner.
1277 // Ensure x is defined and within the container's boundaries. When it's
1278 // exactly at the boundary, it's not considered within the boundaries.
1279 if (x === undefined) {
1280 x = isReverse ? containerRect.right - 1 : containerRect.left + 1;
1281 } else if (x <= containerRect.left) {
1282 x = containerRect.left + 1;
1283 } else if (x >= containerRect.right) {
1284 x = containerRect.right - 1;
1285 }
1286 const y = isReverseDir ? containerRect.bottom - 1 : containerRect.top + 1;
1287 return hiddenCaretRangeFromPoint(ownerDocument, x, y, container);
1288 }
1289
1290 /**
1291 * Places the caret at start or end of a given element.
1292 *
1293 * @param {HTMLElement} container Focusable element.
1294 * @param {boolean} isReverse True for end, false for start.
1295 * @param {number|undefined} x X coordinate to vertically position.
1296 */
1297 function placeCaretAtEdge(container, isReverse, x) {
1298 if (!container) {
1299 return;
1300 }
1301 container.focus();
1302 if (isInputOrTextArea(container)) {
1303 // The element may not support selection setting.
1304 if (typeof container.selectionStart !== 'number') {
1305 return;
1306 }
1307 if (isReverse) {
1308 container.selectionStart = container.value.length;
1309 container.selectionEnd = container.value.length;
1310 } else {
1311 container.selectionStart = 0;
1312 container.selectionEnd = 0;
1313 }
1314 return;
1315 }
1316 if (!container.isContentEditable) {
1317 return;
1318 }
1319 const range = scrollIfNoRange(container, isReverse, () => getRange(container, isReverse, x));
1320 if (!range) {
1321 return;
1322 }
1323 const {
1324 ownerDocument
1325 } = container;
1326 const {
1327 defaultView
1328 } = ownerDocument;
1329 assertIsDefined(defaultView, 'defaultView');
1330 const selection = defaultView.getSelection();
1331 assertIsDefined(selection, 'selection');
1332 selection.removeAllRanges();
1333 selection.addRange(range);
1334 }
1335
1336 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/place-caret-at-horizontal-edge.js
1337 /**
1338 * Internal dependencies
1339 */
1340
1341
1342 /**
1343 * Places the caret at start or end of a given element.
1344 *
1345 * @param {HTMLElement} container Focusable element.
1346 * @param {boolean} isReverse True for end, false for start.
1347 */
1348 function placeCaretAtHorizontalEdge(container, isReverse) {
1349 return placeCaretAtEdge(container, isReverse, undefined);
1350 }
1351
1352 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/place-caret-at-vertical-edge.js
1353 /**
1354 * Internal dependencies
1355 */
1356
1357
1358 /**
1359 * Places the caret at the top or bottom of a given element.
1360 *
1361 * @param {HTMLElement} container Focusable element.
1362 * @param {boolean} isReverse True for bottom, false for top.
1363 * @param {DOMRect} [rect] The rectangle to position the caret with.
1364 */
1365 function placeCaretAtVerticalEdge(container, isReverse, rect) {
1366 return placeCaretAtEdge(container, isReverse, rect?.left);
1367 }
1368
1369 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/insert-after.js
1370 /**
1371 * Internal dependencies
1372 */
1373
1374
1375 /**
1376 * Given two DOM nodes, inserts the former in the DOM as the next sibling of
1377 * the latter.
1378 *
1379 * @param {Node} newNode Node to be inserted.
1380 * @param {Node} referenceNode Node after which to perform the insertion.
1381 * @return {void}
1382 */
1383 function insertAfter(newNode, referenceNode) {
1384 assertIsDefined(referenceNode.parentNode, 'referenceNode.parentNode');
1385 referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
1386 }
1387
1388 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/remove.js
1389 /**
1390 * Internal dependencies
1391 */
1392
1393
1394 /**
1395 * Given a DOM node, removes it from the DOM.
1396 *
1397 * @param {Node} node Node to be removed.
1398 * @return {void}
1399 */
1400 function remove(node) {
1401 assertIsDefined(node.parentNode, 'node.parentNode');
1402 node.parentNode.removeChild(node);
1403 }
1404
1405 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/replace.js
1406 /**
1407 * Internal dependencies
1408 */
1409
1410
1411
1412
1413 /**
1414 * Given two DOM nodes, replaces the former with the latter in the DOM.
1415 *
1416 * @param {Element} processedNode Node to be removed.
1417 * @param {Element} newNode Node to be inserted in its place.
1418 * @return {void}
1419 */
1420 function replace(processedNode, newNode) {
1421 assertIsDefined(processedNode.parentNode, 'processedNode.parentNode');
1422 insertAfter(newNode, processedNode.parentNode);
1423 remove(processedNode);
1424 }
1425
1426 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/unwrap.js
1427 /**
1428 * Internal dependencies
1429 */
1430
1431
1432 /**
1433 * Unwrap the given node. This means any child nodes are moved to the parent.
1434 *
1435 * @param {Node} node The node to unwrap.
1436 *
1437 * @return {void}
1438 */
1439 function unwrap(node) {
1440 const parent = node.parentNode;
1441 assertIsDefined(parent, 'node.parentNode');
1442 while (node.firstChild) {
1443 parent.insertBefore(node.firstChild, node);
1444 }
1445 parent.removeChild(node);
1446 }
1447
1448 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/replace-tag.js
1449 /**
1450 * Internal dependencies
1451 */
1452
1453
1454 /**
1455 * Replaces the given node with a new node with the given tag name.
1456 *
1457 * @param {Element} node The node to replace
1458 * @param {string} tagName The new tag name.
1459 *
1460 * @return {Element} The new node.
1461 */
1462 function replaceTag(node, tagName) {
1463 const newNode = node.ownerDocument.createElement(tagName);
1464 while (node.firstChild) {
1465 newNode.appendChild(node.firstChild);
1466 }
1467 assertIsDefined(node.parentNode, 'node.parentNode');
1468 node.parentNode.replaceChild(newNode, node);
1469 return newNode;
1470 }
1471
1472 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/wrap.js
1473 /**
1474 * Internal dependencies
1475 */
1476
1477
1478 /**
1479 * Wraps the given node with a new node with the given tag name.
1480 *
1481 * @param {Element} newNode The node to insert.
1482 * @param {Element} referenceNode The node to wrap.
1483 */
1484 function wrap(newNode, referenceNode) {
1485 assertIsDefined(referenceNode.parentNode, 'referenceNode.parentNode');
1486 referenceNode.parentNode.insertBefore(newNode, referenceNode);
1487 newNode.appendChild(referenceNode);
1488 }
1489
1490 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/safe-html.js
1491 /**
1492 * Internal dependencies
1493 */
1494
1495
1496 /**
1497 * Strips scripts and on* attributes from HTML.
1498 *
1499 * @param {string} html HTML to sanitize.
1500 *
1501 * @return {string} The sanitized HTML.
1502 */
1503 function safeHTML(html) {
1504 const {
1505 body
1506 } = document.implementation.createHTMLDocument('');
1507 body.innerHTML = html;
1508 const elements = body.getElementsByTagName('*');
1509 let elementIndex = elements.length;
1510 while (elementIndex--) {
1511 const element = elements[elementIndex];
1512 if (element.tagName === 'SCRIPT') {
1513 remove(element);
1514 } else {
1515 let attributeIndex = element.attributes.length;
1516 while (attributeIndex--) {
1517 const {
1518 name: key
1519 } = element.attributes[attributeIndex];
1520 if (key.startsWith('on')) {
1521 element.removeAttribute(key);
1522 }
1523 }
1524 }
1525 }
1526 return body.innerHTML;
1527 }
1528
1529 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/strip-html.js
1530 /**
1531 * Internal dependencies
1532 */
1533
1534
1535 /**
1536 * Removes any HTML tags from the provided string.
1537 *
1538 * @param {string} html The string containing html.
1539 *
1540 * @return {string} The text content with any html removed.
1541 */
1542 function stripHTML(html) {
1543 // Remove any script tags or on* attributes otherwise their *contents* will be left
1544 // in place following removal of HTML tags.
1545 html = safeHTML(html);
1546 const doc = document.implementation.createHTMLDocument('');
1547 doc.body.innerHTML = html;
1548 return doc.body.textContent || '';
1549 }
1550
1551 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-empty.js
1552 /**
1553 * Recursively checks if an element is empty. An element is not empty if it
1554 * contains text or contains elements with attributes such as images.
1555 *
1556 * @param {Element} element The element to check.
1557 *
1558 * @return {boolean} Whether or not the element is empty.
1559 */
1560 function isEmpty(element) {
1561 switch (element.nodeType) {
1562 case element.TEXT_NODE:
1563 // We cannot use \s since it includes special spaces which we want
1564 // to preserve.
1565 return /^[ \f\n\r\t\v\u00a0]*$/.test(element.nodeValue || '');
1566 case element.ELEMENT_NODE:
1567 if (element.hasAttributes()) {
1568 return false;
1569 } else if (!element.hasChildNodes()) {
1570 return true;
1571 }
1572 return /** @type {Element[]} */Array.from(element.childNodes).every(isEmpty);
1573 default:
1574 return true;
1575 }
1576 }
1577
1578 ;// CONCATENATED MODULE: ./packages/dom/build-module/phrasing-content.js
1579 /**
1580 * All phrasing content elements.
1581 *
1582 * @see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#phrasing-content-0
1583 */
1584
1585 /**
1586 * @typedef {Record<string,SemanticElementDefinition>} ContentSchema
1587 */
1588
1589 /**
1590 * @typedef SemanticElementDefinition
1591 * @property {string[]} [attributes] Content attributes
1592 * @property {ContentSchema} [children] Content attributes
1593 */
1594
1595 /**
1596 * All text-level semantic elements.
1597 *
1598 * @see https://html.spec.whatwg.org/multipage/text-level-semantics.html
1599 *
1600 * @type {ContentSchema}
1601 */
1602 const textContentSchema = {
1603 strong: {},
1604 em: {},
1605 s: {},
1606 del: {},
1607 ins: {},
1608 a: {
1609 attributes: ['href', 'target', 'rel', 'id']
1610 },
1611 code: {},
1612 abbr: {
1613 attributes: ['title']
1614 },
1615 sub: {},
1616 sup: {},
1617 br: {},
1618 small: {},
1619 // To do: fix blockquote.
1620 // cite: {},
1621 q: {
1622 attributes: ['cite']
1623 },
1624 dfn: {
1625 attributes: ['title']
1626 },
1627 data: {
1628 attributes: ['value']
1629 },
1630 time: {
1631 attributes: ['datetime']
1632 },
1633 var: {},
1634 samp: {},
1635 kbd: {},
1636 i: {},
1637 b: {},
1638 u: {},
1639 mark: {},
1640 ruby: {},
1641 rt: {},
1642 rp: {},
1643 bdi: {
1644 attributes: ['dir']
1645 },
1646 bdo: {
1647 attributes: ['dir']
1648 },
1649 wbr: {},
1650 '#text': {}
1651 };
1652
1653 // Recursion is needed.
1654 // Possible: strong > em > strong.
1655 // Impossible: strong > strong.
1656 const excludedElements = ['#text', 'br'];
1657 Object.keys(textContentSchema).filter(element => !excludedElements.includes(element)).forEach(tag => {
1658 const {
1659 [tag]: removedTag,
1660 ...restSchema
1661 } = textContentSchema;
1662 textContentSchema[tag].children = restSchema;
1663 });
1664
1665 /**
1666 * Embedded content elements.
1667 *
1668 * @see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#embedded-content-0
1669 *
1670 * @type {ContentSchema}
1671 */
1672 const embeddedContentSchema = {
1673 audio: {
1674 attributes: ['src', 'preload', 'autoplay', 'mediagroup', 'loop', 'muted']
1675 },
1676 canvas: {
1677 attributes: ['width', 'height']
1678 },
1679 embed: {
1680 attributes: ['src', 'type', 'width', 'height']
1681 },
1682 img: {
1683 attributes: ['alt', 'src', 'srcset', 'usemap', 'ismap', 'width', 'height']
1684 },
1685 object: {
1686 attributes: ['data', 'type', 'name', 'usemap', 'form', 'width', 'height']
1687 },
1688 video: {
1689 attributes: ['src', 'poster', 'preload', 'playsinline', 'autoplay', 'mediagroup', 'loop', 'muted', 'controls', 'width', 'height']
1690 }
1691 };
1692
1693 /**
1694 * Phrasing content elements.
1695 *
1696 * @see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#phrasing-content-0
1697 */
1698 const phrasingContentSchema = {
1699 ...textContentSchema,
1700 ...embeddedContentSchema
1701 };
1702
1703 /**
1704 * Get schema of possible paths for phrasing content.
1705 *
1706 * @see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content
1707 *
1708 * @param {string} [context] Set to "paste" to exclude invisible elements and
1709 * sensitive data.
1710 *
1711 * @return {Partial<ContentSchema>} Schema.
1712 */
1713 function getPhrasingContentSchema(context) {
1714 if (context !== 'paste') {
1715 return phrasingContentSchema;
1716 }
1717
1718 /**
1719 * @type {Partial<ContentSchema>}
1720 */
1721 const {
1722 u,
1723 // Used to mark misspelling. Shouldn't be pasted.
1724 abbr,
1725 // Invisible.
1726 data,
1727 // Invisible.
1728 time,
1729 // Invisible.
1730 wbr,
1731 // Invisible.
1732 bdi,
1733 // Invisible.
1734 bdo,
1735 // Invisible.
1736 ...remainingContentSchema
1737 } = {
1738 ...phrasingContentSchema,
1739 // We shouldn't paste potentially sensitive information which is not
1740 // visible to the user when pasted, so strip the attributes.
1741 ins: {
1742 children: phrasingContentSchema.ins.children
1743 },
1744 del: {
1745 children: phrasingContentSchema.del.children
1746 }
1747 };
1748 return remainingContentSchema;
1749 }
1750
1751 /**
1752 * Find out whether or not the given node is phrasing content.
1753 *
1754 * @see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content
1755 *
1756 * @param {Node} node The node to test.
1757 *
1758 * @return {boolean} True if phrasing content, false if not.
1759 */
1760 function isPhrasingContent(node) {
1761 const tag = node.nodeName.toLowerCase();
1762 return getPhrasingContentSchema().hasOwnProperty(tag) || tag === 'span';
1763 }
1764
1765 /**
1766 * @param {Node} node
1767 * @return {boolean} Node is text content
1768 */
1769 function isTextContent(node) {
1770 const tag = node.nodeName.toLowerCase();
1771 return textContentSchema.hasOwnProperty(tag) || tag === 'span';
1772 }
1773
1774 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/is-element.js
1775 /* eslint-disable jsdoc/valid-types */
1776 /**
1777 * @param {Node | null | undefined} node
1778 * @return {node is Element} True if node is an Element node
1779 */
1780 function isElement(node) {
1781 /* eslint-enable jsdoc/valid-types */
1782 return !!node && node.nodeType === node.ELEMENT_NODE;
1783 }
1784
1785 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/clean-node-list.js
1786 /**
1787 * Internal dependencies
1788 */
1789
1790
1791
1792
1793
1794
1795 const noop = () => {};
1796
1797 /* eslint-disable jsdoc/valid-types */
1798 /**
1799 * @typedef SchemaItem
1800 * @property {string[]} [attributes] Attributes.
1801 * @property {(string | RegExp)[]} [classes] Classnames or RegExp to test against.
1802 * @property {'*' | { [tag: string]: SchemaItem }} [children] Child schemas.
1803 * @property {string[]} [require] Selectors to test required children against. Leave empty or undefined if there are no requirements.
1804 * @property {boolean} allowEmpty Whether to allow nodes without children.
1805 * @property {(node: Node) => boolean} [isMatch] Function to test whether a node is a match. If left undefined any node will be assumed to match.
1806 */
1807
1808 /** @typedef {{ [tag: string]: SchemaItem }} Schema */
1809 /* eslint-enable jsdoc/valid-types */
1810
1811 /**
1812 * Given a schema, unwraps or removes nodes, attributes and classes on a node
1813 * list.
1814 *
1815 * @param {NodeList} nodeList The nodeList to filter.
1816 * @param {Document} doc The document of the nodeList.
1817 * @param {Schema} schema An array of functions that can mutate with the provided node.
1818 * @param {boolean} inline Whether to clean for inline mode.
1819 */
1820 function cleanNodeList(nodeList, doc, schema, inline) {
1821 Array.from(nodeList).forEach(( /** @type {Node & { nextElementSibling?: unknown }} */node) => {
1822 const tag = node.nodeName.toLowerCase();
1823
1824 // It's a valid child, if the tag exists in the schema without an isMatch
1825 // function, or with an isMatch function that matches the node.
1826 if (schema.hasOwnProperty(tag) && (!schema[tag].isMatch || schema[tag].isMatch?.(node))) {
1827 if (isElement(node)) {
1828 const {
1829 attributes = [],
1830 classes = [],
1831 children,
1832 require = [],
1833 allowEmpty
1834 } = schema[tag];
1835
1836 // If the node is empty and it's supposed to have children,
1837 // remove the node.
1838 if (children && !allowEmpty && isEmpty(node)) {
1839 remove(node);
1840 return;
1841 }
1842 if (node.hasAttributes()) {
1843 // Strip invalid attributes.
1844 Array.from(node.attributes).forEach(({
1845 name
1846 }) => {
1847 if (name !== 'class' && !attributes.includes(name)) {
1848 node.removeAttribute(name);
1849 }
1850 });
1851
1852 // Strip invalid classes.
1853 // In jsdom-jscore, 'node.classList' can be undefined.
1854 // TODO: Explore patching this in jsdom-jscore.
1855 if (node.classList && node.classList.length) {
1856 const mattchers = classes.map(item => {
1857 if (typeof item === 'string') {
1858 return ( /** @type {string} */className) => className === item;
1859 } else if (item instanceof RegExp) {
1860 return ( /** @type {string} */className) => item.test(className);
1861 }
1862 return noop;
1863 });
1864 Array.from(node.classList).forEach(name => {
1865 if (!mattchers.some(isMatch => isMatch(name))) {
1866 node.classList.remove(name);
1867 }
1868 });
1869 if (!node.classList.length) {
1870 node.removeAttribute('class');
1871 }
1872 }
1873 }
1874 if (node.hasChildNodes()) {
1875 // Do not filter any content.
1876 if (children === '*') {
1877 return;
1878 }
1879
1880 // Continue if the node is supposed to have children.
1881 if (children) {
1882 // If a parent requires certain children, but it does
1883 // not have them, drop the parent and continue.
1884 if (require.length && !node.querySelector(require.join(','))) {
1885 cleanNodeList(node.childNodes, doc, schema, inline);
1886 unwrap(node);
1887 // If the node is at the top, phrasing content, and
1888 // contains children that are block content, unwrap
1889 // the node because it is invalid.
1890 } else if (node.parentNode && node.parentNode.nodeName === 'BODY' && isPhrasingContent(node)) {
1891 cleanNodeList(node.childNodes, doc, schema, inline);
1892 if (Array.from(node.childNodes).some(child => !isPhrasingContent(child))) {
1893 unwrap(node);
1894 }
1895 } else {
1896 cleanNodeList(node.childNodes, doc, children, inline);
1897 }
1898 // Remove children if the node is not supposed to have any.
1899 } else {
1900 while (node.firstChild) {
1901 remove(node.firstChild);
1902 }
1903 }
1904 }
1905 }
1906 // Invalid child. Continue with schema at the same place and unwrap.
1907 } else {
1908 cleanNodeList(node.childNodes, doc, schema, inline);
1909
1910 // For inline mode, insert a line break when unwrapping nodes that
1911 // are not phrasing content.
1912 if (inline && !isPhrasingContent(node) && node.nextElementSibling) {
1913 insertAfter(doc.createElement('br'), node);
1914 }
1915 unwrap(node);
1916 }
1917 });
1918 }
1919
1920 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/remove-invalid-html.js
1921 /**
1922 * Internal dependencies
1923 */
1924
1925
1926 /**
1927 * Given a schema, unwraps or removes nodes, attributes and classes on HTML.
1928 *
1929 * @param {string} HTML The HTML to clean up.
1930 * @param {import('./clean-node-list').Schema} schema Schema for the HTML.
1931 * @param {boolean} inline Whether to clean for inline mode.
1932 *
1933 * @return {string} The cleaned up HTML.
1934 */
1935 function removeInvalidHTML(HTML, schema, inline) {
1936 const doc = document.implementation.createHTMLDocument('');
1937 doc.body.innerHTML = HTML;
1938 cleanNodeList(doc.body.childNodes, doc, schema, inline);
1939 return doc.body.innerHTML;
1940 }
1941
1942 ;// CONCATENATED MODULE: ./packages/dom/build-module/dom/index.js
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970 ;// CONCATENATED MODULE: ./packages/dom/build-module/data-transfer.js
1971 /**
1972 * Gets all files from a DataTransfer object.
1973 *
1974 * @param {DataTransfer} dataTransfer DataTransfer object to inspect.
1975 *
1976 * @return {File[]} An array containing all files.
1977 */
1978 function getFilesFromDataTransfer(dataTransfer) {
1979 const files = Array.from(dataTransfer.files);
1980 Array.from(dataTransfer.items).forEach(item => {
1981 const file = item.getAsFile();
1982 if (file && !files.find(({
1983 name,
1984 type,
1985 size
1986 }) => name === file.name && type === file.type && size === file.size)) {
1987 files.push(file);
1988 }
1989 });
1990 return files;
1991 }
1992
1993 ;// CONCATENATED MODULE: ./packages/dom/build-module/index.js
1994 /**
1995 * Internal dependencies
1996 */
1997
1998
1999
2000 /**
2001 * Object grouping `focusable` and `tabbable` utils
2002 * under the keys with the same name.
2003 */
2004 const build_module_focus = {
2005 focusable: focusable_namespaceObject,
2006 tabbable: tabbable_namespaceObject
2007 };
2008
2009
2010
2011
2012 (window.wp = window.wp || {}).dom = __webpack_exports__;
2013 /******/ })()
2014 ;