@tanstack/virtual-core 3.0.0-beta.20 → 3.0.0-beta.22
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/build/cjs/index.js +95 -36
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.js +95 -36
- package/build/esm/index.js.map +1 -1
- package/build/stats.html +1 -1
- package/build/stats.json +14 -14
- package/build/types/index.d.ts +12 -10
- package/build/umd/index.development.js +95 -36
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +1 -1
- package/build/umd/index.production.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +121 -71
package/src/index.ts
CHANGED
|
@@ -24,7 +24,7 @@ export interface Range {
|
|
|
24
24
|
|
|
25
25
|
type Key = number | string
|
|
26
26
|
|
|
27
|
-
interface
|
|
27
|
+
export interface VirtualItem {
|
|
28
28
|
key: Key
|
|
29
29
|
index: number
|
|
30
30
|
start: number
|
|
@@ -37,10 +37,6 @@ interface Rect {
|
|
|
37
37
|
height: number
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export interface VirtualItem<TItemElement> extends Item {
|
|
41
|
-
measureElement: (el: TItemElement | null) => void
|
|
42
|
-
}
|
|
43
|
-
|
|
44
40
|
//
|
|
45
41
|
|
|
46
42
|
export const defaultKeyExtractor = (index: number) => index
|
|
@@ -183,13 +179,15 @@ const createOffsetObserver = (mode: ObserverMode) => {
|
|
|
183
179
|
export const observeElementOffset = createOffsetObserver('element')
|
|
184
180
|
export const observeWindowOffset = createOffsetObserver('window')
|
|
185
181
|
|
|
186
|
-
export const measureElement = (
|
|
187
|
-
element:
|
|
188
|
-
instance: Virtualizer<any,
|
|
182
|
+
export const measureElement = <TItemElement extends Element>(
|
|
183
|
+
element: TItemElement,
|
|
184
|
+
instance: Virtualizer<any, TItemElement>,
|
|
189
185
|
) => {
|
|
190
|
-
return
|
|
191
|
-
|
|
192
|
-
|
|
186
|
+
return Math.round(
|
|
187
|
+
element.getBoundingClientRect()[
|
|
188
|
+
instance.options.horizontal ? 'width' : 'height'
|
|
189
|
+
],
|
|
190
|
+
)
|
|
193
191
|
}
|
|
194
192
|
|
|
195
193
|
export const windowScroll = (
|
|
@@ -219,12 +217,12 @@ export const elementScroll = (
|
|
|
219
217
|
}
|
|
220
218
|
|
|
221
219
|
export interface VirtualizerOptions<
|
|
222
|
-
TScrollElement
|
|
223
|
-
TItemElement
|
|
220
|
+
TScrollElement extends unknown,
|
|
221
|
+
TItemElement extends Element,
|
|
224
222
|
> {
|
|
225
223
|
// Required from the user
|
|
226
224
|
count: number
|
|
227
|
-
getScrollElement: () => TScrollElement
|
|
225
|
+
getScrollElement: () => TScrollElement | null
|
|
228
226
|
estimateSize: (index: number) => number
|
|
229
227
|
|
|
230
228
|
// Required from the framework adapter (but can be overridden)
|
|
@@ -262,15 +260,19 @@ export interface VirtualizerOptions<
|
|
|
262
260
|
enableSmoothScroll?: boolean
|
|
263
261
|
scrollMargin?: number
|
|
264
262
|
scrollingDelay?: number
|
|
263
|
+
indexAttribute?: string
|
|
265
264
|
}
|
|
266
265
|
|
|
267
|
-
export class Virtualizer<
|
|
266
|
+
export class Virtualizer<
|
|
267
|
+
TScrollElement extends unknown,
|
|
268
|
+
TItemElement extends Element,
|
|
269
|
+
> {
|
|
268
270
|
private unsubs: (void | (() => void))[] = []
|
|
269
271
|
options!: Required<VirtualizerOptions<TScrollElement, TItemElement>>
|
|
270
272
|
scrollElement: TScrollElement | null = null
|
|
271
273
|
isScrolling: boolean = false
|
|
272
274
|
private isScrollingTimeoutId: ReturnType<typeof setTimeout> | null = null
|
|
273
|
-
|
|
275
|
+
measurementsCache: VirtualItem[] = []
|
|
274
276
|
private itemMeasurementsCache: Record<Key, number> = {}
|
|
275
277
|
private pendingMeasuredCacheIndexes: number[] = []
|
|
276
278
|
private scrollRect: Rect
|
|
@@ -278,10 +280,24 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
278
280
|
private scrollDelta: number = 0
|
|
279
281
|
private destinationOffset: undefined | number
|
|
280
282
|
private scrollCheckFrame!: ReturnType<typeof setTimeout>
|
|
281
|
-
private measureElementCache: Record<
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
283
|
+
private measureElementCache: Record<Key, TItemElement> = {}
|
|
284
|
+
private getResizeObserver = (() => {
|
|
285
|
+
let _ro: ResizeObserver | null = null
|
|
286
|
+
|
|
287
|
+
return () => {
|
|
288
|
+
if (_ro) {
|
|
289
|
+
return _ro
|
|
290
|
+
} else if (typeof ResizeObserver !== 'undefined') {
|
|
291
|
+
return (_ro = new ResizeObserver((entries) => {
|
|
292
|
+
entries.forEach((entry) => {
|
|
293
|
+
this._measureElement(entry.target as TItemElement, false)
|
|
294
|
+
})
|
|
295
|
+
}))
|
|
296
|
+
} else {
|
|
297
|
+
return null
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
})()
|
|
285
301
|
range: { startIndex: number; endIndex: number } = {
|
|
286
302
|
startIndex: 0,
|
|
287
303
|
endIndex: 0,
|
|
@@ -317,6 +333,7 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
317
333
|
initialRect: { width: 0, height: 0 },
|
|
318
334
|
scrollMargin: 0,
|
|
319
335
|
scrollingDelay: 150,
|
|
336
|
+
indexAttribute: 'data-index',
|
|
320
337
|
...opts,
|
|
321
338
|
}
|
|
322
339
|
}
|
|
@@ -333,6 +350,9 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
333
350
|
|
|
334
351
|
_didMount = () => {
|
|
335
352
|
return () => {
|
|
353
|
+
this.getResizeObserver()?.disconnect()
|
|
354
|
+
this.measureElementCache = {}
|
|
355
|
+
|
|
336
356
|
this.cleanup()
|
|
337
357
|
}
|
|
338
358
|
}
|
|
@@ -474,68 +494,98 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
474
494
|
},
|
|
475
495
|
)
|
|
476
496
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
this.getMeasurements(),
|
|
481
|
-
this.options.measureElement,
|
|
482
|
-
],
|
|
483
|
-
(indexes, measurements, measureElement) => {
|
|
484
|
-
const makeMeasureElement =
|
|
485
|
-
(index: number) => (measurableItem: TItemElement | null) => {
|
|
486
|
-
const item = this.measurementsCache[index]!
|
|
497
|
+
indexFromElement = (node: TItemElement) => {
|
|
498
|
+
const attributeName = this.options.indexAttribute
|
|
499
|
+
const indexStr = node.getAttribute(attributeName)
|
|
487
500
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
501
|
+
if (!indexStr) {
|
|
502
|
+
console.warn(
|
|
503
|
+
`Missing attribute name '${attributeName}={index}' on measured element.`,
|
|
504
|
+
)
|
|
505
|
+
return -1
|
|
506
|
+
}
|
|
491
507
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
508
|
+
return parseInt(indexStr, 10)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
private _measureElement = (node: TItemElement, _sync: boolean) => {
|
|
512
|
+
const index = this.indexFromElement(node)
|
|
513
|
+
|
|
514
|
+
const item = this.measurementsCache[index]
|
|
515
|
+
if (!item) {
|
|
516
|
+
return
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const prevNode = this.measureElementCache[item.key]
|
|
520
|
+
|
|
521
|
+
const ro = this.getResizeObserver()
|
|
522
|
+
|
|
523
|
+
if (!node.isConnected) {
|
|
524
|
+
if (prevNode) {
|
|
525
|
+
ro?.unobserve(prevNode)
|
|
526
|
+
delete this.measureElementCache[item.key]
|
|
527
|
+
}
|
|
528
|
+
return
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (!prevNode || prevNode !== node) {
|
|
532
|
+
if (prevNode) {
|
|
533
|
+
ro?.unobserve(prevNode)
|
|
534
|
+
}
|
|
535
|
+
this.measureElementCache[item.key] = node
|
|
536
|
+
ro?.observe(node)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const measuredItemSize = this.options.measureElement(node, this)
|
|
540
|
+
|
|
541
|
+
const itemSize = this.itemMeasurementsCache[item.key] ?? item.size
|
|
542
|
+
|
|
543
|
+
if (measuredItemSize !== itemSize) {
|
|
544
|
+
if (item.start < this.scrollOffset) {
|
|
545
|
+
if (process.env.NODE_ENV !== 'production' && this.options.debug) {
|
|
546
|
+
console.info('correction', measuredItemSize - itemSize)
|
|
519
547
|
}
|
|
520
548
|
|
|
521
|
-
|
|
549
|
+
if (this.destinationOffset === undefined) {
|
|
550
|
+
this.scrollDelta += measuredItemSize - itemSize
|
|
522
551
|
|
|
523
|
-
|
|
552
|
+
this._scrollToOffset(this.scrollOffset + this.scrollDelta, {
|
|
553
|
+
canSmooth: false,
|
|
554
|
+
sync: false,
|
|
555
|
+
requested: false,
|
|
556
|
+
})
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
this.pendingMeasuredCacheIndexes.push(index)
|
|
561
|
+
this.itemMeasurementsCache = {
|
|
562
|
+
...this.itemMeasurementsCache,
|
|
563
|
+
[item.key]: measuredItemSize,
|
|
564
|
+
}
|
|
565
|
+
this.notify()
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
measureElement = (node: TItemElement | null) => {
|
|
570
|
+
if (!node) {
|
|
571
|
+
return
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
this._measureElement(node, true)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
getVirtualItems = memo(
|
|
578
|
+
() => [this.getIndexes(), this.getMeasurements()],
|
|
579
|
+
(indexes, measurements) => {
|
|
580
|
+
const virtualItems: VirtualItem[] = []
|
|
524
581
|
|
|
525
582
|
for (let k = 0, len = indexes.length; k < len; k++) {
|
|
526
583
|
const i = indexes[k]!
|
|
527
584
|
const measurement = measurements[i]!
|
|
528
585
|
|
|
529
|
-
|
|
530
|
-
...measurement,
|
|
531
|
-
measureElement: (currentMeasureElements[i] =
|
|
532
|
-
this.measureElementCache[i] ?? makeMeasureElement(i)),
|
|
533
|
-
}
|
|
534
|
-
virtualItems.push(item)
|
|
586
|
+
virtualItems.push(measurement)
|
|
535
587
|
}
|
|
536
588
|
|
|
537
|
-
this.measureElementCache = currentMeasureElements
|
|
538
|
-
|
|
539
589
|
return virtualItems
|
|
540
590
|
},
|
|
541
591
|
{
|
|
@@ -695,7 +745,7 @@ function calculateRange({
|
|
|
695
745
|
outerSize,
|
|
696
746
|
scrollOffset,
|
|
697
747
|
}: {
|
|
698
|
-
measurements:
|
|
748
|
+
measurements: VirtualItem[]
|
|
699
749
|
outerSize: number
|
|
700
750
|
scrollOffset: number
|
|
701
751
|
}) {
|