@@ -2,8 +2,7 @@ import React from 'react';
22
33import { throttle } from 'lodash' ;
44
5- import { rafThrottle } from './utils' ;
6-
5+ import { getCurrentTableOffset , isTableOffscreen , rafThrottle } from './utils' ;
76interface UseScrollBasedChunksProps {
87 scrollContainerRef : React . RefObject < HTMLElement > ;
98 tableRef : React . RefObject < HTMLElement > ;
@@ -12,7 +11,6 @@ interface UseScrollBasedChunksProps {
1211 chunkSize : number ;
1312 renderOverscan ?: number ;
1413 fetchOverscan ?: number ;
15- tableOffset : number ;
1614}
1715
1816interface ChunkState {
@@ -27,13 +25,31 @@ const DEFAULT_RENDER_OVERSCAN = isSafari ? 1 : 2;
2725const DEFAULT_FETCH_OVERSCAN = 4 ;
2826const THROTTLE_DELAY = 200 ;
2927
28+ /**
29+ * Virtualized chunking for tables within a shared scroll container.
30+ *
31+ * Behavior:
32+ * - Dynamic offset: On scroll/resize, compute the table's current offset relative to the
33+ * scroll container using DOM rects. This stays correct as surrounding layout changes.
34+ * - Visible range: Convert the container viewport [scrollTop, scrollTop+clientHeight]
35+ * into table coordinates and derive visible chunk indices from rowHeight and chunkSize.
36+ * - Offscreen freeze: If the table's [tableStartY, tableEndY] is farther than one viewport
37+ * away (freeze margin = container.clientHeight), skip updating the visible chunk range.
38+ * This keeps offscreen groups stable and prevents scroll jumps when many groups are open.
39+ * - Overscan: renderOverscan/fetchOverscan buffer around the visible range to reduce
40+ * thrashing (Safari uses smaller render overscan).
41+ * - Throttling: Scroll updates are throttled (THROTTLE_DELAY), and resize is raf-throttled.
42+ *
43+ * Notes:
44+ * - totalItems/rowHeight changes re-evaluate bounds.
45+ * - When offscreen, the hook returns skipUpdate to preserve the previous range.
46+ */
3047export const useScrollBasedChunks = ( {
3148 scrollContainerRef,
3249 tableRef,
3350 totalItems,
3451 rowHeight,
3552 chunkSize,
36- tableOffset,
3753 renderOverscan = DEFAULT_RENDER_OVERSCAN ,
3854 fetchOverscan = DEFAULT_FETCH_OVERSCAN ,
3955} : UseScrollBasedChunksProps ) : ChunkState [ ] => {
@@ -52,23 +68,45 @@ export const useScrollBasedChunks = ({
5268 return null ;
5369 }
5470
55- const containerScroll = container . scrollTop ;
56- const visibleStart = Math . max ( containerScroll - tableOffset , 0 ) ;
71+ // Compute current table offset relative to the scroll container using DOM rects.
72+ // This accounts for dynamic layout changes as groups above expand/collapse.
73+ const currentTableOffset = getCurrentTableOffset ( container , table ) ;
74+
75+ const visibleStart = Math . max ( container . scrollTop - currentTableOffset , 0 ) ;
5776 const visibleEnd = visibleStart + container . clientHeight ;
5877
59- const start = Math . max ( Math . floor ( visibleStart / rowHeight / chunkSize ) , 0 ) ;
60- const end = Math . min (
61- Math . floor ( visibleEnd / rowHeight / chunkSize ) ,
62- Math . max ( chunksCount - 1 , 0 ) ,
63- ) ;
64- return { start, end} ;
65- } , [ scrollContainerRef , tableRef , tableOffset , rowHeight , chunkSize , chunksCount ] ) ;
78+ // Determine if this table is far outside of the viewport; if so, freeze updates
79+ const isOffscreen = isTableOffscreen ( {
80+ container,
81+ currentTableOffset,
82+ totalItems,
83+ rowHeight,
84+ } ) ;
85+
86+ return {
87+ start : Math . max ( Math . floor ( visibleStart / rowHeight / chunkSize ) , 0 ) ,
88+ end : Math . min (
89+ Math . floor ( visibleEnd / rowHeight / chunkSize ) ,
90+ Math . max ( chunksCount - 1 , 0 ) ,
91+ ) ,
92+ skipUpdate : isOffscreen ,
93+ } ;
94+ } , [ scrollContainerRef , tableRef , rowHeight , chunkSize , chunksCount , totalItems ] ) ;
6695
6796 const updateVisibleChunks = React . useCallback ( ( ) => {
6897 const newRange = calculateVisibleRange ( ) ;
6998 if ( newRange ) {
70- setVisibleStartChunk ( newRange . start ) ;
71- setVisibleEndChunk ( newRange . end ) ;
99+ const { start, end, skipUpdate} = newRange as unknown as {
100+ start : number ;
101+ end : number ;
102+ skipUpdate ?: boolean ;
103+ } ;
104+ if ( skipUpdate ) {
105+ return ;
106+ }
107+
108+ setVisibleStartChunk ( start ) ;
109+ setVisibleEndChunk ( end ) ;
72110 }
73111 } , [ calculateVisibleRange ] ) ;
74112
0 commit comments