Skip to content
This repository has been archived by the owner on Feb 6, 2023. It is now read-only.

Support rendering Draft-js into iframe #1877

Closed
wants to merge 10 commits into from
2 changes: 1 addition & 1 deletion meta/bundle-size-stats/Draft.js.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion meta/bundle-size-stats/Draft.min.js.json

Large diffs are not rendered by default.

19 changes: 10 additions & 9 deletions src/component/base/DraftEditor.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const DraftEditorEditHandler = require('DraftEditorEditHandler');
const DraftEditorPlaceholder = require('DraftEditorPlaceholder.react');
const DraftEffects = require('DraftEffects');
const EditorState = require('EditorState');
const isHTMLElement = require('isHTMLElement');
const React = require('React');
const ReactDOM = require('ReactDOM');
const Scroll = require('Scroll');
Expand Down Expand Up @@ -444,7 +445,13 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
* ie9-beta-minor-change-list.aspx
*/
if (isIE) {
document.execCommand('AutoUrlDetect', false, false);
// editor can be null after mounting
// https://stackoverflow.com/questions/44074747/componentdidmount-called-before-ref-callback
if (!this.editor) {
global.execCommand('AutoUrlDetect', false, false);
} else {
this.editor.ownerDocument.execCommand('AutoUrlDetect', false, false);
}
}
}

Expand Down Expand Up @@ -478,10 +485,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
const scrollParent = Style.getScrollParent(editorNode);
const {x, y} = scrollPosition || getScrollPosition(scrollParent);

invariant(
editorNode instanceof HTMLElement,
'editorNode is not an HTMLElement',
);
invariant(isHTMLElement(editorNode), 'editorNode is not an HTMLElement');

editorNode.focus();

