1
- import { createVirtualizer , VirtualItem , VirtualizerOptions } from "@tanstack/solid-virtual" ;
2
- import { createUniqueId , mergeProps , createMemo , createSignal , onMount , untrack } from "solid-js" ;
3
- import { mergeRefs , createGenerateId } from "@kobalte/utils" ;
4
- import { isServer } from "solid-js/web" ;
1
+ import { createVirtualizer , VirtualItem , VirtualizerOptions } from "@tanstack/solid-virtual"
2
+ import { createUniqueId , mergeProps , createMemo , createSignal , onMount , untrack , createEffect , on } from "solid-js"
3
+ import { mergeRefs , createGenerateId } from "@kobalte/utils"
4
+ import { isServer } from "solid-js/web"
5
5
6
- export type Primitive = string | number | boolean | null | undefined ;
7
- export type ObjectWithKey = { [ key : string ] : any } ;
6
+ export type Primitive = string | number | boolean | null | undefined
7
+ export type ObjectWithKey = { [ key : string ] : any }
8
8
9
- export type KeyFunction < T > = ( item : T , index ?: number ) => string | number ;
9
+ export type KeyFunction < T > = ( item : T , index ?: number ) => string | number
10
10
11
11
12
12
export interface VirtualizedListArgs < T , ScrollElement extends Element = Element , ItemElement extends Element = Element > extends Partial < VirtualizerOptions < ScrollElement , ItemElement > > {
13
- id ?: string ;
14
- data : ( ) => T [ ] ;
15
- determineKey ?: KeyFunction < T > ;
16
- itemHeight ?: number ;
17
- width ?: number ;
18
- height ?: number ;
19
- rootProps ?: Record < string , any > ;
20
- containerProps ?: Record < string , any > ;
21
- itemProps ?: Record < string , any > ;
13
+ id ?: string
14
+ data : ( ) => T [ ]
15
+ determineKey ?: KeyFunction < T >
16
+ itemHeight ?: number
17
+ width ?: number
18
+ height ?: number
19
+ rootProps ?: Record < string , any >
20
+ containerProps ?: Record < string , any >
21
+ itemProps ?: Record < string , any >
22
22
}
23
23
24
24
export interface VirtualItemWithExtras extends VirtualItem {
25
- isLast : boolean ;
26
- isEven : boolean ;
25
+ isLast : boolean
26
+ isEven : boolean
27
27
}
28
28
29
29
export interface ItemArgs < T > {
30
- data : T ;
31
- props : Record < string , any > ;
32
- virtualItem : VirtualItemWithExtras ;
30
+ data : T
31
+ props : Record < string , any >
32
+ virtualItem : VirtualItemWithExtras
33
33
}
34
34
35
35
/**
@@ -49,13 +49,13 @@ export interface ItemArgs<T> {
49
49
*
50
50
* @example
51
51
* const MyList = () => {
52
- * const items = () => ['Item 1', 'Item 2', 'Item 3'];
52
+ * const items = () => ['Item 1', 'Item 2', 'Item 3']
53
53
* const virtualList = createVirtualizedList({
54
54
* data: items,
55
55
* itemHeight: 30,
56
56
* height: 300,
57
57
* width: 500,
58
- * });
58
+ * })
59
59
*
60
60
* return (
61
61
* <div {...virtualList.root}>
@@ -67,62 +67,62 @@ export interface ItemArgs<T> {
67
67
* </For>
68
68
* </div>
69
69
* </div>
70
- * );
71
- * };
70
+ * )
71
+ * }
72
72
*/
73
73
export function createVirtualizedList < T extends Primitive | ObjectWithKey > ( args : VirtualizedListArgs < T > ) {
74
- const id = args . id || createUniqueId ( ) ;
75
- const generateId = createGenerateId ( ( ) => id ) ;
74
+ const id = ( ) => args . id || createUniqueId ( )
75
+ const generateId = ( ) => createGenerateId ( ( ) => id ( ) )
76
+
77
+ const data = args . data
78
+ const count = createMemo ( ( ) => args ?. count ?? data ( ) ?. length ?? 0 )
76
79
77
- const data = args . data ;
78
- const count = createMemo ( ( ) => args . count || data ( ) ?. length || 0 ) ;
79
-
80
80
// @ts -expect-error
81
- const determineKey : KeyFunction < T > = args . determineKey || args . getItemKey || ( ( item : T , index ?: number ) => {
81
+ const determineKey : ( ) => KeyFunction < T > = createMemo ( ( ) => args ? .determineKey ?? args ? .getItemKey ?? ( ( item : T , index ?: number ) => {
82
82
if ( typeof item === 'object' && item !== null ) {
83
- return ( item as ObjectWithKey ) ?. id ??
84
- ( item as ObjectWithKey ) ?. Id ??
85
- ( item as ObjectWithKey ) ?. ID ??
86
- ( item as ObjectWithKey ) ?. uuid ??
87
- ( item as ObjectWithKey ) ?. UUID ??
88
- ( item as ObjectWithKey ) ?. key ??
89
- ( item as ObjectWithKey ) ?. sku ??
90
- index ! ;
83
+ return ( item as ObjectWithKey ) ?. id ??
84
+ ( item as ObjectWithKey ) ?. Id ??
85
+ ( item as ObjectWithKey ) ?. ID ??
86
+ ( item as ObjectWithKey ) ?. uuid ??
87
+ ( item as ObjectWithKey ) ?. UUID ??
88
+ ( item as ObjectWithKey ) ?. key ??
89
+ ( item as ObjectWithKey ) ?. sku ??
90
+ index !
91
91
}
92
- return item as unknown as string | number ;
93
- } ) ;
92
+ return item as unknown as string | number
93
+ } ) )
94
94
95
- const horizontal = ( ) => args ?. horizontal ?? false ;
95
+ const horizontal = ( ) => args ?. horizontal ?? false
96
96
97
- const [ rootElement , setRootElement ] = createSignal < Element | null > ( null ) ;
97
+ const [ rootElement , setRootElement ] = createSignal < Element | null > ( null )
98
98
99
- const getScrollElement = ( ) => rootElement ( ) ;
99
+ const getScrollElement = ( ) => rootElement ( )
100
100
101
- const estimateSize = createMemo ( ( ) => args ?. estimateSize || ( ( index : number ) => args . itemHeight || 50 ) ) ;
101
+ const estimateSize = createMemo ( ( ) => args ?. estimateSize || ( ( index : number ) => args . itemHeight || 50 ) )
102
102
103
103
const initialRect = ( ) => ( {
104
104
width : args ?. width ?? args . initialRect ?. width ?? 600 ,
105
105
height : args ?. height ?? args . initialRect ?. height ?? 400 ,
106
- } ) ;
106
+ } )
107
107
108
108
const measureElement = createMemo ( ( ) => {
109
- if ( isServer ) return undefined ;
109
+ if ( isServer ) return undefined
110
110
111
- if ( args . measureElement ) return args . measureElement ;
111
+ if ( args . measureElement ) return args . measureElement
112
112
if ( navigator . userAgent . indexOf ( 'Firefox' ) === - 1 ) {
113
113
return ( element : Element ) => {
114
- return element ?. getBoundingClientRect ( ) [ horizontal ( ) ? 'width' : 'height' ] ;
115
- } ;
114
+ return element ?. getBoundingClientRect ( ) [ horizontal ( ) ? 'width' : 'height' ]
115
+ }
116
116
}
117
117
118
- return undefined ;
119
- } ) ;
118
+ return undefined
119
+ } )
120
120
121
121
const reactiveArgs = createMemo ( ( ) => args )
122
122
123
123
const options : ( ) => VirtualizerOptions < Element , Element > = createMemo ( ( ) => {
124
124
const currentArgs = reactiveArgs ( )
125
-
125
+
126
126
return mergeProps ( {
127
127
get count ( ) {
128
128
return count ( )
@@ -139,47 +139,47 @@ export function createVirtualizedList<T extends Primitive | ObjectWithKey>(args:
139
139
initialOffset : currentArgs ?. initialOffset ?? 0 ,
140
140
onChange : currentArgs . onChange ,
141
141
scrollToFn : currentArgs ?. scrollToFn ?? ( ( offset , { behavior } ) => {
142
- const scrollElement = getScrollElement ( ) ;
142
+ const scrollElement = getScrollElement ( )
143
143
if ( scrollElement ) {
144
144
scrollElement . scrollTo ( {
145
145
[ horizontal ( ) ? 'left' : 'top' ] : offset ,
146
146
behavior,
147
- } ) ;
147
+ } )
148
148
}
149
149
} ) ,
150
150
observeElementRect : currentArgs ?. observeElementRect ?? ( ( instance , cb ) => {
151
- const scrollElement = getScrollElement ( ) ;
152
- if ( ! scrollElement ) return ;
153
-
151
+ const scrollElement = getScrollElement ( )
152
+ if ( ! scrollElement ) return
153
+
154
154
const resizeObserver = new ResizeObserver ( ( ) => {
155
- const rect = scrollElement . getBoundingClientRect ( ) ;
156
- cb ( rect ) ;
157
- } ) ;
158
-
159
- resizeObserver . observe ( scrollElement ) ;
160
-
161
- return ( ) => resizeObserver . disconnect ( ) ;
155
+ const rect = scrollElement . getBoundingClientRect ( )
156
+ cb ( rect )
157
+ } )
158
+
159
+ resizeObserver . observe ( scrollElement )
160
+
161
+ return ( ) => resizeObserver . disconnect ( )
162
162
} ) ,
163
163
observeElementOffset : currentArgs ?. observeElementOffset ?? ( ( instance , cb ) => {
164
- const scrollElement = getScrollElement ( ) ;
165
- if ( ! scrollElement ) return ;
166
-
164
+ const scrollElement = getScrollElement ( )
165
+ if ( ! scrollElement ) return
166
+
167
167
const handleScroll = ( ) => {
168
168
const offset = horizontal ( )
169
169
? scrollElement . scrollLeft
170
- : scrollElement . scrollTop ;
171
- cb ( offset , true ) ;
172
- } ;
173
-
170
+ : scrollElement . scrollTop
171
+ cb ( offset , true )
172
+ }
173
+
174
174
scrollElement . addEventListener ( 'scroll' , handleScroll , {
175
175
passive : true ,
176
- } ) ;
177
-
178
- return ( ) => scrollElement . removeEventListener ( 'scroll' , handleScroll ) ;
176
+ } )
177
+
178
+ return ( ) => scrollElement . removeEventListener ( 'scroll' , handleScroll )
179
179
} ) ,
180
180
debug : currentArgs ?. debug ,
181
181
measureElement : measureElement ( ) ,
182
- getItemKey : ( index : number ) => determineKey ( data ( ) [ index ] , index ) ,
182
+ getItemKey : ( index : number ) => { return determineKey ( ) ( data ( ) [ index ] , index ) } ,
183
183
rangeExtractor : currentArgs ?. rangeExtractor ,
184
184
scrollMargin : currentArgs ?. scrollMargin ,
185
185
gap : currentArgs ?. gap ,
@@ -191,67 +191,72 @@ export function createVirtualizedList<T extends Primitive | ObjectWithKey>(args:
191
191
isRtl : currentArgs ?. isRtl ,
192
192
} , currentArgs )
193
193
}
194
- )
194
+ )
195
195
196
- const virtualizer = createMemo ( ( ) => createVirtualizer ( options ( ) ) )
196
+ const virtualizer = ( createVirtualizer ( options ( ) ) )
197
+
198
+ createEffect ( on ( options , ( ) => {
199
+ virtualizer . setOptions ( options ( ) )
200
+ } , { defer : true } ) )
197
201
198
202
const rootProps = createMemo ( ( ) => {
199
203
const defaultStyle = {
200
204
'overflow-y' : horizontal ( ) ? 'hidden' : 'auto' ,
201
205
'overflow-x' : horizontal ( ) ? 'auto' : 'hidden' ,
202
206
position : 'relative' ,
203
207
height : args ?. height ? `${ args . height } px` : '400px' ,
204
- width : args ?. width ? `${ args . width } px` : '100%' ,
205
- } ;
206
- const horizontalAttr = horizontal ( ) ? "" : undefined ;
208
+ width : args ?. width ? `${ args . width } px` : '100%' ,
209
+ }
210
+ const horizontalAttr = horizontal ( ) ? "" : undefined
207
211
208
212
return mergeProps ( {
209
213
id : id ,
210
214
style : defaultStyle ,
211
215
"data-horizontal" : horizontalAttr ,
212
216
"data-list-id" : id ,
213
217
ref : mergeRefs ( ( el : Element ) => setRootElement ( el ) , args . rootProps ?. ref ) ,
214
- } , args . rootProps || { } ) ;
215
- } ) ;
218
+ } , args . rootProps || { } )
219
+ } )
216
220
217
221
const containerProps = createMemo ( ( ) => {
218
- const containerId = generateId ( 'list' ) ;
222
+ const containerId = generateId ( ) ( 'list' )
219
223
const defaultStyle = {
220
224
position : 'relative' ,
221
- height : horizontal ( ) ? '100%' : `${ virtualizer ( ) . getTotalSize ( ) } px` ,
222
- width : horizontal ( ) ? `${ virtualizer ( ) . getTotalSize ( ) } px` : '100%' ,
223
- } ;
225
+ height : horizontal ( ) ? '100%' : `${ virtualizer . getTotalSize ( ) } px` ,
226
+ width : horizontal ( ) ? `${ virtualizer . getTotalSize ( ) } px` : '100%' ,
227
+ }
224
228
225
229
return mergeProps ( {
226
230
style : defaultStyle ,
227
231
"data-list-container" : containerId ,
228
- } , args . containerProps || { } ) ;
229
- } ) ;
232
+ } , args . containerProps || { } )
233
+ } )
230
234
231
- const itemWrapper = ( itemCreator : ( args : ItemArgs < T > ) => any , trackChanges = false ) =>
235
+ const itemWrapper = ( itemCreator : ( args : ItemArgs < T > ) => any , trackChanges = false ) =>
232
236
( virtualItem : VirtualItem , virtualItemIndex : ( ) => number ) => {
233
237
const createItem = ( ) => {
234
- const itemData = data ( ) [ virtualItem . index ] ;
238
+ const itemData = data ( ) [ virtualItem . index ]
235
239
const style = {
236
240
position : 'absolute' ,
237
241
top : horizontal ( ) ? 0 : `${ virtualItem . start } px` ,
238
242
left : horizontal ( ) ? `${ virtualItem . start } px` : 0 ,
239
243
width : horizontal ( ) ? `${ virtualItem . size } px` : '100%' ,
240
244
height : horizontal ( ) ? '100%' : `${ virtualItem . size } px` ,
241
- } ;
242
- const key = determineKey ( itemData , virtualItem . index ) ;
245
+ }
246
+
247
+ const key = determineKey ( ) ( itemData , virtualItem . index )
243
248
const itemProps = mergeProps ( {
244
249
style,
245
250
'data-list-item' : 'true' ,
246
251
'data-index' : virtualItem . index ,
247
252
key,
248
- ref : mergeRefs ( ( el : Element ) => {
249
- if ( el ) virtualizer ( ) . measureElement ( el ) ;
253
+ ref : mergeRefs ( ( el : Element ) => {
254
+ if ( el ) virtualizer . measureElement ( el )
250
255
} , args . itemProps ?. ref ) ,
251
- } , args ?. itemProps ?? { } ) ;
256
+ } , args ?. itemProps ?? { } )
252
257
253
- const isLast = virtualItem . index === count ( ) - 1 ;
254
- const isEven = virtualItem . index % 2 === 0 ;
258
+ const isLast = virtualItem . index === count ( ) - 1
259
+ const isEven = virtualItem . index % 2 === 0
255
260
const itemArgs : ItemArgs < T > = {
256
261
data : itemData ,
257
262
props : itemProps ,
@@ -260,31 +265,33 @@ export function createVirtualizedList<T extends Primitive | ObjectWithKey>(args:
260
265
isLast,
261
266
isEven,
262
267
} ,
263
- } ;
264
-
265
- return itemCreator ( itemArgs ) ;
268
+ }
269
+
270
+ return itemCreator ( itemArgs )
266
271
}
267
272
268
- return trackChanges ? createMemo ( createItem ) : untrack ( createItem )
273
+ return trackChanges ? createMemo ( createItem ) ( ) : untrack ( createItem )
269
274
}
270
275
271
276
return {
272
277
id,
273
- virtualizer,
278
+ get virtualizer ( ) {
279
+ return virtualizer
280
+ } ,
274
281
get root ( ) {
275
282
return rootProps ( )
276
283
} ,
277
284
get count ( ) {
278
285
return count ( )
279
286
} ,
280
- get container ( ) {
287
+ get container ( ) {
281
288
return containerProps ( )
282
289
} ,
283
290
items : itemWrapper ,
284
291
get item ( ) {
285
- return virtualizer ( ) . getVirtualItems ( )
292
+ return virtualizer . getVirtualItems ( )
286
293
}
287
- } ;
294
+ }
288
295
}
289
296
290
- export default createVirtualizedList ;
297
+ export default createVirtualizedList
0 commit comments