PluginProbe
Gutenberg / 12.7.1
Gutenberg v12.7.1
24.1.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 All 404 releases
gutenberg / build / dom / index.js

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

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