@@ -5,8 +5,10 @@ import {
5
5
getScrollPercentage ,
6
6
getNodeHeight ,
7
7
getRangeIndex ,
8
- getItemTop ,
8
+ getItemAbsoluteTop ,
9
9
GHOST_ITEM_KEY ,
10
+ getItemRelativeTop ,
11
+ getCompareItemRelativeTop ,
10
12
} from './util' ;
11
13
12
14
type RenderFunc < T > = ( item : T ) => React . ReactNode ;
@@ -115,7 +117,7 @@ class List<T> extends React.Component<ListProps<T>, ListState> {
115
117
}
116
118
117
119
// Calculate top visible item top offset
118
- const locatedItemTop = getItemTop ( {
120
+ const locatedItemTop = getItemAbsoluteTop ( {
119
121
itemIndex,
120
122
itemOffsetPtg,
121
123
itemElementHeights : this . itemElementHeights ,
@@ -133,65 +135,73 @@ class List<T> extends React.Component<ListProps<T>, ListState> {
133
135
this . setState ( { status : 'MEASURE_DONE' , startItemTop } ) ;
134
136
}
135
137
136
- // TODO: If is add node
137
- // Re-calculate the scroll position align with the current visible item position
138
+ /**
139
+ * Re-calculate the item position since `dataSource` length changed.
140
+ * [IMPORTANT] We use relative position calculate here.
141
+ */
138
142
if ( prevProps . dataSource . length !== dataSource . length && height ) {
139
- /**
140
- * When an item removed,
141
- * we should know the item (called as compare item) before the removed item position.
142
- * After loop re-calculation, we need keep the compare item position not change.
143
- */
144
- // Find removed item key
143
+ const {
144
+ itemIndex : originItemIndex ,
145
+ itemOffsetPtg : originItemOffsetPtg ,
146
+ startIndex : originStartIndex ,
147
+ endIndex : originEndIndex ,
148
+ } = this . state ;
149
+
150
+ // 1. Get origin located item top
151
+ const originLocatedItemRelativeTop = getItemRelativeTop ( {
152
+ itemIndex : originItemIndex ,
153
+ itemOffsetPtg : originItemOffsetPtg ,
154
+ itemElementHeights : this . itemElementHeights ,
155
+ scrollPtg : getElementScrollPercentage ( this . listRef . current ) ,
156
+ clientHeight : this . listRef . current . clientHeight ,
157
+ getItemKey : ( index : number ) => this . getItemKey ( index , prevProps ) ,
158
+ } ) ;
159
+
160
+ console . log (
161
+ '1. Origin Located:' ,
162
+ originItemIndex ,
163
+ originItemOffsetPtg ,
164
+ this . getItemKey ( originItemIndex , prevProps ) ,
165
+ originLocatedItemRelativeTop ,
166
+ ) ;
167
+
168
+ // 2. Find the compare item
145
169
const removedItemIndex : number = prevProps . dataSource . findIndex ( ( _ , index ) => {
146
170
const key = this . getItemKey ( index , prevProps ) ;
147
171
return dataSource . every ( ( __ , nextIndex ) => key !== this . getItemKey ( nextIndex ) ) ;
148
172
} ) ;
149
-
150
- // We use the item before removed item as base compare position to enhance the accuracy
151
- const { startIndex : originStartIndex , itemIndex : originItemIndex } = this . state ;
152
-
153
173
let originCompareItemIndex = removedItemIndex - 1 ;
154
- if ( originCompareItemIndex >= originItemIndex ) originCompareItemIndex = originItemIndex ;
155
-
156
- // Find compare item key & offset top
157
- let compareItemIndex : number ;
158
- let compareItemKey : string ;
159
- let originCompareItemTop = this . state . startItemTop ;
174
+ // Use next one since there are not more item before removed
160
175
if ( originCompareItemIndex < 0 ) {
161
- // If remove item is the first one, we have compare next one
162
176
originCompareItemIndex = 0 ;
163
- compareItemIndex = 0 ;
164
- compareItemKey = this . getItemKey ( compareItemIndex ) ;
165
- } else {
166
- // If exist compare item
167
- compareItemKey = this . getItemKey ( originCompareItemIndex , prevProps ) ;
168
-
169
- for ( let index = originStartIndex ; index <= originItemIndex ; index += 1 ) {
170
- const key = this . getItemKey ( index , prevProps ) ;
171
- if ( key === compareItemKey ) {
172
- break ;
173
- }
174
-
175
- originCompareItemTop += this . itemElementHeights [ key ] || 0 ;
176
- }
177
-
178
- // Find current compare item index
179
- compareItemIndex = dataSource . findIndex (
180
- ( _ , index ) => this . getItemKey ( index ) === compareItemKey ,
181
- ) ;
182
177
}
178
+ const compareItemKey = this . getItemKey ( originCompareItemIndex , prevProps ) ;
179
+ console . log ( '2. Compare Item:' , compareItemKey ) ;
180
+
181
+ // 3. Find the compare item top
182
+ const originCompareItemTop = getCompareItemRelativeTop ( {
183
+ locatedItemRelativeTop : originLocatedItemRelativeTop ,
184
+ locatedItemIndex : originItemIndex ,
185
+ compareItemIndex : originCompareItemIndex ,
186
+ startIndex : originStartIndex ,
187
+ endIndex : originEndIndex ,
188
+ getItemKey : ( index : number ) => this . getItemKey ( index , prevProps ) ,
189
+ itemElementHeights : this . itemElementHeights ,
190
+ } ) ;
183
191
184
- // Loop to generate compared item top and find best one
185
- const { scrollHeight, clientHeight } = this . listRef . current ;
186
- const maxScrollTop = scrollHeight - clientHeight ;
192
+ console . log ( '3. Compare Item Top:' , originCompareItemTop ) ;
187
193
194
+ // 4. Find the best match compare item top
195
+ let bestSimilarity = Number . MAX_VALUE ;
188
196
let bestScrollTop : number = null ;
189
- let bestSimilarity : number = Number . MAX_VALUE ;
190
- let bestItemIndex = 0 ;
191
- let bestItemOffsetPtg = 0 ;
192
- let bestStartIndex = 0 ;
193
- let bestEndIndex = 0 ;
197
+ let bestItemIndex : number = null ;
198
+ let bestItemOffsetPtg : number = null ;
199
+ let bestStartIndex : number = null ;
200
+ let bestEndIndex : number = null ;
194
201
202
+ const scrollHeight = dataSource . length * itemHeight ;
203
+ const { clientHeight } = this . listRef . current ;
204
+ const maxScrollTop = scrollHeight - clientHeight ;
195
205
for ( let scrollTop = 0 ; scrollTop < maxScrollTop ; scrollTop += 1 ) {
196
206
const scrollPtg = getScrollPercentage ( { scrollTop, scrollHeight, clientHeight } ) ;
197
207
const visibleCount = Math . ceil ( height / itemHeight ) ;
@@ -202,66 +212,59 @@ class List<T> extends React.Component<ListProps<T>, ListState> {
202
212
visibleCount ,
203
213
) ;
204
214
205
- /**
206
- * No need to check if compare item out of the index
207
- * to save performance.
208
- */
209
- if ( startIndex <= compareItemIndex && compareItemIndex <= endIndex ) {
210
- let locatedItemTop = getItemTop ( {
215
+ // No need to check if compare item out of the index to save performance
216
+ if ( startIndex <= originCompareItemIndex && originCompareItemIndex <= endIndex ) {
217
+ // 4.1 Get measure located item relative top
218
+ const locatedItemRelativeTop = getItemRelativeTop ( {
211
219
itemIndex,
212
220
itemOffsetPtg,
213
221
itemElementHeights : this . itemElementHeights ,
214
- scrollTop : this . listRef . current . scrollTop ,
215
222
scrollPtg,
216
- clientHeight : this . listRef . current . clientHeight ,
223
+ clientHeight,
217
224
getItemKey : this . getItemKey ,
218
225
} ) ;
219
226
220
- let newCompareItemTop : number = null ;
221
- if ( compareItemIndex <= itemIndex ) {
222
- // Measure if compare item is before located item
223
- for ( let index = itemIndex ; index >= startIndex ; index -= 1 ) {
224
- const key = this . getItemKey ( index ) ;
225
- if ( key === compareItemKey ) {
226
- newCompareItemTop = locatedItemTop ;
227
- break ;
228
- }
229
-
230
- if ( index <= 0 ) {
231
- break ;
232
- }
233
-
234
- const prevItemKey = this . getItemKey ( index - 1 ) ;
235
- locatedItemTop -= this . itemElementHeights [ prevItemKey ] || 0 ;
236
- }
237
- } else {
238
- // Measure if compare item is after located item
239
- for ( let index = itemIndex ; index <= endIndex ; index += 1 ) {
240
- const key = this . getItemKey ( index ) ;
241
- locatedItemTop += this . itemElementHeights [ key ] || 0 ;
242
- if ( key === compareItemKey ) {
243
- newCompareItemTop = locatedItemTop ;
244
- break ;
245
- }
246
- }
247
- }
227
+ const compareItemTop = getCompareItemRelativeTop ( {
228
+ locatedItemRelativeTop,
229
+ locatedItemIndex : itemIndex ,
230
+ compareItemIndex : originCompareItemIndex , // Same as origin index
231
+ startIndex,
232
+ endIndex,
233
+ getItemKey : this . getItemKey ,
234
+ itemElementHeights : this . itemElementHeights ,
235
+ } ) ;
236
+
237
+ // console.log('4.1 ScrollTop:', scrollTop, 'CompareTop:', compareItemTop);
248
238
249
- if ( newCompareItemTop !== null ) {
250
- const similarity = Math . abs ( newCompareItemTop - originCompareItemTop ) ;
251
- if ( similarity < bestSimilarity ) {
252
- bestScrollTop = scrollTop ;
253
- bestSimilarity = similarity ;
254
- bestItemIndex = itemIndex ;
255
- bestItemOffsetPtg = itemOffsetPtg ;
256
- bestStartIndex = startIndex ;
257
- bestEndIndex = endIndex ;
258
- }
239
+ // 4.2 Find best match compare item top
240
+ const similarity = Math . abs ( compareItemTop - originCompareItemTop ) ;
241
+ if ( similarity < bestSimilarity ) {
242
+ console . log ( '4.2 Winner:' , scrollTop , compareItemTop . toFixed ( 2 ) ) ;
243
+ console . log ( ' -> ' , similarity . toFixed ( 2 ) , bestSimilarity . toFixed ( 2 ) ) ;
244
+
245
+ bestSimilarity = similarity ;
246
+ bestScrollTop = scrollTop ;
247
+ bestItemIndex = itemIndex ;
248
+ bestItemOffsetPtg = itemOffsetPtg ;
249
+ bestStartIndex = startIndex ;
250
+ bestEndIndex = endIndex ;
259
251
}
260
252
}
261
253
}
262
254
263
- // Update `scrollTop` to new calculated position
255
+ // 5. Re-scroll if has best scroll match
264
256
if ( bestScrollTop !== null ) {
257
+ console . log (
258
+ '5. Find best match:' ,
259
+ bestScrollTop ,
260
+ 'Located:' ,
261
+ bestItemIndex ,
262
+ bestItemOffsetPtg ,
263
+ 'Range:' ,
264
+ bestStartIndex ,
265
+ bestEndIndex ,
266
+ ) ;
267
+
265
268
this . lockScroll = true ;
266
269
this . listRef . current . scrollTop = bestScrollTop ;
267
270
0 commit comments