Commit f7961d7
authored
fix(editor): arrow key navigation across page boundaries and auto-scroll (SD-1950) (#2191)
* fix(editor): arrow key navigation across page boundaries and auto-scroll (SD-1950)
Fix vertical arrow key navigation that would jump sections or get stuck
at page boundaries, and add auto-scroll to keep the caret visible during
keyboard navigation.
Two bugs in the vertical-navigation extension:
1. ArrowDown would jump thousands of characters when crossing page
boundaries because the adjacent line's getBoundingClientRect() returns
off-screen coordinates, causing hitTest() to map to the wrong position.
2. ArrowUp would get stuck at certain positions because the adjacent
line's DOM center Y falls in a zone where hitTest() maps to the
current fragment instead of the adjacent one (fragment boundary
misalignment), so the cursor stays at the same position.
The fix reads data-pm-start/data-pm-end from the adjacent line element
and validates the hit test result against this range. When the hit
falls outside the line's PM range (with a tight tolerance of 5), a
binary search using computeCaretLayoutRect resolves the correct position
at the goal X coordinate within the line. This avoids relying on
screen-space hit testing when it produces unreliable results.
Additionally adds scrollCaretIntoViewIfNeeded() to PresentationEditor
to auto-scroll the viewport after selection changes during keyboard
navigation.
* fix(editor): improve binary search resilience in vertical navigation
Skip unmeasurable positions in resolvePositionAtGoalX instead of
breaking the entire search. Positions at inline node boundaries may
return null from computeCaretLayoutRect, and breaking falls back to
pmStart causing the caret to jump to line start.
Also document the LTR assumption in the binary search.
* fix(editor): scope auto-scroll to arrow keys and add page-mount guard
Address review feedback for SD-1950:
- Scope auto-scroll to arrow-key navigation only. Add
requestScrollCaretIntoView() public method that the vertical-nav
extension calls before dispatch. #scrollCaretIntoViewIfNeeded only
runs when this flag is set, preventing unexpected scroll jumps from
collab edits, undo, find-and-replace, or other programmatic changes.
- Add page-mount guard in #scrollCaretIntoViewIfNeeded. The caret
element may exist with estimated coordinates even when the page isn't
visible (virtualized). Check for the page element in the DOM before
using the caret rect, falling back to scrollPageIntoView otherwise.
- Add 7 unit tests for resolvePositionAtGoalX covering: closest match,
exact match, goalX before/after all positions, null midpoints (inline
node boundaries), all-null positions, and single-position ranges.
- Export resolvePositionAtGoalX for direct testing.
* fix(editor): revert scroll gating, keep unconditional auto-scroll
The flag-based scroll gating (requestScrollCaretIntoView) was
over-engineered. The 20px margin check in scrollCaretIntoViewIfNeeded
already prevents scrolling when the caret is visible. Scrolling when
the caret is off-screen is correct for all selection change sources
(arrow keys, undo, collab, find-and-replace).
Revert to unconditional scroll after caret rendering. Keep the
page-mount guard and resolvePositionAtGoalX tests from the previous
commit.
* fix(editor): remove page-mount guard from scrollCaretIntoViewIfNeeded
The DOM query for page mount status was failing (wrong selector after
main merge), causing scrollPageIntoView to fire on every selection
update instead of precise scroll. Restore the original guard that
only checks caret element existence.
* fix(editor): add page-mount guard using painterHost reference
Check if the caret's target page is mounted before using the caret
element's rect for scroll calculations. The caret overlay can exist
with estimated coordinates even when the page is virtualized. Query
this.#painterHost (stable class field) for the page element instead
of the previous fragile querySelector('.superdoc-container') approach.
* test(editor): add integration tests for hit validation and fallback path
Add two tests exercising the data-pm-start/data-pm-end validation
block in handleKeyDown:
- Hit lands within PM range: uses hit test result directly, no fallback
- Hit lands outside PM range: triggers resolvePositionAtGoalX fallback,
binary search resolves position within the line's range
* fix(editor): scroll selection head into view for Shift+Arrow navigation
Refactor scroll logic to handle both collapsed and range selections:
- Extract #scrollScreenRectIntoView for reusable scroll-bounds check
- Replace #scrollCaretIntoViewIfNeeded with #scrollActiveEndIntoView
that works for both carets and range selections
- For range selections, pick the rendered rect nearest the selection
head: first child for backward (Shift+ArrowUp), last child for
forward (Shift+ArrowDown)
- Call scroll after range selection rendering so Shift+Arrow across
page boundaries follows the active end
* chore: fix behavior tests
* chore: fix pnpm lock
* chore: fix pnpm dedupe1 parent 6473acf commit f7961d7
7 files changed
Lines changed: 1208 additions & 1232 deletions
File tree
- packages/super-editor/src
- core/presentation-editor
- dom
- tests
Lines changed: 125 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
269 | 269 | | |
270 | 270 | | |
271 | 271 | | |
| 272 | + | |
| 273 | + | |
272 | 274 | | |
273 | 275 | | |
274 | 276 | | |
| |||
287 | 289 | | |
288 | 290 | | |
289 | 291 | | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
290 | 299 | | |
291 | 300 | | |
292 | 301 | | |
| |||
583 | 592 | | |
584 | 593 | | |
585 | 594 | | |
586 | | - | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
587 | 601 | | |
588 | | - | |
| 602 | + | |
589 | 603 | | |
590 | | - | |
| 604 | + | |
591 | 605 | | |
592 | 606 | | |
593 | 607 | | |
| |||
2436 | 2450 | | |
2437 | 2451 | | |
2438 | 2452 | | |
| 2453 | + | |
2439 | 2454 | | |
2440 | 2455 | | |
2441 | 2456 | | |
| |||
2557 | 2572 | | |
2558 | 2573 | | |
2559 | 2574 | | |
2560 | | - | |
| 2575 | + | |
2561 | 2576 | | |
2562 | 2577 | | |
2563 | 2578 | | |
| |||
2682 | 2697 | | |
2683 | 2698 | | |
2684 | 2699 | | |
| 2700 | + | |
| 2701 | + | |
2685 | 2702 | | |
2686 | 2703 | | |
2687 | 2704 | | |
| |||
3028 | 3045 | | |
3029 | 3046 | | |
3030 | 3047 | | |
| 3048 | + | |
3031 | 3049 | | |
3032 | 3050 | | |
3033 | 3051 | | |
| |||
4205 | 4223 | | |
4206 | 4224 | | |
4207 | 4225 | | |
| 4226 | + | |
| 4227 | + | |
| 4228 | + | |
| 4229 | + | |
| 4230 | + | |
| 4231 | + | |
| 4232 | + | |
4208 | 4233 | | |
4209 | 4234 | | |
4210 | 4235 | | |
| |||
4324 | 4349 | | |
4325 | 4350 | | |
4326 | 4351 | | |
| 4352 | + | |
| 4353 | + | |
| 4354 | + | |
4327 | 4355 | | |
4328 | 4356 | | |
4329 | 4357 | | |
| |||
4366 | 4394 | | |
4367 | 4395 | | |
4368 | 4396 | | |
| 4397 | + | |
| 4398 | + | |
| 4399 | + | |
| 4400 | + | |
| 4401 | + | |
| 4402 | + | |
| 4403 | + | |
| 4404 | + | |
| 4405 | + | |
| 4406 | + | |
| 4407 | + | |
| 4408 | + | |
| 4409 | + | |
| 4410 | + | |
| 4411 | + | |
| 4412 | + | |
| 4413 | + | |
| 4414 | + | |
| 4415 | + | |
| 4416 | + | |
| 4417 | + | |
| 4418 | + | |
| 4419 | + | |
| 4420 | + | |
| 4421 | + | |
| 4422 | + | |
| 4423 | + | |
| 4424 | + | |
| 4425 | + | |
| 4426 | + | |
| 4427 | + | |
| 4428 | + | |
| 4429 | + | |
| 4430 | + | |
| 4431 | + | |
| 4432 | + | |
| 4433 | + | |
| 4434 | + | |
| 4435 | + | |
| 4436 | + | |
| 4437 | + | |
| 4438 | + | |
| 4439 | + | |
| 4440 | + | |
| 4441 | + | |
| 4442 | + | |
| 4443 | + | |
| 4444 | + | |
| 4445 | + | |
| 4446 | + | |
| 4447 | + | |
| 4448 | + | |
| 4449 | + | |
| 4450 | + | |
| 4451 | + | |
| 4452 | + | |
| 4453 | + | |
| 4454 | + | |
| 4455 | + | |
| 4456 | + | |
| 4457 | + | |
| 4458 | + | |
| 4459 | + | |
| 4460 | + | |
| 4461 | + | |
| 4462 | + | |
| 4463 | + | |
| 4464 | + | |
| 4465 | + | |
| 4466 | + | |
| 4467 | + | |
| 4468 | + | |
| 4469 | + | |
| 4470 | + | |
| 4471 | + | |
| 4472 | + | |
| 4473 | + | |
| 4474 | + | |
| 4475 | + | |
| 4476 | + | |
| 4477 | + | |
| 4478 | + | |
| 4479 | + | |
| 4480 | + | |
| 4481 | + | |
| 4482 | + | |
| 4483 | + | |
| 4484 | + | |
| 4485 | + | |
| 4486 | + | |
| 4487 | + | |
| 4488 | + | |
| 4489 | + | |
4369 | 4490 | | |
4370 | 4491 | | |
4371 | 4492 | | |
| |||
Lines changed: 57 additions & 18 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
3 | 24 | | |
4 | 25 | | |
5 | 26 | | |
| |||
8 | 29 | | |
9 | 30 | | |
10 | 31 | | |
11 | | - | |
| 32 | + | |
12 | 33 | | |
13 | 34 | | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
21 | 49 | | |
22 | | - | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
23 | 65 | | |
24 | 66 | | |
25 | | - | |
26 | | - | |
| 67 | + | |
| 68 | + | |
27 | 69 | | |
28 | | - | |
29 | 70 | | |
30 | 71 | | |
31 | 72 | | |
32 | 73 | | |
33 | | - | |
34 | 74 | | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | 75 | | |
39 | 76 | | |
40 | 77 | | |
41 | 78 | | |
42 | 79 | | |
43 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
44 | 83 | | |
0 commit comments