Skip to content

fix: table gap clicks selecting wrong paragraph and jumping scroll (SD-2356)#2707

Open
luccas-harbour wants to merge 2 commits intomainfrom
luccas/sd-2356-bug-clicking-into-table-in-specific-document-jumps-user-to
Open

fix: table gap clicks selecting wrong paragraph and jumping scroll (SD-2356)#2707
luccas-harbour wants to merge 2 commits intomainfrom
luccas/sd-2356-bug-clicking-into-table-in-specific-document-jumps-user-to

Conversation

@luccas-harbour
Copy link
Copy Markdown
Contributor

Summary

  • Fix clicks in table cell gaps (between paragraphs) selecting the wrong paragraph and causing a scroll jump. Replaces the flawed "last paragraph" fallback in hitTestTableFragment with a nearest-distance algorithm that picks the closest paragraph to the click point.
  • Fix non-text clicks (page margins, table padding) resetting the cursor to position 1, which triggered an unwanted scroll-to-top. Now preserves the existing cursor and scroll position.
  • Add scoped line-container resolution in DomPointerMapping so gap clicks in nested table DOM resolve to the nearest line within the correct cell.

@linear
Copy link
Copy Markdown

linear bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

@caio-pizzol caio-pizzol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luccas-harbour good direction — nearest-paragraph selection is a clear improvement over always picking the last one.

I tested both customer docs manually and the scroll jump still happens. two things going on:

  1. when a table splits across pages, the container used to find the nearest line includes lines from ALL pages of that table — so the click can land on a line that's on a different page, and the view scrolls there.

  2. clicking on page margins doesn't trigger the "no hit" path. the table is so wide that margin clicks get resolved as table clicks, so the cursor moves and the view jumps.

the getFirstTextPosition early-exit fix (the found flag) doesn't have a test that exercises it — existing tests use single-paragraph docs that pass either way. a quick test with two paragraphs would cover it.

one small cleanup suggestion and a couple test gaps. left inline comments.

return resolveLineAtX(hitChainLine, clientX);
}

if (fragmentEl.classList.contains(CLASS.tableFragment)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when a table splits across pages, the container here can hold lines from all pages of that table. so findLineAtY picks the closest line overall, which might be on a different page — and the view scrolls there.

tested with both customer docs, the jump still happens. could you scope this to only lines within the current page fragment?


// Handle click outside text content
// Handle click outside text content — keep cursor and scroll position unchanged.
if (!rawHit) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this path never fires during manual testing — the table extends into the margin area, so margin clicks get resolved as table hits instead of reaching here.

return resolveLineAtX(lineEl, viewX);
}

function resolveLineInContainer(containerEl: HTMLElement, viewX: number, viewY: number): number | null {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is the same as resolveFragment above — same query, same two calls. only difference is a debug log that's off by default. could reuse resolveFragment and delete this one?

const scrollDelta = Math.abs(scrollAfter - scrollBefore);
expect(
scrollDelta,
`Scroll jumped by ${scrollDelta}px after clicking page margin — expected no significant scroll change`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selBefore is captured but never checked after the click. worth adding:

const selAfter = await superdoc.getSelection();
expect(selAfter.from).toBe(selBefore.from);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants