PluginProbe
Gutenberg / 21.2.0
Gutenberg v21.2.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 21.2.0, at build/dom/index.js

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