()
| 739 | // scrolling, highlight walks up with the text). Keeping sticky also |
| 740 | // avoids useVirtualScroll's tail-walk → forward-walk phantom growth. |
| 741 | function check(): void { |
| 742 | const s_0 = scrollRef.current; |
| 743 | if (!s_0) { |
| 744 | stop(); |
| 745 | return; |
| 746 | } |
| 747 | const top_0 = s_0.getViewportTop(); |
| 748 | const bottom_0 = top_0 + s_0.getViewportHeight() - 1; |
| 749 | const sel_0 = selection.getState(); |
| 750 | // Pass the LAST-scrolled direction (not dirRef) so the anchor guard is |
| 751 | // bypassed after shiftAnchor has clamped anchor toward row 0. Using |
| 752 | // lastScrolledDirRef (survives stop()) lets autoscroll resume after a |
| 753 | // brief mouse dip into the viewport. Same-direction only — a mouse |
| 754 | // jump from below-bottom to above-top must stop, since reversing while |
| 755 | // the scrolledOffAbove/Below accumulators hold the prior direction's |
| 756 | // rows would duplicate text in getSelectedText. Reset on drag-finish |
| 757 | // OR when both accumulators are empty: startSelection clears them |
| 758 | // (selection.ts), so a new drag after a lost-release (isDragging |
| 759 | // stuck true, the reason AUTOSCROLL_MAX_TICKS exists) still resets. |
| 760 | // Safe: start() below re-records lastScrolledDirRef before its |
| 761 | // early-return, so a mid-scroll reset here is instantly undone. |
| 762 | if (!sel_0?.isDragging || sel_0.scrolledOffAbove.length === 0 && sel_0.scrolledOffBelow.length === 0) { |
| 763 | lastScrolledDirRef.current = 0; |
| 764 | } |
| 765 | const dir_1 = dragScrollDirection(sel_0, top_0, bottom_0, lastScrolledDirRef.current); |
| 766 | if (dir_1 === 0) { |
| 767 | // Blocked reversal: focus jumped to the opposite edge (off-window |
| 768 | // drag return, fast flick). handleSelectionDrag already moved focus |
| 769 | // past the anchor, flipping selectionBounds — the accumulator is |
| 770 | // now orphaned (holds rows on the wrong side). Clear it so |
| 771 | // getSelectedText matches the visible highlight. |
| 772 | if (lastScrolledDirRef.current !== 0 && sel_0?.focus) { |
| 773 | const want = sel_0.focus.row < top_0 ? -1 : sel_0.focus.row > bottom_0 ? 1 : 0; |
| 774 | if (want !== 0 && want !== lastScrolledDirRef.current) { |
| 775 | sel_0.scrolledOffAbove = []; |
| 776 | sel_0.scrolledOffBelow = []; |
| 777 | sel_0.scrolledOffAboveSW = []; |
| 778 | sel_0.scrolledOffBelowSW = []; |
| 779 | lastScrolledDirRef.current = 0; |
| 780 | } |
| 781 | } |
| 782 | stop(); |
| 783 | } else start(dir_1); |
| 784 | } |
| 785 | const unsubscribe = selection.subscribe(check); |
| 786 | return () => { |
| 787 | unsubscribe(); |
nothing calls this directly
no test coverage detected