@tanstack/virtual-core 3.3.0 → 3.4.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 +57 -65
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +4 -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 +4 -7
- package/dist/esm/index.js +58 -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 +77 -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
|
+
}, 150)
|
|
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
|
+
}, 150)
|
|
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,7 +294,6 @@ 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
|
|
@@ -281,7 +307,6 @@ export class Virtualizer<
|
|
|
281
307
|
options!: Required<VirtualizerOptions<TScrollElement, TItemElement>>
|
|
282
308
|
scrollElement: TScrollElement | null = null
|
|
283
309
|
isScrolling: boolean = false
|
|
284
|
-
private isScrollingTimeoutId: ReturnType<typeof setTimeout> | null = null
|
|
285
310
|
private scrollToIndexTimeoutId: ReturnType<typeof setTimeout> | null = null
|
|
286
311
|
measurementsCache: VirtualItem[] = []
|
|
287
312
|
private itemSizeCache = new Map<Key, number>()
|
|
@@ -336,7 +361,7 @@ export class Virtualizer<
|
|
|
336
361
|
this.itemSizeCache.set(item.key, item.size)
|
|
337
362
|
})
|
|
338
363
|
|
|
339
|
-
this.
|
|
364
|
+
this.notify(false, false)
|
|
340
365
|
}
|
|
341
366
|
|
|
342
367
|
setOptions = (opts: VirtualizerOptions<TScrollElement, TItemElement>) => {
|
|
@@ -360,7 +385,6 @@ export class Virtualizer<
|
|
|
360
385
|
initialRect: { width: 0, height: 0 },
|
|
361
386
|
scrollMargin: 0,
|
|
362
387
|
gap: 0,
|
|
363
|
-
scrollingDelay: 150,
|
|
364
388
|
indexAttribute: 'data-index',
|
|
365
389
|
initialMeasurementsCache: [],
|
|
366
390
|
lanes: 1,
|
|
@@ -368,34 +392,22 @@ export class Virtualizer<
|
|
|
368
392
|
}
|
|
369
393
|
}
|
|
370
394
|
|
|
371
|
-
private notify = (sync: boolean) => {
|
|
372
|
-
this.
|
|
395
|
+
private notify = (force: boolean, sync: boolean) => {
|
|
396
|
+
const { startIndex, endIndex } = this.range ?? {
|
|
397
|
+
startIndex: undefined,
|
|
398
|
+
endIndex: undefined,
|
|
399
|
+
}
|
|
400
|
+
const range = this.calculateRange()
|
|
401
|
+
|
|
402
|
+
if (
|
|
403
|
+
force ||
|
|
404
|
+
startIndex !== range?.startIndex ||
|
|
405
|
+
endIndex !== range?.endIndex
|
|
406
|
+
) {
|
|
407
|
+
this.options.onChange?.(this, sync)
|
|
408
|
+
}
|
|
373
409
|
}
|
|
374
410
|
|
|
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
411
|
private cleanup = () => {
|
|
400
412
|
this.unsubs.filter(Boolean).forEach((d) => d!())
|
|
401
413
|
this.unsubs = []
|
|
@@ -426,37 +438,24 @@ export class Virtualizer<
|
|
|
426
438
|
this.unsubs.push(
|
|
427
439
|
this.options.observeElementRect(this, (rect) => {
|
|
428
440
|
this.scrollRect = rect
|
|
429
|
-
this.
|
|
441
|
+
this.notify(false, false)
|
|
430
442
|
}),
|
|
431
443
|
)
|
|
432
444
|
|
|
433
445
|
this.unsubs.push(
|
|
434
|
-
this.options.observeElementOffset(this, (offset) => {
|
|
446
|
+
this.options.observeElementOffset(this, (offset, isScrolling) => {
|
|
435
447
|
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'
|
|
448
|
+
this.scrollDirection = isScrolling
|
|
449
|
+
? this.scrollOffset < offset
|
|
450
|
+
? 'forward'
|
|
451
|
+
: 'backward'
|
|
452
|
+
: null
|
|
449
453
|
this.scrollOffset = offset
|
|
450
454
|
|
|
451
|
-
this.
|
|
452
|
-
|
|
453
|
-
this.isScrollingTimeoutId = setTimeout(() => {
|
|
454
|
-
this.isScrollingTimeoutId = null
|
|
455
|
-
this.isScrolling = false
|
|
456
|
-
this.scrollDirection = null
|
|
455
|
+
const prevIsScrolling = this.isScrolling
|
|
456
|
+
this.isScrolling = isScrolling
|
|
457
457
|
|
|
458
|
-
|
|
459
|
-
}, this.options.scrollingDelay)
|
|
458
|
+
this.notify(prevIsScrolling !== isScrolling, isScrolling)
|
|
460
459
|
}),
|
|
461
460
|
)
|
|
462
461
|
}
|
|
@@ -466,7 +465,7 @@ export class Virtualizer<
|
|
|
466
465
|
return this.scrollRect[this.options.horizontal ? 'width' : 'height']
|
|
467
466
|
}
|
|
468
467
|
|
|
469
|
-
private
|
|
468
|
+
private getMeasurementOptions = memo(
|
|
470
469
|
() => [
|
|
471
470
|
this.options.count,
|
|
472
471
|
this.options.paddingStart,
|
|
@@ -529,7 +528,7 @@ export class Virtualizer<
|
|
|
529
528
|
}
|
|
530
529
|
|
|
531
530
|
private getMeasurements = memo(
|
|
532
|
-
() => [this.
|
|
531
|
+
() => [this.getMeasurementOptions(), this.itemSizeCache],
|
|
533
532
|
({ count, paddingStart, scrollMargin, getItemKey }, itemSizeCache) => {
|
|
534
533
|
const min =
|
|
535
534
|
this.pendingMeasuredCacheIndexes.length > 0
|
|
@@ -612,7 +611,8 @@ export class Virtualizer<
|
|
|
612
611
|
return range === null
|
|
613
612
|
? []
|
|
614
613
|
: rangeExtractor({
|
|
615
|
-
|
|
614
|
+
startIndex: range.startIndex,
|
|
615
|
+
endIndex: range.endIndex,
|
|
616
616
|
overscan,
|
|
617
617
|
count,
|
|
618
618
|
})
|
|
@@ -691,7 +691,7 @@ export class Virtualizer<
|
|
|
691
691
|
this.pendingMeasuredCacheIndexes.push(item.index)
|
|
692
692
|
this.itemSizeCache = new Map(this.itemSizeCache.set(item.key, size))
|
|
693
693
|
|
|
694
|
-
this.notify(false)
|
|
694
|
+
this.notify(true, false)
|
|
695
695
|
}
|
|
696
696
|
}
|
|
697
697
|
|
|
@@ -918,7 +918,7 @@ export class Virtualizer<
|
|
|
918
918
|
|
|
919
919
|
measure = () => {
|
|
920
920
|
this.itemSizeCache = new Map()
|
|
921
|
-
this.
|
|
921
|
+
this.options.onChange?.(this, false)
|
|
922
922
|
}
|
|
923
923
|
}
|
|
924
924
|
|
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
|
+
}
|