@@ -3,6 +3,10 @@ import * as React from 'react';
33import raf from 'rc-util/lib/raf' ;
44import type { GetKey } from '../interface' ;
55import type CacheMap from '../utils/CacheMap' ;
6+ import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect' ;
7+ import { warning } from 'rc-util' ;
8+
9+ const MAX_TIMES = 10 ;
610
711export type ScrollAlign = 'top' | 'bottom' | 'auto' ;
812
@@ -35,6 +39,118 @@ export default function useScrollTo<T>(
3539) : ( arg : number | ScrollTarget ) => void {
3640 const scrollRef = React . useRef < number > ( ) ;
3741
42+ const [ syncState , setSyncState ] = React . useState < {
43+ times : number ;
44+ index : number ;
45+ offset : number ;
46+ originAlign : ScrollAlign ;
47+ targetAlign ?: 'top' | 'bottom' ;
48+ } > ( null ) ;
49+
50+ // ========================== Sync Scroll ==========================
51+ useLayoutEffect ( ( ) => {
52+ if ( syncState && syncState . times < MAX_TIMES ) {
53+ // Never reach
54+ if ( ! containerRef . current ) {
55+ setSyncState ( ( ori ) => ( { ...ori } ) ) ;
56+ return ;
57+ }
58+
59+ collectHeight ( ) ;
60+
61+ const { targetAlign, originAlign, index, offset } = syncState ;
62+
63+ const height = containerRef . current . clientHeight ;
64+ let needCollectHeight = false ;
65+ let newTargetAlign : 'top' | 'bottom' | null = targetAlign ;
66+
67+ // Go to next frame if height not exist
68+ if ( height ) {
69+ const mergedAlign = targetAlign || originAlign ;
70+
71+ // Get top & bottom
72+ let stackTop = 0 ;
73+ let itemTop = 0 ;
74+ let itemBottom = 0 ;
75+
76+ const maxLen = Math . min ( data . length , index ) ;
77+
78+ for ( let i = 0 ; i <= maxLen ; i += 1 ) {
79+ const key = getKey ( data [ i ] ) ;
80+ itemTop = stackTop ;
81+ const cacheHeight = heights . get ( key ) ;
82+ itemBottom = itemTop + ( cacheHeight === undefined ? itemHeight : cacheHeight ) ;
83+
84+ stackTop = itemBottom ;
85+ }
86+
87+ // Check if need sync height (visible range has item not record height)
88+ let leftHeight = mergedAlign === 'top' ? offset : height - offset ;
89+ for ( let i = maxLen ; i >= 0 ; i -= 1 ) {
90+ const key = getKey ( data [ i ] ) ;
91+ const cacheHeight = heights . get ( key ) ;
92+
93+ if ( cacheHeight === undefined ) {
94+ needCollectHeight = true ;
95+ break ;
96+ }
97+
98+ leftHeight -= cacheHeight ;
99+ if ( leftHeight <= 0 ) {
100+ break ;
101+ }
102+ }
103+
104+ // Scroll to
105+ let targetTop : number | null = null ;
106+ let inView = false ;
107+
108+ switch ( mergedAlign ) {
109+ case 'top' :
110+ targetTop = itemTop - offset ;
111+ break ;
112+ case 'bottom' :
113+ targetTop = itemBottom - height + offset ;
114+ break ;
115+
116+ default : {
117+ const { scrollTop } = containerRef . current ;
118+ const scrollBottom = scrollTop + height ;
119+ if ( itemTop < scrollTop ) {
120+ newTargetAlign = 'top' ;
121+ } else if ( itemBottom > scrollBottom ) {
122+ newTargetAlign = 'bottom' ;
123+ } else {
124+ // No need to collect since already in view
125+ inView = true ;
126+ }
127+ }
128+ }
129+
130+ if ( targetTop !== null ) {
131+ syncScrollTop ( targetTop ) ;
132+ } else if ( ! inView ) {
133+ needCollectHeight = true ;
134+ }
135+ }
136+
137+ // Trigger next effect
138+ if ( needCollectHeight ) {
139+ setSyncState ( ( ori ) => ( {
140+ ...ori ,
141+ times : ori . times + 1 ,
142+ targetAlign : newTargetAlign ,
143+ } ) ) ;
144+ }
145+ } else if ( process . env . NODE_ENV !== 'production' && syncState ?. times === MAX_TIMES ) {
146+ warning (
147+ false ,
148+ 'Seems `scrollTo` with `rc-virtual-list` reach toe max limitation. Please fire issue for us. Thanks.' ,
149+ ) ;
150+ }
151+ } , [ syncState , containerRef . current ] ) ;
152+
153+ // =========================== Scroll To ===========================
38154 return ( arg ) => {
39155 // When not argument provided, we think dev may want to show the scrollbar
40156 if ( arg === null || arg === undefined ) {
@@ -59,75 +175,12 @@ export default function useScrollTo<T>(
59175
60176 const { offset = 0 } = arg ;
61177
62- // We will retry 3 times in case dynamic height shaking
63- const syncScroll = ( times : number , targetAlign ?: 'top' | 'bottom' ) => {
64- if ( times < 0 || ! containerRef . current ) return ;
65-
66- const height = containerRef . current . clientHeight ;
67- let needCollectHeight = false ;
68- let newTargetAlign : 'top' | 'bottom' | null = targetAlign ;
69-
70- // Go to next frame if height not exist
71- if ( height ) {
72- const mergedAlign = targetAlign || align ;
73-
74- // Get top & bottom
75- let stackTop = 0 ;
76- let itemTop = 0 ;
77- let itemBottom = 0 ;
78-
79- const maxLen = Math . min ( data . length , index ) ;
80-
81- for ( let i = 0 ; i <= maxLen ; i += 1 ) {
82- const key = getKey ( data [ i ] ) ;
83- itemTop = stackTop ;
84- const cacheHeight = heights . get ( key ) ;
85- itemBottom = itemTop + ( cacheHeight === undefined ? itemHeight : cacheHeight ) ;
86-
87- stackTop = itemBottom ;
88-
89- if ( i === index && cacheHeight === undefined ) {
90- needCollectHeight = true ;
91- }
92- }
93-
94- // Scroll to
95- let targetTop : number | null = null ;
96-
97- switch ( mergedAlign ) {
98- case 'top' :
99- targetTop = itemTop - offset ;
100- break ;
101- case 'bottom' :
102- targetTop = itemBottom - height + offset ;
103- break ;
104-
105- default : {
106- const { scrollTop } = containerRef . current ;
107- const scrollBottom = scrollTop + height ;
108- if ( itemTop < scrollTop ) {
109- newTargetAlign = 'top' ;
110- } else if ( itemBottom > scrollBottom ) {
111- newTargetAlign = 'bottom' ;
112- }
113- }
114- }
115-
116- if ( targetTop !== null && targetTop !== containerRef . current . scrollTop ) {
117- syncScrollTop ( targetTop ) ;
118- }
119- }
120-
121- // We will retry since element may not sync height as it described
122- scrollRef . current = raf ( ( ) => {
123- if ( needCollectHeight ) {
124- collectHeight ( ) ;
125- }
126- syncScroll ( times - 1 , newTargetAlign ) ;
127- } , 2 ) ; // Delay 2 to wait for List collect heights
128- } ;
129-
130- syncScroll ( 3 ) ;
178+ setSyncState ( {
179+ times : 0 ,
180+ index,
181+ offset,
182+ originAlign : align ,
183+ } ) ;
131184 }
132185 } ;
133186}
0 commit comments