@tanstack/virtual-core 3.0.0-beta.32 → 3.0.0-beta.33

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/src/index.ts CHANGED
@@ -4,6 +4,8 @@ export * from './utils'
4
4
 
5
5
  //
6
6
 
7
+ type ScrollDirection = 'forward' | 'backward'
8
+
7
9
  type ScrollAlignment = 'start' | 'center' | 'end' | 'auto'
8
10
 
9
11
  type ScrollBehavior = 'auto' | 'smooth'
@@ -149,7 +151,7 @@ const createOffsetObserver = (mode: ObserverMode) => {
149
151
  const offset =
150
152
  instance.scrollElement[instance.options.horizontal ? propX : propY]
151
153
 
152
- cb(Math.max(0, offset - instance.options.scrollMargin))
154
+ cb(offset)
153
155
  }
154
156
 
155
157
  scroll()
@@ -195,15 +197,12 @@ export const measureElement = <TItemElement extends Element>(
195
197
  export const windowScroll = <T extends Window>(
196
198
  offset: number,
197
199
  {
198
- adjustments,
200
+ adjustments = 0,
199
201
  behavior,
200
- sync,
201
- }: { adjustments?: number; behavior?: ScrollBehavior; sync: boolean },
202
+ }: { adjustments?: number; behavior?: ScrollBehavior },
202
203
  instance: Virtualizer<T, any>,
203
204
  ) => {
204
- const toOffset =
205
- (sync ? offset : offset + instance.options.scrollMargin) +
206
- (adjustments ?? 0)
205
+ const toOffset = offset + adjustments
207
206
 
208
207
  instance.scrollElement?.scrollTo?.({
209
208
  [instance.options.horizontal ? 'left' : 'top']: toOffset,
@@ -214,15 +213,12 @@ export const windowScroll = <T extends Window>(
214
213
  export const elementScroll = <T extends Element>(
215
214
  offset: number,
216
215
  {
217
- adjustments,
216
+ adjustments = 0,
218
217
  behavior,
219
- sync,
220
- }: { adjustments?: number; behavior?: ScrollBehavior; sync: boolean },
218
+ }: { adjustments?: number; behavior?: ScrollBehavior },
221
219
  instance: Virtualizer<T, any>,
222
220
  ) => {
223
- const toOffset =
224
- (sync ? offset : offset + instance.options.scrollMargin) +
225
- (adjustments ?? 0)
221
+ const toOffset = offset + adjustments
226
222
 
227
223
  instance.scrollElement?.scrollTo?.({
228
224
  [instance.options.horizontal ? 'left' : 'top']: toOffset,
@@ -242,7 +238,7 @@ export interface VirtualizerOptions<
242
238
  // Required from the framework adapter (but can be overridden)
243
239
  scrollToFn: (
244
240
  offset: number,
245
- options: { adjustments?: number; behavior?: ScrollBehavior; sync: boolean },
241
+ options: { adjustments?: number; behavior?: ScrollBehavior },
246
242
  instance: Virtualizer<TScrollElement, TItemElement>,
247
243
  ) => void
248
244
  observeElementRect: (
@@ -274,6 +270,7 @@ export interface VirtualizerOptions<
274
270
  scrollMargin?: number
275
271
  scrollingDelay?: number
276
272
  indexAttribute?: string
273
+ initialMeasurementsCache?: VirtualItem[]
277
274
  }
278
275
 
279
276
  export class Virtualizer<
@@ -286,10 +283,11 @@ export class Virtualizer<
286
283
  isScrolling: boolean = false
287
284
  private isScrollingTimeoutId: ReturnType<typeof setTimeout> | null = null
288
285
  measurementsCache: VirtualItem[] = []
289
- private itemMeasurementsCache: Record<Key, number> = {}
286
+ private itemSizeCache: Record<Key, number> = {}
290
287
  private pendingMeasuredCacheIndexes: number[] = []
291
288
  private scrollRect: Rect
292
289
  scrollOffset: number
290
+ scrollDirection: ScrollDirection | null = null
293
291
  private scrollAdjustments: number = 0
294
292
  private measureElementCache: Record<Key, TItemElement> = {}
295
293
  private pendingScrollToIndexCallback: (() => void) | null = null
@@ -319,6 +317,10 @@ export class Virtualizer<
319
317
  this.setOptions(opts)
320
318
  this.scrollRect = this.options.initialRect
321
319
  this.scrollOffset = this.options.initialOffset
320
+ this.measurementsCache = this.options.initialMeasurementsCache
321
+ this.measurementsCache.forEach((item) => {
322
+ this.itemSizeCache[item.key] = item.size
323
+ })
322
324
 
323
325
  this.calculateRange()
324
326
  }
@@ -345,6 +347,7 @@ export class Virtualizer<
345
347
  scrollMargin: 0,
346
348
  scrollingDelay: 150,
347
349
  indexAttribute: 'data-index',
350
+ initialMeasurementsCache: [],
348
351
  ...opts,
349
352
  }
350
353
  }
@@ -383,7 +386,6 @@ export class Virtualizer<
383
386
  this._scrollToOffset(this.scrollOffset, {
384
387
  adjustments: undefined,
385
388
  behavior: undefined,
386
- sync: true,
387
389
  })
388
390
 
389
391
  this.unsubs.push(
@@ -395,6 +397,12 @@ export class Virtualizer<
395
397
 
396
398
  this.unsubs.push(
397
399
  this.options.observeElementOffset(this, (offset) => {
400
+ this.scrollAdjustments = 0
401
+
402
+ if (this.scrollOffset === offset) {
403
+ return
404
+ }
405
+
398
406
  if (this.isScrollingTimeoutId !== null) {
399
407
  clearTimeout(this.isScrollingTimeoutId)
400
408
  this.isScrollingTimeoutId = null
@@ -407,17 +415,18 @@ export class Virtualizer<
407
415
  }
408
416
  }
409
417
 
410
- this.scrollAdjustments = 0
418
+ this.scrollDirection =
419
+ this.scrollOffset < offset ? 'forward' : 'backward'
411
420
 
412
- if (this.scrollOffset !== offset) {
413
- this.scrollOffset = offset
414
- onIsScrollingChange(true)
415
- }
421
+ this.scrollOffset = offset
416
422
 
417
423
  this.calculateRange()
418
424
 
425
+ onIsScrollingChange(true)
426
+
419
427
  this.isScrollingTimeoutId = setTimeout(() => {
420
428
  this.isScrollingTimeoutId = null
429
+ this.scrollDirection = null
421
430
  onIsScrollingChange(false)
422
431
  }, this.options.scrollingDelay)
423
432
  }),
@@ -435,10 +444,11 @@ export class Virtualizer<
435
444
  () => [
436
445
  this.options.count,
437
446
  this.options.paddingStart,
447
+ this.options.scrollMargin,
438
448
  this.options.getItemKey,
439
- this.itemMeasurementsCache,
449
+ this.itemSizeCache,
440
450
  ],
441
- (count, paddingStart, getItemKey, measurementsCache) => {
451
+ (count, paddingStart, scrollMargin, getItemKey, itemSizeCache) => {
442
452
  const min =
443
453
  this.pendingMeasuredCacheIndexes.length > 0
444
454
  ? Math.min(...this.pendingMeasuredCacheIndexes)
@@ -449,10 +459,10 @@ export class Virtualizer<
449
459
 
450
460
  for (let i = min; i < count; i++) {
451
461
  const key = getItemKey(i)
452
- const measuredSize = measurementsCache[key]
462
+ const measuredSize = itemSizeCache[key]
453
463
  const start = measurements[i - 1]
454
464
  ? measurements[i - 1]!.end
455
- : paddingStart
465
+ : paddingStart + scrollMargin
456
466
  const size =
457
467
  typeof measuredSize === 'number'
458
468
  ? measuredSize
@@ -462,6 +472,7 @@ export class Virtualizer<
462
472
  }
463
473
 
464
474
  this.measurementsCache = measurements
475
+
465
476
  return measurements
466
477
  },
467
478
  {
@@ -557,12 +568,16 @@ export class Virtualizer<
557
568
 
558
569
  const measuredItemSize = this.options.measureElement(node, this)
559
570
 
560
- const itemSize = this.itemMeasurementsCache[item.key] ?? item.size
571
+ const itemSize = this.itemSizeCache[item.key] ?? item.size
561
572
 
562
573
  const delta = measuredItemSize - itemSize
563
574
 
564
575
  if (delta !== 0) {
565
- if (item.start < this.scrollOffset && this.isScrolling) {
576
+ if (
577
+ item.start < this.scrollOffset &&
578
+ this.isScrolling &&
579
+ this.scrollDirection === 'backward'
580
+ ) {
566
581
  if (process.env.NODE_ENV !== 'production' && this.options.debug) {
567
582
  console.info('correction', delta)
568
583
  }
@@ -570,13 +585,12 @@ export class Virtualizer<
570
585
  this._scrollToOffset(this.scrollOffset, {
571
586
  adjustments: (this.scrollAdjustments += delta),
572
587
  behavior: undefined,
573
- sync: false,
574
588
  })
575
589
  }
576
590
 
577
591
  this.pendingMeasuredCacheIndexes.push(index)
578
- this.itemMeasurementsCache = {
579
- ...this.itemMeasurementsCache,
592
+ this.itemSizeCache = {
593
+ ...this.itemSizeCache,
580
594
  [item.key]: measuredItemSize,
581
595
  }
582
596
  this.notify()
@@ -695,7 +709,6 @@ export class Virtualizer<
695
709
  const options = {
696
710
  adjustments: undefined,
697
711
  behavior,
698
- sync: false,
699
712
  }
700
713
  this._scrollToOffset(toOffset, options)
701
714
 
@@ -712,31 +725,30 @@ export class Virtualizer<
712
725
  this._scrollToOffset(this.scrollOffset, {
713
726
  adjustments,
714
727
  behavior: options?.behavior,
715
- sync: false,
716
728
  })
717
729
  }
718
730
 
719
731
  getTotalSize = () =>
720
732
  (this.getMeasurements()[this.options.count - 1]?.end ||
721
- this.options.paddingStart) + this.options.paddingEnd
733
+ this.options.paddingStart) -
734
+ this.options.scrollMargin +
735
+ this.options.paddingEnd
722
736
 
723
737
  private _scrollToOffset = (
724
738
  offset: number,
725
739
  {
726
740
  adjustments,
727
741
  behavior,
728
- sync,
729
742
  }: {
730
743
  adjustments: number | undefined
731
744
  behavior: ScrollBehavior | undefined
732
- sync: boolean
733
745
  },
734
746
  ) => {
735
- this.options.scrollToFn(offset, { behavior, sync, adjustments }, this)
747
+ this.options.scrollToFn(offset, { behavior, adjustments }, this)
736
748
  }
737
749
 
738
750
  measure = () => {
739
- this.itemMeasurementsCache = {}
751
+ this.itemSizeCache = {}
740
752
  this.notify()
741
753
  }
742
754
  }