diff --git a/.changeset/mean-ears-shop.md b/.changeset/mean-ears-shop.md new file mode 100644 index 0000000000..d402f8d65c --- /dev/null +++ b/.changeset/mean-ears-shop.md @@ -0,0 +1,6 @@ +--- +'slate-react': patch +--- + +- Restore logic to delay text insertion on android +- Always call Trasform.setSelection before calling Editor.insertText diff --git a/packages/slate-react/src/components/android/android-editable.tsx b/packages/slate-react/src/components/android/android-editable.tsx index 5fe0c0021f..3a85eafc3f 100644 --- a/packages/slate-react/src/components/android/android-editable.tsx +++ b/packages/slate-react/src/components/android/android-editable.tsx @@ -24,7 +24,11 @@ import { IS_READ_ONLY, NODE_TO_ELEMENT, PLACEHOLDER_SYMBOL, + IS_COMPOSING, + IS_ON_COMPOSITION_END, + EDITOR_ON_COMPOSITION_TEXT, } from '../../utils/weak-maps' +import { normalizeTextInsertionRange } from './diff-text' import { EditableProps, hasTarget } from '../editable' import useChildren from '../../hooks/use-children' @@ -523,6 +527,43 @@ export const AndroidEditable = (props: EditableProps): JSX.Element => { setTimeout(() => { state.isComposing && setIsComposing(false) state.isComposing = false + + IS_COMPOSING.set(editor, false) + IS_ON_COMPOSITION_END.set(editor, true) + + const insertedText = + EDITOR_ON_COMPOSITION_TEXT.get(editor) || [] + + // `insertedText` is set in `MutationObserver` constructor. + // If open phone keyboard association function, `CompositionEvent` will be triggered. + if (!insertedText.length) { + return + } + + EDITOR_ON_COMPOSITION_TEXT.set(editor, []) + + const { selection, marks } = editor + + insertedText.forEach(insertion => { + const text = insertion.text.insertText + const at = normalizeTextInsertionRange( + editor, + selection, + insertion + ) + if (marks) { + const node = { text, ...marks } + Transforms.insertNodes(editor, node, { + match: Text.isText, + at, + select: true, + }) + editor.marks = null + } else { + Transforms.setSelection(editor, at) + Editor.insertText(editor, text) + } + }) }, RESOLVE_DELAY) } }, @@ -536,6 +577,7 @@ export const AndroidEditable = (props: EditableProps): JSX.Element => { ) { !state.isComposing && setIsComposing(true) state.isComposing = true + IS_COMPOSING.set(editor, true) } }, [attributes.onCompositionUpdate] @@ -548,6 +590,7 @@ export const AndroidEditable = (props: EditableProps): JSX.Element => { ) { !state.isComposing && setIsComposing(true) state.isComposing = true + IS_COMPOSING.set(editor, true) } }, [attributes.onCompositionStart] diff --git a/packages/slate-react/src/components/android/android-input-manager.ts b/packages/slate-react/src/components/android/android-input-manager.ts index 36e34fb487..6521240b93 100644 --- a/packages/slate-react/src/components/android/android-input-manager.ts +++ b/packages/slate-react/src/components/android/android-input-manager.ts @@ -1,9 +1,16 @@ -import { Editor, Range, Text, Transforms } from 'slate' import { ReactEditor } from '../../plugin/react-editor' +import { Editor, Range, Transforms, Text } from 'slate' +import { + IS_COMPOSING, + IS_ON_COMPOSITION_END, + EDITOR_ON_COMPOSITION_TEXT, +} from '../../utils/weak-maps' + import { DOMNode } from '../../utils/dom' + import { - combineInsertedText, normalizeTextInsertionRange, + combineInsertedText, TextInsertion, } from './diff-text' import { @@ -103,6 +110,17 @@ export class AndroidInputManager { const { selection, marks } = this.editor + // If it is in composing or after `onCompositionend`, set `EDITOR_ON_COMPOSITION_TEXT` and return. + // Text will be inserted on compositionend event. + if ( + IS_COMPOSING.get(this.editor) || + IS_ON_COMPOSITION_END.get(this.editor) + ) { + EDITOR_ON_COMPOSITION_TEXT.set(this.editor, insertedText) + IS_ON_COMPOSITION_END.set(this.editor, false) + return + } + // Insert the batched text diffs insertedText.forEach(insertion => { const text = insertion.text.insertText diff --git a/packages/slate-react/src/utils/weak-maps.ts b/packages/slate-react/src/utils/weak-maps.ts index 8045480484..6b5bcb471b 100644 --- a/packages/slate-react/src/utils/weak-maps.ts +++ b/packages/slate-react/src/utils/weak-maps.ts @@ -1,5 +1,6 @@ import { Ancestor, Editor, Node } from 'slate' import { Key } from './key' +import { TextInsertion } from '../components/android/diff-text' /** * Two weak maps that allow us rebuild a path given a node. They are populated @@ -32,6 +33,17 @@ export const IS_READ_ONLY: WeakMap = new WeakMap() export const IS_FOCUSED: WeakMap = new WeakMap() export const IS_DRAGGING: WeakMap = new WeakMap() export const IS_CLICKING: WeakMap = new WeakMap() +export const IS_COMPOSING: WeakMap = new WeakMap() +export const IS_ON_COMPOSITION_END: WeakMap = new WeakMap() + +/** + * Weak maps for saving text on composition stage. + */ + +export const EDITOR_ON_COMPOSITION_TEXT: WeakMap< + Editor, + TextInsertion[] +> = new WeakMap() /** * Weak map for associating the context `onChange` context with the plugin.