UNPKG

d2-ui

Version:
141 lines (113 loc) 5.42 kB
/** * Copyright (c) 2013-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule editOnInput * */ 'use strict'; var DraftModifier = require('./DraftModifier'); var DraftOffsetKey = require('./DraftOffsetKey'); var EditorState = require('./EditorState'); var Entity = require('./DraftEntity'); var UserAgent = require('fbjs/lib/UserAgent'); var findAncestorOffsetKey = require('./findAncestorOffsetKey'); var nullthrows = require('fbjs/lib/nullthrows'); var isGecko = UserAgent.isEngine('Gecko'); var DOUBLE_NEWLINE = '\n\n'; /** * This function is intended to handle spellcheck and autocorrect changes, * which occur in the DOM natively without any opportunity to observe or * interpret the changes before they occur. * * The `input` event fires in contentEditable elements reliably for non-IE * browsers, immediately after changes occur to the editor DOM. Since our other * handlers override or otherwise handle cover other varieties of text input, * the DOM state should match the model in all controlled input cases. Thus, * when an `input` change leads to a DOM/model mismatch, the change should be * due to a spellcheck change, and we can incorporate it into our model. */ function editOnInput() { var domSelection = global.getSelection(); var anchorNode = domSelection.anchorNode; var isCollapsed = domSelection.isCollapsed; if (anchorNode.nodeType !== Node.TEXT_NODE) { return; } var domText = anchorNode.textContent; var editorState = this.props.editorState; var offsetKey = nullthrows(findAncestorOffsetKey(anchorNode)); var _DraftOffsetKey$decode = DraftOffsetKey.decode(offsetKey); var blockKey = _DraftOffsetKey$decode.blockKey; var decoratorKey = _DraftOffsetKey$decode.decoratorKey; var leafKey = _DraftOffsetKey$decode.leafKey; var _editorState$getBlockTree$getIn = editorState.getBlockTree(blockKey).getIn([decoratorKey, 'leaves', leafKey]); var start = _editorState$getBlockTree$getIn.start; var end = _editorState$getBlockTree$getIn.end; var content = editorState.getCurrentContent(); var block = content.getBlockForKey(blockKey); var modelText = block.getText().slice(start, end); // Special-case soft newlines here. If the DOM text ends in a soft newline, // we will have manually inserted an extra soft newline in DraftEditorLeaf. // We want to remove this extra newline for the purpose of our comparison // of DOM and model text. if (domText.endsWith(DOUBLE_NEWLINE)) { domText = domText.slice(0, -1); } // No change -- the DOM is up to date. Nothing to do here. if (domText === modelText) { return; } var selection = editorState.getSelection(); // We'll replace the entire leaf with the text content of the target. var targetRange = selection.merge({ anchorOffset: start, focusOffset: end, isBackward: false }); var entityKey = block.getEntityAt(start); var entity = entityKey && Entity.get(entityKey); var entityType = entity && entity.getMutability(); var preserveEntity = entityType === 'MUTABLE'; // Immutable or segmented entities cannot properly be handled by the // default browser undo, so we have to use a different change type to // force using our internal undo method instead of falling through to the // native browser undo. var changeType = preserveEntity ? 'spellcheck-change' : 'apply-entity'; var newContent = DraftModifier.replaceText(content, targetRange, domText, block.getInlineStyleAt(start), preserveEntity ? block.getEntityAt(start) : null); var anchorOffset, focusOffset, startOffset, endOffset; if (isGecko) { // Firefox selection does not change while the context menu is open, so // we preserve the anchor and focus values of the DOM selection. anchorOffset = domSelection.anchorOffset; focusOffset = domSelection.focusOffset; startOffset = start + Math.min(anchorOffset, focusOffset); endOffset = startOffset + Math.abs(anchorOffset - focusOffset); anchorOffset = startOffset; focusOffset = endOffset; } else { // Browsers other than Firefox may adjust DOM selection while the context // menu is open, and Safari autocorrect is prone to providing an inaccurate // DOM selection. Don't trust it. Instead, use our existing SelectionState // and adjust it based on the number of characters changed during the // mutation. var charDelta = domText.length - modelText.length; startOffset = selection.getStartOffset(); endOffset = selection.getEndOffset(); anchorOffset = isCollapsed ? endOffset + charDelta : startOffset; focusOffset = endOffset + charDelta; } // Segmented entities are completely or partially removed when their // text content changes. For this case we do not want any text to be selected // after the change, so we are not merging the selection. var contentWithAdjustedDOMSelection = newContent.merge({ selectionBefore: content.getSelectionAfter(), selectionAfter: selection.merge({ anchorOffset: anchorOffset, focusOffset: focusOffset }) }); this.update(EditorState.push(editorState, contentWithAdjustedDOMSelection, changeType)); } module.exports = editOnInput;