@@ -4,6 +4,7 @@ import * as ReactDOM from 'react-dom/client'
4
4
import './index.css'
5
5
6
6
import { useVirtualizer } from '@tanstack/react-virtual'
7
+ import { debounce } from '@tanstack/react-pacer'
7
8
8
9
function App ( ) {
9
10
return (
@@ -16,7 +17,17 @@ function App() {
16
17
< br />
17
18
18
19
< 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 />
20
31
< br />
21
32
< br />
22
33
{ process . env . NODE_ENV === 'development' ? (
@@ -30,21 +41,25 @@ function App() {
30
41
)
31
42
}
32
43
33
- const NUM_LANES = 5
34
-
35
44
function LanesVirtualizer ( ) {
45
+ const [ numLanes , setNumLanes ] = React . useState ( 4 )
36
46
const parentRef = React . useRef ( null )
37
47
38
48
const rowVirtualizer = useVirtualizer ( {
39
49
count : 10000 ,
40
50
getScrollElement : ( ) => parentRef . current ,
41
51
estimateSize : ( ) => 35 ,
42
52
overscan : 5 ,
43
- lanes : NUM_LANES ,
53
+ lanes : numLanes ,
44
54
} )
45
55
46
56
return (
47
57
< >
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 />
48
63
< div
49
64
ref = { parentRef }
50
65
className = "List"
@@ -68,8 +83,8 @@ function LanesVirtualizer() {
68
83
style = { {
69
84
position : 'absolute' ,
70
85
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 } )` ,
73
88
height : `${ virtualRow . size } px` ,
74
89
transform : `translateY(${ virtualRow . start } px)` ,
75
90
} }
@@ -83,6 +98,169 @@ function LanesVirtualizer() {
83
98
)
84
99
}
85
100
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
+
86
264
// eslint-disable-next-line
87
265
ReactDOM . createRoot ( document . getElementById ( 'root' ) ! ) . render (
88
266
< React . StrictMode >
0 commit comments