in src/component/handlers/edit/editOnInput.js [62:226]
function editOnInput(editor: DraftEditor, event: ?SyntheticInputEvent<>): void {
// This will happen for most simple insertions. The new state is already
// computed. Let's just call "editor.update". Things should match nicely so
// this function will exit below where we check "domText === modelText".
if (editor._pendingStateFromBeforeInput !== undefined) {
editor.update(editor._pendingStateFromBeforeInput);
editor._pendingStateFromBeforeInput = undefined;
}
// at this point editor is not null for sure (after input)
const castedEditorElement: HTMLElement = (editor.editor: any);
const domSelection: SelectionObject =
castedEditorElement.ownerDocument.defaultView.getSelection();
const {anchorNode, isCollapsed} = domSelection;
const isNotTextOrElementNode =
anchorNode?.nodeType !== Node.TEXT_NODE &&
anchorNode?.nodeType !== Node.ELEMENT_NODE;
if (anchorNode == null || isNotTextOrElementNode) {
// TODO: (t16149272) figure out context for this change
return;
}
if (
anchorNode.nodeType === Node.TEXT_NODE &&
(anchorNode.previousSibling !== null || anchorNode.nextSibling !== null)
) {
// When typing at the beginning of a visual line, Chrome splits the text
// nodes into two. Why? No one knows. This commit is suspicious:
// https://chromium.googlesource.com/chromium/src/+/a3b600981286b135632371477f902214c55a1724
// To work around, we'll merge the sibling text nodes back into this one.
const span = anchorNode.parentNode;
if (span == null) {
// Handle null-parent case.
return;
}
anchorNode.nodeValue = span.textContent;
for (
let child = span.firstChild;
child != null;
child = child.nextSibling
) {
if (child !== anchorNode) {
span.removeChild(child);
}
}
}
let domText = anchorNode.textContent;
const editorState = editor._latestEditorState;
const offsetKey = nullthrows(findAncestorOffsetKey(anchorNode));
const {blockKey, decoratorKey, leafKey} = DraftOffsetKey.decode(offsetKey);
const {start, end} = editorState
.getBlockTree(blockKey)
.getIn([decoratorKey, 'leaves', leafKey]);
const content = editorState.getCurrentContent();
const block = content.getBlockForKey(blockKey);
const 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) {
// This can be buggy for some Android keyboards because they don't fire
// standard onkeydown/pressed events and only fired editOnInput
// so domText is already changed by the browser and ends up being equal
// to modelText unexpectedly.
// Newest versions of Android support the dom-inputevent-inputtype
// and we can use the `inputType` to properly apply the state changes.
/* $FlowFixMe[prop-missing] inputType is only defined on a draft of a
* standard. https://w3c.github.io/input-events/#dom-inputevent-inputtype
*/
const inputType = event ? event.nativeEvent.inputType : undefined;
if (inputType) {
const newEditorState = onInputType(inputType, editorState);
if (newEditorState !== editorState) {
editor.restoreEditorDOM();
editor.update(newEditorState);
return;
}
}
return;
}
const selection = editorState.getSelection();
// We'll replace the entire leaf with the text content of the target.
const targetRange = selection.merge({
anchorOffset: start,
focusOffset: end,
isBackward: false,
});
const entityKey = block.getEntityAt(start);
const entity = notEmptyKey(entityKey) ? content.getEntity(entityKey) : null;
const entityType = entity != null ? entity.getMutability() : null;
const 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.
const changeType = preserveEntity ? 'spellcheck-change' : 'apply-entity';
const newContent = DraftModifier.replaceText(
content,
targetRange,
domText,
block.getInlineStyleAt(start),
preserveEntity ? block.getEntityAt(start) : null,
);
let anchorOffset, focusOffset, startOffset, endOffset;
const isDeleteWordForward =
// $FlowFixMe[prop-missing] Flow doesn't know if can be an InputEvent w/ inputType
event?.nativeEvent?.inputType === 'deleteWordForward';
// Adjust our selection if appropriate. If we're deleting the word forward, we
// don't do this, since we want to stay at the same offset.
if (isGecko && !isDeleteWordForward) {
// 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 if (!isDeleteWordForward) {
// 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.
const 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.
const contentWithAdjustedDOMSelection = newContent.merge({
selectionBefore: content.getSelectionAfter(),
selectionAfter: selection.merge({anchorOffset, focusOffset}),
});
editor.update(
EditorState.push(editorState, contentWithAdjustedDOMSelection, changeType),
);
}