@tanstack/virtual-core 3.0.0-alpha.1 → 3.0.0-beta.1
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/packages/virtual-core/src/index.js +66 -62
- package/build/cjs/packages/virtual-core/src/index.js.map +1 -1
- package/build/esm/index.js +66 -61
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +21 -21
- package/build/types/index.d.ts +10 -10
- package/build/umd/index.development.js +66 -62
- 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 +90 -79
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import observeRect from '@reach/observe-rect'
|
|
2
|
+
import { check } from 'prettier'
|
|
2
3
|
import React from 'react'
|
|
3
4
|
import { memo } from './utils'
|
|
4
5
|
|
|
@@ -44,7 +45,6 @@ export interface VirtualItem<TItemElement> extends Item {
|
|
|
44
45
|
|
|
45
46
|
//
|
|
46
47
|
|
|
47
|
-
export const defaultEstimateSize = () => 50
|
|
48
48
|
export const defaultKeyExtractor = (index: number) => index
|
|
49
49
|
|
|
50
50
|
export const defaultRangeExtractor = (range: Range) => {
|
|
@@ -162,11 +162,13 @@ export const observeWindowOffset = (
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
export const
|
|
165
|
+
export const measureElement = (
|
|
166
166
|
element: unknown,
|
|
167
167
|
instance: Virtualizer<any, any>,
|
|
168
168
|
) => {
|
|
169
|
-
return (element as Element).getBoundingClientRect()[
|
|
169
|
+
return (element as Element).getBoundingClientRect()[
|
|
170
|
+
instance.options.horizontal ? 'width' : 'height'
|
|
171
|
+
]
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
export const windowScroll = (
|
|
@@ -195,13 +197,17 @@ export interface VirtualizerOptions<
|
|
|
195
197
|
TScrollElement = unknown,
|
|
196
198
|
TItemElement = unknown,
|
|
197
199
|
> {
|
|
200
|
+
// Required from the user
|
|
198
201
|
count: number
|
|
202
|
+
getScrollElement: () => TScrollElement
|
|
203
|
+
estimateSize: (index: number) => number
|
|
204
|
+
|
|
205
|
+
// Required from the framework adapter (but can be overridden)
|
|
199
206
|
scrollToFn: (
|
|
200
207
|
offset: number,
|
|
201
208
|
canSmooth: boolean,
|
|
202
209
|
instance: Virtualizer<TScrollElement, TItemElement>,
|
|
203
210
|
) => void
|
|
204
|
-
getScrollElement: () => TScrollElement
|
|
205
211
|
observeElementRect: (
|
|
206
212
|
instance: Virtualizer<TScrollElement, TItemElement>,
|
|
207
213
|
cb: (rect: Rect) => void,
|
|
@@ -211,8 +217,7 @@ export interface VirtualizerOptions<
|
|
|
211
217
|
cb: (offset: number) => void,
|
|
212
218
|
) => void | (() => void)
|
|
213
219
|
|
|
214
|
-
//
|
|
215
|
-
|
|
220
|
+
// Optional
|
|
216
221
|
debug?: any
|
|
217
222
|
initialRect?: Rect
|
|
218
223
|
onChange?: (instance: Virtualizer<TScrollElement, TItemElement>) => void
|
|
@@ -220,19 +225,20 @@ export interface VirtualizerOptions<
|
|
|
220
225
|
el: TItemElement,
|
|
221
226
|
instance: Virtualizer<TScrollElement, TItemElement>,
|
|
222
227
|
) => number
|
|
223
|
-
estimateSize?: (index: number) => number
|
|
224
228
|
overscan?: number
|
|
225
229
|
horizontal?: boolean
|
|
226
230
|
paddingStart?: number
|
|
227
231
|
paddingEnd?: number
|
|
232
|
+
scrollPaddingStart?: number
|
|
233
|
+
scrollPaddingEnd?: number
|
|
228
234
|
initialOffset?: number
|
|
229
|
-
|
|
235
|
+
getItemKey?: (index: number) => Key
|
|
230
236
|
rangeExtractor?: (range: Range) => number[]
|
|
231
237
|
enableSmoothScroll?: boolean
|
|
232
238
|
}
|
|
233
239
|
|
|
234
240
|
export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
235
|
-
unsubs: (void | (() => void))[] = []
|
|
241
|
+
private unsubs: (void | (() => void))[] = []
|
|
236
242
|
options!: Required<VirtualizerOptions<TScrollElement, TItemElement>>
|
|
237
243
|
scrollElement: TScrollElement | null = null
|
|
238
244
|
private measurementsCache: Item[] = []
|
|
@@ -240,13 +246,8 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
240
246
|
private pendingMeasuredCacheIndexes: number[] = []
|
|
241
247
|
private scrollRect: Rect
|
|
242
248
|
private scrollOffset: number
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// virtualItems: VirtualItem<TItemElement>[]
|
|
246
|
-
// totalSize: number
|
|
247
|
-
// scrollToOffset: (offset: number, options?: ScrollToOffsetOptions) => void
|
|
248
|
-
// scrollToIndex: (index: number, options?: ScrollToIndexOptions) => void
|
|
249
|
-
// measure: (index: number) => void
|
|
249
|
+
private destinationOffset: undefined | number
|
|
250
|
+
private scrollCheckFrame!: ReturnType<typeof setTimeout>
|
|
250
251
|
|
|
251
252
|
constructor(opts: VirtualizerOptions<TScrollElement, TItemElement>) {
|
|
252
253
|
this.setOptions(opts)
|
|
@@ -262,16 +263,17 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
262
263
|
this.options = {
|
|
263
264
|
debug: false,
|
|
264
265
|
initialOffset: 0,
|
|
265
|
-
estimateSize: defaultEstimateSize,
|
|
266
266
|
overscan: 1,
|
|
267
267
|
paddingStart: 0,
|
|
268
268
|
paddingEnd: 0,
|
|
269
|
+
scrollPaddingStart: 0,
|
|
270
|
+
scrollPaddingEnd: 0,
|
|
269
271
|
horizontal: false,
|
|
270
|
-
|
|
272
|
+
getItemKey: defaultKeyExtractor,
|
|
271
273
|
rangeExtractor: defaultRangeExtractor,
|
|
272
|
-
enableSmoothScroll:
|
|
274
|
+
enableSmoothScroll: true,
|
|
273
275
|
onChange: () => {},
|
|
274
|
-
measureElement
|
|
276
|
+
measureElement,
|
|
275
277
|
initialRect: { width: 0, height: 0 },
|
|
276
278
|
...opts,
|
|
277
279
|
}
|
|
@@ -317,18 +319,17 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
317
319
|
}
|
|
318
320
|
|
|
319
321
|
private getSize = () => {
|
|
320
|
-
return this.scrollRect[this.
|
|
322
|
+
return this.scrollRect[this.options.horizontal ? 'width' : 'height']
|
|
321
323
|
}
|
|
322
324
|
|
|
323
325
|
private getMeasurements = memo(
|
|
324
326
|
() => [
|
|
325
327
|
this.options.count,
|
|
326
328
|
this.options.paddingStart,
|
|
327
|
-
this.
|
|
328
|
-
this.options.keyExtractor,
|
|
329
|
+
this.options.getItemKey,
|
|
329
330
|
this.itemMeasurementsCache,
|
|
330
331
|
],
|
|
331
|
-
(count, paddingStart,
|
|
332
|
+
(count, paddingStart, getItemKey, measurementsCache) => {
|
|
332
333
|
const min =
|
|
333
334
|
this.pendingMeasuredCacheIndexes.length > 0
|
|
334
335
|
? Math.min(...this.pendingMeasuredCacheIndexes)
|
|
@@ -338,13 +339,15 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
338
339
|
const measurements = this.measurementsCache.slice(0, min)
|
|
339
340
|
|
|
340
341
|
for (let i = min; i < count; i++) {
|
|
341
|
-
const key =
|
|
342
|
+
const key = getItemKey(i)
|
|
342
343
|
const measuredSize = measurementsCache[key]
|
|
343
344
|
const start = measurements[i - 1]
|
|
344
345
|
? measurements[i - 1]!.end
|
|
345
346
|
: paddingStart
|
|
346
347
|
const size =
|
|
347
|
-
typeof measuredSize === 'number'
|
|
348
|
+
typeof measuredSize === 'number'
|
|
349
|
+
? measuredSize
|
|
350
|
+
: this.options.estimateSize(i)
|
|
348
351
|
const end = start + size
|
|
349
352
|
measurements[i] = { index: i, start, size, end, key }
|
|
350
353
|
}
|
|
@@ -419,10 +422,12 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
419
422
|
)
|
|
420
423
|
console.info('correction', measuredItemSize - item.size)
|
|
421
424
|
|
|
422
|
-
this.
|
|
423
|
-
this.
|
|
424
|
-
|
|
425
|
-
|
|
425
|
+
if (!this.destinationOffset) {
|
|
426
|
+
this._scrollToOffset(
|
|
427
|
+
this.scrollOffset + (measuredItemSize - item.size),
|
|
428
|
+
false,
|
|
429
|
+
)
|
|
430
|
+
}
|
|
426
431
|
}
|
|
427
432
|
|
|
428
433
|
this.pendingMeasuredCacheIndexes.push(i)
|
|
@@ -450,29 +455,36 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
450
455
|
toOffset: number,
|
|
451
456
|
{ align }: ScrollToOffsetOptions = { align: 'start' },
|
|
452
457
|
) => {
|
|
453
|
-
const
|
|
454
|
-
|
|
458
|
+
const attempt = () => {
|
|
459
|
+
const offset = this.scrollOffset
|
|
460
|
+
const size = this.getSize()
|
|
461
|
+
|
|
462
|
+
if (align === 'auto') {
|
|
463
|
+
if (toOffset <= offset) {
|
|
464
|
+
align = 'start'
|
|
465
|
+
} else if (toOffset >= offset + size) {
|
|
466
|
+
align = 'end'
|
|
467
|
+
} else {
|
|
468
|
+
align = 'start'
|
|
469
|
+
}
|
|
470
|
+
}
|
|
455
471
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
align = 'start'
|
|
472
|
+
if (align === 'start') {
|
|
473
|
+
this._scrollToOffset(toOffset, true)
|
|
474
|
+
} else if (align === 'end') {
|
|
475
|
+
this._scrollToOffset(toOffset - size, true)
|
|
476
|
+
} else if (align === 'center') {
|
|
477
|
+
this._scrollToOffset(toOffset - size / 2, true)
|
|
463
478
|
}
|
|
464
479
|
}
|
|
465
480
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
} else if (align === 'center') {
|
|
471
|
-
this._scrollToOffset(toOffset - size / 2, true)
|
|
472
|
-
}
|
|
481
|
+
attempt()
|
|
482
|
+
requestAnimationFrame(() => {
|
|
483
|
+
attempt()
|
|
484
|
+
})
|
|
473
485
|
}
|
|
474
486
|
|
|
475
|
-
|
|
487
|
+
scrollToIndex = (
|
|
476
488
|
index: number,
|
|
477
489
|
{ align, ...rest }: ScrollToIndexOptions = { align: 'auto' },
|
|
478
490
|
) => {
|
|
@@ -488,9 +500,12 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
488
500
|
}
|
|
489
501
|
|
|
490
502
|
if (align === 'auto') {
|
|
491
|
-
if (measurement.end >= offset + size) {
|
|
503
|
+
if (measurement.end >= offset + size - this.options.scrollPaddingEnd) {
|
|
492
504
|
align = 'end'
|
|
493
|
-
} else if (
|
|
505
|
+
} else if (
|
|
506
|
+
measurement.start <=
|
|
507
|
+
offset + this.options.scrollPaddingStart
|
|
508
|
+
) {
|
|
494
509
|
align = 'start'
|
|
495
510
|
} else {
|
|
496
511
|
return
|
|
@@ -498,51 +513,47 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
498
513
|
}
|
|
499
514
|
|
|
500
515
|
const toOffset =
|
|
501
|
-
align === '
|
|
502
|
-
? measurement.
|
|
503
|
-
:
|
|
504
|
-
? measurement.end
|
|
505
|
-
: measurement.start
|
|
516
|
+
align === 'end'
|
|
517
|
+
? measurement.end + this.options.scrollPaddingEnd
|
|
518
|
+
: measurement.start - this.options.scrollPaddingStart
|
|
506
519
|
|
|
507
520
|
this.scrollToOffset(toOffset, { align, ...rest })
|
|
508
521
|
}
|
|
509
522
|
|
|
510
|
-
scrollToIndex = (index: number, options?: ScrollToIndexOptions) => {
|
|
511
|
-
// We do a double request here because of
|
|
512
|
-
// dynamic sizes which can cause offset shift
|
|
513
|
-
// and end up in the wrong spot. Unfortunately,
|
|
514
|
-
// we can't know about those dynamic sizes until
|
|
515
|
-
// we try and render them. So double down!
|
|
516
|
-
this.tryScrollToIndex(index, options)
|
|
517
|
-
requestAnimationFrame(() => {
|
|
518
|
-
this.tryScrollToIndex(index, options)
|
|
519
|
-
})
|
|
520
|
-
}
|
|
521
|
-
|
|
522
523
|
getTotalSize = () =>
|
|
523
524
|
(this.getMeasurements()[this.options.count - 1]?.end ||
|
|
524
525
|
this.options.paddingStart) + this.options.paddingEnd
|
|
525
526
|
|
|
526
|
-
getSizeKey = () => (this.options.horizontal ? 'width' : 'height')
|
|
527
|
-
|
|
528
527
|
private _scrollToOffset = (offset: number, canSmooth: boolean) => {
|
|
528
|
+
clearTimeout(this.scrollCheckFrame)
|
|
529
|
+
|
|
530
|
+
this.destinationOffset = offset
|
|
529
531
|
this.options.scrollToFn(
|
|
530
532
|
offset,
|
|
531
533
|
this.options.enableSmoothScroll && canSmooth,
|
|
532
534
|
this,
|
|
533
535
|
)
|
|
534
|
-
}
|
|
535
536
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
(
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
537
|
+
let scrollCheckFrame: ReturnType<typeof setTimeout>
|
|
538
|
+
|
|
539
|
+
const check = () => {
|
|
540
|
+
let lastOffset = this.scrollOffset
|
|
541
|
+
this.scrollCheckFrame = scrollCheckFrame = setTimeout(() => {
|
|
542
|
+
if (this.scrollCheckFrame !== scrollCheckFrame) {
|
|
543
|
+
return
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (this.scrollOffset === lastOffset) {
|
|
547
|
+
this.destinationOffset = undefined
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
lastOffset = this.scrollOffset
|
|
551
|
+
check()
|
|
552
|
+
}, 100)
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
check()
|
|
556
|
+
}
|
|
546
557
|
|
|
547
558
|
measure = () => {
|
|
548
559
|
this.itemMeasurementsCache = {}
|