11import {
2- computed ,
32 defineComponent ,
4- h ,
53 onActivated ,
64 onBeforeMount ,
75 onMounted ,
6+ onUnmounted ,
87 ref ,
98 watch ,
109} from 'vue' ;
1110import Virtual from './virtual' ;
1211import { Item , Slot } from './item' ;
1312import { VirtualProps } from './props' ;
14- // TODO: remove this
15- import emitter from 'tiny-emitter/instance' ;
1613
1714enum EVENT_TYPE {
18- ITEM = 'item_resize ' ,
19- SLOT = 'slot_resize ' ,
15+ ITEM = 'itemResize ' ,
16+ SLOT = 'slotResize ' ,
2017}
2118
2219enum SLOT_TYPE {
2320 HEADER = 'thead' , // string value also use for aria role attribute
2421 FOOTER = 'tfoot' ,
2522}
2623
24+ interface Range {
25+ start : number ;
26+ end : number ;
27+ padFront : number ;
28+ padBehind : number ;
29+ }
30+
2731export default defineComponent ( {
2832 name : 'VirtualList' ,
2933 props : VirtualProps ,
3034 setup ( props , { emit, slots, expose } ) {
31- // TODO: TS
32- const range = ref < any > ( null ) ;
35+ const isHorizontal = props . direction === 'horizontal' ;
36+ const directionKey = isHorizontal ? 'scrollLeft' : 'scrollTop' ;
37+ const range = ref < Range | null > ( null ) ;
3338 const root = ref < HTMLElement | null > ( ) ;
3439 const shepherd = ref < HTMLDivElement | null > ( null ) ;
35- const isHorizontal = computed ( ( ) => props . direction === 'horizontal' ) ;
36- const directionKey = computed ( ( ) =>
37- isHorizontal . value ? 'scrollLeft' : 'scrollTop' ,
38- ) ;
3940 let virtual : Virtual ;
4041
4142 /**
@@ -69,19 +70,22 @@ export default defineComponent({
6970 /**
7071 * methods
7172 */
73+ // get item size by id
74+ const getSize = ( id ) => {
75+ return virtual . sizes . get ( id ) ;
76+ } ;
7277 const getOffset = ( ) => {
7378 if ( props . pageMode ) {
7479 return (
75- document . documentElement [ directionKey . value ] ||
76- document . body [ directionKey . value ]
80+ document . documentElement [ directionKey ] || document . body [ directionKey ]
7781 ) ;
7882 } else {
79- return root . value ? Math . ceil ( root . value [ directionKey . value ] ) : 0 ;
83+ return root . value ? Math . ceil ( root . value [ directionKey ] ) : 0 ;
8084 }
8185 } ;
8286 // return client viewport size
8387 const getClientSize = ( ) => {
84- const key = isHorizontal . value ? 'clientWidth' : 'clientHeight' ;
88+ const key = isHorizontal ? 'clientWidth' : 'clientHeight' ;
8589 if ( props . pageMode ) {
8690 return document . documentElement [ key ] || document . body [ key ] ;
8791 } else {
@@ -90,7 +94,7 @@ export default defineComponent({
9094 } ;
9195 // return all scroll size
9296 const getScrollSize = ( ) => {
93- const key = isHorizontal . value ? 'scrollWidth' : 'scrollHeight' ;
97+ const key = isHorizontal ? 'scrollWidth' : 'scrollHeight' ;
9498 if ( props . pageMode ) {
9599 return document . documentElement [ key ] || document . body [ key ] ;
96100 } else {
@@ -167,11 +171,11 @@ export default defineComponent({
167171 // set current scroll position to a expectant offset
168172 const scrollToOffset = ( offset : number ) => {
169173 if ( props . pageMode ) {
170- document . body [ directionKey . value ] = offset ;
171- document . documentElement [ directionKey . value ] = offset ;
174+ document . body [ directionKey ] = offset ;
175+ document . documentElement [ directionKey ] = offset ;
172176 } else {
173177 if ( root . value ) {
174- root . value [ directionKey . value ] = offset ;
178+ root . value [ directionKey ] = offset ;
175179 }
176180 }
177181 } ;
@@ -204,7 +208,7 @@ export default defineComponent({
204208 index = { index }
205209 tag = { itemTag }
206210 event = { EVENT_TYPE . ITEM }
207- horizontal = { isHorizontal . value }
211+ horizontal = { isHorizontal }
208212 uniqueKey = { uniqueKey }
209213 source = { dataSource }
210214 extraProps = { extraProps }
@@ -214,6 +218,7 @@ export default defineComponent({
214218 class = { `${ itemClass } ${
215219 props . itemClassAdd ? ' ' + props . itemClassAdd ( index ) : ''
216220 } `}
221+ onItemResize = { onItemResized }
217222 /> ,
218223 ) ;
219224 } else {
@@ -247,19 +252,47 @@ export default defineComponent({
247252 }
248253 } ;
249254
255+ // set current scroll position to bottom
256+ const scrollToBottom = ( ) => {
257+ if ( shepherd . value ) {
258+ const offset =
259+ shepherd . value [ isHorizontal ? 'offsetLeft' : 'offsetTop' ] ;
260+ scrollToOffset ( offset ) ;
261+
262+ // check if it's really scrolled to the bottom
263+ // maybe list doesn't render and calculate to last range
264+ // so we need retry in next event loop until it really at bottom
265+ setTimeout ( ( ) => {
266+ if ( getOffset ( ) + getClientSize ( ) < getScrollSize ( ) ) {
267+ scrollToBottom ( ) ;
268+ }
269+ } , 3 ) ;
270+ }
271+ } ;
272+
273+ // when using page mode we need update slot header size manually
274+ // taking root offset relative to the browser as slot header size
275+ const updatePageModeFront = ( ) => {
276+ if ( root . value ) {
277+ const rect = root . value . getBoundingClientRect ( ) ;
278+ const { defaultView } = root . value . ownerDocument ;
279+ const offsetFront = isHorizontal
280+ ? rect . left + defaultView ! . pageXOffset
281+ : rect . top + defaultView ! . pageYOffset ;
282+ virtual . updateParam ( 'slotHeaderSize' , offsetFront ) ;
283+ }
284+ } ;
285+
286+ // get the total number of stored (rendered) items
287+ const getSizes = ( ) => {
288+ return virtual . sizes . size ;
289+ } ;
290+
250291 /**
251292 * life cycles
252293 */
253294 onBeforeMount ( ( ) => {
254295 installVirtual ( ) ;
255-
256- // listen item size change
257- emitter . on ( EVENT_TYPE . ITEM , onItemResized ) ;
258-
259- // listen slot size change
260- if ( slots . header || slots . footer ) {
261- emitter . on ( EVENT_TYPE . SLOT , onSlotResized ) ;
262- }
263296 } ) ;
264297
265298 // set back offset when awake from keep-alive
@@ -277,38 +310,23 @@ export default defineComponent({
277310
278311 // in page mode we bind scroll event to document
279312 if ( props . pageMode ) {
280- // todo
313+ updatePageModeFront ( ) ;
314+ document . addEventListener ( 'scroll' , onScroll , {
315+ passive : false ,
316+ } ) ;
281317 }
282318 } ) ;
283319
284- // set current scroll position to bottom
285- const scrollToBottom = ( ) => {
286- if ( shepherd . value ) {
287- const offset =
288- shepherd . value [ isHorizontal . value ? 'offsetLeft' : 'offsetTop' ] ;
289- scrollToOffset ( offset ) ;
290-
291- // check if it's really scrolled to the bottom
292- // maybe list doesn't render and calculate to last range
293- // so we need retry in next event loop until it really at bottom
294- setTimeout ( ( ) => {
295- if ( getOffset ( ) + getClientSize ( ) < getScrollSize ( ) ) {
296- scrollToBottom ( ) ;
297- }
298- } , 3 ) ;
320+ onUnmounted ( ( ) => {
321+ virtual . destroy ( ) ;
322+ if ( props . pageMode ) {
323+ document . removeEventListener ( 'scroll' , onScroll ) ;
299324 }
300- } ;
301-
302- // get the total number of stored (rendered) items
303- const getSizes = ( ) => {
304- return virtual . sizes . size ;
305- } ;
306-
307- // get item size by id
308- const getSize = ( id ) => {
309- return virtual . sizes . get ( id ) ;
310- } ;
325+ } ) ;
311326
327+ /**
328+ * public methods
329+ */
312330 expose ( {
313331 scrollToBottom,
314332 getSizes,
@@ -333,9 +351,9 @@ export default defineComponent({
333351 footerClass,
334352 footerStyle,
335353 } = props ;
336- const { padFront, padBehind } = range . value ;
354+ const { padFront, padBehind } = range . value ! ;
337355 const paddingStyle = {
338- padding : isHorizontal . value
356+ padding : isHorizontal
339357 ? `0px ${ padBehind } px 0px ${ padFront } px`
340358 : `${ padFront } px 0px ${ padBehind } px` ,
341359 } ;
@@ -354,6 +372,7 @@ export default defineComponent({
354372 tag = { headerTag }
355373 event = { EVENT_TYPE . SLOT }
356374 uniqueKey = { SLOT_TYPE . HEADER }
375+ onSlotResize = { onSlotResized }
357376 >
358377 { header ( ) }
359378 </ Slot >
@@ -372,6 +391,7 @@ export default defineComponent({
372391 tag = { footerTag }
373392 event = { EVENT_TYPE . SLOT }
374393 uniqueKey = { SLOT_TYPE . FOOTER }
394+ onSlotResize = { onSlotResized }
375395 >
376396 { footer ( ) }
377397 </ Slot >
@@ -381,8 +401,8 @@ export default defineComponent({
381401 < div
382402 ref = { shepherd }
383403 style = { {
384- width : isHorizontal . value ? '0px' : '100%' ,
385- height : isHorizontal . value ? '100%' : '0px' ,
404+ width : isHorizontal ? '0px' : '100%' ,
405+ height : isHorizontal ? '100%' : '0px' ,
386406 } }
387407 />
388408 </ RootTag >
0 commit comments