@@ -3,6 +3,10 @@ import * as React from 'react';
3
3
import raf from 'rc-util/lib/raf' ;
4
4
import type { GetKey } from '../interface' ;
5
5
import 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 ;
6
10
7
11
export type ScrollAlign = 'top' | 'bottom' | 'auto' ;
8
12
@@ -35,6 +39,118 @@ export default function useScrollTo<T>(
35
39
) : ( arg : number | ScrollTarget ) => void {
36
40
const scrollRef = React . useRef < number > ( ) ;
37
41
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 ===========================
38
154
return ( arg ) => {
39
155
// When not argument provided, we think dev may want to show the scrollbar
40
156
if ( arg === null || arg === undefined ) {
@@ -59,75 +175,12 @@ export default function useScrollTo<T>(
59
175
60
176
const { offset = 0 } = arg ;
61
177
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
+ } ) ;
131
184
}
132
185
} ;
133
186
}
0 commit comments