1
1
import {
2
- computed ,
3
2
defineComponent ,
4
- h ,
5
3
onActivated ,
6
4
onBeforeMount ,
7
5
onMounted ,
6
+ onUnmounted ,
8
7
ref ,
9
8
watch ,
10
9
} from 'vue' ;
11
10
import Virtual from './virtual' ;
12
11
import { Item , Slot } from './item' ;
13
12
import { VirtualProps } from './props' ;
14
- // TODO: remove this
15
- import emitter from 'tiny-emitter/instance' ;
16
13
17
14
enum EVENT_TYPE {
18
- ITEM = 'item_resize ' ,
19
- SLOT = 'slot_resize ' ,
15
+ ITEM = 'itemResize ' ,
16
+ SLOT = 'slotResize ' ,
20
17
}
21
18
22
19
enum SLOT_TYPE {
23
20
HEADER = 'thead' , // string value also use for aria role attribute
24
21
FOOTER = 'tfoot' ,
25
22
}
26
23
24
+ interface Range {
25
+ start : number ;
26
+ end : number ;
27
+ padFront : number ;
28
+ padBehind : number ;
29
+ }
30
+
27
31
export default defineComponent ( {
28
32
name : 'VirtualList' ,
29
33
props : VirtualProps ,
30
34
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 ) ;
33
38
const root = ref < HTMLElement | null > ( ) ;
34
39
const shepherd = ref < HTMLDivElement | null > ( null ) ;
35
- const isHorizontal = computed ( ( ) => props . direction === 'horizontal' ) ;
36
- const directionKey = computed ( ( ) =>
37
- isHorizontal . value ? 'scrollLeft' : 'scrollTop' ,
38
- ) ;
39
40
let virtual : Virtual ;
40
41
41
42
/**
@@ -69,19 +70,22 @@ export default defineComponent({
69
70
/**
70
71
* methods
71
72
*/
73
+ // get item size by id
74
+ const getSize = ( id ) => {
75
+ return virtual . sizes . get ( id ) ;
76
+ } ;
72
77
const getOffset = ( ) => {
73
78
if ( props . pageMode ) {
74
79
return (
75
- document . documentElement [ directionKey . value ] ||
76
- document . body [ directionKey . value ]
80
+ document . documentElement [ directionKey ] || document . body [ directionKey ]
77
81
) ;
78
82
} else {
79
- return root . value ? Math . ceil ( root . value [ directionKey . value ] ) : 0 ;
83
+ return root . value ? Math . ceil ( root . value [ directionKey ] ) : 0 ;
80
84
}
81
85
} ;
82
86
// return client viewport size
83
87
const getClientSize = ( ) => {
84
- const key = isHorizontal . value ? 'clientWidth' : 'clientHeight' ;
88
+ const key = isHorizontal ? 'clientWidth' : 'clientHeight' ;
85
89
if ( props . pageMode ) {
86
90
return document . documentElement [ key ] || document . body [ key ] ;
87
91
} else {
@@ -90,7 +94,7 @@ export default defineComponent({
90
94
} ;
91
95
// return all scroll size
92
96
const getScrollSize = ( ) => {
93
- const key = isHorizontal . value ? 'scrollWidth' : 'scrollHeight' ;
97
+ const key = isHorizontal ? 'scrollWidth' : 'scrollHeight' ;
94
98
if ( props . pageMode ) {
95
99
return document . documentElement [ key ] || document . body [ key ] ;
96
100
} else {
@@ -167,11 +171,11 @@ export default defineComponent({
167
171
// set current scroll position to a expectant offset
168
172
const scrollToOffset = ( offset : number ) => {
169
173
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 ;
172
176
} else {
173
177
if ( root . value ) {
174
- root . value [ directionKey . value ] = offset ;
178
+ root . value [ directionKey ] = offset ;
175
179
}
176
180
}
177
181
} ;
@@ -204,7 +208,7 @@ export default defineComponent({
204
208
index = { index }
205
209
tag = { itemTag }
206
210
event = { EVENT_TYPE . ITEM }
207
- horizontal = { isHorizontal . value }
211
+ horizontal = { isHorizontal }
208
212
uniqueKey = { uniqueKey }
209
213
source = { dataSource }
210
214
extraProps = { extraProps }
@@ -214,6 +218,7 @@ export default defineComponent({
214
218
class = { `${ itemClass } ${
215
219
props . itemClassAdd ? ' ' + props . itemClassAdd ( index ) : ''
216
220
} `}
221
+ onItemResize = { onItemResized }
217
222
/> ,
218
223
) ;
219
224
} else {
@@ -247,19 +252,47 @@ export default defineComponent({
247
252
}
248
253
} ;
249
254
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
+
250
291
/**
251
292
* life cycles
252
293
*/
253
294
onBeforeMount ( ( ) => {
254
295
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
- }
263
296
} ) ;
264
297
265
298
// set back offset when awake from keep-alive
@@ -277,38 +310,23 @@ export default defineComponent({
277
310
278
311
// in page mode we bind scroll event to document
279
312
if ( props . pageMode ) {
280
- // todo
313
+ updatePageModeFront ( ) ;
314
+ document . addEventListener ( 'scroll' , onScroll , {
315
+ passive : false ,
316
+ } ) ;
281
317
}
282
318
} ) ;
283
319
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 ) ;
299
324
}
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
+ } ) ;
311
326
327
+ /**
328
+ * public methods
329
+ */
312
330
expose ( {
313
331
scrollToBottom,
314
332
getSizes,
@@ -333,9 +351,9 @@ export default defineComponent({
333
351
footerClass,
334
352
footerStyle,
335
353
} = props ;
336
- const { padFront, padBehind } = range . value ;
354
+ const { padFront, padBehind } = range . value ! ;
337
355
const paddingStyle = {
338
- padding : isHorizontal . value
356
+ padding : isHorizontal
339
357
? `0px ${ padBehind } px 0px ${ padFront } px`
340
358
: `${ padFront } px 0px ${ padBehind } px` ,
341
359
} ;
@@ -354,6 +372,7 @@ export default defineComponent({
354
372
tag = { headerTag }
355
373
event = { EVENT_TYPE . SLOT }
356
374
uniqueKey = { SLOT_TYPE . HEADER }
375
+ onSlotResize = { onSlotResized }
357
376
>
358
377
{ header ( ) }
359
378
</ Slot >
@@ -372,6 +391,7 @@ export default defineComponent({
372
391
tag = { footerTag }
373
392
event = { EVENT_TYPE . SLOT }
374
393
uniqueKey = { SLOT_TYPE . FOOTER }
394
+ onSlotResize = { onSlotResized }
375
395
>
376
396
{ footer ( ) }
377
397
</ Slot >
@@ -381,8 +401,8 @@ export default defineComponent({
381
401
< div
382
402
ref = { shepherd }
383
403
style = { {
384
- width : isHorizontal . value ? '0px' : '100%' ,
385
- height : isHorizontal . value ? '100%' : '0px' ,
404
+ width : isHorizontal ? '0px' : '100%' ,
405
+ height : isHorizontal ? '100%' : '0px' ,
386
406
} }
387
407
/>
388
408
</ RootTag >
0 commit comments