From 1b7f2ddcb659d7e4e2ce797306b28809ce6acbad Mon Sep 17 00:00:00 2001 From: Ella van Durpe Date: Tue, 24 Mar 2020 13:33:40 +0100 Subject: [PATCH] wip --- .../components/block-list/block-wrapper.js | 4 +- .../block-list/use-multi-selection.js | 53 +++++++---- packages/block-editor/src/utils/dom.js | 4 +- packages/components/src/popover/index.js | 34 ++++++- packages/dom/src/dom.js | 32 ++++--- .../src/components/visual-editor/index.js | 88 ++++++++++++++----- .../src/components/visual-editor/style.scss | 1 - .../rich-text/src/component/boundary-style.js | 23 ++--- packages/rich-text/src/component/index.js | 48 +++++++--- .../rich-text/src/component/inline-warning.js | 6 +- packages/rich-text/src/create.js | 16 ++-- packages/rich-text/src/to-dom.js | 45 +++++----- 12 files changed, 236 insertions(+), 118 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block-wrapper.js b/packages/block-editor/src/components/block-list/block-wrapper.js index c728b2a8a01364..c16620f4487a2f 100644 --- a/packages/block-editor/src/components/block-list/block-wrapper.js +++ b/packages/block-editor/src/components/block-list/block-wrapper.js @@ -97,11 +97,13 @@ const BlockComponent = forwardRef( * When a block becomes selected, transition focus to an inner tabbable. */ const focusTabbable = () => { + const { ownerDocument } = wrapper.current; + // Focus is captured by the wrapper node, so while focus transition // should only consider tabbables within editable display, since it // may be the wrapper itself or a side control which triggered the // focus event, don't unnecessary transition to an inner tabbable. - if ( wrapper.current.contains( document.activeElement ) ) { + if ( wrapper.current.contains( ownerDocument.activeElement ) ) { return; } diff --git a/packages/block-editor/src/components/block-list/use-multi-selection.js b/packages/block-editor/src/components/block-list/use-multi-selection.js index 1e77f8711a51de..21cff5cc1ed0c1 100644 --- a/packages/block-editor/src/components/block-list/use-multi-selection.js +++ b/packages/block-editor/src/components/block-list/use-multi-selection.js @@ -91,15 +91,21 @@ export default function useMultiSelection( ref ) { * select the entire block contents. */ useEffect( () => { + const { ownerDocument } = ref.current; + const { defaultView } = ownerDocument; + if ( ! hasMultiSelection || isMultiSelecting ) { if ( ! selectedBlockClientId || isMultiSelecting ) { return; } - const selection = window.getSelection(); + const selection = defaultView.getSelection(); if ( selection.rangeCount && ! selection.isCollapsed ) { - const blockNode = getBlockDOMNode( selectedBlockClientId ); + const blockNode = getBlockDOMNode( + selectedBlockClientId, + ownerDocument + ); const { startContainer, endContainer } = selection.getRangeAt( 0 ); @@ -125,11 +131,11 @@ export default function useMultiSelection( ref ) { const start = multiSelectedBlockClientIds[ 0 ]; const end = multiSelectedBlockClientIds[ length - 1 ]; - let startNode = getBlockDOMNode( start ); - let endNode = getBlockDOMNode( end ); + let startNode = getBlockDOMNode( start, ownerDocument ); + let endNode = getBlockDOMNode( end, ownerDocument ); - const selection = window.getSelection(); - const range = document.createRange(); + const selection = defaultView.getSelection(); + const range = ownerDocument.createRange(); // The most stable way to select the whole block contents is to start // and end at the deepest points. @@ -151,7 +157,9 @@ export default function useMultiSelection( ref ) { const onSelectionChange = useCallback( ( { isSelectionEnd } ) => { - const selection = window.getSelection(); + const { ownerDocument } = ref.current; + const { defaultView } = ownerDocument; + const selection = defaultView.getSelection(); // If no selection is found, end multi selection and enable all rich // text areas. @@ -206,12 +214,17 @@ export default function useMultiSelection( ref ) { * Handles a mouseup event to end the current mouse multi-selection. */ const onSelectionEnd = useCallback( () => { - document.removeEventListener( 'selectionchange', onSelectionChange ); + const { ownerDocument } = ref.current; + const { defaultView } = ownerDocument; + ownerDocument.removeEventListener( + 'selectionchange', + onSelectionChange + ); // Equivalent to attaching the listener once. - window.removeEventListener( 'mouseup', onSelectionEnd ); + defaultView.removeEventListener( 'mouseup', onSelectionEnd ); // The browser selection won't have updated yet at this point, so wait // until the next animation frame to get the browser selection. - rafId.current = window.requestAnimationFrame( () => { + rafId.current = defaultView.requestAnimationFrame( () => { onSelectionChange( { isSelectionEnd: true } ); stopMultiSelect(); } ); @@ -220,12 +233,14 @@ export default function useMultiSelection( ref ) { // Only clean up when unmounting, these are added and cleaned up elsewhere. useEffect( () => () => { - document.removeEventListener( + const { ownerDocument } = ref.current; + const { defaultView } = ownerDocument; + ownerDocument.removeEventListener( 'selectionchange', onSelectionChange ); - window.removeEventListener( 'mouseup', onSelectionEnd ); - window.cancelAnimationFrame( rafId.current ); + defaultView.removeEventListener( 'mouseup', onSelectionEnd ); + defaultView.cancelAnimationFrame( rafId.current ); }, [ onSelectionChange, onSelectionEnd ] ); @@ -240,15 +255,21 @@ export default function useMultiSelection( ref ) { return; } + const { ownerDocument } = ref.current; + const { defaultView } = ownerDocument; + startClientId.current = clientId; - anchorElement.current = document.activeElement; + anchorElement.current = ownerDocument.activeElement; startMultiSelect(); // `onSelectionStart` is called after `mousedown` and `mouseleave` // (from a block). The selection ends when `mouseup` happens anywhere // in the window. - document.addEventListener( 'selectionchange', onSelectionChange ); - window.addEventListener( 'mouseup', onSelectionEnd ); + ownerDocument.addEventListener( + 'selectionchange', + onSelectionChange + ); + defaultView.addEventListener( 'mouseup', onSelectionEnd ); // Removing the contenteditable attributes within the block editor is // essential for selection to work across editable areas. The edible diff --git a/packages/block-editor/src/utils/dom.js b/packages/block-editor/src/utils/dom.js index 727311d06b0b02..bb4fd4d8e12e9e 100644 --- a/packages/block-editor/src/utils/dom.js +++ b/packages/block-editor/src/utils/dom.js @@ -8,8 +8,8 @@ * * @return {Element} Block DOM node. */ -export function getBlockDOMNode( clientId ) { - return document.getElementById( 'block-' + clientId ); +export function getBlockDOMNode( clientId, scope = document ) { + return scope.getElementById( 'block-' + clientId ); } export function getBlockPreviewContainerDOMNode( clientId ) { diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index 2e06f907de8a75..39dd9273f44a2f 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -61,12 +61,30 @@ function computeAnchorRect( return; } - if ( anchorRef instanceof window.Range ) { + if ( anchorRef.endContainer ) { return getRectangleFromRange( anchorRef ); } - if ( anchorRef instanceof window.Element ) { - const rect = anchorRef.getBoundingClientRect(); + const { ownerDocument } = anchorRef; + + if ( ownerDocument ) { + let rect = anchorRef.getBoundingClientRect(); + + if ( ownerDocument !== document ) { + const iframe = Array.from( + document.querySelectorAll( 'iframe' ) + ).find( ( element ) => { + return element.contentDocument === ownerDocument; + } ); + const iframeRect = iframe.getBoundingClientRect(); + + rect = new window.DOMRect( + rect.left + iframeRect.left, + rect.top + iframeRect.top, + rect.width, + rect.height + ); + } if ( shouldAnchorIncludePadding ) { return rect; @@ -446,6 +464,16 @@ const Popover = ( { window.addEventListener( 'resize', refresh ); window.addEventListener( 'scroll', refresh, true ); + Array.from( document.querySelectorAll( 'iframe' ) ).forEach( + ( element ) => { + element.contentWindow.addEventListener( + 'scroll', + refresh, + true + ); + } + ); + let observer; const observeElement = diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index c1ae1fbc17395d..f865963eca0f86 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -82,7 +82,10 @@ function isEdge( container, isReverse, onlyVertical ) { return true; } - const selection = window.getSelection(); + const { ownerDocument } = container; + const { defaultView } = ownerDocument; + + const selection = defaultView.getSelection(); if ( ! selection.rangeCount ) { return false; @@ -104,7 +107,7 @@ function isEdge( container, isReverse, onlyVertical ) { return false; } - const computedStyle = window.getComputedStyle( container ); + const computedStyle = defaultView.getComputedStyle( container ); const lineHeight = parseInt( computedStyle.lineHeight, 10 ) || 0; // Only consider the multiline selection at the edge if the direction is @@ -208,6 +211,7 @@ export function getRectangleFromRange( range ) { } const { startContainer } = range; + const { ownerDocument } = startContainer; // Correct invalid "BR" ranges. The cannot contain any children. if ( startContainer.nodeName === 'BR' ) { @@ -216,7 +220,7 @@ export function getRectangleFromRange( range ) { startContainer ); - range = document.createRange(); + range = ownerDocument.createRange(); range.setStart( parentNode, index ); range.setEnd( parentNode, index ); } @@ -229,7 +233,7 @@ export function getRectangleFromRange( range ) { // // See: https://stackoverflow.com/a/6847328/995445 if ( ! rect ) { - const padNode = document.createTextNode( '\u200b' ); + const padNode = ownerDocument.createTextNode( '\u200b' ); // Do not modify the live range. range = range.cloneRange(); range.insertNode( padNode ); @@ -296,8 +300,10 @@ export function placeCaretAtHorizontalEdge( container, isReverse ) { return; } - const selection = window.getSelection(); - const range = document.createRange(); + const { ownerDocument } = container; + const { defaultView } = ownerDocument; + const selection = defaultView.getSelection(); + const range = ownerDocument.createRange(); range.selectNodeContents( rangeTarget ); range.collapse( ! isReverse ); @@ -407,7 +413,9 @@ export function placeCaretAtVerticalEdge( ? editableRect.bottom - buffer : editableRect.top + buffer; - const range = hiddenCaretRangeFromPoint( document, x, y, container ); + const { ownerDocument } = container; + const { defaultView } = ownerDocument; + const range = hiddenCaretRangeFromPoint( ownerDocument, x, y, container ); if ( ! range || ! container.contains( range.startContainer ) ) { if ( @@ -427,7 +435,7 @@ export function placeCaretAtVerticalEdge( return; } - const selection = window.getSelection(); + const selection = defaultView.getSelection(); selection.removeAllRanges(); selection.addRange( range ); container.focus(); @@ -506,7 +514,9 @@ export function isEntirelySelected( element ) { return true; } - const selection = window.getSelection(); + const { ownerDocument } = element; + const { defaultView } = ownerDocument; + const selection = defaultView.getSelection(); const range = selection.rangeCount ? selection.getRangeAt( 0 ) : null; if ( ! range ) { @@ -552,8 +562,10 @@ export function getScrollContainer( node ) { // Scrollable if scrollable height exceeds displayed... if ( node.scrollHeight > node.clientHeight ) { + const { ownerDocument } = node; + const { defaultView } = ownerDocument; // ...except when overflow is defined to be hidden or visible - const { overflowY } = window.getComputedStyle( node ); + const { overflowY } = defaultView.getComputedStyle( node ); if ( /(auto|scroll)/.test( overflowY ) ) { return node; } diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index b34d8a1375b66c..9907c46babfa81 100644 --- a/packages/edit-post/src/components/visual-editor/index.js +++ b/packages/edit-post/src/components/visual-editor/index.js @@ -16,6 +16,7 @@ import { __experimentalBlockSettingsMenuFirstItem, } from '@wordpress/block-editor'; import { Popover } from '@wordpress/components'; +import { useState, useEffect, createPortal } from '@wordpress/element'; /** * Internal dependencies @@ -23,35 +24,74 @@ import { Popover } from '@wordpress/components'; import BlockInspectorButton from './block-inspector-button'; import { useResizeCanvas } from '../resize-canvas'; +export const IFrame = ( { children, ...props } ) => { + const [ contentRef, setContentRef ] = useState(); + const doc = contentRef && contentRef.contentWindow.document; + + useEffect( () => { + if ( doc ) { + doc.body.style.margin = '0px'; + doc.head.appendChild( + document.getElementById( 'wp-block-editor-css' ).cloneNode() + ); + doc.head.appendChild( + document.getElementById( 'wp-block-library-css' ).cloneNode() + ); + doc.head.appendChild( + document.getElementById( 'wp-edit-blocks-css' ).cloneNode() + ); + doc.head.appendChild( + document + .getElementById( 'twentytwenty-block-editor-styles-css' ) + .cloneNode() + ); + doc.head.appendChild( + document.getElementById( 'wp-components-css' ).cloneNode() + ); + } + }, [ doc ] ); + + return ( + // eslint-disable-next-line jsx-a11y/iframe-has-title + + ); +}; + function VisualEditor() { const inlineStyles = useResizeCanvas(); return ( - - - + <> - - - - - - - - - - - - - <__experimentalBlockSettingsMenuFirstItem> - { ( { onClose } ) => ( - - ) } - - + + ); } diff --git a/packages/edit-post/src/components/visual-editor/style.scss b/packages/edit-post/src/components/visual-editor/style.scss index b29292d18308af..c71074742c1c67 100644 --- a/packages/edit-post/src/components/visual-editor/style.scss +++ b/packages/edit-post/src/components/visual-editor/style.scss @@ -1,6 +1,5 @@ .edit-post-visual-editor { position: relative; - padding-top: 50px; // Default background color so that grey .edit-post-editor-regions__content color doesn't show through. background-color: $white; diff --git a/packages/rich-text/src/component/boundary-style.js b/packages/rich-text/src/component/boundary-style.js index cba9eeaf6b5a3f..3c17da53697041 100644 --- a/packages/rich-text/src/component/boundary-style.js +++ b/packages/rich-text/src/component/boundary-style.js @@ -3,15 +3,6 @@ */ import { useEffect } from '@wordpress/element'; -/** - * Global stylesheet shared by all RichText instances. - */ -const globalStyle = document.createElement( 'style' ); - -const boundarySelector = '*[data-rich-text-format-boundary]'; - -document.head.appendChild( globalStyle ); - /** * Calculates and renders the format boundary style when the active formats * change. @@ -24,19 +15,31 @@ export function BoundaryStyle( { activeFormats, forwardedRef } ) { return; } + const boundarySelector = '*[data-rich-text-format-boundary]'; const element = forwardedRef.current.querySelector( boundarySelector ); if ( ! element ) { return; } - const computedStyle = window.getComputedStyle( element ); + const { ownerDocument } = element; + const { defaultView } = ownerDocument; + const computedStyle = defaultView.getComputedStyle( element ); const newColor = computedStyle.color .replace( ')', ', 0.2)' ) .replace( 'rgb', 'rgba' ); const selector = `.rich-text:focus ${ boundarySelector }`; const rule = `background-color: ${ newColor }`; const style = `${ selector } {${ rule }}`; + const globalStyleId = 'rich-text-boundary-style'; + + let globalStyle = ownerDocument.getElementById( globalStyleId ); + + if ( ! globalStyle ) { + globalStyle = ownerDocument.createElement( 'style' ); + globalStyle.id = globalStyleId; + ownerDocument.head.appendChild( globalStyle ); + } if ( globalStyle.innerHTML !== style ) { globalStyle.innerHTML = style; diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index baa8826f12879e..b0c7241f8bbd45 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -45,7 +45,7 @@ import { InlineWarning } from './inline-warning'; * Browser dependencies */ -const { getSelection, getComputedStyle } = window; +const { getComputedStyle } = window; /** @typedef {import('@wordpress/element').WPSyntheticEvent} WPSyntheticEvent */ @@ -113,9 +113,11 @@ function createPrepareEditableTree( props, prefix ) { /** * If the selection is set on the placeholder element, collapse the selection to * the start (before the placeholder). + * + * @param {Window} defaultView */ -function fixPlaceholderSelection() { - const selection = window.getSelection(); +function fixPlaceholderSelection( defaultView ) { + const selection = defaultView.getSelection(); const { anchorNode, anchorOffset } = selection; if ( anchorNode.nodeType !== anchorNode.ELEMENT_NODE ) { @@ -142,6 +144,8 @@ class RichText extends Component { constructor( { value, selectionStart, selectionEnd } ) { super( ...arguments ); + this.getDocument = this.getDocument.bind( this ); + this.getWindow = this.getWindow.bind( this ); this.onFocus = this.onFocus.bind( this ); this.onBlur = this.onBlur.bind( this ); this.onChange = this.onChange.bind( this ); @@ -186,24 +190,32 @@ class RichText extends Component { } componentWillUnmount() { - document.removeEventListener( + this.getDocument().removeEventListener( 'selectionchange', this.onSelectionChange ); - window.cancelAnimationFrame( this.rafId ); + this.getWindow().cancelAnimationFrame( this.rafId ); } componentDidMount() { this.applyRecord( this.record, { domOnly: true } ); } + getDocument() { + return this.props.forwardedRef.current.ownerDocument; + } + + getWindow() { + return this.getDocument().defaultView; + } + createRecord() { const { __unstableMultilineTag: multilineTag, forwardedRef, preserveWhiteSpace, } = this.props; - const selection = getSelection(); + const selection = this.getWindow().getSelection(); const range = selection.rangeCount > 0 ? selection.getRangeAt( 0 ) : null; @@ -401,9 +413,14 @@ class RichText extends Component { // frame. The event listener for selection changes may be added too late // at this point, but this focus event is still too early to calculate // the selection. - this.rafId = window.requestAnimationFrame( this.onSelectionChange ); + this.rafId = this.getWindow().requestAnimationFrame( + this.onSelectionChange + ); - document.addEventListener( 'selectionchange', this.onSelectionChange ); + this.getDocument().addEventListener( + 'selectionchange', + this.onSelectionChange + ); if ( this.props.setFocusedElement ) { deprecated( 'wp.blockEditor.RichText setFocusedElement prop', { @@ -414,7 +431,7 @@ class RichText extends Component { } onBlur() { - document.removeEventListener( + this.getDocument().removeEventListener( 'selectionchange', this.onSelectionChange ); @@ -514,7 +531,7 @@ class RichText extends Component { // Do not update the selection when characters are being composed as // this rerenders the component and might distroy internal browser // editing state. - document.removeEventListener( + this.getDocument().removeEventListener( 'selectionchange', this.onSelectionChange ); @@ -526,7 +543,10 @@ class RichText extends Component { // input event after composition. this.onInput( { inputType: 'insertText' } ); // Tracking selection changes can be resumed. - document.addEventListener( 'selectionchange', this.onSelectionChange ); + this.getDocument().addEventListener( + 'selectionchange', + this.onSelectionChange + ); } /** @@ -569,7 +589,7 @@ class RichText extends Component { // element, in which case the caret is not visible. We need to set // the caret before the placeholder if that's the case. if ( value.text.length === 0 && start === 0 ) { - fixPlaceholderSelection(); + fixPlaceholderSelection( this.getWindow() ); } return; @@ -933,8 +953,8 @@ class RichText extends Component { const { parentNode } = target; const index = Array.from( parentNode.childNodes ).indexOf( target ); - const range = target.ownerDocument.createRange(); - const selection = getSelection(); + const range = this.getDocument().createRange(); + const selection = this.getWindow().getSelection(); range.setStart( target.parentNode, index ); range.setEnd( target.parentNode, index + 1 ); diff --git a/packages/rich-text/src/component/inline-warning.js b/packages/rich-text/src/component/inline-warning.js index d74858dd912b66..00598b4d7c544d 100644 --- a/packages/rich-text/src/component/inline-warning.js +++ b/packages/rich-text/src/component/inline-warning.js @@ -6,9 +6,9 @@ import { useEffect } from '@wordpress/element'; export function InlineWarning( { forwardedRef } ) { useEffect( () => { if ( process.env.NODE_ENV === 'development' ) { - const computedStyle = window.getComputedStyle( - forwardedRef.current - ); + const target = forwardedRef.current; + const { defaultView } = target.ownerDocument; + const computedStyle = defaultView.getComputedStyle( target ); if ( computedStyle.display === 'inline' ) { // eslint-disable-next-line no-console diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index ca25ef8e09be10..89c8d298da6865 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -16,12 +16,6 @@ import { ZWNBSP, } from './special-characters'; -/** - * Browser dependencies - */ - -const { TEXT_NODE, ELEMENT_NODE } = window.Node; - function createEmptyValue() { return { formats: [], @@ -160,6 +154,8 @@ export function create( { } if ( typeof html === 'string' && html.length > 0 ) { + // It does not matter which document this is, we're just using it to + // parse. element = createElement( document, html ); } @@ -208,7 +204,7 @@ function accumulateSelection( accumulator, node, range, value ) { if ( value.start !== undefined ) { accumulator.start = currentLength + value.start; // Range indicates that the current node has selection. - } else if ( node === startContainer && node.nodeType === TEXT_NODE ) { + } else if ( node === startContainer && node.nodeType === node.TEXT_NODE ) { accumulator.start = currentLength + startOffset; // Range indicates that the current node is selected. } else if ( @@ -231,7 +227,7 @@ function accumulateSelection( accumulator, node, range, value ) { if ( value.end !== undefined ) { accumulator.end = currentLength + value.end; // Range indicates that the current node has selection. - } else if ( node === endContainer && node.nodeType === TEXT_NODE ) { + } else if ( node === endContainer && node.nodeType === node.TEXT_NODE ) { accumulator.end = currentLength + endOffset; // Range indicates that the current node is selected. } else if ( @@ -342,7 +338,7 @@ function createFromElement( { const node = element.childNodes[ index ]; const type = node.nodeName.toLowerCase(); - if ( node.nodeType === TEXT_NODE ) { + if ( node.nodeType === node.TEXT_NODE ) { let filter = removePadding; if ( ! preserveWhiteSpace ) { @@ -361,7 +357,7 @@ function createFromElement( { continue; } - if ( node.nodeType !== ELEMENT_NODE ) { + if ( node.nodeType !== node.ELEMENT_NODE ) { continue; } diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index 3fc585b35f6ba8..b21544d09c1ecd 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -5,12 +5,6 @@ import { toTree } from './to-tree'; import { createElement } from './create-element'; -/** - * Browser dependencies - */ - -const { TEXT_NODE } = window.Node; - /** * Creates a path as an array of indices from the given root node to the given * node. @@ -59,18 +53,6 @@ function getNodeByPath( node, path ) { }; } -/** - * Returns a new instance of a DOM tree upon which RichText operations can be - * applied. - * - * Note: The current implementation will return a shared reference, reset on - * each call to `createEmpty`. Therefore, you should not hold a reference to - * the value to operate upon asynchronously, as it may have unexpected results. - * - * @return {Object} RichText tree. - */ -const createEmpty = () => createElement( document, '' ); - function append( element, child ) { if ( typeof child === 'string' ) { child = element.ownerDocument.createTextNode( child ); @@ -101,8 +83,8 @@ function getParent( { parentNode } ) { return parentNode; } -function isText( { nodeType } ) { - return nodeType === TEXT_NODE; +function isText( node ) { + return node.nodeType === node.TEXT_NODE; } function getText( { nodeValue } ) { @@ -119,6 +101,7 @@ export function toDom( { prepareEditableTree, isEditableTree = true, placeholder, + doc, } ) { let startPath = []; let endPath = []; @@ -130,6 +113,18 @@ export function toDom( { }; } + /** + * Returns a new instance of a DOM tree upon which RichText operations can be + * applied. + * + * Note: The current implementation will return a shared reference, reset on + * each call to `createEmpty`. Therefore, you should not hold a reference to + * the value to operate upon asynchronously, as it may have unexpected results. + * + * @return {Object} RichText tree. + */ + const createEmpty = () => createElement( doc, '' ); + const tree = toTree( { value, multilineTag, @@ -186,6 +181,7 @@ export function apply( { multilineTag, prepareEditableTree, placeholder, + doc: current.ownerDocument, } ); applyValue( body, current ); @@ -207,7 +203,7 @@ export function applyValue( future, current ) { } else if ( ! currentChild.isEqualNode( futureChild ) ) { if ( currentChild.nodeName !== futureChild.nodeName || - ( currentChild.nodeType === TEXT_NODE && + ( currentChild.nodeType === currentChild.TEXT_NODE && currentChild.data !== futureChild.data ) ) { current.replaceChild( futureChild, currentChild ); @@ -282,8 +278,9 @@ export function applySelection( { startPath, endPath }, current ) { current, endPath ); - const selection = window.getSelection(); const { ownerDocument } = current; + const { defaultView } = ownerDocument; + const selection = defaultView.getSelection(); const range = ownerDocument.createRange(); range.setStart( startContainer, startOffset ); @@ -306,13 +303,13 @@ export function applySelection( { startPath, endPath }, current ) { // This function is not intended to cause a shift in focus. Since the above // selection manipulations may shift focus, ensure that focus is restored to // its previous state. - if ( activeElement !== document.activeElement ) { + if ( activeElement !== ownerDocument.activeElement ) { // The `instanceof` checks protect against edge cases where the focused // element is not of the interface HTMLElement (does not have a `focus` // or `blur` property). // // See: https://github.com/Microsoft/TypeScript/issues/5901#issuecomment-431649653 - if ( activeElement instanceof window.HTMLElement ) { + if ( activeElement instanceof defaultView.HTMLElement ) { activeElement.focus(); } }