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

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) {
@@ -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
  }
@@ -659,6 +653,15 @@ export class Virtualizer<
659
653
  toOffset: number,
660
654
  { align = 'start', behavior }: ScrollToOffsetOptions = {},
661
655
  ) => {
656
+ const isDynamic = Object.keys(this.measureElementCache).length > 0
657
+
658
+ if (isDynamic && behavior === 'smooth') {
659
+ console.warn(
660
+ 'The `smooth` scroll behavior is not supported with dynamic size.',
661
+ )
662
+ return
663
+ }
664
+
662
665
  const options = {
663
666
  adjustments: undefined,
664
667
  behavior,
@@ -671,25 +674,43 @@ export class Virtualizer<
671
674
  index: number,
672
675
  { align = 'auto', behavior }: ScrollToIndexOptions = {},
673
676
  ) => {
674
- this.pendingScrollToIndexCallback = null
677
+ if (this.scrollToIndexTimeoutId !== null) {
678
+ clearTimeout(this.scrollToIndexTimeoutId)
679
+ this.scrollToIndexTimeoutId = null
680
+ }
675
681
 
676
- const offset = this.scrollOffset
677
- const size = this.getSize()
678
- const { count } = this.options
682
+ const isDynamic = Object.keys(this.measureElementCache).length > 0
683
+
684
+ if (isDynamic && behavior === 'smooth') {
685
+ console.warn(
686
+ 'The `smooth` scroll behavior is not supported with dynamic size.',
687
+ )
688
+ return
689
+ }
690
+
691
+ const getMeasurement = () => {
692
+ const measurements = this.getMeasurements()
693
+ const measurement =
694
+ measurements[Math.max(0, Math.min(index, this.options.count - 1))]
679
695
 
680
- const measurements = this.getMeasurements()
681
- const measurement = measurements[Math.max(0, Math.min(index, count - 1))]
696
+ if (!measurement) {
697
+ throw new Error(`VirtualItem not found for index = ${index}`)
698
+ }
682
699
 
683
- if (!measurement) {
684
- throw new Error(`VirtualItem not found for index = ${index}`)
700
+ return measurement
685
701
  }
686
702
 
703
+ const measurement = getMeasurement()
704
+
687
705
  if (align === 'auto') {
688
- if (measurement.end >= offset + size - this.options.scrollPaddingEnd) {
706
+ if (
707
+ measurement.end >=
708
+ this.scrollOffset + this.getSize() - this.options.scrollPaddingEnd
709
+ ) {
689
710
  align = 'end'
690
711
  } else if (
691
712
  measurement.start <=
692
- offset + this.options.scrollPaddingStart
713
+ this.scrollOffset + this.options.scrollPaddingStart
693
714
  ) {
694
715
  align = 'start'
695
716
  } else {
@@ -703,34 +724,61 @@ export class Virtualizer<
703
724
  ? measurement.end + this.options.scrollPaddingEnd
704
725
  : measurement.start - this.options.scrollPaddingStart
705
726
 
706
- return this.getOffsetForAlignment(toOffset, align)
707
- }
727
+ const sizeProp = this.options.horizontal ? 'scrollWidth' : 'scrollHeight'
728
+ const scrollSize = this.scrollElement
729
+ ? 'document' in this.scrollElement
730
+ ? this.scrollElement.document.documentElement[sizeProp]
731
+ : this.scrollElement[sizeProp]
732
+ : 0
708
733
 
709
- const toOffset = getOffsetForIndexAndAlignment(measurement)
734
+ const maxOffset = scrollSize - this.getSize()
710
735
 
711
- if (toOffset === offset) {
712
- return
736
+ return Math.min(maxOffset, this.getOffsetForAlignment(toOffset, align))
713
737
  }
714
738
 
739
+ const toOffset = getOffsetForIndexAndAlignment(measurement)
740
+
715
741
  const options = {
716
742
  adjustments: undefined,
717
743
  behavior,
718
744
  }
719
745
  this._scrollToOffset(toOffset, options)
720
746
 
721
- const isDynamic = Object.keys(this.measureElementCache).length > 0
747
+ const approxEqual = (a: number, b: number) => Math.abs(a - b) < 1
722
748
 
723
749
  if (isDynamic) {
724
- this.pendingScrollToIndexCallback = () => {
725
- this.scrollToIndex(index, { align, behavior })
726
- }
750
+ this.scrollToIndexTimeoutId = setTimeout(() => {
751
+ this.scrollToIndexTimeoutId = null
752
+
753
+ const elementInDOM =
754
+ !!this.measureElementCache[this.options.getItemKey(index)]
755
+
756
+ if (elementInDOM) {
757
+ const toOffset = getOffsetForIndexAndAlignment(getMeasurement())
758
+
759
+ if (!approxEqual(toOffset, this.scrollOffset)) {
760
+ this.scrollToIndex(index, { align, behavior })
761
+ }
762
+ } else {
763
+ this.scrollToIndex(index, { align, behavior })
764
+ }
765
+ })
727
766
  }
728
767
  }
729
768
 
730
- scrollBy = (adjustments: number, options?: { behavior: ScrollBehavior }) => {
731
- this._scrollToOffset(this.scrollOffset, {
732
- adjustments,
733
- behavior: options?.behavior,
769
+ scrollBy = (delta: number, { behavior }: ScrollToOffsetOptions = {}) => {
770
+ const isDynamic = Object.keys(this.measureElementCache).length > 0
771
+
772
+ if (isDynamic && behavior === 'smooth') {
773
+ console.warn(
774
+ 'The `smooth` scroll behavior is not supported with dynamic size.',
775
+ )
776
+ return
777
+ }
778
+
779
+ this._scrollToOffset(this.scrollOffset + delta, {
780
+ adjustments: undefined,
781
+ behavior,
734
782
  })
735
783
  }
736
784