99 onBeforeUnmount ,
1010 reactive ,
1111 CSSProperties ,
12+ watch ,
1213} from 'vue' ;
1314import { Key } from '../_util/type' ;
1415import Filler from './Filler' ;
@@ -53,10 +54,9 @@ function renderChildren<T>(
5354 } ) ;
5455}
5556
56- export interface ListState < T = object > {
57+ export interface ListState {
5758 scrollTop : number ;
5859 scrollMoving : boolean ;
59- mergedData : T [ ] ;
6060}
6161
6262const List = defineComponent ( {
@@ -97,7 +97,10 @@ const List = defineComponent({
9797 const state = reactive < ListState > ( {
9898 scrollTop : 0 ,
9999 scrollMoving : false ,
100- mergedData : computed ( ( ) => props . data || EMPTY_DATA ) as any ,
100+ } ) ;
101+
102+ const mergedData = computed ( ( ) => {
103+ return props . data || EMPTY_DATA ;
101104 } ) ;
102105
103106 const componentRef = ref < HTMLDivElement > ( ) ;
@@ -108,7 +111,7 @@ const List = defineComponent({
108111 if ( typeof props . itemKey === 'function' ) {
109112 return props . itemKey ( item ) ;
110113 }
111- return item [ props . itemKey ] ;
114+ return item ?. [ props . itemKey ] ;
112115 } ;
113116
114117 const sharedConfig = {
@@ -135,83 +138,94 @@ const List = defineComponent({
135138 // ================================ Height ================================
136139 const [ setInstance , collectHeight , heights ] = useHeights ( getKey , null , null ) ;
137140
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 ;
177158 }
178159
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 ;
182169 }
183170
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+ }
186189
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+ }
196194
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+ ) ;
206219
207220 // =============================== In Range ===============================
208221 const maxScrollHeight = computed ( ( ) => calRes . value . scrollHeight ! - props . height ! ) ;
209222
210223 function keepInRange ( newScrollTop : number ) {
211- let newTop = Math . max ( newScrollTop , 0 ) ;
224+ let newTop = newScrollTop ;
212225 if ( ! Number . isNaN ( maxScrollHeight . value ) ) {
213226 newTop = Math . min ( newTop , maxScrollHeight . value ) ;
214227 }
228+ newTop = Math . max ( newTop , 0 ) ;
215229 return newTop ;
216230 }
217231
@@ -226,8 +240,7 @@ const List = defineComponent({
226240 syncScrollTop ( newTop ) ;
227241 }
228242
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
231244 function onFallbackScroll ( e : UIEvent ) {
232245 const { scrollTop : newScrollTop } = e . currentTarget as Element ;
233246 if ( Math . abs ( newScrollTop - state . scrollTop ) >= 1 ) {
@@ -299,7 +312,7 @@ const List = defineComponent({
299312 // ================================= Ref ==================================
300313 const scrollTo = useScrollTo (
301314 componentRef ,
302- state ,
315+ mergedData ,
303316 heights ,
304317 props ,
305318 getKey ,
@@ -328,6 +341,7 @@ const List = defineComponent({
328341
329342 return {
330343 state,
344+ mergedData,
331345 componentStyle,
332346 scrollTo,
333347 onFallbackScroll,
@@ -360,7 +374,7 @@ const List = defineComponent({
360374 ...restProps
361375 } = { ...this . $props , ...this . $attrs } as any ;
362376 const mergedClassName = classNames ( prefixCls , className ) ;
363- const { scrollTop, mergedData } = this . state ;
377+ const { scrollTop } = this . state ;
364378 const { scrollHeight, offset, start, end } = this . calRes ;
365379 const {
366380 componentStyle,
@@ -370,6 +384,7 @@ const List = defineComponent({
370384 collectHeight,
371385 sharedConfig,
372386 setInstance,
387+ mergedData,
373388 } = this ;
374389 const listChildren = renderChildren (
375390 mergedData ,
0 commit comments