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();
}
}