@@ -4,6 +4,7 @@ import * as ReactDOM from 'react-dom/client'
44import './index.css'
55
66import { useVirtualizer } from '@tanstack/react-virtual'
7+ import { debounce } from '@tanstack/react-pacer'
78
89function App ( ) {
910 return (
@@ -16,7 +17,17 @@ function App() {
1617 < br />
1718
1819 < h3 > Lanes</ h3 >
19- < LanesVirtualizer />
20+ < LanesVirtualizer />
21+ < br />
22+ < br />
23+ < h3 > Padding Lanes</ h3 >
24+ < PaddingVirtualizer />
25+ < br />
26+ < br />
27+ < h3 > Resizable Lanes</ h3 >
28+ < ResizeVirtualizer />
29+ < br />
30+ < br />
2031 < br />
2132 < br />
2233 { process . env . NODE_ENV === 'development' ? (
@@ -30,21 +41,25 @@ function App() {
3041 )
3142}
3243
33- const NUM_LANES = 5
34-
3544function LanesVirtualizer ( ) {
45+ const [ numLanes , setNumLanes ] = React . useState ( 4 )
3646 const parentRef = React . useRef ( null )
3747
3848 const rowVirtualizer = useVirtualizer ( {
3949 count : 10000 ,
4050 getScrollElement : ( ) => parentRef . current ,
4151 estimateSize : ( ) => 35 ,
4252 overscan : 5 ,
43- lanes : NUM_LANES ,
53+ lanes : numLanes ,
4454 } )
4555
4656 return (
4757 < >
58+ < div style = { { display : 'grid' , gridTemplateColumns : '80px 200px' , gap : '10px' } } >
59+ < label htmlFor = "numLanes1" > Num Lanes</ label >
60+ < input type = "number" id = "numLanes1" value = { numLanes } onChange = { ( e ) => { setNumLanes ( Number ( e . target . value ) ) ; rowVirtualizer . measure ( ) } } />
61+ </ div >
62+ < br />
4863 < div
4964 ref = { parentRef }
5065 className = "List"
@@ -68,8 +83,8 @@ function LanesVirtualizer() {
6883 style = { {
6984 position : 'absolute' ,
7085 top : 0 ,
71- left : `calc(${ virtualRow . index % NUM_LANES } * 100% / ${ NUM_LANES } )` ,
72- width : `calc(100% / ${ NUM_LANES } )` ,
86+ left : `calc(${ virtualRow . index % numLanes } * 100% / ${ numLanes } )` ,
87+ width : `calc(100% / ${ numLanes } )` ,
7388 height : `${ virtualRow . size } px` ,
7489 transform : `translateY(${ virtualRow . start } px)` ,
7590 } }
@@ -83,6 +98,169 @@ function LanesVirtualizer() {
8398 )
8499}
85100
101+ function PaddingVirtualizer ( ) {
102+ const parentRef = React . useRef < HTMLDivElement > ( null )
103+ const [ numLanes , setNumLanes ] = React . useState ( 4 )
104+ const [ rowGap , setRowGap ] = React . useState ( 10 )
105+ const [ columnGap , setColumnGap ] = React . useState ( 10 )
106+
107+ const rowVirtualizer = useVirtualizer ( {
108+ count : 10000 ,
109+ getScrollElement : ( ) => parentRef . current ,
110+ estimateSize : ( ) => 35 ,
111+ overscan : 5 ,
112+ lanes : numLanes ,
113+ gap : rowGap ,
114+ } )
115+
116+ return (
117+ < >
118+ < div style = { { display : 'grid' , gridTemplateColumns : '80px 200px' , gap : '10px' } } >
119+ < label htmlFor = "numLanes2" > Num Lanes</ label >
120+ < input type = "number" id = "numLanes2" value = { numLanes } onChange = { ( e ) => { setNumLanes ( Number ( e . target . value ) ) ; rowVirtualizer . measure ( ) } } />
121+ < label htmlFor = "rowGap" > Row Gap</ label >
122+ < input type = "number" id = "rowGap" value = { rowGap } onChange = { ( e ) => { setRowGap ( Number ( e . target . value ) ) ; rowVirtualizer . measure ( ) } } />
123+ < label htmlFor = "columnGap" > Column Gap</ label >
124+ < input type = "number" id = "columnGap" value = { columnGap } onChange = { ( e ) => { setColumnGap ( Number ( e . target . value ) ) ; rowVirtualizer . measure ( ) } } />
125+ </ div >
126+ < br />
127+
128+ < div
129+ ref = { parentRef }
130+ className = "List"
131+ style = { {
132+ height : "200px" ,
133+ width : "400px" ,
134+ overflow : "auto" ,
135+ } }
136+ >
137+ < div
138+ style = { {
139+ height : `${ rowVirtualizer . getTotalSize ( ) } px` ,
140+ width : '100%' ,
141+ position : 'relative' ,
142+ } }
143+ >
144+ { rowVirtualizer . getVirtualItems ( ) . map ( ( virtualRow ) => {
145+ return (
146+ < div
147+ key = { virtualRow . index }
148+ className = { virtualRow . index % 2 ? 'ListItemOdd' : 'ListItemEven' }
149+ style = { {
150+ position : 'absolute' ,
151+ top : 0 ,
152+ left : `calc((${ virtualRow . index % numLanes } * 100% / ${ numLanes } ) + (${ columnGap } px * (${ virtualRow . index % numLanes } ) / ${ numLanes } ))` ,
153+ width : `calc((100% / ${ numLanes } ) - (${ columnGap } px * (${ numLanes } - 1) / ${ numLanes } ))` ,
154+ height : `${ virtualRow . size } px` ,
155+ transform : `translateY(${ virtualRow . start } px)` ,
156+ outline : '1px solid red' ,
157+ } }
158+ >
159+ Cell { virtualRow . index }
160+ </ div >
161+ )
162+ } ) }
163+ </ div >
164+ </ div >
165+ </ >
166+ )
167+ }
168+
169+ const CELL_WIDTH = 100
170+ function ResizeVirtualizer ( ) {
171+ const parentRef = React . useRef < HTMLDivElement > ( null )
172+ const [ numLanes , setNumLanes ] = React . useState ( 4 )
173+ const [ rowGap , setRowGap ] = React . useState ( 10 )
174+ const [ columnGap , setColumnGap ] = React . useState ( 10 )
175+
176+ const rowVirtualizer = useVirtualizer ( {
177+ count : 10000 ,
178+ getScrollElement : ( ) => parentRef . current ,
179+ estimateSize : ( ) => 35 ,
180+ overscan : 5 ,
181+ lanes : numLanes ,
182+ gap : rowGap ,
183+ } )
184+
185+ React . useEffect ( ( ) => {
186+ if ( ! parentRef . current ) return
187+ const debouncedOnResize = debounce ( ( entries : Array < ResizeObserverEntry > ) => {
188+ const rect = entries . at ( 0 ) ?. contentRect
189+ if ( ! rect ) return
190+ const { width } = rect
191+ setNumLanes ( Math . floor ( width / CELL_WIDTH ) )
192+ rowVirtualizer . measure ( )
193+ } , {
194+ wait : 100 ,
195+
196+ } )
197+ const resizeObserver = new ResizeObserver ( ( entries ) => {
198+ debouncedOnResize ( entries )
199+ } )
200+ resizeObserver . observe ( parentRef . current )
201+ return ( ) => {
202+ resizeObserver . disconnect ( )
203+ }
204+ } , [ rowVirtualizer ] )
205+
206+
207+
208+ return (
209+ < >
210+ < div style = { { display : 'grid' , gridTemplateColumns : '80px 200px' , gap : '10px' } } >
211+ < label htmlFor = "numLanes2" > Num Lanes</ label >
212+ < input type = "number" id = "numLanes2" value = { numLanes } readOnly disabled />
213+ < label htmlFor = "rowGap" > Row Gap</ label >
214+ < input type = "number" id = "rowGap" value = { rowGap } onChange = { ( e ) => { setRowGap ( Number ( e . target . value ) ) ; rowVirtualizer . measure ( ) } } />
215+ < label htmlFor = "columnGap" > Column Gap</ label >
216+ < input type = "number" id = "columnGap" value = { columnGap } onChange = { ( e ) => { setColumnGap ( Number ( e . target . value ) ) ; rowVirtualizer . measure ( ) } } />
217+ </ div >
218+ < br />
219+
220+ < div
221+ ref = { parentRef }
222+ className = "List"
223+ style = { {
224+ height : "200px" ,
225+ width : "400px" ,
226+ overflow : "auto" ,
227+ minWidth : CELL_WIDTH ,
228+ minHeight : "35px" ,
229+ resize : 'horizontal' ,
230+ } }
231+ >
232+ < div
233+ style = { {
234+ height : `${ rowVirtualizer . getTotalSize ( ) } px` ,
235+ width : '100%' ,
236+ position : 'relative' ,
237+ } }
238+ >
239+ { rowVirtualizer . getVirtualItems ( ) . map ( ( virtualRow ) => {
240+ return (
241+ < div
242+ key = { virtualRow . index }
243+ className = { virtualRow . index % 2 ? 'ListItemOdd' : 'ListItemEven' }
244+ style = { {
245+ position : 'absolute' ,
246+ top : 0 ,
247+ left : `calc((${ virtualRow . index % numLanes } * 100% / ${ numLanes } ) + (${ columnGap } px * (${ virtualRow . index % numLanes } ) / ${ numLanes } ))` ,
248+ width : `calc((100% / ${ numLanes } ) - (${ columnGap } px * (${ numLanes } - 1) / ${ numLanes } ))` ,
249+ height : `${ virtualRow . size } px` ,
250+ transform : `translateY(${ virtualRow . start } px)` ,
251+ outline : '1px solid red' ,
252+ } }
253+ >
254+ Cell { virtualRow . index }
255+ </ div >
256+ )
257+ } ) }
258+ </ div >
259+ </ div >
260+ </ >
261+ )
262+ }
263+
86264// eslint-disable-next-line
87265ReactDOM . createRoot ( document . getElementById ( 'root' ) ! ) . render (
88266 < React . StrictMode >
0 commit comments