| 808 | * would duplicate captured rows when they scroll back on-screen. |
| 809 | */ |
| 810 | export function dragScrollDirection(sel: SelectionState | null, top: number, bottom: number, alreadyScrollingDir: -1 | 0 | 1 = 0): -1 | 0 | 1 { |
| 811 | if (!sel?.isDragging || !sel.anchor || !sel.focus) return 0; |
| 812 | const row = sel.focus.row; |
| 813 | const want: -1 | 0 | 1 = row < top ? -1 : row > bottom ? 1 : 0; |
| 814 | if (alreadyScrollingDir !== 0) { |
| 815 | // Same-direction only. Focus on the opposite side, or back inside the |
| 816 | // viewport, stops the scroll — captured rows stay in scrolledOffAbove/ |
| 817 | // Below but never scroll back on-screen, so getSelectedText is correct. |
| 818 | return want === alreadyScrollingDir ? want : 0; |
| 819 | } |
| 820 | // Anchor must be inside the viewport for us to own this drag. If the |
| 821 | // user started selecting in the input box or header, autoscrolling the |
| 822 | // message history is surprising and corrupts the anchor via shiftAnchor. |
| 823 | if (sel.anchor.row < top || sel.anchor.row > bottom) return 0; |
| 824 | return want; |
| 825 | } |
| 826 | |
| 827 | // Keyboard page jumps: scrollTo() writes scrollTop directly and clears |
| 828 | // pendingScrollDelta — one frame, no drain. scrollBy() accumulates into |