| 440 | * keeps the off-screen rows. Caller supplies coords already clamped/wrapped. |
| 441 | */ |
| 442 | export function moveFocus(s: SelectionState, col: number, row: number): void { |
| 443 | if (!s.focus) return |
| 444 | s.anchorSpan = null |
| 445 | s.focus = { col, row } |
| 446 | // Explicit user repositioning — any stale virtual focus (from a prior |
| 447 | // shiftSelection clamp) no longer reflects intent. Anchor stays put so |
| 448 | // virtualAnchorRow is still valid for its own round-trip. |
| 449 | s.virtualFocusRow = undefined |
| 450 | } |
| 451 | |
| 452 | /** |
| 453 | * Shift anchor AND focus by dRow, clamped to [minRow, maxRow]. Used for |