@@ -288,6 +288,7 @@ export class Virtualizer<
288288 scrollElement : TScrollElement | null = null
289289 isScrolling : boolean = false
290290 private isScrollingTimeoutId : ReturnType < typeof setTimeout > | null = null
291+ private scrollToIndexTimeoutId : ReturnType < typeof setTimeout > | null = null
291292 measurementsCache : VirtualItem [ ] = [ ]
292293 private itemSizeCache : Record < Key , number > = { }
293294 private pendingMeasuredCacheIndexes : number [ ] = [ ]
@@ -296,7 +297,6 @@ export class Virtualizer<
296297 scrollDirection : ScrollDirection | null = null
297298 private scrollAdjustments : number = 0
298299 private measureElementCache : Record < Key , TItemElement > = { }
299- private pendingScrollToIndexCallback : ( ( ) => void ) | null = null
300300 private getResizeObserver = ( ( ) => {
301301 let _ro : ResizeObserver | null = null
302302
@@ -380,8 +380,6 @@ export class Virtualizer<
380380 }
381381
382382 _willUpdate = ( ) => {
383- this . pendingScrollToIndexCallback ?.( )
384-
385383 const scrollElement = this . options . getScrollElement ( )
386384
387385 if ( this . scrollElement !== scrollElement ) {
@@ -579,11 +577,7 @@ export class Virtualizer<
579577 const delta = measuredItemSize - itemSize
580578
581579 if ( delta !== 0 ) {
582- if (
583- item . start < this . scrollOffset &&
584- this . isScrolling &&
585- this . scrollDirection === 'backward'
586- ) {
580+ if ( item . start < this . scrollOffset ) {
587581 if ( process . env . NODE_ENV !== 'production' && this . options . debug ) {
588582 console . info ( 'correction' , delta )
589583 }
@@ -659,6 +653,15 @@ export class Virtualizer<
659653 toOffset : number ,
660654 { align = 'start' , behavior } : ScrollToOffsetOptions = { } ,
661655 ) => {
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+
662665 const options = {
663666 adjustments : undefined ,
664667 behavior,
@@ -671,25 +674,43 @@ export class Virtualizer<
671674 index : number ,
672675 { align = 'auto' , behavior } : ScrollToIndexOptions = { } ,
673676 ) => {
674- this . pendingScrollToIndexCallback = null
677+ if ( this . scrollToIndexTimeoutId !== null ) {
678+ clearTimeout ( this . scrollToIndexTimeoutId )
679+ this . scrollToIndexTimeoutId = null
680+ }
675681
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 ) ) ]
679695
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+ }
682699
683- if ( ! measurement ) {
684- throw new Error ( `VirtualItem not found for index = ${ index } ` )
700+ return measurement
685701 }
686702
703+ const measurement = getMeasurement ( )
704+
687705 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+ ) {
689710 align = 'end'
690711 } else if (
691712 measurement . start <=
692- offset + this . options . scrollPaddingStart
713+ this . scrollOffset + this . options . scrollPaddingStart
693714 ) {
694715 align = 'start'
695716 } else {
@@ -703,34 +724,61 @@ export class Virtualizer<
703724 ? measurement . end + this . options . scrollPaddingEnd
704725 : measurement . start - this . options . scrollPaddingStart
705726
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
708733
709- const toOffset = getOffsetForIndexAndAlignment ( measurement )
734+ const maxOffset = scrollSize - this . getSize ( )
710735
711- if ( toOffset === offset ) {
712- return
736+ return Math . min ( maxOffset , this . getOffsetForAlignment ( toOffset , align ) )
713737 }
714738
739+ const toOffset = getOffsetForIndexAndAlignment ( measurement )
740+
715741 const options = {
716742 adjustments : undefined ,
717743 behavior,
718744 }
719745 this . _scrollToOffset ( toOffset , options )
720746
721- const isDynamic = Object . keys ( this . measureElementCache ) . length > 0
747+ const approxEqual = ( a : number , b : number ) => Math . abs ( a - b ) < 1
722748
723749 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+ } )
727766 }
728767 }
729768
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,
734782 } )
735783 }
736784
0 commit comments