@tanstack/virtual-core 3.0.0-beta.20 → 3.0.0-beta.21
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 +80 -36
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.js +80 -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 +80 -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 +108 -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,12 @@ 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<string, TItemElement> = {}
|
|
284
|
+
private ro = new ResizeObserver((entries) => {
|
|
285
|
+
entries.forEach((entry) => {
|
|
286
|
+
this._measureElement(entry.target as TItemElement, false)
|
|
287
|
+
})
|
|
288
|
+
})
|
|
285
289
|
range: { startIndex: number; endIndex: number } = {
|
|
286
290
|
startIndex: 0,
|
|
287
291
|
endIndex: 0,
|
|
@@ -317,6 +321,7 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
317
321
|
initialRect: { width: 0, height: 0 },
|
|
318
322
|
scrollMargin: 0,
|
|
319
323
|
scrollingDelay: 150,
|
|
324
|
+
indexAttribute: 'data-index',
|
|
320
325
|
...opts,
|
|
321
326
|
}
|
|
322
327
|
}
|
|
@@ -333,6 +338,9 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
333
338
|
|
|
334
339
|
_didMount = () => {
|
|
335
340
|
return () => {
|
|
341
|
+
this.ro.disconnect()
|
|
342
|
+
this.measureElementCache = {}
|
|
343
|
+
|
|
336
344
|
this.cleanup()
|
|
337
345
|
}
|
|
338
346
|
}
|
|
@@ -474,68 +482,97 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
474
482
|
},
|
|
475
483
|
)
|
|
476
484
|
|
|
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]!
|
|
485
|
+
indexFromElement = (node: TItemElement) => {
|
|
486
|
+
const attributeName = this.options.indexAttribute
|
|
487
|
+
const indexStr = node.getAttribute(attributeName)
|
|
487
488
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
489
|
+
if (!indexStr) {
|
|
490
|
+
console.warn(
|
|
491
|
+
`Missing attribute name '${attributeName}={index}' on measured element.`,
|
|
492
|
+
)
|
|
493
|
+
return -1
|
|
494
|
+
}
|
|
491
495
|
|
|
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
|
-
|
|
496
|
+
return parseInt(indexStr, 10)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
_measureElement = (node: TItemElement, _sync: boolean) => {
|
|
500
|
+
const index = this.indexFromElement(node)
|
|
501
|
+
|
|
502
|
+
const item = this.measurementsCache[index]
|
|
503
|
+
if (!item) {
|
|
504
|
+
return
|
|
505
|
+
}
|
|
506
|
+
const key = String(item.key)
|
|
507
|
+
|
|
508
|
+
const prevNode = this.measureElementCache[key]
|
|
509
|
+
|
|
510
|
+
if (!node.isConnected) {
|
|
511
|
+
if (prevNode) {
|
|
512
|
+
this.ro.unobserve(prevNode)
|
|
513
|
+
delete this.measureElementCache[key]
|
|
514
|
+
}
|
|
515
|
+
return
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (!prevNode || prevNode !== node) {
|
|
519
|
+
if (prevNode) {
|
|
520
|
+
this.ro.unobserve(prevNode)
|
|
521
|
+
}
|
|
522
|
+
this.measureElementCache[key] = node
|
|
523
|
+
this.ro.observe(node)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const measuredItemSize = this.options.measureElement(node, this)
|
|
527
|
+
|
|
528
|
+
const itemSize = this.itemMeasurementsCache[item.key] ?? item.size
|
|
529
|
+
|
|
530
|
+
if (measuredItemSize !== itemSize) {
|
|
531
|
+
if (item.start < this.scrollOffset) {
|
|
532
|
+
if (process.env.NODE_ENV !== 'production' && this.options.debug) {
|
|
533
|
+
console.info('correction', measuredItemSize - itemSize)
|
|
519
534
|
}
|
|
520
535
|
|
|
521
|
-
|
|
536
|
+
if (this.destinationOffset === undefined) {
|
|
537
|
+
this.scrollDelta += measuredItemSize - itemSize
|
|
522
538
|
|
|
523
|
-
|
|
539
|
+
this._scrollToOffset(this.scrollOffset + this.scrollDelta, {
|
|
540
|
+
canSmooth: false,
|
|
541
|
+
sync: false,
|
|
542
|
+
requested: false,
|
|
543
|
+
})
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
this.pendingMeasuredCacheIndexes.push(index)
|
|
548
|
+
this.itemMeasurementsCache = {
|
|
549
|
+
...this.itemMeasurementsCache,
|
|
550
|
+
[item.key]: measuredItemSize,
|
|
551
|
+
}
|
|
552
|
+
this.notify()
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
measureElement = (node: TItemElement | null) => {
|
|
557
|
+
if (!node) {
|
|
558
|
+
return
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
this._measureElement(node, true)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
getVirtualItems = memo(
|
|
565
|
+
() => [this.getIndexes(), this.getMeasurements()],
|
|
566
|
+
(indexes, measurements) => {
|
|
567
|
+
const virtualItems: VirtualItem[] = []
|
|
524
568
|
|
|
525
569
|
for (let k = 0, len = indexes.length; k < len; k++) {
|
|
526
570
|
const i = indexes[k]!
|
|
527
571
|
const measurement = measurements[i]!
|
|
528
572
|
|
|
529
|
-
|
|
530
|
-
...measurement,
|
|
531
|
-
measureElement: (currentMeasureElements[i] =
|
|
532
|
-
this.measureElementCache[i] ?? makeMeasureElement(i)),
|
|
533
|
-
}
|
|
534
|
-
virtualItems.push(item)
|
|
573
|
+
virtualItems.push(measurement)
|
|
535
574
|
}
|
|
536
575
|
|
|
537
|
-
this.measureElementCache = currentMeasureElements
|
|
538
|
-
|
|
539
576
|
return virtualItems
|
|
540
577
|
},
|
|
541
578
|
{
|
|
@@ -695,7 +732,7 @@ function calculateRange({
|
|
|
695
732
|
outerSize,
|
|
696
733
|
scrollOffset,
|
|
697
734
|
}: {
|
|
698
|
-
measurements:
|
|
735
|
+
measurements: VirtualItem[]
|
|
699
736
|
outerSize: number
|
|
700
737
|
scrollOffset: number
|
|
701
738
|
}) {
|