From 5863399a3a1bcbbe9b090249504a70496a7af7cc Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Wed, 12 Jul 2017 14:45:41 -0700 Subject: [PATCH] [draft] Fix typing into text nodes containing Tab Summary: lolwaffle chrome is a mystery. if you have like ``` ABC\tDEF ``` (where \t is a tab), typing *anywhere* in that span will break the span into two pieces leaving you with like ``` ABxC\tDEF ``` Test Plan: paste "a\tbc" into text field. type into it. no crashes. --- .../handlers/edit/editOnBeforeInput.js | 106 +++++++++++------- 1 file changed, 65 insertions(+), 41 deletions(-) diff --git a/src/component/handlers/edit/editOnBeforeInput.js b/src/component/handlers/edit/editOnBeforeInput.js index 2c6e18424b..537bd14321 100644 --- a/src/component/handlers/edit/editOnBeforeInput.js +++ b/src/component/handlers/edit/editOnBeforeInput.js @@ -108,6 +108,7 @@ function editOnBeforeInput(editor: DraftEditor, e: SyntheticInputEvent): void { // reduces re-renders and preserves spellcheck highlighting. If the selection // is not collapsed, we will re-render. var selection = editorState.getSelection(); + var anchorKey = selection.getAnchorKey(); if (!selection.isCollapsed()) { e.preventDefault(); @@ -125,9 +126,6 @@ function editOnBeforeInput(editor: DraftEditor, e: SyntheticInputEvent): void { return; } - var mayAllowNative = !isSelectionAtLeafStart( - editor._latestCommittedEditorState, - ); var newEditorState = replaceText( editorState, chars, @@ -138,49 +136,75 @@ function editOnBeforeInput(editor: DraftEditor, e: SyntheticInputEvent): void { ), ); - if (!mayAllowNative) { - e.preventDefault(); - editor.update(newEditorState); - return; + // Bunch of different cases follow where we need to prevent native insertion. + let mustPreventNative = false; + if (!mustPreventNative) { + // Browsers tend to insert text in weird places in the DOM when typing at + // the start of a leaf, so we'll handle it ourselves. + mustPreventNative = isSelectionAtLeafStart( + editor._latestCommittedEditorState, + ); } - - var anchorKey = selection.getAnchorKey(); - var anchorTree = editorState.getBlockTree(anchorKey); - - // Check the old and new "fingerprints" of the current block to determine - // whether this insertion requires any addition or removal of text nodes, - // in which case we would prevent the native character insertion. - var originalFingerprint = BlockTree.getFingerprint(anchorTree); - var newFingerprint = BlockTree.getFingerprint( - newEditorState.getBlockTree(anchorKey), - ); - - if ( - mustPreventDefaultForCharacter(chars) || - originalFingerprint !== newFingerprint || - ( + if (!mustPreventNative) { + // 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(); + // Selection is necessarily collapsed at this point due to earlier check. + if ( + nativeSelection.anchorNode !== null && + nativeSelection.anchorNode.nodeType === Node.TEXT_NODE + ) { + // See isTabHTMLSpanElement in chromium EditingUtilities.cpp. + const parentNode = nativeSelection.anchorNode.parentNode; + mustPreventNative = + parentNode.nodeName === 'SPAN' && + parentNode.firstChild.nodeType === Node.TEXT_NODE && + parentNode.firstChild.nodeValue.indexOf('\t') !== -1; + } + } + if (!mustPreventNative) { + // Check the old and new "fingerprints" of the current block to determine + // whether this insertion requires any addition or removal of text nodes, + // in which case we would prevent the native character insertion. + var originalFingerprint = BlockTree.getFingerprint( + editorState.getBlockTree(anchorKey) + ); + var newFingerprint = BlockTree.getFingerprint( + newEditorState.getBlockTree(anchorKey), + ); + mustPreventNative = originalFingerprint !== newFingerprint; + } + if (!mustPreventNative) { + mustPreventNative = mustPreventDefaultForCharacter(chars); + } + if (!mustPreventNative) { + mustPreventNative = nullthrows(newEditorState.getDirectionMap()).get(anchorKey) !== - nullthrows(editorState.getDirectionMap()).get(anchorKey) - ) - ) { + nullthrows(editorState.getDirectionMap()).get(anchorKey); + } + + if (mustPreventNative) { e.preventDefault(); editor.update(newEditorState); - } else { - newEditorState = EditorState.set(newEditorState, { - nativelyRenderedContent: newEditorState.getCurrentContent(), - }); - // The native event is allowed to occur. To allow user onChange handlers to - // change the inserted text, we wait until the text is actually inserted - // before we actually update our state. That way when we rerender, the text - // we see in the DOM will already have been inserted properly. - editor._pendingStateFromBeforeInput = newEditorState; - setImmediate(() => { - if (editor._pendingStateFromBeforeInput !== undefined) { - editor.update(editor._pendingStateFromBeforeInput); - editor._pendingStateFromBeforeInput = undefined; - } - }); + return; } + + // We made it all the way! Let the browser do its thing and insert the char. + newEditorState = EditorState.set(newEditorState, { + nativelyRenderedContent: newEditorState.getCurrentContent(), + }); + // The native event is allowed to occur. To allow user onChange handlers to + // change the inserted text, we wait until the text is actually inserted + // before we actually update our state. That way when we rerender, the text + // we see in the DOM will already have been inserted properly. + editor._pendingStateFromBeforeInput = newEditorState; + setImmediate(() => { + if (editor._pendingStateFromBeforeInput !== undefined) { + editor.update(editor._pendingStateFromBeforeInput); + editor._pendingStateFromBeforeInput = undefined; + } + }); } module.exports = editOnBeforeInput;