diff --git a/packages/super-editor/src/core/presentation-editor/PresentationEditor.ts b/packages/super-editor/src/core/presentation-editor/PresentationEditor.ts index de9ab24639..5716762247 100644 --- a/packages/super-editor/src/core/presentation-editor/PresentationEditor.ts +++ b/packages/super-editor/src/core/presentation-editor/PresentationEditor.ts @@ -753,6 +753,7 @@ export class PresentationEditor extends EventEmitter { const beforeX = win.scrollX; const beforeY = win.scrollY; + const alreadyFocused = view.hasFocus(); let focused = false; // Strategy 1: Try focus with preventScroll option (modern browsers) @@ -791,6 +792,16 @@ export class PresentationEditor extends EventEmitter { } } + // When the editor was not focused before, the browser places the DOM selection + // at an arbitrary position inside the off-screen contenteditable. ProseMirror's + // DOMObserver would read this stale position via a selectionchange event and + // overwrite PM state, causing the cursor to jump. Suppress selection updates + // for the next 50ms so PM re-applies its own selection to the DOM instead. + if (!alreadyFocused) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (view as any).domObserver.suppressSelectionUpdates(); + } + // Restore scroll position if any focus attempt changed it if (win.scrollX !== beforeX || win.scrollY !== beforeY) { win.scrollTo(beforeX, beforeY); diff --git a/packages/super-editor/src/core/presentation-editor/tests/PresentationEditor.draggableFocus.test.ts b/packages/super-editor/src/core/presentation-editor/tests/PresentationEditor.draggableFocus.test.ts index f4d8b95657..5183eaa622 100644 --- a/packages/super-editor/src/core/presentation-editor/tests/PresentationEditor.draggableFocus.test.ts +++ b/packages/super-editor/src/core/presentation-editor/tests/PresentationEditor.draggableFocus.test.ts @@ -118,6 +118,12 @@ vi.mock('../../Editor.js', () => { focus: function () { // Plain function that can be wrapped }, + hasFocus: function () { + return domElement === domElement.ownerDocument.activeElement; + }, + domObserver: { + suppressSelectionUpdates: vi.fn(), + }, dispatch: vi.fn(), }, options: { diff --git a/packages/super-editor/src/core/presentation-editor/tests/PresentationEditor.focusWrapping.test.ts b/packages/super-editor/src/core/presentation-editor/tests/PresentationEditor.focusWrapping.test.ts index 38ce60aadd..394f43d6de 100644 --- a/packages/super-editor/src/core/presentation-editor/tests/PresentationEditor.focusWrapping.test.ts +++ b/packages/super-editor/src/core/presentation-editor/tests/PresentationEditor.focusWrapping.test.ts @@ -133,6 +133,12 @@ vi.mock('../../Editor.js', () => { focus: function () { // Plain function that can be wrapped }, + hasFocus: function () { + return domElement === domElement.ownerDocument.activeElement; + }, + domObserver: { + suppressSelectionUpdates: vi.fn(), + }, dispatch: vi.fn(), }, options: {