Expand All @@ -505,10 +509,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {

blur = (): void => {
const editorNode = ReactDOM.findDOMNode(this.editor);
invariant(
editorNode instanceof HTMLElement,
'editorNode is not an HTMLElement',
);
invariant(isHTMLElement(editorNode), 'editorNode is not an HTMLElement');
editorNode.blur();
};

Expand Down
6 changes: 2 additions & 4 deletions src/component/contents/DraftEditorBlock.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const cx = require('cx');
const getElementPosition = require('getElementPosition');
const getScrollPosition = require('getScrollPosition');
const getViewportDimensions = require('getViewportDimensions');
const isHTMLElement = require('isHTMLElement');
const invariant = require('invariant');
const nullthrows = require('nullthrows');

Expand Down Expand Up @@ -119,10 +120,7 @@ class DraftEditorBlock extends React.Component<Props> {
);
}
} else {
invariant(
blockNode instanceof HTMLElement,
'blockNode is not an HTMLElement',
);
invariant(isHTMLElement(blockNode), 'blockNode is not an HTMLElement');
const blockBottom = blockNode.offsetHeight + blockNode.offsetTop;
const scrollBottom = scrollParent.offsetHeight + scrollPosition.y;
scrollDelta = blockBottom - scrollBottom;
Expand Down
4 changes: 3 additions & 1 deletion src/component/contents/DraftEditorTextNode.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const React = require('React');
const ReactDOM = require('ReactDOM');
const UserAgent = require('UserAgent');

const isElement = require('isElement');
const invariant = require('invariant');

// In IE, spans with <br> tags render as two newlines. By rendering a span
Expand Down Expand Up @@ -81,7 +82,8 @@ class DraftEditorTextNode extends React.Component<Props> {
shouldComponentUpdate(nextProps: Props): boolean {
const node = ReactDOM.findDOMNode(this);
const shouldBeNewline = nextProps.children === '';
invariant(node instanceof Element, 'node is not an Element');

invariant(isElement(node), 'node is not an Element');
if (shouldBeNewline) {
return !isNewline(node);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const Style = require('Style');

const getElementPosition = require('getElementPosition');
const getScrollPosition = require('getScrollPosition');
const isHTMLElement = require('isHTMLElement');
const getViewportDimensions = require('getViewportDimensions');
const Immutable = require('immutable');
const invariant = require('invariant');
Expand Down Expand Up @@ -253,10 +254,7 @@ class DraftEditorBlockNode extends React.Component<Props> {
);
}
} else {
invariant(
blockNode instanceof HTMLElement,
'blockNode is not an HTMLElement',
);
invariant(isHTMLElement(blockNode), 'blockNode is not an HTMLElement');
const blockBottom = blockNode.offsetHeight + blockNode.offsetTop;
const scrollBottom = scrollParent.offsetHeight + scrollPosition.y;
scrollDelta = blockBottom - scrollBottom;
Expand Down
6 changes: 4 additions & 2 deletions src/component/handlers/drag/DraftEditorDragHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ function getSelectionForEvent(
/* $FlowFixMe(>=0.68.0 site=www,mobile) This comment suppresses an error
* found when Flow v0.68 was deployed. To see the error delete this comment
* and run Flow. */
if (typeof document.caretRangeFromPoint === 'function') {
const dropRange = document.caretRangeFromPoint(event.x, event.y);
const {ownerDocument} = event.currentTarget;

if (typeof ownerDocument.caretRangeFromPoint === 'function') {
const dropRange = ownerDocument.caretRangeFromPoint(event.x, event.y);
node = dropRange.startContainer;
offset = dropRange.startOffset;
} else if (event.rangeParent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const removeTextWithStrategy = require('removeTextWithStrategy');

function keyCommandBackspaceToStartOfLine(
editorState: EditorState,
e: SyntheticKeyboardEvent<HTMLElement>,
): EditorState {
const afterRemoval = removeTextWithStrategy(
editorState,
Expand All @@ -30,8 +31,8 @@ function keyCommandBackspaceToStartOfLine(
if (selection.isCollapsed() && selection.getAnchorOffset() === 0) {
return moveSelectionBackward(strategyState, 1);
}

const domSelection = global.getSelection();
const {ownerDocument} = e.currentTarget;
const domSelection = ownerDocument.defaultView.getSelection();
let range = domSelection.getRangeAt(0);
range = expandRangeToStartOfLine(range);

Expand Down
9 changes: 7 additions & 2 deletions src/component/handlers/edit/editOnBeforeInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function replaceText(
*/
function editOnBeforeInput(
editor: DraftEditor,
e: SyntheticInputEvent<>,
e: SyntheticInputEvent<HTMLElement>,
): void {
if (editor._pendingStateFromBeforeInput !== undefined) {
editor.update(editor._pendingStateFromBeforeInput);
Expand Down Expand Up @@ -161,7 +161,12 @@ function editOnBeforeInput(
// Chrome will also split up a node into two pieces if it contains a Tab
// char, for no explicable reason. Seemingly caused by this commit:
// https://chromium.googlesource.com/chromium/src/+/013ac5eaf3%5E%21/
const nativeSelection = global.getSelection();

// in test environment, e.target is not available
const nativeSelection = (e.currentTarget
? e.currentTarget.ownerDocument.defaultView
: global
).getSelection();
// Selection is necessarily collapsed at this point due to earlier check.
if (
nativeSelection.anchorNode &&
Expand Down
7 changes: 4 additions & 3 deletions src/component/handlers/edit/editOnBlur.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const EditorState = require('EditorState');
const containsNode = require('containsNode');
const getActiveElement = require('getActiveElement');

function editOnBlur(editor: DraftEditor, e: SyntheticEvent<>): void {
function editOnBlur(editor: DraftEditor, e: SyntheticEvent<HTMLElement>): void {
// In a contentEditable element, when you select a range and then click
// another active element, this does trigger a `blur` event but will not
// remove the DOM selection from the contenteditable.
Expand All @@ -29,8 +29,9 @@ function editOnBlur(editor: DraftEditor, e: SyntheticEvent<>): void {
// We therefore force the issue to be certain, checking whether the active
// element is `body` to force it when blurring occurs within the window (as
// opposed to clicking to another tab or window).
if (getActiveElement() === document.body) {
const selection = global.getSelection();
const {ownerDocument} = e.currentTarget;
if (getActiveElement(ownerDocument) === ownerDocument.body) {
const selection = ownerDocument.defaultView.getSelection();
const editorNode = editor.editor;
if (
selection.rangeCount === 1 &&
Expand Down
4 changes: 2 additions & 2 deletions src/component/handlers/edit/editOnCut.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type DraftEditor from 'DraftEditor.react';
const DraftModifier = require('DraftModifier');
const EditorState = require('EditorState');
const Style = require('Style');

const isNode = require('isInstanceOfNode');
const getFragmentFromSelection = require('getFragmentFromSelection');
const getScrollPosition = require('getScrollPosition');

Expand All @@ -45,7 +45,7 @@ function editOnCut(editor: DraftEditor, e: SyntheticClipboardEvent<>): void {

// Track the current scroll position so that it can be forced back in place
// after the editor regains control of the DOM.
if (element instanceof Node) {
if (isNode(element)) {
scrollPosition = getScrollPosition(Style.getScrollParent(element));
}

Expand Down
5 changes: 3 additions & 2 deletions src/component/handlers/edit/editOnInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ function editOnInput(editor: DraftEditor): void {
editor.update(editor._pendingStateFromBeforeInput);
editor._pendingStateFromBeforeInput = undefined;
}

const domSelection = global.getSelection();
// at this point editor is not null for sure (after input)
const castedEditorElement: HTMLElement = (editor.editor: any);
const domSelection = castedEditorElement.ownerDocument.defaultView.getSelection();

const {anchorNode, isCollapsed} = domSelection;
const isNotTextNode = anchorNode.nodeType !== Node.TEXT_NODE;
Expand Down
10 changes: 7 additions & 3 deletions src/component/handlers/edit/editOnKeyDown.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const isChrome = UserAgent.isBrowser('Chrome');
function onKeyCommand(
command: DraftEditorCommand | string,
editorState: EditorState,
e: SyntheticKeyboardEvent<HTMLElement>,
): EditorState {
switch (command) {
case 'redo':
Expand All @@ -57,7 +58,7 @@ function onKeyCommand(
case 'backspace-word':
return keyCommandBackspaceWord(editorState);
case 'backspace-to-start-of-line':
return keyCommandBackspaceToStartOfLine(editorState);
return keyCommandBackspaceToStartOfLine(editorState, e);
case 'split-block':
return keyCommandInsertNewline(editorState);
case 'transpose-characters':
Expand All @@ -84,7 +85,10 @@ function onKeyCommand(
* See `getDefaultKeyBinding` for defaults. Alternatively, the top-level
* component may provide a custom mapping via the `keyBindingFn` prop.
*/
function editOnKeyDown(editor: DraftEditor, e: SyntheticKeyboardEvent<>): void {
function editOnKeyDown(
editor: DraftEditor,
e: SyntheticKeyboardEvent<HTMLElement>,
): void {
const keyCode = e.which;
const editorState = editor._latestEditorState;
function callDeprecatedHandler(
Expand Down Expand Up @@ -192,7 +196,7 @@ function editOnKeyDown(editor: DraftEditor, e: SyntheticKeyboardEvent<>): void {
return;
}

const newState = onKeyCommand(command, editorState);
const newState = onKeyCommand(command, editorState, e);
if (newState !== editorState) {
editor.update(newState);
}
Expand Down
3 changes: 2 additions & 1 deletion src/component/handlers/edit/editOnSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const ReactDOM = require('ReactDOM');

const getDraftEditorSelection = require('getDraftEditorSelection');
const invariant = require('invariant');
const isHTMLElement = require('isHTMLElement');

function editOnSelect(editor: DraftEditor): void {
if (
Expand All @@ -44,7 +45,7 @@ function editOnSelect(editor: DraftEditor): void {
const editorNode = ReactDOM.findDOMNode(editor.editorContainer);
invariant(editorNode, 'Missing editorNode');
invariant(
editorNode.firstChild instanceof HTMLElement,
isHTMLElement(editorNode.firstChild),
'editorNode.firstChild is not an HTMLElement',
);
const documentSelection = getDraftEditorSelection(
Expand Down
7 changes: 4 additions & 3 deletions src/component/selection/expandRangeToStartOfLine.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
const UnicodeUtils = require('UnicodeUtils');

const getRangeClientRects = require('getRangeClientRects');
const getCorrectDocumentFromNode = require('getCorrectDocumentFromNode');
const invariant = require('invariant');

/**
* Return the computed line height, in pixels, for the provided element.
*/
function getLineHeightPx(element: Element): number {
const computed = getComputedStyle(element);
const div = document.createElement('div');
const correctDocument = getCorrectDocumentFromNode(element);
const div = correctDocument.createElement('div');
div.style.fontFamily = computed.fontFamily;
div.style.fontSize = computed.fontSize;
div.style.fontStyle = computed.fontStyle;
Expand All @@ -30,7 +31,7 @@ function getLineHeightPx(element: Element): number {
div.style.position = 'absolute';
div.textContent = 'M';

const documentBody = document.body;
const documentBody = correctDocument.body;
invariant(documentBody, 'Missing document.body');

// forced layout here
Expand Down
7 changes: 5 additions & 2 deletions src/component/selection/findAncestorOffsetKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
'use strict';

const getSelectionOffsetKeyForNode = require('getSelectionOffsetKeyForNode');

const getCorrectDocumentFromNode = require('getCorrectDocumentFromNode');
/**
* Get the key from the node's nearest offset-aware ancestor.
*/
function findAncestorOffsetKey(node: Node): ?string {
let searchNode = node;
while (searchNode && searchNode !== document.documentElement) {
while (
searchNode &&
searchNode !== getCorrectDocumentFromNode(node).documentElement
) {
const key = getSelectionOffsetKeyForNode(searchNode);
if (key != null) {
return key;
Expand Down
2 changes: 1 addition & 1 deletion src/component/selection/getDraftEditorSelection.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function getDraftEditorSelection(
editorState: EditorState,
root: HTMLElement,
): DOMDerivedSelection {
const selection = global.getSelection();
const selection = root.ownerDocument.defaultView.getSelection();

// No active selection.
if (selection.rangeCount === 0) {
Expand Down
15 changes: 11 additions & 4 deletions src/component/selection/getDraftEditorSelectionWithNodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type EditorState from 'EditorState';
const findAncestorOffsetKey = require('findAncestorOffsetKey');
const getSelectionOffsetKeyForNode = require('getSelectionOffsetKeyForNode');
const getUpdatedSelectionState = require('getUpdatedSelectionState');
const isElement = require('isElement');
const invariant = require('invariant');
const nullthrows = require('nullthrows');

Expand Down Expand Up @@ -125,8 +126,8 @@ function getFirstLeaf(node: any): Node {
while (
node.firstChild &&
// data-blocks has no offset
((node.firstChild instanceof Element &&
node.firstChild.getAttribute('data-blocks') === 'true') ||
((isElement(node.firstChild) &&
(node.firstChild: Element).getAttribute('data-blocks') === 'true') ||
getSelectionOffsetKeyForNode(node.firstChild))
) {
node = node.firstChild;
Expand All @@ -141,7 +142,7 @@ function getLastLeaf(node: any): Node {
while (
node.lastChild &&
// data-blocks has no offset
((node.lastChild instanceof Element &&
((isElement(node.lastChild) &&
node.lastChild.getAttribute('data-blocks') === 'true') ||
getSelectionOffsetKeyForNode(node.lastChild))
) {
Expand All @@ -168,8 +169,14 @@ function getPointForNonTextNode(
// wrapper.
if (editorRoot === node) {
node = node.firstChild;
invariant(isElement(node), 'Invalid DraftEditorContents node.');
const castedNode: Element = (node: any);

// assignment only added for flow :/
// otherwise it throws in line 200 saying that node can be null or undefined
node = castedNode;
invariant(
node instanceof Element && node.getAttribute('data-contents') === 'true',
node.getAttribute('data-contents') === 'true',
'Invalid DraftEditorContents structure.',
);
if (childOffset > 0) {
Expand Down
13 changes: 9 additions & 4 deletions src/component/selection/getSelectionOffsetKeyForNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@
* Get offset key from a node or it's child nodes. Return the first offset key
* found on the DOM tree of given node.
*/
const isElement = require('isElement');

function getSelectionOffsetKeyForNode(node: Node): ?string {
if (node instanceof Element) {
const offsetKey = node.getAttribute('data-offset-key');
if (isElement(node)) {
const castedNode: Element = (node: any);
const offsetKey = castedNode.getAttribute('data-offset-key');
if (offsetKey) {
return offsetKey;
}
for (let ii = 0; ii < node.childNodes.length; ii++) {
const childOffsetKey = getSelectionOffsetKeyForNode(node.childNodes[ii]);
for (let ii = 0; ii < castedNode.childNodes.length; ii++) {
const childOffsetKey = getSelectionOffsetKeyForNode(
castedNode.childNodes[ii],
);
if (childOffsetKey) {
return childOffsetKey;
}
Expand Down
2 changes: 2 additions & 0 deletions src/component/selection/getVisibleSelectionRect.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const getRangeBoundingClientRect = require('getRangeBoundingClientRect');
* Return the bounding ClientRect for the visible DOM selection, if any.
* In cases where there are no selected ranges or the bounding rect is
* temporarily invalid, return null.
*
* When using from an iframe, you should pass the iframe window object
*/
function getVisibleSelectionRect(global: any): ?FakeClientRect {
const selection = global.getSelection();
Expand Down
Loading