Skip to content

Commit 5ee59c1

Browse files
committed
fix(cell-selection): keep the range head following a fast auto-scroll when the rows under the pointer are not rendered yet
1 parent 4355a04 commit 5ee59c1

1 file changed

Lines changed: 24 additions & 2 deletions

File tree

‎lib/hooks/core/useGridCellSelection.ts‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,21 @@ export function useGridCellSelection<R extends GridRowModel>(params: UseGridCell
397397
return field === undefined ? null : { id, field };
398398
}, []);
399399

400+
/** The row whose band holds `centerY` (px from the top of the unpinned rows), with `field`. */
401+
const headRowAtOffset = useCallback((centerY: number, field: string): GridCellCoordinates | null => {
402+
const { context: ctx, layout, pinnedTopRowCount } = latestRef.current;
403+
const heights = layout.cumulativeHeights;
404+
if (!ctx || heights.length === 0 || centerY < 0) return null;
405+
let lo = 0;
406+
let hi = heights.length - 1;
407+
while (lo < hi) {
408+
const mid = (lo + hi) >> 1;
409+
if (heights[mid] > centerY) hi = mid; else lo = mid + 1;
410+
}
411+
const id = ctx.rowIds[lo + pinnedTopRowCount];
412+
return id === undefined ? null : { id, field };
413+
}, []);
414+
400415
// The auto-scroll loop schedules the next frame through this ref: a callback cannot name itself.
401416
const runDragFrameRef = useRef<(allowScroll: boolean) => void>(() => {});
402417
const runDragFrame = useCallback((allowScroll: boolean) => {
@@ -421,7 +436,14 @@ export function useGridCellSelection<R extends GridRowModel>(params: UseGridCell
421436
const x = clamp(drag.x, box.left + 1, box.left + width - 2);
422437
let y = drag.y;
423438
if (y < box.top || y > box.top + height || y < headerBottom) y = clamp(y, bodyTop + 1, Math.max(bodyTop + 1, bodyBottom - 2));
424-
const head = headAtPoint(viewport, x, y);
439+
// During fast auto-scroll the rows under the pointer may not be rendered yet (virtualization
440+
// lags a frame or more on a slow machine), so the hit test finds no cell. Work out the row from
441+
// the scroll position and the row heights instead, keeping the column the head already has.
442+
const canHitTest = typeof viewport.ownerDocument.elementFromPoint === 'function';
443+
const hit = headAtPoint(viewport, x, y);
444+
const head = hit ?? (canHitTest && drag.moved
445+
? headRowAtOffset(y - box.top - (stickyTop ? stickyTop.getBoundingClientRect().height : 0) + viewport.scrollTop, drag.head.field)
446+
: null);
425447
if (head && !isSameCoordinates(head, drag.head)) {
426448
drag.head = head;
427449
if (!drag.moved && !isSameCoordinates(head, drag.anchor)) {
@@ -448,7 +470,7 @@ export function useGridCellSelection<R extends GridRowModel>(params: UseGridCell
448470
if (canScrollX) viewport.scrollLeft += dx;
449471
// Keep going while the pointer rests in the edge zone; the next frame picks the new head.
450472
drag.frame = requestAnimationFrame(() => runDragFrameRef.current(true));
451-
}, [headAtPoint, setRange]);
473+
}, [headAtPoint, headRowAtOffset, setRange]);
452474
useLayoutEffect(() => {
453475
runDragFrameRef.current = runDragFrame;
454476
}, [runDragFrame]);

0 commit comments

Comments
 (0)