vue-virtual-flow 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +421 -97
- package/README.vi.md +422 -95
- package/dist/components/SkeletonLoader.d.cts +9 -0
- package/dist/components/SkeletonLoader.d.ts +9 -0
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/types.d.cts +9 -0
- package/dist/types.d.ts +9 -0
- package/dist/vue-virtual-flow.cjs +1 -1
- package/dist/vue-virtual-flow.css +1 -1
- package/dist/vue-virtual-flow.js +811 -777
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ sets responsive without prescribing how an item should look.
|
|
|
22
22
|
- [ChatVirtualScroll](#chatvirtualscroll)
|
|
23
23
|
- [ShortMediaFeed](#shortmediafeed)
|
|
24
24
|
- [VirtualCarousel](#virtualcarousel)
|
|
25
|
+
- [SkeletonLoader](#skeletonloader)
|
|
25
26
|
- [Shared recipes](#shared-recipes)
|
|
26
27
|
- [Viewport height](#viewport-height)
|
|
27
28
|
- [Stable item keys](#stable-item-keys)
|
|
@@ -44,10 +45,19 @@ Import the library stylesheet once in your application entry point:
|
|
|
44
45
|
import 'vue-virtual-flow/style.css'
|
|
45
46
|
```
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
| Dependency | Requirement |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| Vue | `^3.4.0` |
|
|
51
|
+
| Node.js | `>=18` |
|
|
48
52
|
|
|
49
53
|
## Quick start
|
|
50
54
|
|
|
55
|
+
| Registration | Usage |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| Local imports | `import { VirtualList, SkeletonLoader } from 'vue-virtual-flow'` |
|
|
58
|
+
| Global plugin | `createApp(App).use(VueVirtualScroll)` |
|
|
59
|
+
| Shared styles | `import 'vue-virtual-flow/style.css'` |
|
|
60
|
+
|
|
51
61
|
Import components locally:
|
|
52
62
|
|
|
53
63
|
```vue
|
|
@@ -85,7 +95,7 @@ createApp(App).use(VueVirtualScroll).mount('#app')
|
|
|
85
95
|
|
|
86
96
|
The plugin registers `VirtualList`, `DynamicVirtualScroll`, `VirtualScroll`,
|
|
87
97
|
`WindowDynamicVirtualScroll`, `WindowGirdVirtualScroll`, `ChatVirtualScroll`,
|
|
88
|
-
`ShortMediaFeed`, and `
|
|
98
|
+
`ShortMediaFeed`, `VirtualCarousel`, and `SkeletonLoader`.
|
|
89
99
|
|
|
90
100
|
## Choose a component
|
|
91
101
|
|
|
@@ -99,9 +109,22 @@ The plugin registers `VirtualList`, `DynamicVirtualScroll`, `VirtualScroll`,
|
|
|
99
109
|
| `ChatVirtualScroll` | Messages are appended at the bottom and older history is prepended. | Component container | Variable, measured automatically |
|
|
100
110
|
| `ShortMediaFeed` | One full-height item should snap into view at a time. | Component container | One viewport per item |
|
|
101
111
|
| `VirtualCarousel` | Several horizontal slides should snap and virtualize. | Component container | Calculated from container width |
|
|
112
|
+
| `SkeletonLoader` | You need a placeholder while content loads. | None | Configurable width and height |
|
|
102
113
|
|
|
103
114
|
## Component guides
|
|
104
115
|
|
|
116
|
+
Events use Vue template listeners; action methods are called through a component
|
|
117
|
+
ref. The tables below use `list`, `chat`, `feed`, or `carousel` as the ref name.
|
|
118
|
+
|
|
119
|
+
| Task | Usage |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| Declare a typed ref | `const list = ref<VirtualScrollExpose>()` |
|
|
122
|
+
| Attach the ref to the component | `<VirtualScroll ref="list" ... />` |
|
|
123
|
+
| Call after mount in a script | `list.value?.scrollToIndex(10)` |
|
|
124
|
+
| Call from a template event | `@click="list?.scrollToIndex(10)"` |
|
|
125
|
+
| Select scroll behavior | Pass `{ behavior: 'smooth' }` to `scrollTo` / `scrollToIndex`, or `'smooth'` to `scrollToTop`, `scrollToBottom`, `next`, or `previous`. |
|
|
126
|
+
| Align a list item | `scrollToIndex(index, { align: 'start' })` / `'center'` / `'end'` |
|
|
127
|
+
|
|
105
128
|
### VirtualList and DynamicVirtualScroll
|
|
106
129
|
|
|
107
130
|
`VirtualList` is an alias of `DynamicVirtualScroll`. Both names render the same
|
|
@@ -199,19 +222,19 @@ to items that have not been measured.
|
|
|
199
222
|
|
|
200
223
|
#### Events
|
|
201
224
|
|
|
202
|
-
| Event | Payload | When it fires |
|
|
203
|
-
| --- | --- | --- |
|
|
204
|
-
| `scroll` | `{ scrollTop, startIndex, endIndex }` | On container scroll. `endIndex` is exclusive. |
|
|
205
|
-
| `load-more` | none | Near the end when `hasMore` is true and `loading` is false. |
|
|
206
|
-
| `refresh` | none | After pulling past the refresh threshold and releasing. |
|
|
225
|
+
| Event | Payload | When it fires | Usage |
|
|
226
|
+
| --- | --- | --- | --- |
|
|
227
|
+
| `scroll` | `{ scrollTop, startIndex, endIndex }` | On container scroll. `endIndex` is exclusive. | `@scroll="onScroll"` |
|
|
228
|
+
| `load-more` | none | Near the end when `hasMore` is true and `loading` is false. | `@load-more="loadMore"` |
|
|
229
|
+
| `refresh` | none | After pulling past the refresh threshold and releasing. | `@refresh="refreshFirstPage"` |
|
|
207
230
|
|
|
208
231
|
#### Exposed API
|
|
209
232
|
|
|
210
|
-
| Method | Description |
|
|
211
|
-
| --- | --- |
|
|
212
|
-
| `scrollTo(position, options?)` | Scrolls to a pixel offset inside the list. |
|
|
213
|
-
| `scrollToIndex(index,
|
|
214
|
-
| `scrollToTop(behavior?)` | Scrolls to the start of the list. |
|
|
233
|
+
| Method / state | Description | Template example |
|
|
234
|
+
| --- | --- | --- |
|
|
235
|
+
| `scrollTo(position, options?)` | Scrolls to a pixel offset inside the list. | `list?.scrollTo(200, { behavior: 'smooth' })` |
|
|
236
|
+
| `scrollToIndex(index, options?)` | Scrolls to a zero-based index. `align` is `start`, `center`, or `end`. | `list?.scrollToIndex(10, { behavior: 'smooth' })` |
|
|
237
|
+
| `scrollToTop(behavior?)` | Scrolls to the start of the list. | `list?.scrollToTop()` |
|
|
215
238
|
|
|
216
239
|
Long-distance smooth jumps automatically become immediate jumps because
|
|
217
240
|
unmeasured rows can change the target offset while scrolling.
|
|
@@ -285,13 +308,30 @@ The rendered row must stay at `itemSize`. If content can wrap or resize, use
|
|
|
285
308
|
| `refreshing` | `boolean` | `false` | Keeps the refresh indicator open during async work. |
|
|
286
309
|
| `pullRefreshThreshold` | `number` | `64` | Pull distance required to emit `refresh`, in pixels. |
|
|
287
310
|
|
|
288
|
-
#### Slots
|
|
311
|
+
#### Slots
|
|
289
312
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
313
|
+
| Slot | Scope | Description |
|
|
314
|
+
| --- | --- | --- |
|
|
315
|
+
| `default` | `{ item, index }` | Renders each mounted row. |
|
|
316
|
+
| `empty` | none | Rendered when `items` is empty and `loading` is false. |
|
|
317
|
+
| `loading` | none | Loading row; defaults to “Loading more…”. |
|
|
318
|
+
| `refresh` | `{ pullDistance, progress, refreshing }` | Pull-to-refresh indicator. `progress` is clamped from `0` to `1`. |
|
|
319
|
+
|
|
320
|
+
#### Events
|
|
321
|
+
|
|
322
|
+
| Event | Payload | When it fires | Usage |
|
|
323
|
+
| --- | --- | --- | --- |
|
|
324
|
+
| `scroll` | `{ scrollTop, startIndex, endIndex }` | On container scroll. `endIndex` is exclusive. | `@scroll="onScroll"` |
|
|
325
|
+
| `load-more` | none | Near the end when `hasMore` is true and `loading` is false. | `@load-more="loadMore"` |
|
|
326
|
+
| `refresh` | none | After pulling past the refresh threshold and releasing. | `@refresh="refreshFirstPage"` |
|
|
327
|
+
|
|
328
|
+
#### Exposed API
|
|
329
|
+
|
|
330
|
+
| Method / state | Description | Template example |
|
|
331
|
+
| --- | --- | --- |
|
|
332
|
+
| `scrollTo(position, options?)` | Scrolls to a pixel offset inside the list. | `list?.scrollTo(200, { behavior: 'smooth' })` |
|
|
333
|
+
| `scrollToIndex(index, options?)` | Scrolls to a zero-based item index. `align` is `start`, `center`, or `end`. | `list?.scrollToIndex(10, { behavior: 'smooth' })` |
|
|
334
|
+
| `scrollToTop(behavior?)` | Scrolls to the start of the list. | `list?.scrollToTop()` |
|
|
295
335
|
|
|
296
336
|
### WindowDynamicVirtualScroll
|
|
297
337
|
|
|
@@ -355,14 +395,30 @@ const list = ref<VirtualScrollExpose>()
|
|
|
355
395
|
| `refreshing` | `boolean` | `false` | Keeps the refresh indicator open during async work. |
|
|
356
396
|
| `pullRefreshThreshold` | `number` | `64` | Pull distance required to emit `refresh`, in pixels. |
|
|
357
397
|
|
|
358
|
-
#### Slots
|
|
398
|
+
#### Slots
|
|
359
399
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
400
|
+
| Slot | Scope | Description |
|
|
401
|
+
| --- | --- | --- |
|
|
402
|
+
| `default` | `{ item, index }` | Renders each mounted row. |
|
|
403
|
+
| `empty` | none | Rendered when `items` is empty and `loading` is false. |
|
|
404
|
+
| `loading` | none | Loading row; defaults to “Loading more…”. |
|
|
405
|
+
| `refresh` | `{ pullDistance, progress, refreshing }` | Pull-to-refresh indicator. `progress` is clamped from `0` to `1`. |
|
|
406
|
+
|
|
407
|
+
#### Events
|
|
408
|
+
|
|
409
|
+
| Event | Payload | When it fires | Usage |
|
|
410
|
+
| --- | --- | --- | --- |
|
|
411
|
+
| `scroll` | `{ scrollTop, startIndex, endIndex }` | On window scroll. `scrollTop` is relative to the component start. `endIndex` is exclusive. | `@scroll="onScroll"` |
|
|
412
|
+
| `load-more` | none | Near the end when `hasMore` is true and `loading` is false. | `@load-more="loadMore"` |
|
|
413
|
+
| `refresh` | none | After pulling past the refresh threshold and releasing. | `@refresh="refreshFirstPage"` |
|
|
414
|
+
|
|
415
|
+
#### Exposed API
|
|
416
|
+
|
|
417
|
+
| Method / state | Description | Template example |
|
|
418
|
+
| --- | --- | --- |
|
|
419
|
+
| `scrollTo(position, options?)` | Scrolls the window to a pixel offset relative to the list start. | `list?.scrollTo(200, { behavior: 'smooth' })` |
|
|
420
|
+
| `scrollToIndex(index, options?)` | Scrolls the window to a zero-based item index. `align` is `start`, `center`, or `end`. | `list?.scrollToIndex(10, { behavior: 'smooth' })` |
|
|
421
|
+
| `scrollToTop(behavior?)` | Scrolls the window to the component start. | `list?.scrollToTop()` |
|
|
366
422
|
|
|
367
423
|
### WindowGirdVirtualScroll
|
|
368
424
|
|
|
@@ -370,6 +426,8 @@ Use `WindowGirdVirtualScroll` for a multi-column grid whose cards have a known,
|
|
|
370
426
|
fixed height and whose viewport is the browser window. Virtualization happens
|
|
371
427
|
by complete rows, so every visible column remains mounted together.
|
|
372
428
|
|
|
429
|
+
#### Example
|
|
430
|
+
|
|
373
431
|
```vue
|
|
374
432
|
<script setup lang="ts">
|
|
375
433
|
import { WindowGirdVirtualScroll } from 'vue-virtual-flow'
|
|
@@ -399,11 +457,49 @@ const products = Array.from({ length: 10_000 }, (_, id) => ({
|
|
|
399
457
|
</template>
|
|
400
458
|
```
|
|
401
459
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
460
|
+
#### Props
|
|
461
|
+
|
|
462
|
+
| Prop | Type | Default | Description |
|
|
463
|
+
| --- | --- | --- | --- |
|
|
464
|
+
| `items` | `readonly T[]` | required | Data passed to the default slot. |
|
|
465
|
+
| `itemSize` | `number` | required | Fixed height of each grid row in pixels. Overflow is clipped. |
|
|
466
|
+
| `columns` | `number` | `2` | Number of columns in each row. |
|
|
467
|
+
| `gap` | `number` | `0` | Spacing between rows and columns in pixels. |
|
|
468
|
+
| `overscan` | `number` | `1` | Extra rows mounted before and after the visible range. |
|
|
469
|
+
| `itemKey` | `keyof T \| (item, index) => PropertyKey` | `id`, `key`, or index | Stable identity for each item. |
|
|
470
|
+
| `ariaLabel` | `string` | `"Window virtual grid"` | Accessible label for the grid. |
|
|
471
|
+
| `hasMore` | `boolean` | `false` | Indicates that another page can be loaded. |
|
|
472
|
+
| `loading` | `boolean` | `false` | Prevents duplicate requests and shows the loading row. |
|
|
473
|
+
| `loadingItemSize` | `number` | `itemSize` | Reserved loading-row height in pixels. |
|
|
474
|
+
| `loadMoreThreshold` | `number` | `200` | Distance from the window bottom to the grid end that triggers `load-more`, in pixels. |
|
|
475
|
+
| `pullToRefresh` | `boolean` | `false` | Enables refresh while the page is at `window.scrollY = 0`. |
|
|
476
|
+
| `refreshing` | `boolean` | `false` | Keeps the refresh indicator open during async work. |
|
|
477
|
+
| `pullRefreshThreshold` | `number` | `64` | Pull distance required to emit `refresh`, in pixels. |
|
|
478
|
+
|
|
479
|
+
#### Slots
|
|
480
|
+
|
|
481
|
+
| Slot | Scope | Description |
|
|
482
|
+
| --- | --- | --- |
|
|
483
|
+
| `default` | `{ item, index, rowIndex, columnIndex }` | Renders each mounted cell. |
|
|
484
|
+
| `empty` | none | Rendered when `items` is empty and `loading` is false. |
|
|
485
|
+
| `loading` | none | Loading row; defaults to “Loading more…”. |
|
|
486
|
+
| `refresh` | `{ pullDistance, progress, refreshing }` | Pull-to-refresh indicator. `progress` is clamped from `0` to `1`. |
|
|
487
|
+
|
|
488
|
+
#### Events
|
|
489
|
+
|
|
490
|
+
| Event | Payload | When it fires | Usage |
|
|
491
|
+
| --- | --- | --- | --- |
|
|
492
|
+
| `scroll` | `{ scrollTop, startIndex, endIndex }` | On window scroll. `scrollTop` is relative to the component start. `endIndex` is exclusive. | `@scroll="onScroll"` |
|
|
493
|
+
| `load-more` | none | Near the end when `hasMore` is true and `loading` is false. | `@load-more="loadMore"` |
|
|
494
|
+
| `refresh` | none | After pulling past the refresh threshold and releasing. | `@refresh="refreshFirstPage"` |
|
|
495
|
+
|
|
496
|
+
#### Exposed API
|
|
497
|
+
|
|
498
|
+
| Method / state | Description | Template example |
|
|
499
|
+
| --- | --- | --- |
|
|
500
|
+
| `scrollTo(position, options?)` | Scrolls the window to a pixel offset relative to the list start. | `list?.scrollTo(200, { behavior: 'smooth' })` |
|
|
501
|
+
| `scrollToIndex(index, options?)` | Scrolls the window to the row containing a zero-based item index. `align` is `start`, `center`, or `end`. | `list?.scrollToIndex(10, { behavior: 'smooth' })` |
|
|
502
|
+
| `scrollToTop(behavior?)` | Scrolls the window to the component start. | `list?.scrollToTop()` |
|
|
407
503
|
|
|
408
504
|
### ChatVirtualScroll
|
|
409
505
|
|
|
@@ -494,21 +590,21 @@ height added above the viewport and restores the reader's position.
|
|
|
494
590
|
|
|
495
591
|
#### Events
|
|
496
592
|
|
|
497
|
-
| Event | Payload | When it fires |
|
|
498
|
-
| --- | --- | --- |
|
|
499
|
-
| `scroll` | `{ scrollTop, startIndex, endIndex }` | On container scroll. |
|
|
500
|
-
| `load-older` | none | Near the top when `hasOlder` is true and `loadingOlder` is false. |
|
|
501
|
-
| `bottom-change` | `boolean` | When the viewport enters or leaves the bottom threshold. |
|
|
593
|
+
| Event | Payload | When it fires | Usage |
|
|
594
|
+
| --- | --- | --- | --- |
|
|
595
|
+
| `scroll` | `{ scrollTop, startIndex, endIndex }` | On container scroll. | `@scroll="onScroll"` |
|
|
596
|
+
| `load-older` | none | Near the top when `hasOlder` is true and `loadingOlder` is false. | `@load-older="loadOlder"` |
|
|
597
|
+
| `bottom-change` | `boolean` | When the viewport enters or leaves the bottom threshold. | `@bottom-change="onBottomChange"` |
|
|
502
598
|
|
|
503
599
|
#### Exposed API
|
|
504
600
|
|
|
505
|
-
|
|
|
506
|
-
| --- | --- |
|
|
507
|
-
| `isAtBottom` | Readonly boolean indicating whether the user is near the bottom. |
|
|
508
|
-
| `scrollTo(position, options?)` | Scrolls to a pixel offset. |
|
|
509
|
-
| `scrollToIndex(index,
|
|
510
|
-
| `scrollToTop(behavior?)` | Scrolls to the oldest loaded message. |
|
|
511
|
-
| `scrollToBottom(behavior?)` | Scrolls to the newest loaded message. |
|
|
601
|
+
| Method / state | Description | Template example |
|
|
602
|
+
| --- | --- | --- |
|
|
603
|
+
| `isAtBottom` | Readonly boolean indicating whether the user is near the bottom. | `chat?.isAtBottom` |
|
|
604
|
+
| `scrollTo(position, options?)` | Scrolls to a pixel offset. | `chat?.scrollTo(200, { behavior: 'smooth' })` |
|
|
605
|
+
| `scrollToIndex(index, options?)` | Scrolls to a zero-based message index. | `chat?.scrollToIndex(10, { behavior: 'smooth' })` |
|
|
606
|
+
| `scrollToTop(behavior?)` | Scrolls to the oldest loaded message. | `chat?.scrollToTop()` |
|
|
607
|
+
| `scrollToBottom(behavior?)` | Scrolls to the newest loaded message. | `chat?.scrollToBottom('smooth')` |
|
|
512
608
|
|
|
513
609
|
### ShortMediaFeed
|
|
514
610
|
|
|
@@ -576,23 +672,39 @@ keep persistent playback state in the parent.
|
|
|
576
672
|
| `loading` | `boolean` | `false` | Prevents duplicate `load-more` requests. |
|
|
577
673
|
| `loadMoreThreshold` | `number` | `2` | Remaining item count that triggers `load-more`. |
|
|
578
674
|
|
|
579
|
-
#### Slots
|
|
675
|
+
#### Slots
|
|
676
|
+
|
|
677
|
+
| Slot | Scope | Description |
|
|
678
|
+
| --- | --- | --- |
|
|
679
|
+
| `default` | `{ item, index, active }` | Renders a mounted item; `active` marks the snapped item. |
|
|
680
|
+
| `empty` | none | Rendered when `items` is empty. |
|
|
681
|
+
|
|
682
|
+
#### Events
|
|
580
683
|
|
|
581
|
-
|
|
|
684
|
+
| Event | Payload | When it fires | Usage |
|
|
582
685
|
| --- | --- | --- | --- |
|
|
583
|
-
|
|
|
584
|
-
|
|
|
585
|
-
|
|
|
586
|
-
|
|
|
587
|
-
|
|
|
588
|
-
| Event | `reach-end` | none | Navigation changes to the last item. |
|
|
589
|
-
| Event | `load-more` | none | Remaining items reach `loadMoreThreshold`. |
|
|
686
|
+
| `update:activeIndex` | `number` | Supports `v-model:active-index`. | `v-model:active-index="activeIndex"` |
|
|
687
|
+
| `change` | `{ index, item }` | The active item changes. | `@change="onChange"` |
|
|
688
|
+
| `reach-start` | none | Navigation changes to the first item. | `@reach-start="onReachStart"` |
|
|
689
|
+
| `reach-end` | none | Navigation changes to the last item. | `@reach-end="onReachEnd"` |
|
|
690
|
+
| `load-more` | none | Remaining items reach `loadMoreThreshold`. | `@load-more="loadMore"` |
|
|
590
691
|
|
|
591
|
-
#### Exposed API
|
|
692
|
+
#### Exposed API
|
|
693
|
+
|
|
694
|
+
| Method / state | Description | Template example |
|
|
695
|
+
| --- | --- | --- |
|
|
696
|
+
| `scrollToIndex(index, options?)` | Scrolls to a zero-based item index using standard `ScrollToOptions`. | `feed?.scrollToIndex(10, { behavior: 'smooth' })` |
|
|
592
697
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
698
|
+
#### Keyboard controls
|
|
699
|
+
|
|
700
|
+
These keys work while the viewport is focused:
|
|
701
|
+
|
|
702
|
+
| Key | Action |
|
|
703
|
+
| --- | --- |
|
|
704
|
+
| `ArrowDown`, `PageDown` | Moves to the next item. |
|
|
705
|
+
| `ArrowUp`, `PageUp` | Moves to the previous item. |
|
|
706
|
+
| `Home` | Moves to the first position. |
|
|
707
|
+
| `End` | Moves to the last item. |
|
|
596
708
|
|
|
597
709
|
For an unbounded feed, page data with `load-more` and keep a reasonable item
|
|
598
710
|
window in the parent. Virtualization limits mounted DOM and media elements,
|
|
@@ -676,31 +788,74 @@ five slides are normally mounted in the middle of the collection.
|
|
|
676
788
|
| `autoplayDelay` | `number` | `3000` | Delay in milliseconds between automatic transitions; values below `1` are clamped and non-finite values use the default. |
|
|
677
789
|
| `autoplayLoop` | `boolean` | `true` | Returns to the first slide when reaching the end. |
|
|
678
790
|
| `pauseOnHover` | `boolean` | `true` | Pauses autoplay when the carousel is hovered. |
|
|
791
|
+
| `responsive` | `ResponsiveBreakpoint[]` | `undefined` | Overrides slide count, scroll step, and gap at container-width breakpoints. |
|
|
679
792
|
|
|
680
|
-
####
|
|
793
|
+
#### Responsive configuration
|
|
681
794
|
|
|
682
|
-
|
|
795
|
+
The largest `breakpoint` not exceeding the carousel container width is used.
|
|
796
|
+
If no breakpoint matches, the base props apply. Omitted fields fall back to
|
|
797
|
+
the base props, not to another breakpoint.
|
|
798
|
+
|
|
799
|
+
| Field | Type | Default | Description |
|
|
683
800
|
| --- | --- | --- | --- |
|
|
684
|
-
|
|
|
685
|
-
|
|
|
686
|
-
|
|
|
687
|
-
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
801
|
+
| `breakpoint` | `number` | required | Minimum container width in pixels. |
|
|
802
|
+
| `slidesPerView` | `number` | `slidesPerView` | Slides visible at this breakpoint. |
|
|
803
|
+
| `slidesToScroll` | `number` | `slidesToScroll` | Slides moved by `next()` and `previous()`. |
|
|
804
|
+
| `gap` | `number` | `gap` | Space between slides in pixels. |
|
|
805
|
+
|
|
806
|
+
```vue
|
|
807
|
+
<VirtualCarousel
|
|
808
|
+
:items="products"
|
|
809
|
+
:slides-per-view="1"
|
|
810
|
+
:responsive="[
|
|
811
|
+
{ breakpoint: 640, slidesPerView: 2, gap: 12 },
|
|
812
|
+
{ breakpoint: 1024, slidesPerView: 3, slidesToScroll: 2, gap: 16 },
|
|
813
|
+
]"
|
|
814
|
+
>
|
|
815
|
+
<template #default="{ item }"><ProductCard :product="item" /></template>
|
|
816
|
+
</VirtualCarousel>
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
#### Slots
|
|
820
|
+
|
|
821
|
+
| Slot | Scope | Description |
|
|
822
|
+
| --- | --- | --- |
|
|
823
|
+
| `default` | `{ item, index, active, visible }` | Renders each mounted slide. |
|
|
824
|
+
| `empty` | none | Rendered when `items` is empty. |
|
|
825
|
+
|
|
826
|
+
#### Events
|
|
827
|
+
|
|
828
|
+
| Event | Payload | When it fires | Usage |
|
|
829
|
+
| --- | --- | --- | --- |
|
|
830
|
+
| `update:activeIndex` | `number` | Supports `v-model:active-index`. | `v-model:active-index="activeIndex"` |
|
|
831
|
+
| `change` | `{ index, item }` | The first visible slide changes. | `@change="onChange"` |
|
|
832
|
+
| `reach-start` | none | Navigation changes to the first position. | `@reach-start="onReachStart"` |
|
|
833
|
+
| `reach-end` | none | Navigation changes to the final valid position. | `@reach-end="onReachEnd"` |
|
|
834
|
+
| `load-more` | none | Remaining slides reach `loadMoreThreshold`. | `@load-more="loadMore"` |
|
|
835
|
+
|
|
836
|
+
#### Exposed API
|
|
837
|
+
|
|
838
|
+
| Method / state | Description | Template example |
|
|
839
|
+
| --- | --- | --- |
|
|
840
|
+
| `next(behavior?)` | Advances by `slidesToScroll`; defaults to smooth scrolling. | `carousel?.next()` |
|
|
841
|
+
| `previous(behavior?)` | Moves back by `slidesToScroll`. | `carousel?.previous()` |
|
|
842
|
+
| `scrollToIndex(index, options?)` | Makes the index the first visible slide. | `carousel?.scrollToIndex(10, { behavior: 'smooth' })` |
|
|
843
|
+
| `startAutoplay()` | Starts or restarts the timer when `autoplay` is true and items are available. | `carousel?.startAutoplay()` |
|
|
844
|
+
| `stopAutoplay()` | Clears the current timer; set `autoplay=false` to keep automatic progression disabled. | `carousel?.stopAutoplay()` |
|
|
691
845
|
|
|
692
|
-
####
|
|
846
|
+
#### Keyboard controls
|
|
693
847
|
|
|
694
|
-
|
|
848
|
+
These keys work while the viewport is focused:
|
|
849
|
+
|
|
850
|
+
| Key | Action |
|
|
695
851
|
| --- | --- |
|
|
696
|
-
| `
|
|
697
|
-
| `
|
|
698
|
-
| `
|
|
699
|
-
| `
|
|
700
|
-
| `stopAutoplay()` | Stops automatic slide progression. |
|
|
852
|
+
| `ArrowRight`, `PageDown` | Advances by `slidesToScroll`, equivalent to `next()`. |
|
|
853
|
+
| `ArrowLeft`, `PageUp` | Moves back by `slidesToScroll`, equivalent to `previous()`. |
|
|
854
|
+
| `Home` | Moves to the first position. |
|
|
855
|
+
| `End` | Moves to the final valid first-slide position. |
|
|
701
856
|
|
|
702
|
-
|
|
703
|
-
|
|
857
|
+
A long-distance smooth jump becomes immediate so virtualized gaps are not
|
|
858
|
+
shown.
|
|
704
859
|
|
|
705
860
|
#### Autoplay example
|
|
706
861
|
|
|
@@ -733,17 +888,134 @@ const products = ref(loadProducts())
|
|
|
733
888
|
</template>
|
|
734
889
|
```
|
|
735
890
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
`autoplay`
|
|
739
|
-
`
|
|
891
|
+
| Action / condition | Result |
|
|
892
|
+
| --- | --- |
|
|
893
|
+
| `autoplay=true` | Starts automatically, including when an initially empty `items` collection receives data. |
|
|
894
|
+
| `stopAutoplay()` | Clears the current timer. Later navigation or pointer events may restart it when `autoplay` remains true. |
|
|
895
|
+
| `startAutoplay()` | Starts or restarts the timer when `autoplay` is true and items are available. |
|
|
896
|
+
| `autoplay=false` | Disables automatic progression until the prop is enabled again. |
|
|
897
|
+
| `autoplayLoop` | At the end, true returns to the start; false stops the timer. |
|
|
898
|
+
| `pauseOnHover` | When true, hovering pauses the timer; leaving resumes it if autoplay is enabled. |
|
|
899
|
+
| Change autoplay settings | `autoplayDelay`, `autoplayLoop`, and `pauseOnHover` take effect at runtime. |
|
|
900
|
+
|
|
901
|
+
### SkeletonLoader
|
|
902
|
+
|
|
903
|
+
`SkeletonLoader` renders a placeholder with a shimmer animation. Use it on its
|
|
904
|
+
own or inside a list's `loading` slot. It stays visible while mounted; control
|
|
905
|
+
standalone visibility with `v-if`, or let the list's `loading` prop control the
|
|
906
|
+
slot.
|
|
907
|
+
|
|
908
|
+
#### Example
|
|
909
|
+
|
|
910
|
+
Import the library stylesheet once, as shown below or in your application entry
|
|
911
|
+
point. Set `loading` to false when your data request finishes.
|
|
912
|
+
|
|
913
|
+
```vue
|
|
914
|
+
<script setup lang="ts">
|
|
915
|
+
import { ref } from 'vue'
|
|
916
|
+
import { SkeletonLoader } from 'vue-virtual-flow'
|
|
917
|
+
import 'vue-virtual-flow/style.css'
|
|
918
|
+
|
|
919
|
+
const loading = ref(true)
|
|
920
|
+
</script>
|
|
921
|
+
|
|
922
|
+
<template>
|
|
923
|
+
<SkeletonLoader
|
|
924
|
+
v-if="loading"
|
|
925
|
+
width="100%"
|
|
926
|
+
:height="120"
|
|
927
|
+
aria-label="Loading content…"
|
|
928
|
+
/>
|
|
929
|
+
<p v-else>Content is ready.</p>
|
|
930
|
+
</template>
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
#### Props
|
|
934
|
+
|
|
935
|
+
| Prop | Type | Default | Description |
|
|
936
|
+
| --- | --- | --- | --- |
|
|
937
|
+
| `width` | `number \| string` | `'100%'` | Width in pixels for numbers, or a CSS length such as `'60%'` or `'12rem'`. |
|
|
938
|
+
| `height` | `number \| string` | `16` | Height in pixels for numbers, or a CSS length. |
|
|
939
|
+
| `variant` | `'rectangle' \| 'rounded' \| 'circle'` | `'rounded'` | Placeholder shape. Use equal width and height for a circle. |
|
|
940
|
+
| `animated` | `boolean` | `true` | Enables the shimmer animation. |
|
|
941
|
+
| `ariaLabel` | `string` | `'Loading…'` | Accessible label for the placeholder's `role="status"`. |
|
|
942
|
+
|
|
943
|
+
```vue
|
|
944
|
+
<SkeletonLoader variant="circle" :width="48" :height="48" />
|
|
945
|
+
<SkeletonLoader variant="rectangle" :height="180" />
|
|
946
|
+
<SkeletonLoader width="60%" :height="20" :animated="false" />
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
The animation automatically stops when the user prefers reduced motion. When
|
|
950
|
+
composing several decorative skeletons under a shared loading message, pass
|
|
951
|
+
`aria-hidden="true"` to each skeleton to avoid repeated accessible labels.
|
|
952
|
+
|
|
953
|
+
#### Slots, events, and exposed API
|
|
954
|
+
|
|
955
|
+
| API | Support |
|
|
956
|
+
| --- | --- |
|
|
957
|
+
| Slots | None. The component renders a single placeholder. |
|
|
958
|
+
| Events | No custom events. |
|
|
959
|
+
| Exposed methods | None. Control the component with props and `v-if`. |
|
|
960
|
+
|
|
961
|
+
#### Actions
|
|
962
|
+
|
|
963
|
+
| Action | Usage |
|
|
964
|
+
| --- | --- |
|
|
965
|
+
| Show or hide | `<SkeletonLoader v-if="loading" />` |
|
|
966
|
+
| Use a circle | `<SkeletonLoader variant="circle" :width="48" :height="48" />` |
|
|
967
|
+
| Disable shimmer | `<SkeletonLoader :animated="false" />` |
|
|
968
|
+
| Set the accessible label | `aria-label="Loading…"` |
|
|
969
|
+
| Hide a decorative skeleton from screen readers | `aria-hidden="true"` |
|
|
970
|
+
|
|
971
|
+
#### Use in a loading slot
|
|
972
|
+
|
|
973
|
+
Import `SkeletonLoader` alongside `VirtualList`. With your existing `items` and
|
|
974
|
+
`loading` state, replace the loading message with a skeleton:
|
|
975
|
+
|
|
976
|
+
```vue
|
|
977
|
+
<VirtualList :items="items" :loading="loading" :loading-item-size="64">
|
|
978
|
+
<template #default="{ item }"><FeedCard :item="item" /></template>
|
|
979
|
+
<template #loading>
|
|
980
|
+
<SkeletonLoader :height="40" aria-label="Loading the next page…" />
|
|
981
|
+
</template>
|
|
982
|
+
</VirtualList>
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
| Setting | Usage |
|
|
986
|
+
| --- | --- |
|
|
987
|
+
| Components with a `loading` slot | `VirtualList`, `DynamicVirtualScroll`, `VirtualScroll`, `WindowDynamicVirtualScroll`, `WindowGirdVirtualScroll`. |
|
|
988
|
+
| List `loading` prop | Set to true to show the slot; false to hide it. |
|
|
989
|
+
| Skeleton `height` prop | Sets the placeholder height, for example `40` pixels. |
|
|
990
|
+
| List `loadingItemSize` prop | Reserve space for the skeleton plus the row's vertical padding. Example: `40px + 1.5rem = 64px` at a 16px root font size. |
|
|
991
|
+
|
|
992
|
+
#### Customize appearance
|
|
993
|
+
|
|
994
|
+
Set these CSS custom properties on the skeleton or a parent element:
|
|
995
|
+
|
|
996
|
+
| CSS custom property | Default | Description |
|
|
997
|
+
| --- | --- | --- |
|
|
998
|
+
| `--skeleton-background` | `#e2e8f0` | Base background color. |
|
|
999
|
+
| `--skeleton-highlight` | `rgb(255 255 255 / 60%)` | Shimmer highlight color. |
|
|
1000
|
+
| `--skeleton-radius` | `8px` | Corner radius for the `rounded` variant. |
|
|
1001
|
+
|
|
1002
|
+
```vue
|
|
1003
|
+
<SkeletonLoader
|
|
1004
|
+
:height="80"
|
|
1005
|
+
style="--skeleton-background: #334155; --skeleton-highlight: #475569; --skeleton-radius: 12px"
|
|
1006
|
+
/>
|
|
1007
|
+
```
|
|
740
1008
|
|
|
741
1009
|
## Shared recipes
|
|
742
1010
|
|
|
743
1011
|
### Viewport height
|
|
744
1012
|
|
|
745
|
-
|
|
746
|
-
|
|
1013
|
+
| Value / component | Behavior |
|
|
1014
|
+
| --- | --- |
|
|
1015
|
+
| `:height="400"` | Numeric values become pixels. |
|
|
1016
|
+
| `height="60vh"` | CSS height strings are passed through. |
|
|
1017
|
+
| `height="fill"` / `height="100%"` | Uses the parent height. The parent must have an explicit height; development builds warn if this resolves to `0px`. |
|
|
1018
|
+
| `WindowDynamicVirtualScroll`, `WindowGirdVirtualScroll` | Use the browser window and do not accept a `height` prop. |
|
|
747
1019
|
|
|
748
1020
|
```vue
|
|
749
1021
|
<div class="list-container">
|
|
@@ -760,16 +1032,14 @@ converted to pixels. `height="fill"` is shorthand for `height="100%"`:
|
|
|
760
1032
|
}
|
|
761
1033
|
```
|
|
762
1034
|
|
|
763
|
-
A percentage height only works when its parent has an explicit height.
|
|
764
|
-
Development builds warn when it resolves to `0px`.
|
|
765
|
-
`WindowDynamicVirtualScroll` and `WindowGirdVirtualScroll` use the browser
|
|
766
|
-
window and have no `height` prop.
|
|
767
|
-
|
|
768
1035
|
### Stable item keys
|
|
769
1036
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
1037
|
+
| Configuration / data | Key selection |
|
|
1038
|
+
| --- | --- |
|
|
1039
|
+
| `:item-key="(item, index) => …"` | Uses the function result. |
|
|
1040
|
+
| `item-key="uuid"` | Uses a valid `item.uuid`; otherwise tries `id`, `key`, then the index. |
|
|
1041
|
+
| Object without an explicit `itemKey` | Tries `item.id`, then `item.key`, then the index. |
|
|
1042
|
+
| Primitive without a key function | Falls back to the index. |
|
|
773
1043
|
|
|
774
1044
|
```vue
|
|
775
1045
|
<VirtualList :items="users" item-key="uuid" />
|
|
@@ -786,8 +1056,11 @@ variable-height item.
|
|
|
786
1056
|
|
|
787
1057
|
### Load more
|
|
788
1058
|
|
|
789
|
-
|
|
790
|
-
|
|
1059
|
+
| Component | `loadMoreThreshold` | Event |
|
|
1060
|
+
| --- | --- | --- |
|
|
1061
|
+
| `VirtualList`, `DynamicVirtualScroll`, `VirtualScroll`, `WindowDynamicVirtualScroll`, `WindowGirdVirtualScroll` | Distance to the end in pixels. | `load-more` |
|
|
1062
|
+
| `ShortMediaFeed` | Number of items after the active item. | `load-more` |
|
|
1063
|
+
| `VirtualCarousel` | Number of slides after the visible group. | `load-more` |
|
|
791
1064
|
|
|
792
1065
|
```vue
|
|
793
1066
|
<script setup lang="ts">
|
|
@@ -819,15 +1092,20 @@ async function loadMore() {
|
|
|
819
1092
|
</template>
|
|
820
1093
|
```
|
|
821
1094
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1095
|
+
| Step / condition | Action / result |
|
|
1096
|
+
| --- | --- |
|
|
1097
|
+
| Receive `load-more` | Start the next request only when `loading` is false and `hasMore` is true. |
|
|
1098
|
+
| Request in progress | Keep `loading=true` for the entire request. |
|
|
1099
|
+
| Receive the next page | Append its items. The event normally fires once per current item count; increasing that count permits another request. |
|
|
1100
|
+
| Request finished | Set `loading=false`; use `finally` when requests may fail. |
|
|
1101
|
+
| Final page | Set `hasMore=false` to stop further requests. |
|
|
825
1102
|
|
|
826
1103
|
### Pull to refresh
|
|
827
1104
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
`
|
|
1105
|
+
| Component | Gesture starts at |
|
|
1106
|
+
| --- | --- |
|
|
1107
|
+
| `VirtualList`, `DynamicVirtualScroll`, `VirtualScroll` | The top of the component container. |
|
|
1108
|
+
| `WindowDynamicVirtualScroll`, `WindowGirdVirtualScroll` | `window.scrollY = 0` |
|
|
831
1109
|
|
|
832
1110
|
```vue
|
|
833
1111
|
<VirtualList
|
|
@@ -844,21 +1122,51 @@ Pull-to-refresh is available on `VirtualList`, `DynamicVirtualScroll`,
|
|
|
844
1122
|
</VirtualList>
|
|
845
1123
|
```
|
|
846
1124
|
|
|
847
|
-
|
|
848
|
-
|
|
1125
|
+
| Step / state | Action / result |
|
|
1126
|
+
| --- | --- |
|
|
1127
|
+
| Enable the gesture | `pull-to-refresh` |
|
|
1128
|
+
| Pull down with one finger at the top | Updates the `refresh` slot scope: `pullDistance`, `progress`, and `refreshing`. |
|
|
1129
|
+
| Release after reaching `pullRefreshThreshold` | Emits `refresh` without a payload. |
|
|
1130
|
+
| Handle `refresh` | Set `refreshing=true` while fetching fresh data to keep the indicator open. |
|
|
1131
|
+
| Finish refreshing | Set `refreshing=false` to close the indicator. |
|
|
849
1132
|
|
|
850
1133
|
## TypeScript exports
|
|
851
1134
|
|
|
1135
|
+
| Type | Purpose |
|
|
1136
|
+
| --- | --- |
|
|
1137
|
+
| `ChatVirtualScrollExpose` | Chat ref API and `isAtBottom` state. |
|
|
1138
|
+
| `ChatVirtualScrollProps<T>` | Chat component props. |
|
|
1139
|
+
| `DynamicVirtualScrollProps<T>` | Variable-height list props. |
|
|
1140
|
+
| `ItemKey<T>` | Property name or function used to identify an item. |
|
|
1141
|
+
| `ResponsiveBreakpoint` | Carousel container breakpoint and optional layout overrides. |
|
|
1142
|
+
| `ScrollAlignment` | `'start'`, `'center'`, or `'end'` for list index scrolling. |
|
|
1143
|
+
| `ShortMediaFeedChangeEvent<T>` | Feed `change` payload: `{ index, item }`. |
|
|
1144
|
+
| `ShortMediaFeedExpose` | Feed ref API. |
|
|
1145
|
+
| `ShortMediaFeedProps<T>` | Feed props. |
|
|
1146
|
+
| `SkeletonLoaderProps` | Skeleton size, shape, animation, and accessible label. |
|
|
1147
|
+
| `VirtualCarouselChangeEvent<T>` | Carousel `change` payload: `{ index, item }`. |
|
|
1148
|
+
| `VirtualCarouselExpose` | Carousel navigation and autoplay ref API. |
|
|
1149
|
+
| `VirtualCarouselProps<T>` | Carousel props, including responsive settings. |
|
|
1150
|
+
| `VirtualListExpose` | Alias of `VirtualScrollExpose`. |
|
|
1151
|
+
| `VirtualListProps<T>` | Alias of `DynamicVirtualScrollProps<T>`. |
|
|
1152
|
+
| `VirtualScrollEvent` | Scroll payload: `{ scrollTop, startIndex, endIndex }`; `endIndex` is exclusive. |
|
|
1153
|
+
| `VirtualScrollExpose` | Shared ref API for fixed, dynamic, and window lists/grids. |
|
|
1154
|
+
| `VirtualScrollProps<T>` | Fixed-height list props. |
|
|
1155
|
+
| `WindowDynamicVirtualScrollProps<T>` | Window-based variable-height list props. |
|
|
1156
|
+
| `WindowGirdVirtualScrollProps<T>` | Window-based fixed-row grid props. |
|
|
1157
|
+
|
|
852
1158
|
```ts
|
|
853
1159
|
import type {
|
|
854
1160
|
ChatVirtualScrollExpose,
|
|
855
1161
|
ChatVirtualScrollProps,
|
|
856
1162
|
DynamicVirtualScrollProps,
|
|
857
1163
|
ItemKey,
|
|
1164
|
+
ResponsiveBreakpoint,
|
|
858
1165
|
ScrollAlignment,
|
|
859
1166
|
ShortMediaFeedChangeEvent,
|
|
860
1167
|
ShortMediaFeedExpose,
|
|
861
1168
|
ShortMediaFeedProps,
|
|
1169
|
+
SkeletonLoaderProps,
|
|
862
1170
|
VirtualCarouselChangeEvent,
|
|
863
1171
|
VirtualCarouselExpose,
|
|
864
1172
|
VirtualCarouselProps,
|
|
@@ -874,6 +1182,22 @@ import type {
|
|
|
874
1182
|
|
|
875
1183
|
## Development and release
|
|
876
1184
|
|
|
1185
|
+
| Command / lifecycle | Action |
|
|
1186
|
+
| --- | --- |
|
|
1187
|
+
| `bun install` | Install dependencies. |
|
|
1188
|
+
| `bunx playwright install chromium firefox webkit` | Install browsers for smoke tests. |
|
|
1189
|
+
| `bun run dev` | Start the playground. |
|
|
1190
|
+
| `bun run typecheck` | Check TypeScript and Vue types. |
|
|
1191
|
+
| `bun run test` | Run unit tests. |
|
|
1192
|
+
| `bun run test:watch` | Run unit tests in watch mode. |
|
|
1193
|
+
| `bun run build` | Typecheck and build the package with type declarations. |
|
|
1194
|
+
| `bun run typecheck:package` | Check ESM and CommonJS consumer types after building. |
|
|
1195
|
+
| `bun run lint:package` | Validate package exports and type packaging after building. |
|
|
1196
|
+
| `bun run test:browser` | Run Playwright browser tests. |
|
|
1197
|
+
| `bun run check` | Run all checks: unit tests, build, consumer types, package lint, and browser tests. |
|
|
1198
|
+
| `npm pack --dry-run` | Inspect the files that will be published. |
|
|
1199
|
+
| `prepublishOnly` | Automatically runs `bun run check` before publishing. |
|
|
1200
|
+
|
|
877
1201
|
```bash
|
|
878
1202
|
bun install
|
|
879
1203
|
bunx playwright install chromium firefox webkit
|