@@ -28,7 +28,8 @@ export const defaultProps = {
2828}
2929
3030export interface VirtualListHandle {
31- scrollToIndex ( index : number ) : void
31+ scrollToIndex ( index : number , block ?: 'start' | 'end' | 'center' | 'nearest' ) : void
32+ forceUpdate ( ) : void
3233}
3334
3435export const VirtualList = forwardRef ( function < ITEM > (
@@ -41,9 +42,11 @@ export const VirtualList = forwardRef(function <ITEM>(
4142 const list = useRef < HTMLDivElement > ( null ) ;
4243 const listInner = useRef < HTMLDivElement > ( null ) ;
4344 const prevScrollTop = useRef ( 0 ) ;
45+ const scrollToIndexRef = useRef < { index : number , block : string } > ( ) ;
4446 const [ scrollTop , setscrollTop ] = useState ( 0 ) ;
4547 const [ listSize , setlistSize ] = useState ( props . listSize ! ) ;
46-
48+ const [ forceRerender , setforceRerender ] = useState ( [ ] ) ; // change value to force rerender
49+ const ignoreScrollOnce = useRef ( false ) ;
4750 //
4851 const totalSpace = itemSize * count
4952 let topSpace = scrollTop - buffer
@@ -64,12 +67,12 @@ export const VirtualList = forwardRef(function <ITEM>(
6467 } else {
6568 endIndex = count - Math . floor ( bottomSpace / itemSize )
6669 }
67- const mainVisibleIndexes = Array . from ( { length : endIndex - startIndex } , ( _ , index ) => index + startIndex ) ;
68- let visibleIndexes = mainVisibleIndexes . concat ( props . persistentIndices || [ ] )
70+ const mainVisibleIndices = Array . from ( { length : endIndex - startIndex } , ( _ , index ) => index + startIndex ) ;
71+ let visibleIndices = mainVisibleIndices . concat ( props . persistentIndices || [ ] )
6972 if ( props . persistentIndices ?. length ) {
70- visibleIndexes = [ ...new Set ( visibleIndexes ) ] . sort ( ( a , b ) => a - b )
73+ visibleIndices = [ ...new Set ( visibleIndices ) ] . sort ( ( a , b ) => a - b )
7174 }
72- const visible = visibleIndexes . map ( i => props . items [ i ] )
75+ const visible = visibleIndices . map ( i => props . items [ i ] )
7376
7477 //
7578 const listInnerStyle : any = { paddingTop : `${ topSpace } px` , boxSizing : 'border-box' }
@@ -78,39 +81,72 @@ export const VirtualList = forwardRef(function <ITEM>(
7881 } else {
7982 listInnerStyle [ 'height' ] = `${ totalSpace } px`
8083 }
84+
8185 useLayoutEffect ( ( ) => {
8286 setlistSize ( list . current ! . clientHeight )
87+ // get avg item size
8388 if ( props . itemSize == null ) {
84- // get avg item size
8589 let count = 0
8690 let totalHeight = 0
91+ const persistentIndices = new Set ( props . persistentIndices || [ ] )
92+ let i = - 1
8793 for ( const el of listInner . current ! . children ) {
94+ i ++
95+ if ( persistentIndices . has ( visibleIndices [ i ] ) ) {
96+ continue
97+ }
8898 const style = getComputedStyle ( el )
8999 totalHeight += ( el as HTMLElement ) . offsetHeight + parseFloat ( style . marginTop ) + parseFloat ( style . marginBottom )
90100 count ++
91101 }
92102 setitemSize ( totalHeight / count )
93103 }
94- } , [ props . itemSize , props . items ] ) ;
104+ } , [ props . itemSize , props . items , forceRerender ] ) ;
95105 //
96106 const handleScroll = ( ) => {
107+ if ( ignoreScrollOnce . current ) {
108+ ignoreScrollOnce . current = false
109+ return
110+ }
97111 setlistSize ( list . current ! . clientHeight )
98112 const scrollTop2 = list . current ! . scrollTop
99113 if ( Math . abs ( prevScrollTop . current - scrollTop2 ) > itemSize ) {
100114 setscrollTop ( scrollTop2 )
101115 prevScrollTop . current = scrollTop2
116+ } else if ( scrollToIndexRef . current ) {
117+ setforceRerender ( [ ] )
102118 }
103119 }
104120 //
105121 useImperativeHandle ( ref , ( ) => ( {
106- scrollToIndex : ( index : number ) => {
122+ scrollToIndex ( index , block = 'start' ) {
123+ scrollToIndexRef . current = {
124+ index,
125+ block
126+ }
107127 list . current ! . scrollTop = index * itemSize
108128 } ,
109- } ) , [ ] ) ;
129+ forceUpdate ( ) {
130+ setforceRerender ( [ ] )
131+ }
132+ } ) , [ itemSize ] ) ;
133+ useLayoutEffect ( ( ) => {
134+ if ( scrollToIndexRef . current ) {
135+ const { index, block } = scrollToIndexRef . current ;
136+ scrollToIndexRef . current = undefined
137+ const indexInVisible = visibleIndices . indexOf ( index )
138+ const el = listInner . current ! . children [ indexInVisible ] as HTMLElement
139+ if ( el ) {
140+ // @ts -ignore
141+ el . scrollIntoView ( { block } )
142+ ignoreScrollOnce . current = true
143+ }
144+ }
145+ } , [ visibleIndices ] )
110146 //
111147 return < div ref = { list } onScroll = { handleScroll } className = { props . className } style = { { overflow : 'auto' , ...props . style } } >
112148 < div ref = { listInner } style = { { display : 'flex' , flexDirection : 'column' , ...listInnerStyle } } >
113- { visible . map ( ( item , i ) => props . renderItem ( item , visibleIndexes [ i ] ) ) }
149+ { visible . map ( ( item , i ) => props . renderItem ( item , visibleIndices [ i ] ) ) }
114150 </ div >
115151 </ div >
116152} )
0 commit comments