diff --git a/apps/web/src/components/editor/tiptap.tsx b/apps/web/src/components/editor/tiptap.tsx index 247a716a7..c2e2308f0 100644 --- a/apps/web/src/components/editor/tiptap.tsx +++ b/apps/web/src/components/editor/tiptap.tsx @@ -298,7 +298,10 @@ function TipTap(props: TipTapProps) { element: editorContainer(), editable: !readonly, content: content?.(), - autofocus: "start", + // Tiptap's autofocus scrolls to the caret a frame after the editor is + // created, which would undo the restored scroll position. The caret is + // placed by the selection restore instead. + autofocus: false, onFocus, onCreate: async ({ editor }) => { setProfiledEditor(editor as Editor); diff --git a/packages/editor/src/hooks/use-editor.ts b/packages/editor/src/hooks/use-editor.ts index 5cddf22ba..f0435ea56 100644 --- a/packages/editor/src/hooks/use-editor.ts +++ b/packages/editor/src/hooks/use-editor.ts @@ -70,7 +70,9 @@ export const useEditor = ( editor.createView(); }); } - if (oldIsFocused && !editor.isFocused) editor.commands.focus(); + // Restoring focus must not drag the view to the caret. + if (oldIsFocused && !editor.isFocused) + editor.commands.focus(null, { scrollIntoView: false }); // The Editor constructor emits `create` for the view it made, so calling // the option here as well would run every consumer twice on load. if (!firstRun) options.onCreate?.({ editor: editor });