Skip to content

Commit 4ded2ae

Browse files
bjohasclaude
andcommitted
feat(superdoc): scrollToHeading walks forward to next text content for empty headings
scrollToHeading targets a position INSIDE the heading paragraph's text content (not the doc-level boundary just before it) so the presentation editor's layout-fragment index — which only spans inside-text positions — has something to scroll to. The original patch did this by walking the heading node's own descendants. Refinement: when the heading itself carries no text content (empty paragraph, or content limited to structural markers like bookmarkStart / commentRangeStart), walk forward in the doc for the next text-bearing position instead of returning false. That way the viewport at least lands near where the empty heading lives — useful in user-facing TOC clicks where the click target is the empty heading entry the user intentionally selected. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 5e66227 commit 4ded2ae

1 file changed

Lines changed: 19 additions & 4 deletions

File tree

packages/superdoc/src/core/SuperDoc.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,19 +1526,34 @@ export class SuperDoc extends EventEmitter {
15261526
// paragraph to find the first descendant that has actual text
15271527
// content (skipping bookmark markers, comment-range markers, etc.)
15281528
// and target that position instead.
1529+
let resolved = null;
15291530
if (foundNode && foundNode.content?.size > 0) {
1530-
let textInsidePos = null;
15311531
foundNode.descendants((child, offset) => {
1532-
if (textInsidePos !== null) return false;
1532+
if (resolved !== null) return false;
15331533
if (child.isText && child.text && child.text.length > 0) {
15341534
// Position inside the paragraph = paragraph-start (foundPos+1)
15351535
// + descendant offset.
1536-
textInsidePos = foundPos + 1 + offset;
1536+
resolved = foundPos + 1 + offset;
15371537
return false;
15381538
}
15391539
});
1540-
if (textInsidePos != null) foundPos = textInsidePos;
15411540
}
1541+
if (resolved == null) {
1542+
// The heading itself carries no text content (truly-empty
1543+
// paragraph, or content limited to structural markers like
1544+
// bookmarkStart). Walk forward in the doc for the next text-bearing
1545+
// position so the viewport at least lands near where the heading
1546+
// lives instead of returning false.
1547+
editor.state.doc.descendants((child, p) => {
1548+
if (resolved !== null) return false;
1549+
if (p <= foundPos) return undefined;
1550+
if (child.isText && child.text && child.text.length > 0) {
1551+
resolved = p;
1552+
return false;
1553+
}
1554+
});
1555+
}
1556+
if (resolved != null) foundPos = resolved;
15421557

15431558
// Same dispatch as scrollToElement: presentation if available, else
15441559
// body-editor + DOM scrollIntoView.

0 commit comments

Comments
 (0)