@tanstack/virtual-core 3.0.0-beta.35 → 3.0.0-beta.39

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
@@ -288,6 +288,7 @@ export class Virtualizer<
288
288
  scrollElement: TScrollElement | null = null
289
289
  isScrolling: boolean = false
290
290
  private isScrollingTimeoutId: ReturnType<typeof setTimeout> | null = null
291
+ private scrollToIndexTimeoutId: ReturnType<typeof setTimeout> | null = null
291
292
  measurementsCache: VirtualItem[] = []
292
293
  private itemSizeCache: Record<Key, number> = {}
293
294
  private pendingMeasuredCacheIndexes: number[] = []
@@ -296,7 +297,6 @@ export class Virtualizer<
296
297
  scrollDirection: ScrollDirection | null = null
297
298
  private scrollAdjustments: number = 0
298
299
  private measureElementCache: Record<Key, TItemElement> = {}
299
- private pendingScrollToIndexCallback: (() => void) | null = null
300
300
  private getResizeObserver = (() => {
301
301
  let _ro: ResizeObserver | null = null
302
302
 
@@ -380,8 +380,6 @@ export class Virtualizer<
380
380
  }
381
381
 
382
382
  _willUpdate = () => {
383
- this.pendingScrollToIndexCallback?.()
384
-
385
383
  const scrollElement = this.options.getScrollElement()
386
384
 
387
385
  if (this.scrollElement !== scrollElement) {
@@ -521,7 +519,7 @@ export class Virtualizer<
521
519
  return rangeExtractor({
522
520
  ...range,
523
521
  overscan,
524
- count: count,
522
+ count,
525
523
  })
526
524
  },
527
525
  {
@@ -579,11 +577,7 @@ export class Virtualizer<
579
577
  const delta = measuredItemSize - itemSize
580
578
 
581
579
  if (delta !== 0) {
582
- if (
583
- item.start < this.scrollOffset &&
584
- this.isScrolling &&
585
- this.scrollDirection === 'backward'
586
- ) {
580
+ if (item.start < this.scrollOffset) {
587
581
  if (process.env.NODE_ENV !== 'production' && this.options.debug) {
588
582
  console.info('correction', delta)
589
583
  }
@@ -632,13 +626,12 @@ export class Virtualizer<
632
626
  )
633
627
 
634
628
  getOffsetForAlignment = (toOffset: number, align: ScrollAlignment) => {
635
- const offset = this.scrollOffset
636
629
  const size = this.getSize()
637
630
 
638
631
  if (align === 'auto') {
639
- if (toOffset <= offset) {
632
+ if (toOffset <= this.scrollOffset) {
640
633
  align = 'start'
641
- } else if (toOffset >= offset + size) {
634
+ } else if (toOffset >= this.scrollOffset + size) {
642
635
  align = 'end'
643
636
  } else {
644
637
  align = 'start'
@@ -646,19 +639,40 @@ export class Virtualizer<
646
639
  }
647
640
 
648
641
  if (align === 'start') {
649
- return toOffset
642
+ toOffset = toOffset
650
643
  } else if (align === 'end') {
651
- return toOffset - size
644
+ toOffset = toOffset - size
652
645
  } else if (align === 'center') {
653
- return toOffset - size / 2
646
+ toOffset = toOffset - size / 2
654
647
  }
655
- return toOffset
648
+
649
+ const scrollSizeProp = this.options.horizontal
650
+ ? 'scrollWidth'
651
+ : 'scrollHeight'
652
+ const scrollSize = this.scrollElement
653
+ ? 'document' in this.scrollElement
654
+ ? this.scrollElement.document.documentElement[scrollSizeProp]
655
+ : this.scrollElement[scrollSizeProp]
656
+ : 0
657
+
658
+ const maxOffset = scrollSize - this.getSize()
659
+
660
+ return Math.max(Math.min(maxOffset, toOffset), 0)
656
661
  }
657
662
 
658
663
  scrollToOffset = (
659
664
  toOffset: number,
660
665
  { align = 'start', behavior }: ScrollToOffsetOptions = {},
661
666
  ) => {
667
+ const isDynamic = Object.keys(this.measureElementCache).length > 0
668
+
669
+ if (isDynamic && behavior === 'smooth') {
670
+ console.warn(
671
+ 'The `smooth` scroll behavior is not supported with dynamic size.',
672
+ )
673
+ return
674
+ }
675
+
662
676
  const options = {
663
677
  adjustments: undefined,
664
678
  behavior,
@@ -671,25 +685,43 @@ export class Virtualizer<
671
685
  index: number,
672
686
  { align = 'auto', behavior }: ScrollToIndexOptions = {},
673
687
  ) => {
674
- this.pendingScrollToIndexCallback = null
688
+ if (this.scrollToIndexTimeoutId !== null) {
689
+ clearTimeout(this.scrollToIndexTimeoutId)
690
+ this.scrollToIndexTimeoutId = null
691
+ }
675
692
 
676
- const offset = this.scrollOffset
677
- const size = this.getSize()
678
- const { count } = this.options
693
+ const isDynamic = Object.keys(this.measureElementCache).length > 0
694
+
695
+ if (isDynamic && behavior === 'smooth') {
696
+ console.warn(
697
+ 'The `smooth` scroll behavior is not supported with dynamic size.',
698
+ )
699
+ return
700
+ }
679
701
 
680
- const measurements = this.getMeasurements()
681
- const measurement = measurements[Math.max(0, Math.min(index, count - 1))]
702
+ const getMeasurement = () => {
703
+ const measurements = this.getMeasurements()
704
+ const measurement =
705
+ measurements[Math.max(0, Math.min(index, this.options.count - 1))]
682
706
 
683
- if (!measurement) {
684
- throw new Error(`VirtualItem not found for index = ${index}`)
707
+ if (!measurement) {
708
+ throw new Error(`VirtualItem not found for index = ${index}`)
709
+ }
710
+
711
+ return measurement
685
712
  }
686
713
 
714
+ const measurement = getMeasurement()
715
+
687
716
  if (align === 'auto') {
688
- if (measurement.end >= offset + size - this.options.scrollPaddingEnd) {
717
+ if (
718
+ measurement.end >=
719
+ this.scrollOffset + this.getSize() - this.options.scrollPaddingEnd
720
+ ) {
689
721
  align = 'end'
690
722
  } else if (
691
723
  measurement.start <=
692
- offset + this.options.scrollPaddingStart
724
+ this.scrollOffset + this.options.scrollPaddingStart
693
725
  ) {
694
726
  align = 'start'
695
727
  } else {
@@ -708,29 +740,47 @@ export class Virtualizer<
708
740
 
709
741
  const toOffset = getOffsetForIndexAndAlignment(measurement)
710
742
 
711
- if (toOffset === offset) {
712
- return
713
- }
714
-
715
743
  const options = {
716
744
  adjustments: undefined,
717
745
  behavior,
718
746
  }
719
747
  this._scrollToOffset(toOffset, options)
720
748
 
721
- const isDynamic = Object.keys(this.measureElementCache).length > 0
749
+ const approxEqual = (a: number, b: number) => Math.abs(a - b) < 1
722
750
 
723
751
  if (isDynamic) {
724
- this.pendingScrollToIndexCallback = () => {
725
- this.scrollToIndex(index, { align, behavior })
726
- }
752
+ this.scrollToIndexTimeoutId = setTimeout(() => {
753
+ this.scrollToIndexTimeoutId = null
754
+
755
+ const elementInDOM =
756
+ !!this.measureElementCache[this.options.getItemKey(index)]
757
+
758
+ if (elementInDOM) {
759
+ const toOffset = getOffsetForIndexAndAlignment(getMeasurement())
760
+
761
+ if (!approxEqual(toOffset, this.scrollOffset)) {
762
+ this.scrollToIndex(index, { align, behavior })
763
+ }
764
+ } else {
765
+ this.scrollToIndex(index, { align, behavior })
766
+ }
767
+ })
727
768
  }
728
769
  }
729
770
 
730
- scrollBy = (adjustments: number, options?: { behavior: ScrollBehavior }) => {
731
- this._scrollToOffset(this.scrollOffset, {
732
- adjustments,
733
- behavior: options?.behavior,
771
+ scrollBy = (delta: number, { behavior }: ScrollToOffsetOptions = {}) => {
772
+ const isDynamic = Object.keys(this.measureElementCache).length > 0
773
+
774
+ if (isDynamic && behavior === 'smooth') {
775
+ console.warn(
776
+ 'The `smooth` scroll behavior is not supported with dynamic size.',
777
+ )
778
+ return
779
+ }
780
+
781
+ this._scrollToOffset(this.scrollOffset + delta, {
782
+ adjustments: undefined,
783
+ behavior,
734
784
  })
735
785
  }
736
786