609 function getRectangleFromRange(range) { |
460 function getRectangleFromRange(range) { |
610 // For uncollapsed ranges, get the rectangle that bounds the contents of the |
461 // For uncollapsed ranges, get the rectangle that bounds the contents of the |
611 // range; this a rectangle enclosing the union of the bounding rectangles |
462 // range; this a rectangle enclosing the union of the bounding rectangles |
612 // for all the elements in the range. |
463 // for all the elements in the range. |
613 if (!range.collapsed) { |
464 if (!range.collapsed) { |
614 return range.getBoundingClientRect(); |
465 const rects = Array.from(range.getClientRects()); // If there's just a single rect, return it. |
615 } |
466 |
616 |
467 if (rects.length === 1) { |
617 var _range = range, |
468 return rects[0]; |
618 startContainer = _range.startContainer; // Correct invalid "BR" ranges. The cannot contain any children. |
469 } // Ignore tiny selection at the edge of a range. |
|
470 |
|
471 |
|
472 const filteredRects = rects.filter(({ |
|
473 width |
|
474 }) => width > 1); // If it's full of tiny selections, return browser default. |
|
475 |
|
476 if (filteredRects.length === 0) { |
|
477 return range.getBoundingClientRect(); |
|
478 } |
|
479 |
|
480 if (filteredRects.length === 1) { |
|
481 return filteredRects[0]; |
|
482 } |
|
483 |
|
484 let { |
|
485 top: furthestTop, |
|
486 bottom: furthestBottom, |
|
487 left: furthestLeft, |
|
488 right: furthestRight |
|
489 } = filteredRects[0]; |
|
490 |
|
491 for (const { |
|
492 top, |
|
493 bottom, |
|
494 left, |
|
495 right |
|
496 } of filteredRects) { |
|
497 if (top < furthestTop) furthestTop = top; |
|
498 if (bottom > furthestBottom) furthestBottom = bottom; |
|
499 if (left < furthestLeft) furthestLeft = left; |
|
500 if (right > furthestRight) furthestRight = right; |
|
501 } |
|
502 |
|
503 return new window.DOMRect(furthestLeft, furthestTop, furthestRight - furthestLeft, furthestBottom - furthestTop); |
|
504 } |
|
505 |
|
506 const { |
|
507 startContainer |
|
508 } = range; |
|
509 const { |
|
510 ownerDocument |
|
511 } = startContainer; // Correct invalid "BR" ranges. The cannot contain any children. |
619 |
512 |
620 if (startContainer.nodeName === 'BR') { |
513 if (startContainer.nodeName === 'BR') { |
621 var parentNode = startContainer.parentNode; |
514 const { |
622 var index = Array.from(parentNode.childNodes).indexOf(startContainer); |
515 parentNode |
623 range = document.createRange(); |
516 } = startContainer; |
|
517 assertIsDefined(parentNode, 'parentNode'); |
|
518 const index = |
|
519 /** @type {Node[]} */ |
|
520 Array.from(parentNode.childNodes).indexOf(startContainer); |
|
521 assertIsDefined(ownerDocument, 'ownerDocument'); |
|
522 range = ownerDocument.createRange(); |
624 range.setStart(parentNode, index); |
523 range.setStart(parentNode, index); |
625 range.setEnd(parentNode, index); |
524 range.setEnd(parentNode, index); |
626 } |
525 } |
627 |
526 |
628 var rect = range.getClientRects()[0]; // If the collapsed range starts (and therefore ends) at an element node, |
527 let rect = range.getClientRects()[0]; // If the collapsed range starts (and therefore ends) at an element node, |
629 // `getClientRects` can be empty in some browsers. This can be resolved |
528 // `getClientRects` can be empty in some browsers. This can be resolved |
630 // by adding a temporary text node with zero-width space to the range. |
529 // by adding a temporary text node with zero-width space to the range. |
631 // |
530 // |
632 // See: https://stackoverflow.com/a/6847328/995445 |
531 // See: https://stackoverflow.com/a/6847328/995445 |
633 |
532 |
634 if (!rect) { |
533 if (!rect) { |
635 var padNode = document.createTextNode("\u200B"); // Do not modify the live range. |
534 assertIsDefined(ownerDocument, 'ownerDocument'); |
|
535 const padNode = ownerDocument.createTextNode('\u200b'); // Do not modify the live range. |
636 |
536 |
637 range = range.cloneRange(); |
537 range = range.cloneRange(); |
638 range.insertNode(padNode); |
538 range.insertNode(padNode); |
639 rect = range.getClientRects()[0]; |
539 rect = range.getClientRects()[0]; |
|
540 assertIsDefined(padNode.parentNode, 'padNode.parentNode'); |
640 padNode.parentNode.removeChild(padNode); |
541 padNode.parentNode.removeChild(padNode); |
641 } |
542 } |
642 |
543 |
643 return rect; |
544 return rect; |
644 } |
545 } |
|
546 |
|
547 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/compute-caret-rect.js |
|
548 /** |
|
549 * Internal dependencies |
|
550 */ |
|
551 |
|
552 |
645 /** |
553 /** |
646 * Get the rectangle for the selection in a container. |
554 * Get the rectangle for the selection in a container. |
647 * |
555 * |
648 * @return {?DOMRect} The rectangle. |
556 * @param {Window} win The window of the selection. |
649 */ |
557 * |
650 |
558 * @return {DOMRect | null} The rectangle. |
651 function computeCaretRect() { |
559 */ |
652 var selection = window.getSelection(); |
560 |
653 var range = selection.rangeCount ? selection.getRangeAt(0) : null; |
561 function computeCaretRect(win) { |
|
562 const selection = win.getSelection(); |
|
563 assertIsDefined(selection, 'selection'); |
|
564 const range = selection.rangeCount ? selection.getRangeAt(0) : null; |
654 |
565 |
655 if (!range) { |
566 if (!range) { |
656 return; |
567 return null; |
657 } |
568 } |
658 |
569 |
659 return getRectangleFromRange(range); |
570 return getRectangleFromRange(range); |
660 } |
571 } |
661 /** |
572 |
662 * Places the caret at start or end of a given element. |
573 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/document-has-text-selection.js |
663 * |
574 /** |
664 * @param {Element} container Focusable element. |
575 * Internal dependencies |
665 * @param {boolean} isReverse True for end, false for start. |
576 */ |
666 */ |
577 |
667 |
|
668 function placeCaretAtHorizontalEdge(container, isReverse) { |
|
669 if (!container) { |
|
670 return; |
|
671 } |
|
672 |
|
673 if (Object(external_this_lodash_["includes"])(['INPUT', 'TEXTAREA'], container.tagName)) { |
|
674 container.focus(); |
|
675 |
|
676 if (isReverse) { |
|
677 container.selectionStart = container.value.length; |
|
678 container.selectionEnd = container.value.length; |
|
679 } else { |
|
680 container.selectionStart = 0; |
|
681 container.selectionEnd = 0; |
|
682 } |
|
683 |
|
684 return; |
|
685 } |
|
686 |
|
687 container.focus(); |
|
688 |
|
689 if (!container.isContentEditable) { |
|
690 return; |
|
691 } // Select on extent child of the container, not the container itself. This |
|
692 // avoids the selection always being `endOffset` of 1 when placed at end, |
|
693 // where `startContainer`, `endContainer` would always be container itself. |
|
694 |
|
695 |
|
696 var rangeTarget = container[isReverse ? 'lastChild' : 'firstChild']; // If no range target, it implies that the container is empty. Focusing is |
|
697 // sufficient for caret to be placed correctly. |
|
698 |
|
699 if (!rangeTarget) { |
|
700 return; |
|
701 } |
|
702 |
|
703 var selection = window.getSelection(); |
|
704 var range = document.createRange(); |
|
705 range.selectNodeContents(rangeTarget); |
|
706 range.collapse(!isReverse); |
|
707 selection.removeAllRanges(); |
|
708 selection.addRange(range); |
|
709 } |
|
710 /** |
|
711 * Polyfill. |
|
712 * Get a collapsed range for a given point. |
|
713 * |
|
714 * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/caretRangeFromPoint |
|
715 * |
|
716 * @param {Document} doc The document of the range. |
|
717 * @param {number} x Horizontal position within the current viewport. |
|
718 * @param {number} y Vertical position within the current viewport. |
|
719 * |
|
720 * @return {?Range} The best range for the given point. |
|
721 */ |
|
722 |
|
723 function caretRangeFromPoint(doc, x, y) { |
|
724 if (doc.caretRangeFromPoint) { |
|
725 return doc.caretRangeFromPoint(x, y); |
|
726 } |
|
727 |
|
728 if (!doc.caretPositionFromPoint) { |
|
729 return null; |
|
730 } |
|
731 |
|
732 var point = doc.caretPositionFromPoint(x, y); // If x or y are negative, outside viewport, or there is no text entry node. |
|
733 // https://developer.mozilla.org/en-US/docs/Web/API/Document/caretRangeFromPoint |
|
734 |
|
735 if (!point) { |
|
736 return null; |
|
737 } |
|
738 |
|
739 var range = doc.createRange(); |
|
740 range.setStart(point.offsetNode, point.offset); |
|
741 range.collapse(true); |
|
742 return range; |
|
743 } |
|
744 /** |
|
745 * Get a collapsed range for a given point. |
|
746 * Gives the container a temporary high z-index (above any UI). |
|
747 * This is preferred over getting the UI nodes and set styles there. |
|
748 * |
|
749 * @param {Document} doc The document of the range. |
|
750 * @param {number} x Horizontal position within the current viewport. |
|
751 * @param {number} y Vertical position within the current viewport. |
|
752 * @param {Element} container Container in which the range is expected to be found. |
|
753 * |
|
754 * @return {?Range} The best range for the given point. |
|
755 */ |
|
756 |
|
757 |
|
758 function hiddenCaretRangeFromPoint(doc, x, y, container) { |
|
759 var originalZIndex = container.style.zIndex; |
|
760 var originalPosition = container.style.position; // A z-index only works if the element position is not static. |
|
761 |
|
762 container.style.zIndex = '10000'; |
|
763 container.style.position = 'relative'; |
|
764 var range = caretRangeFromPoint(doc, x, y); |
|
765 container.style.zIndex = originalZIndex; |
|
766 container.style.position = originalPosition; |
|
767 return range; |
|
768 } |
|
769 /** |
|
770 * Places the caret at the top or bottom of a given element. |
|
771 * |
|
772 * @param {Element} container Focusable element. |
|
773 * @param {boolean} isReverse True for bottom, false for top. |
|
774 * @param {DOMRect} [rect] The rectangle to position the caret with. |
|
775 * @param {boolean} [mayUseScroll=true] True to allow scrolling, false to disallow. |
|
776 */ |
|
777 |
|
778 |
|
779 function placeCaretAtVerticalEdge(container, isReverse, rect) { |
|
780 var mayUseScroll = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; |
|
781 |
|
782 if (!container) { |
|
783 return; |
|
784 } |
|
785 |
|
786 if (!rect || !container.isContentEditable) { |
|
787 placeCaretAtHorizontalEdge(container, isReverse); |
|
788 return; |
|
789 } // Offset by a buffer half the height of the caret rect. This is needed |
|
790 // because caretRangeFromPoint may default to the end of the selection if |
|
791 // offset is too close to the edge. It's unclear how to precisely calculate |
|
792 // this threshold; it may be the padded area of some combination of line |
|
793 // height, caret height, and font size. The buffer offset is effectively |
|
794 // equivalent to a point at half the height of a line of text. |
|
795 |
|
796 |
|
797 var buffer = rect.height / 2; |
|
798 var editableRect = container.getBoundingClientRect(); |
|
799 var x = rect.left; |
|
800 var y = isReverse ? editableRect.bottom - buffer : editableRect.top + buffer; |
|
801 var range = hiddenCaretRangeFromPoint(document, x, y, container); |
|
802 |
|
803 if (!range || !container.contains(range.startContainer)) { |
|
804 if (mayUseScroll && (!range || !range.startContainer || !range.startContainer.contains(container))) { |
|
805 // Might be out of view. |
|
806 // Easier than attempting to calculate manually. |
|
807 container.scrollIntoView(isReverse); |
|
808 placeCaretAtVerticalEdge(container, isReverse, rect, false); |
|
809 return; |
|
810 } |
|
811 |
|
812 placeCaretAtHorizontalEdge(container, isReverse); |
|
813 return; |
|
814 } |
|
815 |
|
816 var selection = window.getSelection(); |
|
817 selection.removeAllRanges(); |
|
818 selection.addRange(range); |
|
819 container.focus(); // Editable was already focussed, it goes back to old range... |
|
820 // This fixes it. |
|
821 |
|
822 selection.removeAllRanges(); |
|
823 selection.addRange(range); |
|
824 } |
|
825 /** |
|
826 * Check whether the given element is a text field, where text field is defined |
|
827 * by the ability to select within the input, or that it is contenteditable. |
|
828 * |
|
829 * See: https://html.spec.whatwg.org/#textFieldSelection |
|
830 * |
|
831 * @param {HTMLElement} element The HTML element. |
|
832 * |
|
833 * @return {boolean} True if the element is an text field, false if not. |
|
834 */ |
|
835 |
|
836 function isTextField(element) { |
|
837 var nodeName = element.nodeName, |
|
838 contentEditable = element.contentEditable; |
|
839 var nonTextInputs = ['button', 'checkbox', 'hidden', 'file', 'radio', 'image', 'range', 'reset', 'submit', 'number']; |
|
840 return nodeName === 'INPUT' && !nonTextInputs.includes(element.type) || nodeName === 'TEXTAREA' || contentEditable === 'true'; |
|
841 } |
|
842 /** |
|
843 * Check whether the given element is an input field of type number |
|
844 * and has a valueAsNumber |
|
845 * |
|
846 * @param {HTMLElement} element The HTML element. |
|
847 * |
|
848 * @return {boolean} True if the element is input and holds a number. |
|
849 */ |
|
850 |
|
851 function isNumberInput(element) { |
|
852 var nodeName = element.nodeName, |
|
853 type = element.type, |
|
854 valueAsNumber = element.valueAsNumber; |
|
855 return nodeName === 'INPUT' && type === 'number' && !!valueAsNumber; |
|
856 } |
|
857 /** |
578 /** |
858 * Check whether the current document has selected text. This applies to ranges |
579 * Check whether the current document has selected text. This applies to ranges |
859 * of text in the document, and not selection inside <input> and <textarea> |
580 * of text in the document, and not selection inside <input> and <textarea> |
860 * elements. |
581 * elements. |
861 * |
582 * |
862 * See: https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection#Related_objects. |
583 * See: https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection#Related_objects. |
863 * |
584 * |
|
585 * @param {Document} doc The document to check. |
|
586 * |
864 * @return {boolean} True if there is selection, false if not. |
587 * @return {boolean} True if there is selection, false if not. |
865 */ |
588 */ |
866 |
589 |
867 function documentHasTextSelection() { |
590 function documentHasTextSelection(doc) { |
868 var selection = window.getSelection(); |
591 assertIsDefined(doc.defaultView, 'doc.defaultView'); |
869 var range = selection.rangeCount ? selection.getRangeAt(0) : null; |
592 const selection = doc.defaultView.getSelection(); |
870 return range && !range.collapsed; |
593 assertIsDefined(selection, 'selection'); |
871 } |
594 const range = selection.rangeCount ? selection.getRangeAt(0) : null; |
|
595 return !!range && !range.collapsed; |
|
596 } |
|
597 |
|
598 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/is-html-input-element.js |
|
599 /* eslint-disable jsdoc/valid-types */ |
|
600 |
|
601 /** |
|
602 * @param {Node} node |
|
603 * @return {node is HTMLInputElement} Whether the node is an HTMLInputElement. |
|
604 */ |
|
605 function isHTMLInputElement(node) { |
|
606 /* eslint-enable jsdoc/valid-types */ |
|
607 return !!node && node.nodeName === 'INPUT'; |
|
608 } |
|
609 |
|
610 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/is-text-field.js |
|
611 /** |
|
612 * Internal dependencies |
|
613 */ |
|
614 |
|
615 /* eslint-disable jsdoc/valid-types */ |
|
616 |
|
617 /** |
|
618 * Check whether the given element is a text field, where text field is defined |
|
619 * by the ability to select within the input, or that it is contenteditable. |
|
620 * |
|
621 * See: https://html.spec.whatwg.org/#textFieldSelection |
|
622 * |
|
623 * @param {Node} node The HTML element. |
|
624 * @return {node is HTMLElement} True if the element is an text field, false if not. |
|
625 */ |
|
626 |
|
627 function isTextField(node) { |
|
628 /* eslint-enable jsdoc/valid-types */ |
|
629 const nonTextInputs = ['button', 'checkbox', 'hidden', 'file', 'radio', 'image', 'range', 'reset', 'submit', 'number']; |
|
630 return isHTMLInputElement(node) && node.type && !nonTextInputs.includes(node.type) || node.nodeName === 'TEXTAREA' || |
|
631 /** @type {HTMLElement} */ |
|
632 node.contentEditable === 'true'; |
|
633 } |
|
634 |
|
635 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/is-number-input.js |
|
636 /** |
|
637 * Internal dependencies |
|
638 */ |
|
639 |
|
640 /* eslint-disable jsdoc/valid-types */ |
|
641 |
|
642 /** |
|
643 * Check whether the given element is an input field of type number |
|
644 * and has a valueAsNumber |
|
645 * |
|
646 * @param {Node} node The HTML node. |
|
647 * |
|
648 * @return {node is HTMLInputElement} True if the node is input and holds a number. |
|
649 */ |
|
650 |
|
651 function isNumberInput(node) { |
|
652 /* eslint-enable jsdoc/valid-types */ |
|
653 return isHTMLInputElement(node) && node.type === 'number' && !!node.valueAsNumber; |
|
654 } |
|
655 |
|
656 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/input-field-has-uncollapsed-selection.js |
|
657 /** |
|
658 * Internal dependencies |
|
659 */ |
|
660 |
|
661 |
872 /** |
662 /** |
873 * Check whether the given element, assumed an input field or textarea, |
663 * Check whether the given element, assumed an input field or textarea, |
874 * contains a (uncollapsed) selection of text. |
664 * contains a (uncollapsed) selection of text. |
875 * |
665 * |
876 * Note: this is perhaps an abuse of the term "selection", since these elements |
666 * Note: this is perhaps an abuse of the term "selection", since these elements |
877 * manage selection differently and aren't covered by Selection#collapsed. |
667 * manage selection differently and aren't covered by Selection#collapsed. |
878 * |
668 * |
879 * See: https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection#Related_objects. |
669 * See: https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection#Related_objects. |
880 * |
670 * |
881 * @param {HTMLElement} element The HTML element. |
671 * @param {Element} element The HTML element. |
882 * |
672 * |
883 * @return {boolean} Whether the input/textareaa element has some "selection". |
673 * @return {boolean} Whether the input/textareaa element has some "selection". |
884 */ |
674 */ |
885 |
675 |
886 function inputFieldHasUncollapsedSelection(element) { |
676 function inputFieldHasUncollapsedSelection(element) { |
887 if (!isTextField(element) && !isNumberInput(element)) { |
677 if (!isTextField(element) && !isNumberInput(element)) { |
888 return false; |
678 return false; |
889 } |
679 } |
890 |
680 |
891 try { |
681 try { |
892 var selectionStart = element.selectionStart, |
682 const { |
893 selectionEnd = element.selectionEnd; |
683 selectionStart, |
|
684 selectionEnd |
|
685 } = |
|
686 /** @type {HTMLInputElement | HTMLTextAreaElement} */ |
|
687 element; |
894 return selectionStart !== null && selectionStart !== selectionEnd; |
688 return selectionStart !== null && selectionStart !== selectionEnd; |
895 } catch (error) { |
689 } catch (error) { |
896 // Safari throws an exception when trying to get `selectionStart` |
690 // Safari throws an exception when trying to get `selectionStart` |
897 // on non-text <input> elements (which, understandably, don't |
691 // on non-text <input> elements (which, understandably, don't |
898 // have the text selection API). We catch this via a try/catch |
692 // have the text selection API). We catch this via a try/catch |
902 // types that support `selectionStart` changing as the HTML spec |
696 // types that support `selectionStart` changing as the HTML spec |
903 // evolves over time. |
697 // evolves over time. |
904 return false; |
698 return false; |
905 } |
699 } |
906 } |
700 } |
|
701 |
|
702 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/document-has-uncollapsed-selection.js |
|
703 /** |
|
704 * Internal dependencies |
|
705 */ |
|
706 |
|
707 |
907 /** |
708 /** |
908 * Check whether the current document has any sort of selection. This includes |
709 * Check whether the current document has any sort of selection. This includes |
909 * ranges of text across elements and any selection inside <input> and |
710 * ranges of text across elements and any selection inside <input> and |
910 * <textarea> elements. |
711 * <textarea> elements. |
911 * |
712 * |
|
713 * @param {Document} doc The document to check. |
|
714 * |
912 * @return {boolean} Whether there is any sort of "selection" in the document. |
715 * @return {boolean} Whether there is any sort of "selection" in the document. |
913 */ |
716 */ |
914 |
717 |
915 |
718 function documentHasUncollapsedSelection(doc) { |
916 function documentHasUncollapsedSelection() { |
719 return documentHasTextSelection(doc) || !!doc.activeElement && inputFieldHasUncollapsedSelection(doc.activeElement); |
917 return documentHasTextSelection() || inputFieldHasUncollapsedSelection(document.activeElement); |
720 } |
918 } |
721 |
|
722 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/document-has-selection.js |
|
723 /** |
|
724 * Internal dependencies |
|
725 */ |
|
726 |
|
727 |
|
728 |
919 /** |
729 /** |
920 * Check whether the current document has a selection. This checks for both |
730 * Check whether the current document has a selection. This checks for both |
921 * focus in an input field and general text selection. |
731 * focus in an input field and general text selection. |
922 * |
732 * |
|
733 * @param {Document} doc The document to check. |
|
734 * |
923 * @return {boolean} True if there is selection, false if not. |
735 * @return {boolean} True if there is selection, false if not. |
924 */ |
736 */ |
925 |
737 |
926 function documentHasSelection() { |
738 function documentHasSelection(doc) { |
927 return isTextField(document.activeElement) || isNumberInput(document.activeElement) || documentHasTextSelection(); |
739 return !!doc.activeElement && (isTextField(doc.activeElement) || isNumberInput(doc.activeElement) || documentHasTextSelection(doc)); |
928 } |
740 } |
929 /** |
741 |
930 * Check whether the contents of the element have been entirely selected. |
742 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/get-computed-style.js |
931 * Returns true if there is no possibility of selection. |
743 /** |
932 * |
744 * Internal dependencies |
933 * @param {Element} element The element to check. |
745 */ |
934 * |
746 |
935 * @return {boolean} True if entirely selected, false if not. |
747 /* eslint-disable jsdoc/valid-types */ |
936 */ |
748 |
937 |
749 /** |
938 function isEntirelySelected(element) { |
750 * @param {Element} element |
939 if (Object(external_this_lodash_["includes"])(['INPUT', 'TEXTAREA'], element.nodeName)) { |
751 * @return {ReturnType<Window['getComputedStyle']>} The computed style for the element. |
940 return element.selectionStart === 0 && element.value.length === element.selectionEnd; |
752 */ |
941 } |
753 |
942 |
754 function getComputedStyle(element) { |
943 if (!element.isContentEditable) { |
755 /* eslint-enable jsdoc/valid-types */ |
944 return true; |
756 assertIsDefined(element.ownerDocument.defaultView, 'element.ownerDocument.defaultView'); |
945 } |
757 return element.ownerDocument.defaultView.getComputedStyle(element); |
946 |
758 } |
947 var selection = window.getSelection(); |
759 |
948 var range = selection.rangeCount ? selection.getRangeAt(0) : null; |
760 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/get-scroll-container.js |
949 |
761 /** |
950 if (!range) { |
762 * Internal dependencies |
951 return true; |
763 */ |
952 } |
764 |
953 |
|
954 var startContainer = range.startContainer, |
|
955 endContainer = range.endContainer, |
|
956 startOffset = range.startOffset, |
|
957 endOffset = range.endOffset; |
|
958 |
|
959 if (startContainer === element && endContainer === element && startOffset === 0 && endOffset === element.childNodes.length) { |
|
960 return true; |
|
961 } |
|
962 |
|
963 var lastChild = element.lastChild; |
|
964 var lastChildContentLength = lastChild.nodeType === TEXT_NODE ? lastChild.data.length : lastChild.childNodes.length; |
|
965 return startContainer === element.firstChild && endContainer === element.lastChild && startOffset === 0 && endOffset === lastChildContentLength; |
|
966 } |
|
967 /** |
765 /** |
968 * Given a DOM node, finds the closest scrollable container node. |
766 * Given a DOM node, finds the closest scrollable container node. |
969 * |
767 * |
970 * @param {Element} node Node from which to start. |
768 * @param {Element | null} node Node from which to start. |
971 * |
769 * |
972 * @return {?Element} Scrollable container node, if found. |
770 * @return {Element | undefined} Scrollable container node, if found. |
973 */ |
771 */ |
974 |
772 |
975 function getScrollContainer(node) { |
773 function getScrollContainer(node) { |
976 if (!node) { |
774 if (!node) { |
977 return; |
775 return undefined; |
978 } // Scrollable if scrollable height exceeds displayed... |
776 } // Scrollable if scrollable height exceeds displayed... |
979 |
777 |
980 |
778 |
981 if (node.scrollHeight > node.clientHeight) { |
779 if (node.scrollHeight > node.clientHeight) { |
982 // ...except when overflow is defined to be hidden or visible |
780 // ...except when overflow is defined to be hidden or visible |
983 var _window$getComputedSt = window.getComputedStyle(node), |
781 const { |
984 overflowY = _window$getComputedSt.overflowY; |
782 overflowY |
|
783 } = getComputedStyle(node); |
985 |
784 |
986 if (/(auto|scroll)/.test(overflowY)) { |
785 if (/(auto|scroll)/.test(overflowY)) { |
987 return node; |
786 return node; |
988 } |
787 } |
989 } // Continue traversing |
788 } // Continue traversing |
990 |
789 |
991 |
790 |
992 return getScrollContainer(node.parentNode); |
791 return getScrollContainer( |
993 } |
792 /** @type {Element} */ |
|
793 node.parentNode); |
|
794 } |
|
795 |
|
796 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/get-offset-parent.js |
|
797 /** |
|
798 * Internal dependencies |
|
799 */ |
|
800 |
994 /** |
801 /** |
995 * Returns the closest positioned element, or null under any of the conditions |
802 * Returns the closest positioned element, or null under any of the conditions |
996 * of the offsetParent specification. Unlike offsetParent, this function is not |
803 * of the offsetParent specification. Unlike offsetParent, this function is not |
997 * limited to HTMLElement and accepts any Node (e.g. Node.TEXT_NODE). |
804 * limited to HTMLElement and accepts any Node (e.g. Node.TEXT_NODE). |
998 * |
805 * |
999 * @see https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent |
806 * @see https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent |
1000 * |
807 * |
1001 * @param {Node} node Node from which to find offset parent. |
808 * @param {Node} node Node from which to find offset parent. |
1002 * |
809 * |
1003 * @return {?Node} Offset parent. |
810 * @return {Node | null} Offset parent. |
1004 */ |
811 */ |
1005 |
812 |
1006 function getOffsetParent(node) { |
813 function getOffsetParent(node) { |
1007 // Cannot retrieve computed style or offset parent only anything other than |
814 // Cannot retrieve computed style or offset parent only anything other than |
1008 // an element node, so find the closest element node. |
815 // an element node, so find the closest element node. |
1009 var closestElement; |
816 let closestElement; |
1010 |
817 |
1011 while (closestElement = node.parentNode) { |
818 while (closestElement = |
1012 if (closestElement.nodeType === ELEMENT_NODE) { |
819 /** @type {Node} */ |
|
820 node.parentNode) { |
|
821 if (closestElement.nodeType === closestElement.ELEMENT_NODE) { |
1013 break; |
822 break; |
1014 } |
823 } |
1015 } |
824 } |
1016 |
825 |
1017 if (!closestElement) { |
826 if (!closestElement) { |
1018 return null; |
827 return null; |
1019 } // If the closest element is already positioned, return it, as offsetParent |
828 } // If the closest element is already positioned, return it, as offsetParent |
1020 // does not otherwise consider the node itself. |
829 // does not otherwise consider the node itself. |
1021 |
830 |
1022 |
831 |
1023 if (getComputedStyle(closestElement).position !== 'static') { |
832 if (getComputedStyle( |
|
833 /** @type {Element} */ |
|
834 closestElement).position !== 'static') { |
1024 return closestElement; |
835 return closestElement; |
1025 } |
836 } // offsetParent is undocumented/draft |
1026 |
837 |
1027 return closestElement.offsetParent; |
838 |
1028 } |
839 return ( |
|
840 /** @type {Node & { offsetParent: Node }} */ |
|
841 closestElement.offsetParent |
|
842 ); |
|
843 } |
|
844 |
|
845 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/is-input-or-text-area.js |
|
846 /* eslint-disable jsdoc/valid-types */ |
|
847 |
|
848 /** |
|
849 * @param {Element} element |
|
850 * @return {element is HTMLInputElement | HTMLTextAreaElement} Whether the element is an input or textarea |
|
851 */ |
|
852 function isInputOrTextArea(element) { |
|
853 /* eslint-enable jsdoc/valid-types */ |
|
854 return element.tagName === 'INPUT' || element.tagName === 'TEXTAREA'; |
|
855 } |
|
856 |
|
857 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/is-entirely-selected.js |
|
858 /** |
|
859 * Internal dependencies |
|
860 */ |
|
861 |
|
862 |
|
863 /** |
|
864 * Check whether the contents of the element have been entirely selected. |
|
865 * Returns true if there is no possibility of selection. |
|
866 * |
|
867 * @param {HTMLElement} element The element to check. |
|
868 * |
|
869 * @return {boolean} True if entirely selected, false if not. |
|
870 */ |
|
871 |
|
872 function isEntirelySelected(element) { |
|
873 if (isInputOrTextArea(element)) { |
|
874 return element.selectionStart === 0 && element.value.length === element.selectionEnd; |
|
875 } |
|
876 |
|
877 if (!element.isContentEditable) { |
|
878 return true; |
|
879 } |
|
880 |
|
881 const { |
|
882 ownerDocument |
|
883 } = element; |
|
884 const { |
|
885 defaultView |
|
886 } = ownerDocument; |
|
887 assertIsDefined(defaultView, 'defaultView'); |
|
888 const selection = defaultView.getSelection(); |
|
889 assertIsDefined(selection, 'selection'); |
|
890 const range = selection.rangeCount ? selection.getRangeAt(0) : null; |
|
891 |
|
892 if (!range) { |
|
893 return true; |
|
894 } |
|
895 |
|
896 const { |
|
897 startContainer, |
|
898 endContainer, |
|
899 startOffset, |
|
900 endOffset |
|
901 } = range; |
|
902 |
|
903 if (startContainer === element && endContainer === element && startOffset === 0 && endOffset === element.childNodes.length) { |
|
904 return true; |
|
905 } |
|
906 |
|
907 const lastChild = element.lastChild; |
|
908 assertIsDefined(lastChild, 'lastChild'); |
|
909 const endContainerContentLength = endContainer.nodeType === endContainer.TEXT_NODE ? |
|
910 /** @type {Text} */ |
|
911 endContainer.data.length : endContainer.childNodes.length; |
|
912 return isDeepChild(startContainer, element, 'firstChild') && isDeepChild(endContainer, element, 'lastChild') && startOffset === 0 && endOffset === endContainerContentLength; |
|
913 } |
|
914 /** |
|
915 * Check whether the contents of the element have been entirely selected. |
|
916 * Returns true if there is no possibility of selection. |
|
917 * |
|
918 * @param {HTMLElement|Node} query The element to check. |
|
919 * @param {HTMLElement} container The container that we suspect "query" may be a first or last child of. |
|
920 * @param {"firstChild"|"lastChild"} propName "firstChild" or "lastChild" |
|
921 * |
|
922 * @return {boolean} True if query is a deep first/last child of container, false otherwise. |
|
923 */ |
|
924 |
|
925 function isDeepChild(query, container, propName) { |
|
926 /** @type {HTMLElement | ChildNode | null} */ |
|
927 let candidate = container; |
|
928 |
|
929 do { |
|
930 if (query === candidate) { |
|
931 return true; |
|
932 } |
|
933 |
|
934 candidate = candidate[propName]; |
|
935 } while (candidate); |
|
936 |
|
937 return false; |
|
938 } |
|
939 |
|
940 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/is-rtl.js |
|
941 /** |
|
942 * Internal dependencies |
|
943 */ |
|
944 |
|
945 /** |
|
946 * Whether the element's text direction is right-to-left. |
|
947 * |
|
948 * @param {Element} element The element to check. |
|
949 * |
|
950 * @return {boolean} True if rtl, false if ltr. |
|
951 */ |
|
952 |
|
953 function isRTL(element) { |
|
954 return getComputedStyle(element).direction === 'rtl'; |
|
955 } |
|
956 |
|
957 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/get-range-height.js |
|
958 /** |
|
959 * Gets the height of the range without ignoring zero width rectangles, which |
|
960 * some browsers ignore when creating a union. |
|
961 * |
|
962 * @param {Range} range The range to check. |
|
963 * @return {number | undefined} Height of the range or undefined if the range has no client rectangles. |
|
964 */ |
|
965 function getRangeHeight(range) { |
|
966 const rects = Array.from(range.getClientRects()); |
|
967 |
|
968 if (!rects.length) { |
|
969 return; |
|
970 } |
|
971 |
|
972 const highestTop = Math.min(...rects.map(({ |
|
973 top |
|
974 }) => top)); |
|
975 const lowestBottom = Math.max(...rects.map(({ |
|
976 bottom |
|
977 }) => bottom)); |
|
978 return lowestBottom - highestTop; |
|
979 } |
|
980 |
|
981 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/is-selection-forward.js |
|
982 /** |
|
983 * Internal dependencies |
|
984 */ |
|
985 |
|
986 /** |
|
987 * Returns true if the given selection object is in the forward direction, or |
|
988 * false otherwise. |
|
989 * |
|
990 * @see https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition |
|
991 * |
|
992 * @param {Selection} selection Selection object to check. |
|
993 * |
|
994 * @return {boolean} Whether the selection is forward. |
|
995 */ |
|
996 |
|
997 function isSelectionForward(selection) { |
|
998 const { |
|
999 anchorNode, |
|
1000 focusNode, |
|
1001 anchorOffset, |
|
1002 focusOffset |
|
1003 } = selection; |
|
1004 assertIsDefined(anchorNode, 'anchorNode'); |
|
1005 assertIsDefined(focusNode, 'focusNode'); |
|
1006 const position = anchorNode.compareDocumentPosition(focusNode); // Disable reason: `Node#compareDocumentPosition` returns a bitmask value, |
|
1007 // so bitwise operators are intended. |
|
1008 |
|
1009 /* eslint-disable no-bitwise */ |
|
1010 // Compare whether anchor node precedes focus node. If focus node (where |
|
1011 // end of selection occurs) is after the anchor node, it is forward. |
|
1012 |
|
1013 if (position & anchorNode.DOCUMENT_POSITION_PRECEDING) { |
|
1014 return false; |
|
1015 } |
|
1016 |
|
1017 if (position & anchorNode.DOCUMENT_POSITION_FOLLOWING) { |
|
1018 return true; |
|
1019 } |
|
1020 /* eslint-enable no-bitwise */ |
|
1021 // `compareDocumentPosition` returns 0 when passed the same node, in which |
|
1022 // case compare offsets. |
|
1023 |
|
1024 |
|
1025 if (position === 0) { |
|
1026 return anchorOffset <= focusOffset; |
|
1027 } // This should never be reached, but return true as default case. |
|
1028 |
|
1029 |
|
1030 return true; |
|
1031 } |
|
1032 |
|
1033 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/caret-range-from-point.js |
|
1034 /** |
|
1035 * Polyfill. |
|
1036 * Get a collapsed range for a given point. |
|
1037 * |
|
1038 * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/caretRangeFromPoint |
|
1039 * |
|
1040 * @param {Document} doc The document of the range. |
|
1041 * @param {number} x Horizontal position within the current viewport. |
|
1042 * @param {number} y Vertical position within the current viewport. |
|
1043 * |
|
1044 * @return {Range | null} The best range for the given point. |
|
1045 */ |
|
1046 function caretRangeFromPoint(doc, x, y) { |
|
1047 if (doc.caretRangeFromPoint) { |
|
1048 return doc.caretRangeFromPoint(x, y); |
|
1049 } |
|
1050 |
|
1051 if (!doc.caretPositionFromPoint) { |
|
1052 return null; |
|
1053 } |
|
1054 |
|
1055 const point = doc.caretPositionFromPoint(x, y); // If x or y are negative, outside viewport, or there is no text entry node. |
|
1056 // https://developer.mozilla.org/en-US/docs/Web/API/Document/caretRangeFromPoint |
|
1057 |
|
1058 if (!point) { |
|
1059 return null; |
|
1060 } |
|
1061 |
|
1062 const range = doc.createRange(); |
|
1063 range.setStart(point.offsetNode, point.offset); |
|
1064 range.collapse(true); |
|
1065 return range; |
|
1066 } |
|
1067 |
|
1068 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/hidden-caret-range-from-point.js |
|
1069 /** |
|
1070 * Internal dependencies |
|
1071 */ |
|
1072 |
|
1073 |
|
1074 /** |
|
1075 * Get a collapsed range for a given point. |
|
1076 * Gives the container a temporary high z-index (above any UI). |
|
1077 * This is preferred over getting the UI nodes and set styles there. |
|
1078 * |
|
1079 * @param {Document} doc The document of the range. |
|
1080 * @param {number} x Horizontal position within the current viewport. |
|
1081 * @param {number} y Vertical position within the current viewport. |
|
1082 * @param {HTMLElement} container Container in which the range is expected to be found. |
|
1083 * |
|
1084 * @return {?Range} The best range for the given point. |
|
1085 */ |
|
1086 |
|
1087 function hiddenCaretRangeFromPoint(doc, x, y, container) { |
|
1088 const originalZIndex = container.style.zIndex; |
|
1089 const originalPosition = container.style.position; |
|
1090 const { |
|
1091 position = 'static' |
|
1092 } = getComputedStyle(container); // A z-index only works if the element position is not static. |
|
1093 |
|
1094 if (position === 'static') { |
|
1095 container.style.position = 'relative'; |
|
1096 } |
|
1097 |
|
1098 container.style.zIndex = '10000'; |
|
1099 const range = caretRangeFromPoint(doc, x, y); |
|
1100 container.style.zIndex = originalZIndex; |
|
1101 container.style.position = originalPosition; |
|
1102 return range; |
|
1103 } |
|
1104 |
|
1105 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/is-edge.js |
|
1106 /** |
|
1107 * Internal dependencies |
|
1108 */ |
|
1109 |
|
1110 |
|
1111 |
|
1112 |
|
1113 |
|
1114 |
|
1115 |
|
1116 /** |
|
1117 * Check whether the selection is at the edge of the container. Checks for |
|
1118 * horizontal position by default. Set `onlyVertical` to true to check only |
|
1119 * vertically. |
|
1120 * |
|
1121 * @param {Element} container Focusable element. |
|
1122 * @param {boolean} isReverse Set to true to check left, false to check right. |
|
1123 * @param {boolean} [onlyVertical=false] Set to true to check only vertical position. |
|
1124 * |
|
1125 * @return {boolean} True if at the edge, false if not. |
|
1126 */ |
|
1127 |
|
1128 function isEdge(container, isReverse, onlyVertical = false) { |
|
1129 if (isInputOrTextArea(container)) { |
|
1130 if (container.selectionStart !== container.selectionEnd) { |
|
1131 return false; |
|
1132 } |
|
1133 |
|
1134 if (isReverse) { |
|
1135 return container.selectionStart === 0; |
|
1136 } |
|
1137 |
|
1138 return container.value.length === container.selectionStart; |
|
1139 } |
|
1140 |
|
1141 if (! |
|
1142 /** @type {HTMLElement} */ |
|
1143 container.isContentEditable) { |
|
1144 return true; |
|
1145 } |
|
1146 |
|
1147 const { |
|
1148 ownerDocument |
|
1149 } = container; |
|
1150 const { |
|
1151 defaultView |
|
1152 } = ownerDocument; |
|
1153 assertIsDefined(defaultView, 'defaultView'); |
|
1154 const selection = defaultView.getSelection(); |
|
1155 |
|
1156 if (!selection || !selection.rangeCount) { |
|
1157 return false; |
|
1158 } |
|
1159 |
|
1160 const range = selection.getRangeAt(0); |
|
1161 const collapsedRange = range.cloneRange(); |
|
1162 const isForward = isSelectionForward(selection); |
|
1163 const isCollapsed = selection.isCollapsed; // Collapse in direction of selection. |
|
1164 |
|
1165 if (!isCollapsed) { |
|
1166 collapsedRange.collapse(!isForward); |
|
1167 } |
|
1168 |
|
1169 const collapsedRangeRect = getRectangleFromRange(collapsedRange); |
|
1170 const rangeRect = getRectangleFromRange(range); |
|
1171 |
|
1172 if (!collapsedRangeRect || !rangeRect) { |
|
1173 return false; |
|
1174 } // Only consider the multiline selection at the edge if the direction is |
|
1175 // towards the edge. The selection is multiline if it is taller than the |
|
1176 // collapsed selection. |
|
1177 |
|
1178 |
|
1179 const rangeHeight = getRangeHeight(range); |
|
1180 |
|
1181 if (!isCollapsed && rangeHeight && rangeHeight > collapsedRangeRect.height && isForward === isReverse) { |
|
1182 return false; |
|
1183 } // In the case of RTL scripts, the horizontal edge is at the opposite side. |
|
1184 |
|
1185 |
|
1186 const isReverseDir = isRTL(container) ? !isReverse : isReverse; |
|
1187 const containerRect = container.getBoundingClientRect(); // To check if a selection is at the edge, we insert a test selection at the |
|
1188 // edge of the container and check if the selections have the same vertical |
|
1189 // or horizontal position. If they do, the selection is at the edge. |
|
1190 // This method proves to be better than a DOM-based calculation for the |
|
1191 // horizontal edge, since it ignores empty textnodes and a trailing line |
|
1192 // break element. In other words, we need to check visual positioning, not |
|
1193 // DOM positioning. |
|
1194 // It also proves better than using the computed style for the vertical |
|
1195 // edge, because we cannot know the padding and line height reliably in |
|
1196 // pixels. `getComputedStyle` may return a value with different units. |
|
1197 |
|
1198 const x = isReverseDir ? containerRect.left + 1 : containerRect.right - 1; |
|
1199 const y = isReverse ? containerRect.top + 1 : containerRect.bottom - 1; |
|
1200 const testRange = hiddenCaretRangeFromPoint(ownerDocument, x, y, |
|
1201 /** @type {HTMLElement} */ |
|
1202 container); |
|
1203 |
|
1204 if (!testRange) { |
|
1205 return false; |
|
1206 } |
|
1207 |
|
1208 const testRect = getRectangleFromRange(testRange); |
|
1209 |
|
1210 if (!testRect) { |
|
1211 return false; |
|
1212 } |
|
1213 |
|
1214 const verticalSide = isReverse ? 'top' : 'bottom'; |
|
1215 const horizontalSide = isReverseDir ? 'left' : 'right'; |
|
1216 const verticalDiff = testRect[verticalSide] - rangeRect[verticalSide]; |
|
1217 const horizontalDiff = testRect[horizontalSide] - collapsedRangeRect[horizontalSide]; // Allow the position to be 1px off. |
|
1218 |
|
1219 const hasVerticalDiff = Math.abs(verticalDiff) <= 1; |
|
1220 const hasHorizontalDiff = Math.abs(horizontalDiff) <= 1; |
|
1221 return onlyVertical ? hasVerticalDiff : hasVerticalDiff && hasHorizontalDiff; |
|
1222 } |
|
1223 |
|
1224 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/is-horizontal-edge.js |
|
1225 /** |
|
1226 * Internal dependencies |
|
1227 */ |
|
1228 |
|
1229 /** |
|
1230 * Check whether the selection is horizontally at the edge of the container. |
|
1231 * |
|
1232 * @param {Element} container Focusable element. |
|
1233 * @param {boolean} isReverse Set to true to check left, false for right. |
|
1234 * |
|
1235 * @return {boolean} True if at the horizontal edge, false if not. |
|
1236 */ |
|
1237 |
|
1238 function isHorizontalEdge(container, isReverse) { |
|
1239 return isEdge(container, isReverse); |
|
1240 } |
|
1241 |
|
1242 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/is-vertical-edge.js |
|
1243 /** |
|
1244 * Internal dependencies |
|
1245 */ |
|
1246 |
|
1247 /** |
|
1248 * Check whether the selection is vertically at the edge of the container. |
|
1249 * |
|
1250 * @param {Element} container Focusable element. |
|
1251 * @param {boolean} isReverse Set to true to check top, false for bottom. |
|
1252 * |
|
1253 * @return {boolean} True if at the vertical edge, false if not. |
|
1254 */ |
|
1255 |
|
1256 function isVerticalEdge(container, isReverse) { |
|
1257 return isEdge(container, isReverse, true); |
|
1258 } |
|
1259 |
|
1260 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/place-caret-at-horizontal-edge.js |
|
1261 /** |
|
1262 * Internal dependencies |
|
1263 */ |
|
1264 |
|
1265 /** |
|
1266 * Internal dependencies |
|
1267 */ |
|
1268 |
|
1269 |
|
1270 |
|
1271 |
|
1272 /** |
|
1273 * Gets the range to place. |
|
1274 * |
|
1275 * @param {HTMLElement} container Focusable element. |
|
1276 * @param {boolean} isReverse True for end, false for start. |
|
1277 * |
|
1278 * @return {Range|null} The range to place. |
|
1279 */ |
|
1280 |
|
1281 function getRange(container, isReverse) { |
|
1282 const { |
|
1283 ownerDocument |
|
1284 } = container; // In the case of RTL scripts, the horizontal edge is at the opposite side. |
|
1285 |
|
1286 const isReverseDir = isRTL(container) ? !isReverse : isReverse; |
|
1287 const containerRect = container.getBoundingClientRect(); // When placing at the end (isReverse), find the closest range to the bottom |
|
1288 // right corner. When placing at the start, to the top left corner. |
|
1289 |
|
1290 const x = isReverse ? containerRect.right - 1 : containerRect.left + 1; |
|
1291 const y = isReverseDir ? containerRect.bottom - 1 : containerRect.top + 1; |
|
1292 return hiddenCaretRangeFromPoint(ownerDocument, x, y, container); |
|
1293 } |
|
1294 /** |
|
1295 * Places the caret at start or end of a given element. |
|
1296 * |
|
1297 * @param {HTMLElement} container Focusable element. |
|
1298 * @param {boolean} isReverse True for end, false for start. |
|
1299 */ |
|
1300 |
|
1301 |
|
1302 function placeCaretAtHorizontalEdge(container, isReverse) { |
|
1303 if (!container) { |
|
1304 return; |
|
1305 } |
|
1306 |
|
1307 container.focus(); |
|
1308 |
|
1309 if (isInputOrTextArea(container)) { |
|
1310 // The element may not support selection setting. |
|
1311 if (typeof container.selectionStart !== 'number') { |
|
1312 return; |
|
1313 } |
|
1314 |
|
1315 if (isReverse) { |
|
1316 container.selectionStart = container.value.length; |
|
1317 container.selectionEnd = container.value.length; |
|
1318 } else { |
|
1319 container.selectionStart = 0; |
|
1320 container.selectionEnd = 0; |
|
1321 } |
|
1322 |
|
1323 return; |
|
1324 } |
|
1325 |
|
1326 if (!container.isContentEditable) { |
|
1327 return; |
|
1328 } |
|
1329 |
|
1330 let range = getRange(container, isReverse); // If no range range can be created or it is outside the container, the |
|
1331 // element may be out of view. |
|
1332 |
|
1333 if (!range || !range.startContainer || !container.contains(range.startContainer)) { |
|
1334 container.scrollIntoView(isReverse); |
|
1335 range = getRange(container, isReverse); |
|
1336 |
|
1337 if (!range || !range.startContainer || !container.contains(range.startContainer)) { |
|
1338 return; |
|
1339 } |
|
1340 } |
|
1341 |
|
1342 const { |
|
1343 ownerDocument |
|
1344 } = container; |
|
1345 const { |
|
1346 defaultView |
|
1347 } = ownerDocument; |
|
1348 assertIsDefined(defaultView, 'defaultView'); |
|
1349 const selection = defaultView.getSelection(); |
|
1350 assertIsDefined(selection, 'selection'); |
|
1351 selection.removeAllRanges(); |
|
1352 selection.addRange(range); |
|
1353 } |
|
1354 |
|
1355 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/place-caret-at-vertical-edge.js |
|
1356 /** |
|
1357 * Internal dependencies |
|
1358 */ |
|
1359 |
|
1360 |
|
1361 |
|
1362 /** |
|
1363 * Places the caret at the top or bottom of a given element. |
|
1364 * |
|
1365 * @param {HTMLElement} container Focusable element. |
|
1366 * @param {boolean} isReverse True for bottom, false for top. |
|
1367 * @param {DOMRect} [rect] The rectangle to position the caret with. |
|
1368 * @param {boolean} [mayUseScroll=true] True to allow scrolling, false to disallow. |
|
1369 */ |
|
1370 |
|
1371 function placeCaretAtVerticalEdge(container, isReverse, rect, mayUseScroll = true) { |
|
1372 if (!container) { |
|
1373 return; |
|
1374 } |
|
1375 |
|
1376 if (!rect || !container.isContentEditable) { |
|
1377 placeCaretAtHorizontalEdge(container, isReverse); |
|
1378 return; |
|
1379 } |
|
1380 |
|
1381 container.focus(); // Offset by a buffer half the height of the caret rect. This is needed |
|
1382 // because caretRangeFromPoint may default to the end of the selection if |
|
1383 // offset is too close to the edge. It's unclear how to precisely calculate |
|
1384 // this threshold; it may be the padded area of some combination of line |
|
1385 // height, caret height, and font size. The buffer offset is effectively |
|
1386 // equivalent to a point at half the height of a line of text. |
|
1387 |
|
1388 const buffer = rect.height / 2; |
|
1389 const editableRect = container.getBoundingClientRect(); |
|
1390 const x = rect.left; |
|
1391 const y = isReverse ? editableRect.bottom - buffer : editableRect.top + buffer; |
|
1392 const { |
|
1393 ownerDocument |
|
1394 } = container; |
|
1395 const { |
|
1396 defaultView |
|
1397 } = ownerDocument; |
|
1398 const range = hiddenCaretRangeFromPoint(ownerDocument, x, y, container); |
|
1399 |
|
1400 if (!range || !container.contains(range.startContainer)) { |
|
1401 if (mayUseScroll && (!range || !range.startContainer || !range.startContainer.contains(container))) { |
|
1402 // Might be out of view. |
|
1403 // Easier than attempting to calculate manually. |
|
1404 container.scrollIntoView(isReverse); |
|
1405 placeCaretAtVerticalEdge(container, isReverse, rect, false); |
|
1406 return; |
|
1407 } |
|
1408 |
|
1409 placeCaretAtHorizontalEdge(container, isReverse); |
|
1410 return; |
|
1411 } |
|
1412 |
|
1413 assertIsDefined(defaultView, 'defaultView'); |
|
1414 const selection = defaultView.getSelection(); |
|
1415 assertIsDefined(selection, 'selection'); |
|
1416 selection.removeAllRanges(); |
|
1417 selection.addRange(range); |
|
1418 } |
|
1419 |
|
1420 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/insert-after.js |
|
1421 /** |
|
1422 * Internal dependencies |
|
1423 */ |
|
1424 |
|
1425 /** |
|
1426 * Given two DOM nodes, inserts the former in the DOM as the next sibling of |
|
1427 * the latter. |
|
1428 * |
|
1429 * @param {Node} newNode Node to be inserted. |
|
1430 * @param {Node} referenceNode Node after which to perform the insertion. |
|
1431 * @return {void} |
|
1432 */ |
|
1433 |
|
1434 function insertAfter(newNode, referenceNode) { |
|
1435 assertIsDefined(referenceNode.parentNode, 'referenceNode.parentNode'); |
|
1436 referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); |
|
1437 } |
|
1438 |
|
1439 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/remove.js |
|
1440 /** |
|
1441 * Internal dependencies |
|
1442 */ |
|
1443 |
|
1444 /** |
|
1445 * Given a DOM node, removes it from the DOM. |
|
1446 * |
|
1447 * @param {Node} node Node to be removed. |
|
1448 * @return {void} |
|
1449 */ |
|
1450 |
|
1451 function remove(node) { |
|
1452 assertIsDefined(node.parentNode, 'node.parentNode'); |
|
1453 node.parentNode.removeChild(node); |
|
1454 } |
|
1455 |
|
1456 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/replace.js |
|
1457 /** |
|
1458 * Internal dependencies |
|
1459 */ |
|
1460 |
|
1461 |
|
1462 |
1029 /** |
1463 /** |
1030 * Given two DOM nodes, replaces the former with the latter in the DOM. |
1464 * Given two DOM nodes, replaces the former with the latter in the DOM. |
1031 * |
1465 * |
1032 * @param {Element} processedNode Node to be removed. |
1466 * @param {Element} processedNode Node to be removed. |
1033 * @param {Element} newNode Node to be inserted in its place. |
1467 * @param {Element} newNode Node to be inserted in its place. |
1034 * @return {void} |
1468 * @return {void} |
1035 */ |
1469 */ |
1036 |
1470 |
1037 function replace(processedNode, newNode) { |
1471 function replace(processedNode, newNode) { |
|
1472 assertIsDefined(processedNode.parentNode, 'processedNode.parentNode'); |
1038 insertAfter(newNode, processedNode.parentNode); |
1473 insertAfter(newNode, processedNode.parentNode); |
1039 remove(processedNode); |
1474 remove(processedNode); |
1040 } |
1475 } |
1041 /** |
1476 |
1042 * Given a DOM node, removes it from the DOM. |
1477 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/unwrap.js |
1043 * |
1478 /** |
1044 * @param {Element} node Node to be removed. |
1479 * Internal dependencies |
|
1480 */ |
|
1481 |
|
1482 /** |
|
1483 * Unwrap the given node. This means any child nodes are moved to the parent. |
|
1484 * |
|
1485 * @param {Node} node The node to unwrap. |
|
1486 * |
1045 * @return {void} |
1487 * @return {void} |
1046 */ |
1488 */ |
1047 |
1489 |
1048 function remove(node) { |
|
1049 node.parentNode.removeChild(node); |
|
1050 } |
|
1051 /** |
|
1052 * Given two DOM nodes, inserts the former in the DOM as the next sibling of |
|
1053 * the latter. |
|
1054 * |
|
1055 * @param {Element} newNode Node to be inserted. |
|
1056 * @param {Element} referenceNode Node after which to perform the insertion. |
|
1057 * @return {void} |
|
1058 */ |
|
1059 |
|
1060 function insertAfter(newNode, referenceNode) { |
|
1061 referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); |
|
1062 } |
|
1063 /** |
|
1064 * Unwrap the given node. This means any child nodes are moved to the parent. |
|
1065 * |
|
1066 * @param {Node} node The node to unwrap. |
|
1067 * |
|
1068 * @return {void} |
|
1069 */ |
|
1070 |
|
1071 function unwrap(node) { |
1490 function unwrap(node) { |
1072 var parent = node.parentNode; |
1491 const parent = node.parentNode; |
|
1492 assertIsDefined(parent, 'node.parentNode'); |
1073 |
1493 |
1074 while (node.firstChild) { |
1494 while (node.firstChild) { |
1075 parent.insertBefore(node.firstChild, node); |
1495 parent.insertBefore(node.firstChild, node); |
1076 } |
1496 } |
1077 |
1497 |
1078 parent.removeChild(node); |
1498 parent.removeChild(node); |
1079 } |
1499 } |
|
1500 |
|
1501 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/replace-tag.js |
|
1502 /** |
|
1503 * Internal dependencies |
|
1504 */ |
|
1505 |
1080 /** |
1506 /** |
1081 * Replaces the given node with a new node with the given tag name. |
1507 * Replaces the given node with a new node with the given tag name. |
1082 * |
1508 * |
1083 * @param {Element} node The node to replace |
1509 * @param {Element} node The node to replace |
1084 * @param {string} tagName The new tag name. |
1510 * @param {string} tagName The new tag name. |
1085 * |
1511 * |
1086 * @return {Element} The new node. |
1512 * @return {Element} The new node. |
1087 */ |
1513 */ |
1088 |
1514 |
1089 function replaceTag(node, tagName) { |
1515 function replaceTag(node, tagName) { |
1090 var newNode = node.ownerDocument.createElement(tagName); |
1516 const newNode = node.ownerDocument.createElement(tagName); |
1091 |
1517 |
1092 while (node.firstChild) { |
1518 while (node.firstChild) { |
1093 newNode.appendChild(node.firstChild); |
1519 newNode.appendChild(node.firstChild); |
1094 } |
1520 } |
1095 |
1521 |
|
1522 assertIsDefined(node.parentNode, 'node.parentNode'); |
1096 node.parentNode.replaceChild(newNode, node); |
1523 node.parentNode.replaceChild(newNode, node); |
1097 return newNode; |
1524 return newNode; |
1098 } |
1525 } |
|
1526 |
|
1527 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/wrap.js |
|
1528 /** |
|
1529 * Internal dependencies |
|
1530 */ |
|
1531 |
1099 /** |
1532 /** |
1100 * Wraps the given node with a new node with the given tag name. |
1533 * Wraps the given node with a new node with the given tag name. |
1101 * |
1534 * |
1102 * @param {Element} newNode The node to insert. |
1535 * @param {Element} newNode The node to insert. |
1103 * @param {Element} referenceNode The node to wrap. |
1536 * @param {Element} referenceNode The node to wrap. |
1104 */ |
1537 */ |
1105 |
1538 |
1106 function wrap(newNode, referenceNode) { |
1539 function wrap(newNode, referenceNode) { |
|
1540 assertIsDefined(referenceNode.parentNode, 'referenceNode.parentNode'); |
1107 referenceNode.parentNode.insertBefore(newNode, referenceNode); |
1541 referenceNode.parentNode.insertBefore(newNode, referenceNode); |
1108 newNode.appendChild(referenceNode); |
1542 newNode.appendChild(referenceNode); |
1109 } |
1543 } |
|
1544 |
|
1545 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/strip-html.js |
1110 /** |
1546 /** |
1111 * Removes any HTML tags from the provided string. |
1547 * Removes any HTML tags from the provided string. |
1112 * |
1548 * |
1113 * @param {string} html The string containing html. |
1549 * @param {string} html The string containing html. |
1114 * |
1550 * |
1115 * @return {string} The text content with any html removed. |
1551 * @return {string} The text content with any html removed. |
1116 */ |
1552 */ |
1117 |
1553 function stripHTML(html) { |
1118 function __unstableStripHTML(html) { |
1554 const document = new window.DOMParser().parseFromString(html, 'text/html'); |
1119 var document = new DOMParser().parseFromString(html, 'text/html'); |
|
1120 return document.body.textContent || ''; |
1555 return document.body.textContent || ''; |
|
1556 } |
|
1557 |
|
1558 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/is-empty.js |
|
1559 /** |
|
1560 * Recursively checks if an element is empty. An element is not empty if it |
|
1561 * contains text or contains elements with attributes such as images. |
|
1562 * |
|
1563 * @param {Element} element The element to check. |
|
1564 * |
|
1565 * @return {boolean} Whether or not the element is empty. |
|
1566 */ |
|
1567 function isEmpty(element) { |
|
1568 switch (element.nodeType) { |
|
1569 case element.TEXT_NODE: |
|
1570 // We cannot use \s since it includes special spaces which we want |
|
1571 // to preserve. |
|
1572 return /^[ \f\n\r\t\v\u00a0]*$/.test(element.nodeValue || ''); |
|
1573 |
|
1574 case element.ELEMENT_NODE: |
|
1575 if (element.hasAttributes()) { |
|
1576 return false; |
|
1577 } else if (!element.hasChildNodes()) { |
|
1578 return true; |
|
1579 } |
|
1580 |
|
1581 return ( |
|
1582 /** @type {Element[]} */ |
|
1583 Array.from(element.childNodes).every(isEmpty) |
|
1584 ); |
|
1585 |
|
1586 default: |
|
1587 return true; |
|
1588 } |
|
1589 } |
|
1590 |
|
1591 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/phrasing-content.js |
|
1592 /** |
|
1593 * External dependencies |
|
1594 */ |
|
1595 |
|
1596 /** |
|
1597 * All phrasing content elements. |
|
1598 * |
|
1599 * @see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#phrasing-content-0 |
|
1600 */ |
|
1601 |
|
1602 /** |
|
1603 * @typedef {Record<string,SemanticElementDefinition>} ContentSchema |
|
1604 */ |
|
1605 |
|
1606 /** |
|
1607 * @typedef SemanticElementDefinition |
|
1608 * @property {string[]} [attributes] Content attributes |
|
1609 * @property {ContentSchema} [children] Content attributes |
|
1610 */ |
|
1611 |
|
1612 /** |
|
1613 * All text-level semantic elements. |
|
1614 * |
|
1615 * @see https://html.spec.whatwg.org/multipage/text-level-semantics.html |
|
1616 * |
|
1617 * @type {ContentSchema} |
|
1618 */ |
|
1619 |
|
1620 const textContentSchema = { |
|
1621 strong: {}, |
|
1622 em: {}, |
|
1623 s: {}, |
|
1624 del: {}, |
|
1625 ins: {}, |
|
1626 a: { |
|
1627 attributes: ['href', 'target', 'rel'] |
|
1628 }, |
|
1629 code: {}, |
|
1630 abbr: { |
|
1631 attributes: ['title'] |
|
1632 }, |
|
1633 sub: {}, |
|
1634 sup: {}, |
|
1635 br: {}, |
|
1636 small: {}, |
|
1637 // To do: fix blockquote. |
|
1638 // cite: {}, |
|
1639 q: { |
|
1640 attributes: ['cite'] |
|
1641 }, |
|
1642 dfn: { |
|
1643 attributes: ['title'] |
|
1644 }, |
|
1645 data: { |
|
1646 attributes: ['value'] |
|
1647 }, |
|
1648 time: { |
|
1649 attributes: ['datetime'] |
|
1650 }, |
|
1651 var: {}, |
|
1652 samp: {}, |
|
1653 kbd: {}, |
|
1654 i: {}, |
|
1655 b: {}, |
|
1656 u: {}, |
|
1657 mark: {}, |
|
1658 ruby: {}, |
|
1659 rt: {}, |
|
1660 rp: {}, |
|
1661 bdi: { |
|
1662 attributes: ['dir'] |
|
1663 }, |
|
1664 bdo: { |
|
1665 attributes: ['dir'] |
|
1666 }, |
|
1667 wbr: {}, |
|
1668 '#text': {} |
|
1669 }; // Recursion is needed. |
|
1670 // Possible: strong > em > strong. |
|
1671 // Impossible: strong > strong. |
|
1672 |
|
1673 Object(external_lodash_["without"])(Object.keys(textContentSchema), '#text', 'br').forEach(tag => { |
|
1674 textContentSchema[tag].children = Object(external_lodash_["omit"])(textContentSchema, tag); |
|
1675 }); |
|
1676 /** |
|
1677 * Embedded content elements. |
|
1678 * |
|
1679 * @see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#embedded-content-0 |
|
1680 * |
|
1681 * @type {ContentSchema} |
|
1682 */ |
|
1683 |
|
1684 const embeddedContentSchema = { |
|
1685 audio: { |
|
1686 attributes: ['src', 'preload', 'autoplay', 'mediagroup', 'loop', 'muted'] |
|
1687 }, |
|
1688 canvas: { |
|
1689 attributes: ['width', 'height'] |
|
1690 }, |
|
1691 embed: { |
|
1692 attributes: ['src', 'type', 'width', 'height'] |
|
1693 }, |
|
1694 img: { |
|
1695 attributes: ['alt', 'src', 'srcset', 'usemap', 'ismap', 'width', 'height'] |
|
1696 }, |
|
1697 object: { |
|
1698 attributes: ['data', 'type', 'name', 'usemap', 'form', 'width', 'height'] |
|
1699 }, |
|
1700 video: { |
|
1701 attributes: ['src', 'poster', 'preload', 'autoplay', 'mediagroup', 'loop', 'muted', 'controls', 'width', 'height'] |
|
1702 } |
|
1703 }; |
|
1704 /** |
|
1705 * Phrasing content elements. |
|
1706 * |
|
1707 * @see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#phrasing-content-0 |
|
1708 */ |
|
1709 |
|
1710 const phrasingContentSchema = { ...textContentSchema, |
|
1711 ...embeddedContentSchema |
|
1712 }; |
|
1713 /** |
|
1714 * Get schema of possible paths for phrasing content. |
|
1715 * |
|
1716 * @see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content |
|
1717 * |
|
1718 * @param {string} [context] Set to "paste" to exclude invisible elements and |
|
1719 * sensitive data. |
|
1720 * |
|
1721 * @return {Partial<ContentSchema>} Schema. |
|
1722 */ |
|
1723 |
|
1724 function getPhrasingContentSchema(context) { |
|
1725 if (context !== 'paste') { |
|
1726 return phrasingContentSchema; |
|
1727 } |
|
1728 |
|
1729 return Object(external_lodash_["omit"])({ ...phrasingContentSchema, |
|
1730 // We shouldn't paste potentially sensitive information which is not |
|
1731 // visible to the user when pasted, so strip the attributes. |
|
1732 ins: { |
|
1733 children: phrasingContentSchema.ins.children |
|
1734 }, |
|
1735 del: { |
|
1736 children: phrasingContentSchema.del.children |
|
1737 } |
|
1738 }, ['u', // Used to mark misspelling. Shouldn't be pasted. |
|
1739 'abbr', // Invisible. |
|
1740 'data', // Invisible. |
|
1741 'time', // Invisible. |
|
1742 'wbr', // Invisible. |
|
1743 'bdi', // Invisible. |
|
1744 'bdo' // Invisible. |
|
1745 ]); |
|
1746 } |
|
1747 /** |
|
1748 * Find out whether or not the given node is phrasing content. |
|
1749 * |
|
1750 * @see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content |
|
1751 * |
|
1752 * @param {Node} node The node to test. |
|
1753 * |
|
1754 * @return {boolean} True if phrasing content, false if not. |
|
1755 */ |
|
1756 |
|
1757 function isPhrasingContent(node) { |
|
1758 const tag = node.nodeName.toLowerCase(); |
|
1759 return getPhrasingContentSchema().hasOwnProperty(tag) || tag === 'span'; |
|
1760 } |
|
1761 /** |
|
1762 * @param {Node} node |
|
1763 * @return {boolean} Node is text content |
|
1764 */ |
|
1765 |
|
1766 function isTextContent(node) { |
|
1767 const tag = node.nodeName.toLowerCase(); |
|
1768 return textContentSchema.hasOwnProperty(tag) || tag === 'span'; |
|
1769 } |
|
1770 |
|
1771 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/is-element.js |
|
1772 /* eslint-disable jsdoc/valid-types */ |
|
1773 |
|
1774 /** |
|
1775 * @param {Node | null | undefined} node |
|
1776 * @return {node is Element} True if node is an Element node |
|
1777 */ |
|
1778 function isElement(node) { |
|
1779 /* eslint-enable jsdoc/valid-types */ |
|
1780 return !!node && node.nodeType === node.ELEMENT_NODE; |
|
1781 } |
|
1782 |
|
1783 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/clean-node-list.js |
|
1784 /** |
|
1785 * External dependencies |
|
1786 */ |
|
1787 |
|
1788 /** |
|
1789 * Internal dependencies |
|
1790 */ |
|
1791 |
|
1792 |
|
1793 |
|
1794 |
|
1795 |
|
1796 |
|
1797 |
|
1798 /* eslint-disable jsdoc/valid-types */ |
|
1799 |
|
1800 /** |
|
1801 * @typedef SchemaItem |
|
1802 * @property {string[]} [attributes] Attributes. |
|
1803 * @property {(string | RegExp)[]} [classes] Classnames or RegExp to test against. |
|
1804 * @property {'*' | { [tag: string]: SchemaItem }} [children] Child schemas. |
|
1805 * @property {string[]} [require] Selectors to test required children against. Leave empty or undefined if there are no requirements. |
|
1806 * @property {boolean} allowEmpty Whether to allow nodes without children. |
|
1807 * @property {(node: Node) => boolean} [isMatch] Function to test whether a node is a match. If left undefined any node will be assumed to match. |
|
1808 */ |
|
1809 |
|
1810 /** @typedef {{ [tag: string]: SchemaItem }} Schema */ |
|
1811 |
|
1812 /* eslint-enable jsdoc/valid-types */ |
|
1813 |
|
1814 /** |
|
1815 * Given a schema, unwraps or removes nodes, attributes and classes on a node |
|
1816 * list. |
|
1817 * |
|
1818 * @param {NodeList} nodeList The nodeList to filter. |
|
1819 * @param {Document} doc The document of the nodeList. |
|
1820 * @param {Schema} schema An array of functions that can mutate with the provided node. |
|
1821 * @param {boolean} inline Whether to clean for inline mode. |
|
1822 */ |
|
1823 |
|
1824 function cleanNodeList(nodeList, doc, schema, inline) { |
|
1825 Array.from(nodeList).forEach( |
|
1826 /** @type {Node & { nextElementSibling?: unknown }} */ |
|
1827 node => { |
|
1828 var _schema$tag$isMatch, _schema$tag; |
|
1829 |
|
1830 const tag = node.nodeName.toLowerCase(); // It's a valid child, if the tag exists in the schema without an isMatch |
|
1831 // function, or with an isMatch function that matches the node. |
|
1832 |
|
1833 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))) { |
|
1834 if (isElement(node)) { |
|
1835 const { |
|
1836 attributes = [], |
|
1837 classes = [], |
|
1838 children, |
|
1839 require = [], |
|
1840 allowEmpty |
|
1841 } = schema[tag]; // If the node is empty and it's supposed to have children, |
|
1842 // remove the node. |
|
1843 |
|
1844 if (children && !allowEmpty && isEmpty(node)) { |
|
1845 remove(node); |
|
1846 return; |
|
1847 } |
|
1848 |
|
1849 if (node.hasAttributes()) { |
|
1850 // Strip invalid attributes. |
|
1851 Array.from(node.attributes).forEach(({ |
|
1852 name |
|
1853 }) => { |
|
1854 if (name !== 'class' && !Object(external_lodash_["includes"])(attributes, name)) { |
|
1855 node.removeAttribute(name); |
|
1856 } |
|
1857 }); // Strip invalid classes. |
|
1858 // In jsdom-jscore, 'node.classList' can be undefined. |
|
1859 // TODO: Explore patching this in jsdom-jscore. |
|
1860 |
|
1861 if (node.classList && node.classList.length) { |
|
1862 const mattchers = classes.map(item => { |
|
1863 if (typeof item === 'string') { |
|
1864 return ( |
|
1865 /** @type {string} */ |
|
1866 className => className === item |
|
1867 ); |
|
1868 } else if (item instanceof RegExp) { |
|
1869 return ( |
|
1870 /** @type {string} */ |
|
1871 className => item.test(className) |
|
1872 ); |
|
1873 } |
|
1874 |
|
1875 return external_lodash_["noop"]; |
|
1876 }); |
|
1877 Array.from(node.classList).forEach(name => { |
|
1878 if (!mattchers.some(isMatch => isMatch(name))) { |
|
1879 node.classList.remove(name); |
|
1880 } |
|
1881 }); |
|
1882 |
|
1883 if (!node.classList.length) { |
|
1884 node.removeAttribute('class'); |
|
1885 } |
|
1886 } |
|
1887 } |
|
1888 |
|
1889 if (node.hasChildNodes()) { |
|
1890 // Do not filter any content. |
|
1891 if (children === '*') { |
|
1892 return; |
|
1893 } // Continue if the node is supposed to have children. |
|
1894 |
|
1895 |
|
1896 if (children) { |
|
1897 // If a parent requires certain children, but it does |
|
1898 // not have them, drop the parent and continue. |
|
1899 if (require.length && !node.querySelector(require.join(','))) { |
|
1900 cleanNodeList(node.childNodes, doc, schema, inline); |
|
1901 unwrap(node); // If the node is at the top, phrasing content, and |
|
1902 // contains children that are block content, unwrap |
|
1903 // the node because it is invalid. |
|
1904 } else if (node.parentNode && node.parentNode.nodeName === 'BODY' && isPhrasingContent(node)) { |
|
1905 cleanNodeList(node.childNodes, doc, schema, inline); |
|
1906 |
|
1907 if (Array.from(node.childNodes).some(child => !isPhrasingContent(child))) { |
|
1908 unwrap(node); |
|
1909 } |
|
1910 } else { |
|
1911 cleanNodeList(node.childNodes, doc, children, inline); |
|
1912 } // Remove children if the node is not supposed to have any. |
|
1913 |
|
1914 } else { |
|
1915 while (node.firstChild) { |
|
1916 remove(node.firstChild); |
|
1917 } |
|
1918 } |
|
1919 } |
|
1920 } // Invalid child. Continue with schema at the same place and unwrap. |
|
1921 |
|
1922 } else { |
|
1923 cleanNodeList(node.childNodes, doc, schema, inline); // For inline mode, insert a line break when unwrapping nodes that |
|
1924 // are not phrasing content. |
|
1925 |
|
1926 if (inline && !isPhrasingContent(node) && node.nextElementSibling) { |
|
1927 insertAfter(doc.createElement('br'), node); |
|
1928 } |
|
1929 |
|
1930 unwrap(node); |
|
1931 } |
|
1932 }); |
|
1933 } |
|
1934 |
|
1935 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/remove-invalid-html.js |
|
1936 /** |
|
1937 * Internal dependencies |
|
1938 */ |
|
1939 |
|
1940 /** |
|
1941 * Given a schema, unwraps or removes nodes, attributes and classes on HTML. |
|
1942 * |
|
1943 * @param {string} HTML The HTML to clean up. |
|
1944 * @param {import('./clean-node-list').Schema} schema Schema for the HTML. |
|
1945 * @param {boolean} inline Whether to clean for inline mode. |
|
1946 * |
|
1947 * @return {string} The cleaned up HTML. |
|
1948 */ |
|
1949 |
|
1950 function removeInvalidHTML(HTML, schema, inline) { |
|
1951 const doc = document.implementation.createHTMLDocument(''); |
|
1952 doc.body.innerHTML = HTML; |
|
1953 cleanNodeList(doc.body.childNodes, doc, schema, inline); |
|
1954 return doc.body.innerHTML; |
|
1955 } |
|
1956 |
|
1957 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/safe-html.js |
|
1958 /** |
|
1959 * Internal dependencies |
|
1960 */ |
|
1961 |
|
1962 /** |
|
1963 * Strips scripts and on* attributes from HTML. |
|
1964 * |
|
1965 * @param {string} html HTML to sanitize. |
|
1966 * |
|
1967 * @return {string} The sanitized HTML. |
|
1968 */ |
|
1969 |
|
1970 function safeHTML(html) { |
|
1971 const { |
|
1972 body |
|
1973 } = document.implementation.createHTMLDocument(''); |
|
1974 body.innerHTML = html; |
|
1975 const elements = body.getElementsByTagName('*'); |
|
1976 let elementIndex = elements.length; |
|
1977 |
|
1978 while (elementIndex--) { |
|
1979 const element = elements[elementIndex]; |
|
1980 |
|
1981 if (element.tagName === 'SCRIPT') { |
|
1982 remove(element); |
|
1983 } else { |
|
1984 let attributeIndex = element.attributes.length; |
|
1985 |
|
1986 while (attributeIndex--) { |
|
1987 const { |
|
1988 name: key |
|
1989 } = element.attributes[attributeIndex]; |
|
1990 |
|
1991 if (key.startsWith('on')) { |
|
1992 element.removeAttribute(key); |
|
1993 } |
|
1994 } |
|
1995 } |
|
1996 } |
|
1997 |
|
1998 return body.innerHTML; |
|
1999 } |
|
2000 |
|
2001 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/dom/index.js |
|
2002 |
|
2003 |
|
2004 |
|
2005 |
|
2006 |
|
2007 |
|
2008 |
|
2009 |
|
2010 |
|
2011 |
|
2012 |
|
2013 |
|
2014 |
|
2015 |
|
2016 |
|
2017 |
|
2018 |
|
2019 |
|
2020 |
|
2021 |
|
2022 |
|
2023 |
|
2024 |
|
2025 |
|
2026 |
|
2027 |
|
2028 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/data-transfer.js |
|
2029 /** |
|
2030 * Gets all files from a DataTransfer object. |
|
2031 * |
|
2032 * @param {DataTransfer} dataTransfer DataTransfer object to inspect. |
|
2033 * |
|
2034 * @return {File[]} An array containing all files. |
|
2035 */ |
|
2036 function getFilesFromDataTransfer(dataTransfer) { |
|
2037 const files = Array.from(dataTransfer.files); |
|
2038 Array.from(dataTransfer.items).forEach(item => { |
|
2039 const file = item.getAsFile(); |
|
2040 |
|
2041 if (file && !files.find(({ |
|
2042 name, |
|
2043 type, |
|
2044 size |
|
2045 }) => name === file.name && type === file.type && size === file.size)) { |
|
2046 files.push(file); |
|
2047 } |
|
2048 }); |
|
2049 return files; |
1121 } |
2050 } |
1122 |
2051 |
1123 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/index.js |
2052 // CONCATENATED MODULE: ./node_modules/@wordpress/dom/build-module/index.js |
1124 /** |
2053 /** |
1125 * Internal dependencies |
2054 * Internal dependencies |