@tanstack/virtual-core 3.0.0-alpha.1 → 3.0.0-alpha.2
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 +61 -59
- package/build/cjs/packages/virtual-core/src/index.js.map +1 -1
- package/build/esm/index.js +61 -58
- 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 +8 -10
- package/build/umd/index.development.js +61 -59
- 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 +78 -76
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,18 @@ 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
|
|
228
232
|
initialOffset?: number
|
|
229
|
-
|
|
233
|
+
getItemKey?: (index: number) => Key
|
|
230
234
|
rangeExtractor?: (range: Range) => number[]
|
|
231
235
|
enableSmoothScroll?: boolean
|
|
232
236
|
}
|
|
233
237
|
|
|
234
238
|
export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
235
|
-
unsubs: (void | (() => void))[] = []
|
|
239
|
+
private unsubs: (void | (() => void))[] = []
|
|
236
240
|
options!: Required<VirtualizerOptions<TScrollElement, TItemElement>>
|
|
237
241
|
scrollElement: TScrollElement | null = null
|
|
238
242
|
private measurementsCache: Item[] = []
|
|
@@ -240,13 +244,8 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
240
244
|
private pendingMeasuredCacheIndexes: number[] = []
|
|
241
245
|
private scrollRect: Rect
|
|
242
246
|
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
|
|
247
|
+
private destinationOffset: undefined | number
|
|
248
|
+
private scrollCheckFrame!: ReturnType<typeof setTimeout>
|
|
250
249
|
|
|
251
250
|
constructor(opts: VirtualizerOptions<TScrollElement, TItemElement>) {
|
|
252
251
|
this.setOptions(opts)
|
|
@@ -262,16 +261,15 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
262
261
|
this.options = {
|
|
263
262
|
debug: false,
|
|
264
263
|
initialOffset: 0,
|
|
265
|
-
estimateSize: defaultEstimateSize,
|
|
266
264
|
overscan: 1,
|
|
267
265
|
paddingStart: 0,
|
|
268
266
|
paddingEnd: 0,
|
|
269
267
|
horizontal: false,
|
|
270
|
-
|
|
268
|
+
getItemKey: defaultKeyExtractor,
|
|
271
269
|
rangeExtractor: defaultRangeExtractor,
|
|
272
270
|
enableSmoothScroll: false,
|
|
273
271
|
onChange: () => {},
|
|
274
|
-
measureElement
|
|
272
|
+
measureElement,
|
|
275
273
|
initialRect: { width: 0, height: 0 },
|
|
276
274
|
...opts,
|
|
277
275
|
}
|
|
@@ -317,18 +315,17 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
317
315
|
}
|
|
318
316
|
|
|
319
317
|
private getSize = () => {
|
|
320
|
-
return this.scrollRect[this.
|
|
318
|
+
return this.scrollRect[this.options.horizontal ? 'width' : 'height']
|
|
321
319
|
}
|
|
322
320
|
|
|
323
321
|
private getMeasurements = memo(
|
|
324
322
|
() => [
|
|
325
323
|
this.options.count,
|
|
326
324
|
this.options.paddingStart,
|
|
327
|
-
this.
|
|
328
|
-
this.options.keyExtractor,
|
|
325
|
+
this.options.getItemKey,
|
|
329
326
|
this.itemMeasurementsCache,
|
|
330
327
|
],
|
|
331
|
-
(count, paddingStart,
|
|
328
|
+
(count, paddingStart, getItemKey, measurementsCache) => {
|
|
332
329
|
const min =
|
|
333
330
|
this.pendingMeasuredCacheIndexes.length > 0
|
|
334
331
|
? Math.min(...this.pendingMeasuredCacheIndexes)
|
|
@@ -338,13 +335,15 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
338
335
|
const measurements = this.measurementsCache.slice(0, min)
|
|
339
336
|
|
|
340
337
|
for (let i = min; i < count; i++) {
|
|
341
|
-
const key =
|
|
338
|
+
const key = getItemKey(i)
|
|
342
339
|
const measuredSize = measurementsCache[key]
|
|
343
340
|
const start = measurements[i - 1]
|
|
344
341
|
? measurements[i - 1]!.end
|
|
345
342
|
: paddingStart
|
|
346
343
|
const size =
|
|
347
|
-
typeof measuredSize === 'number'
|
|
344
|
+
typeof measuredSize === 'number'
|
|
345
|
+
? measuredSize
|
|
346
|
+
: this.options.estimateSize(i)
|
|
348
347
|
const end = start + size
|
|
349
348
|
measurements[i] = { index: i, start, size, end, key }
|
|
350
349
|
}
|
|
@@ -419,10 +418,12 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
419
418
|
)
|
|
420
419
|
console.info('correction', measuredItemSize - item.size)
|
|
421
420
|
|
|
422
|
-
this.
|
|
423
|
-
this.
|
|
424
|
-
|
|
425
|
-
|
|
421
|
+
if (!this.destinationOffset) {
|
|
422
|
+
this._scrollToOffset(
|
|
423
|
+
this.scrollOffset + (measuredItemSize - item.size),
|
|
424
|
+
false,
|
|
425
|
+
)
|
|
426
|
+
}
|
|
426
427
|
}
|
|
427
428
|
|
|
428
429
|
this.pendingMeasuredCacheIndexes.push(i)
|
|
@@ -450,29 +451,36 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
450
451
|
toOffset: number,
|
|
451
452
|
{ align }: ScrollToOffsetOptions = { align: 'start' },
|
|
452
453
|
) => {
|
|
453
|
-
const
|
|
454
|
-
|
|
454
|
+
const attempt = () => {
|
|
455
|
+
const offset = this.scrollOffset
|
|
456
|
+
const size = this.getSize()
|
|
457
|
+
|
|
458
|
+
if (align === 'auto') {
|
|
459
|
+
if (toOffset <= offset) {
|
|
460
|
+
align = 'start'
|
|
461
|
+
} else if (toOffset >= offset + size) {
|
|
462
|
+
align = 'end'
|
|
463
|
+
} else {
|
|
464
|
+
align = 'start'
|
|
465
|
+
}
|
|
466
|
+
}
|
|
455
467
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
align = 'start'
|
|
468
|
+
if (align === 'start') {
|
|
469
|
+
this._scrollToOffset(toOffset, true)
|
|
470
|
+
} else if (align === 'end') {
|
|
471
|
+
this._scrollToOffset(toOffset - size, true)
|
|
472
|
+
} else if (align === 'center') {
|
|
473
|
+
this._scrollToOffset(toOffset - size / 2, true)
|
|
463
474
|
}
|
|
464
475
|
}
|
|
465
476
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
} else if (align === 'center') {
|
|
471
|
-
this._scrollToOffset(toOffset - size / 2, true)
|
|
472
|
-
}
|
|
477
|
+
attempt()
|
|
478
|
+
requestAnimationFrame(() => {
|
|
479
|
+
attempt()
|
|
480
|
+
})
|
|
473
481
|
}
|
|
474
482
|
|
|
475
|
-
|
|
483
|
+
scrollToIndex = (
|
|
476
484
|
index: number,
|
|
477
485
|
{ align, ...rest }: ScrollToIndexOptions = { align: 'auto' },
|
|
478
486
|
) => {
|
|
@@ -507,42 +515,36 @@ export class Virtualizer<TScrollElement = unknown, TItemElement = unknown> {
|
|
|
507
515
|
this.scrollToOffset(toOffset, { align, ...rest })
|
|
508
516
|
}
|
|
509
517
|
|
|
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
518
|
getTotalSize = () =>
|
|
523
519
|
(this.getMeasurements()[this.options.count - 1]?.end ||
|
|
524
520
|
this.options.paddingStart) + this.options.paddingEnd
|
|
525
521
|
|
|
526
|
-
getSizeKey = () => (this.options.horizontal ? 'width' : 'height')
|
|
527
|
-
|
|
528
522
|
private _scrollToOffset = (offset: number, canSmooth: boolean) => {
|
|
529
|
-
this.
|
|
530
|
-
offset,
|
|
531
|
-
this.options.enableSmoothScroll && canSmooth,
|
|
532
|
-
this,
|
|
533
|
-
)
|
|
534
|
-
}
|
|
523
|
+
clearTimeout(this.scrollCheckFrame)
|
|
535
524
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
525
|
+
this.destinationOffset = offset
|
|
526
|
+
this.options.scrollToFn(offset, canSmooth, this)
|
|
527
|
+
|
|
528
|
+
let scrollCheckFrame: ReturnType<typeof setTimeout>
|
|
529
|
+
|
|
530
|
+
const check = () => {
|
|
531
|
+
let lastOffset = this.scrollOffset
|
|
532
|
+
this.scrollCheckFrame = scrollCheckFrame = setTimeout(() => {
|
|
533
|
+
if (this.scrollCheckFrame !== scrollCheckFrame) {
|
|
534
|
+
return
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (this.scrollOffset === lastOffset) {
|
|
538
|
+
this.destinationOffset = undefined
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
lastOffset = this.scrollOffset
|
|
542
|
+
check()
|
|
543
|
+
}, 100)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
check()
|
|
547
|
+
}
|
|
546
548
|
|
|
547
549
|
measure = () => {
|
|
548
550
|
this.itemMeasurementsCache = {}
|