From 5a20ea3ad8f725b6072b6512f68d48e440da8901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Legi=C4=99=C4=87?= Date: Thu, 26 Jun 2025 20:49:17 +0200 Subject: [PATCH] Fix Android cursor jumping to word start after autocorrect (#5901) * Enhance Android input manager to store current selection before applying diffs and ensure correct selection position post-update. * Add autocorrect test case to Android input examples This update introduces a new test case for autocorrect functionality in both JavaScript and TypeScript examples. The test case instructs users to type "Cant" and verify the cursor position after autocorrection. * Add changeset * Remove unnecessary comments * Drop pending selection change on programatic text insert --- .changeset/pretty-lies-build.md | 5 +++++ .../android-input-manager.ts | 18 ++++++++++++++++++ packages/slate-react/src/plugin/with-react.ts | 18 ++++++++++++++++-- site/examples/js/android-tests.jsx | 12 ++++++++++++ site/examples/ts/android-tests.tsx | 12 ++++++++++++ 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 .changeset/pretty-lies-build.md diff --git a/.changeset/pretty-lies-build.md b/.changeset/pretty-lies-build.md new file mode 100644 index 000000000..cb69132df --- /dev/null +++ b/.changeset/pretty-lies-build.md @@ -0,0 +1,5 @@ +--- +'slate-react': patch +--- + +Fix Android cursor jumping to word start after autocorrect diff --git a/packages/slate-react/src/hooks/android-input-manager/android-input-manager.ts b/packages/slate-react/src/hooks/android-input-manager/android-input-manager.ts index 5a9cbf324..79a886dd2 100644 --- a/packages/slate-react/src/hooks/android-input-manager/android-input-manager.ts +++ b/packages/slate-react/src/hooks/android-input-manager/android-input-manager.ts @@ -689,7 +689,25 @@ export function createAndroidInputManager({ } if (canStoreDiff) { + const currentSelection = editor.selection storeDiff(start.path, diff) + + if (currentSelection) { + const newPoint = { + path: start.path, + offset: start.offset + text.length, + } + + scheduleAction( + () => { + Transforms.select(editor, { + anchor: newPoint, + focus: newPoint, + }) + }, + { at: newPoint } + ) + } return } } diff --git a/packages/slate-react/src/plugin/with-react.ts b/packages/slate-react/src/plugin/with-react.ts index b8258e1a8..361930e46 100644 --- a/packages/slate-react/src/plugin/with-react.ts +++ b/packages/slate-react/src/plugin/with-react.ts @@ -1,6 +1,6 @@ import ReactDOM from 'react-dom' import { BaseEditor, Node } from 'slate' -import { withDOM } from 'slate-dom' +import { withDOM, IS_ANDROID, EDITOR_TO_PENDING_SELECTION } from 'slate-dom' import { ReactEditor } from './react-editor' import { REACT_MAJOR_VERSION } from '../utils/environment' import { getChunkTreeForNode } from '../chunking' @@ -21,10 +21,24 @@ export const withReact = ( e = withDOM(e, clipboardFormatKey) - const { onChange, apply } = e + const { onChange, apply, insertText } = e e.getChunkSize = () => null + if (IS_ANDROID) { + e.insertText = (text, options) => { + // COMPAT: Android devices, specifically Samsung devices, experience cursor jumping. + // This issue occurs when the ⁠insertText function is called immediately after typing. + // The problem arises because typing schedules a selection change. + // However, this selection change is only executed after the ⁠insertText function. + // As a result, the already obsolete selection is applied, leading to incorrect + // final cursor position. + EDITOR_TO_PENDING_SELECTION.delete(e) + + return insertText(text, options) + } + } + e.onChange = options => { // COMPAT: React < 18 doesn't batch `setState` hook calls, which means // that the children and selection can get out of sync for one render diff --git a/site/examples/js/android-tests.jsx b/site/examples/js/android-tests.jsx index 314562f4a..bdb956cb5 100644 --- a/site/examples/js/android-tests.jsx +++ b/site/examples/js/android-tests.jsx @@ -173,6 +173,18 @@ const TEST_CASES = [ }, ], }, + { + id: 'autocorrect', + name: 'Autocorrect', + instructions: + 'Type "Cant", then press space to autocorrect it. Make sure the cursor position is correct (after the autocorrected word)', + value: [ + { + type: 'paragraph', + children: [{ text: '' }], + }, + ], + }, ] const AndroidTestsExample = () => { const [testId, setTestId] = useState( diff --git a/site/examples/ts/android-tests.tsx b/site/examples/ts/android-tests.tsx index 94a009e8c..a74ae1c3a 100644 --- a/site/examples/ts/android-tests.tsx +++ b/site/examples/ts/android-tests.tsx @@ -180,6 +180,18 @@ const TEST_CASES: AndroidTestCase[] = [ }, ], }, + { + id: 'autocorrect', + name: 'Autocorrect', + instructions: + 'Type "Cant" (make sure to misspell it), then press space to autocorrect it. Make sure the cursor position is correct (after the autocorrected word)', + value: [ + { + type: 'paragraph', + children: [{ text: '' }], + }, + ], + }, ] const AndroidTestsExample = () => {