@tanstack/virtual-core 3.3.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +58 -65
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +5 -7
- package/dist/cjs/utils.cjs +8 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -0
- package/dist/esm/index.d.ts +5 -7
- package/dist/esm/index.js +59 -66
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/utils.js +8 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +79 -77
- package/src/utils.ts +8 -0
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { approxEqual, memo, notUndefined } from './utils'
|
|
1
|
+
import { approxEqual, memo, notUndefined, debounce } from './utils'
|
|
2
2
|
|
|
3
3
|
export * from './utils'
|
|
4
4
|
|
|
@@ -98,6 +98,10 @@ export const observeElementRect = <T extends Element>(
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
const addEventListenerOptions = {
|
|
102
|
+
passive: true,
|
|
103
|
+
}
|
|
104
|
+
|
|
101
105
|
export const observeWindowRect = (
|
|
102
106
|
instance: Virtualizer<Window, any>,
|
|
103
107
|
cb: (rect: Rect) => void,
|
|
@@ -112,58 +116,81 @@ export const observeWindowRect = (
|
|
|
112
116
|
}
|
|
113
117
|
handler()
|
|
114
118
|
|
|
115
|
-
element.addEventListener('resize', handler,
|
|
116
|
-
passive: true,
|
|
117
|
-
})
|
|
119
|
+
element.addEventListener('resize', handler, addEventListenerOptions)
|
|
118
120
|
|
|
119
121
|
return () => {
|
|
120
122
|
element.removeEventListener('resize', handler)
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
125
|
|
|
126
|
+
const supportsScrollend =
|
|
127
|
+
typeof window == 'undefined' ? true : 'onscrollend' in window
|
|
128
|
+
|
|
124
129
|
export const observeElementOffset = <T extends Element>(
|
|
125
130
|
instance: Virtualizer<T, any>,
|
|
126
|
-
cb: (offset: number) => void,
|
|
131
|
+
cb: (offset: number, isScrolling: boolean) => void,
|
|
127
132
|
) => {
|
|
128
133
|
const element = instance.scrollElement
|
|
129
134
|
if (!element) {
|
|
130
135
|
return
|
|
131
136
|
}
|
|
132
137
|
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
let offset = 0
|
|
139
|
+
const fallback = supportsScrollend
|
|
140
|
+
? () => undefined
|
|
141
|
+
: debounce(() => {
|
|
142
|
+
cb(offset, false)
|
|
143
|
+
}, instance.options.isScrollingResetDelay)
|
|
144
|
+
|
|
145
|
+
const createHandler = (isScrolling: boolean) => () => {
|
|
146
|
+
offset = element[instance.options.horizontal ? 'scrollLeft' : 'scrollTop']
|
|
147
|
+
fallback()
|
|
148
|
+
cb(offset, isScrolling)
|
|
135
149
|
}
|
|
136
|
-
handler()
|
|
150
|
+
const handler = createHandler(true)
|
|
151
|
+
const endHandler = createHandler(false)
|
|
152
|
+
endHandler()
|
|
137
153
|
|
|
138
|
-
element.addEventListener('scroll', handler,
|
|
139
|
-
|
|
140
|
-
})
|
|
154
|
+
element.addEventListener('scroll', handler, addEventListenerOptions)
|
|
155
|
+
element.addEventListener('scrollend', endHandler, addEventListenerOptions)
|
|
141
156
|
|
|
142
157
|
return () => {
|
|
143
158
|
element.removeEventListener('scroll', handler)
|
|
159
|
+
element.removeEventListener('scrollend', endHandler)
|
|
144
160
|
}
|
|
145
161
|
}
|
|
146
162
|
|
|
147
163
|
export const observeWindowOffset = (
|
|
148
164
|
instance: Virtualizer<Window, any>,
|
|
149
|
-
cb: (offset: number) => void,
|
|
165
|
+
cb: (offset: number, isScrolling: boolean) => void,
|
|
150
166
|
) => {
|
|
151
167
|
const element = instance.scrollElement
|
|
152
168
|
if (!element) {
|
|
153
169
|
return
|
|
154
170
|
}
|
|
155
171
|
|
|
156
|
-
|
|
157
|
-
|
|
172
|
+
let offset = 0
|
|
173
|
+
const fallback = supportsScrollend
|
|
174
|
+
? () => undefined
|
|
175
|
+
: debounce(() => {
|
|
176
|
+
cb(offset, false)
|
|
177
|
+
}, instance.options.isScrollingResetDelay)
|
|
178
|
+
|
|
179
|
+
const createHandler = (isScrolling: boolean) => () => {
|
|
180
|
+
offset = element[instance.options.horizontal ? 'scrollX' : 'scrollY']
|
|
181
|
+
fallback()
|
|
182
|
+
cb(offset, isScrolling)
|
|
158
183
|
}
|
|
159
|
-
handler()
|
|
184
|
+
const handler = createHandler(true)
|
|
185
|
+
const endHandler = createHandler(false)
|
|
186
|
+
endHandler()
|
|
160
187
|
|
|
161
|
-
element.addEventListener('scroll', handler,
|
|
162
|
-
|
|
163
|
-
})
|
|
188
|
+
element.addEventListener('scroll', handler, addEventListenerOptions)
|
|
189
|
+
element.addEventListener('scrollend', endHandler, addEventListenerOptions)
|
|
164
190
|
|
|
165
191
|
return () => {
|
|
166
192
|
element.removeEventListener('scroll', handler)
|
|
193
|
+
element.removeEventListener('scrollend', endHandler)
|
|
167
194
|
}
|
|
168
195
|
}
|
|
169
196
|
|
|
@@ -241,7 +268,7 @@ export interface VirtualizerOptions<
|
|
|
241
268
|
) => void | (() => void)
|
|
242
269
|
observeElementOffset: (
|
|
243
270
|
instance: Virtualizer<TScrollElement, TItemElement>,
|
|
244
|
-
cb: (offset: number) => void,
|
|
271
|
+
cb: (offset: number, isScrolling: boolean) => void,
|
|
245
272
|
) => void | (() => void)
|
|
246
273
|
|
|
247
274
|
// Optional
|
|
@@ -267,10 +294,10 @@ export interface VirtualizerOptions<
|
|
|
267
294
|
rangeExtractor?: (range: Range) => number[]
|
|
268
295
|
scrollMargin?: number
|
|
269
296
|
gap?: number
|
|
270
|
-
scrollingDelay?: number
|
|
271
297
|
indexAttribute?: string
|
|
272
298
|
initialMeasurementsCache?: VirtualItem[]
|
|
273
299
|
lanes?: number
|
|
300
|
+
isScrollingResetDelay?: number
|
|
274
301
|
}
|
|
275
302
|
|
|
276
303
|
export class Virtualizer<
|
|
@@ -281,7 +308,6 @@ export class Virtualizer<
|
|
|
281
308
|
options!: Required<VirtualizerOptions<TScrollElement, TItemElement>>
|
|
282
309
|
scrollElement: TScrollElement | null = null
|
|
283
310
|
isScrolling: boolean = false
|
|
284
|
-
private isScrollingTimeoutId: ReturnType<typeof setTimeout> | null = null
|
|
285
311
|
private scrollToIndexTimeoutId: ReturnType<typeof setTimeout> | null = null
|
|
286
312
|
measurementsCache: VirtualItem[] = []
|
|
287
313
|
private itemSizeCache = new Map<Key, number>()
|
|
@@ -336,7 +362,7 @@ export class Virtualizer<
|
|
|
336
362
|
this.itemSizeCache.set(item.key, item.size)
|
|
337
363
|
})
|
|
338
364
|
|
|
339
|
-
this.
|
|
365
|
+
this.notify(false, false)
|
|
340
366
|
}
|
|
341
367
|
|
|
342
368
|
setOptions = (opts: VirtualizerOptions<TScrollElement, TItemElement>) => {
|
|
@@ -360,42 +386,30 @@ export class Virtualizer<
|
|
|
360
386
|
initialRect: { width: 0, height: 0 },
|
|
361
387
|
scrollMargin: 0,
|
|
362
388
|
gap: 0,
|
|
363
|
-
scrollingDelay: 150,
|
|
364
389
|
indexAttribute: 'data-index',
|
|
365
390
|
initialMeasurementsCache: [],
|
|
366
391
|
lanes: 1,
|
|
392
|
+
isScrollingResetDelay: 150,
|
|
367
393
|
...opts,
|
|
368
394
|
}
|
|
369
395
|
}
|
|
370
396
|
|
|
371
|
-
private notify = (sync: boolean) => {
|
|
372
|
-
this.
|
|
397
|
+
private notify = (force: boolean, sync: boolean) => {
|
|
398
|
+
const { startIndex, endIndex } = this.range ?? {
|
|
399
|
+
startIndex: undefined,
|
|
400
|
+
endIndex: undefined,
|
|
401
|
+
}
|
|
402
|
+
const range = this.calculateRange()
|
|
403
|
+
|
|
404
|
+
if (
|
|
405
|
+
force ||
|
|
406
|
+
startIndex !== range?.startIndex ||
|
|
407
|
+
endIndex !== range?.endIndex
|
|
408
|
+
) {
|
|
409
|
+
this.options.onChange?.(this, sync)
|
|
410
|
+
}
|
|
373
411
|
}
|
|
374
412
|
|
|
375
|
-
private maybeNotify = memo(
|
|
376
|
-
() => {
|
|
377
|
-
this.calculateRange()
|
|
378
|
-
|
|
379
|
-
return [
|
|
380
|
-
this.isScrolling,
|
|
381
|
-
this.range ? this.range.startIndex : null,
|
|
382
|
-
this.range ? this.range.endIndex : null,
|
|
383
|
-
]
|
|
384
|
-
},
|
|
385
|
-
(isScrolling) => {
|
|
386
|
-
this.notify(isScrolling)
|
|
387
|
-
},
|
|
388
|
-
{
|
|
389
|
-
key: process.env.NODE_ENV !== 'production' && 'maybeNotify',
|
|
390
|
-
debug: () => this.options.debug,
|
|
391
|
-
initialDeps: [
|
|
392
|
-
this.isScrolling,
|
|
393
|
-
this.range ? this.range.startIndex : null,
|
|
394
|
-
this.range ? this.range.endIndex : null,
|
|
395
|
-
] as [boolean, number | null, number | null],
|
|
396
|
-
},
|
|
397
|
-
)
|
|
398
|
-
|
|
399
413
|
private cleanup = () => {
|
|
400
414
|
this.unsubs.filter(Boolean).forEach((d) => d!())
|
|
401
415
|
this.unsubs = []
|
|
@@ -426,37 +440,24 @@ export class Virtualizer<
|
|
|
426
440
|
this.unsubs.push(
|
|
427
441
|
this.options.observeElementRect(this, (rect) => {
|
|
428
442
|
this.scrollRect = rect
|
|
429
|
-
this.
|
|
443
|
+
this.notify(false, false)
|
|
430
444
|
}),
|
|
431
445
|
)
|
|
432
446
|
|
|
433
447
|
this.unsubs.push(
|
|
434
|
-
this.options.observeElementOffset(this, (offset) => {
|
|
448
|
+
this.options.observeElementOffset(this, (offset, isScrolling) => {
|
|
435
449
|
this.scrollAdjustments = 0
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (this.isScrollingTimeoutId !== null) {
|
|
442
|
-
clearTimeout(this.isScrollingTimeoutId)
|
|
443
|
-
this.isScrollingTimeoutId = null
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
this.isScrolling = true
|
|
447
|
-
this.scrollDirection =
|
|
448
|
-
this.scrollOffset < offset ? 'forward' : 'backward'
|
|
450
|
+
this.scrollDirection = isScrolling
|
|
451
|
+
? this.scrollOffset < offset
|
|
452
|
+
? 'forward'
|
|
453
|
+
: 'backward'
|
|
454
|
+
: null
|
|
449
455
|
this.scrollOffset = offset
|
|
450
456
|
|
|
451
|
-
this.
|
|
452
|
-
|
|
453
|
-
this.isScrollingTimeoutId = setTimeout(() => {
|
|
454
|
-
this.isScrollingTimeoutId = null
|
|
455
|
-
this.isScrolling = false
|
|
456
|
-
this.scrollDirection = null
|
|
457
|
+
const prevIsScrolling = this.isScrolling
|
|
458
|
+
this.isScrolling = isScrolling
|
|
457
459
|
|
|
458
|
-
|
|
459
|
-
}, this.options.scrollingDelay)
|
|
460
|
+
this.notify(prevIsScrolling !== isScrolling, isScrolling)
|
|
460
461
|
}),
|
|
461
462
|
)
|
|
462
463
|
}
|
|
@@ -466,7 +467,7 @@ export class Virtualizer<
|
|
|
466
467
|
return this.scrollRect[this.options.horizontal ? 'width' : 'height']
|
|
467
468
|
}
|
|
468
469
|
|
|
469
|
-
private
|
|
470
|
+
private getMeasurementOptions = memo(
|
|
470
471
|
() => [
|
|
471
472
|
this.options.count,
|
|
472
473
|
this.options.paddingStart,
|
|
@@ -529,7 +530,7 @@ export class Virtualizer<
|
|
|
529
530
|
}
|
|
530
531
|
|
|
531
532
|
private getMeasurements = memo(
|
|
532
|
-
() => [this.
|
|
533
|
+
() => [this.getMeasurementOptions(), this.itemSizeCache],
|
|
533
534
|
({ count, paddingStart, scrollMargin, getItemKey }, itemSizeCache) => {
|
|
534
535
|
const min =
|
|
535
536
|
this.pendingMeasuredCacheIndexes.length > 0
|
|
@@ -612,7 +613,8 @@ export class Virtualizer<
|
|
|
612
613
|
return range === null
|
|
613
614
|
? []
|
|
614
615
|
: rangeExtractor({
|
|
615
|
-
|
|
616
|
+
startIndex: range.startIndex,
|
|
617
|
+
endIndex: range.endIndex,
|
|
616
618
|
overscan,
|
|
617
619
|
count,
|
|
618
620
|
})
|
|
@@ -691,7 +693,7 @@ export class Virtualizer<
|
|
|
691
693
|
this.pendingMeasuredCacheIndexes.push(item.index)
|
|
692
694
|
this.itemSizeCache = new Map(this.itemSizeCache.set(item.key, size))
|
|
693
695
|
|
|
694
|
-
this.notify(false)
|
|
696
|
+
this.notify(true, false)
|
|
695
697
|
}
|
|
696
698
|
}
|
|
697
699
|
|
|
@@ -918,7 +920,7 @@ export class Virtualizer<
|
|
|
918
920
|
|
|
919
921
|
measure = () => {
|
|
920
922
|
this.itemSizeCache = new Map()
|
|
921
|
-
this.
|
|
923
|
+
this.options.onChange?.(this, false)
|
|
922
924
|
}
|
|
923
925
|
}
|
|
924
926
|
|
package/src/utils.ts
CHANGED
|
@@ -77,3 +77,11 @@ export function notUndefined<T>(value: T | undefined, msg?: string): T {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export const approxEqual = (a: number, b: number) => Math.abs(a - b) < 1
|
|
80
|
+
|
|
81
|
+
export const debounce = (fn: Function, ms: number) => {
|
|
82
|
+
let timeoutId: ReturnType<typeof setTimeout>
|
|
83
|
+
return function (this: any, ...args: any[]) {
|
|
84
|
+
clearTimeout(timeoutId)
|
|
85
|
+
timeoutId = setTimeout(() => fn.apply(this, args), ms)
|
|
86
|
+
}
|
|
87
|
+
}
|