Allow scrolling to initial EditorState's selection #196

Open
sch wants to merge 2 commits from sch/prosemirror-view:initial-focus into main
First-time contributor

I support an app where we need to be able to preserve the user's rough scroll position when re-opening a document. It's not enough to keep track of the scroll offset: we keep track of the pos and both focus the document and scroll you to it once the view exists.

For some time, we've needed to add a 50ms delay before calling tr.scrollIntoView(). This patch to the demo app simulates it, scrolling to the text at the bottom of the editor:

diff --git a/demo/demo.css b/demo/demo.css
index 06bfe78..813bf22 100644
--- a/demo/demo.css
+++ b/demo/demo.css
@@ -17,6 +17,8 @@ textarea {

 .full {
   max-width: 50em;
+  height: 300px;
+  overflow-y: auto;
 }

 .marked {
diff --git a/demo/demo.ts b/demo/demo.ts
index 48e2c34..80632ec 100644
--- a/demo/demo.ts
+++ b/demo/demo.ts
@@ -1,6 +1,6 @@
 import {Schema, DOMParser} from "prosemirror-model"
 import {EditorView} from "prosemirror-view"
-import {EditorState} from "prosemirror-state"
+import {EditorState, TextSelection} from "prosemirror-state"
 import {schema} from "prosemirror-schema-basic"
 import {addListNodes} from "prosemirror-schema-list"
 import {exampleSetup} from "prosemirror-example-setup"
@@ -10,7 +10,13 @@ const demoSchema = new Schema({
   marks: schema.spec.marks
 })

-let state = EditorState.create({doc: DOMParser.fromSchema(demoSchema).parse(document.querySelector("#content")!),
-                                plugins: exampleSetup({schema: demoSchema})})
+const doc = DOMParser.fromSchema(demoSchema).parse(document.querySelector("#content")!)
+const state = EditorState.create({
+  doc,
+  selection: TextSelection.atEnd(doc),
+  plugins: exampleSetup({schema: demoSchema})
+})

-;(window as any).view = new EditorView(document.querySelector(".full"), {state})
+const view = new EditorView(document.querySelector(".full"), {state})
+view.dispatch(view.state.tr.scrollIntoView())
+;(window as any).view = view

I tracked this down the other day to #1503. That fix makes sense: you want to guard against selection in another editor being used as the starting point for the scrollRectIntoView walk. Instead of returning early though, we should fall back to the dom element of the view that is handling the scroll. That's what this commit does, allowing the scroll position to be adjusted even if the editor is not yet focused.

Let me know if this patch works for you or if there's a better way to initialize the view.

I support an app where we need to be able to preserve the user's rough scroll position when re-opening a document. It's not enough to keep track of the scroll offset: we keep track of the pos and both focus the document and scroll you to it once the view exists. For some time, we've needed to add a 50ms delay before calling `tr.scrollIntoView()`. This patch to the demo app simulates it, scrolling to the text at the bottom of the editor: ```diff diff --git a/demo/demo.css b/demo/demo.css index 06bfe78..813bf22 100644 --- a/demo/demo.css +++ b/demo/demo.css @@ -17,6 +17,8 @@ textarea { .full { max-width: 50em; + height: 300px; + overflow-y: auto; } .marked { diff --git a/demo/demo.ts b/demo/demo.ts index 48e2c34..80632ec 100644 --- a/demo/demo.ts +++ b/demo/demo.ts @@ -1,6 +1,6 @@ import {Schema, DOMParser} from "prosemirror-model" import {EditorView} from "prosemirror-view" -import {EditorState} from "prosemirror-state" +import {EditorState, TextSelection} from "prosemirror-state" import {schema} from "prosemirror-schema-basic" import {addListNodes} from "prosemirror-schema-list" import {exampleSetup} from "prosemirror-example-setup" @@ -10,7 +10,13 @@ const demoSchema = new Schema({ marks: schema.spec.marks }) -let state = EditorState.create({doc: DOMParser.fromSchema(demoSchema).parse(document.querySelector("#content")!), - plugins: exampleSetup({schema: demoSchema})}) +const doc = DOMParser.fromSchema(demoSchema).parse(document.querySelector("#content")!) +const state = EditorState.create({ + doc, + selection: TextSelection.atEnd(doc), + plugins: exampleSetup({schema: demoSchema}) +}) -;(window as any).view = new EditorView(document.querySelector(".full"), {state}) +const view = new EditorView(document.querySelector(".full"), {state}) +view.dispatch(view.state.tr.scrollIntoView()) +;(window as any).view = view ``` I tracked this down the other day to #1503. That fix makes sense: you want to guard against selection in another editor being used as the starting point for the `scrollRectIntoView` walk. Instead of returning early though, we should fall back to the dom element of the view that is handling the scroll. That's what this commit does, allowing the scroll position to be adjusted even if the editor is not yet focused. Let me know if this patch works for you or if there's a better way to initialize the view.
I support an app where we need to be able to preserve the user's rough
scroll position when re-opening a document. It's not enough to keep
track of that offset: we keep track of the pos and both focus the
document and scroll you to it once the view exists.

For some time, we've needed to add a 50ms delay before calling
`tr.scrollIntoView()`. This patch to the demo app simulates it,
scrolling to the text at the bottom of the editor:

``
diff --git a/demo/demo.css b/demo/demo.css
index 06bfe78..813bf22 100644
--- a/demo/demo.css
+++ b/demo/demo.css
@@ -17,6 +17,8 @@ textarea {

 .full {
   max-width: 50em;
+  height: 300px;
+  overflow-y: auto;
 }

 .marked {
diff --git a/demo/demo.ts b/demo/demo.ts
index 48e2c34..80632ec 100644
--- a/demo/demo.ts
+++ b/demo/demo.ts
@@ -1,6 +1,6 @@
 import {Schema, DOMParser} from "prosemirror-model"
 import {EditorView} from "prosemirror-view"
-import {EditorState} from "prosemirror-state"
+import {EditorState, TextSelection} from "prosemirror-state"
 import {schema} from "prosemirror-schema-basic"
 import {addListNodes} from "prosemirror-schema-list"
 import {exampleSetup} from "prosemirror-example-setup"
@@ -10,7 +10,13 @@ const demoSchema = new Schema({
   marks: schema.spec.marks
 })

-let state = EditorState.create({doc: DOMParser.fromSchema(demoSchema).parse(document.querySelector("#content")!),
-                                plugins: exampleSetup({schema: demoSchema})})
+const doc = DOMParser.fromSchema(demoSchema).parse(document.querySelector("#content")!)
+const state = EditorState.create({
+  doc,
+  selection: TextSelection.atEnd(doc),
+  plugins: exampleSetup({schema: demoSchema})
+})

-;(window as any).view = new EditorView(document.querySelector(".full"), {state})
+const view = new EditorView(document.querySelector(".full"), {state})
+view.dispatch(view.state.tr.scrollIntoView())
+;(window as any).view = view
diff --git a/demo/index.html b/demo/index.html
index 4b76a94..e9ad42c 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -13,6 +13,7 @@

 <div class="full"></div>

+
 <div id=content style="display: none">
 <h2>Demonstration Text</h2>
```

I tracked this down the other day to #1503. That fix makes sense: you
want to guard against selection in another editor being used as the
starting point for the `scrollRectIntoView` walk. Instead of returning
early though, we should fall back to the dom element of the view that is
handling the scroll. That's what this commit does, allowing the scroll
position to be adjusted even if the editor is not yet focused.

Let me know if this patch works for you or if there's a better way to
initialize the view
@ -239,2 +237,2 @@
// Ignore selections outside the editor
} else if (this.someProp("handleScrollToSelection", f => f(this))) {
let focusNode = this.domSelectionRange().focusNode
let startDOM = focusNode && this.dom.contains(focusNode) ? focusNode : this.dom
Author
First-time contributor

I dropped the nodeType check the previous code had. Seems to work just fine for text nodes and element nodes alike, but maybe I'm missing something.

I dropped the `nodeType` check the previous code had. Seems to work just fine for text nodes and element nodes alike, but maybe I'm missing something.
sch changed title from Allow scrolling to intiial EditorState's selection to Allow scrolling to initial EditorState's selection 2026-06-29 04:22:23 +02:00
This pull request can be merged automatically.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u initial-focus:sch-initial-focus
git switch sch-initial-focus

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff sch-initial-focus
git switch sch-initial-focus
git rebase main
git switch main
git merge --ff-only sch-initial-focus
git switch sch-initial-focus
git rebase main
git switch main
git merge --no-ff sch-initial-focus
git switch main
git merge --squash sch-initial-focus
git switch main
git merge --ff-only sch-initial-focus
git switch main
git merge sch-initial-focus
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
prosemirror/prosemirror-view!196
No description provided.