9
9
onBeforeUnmount ,
10
10
reactive ,
11
11
CSSProperties ,
12
+ watch ,
12
13
} from 'vue' ;
13
14
import { Key } from '../_util/type' ;
14
15
import Filler from './Filler' ;
@@ -53,10 +54,9 @@ function renderChildren<T>(
53
54
} ) ;
54
55
}
55
56
56
- export interface ListState < T = object > {
57
+ export interface ListState {
57
58
scrollTop : number ;
58
59
scrollMoving : boolean ;
59
- mergedData : T [ ] ;
60
60
}
61
61
62
62
const List = defineComponent ( {
@@ -97,7 +97,10 @@ const List = defineComponent({
97
97
const state = reactive < ListState > ( {
98
98
scrollTop : 0 ,
99
99
scrollMoving : false ,
100
- mergedData : computed ( ( ) => props . data || EMPTY_DATA ) as any ,
100
+ } ) ;
101
+
102
+ const mergedData = computed ( ( ) => {
103
+ return props . data || EMPTY_DATA ;
101
104
} ) ;
102
105
103
106
const componentRef = ref < HTMLDivElement > ( ) ;
@@ -108,7 +111,7 @@ const List = defineComponent({
108
111
if ( typeof props . itemKey === 'function' ) {
109
112
return props . itemKey ( item ) ;
110
113
}
111
- return item [ props . itemKey ] ;
114
+ return item ?. [ props . itemKey ] ;
112
115
} ;
113
116
114
117
const sharedConfig = {
@@ -135,83 +138,94 @@ const List = defineComponent({
135
138
// ================================ Height ================================
136
139
const [ setInstance , collectHeight , heights ] = useHeights ( getKey , null , null ) ;
137
140
138
- const calRes = ref ( ) ;
139
- watchEffect ( ( ) => {
140
- if ( ! useVirtual . value ) {
141
- calRes . value = {
142
- scrollHeight : undefined ,
143
- start : 0 ,
144
- end : state . mergedData . length - 1 ,
145
- offset : undefined ,
146
- } ;
147
- return ;
148
- }
149
-
150
- // Always use virtual scroll bar in avoid shaking
151
- if ( ! inVirtual . value ) {
152
- calRes . value = {
153
- scrollHeight : fillerInnerRef . value ?. offsetHeight || 0 ,
154
- start : 0 ,
155
- end : state . mergedData . length - 1 ,
156
- offset : undefined ,
157
- } ;
158
- return ;
159
- }
160
-
161
- let itemTop = 0 ;
162
- let startIndex : number | undefined ;
163
- let startOffset : number | undefined ;
164
- let endIndex : number | undefined ;
165
- const dataLen = state . mergedData . length ;
166
- for ( let i = 0 ; i < dataLen ; i += 1 ) {
167
- const item = state . mergedData [ i ] ;
168
- const key = getKey ( item ) ;
169
-
170
- const cacheHeight = heights [ key ] ;
171
- const currentItemBottom =
172
- itemTop + ( cacheHeight === undefined ? props . itemHeight ! : cacheHeight ) ;
173
-
174
- if ( currentItemBottom >= state . scrollTop && startIndex === undefined ) {
175
- startIndex = i ;
176
- startOffset = itemTop ;
141
+ const calRes = ref < {
142
+ scrollHeight ?: number ;
143
+ start ?: number ;
144
+ end ?: number ;
145
+ offset ?: number ;
146
+ } > ( { } ) ;
147
+ watch (
148
+ [ inVirtual , useVirtual , ( ) => state . scrollTop , mergedData , heights , ( ) => props . height ] ,
149
+ ( ) => {
150
+ if ( ! useVirtual . value ) {
151
+ calRes . value = {
152
+ scrollHeight : undefined ,
153
+ start : 0 ,
154
+ end : mergedData . value . length - 1 ,
155
+ offset : undefined ,
156
+ } ;
157
+ return ;
177
158
}
178
159
179
- // Check item bottom in the range. We will render additional one item for motion usage
180
- if ( currentItemBottom > state . scrollTop + props . height ! && endIndex === undefined ) {
181
- endIndex = i ;
160
+ // Always use virtual scroll bar in avoid shaking
161
+ if ( ! inVirtual . value ) {
162
+ calRes . value = {
163
+ scrollHeight : fillerInnerRef . value ?. offsetHeight || 0 ,
164
+ start : 0 ,
165
+ end : mergedData . value . length - 1 ,
166
+ offset : undefined ,
167
+ } ;
168
+ return ;
182
169
}
183
170
184
- itemTop = currentItemBottom ;
185
- }
171
+ let itemTop = 0 ;
172
+ let startIndex : number | undefined ;
173
+ let startOffset : number | undefined ;
174
+ let endIndex : number | undefined ;
175
+ const dataLen = mergedData . value . length ;
176
+ const data = mergedData . value ;
177
+ for ( let i = 0 ; i < dataLen ; i += 1 ) {
178
+ const item = data [ i ] ;
179
+ const key = getKey ( item ) ;
180
+
181
+ const cacheHeight = heights [ key ] ;
182
+ const currentItemBottom =
183
+ itemTop + ( cacheHeight === undefined ? props . itemHeight ! : cacheHeight ) ;
184
+
185
+ if ( currentItemBottom >= state . scrollTop && startIndex === undefined ) {
186
+ startIndex = i ;
187
+ startOffset = itemTop ;
188
+ }
186
189
187
- // Fallback to normal if not match. This code should never reach
188
- /* istanbul ignore next */
189
- if ( startIndex === undefined ) {
190
- startIndex = 0 ;
191
- startOffset = 0 ;
192
- }
193
- if ( endIndex === undefined ) {
194
- endIndex = state . mergedData . length - 1 ;
195
- }
190
+ // Check item bottom in the range. We will render additional one item for motion usage
191
+ if ( currentItemBottom > state . scrollTop + props . height ! && endIndex === undefined ) {
192
+ endIndex = i ;
193
+ }
196
194
197
- // Give cache to improve scroll experience
198
- endIndex = Math . min ( endIndex + 1 , state . mergedData . length ) ;
199
- calRes . value = {
200
- scrollHeight : itemTop ,
201
- start : startIndex ,
202
- end : endIndex ,
203
- offset : startOffset ,
204
- } ;
205
- } ) ;
195
+ itemTop = currentItemBottom ;
196
+ }
197
+
198
+ // Fallback to normal if not match. This code should never reach
199
+ /* istanbul ignore next */
200
+ if ( startIndex === undefined ) {
201
+ startIndex = 0 ;
202
+ startOffset = 0 ;
203
+ }
204
+ if ( endIndex === undefined ) {
205
+ endIndex = dataLen - 1 ;
206
+ }
207
+
208
+ // Give cache to improve scroll experience
209
+ endIndex = Math . min ( endIndex + 1 , dataLen ) ;
210
+ calRes . value = {
211
+ scrollHeight : itemTop ,
212
+ start : startIndex ,
213
+ end : endIndex ,
214
+ offset : startOffset ,
215
+ } ;
216
+ } ,
217
+ { immediate : true } ,
218
+ ) ;
206
219
207
220
// =============================== In Range ===============================
208
221
const maxScrollHeight = computed ( ( ) => calRes . value . scrollHeight ! - props . height ! ) ;
209
222
210
223
function keepInRange ( newScrollTop : number ) {
211
- let newTop = Math . max ( newScrollTop , 0 ) ;
224
+ let newTop = newScrollTop ;
212
225
if ( ! Number . isNaN ( maxScrollHeight . value ) ) {
213
226
newTop = Math . min ( newTop , maxScrollHeight . value ) ;
214
227
}
228
+ newTop = Math . max ( newTop , 0 ) ;
215
229
return newTop ;
216
230
}
217
231
@@ -226,8 +240,7 @@ const List = defineComponent({
226
240
syncScrollTop ( newTop ) ;
227
241
}
228
242
229
- // This code may only trigger in test case.
230
- // But we still need a sync if some special escape
243
+ // When data size reduce. It may trigger native scroll event back to fit scroll position
231
244
function onFallbackScroll ( e : UIEvent ) {
232
245
const { scrollTop : newScrollTop } = e . currentTarget as Element ;
233
246
if ( Math . abs ( newScrollTop - state . scrollTop ) >= 1 ) {
@@ -299,7 +312,7 @@ const List = defineComponent({
299
312
// ================================= Ref ==================================
300
313
const scrollTo = useScrollTo (
301
314
componentRef ,
302
- state ,
315
+ mergedData ,
303
316
heights ,
304
317
props ,
305
318
getKey ,
@@ -328,6 +341,7 @@ const List = defineComponent({
328
341
329
342
return {
330
343
state,
344
+ mergedData,
331
345
componentStyle,
332
346
scrollTo,
333
347
onFallbackScroll,
@@ -360,7 +374,7 @@ const List = defineComponent({
360
374
...restProps
361
375
} = { ...this . $props , ...this . $attrs } as any ;
362
376
const mergedClassName = classNames ( prefixCls , className ) ;
363
- const { scrollTop, mergedData } = this . state ;
377
+ const { scrollTop } = this . state ;
364
378
const { scrollHeight, offset, start, end } = this . calRes ;
365
379
const {
366
380
componentStyle,
@@ -370,6 +384,7 @@ const List = defineComponent({
370
384
collectHeight,
371
385
sharedConfig,
372
386
setInstance,
387
+ mergedData,
373
388
} = this ;
374
389
const listChildren = renderChildren (
375
390
mergedData ,
0 commit comments