@wyxos/vibe 3.1.34 → 4.0.0
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 +531 -308
- package/lib/components/CardRegion.vue.d.ts +15 -0
- package/lib/components/GalleryFooter.vue.d.ts +17 -0
- package/lib/components/GalleryScrollbar.vue.d.ts +9 -0
- package/lib/components/MasonryFeed.vue.d.ts +24 -0
- package/lib/components/MediaCard.vue.d.ts +35 -0
- package/lib/components/MediaControls.vue.d.ts +22 -0
- package/lib/components/ReelAutoAdvanceProgress.vue.d.ts +11 -0
- package/lib/components/ReelFeed.vue.d.ts +28 -0
- package/lib/components/ReelLayout.vue.d.ts +28 -0
- package/lib/components/VibeSurface.vue.d.ts +35 -0
- package/lib/core/autoScroll.d.ts +26 -0
- package/lib/core/autofill.d.ts +35 -0
- package/lib/core/backendAutofill.d.ts +5 -0
- package/lib/core/backendFill.d.ts +5 -0
- package/lib/core/createVibe.d.ts +2 -0
- package/lib/core/feed.d.ts +40 -0
- package/lib/core/fill.d.ts +29 -0
- package/lib/core/fillController.d.ts +28 -0
- package/lib/core/initialAutofill.d.ts +12 -0
- package/lib/core/initialRuntimeState.d.ts +3 -0
- package/lib/core/masonry.d.ts +37 -0
- package/lib/core/mediaAsset.d.ts +5 -0
- package/lib/core/mediaPreview.d.ts +3 -0
- package/lib/core/mediaType.d.ts +1 -0
- package/lib/core/options.d.ts +3 -0
- package/lib/core/page.d.ts +3 -0
- package/lib/core/reelAutoAdvance.d.ts +5 -0
- package/lib/core/reelInfoSheet.d.ts +3 -0
- package/lib/core/requestDelay.d.ts +24 -0
- package/lib/core/responsiveLayout.d.ts +13 -0
- package/lib/core/runtime.d.ts +23 -0
- package/lib/core/scrollbar.d.ts +15 -0
- package/lib/core/vibeRouting.d.ts +11 -0
- package/lib/index.cjs +1 -1
- package/lib/index.d.ts +3 -73
- package/lib/index.js +2586 -4949
- package/lib/style.css +1 -2
- package/lib/types.d.ts +323 -0
- package/package.json +13 -7
- package/lib/components/FullscreenFooter.vue.d.ts +0 -22
- package/lib/components/FullscreenForwardFillPlaceholder.vue.d.ts +0 -29
- package/lib/components/FullscreenHeader.vue.d.ts +0 -25
- package/lib/components/FullscreenMediaBar.vue.d.ts +0 -26
- package/lib/components/FullscreenSurface.vue.d.ts +0 -83
- package/lib/components/Layout.vue.d.ts +0 -48
- package/lib/components/ListCard.vue.d.ts +0 -44
- package/lib/components/ListSurface.vue.d.ts +0 -87
- package/lib/components/SurfaceEmptyState.vue.d.ts +0 -21
- package/lib/components/viewer-core/assetErrors.d.ts +0 -30
- package/lib/components/viewer-core/assetState.d.ts +0 -14
- package/lib/components/viewer-core/autoBuckets.d.ts +0 -24
- package/lib/components/viewer-core/autoResolveBucketFactory.d.ts +0 -15
- package/lib/components/viewer-core/autoResolveCursors.d.ts +0 -15
- package/lib/components/viewer-core/autoResolveFillUntil.d.ts +0 -46
- package/lib/components/viewer-core/autoResolveHelpers.d.ts +0 -20
- package/lib/components/viewer-core/autoResolveLoading.d.ts +0 -44
- package/lib/components/viewer-core/autoResolveState.d.ts +0 -72
- package/lib/components/viewer-core/dom.d.ts +0 -2
- package/lib/components/viewer-core/dominantImageTone.d.ts +0 -6
- package/lib/components/viewer-core/emptyVisiblePrefetch.d.ts +0 -18
- package/lib/components/viewer-core/fillDelay.d.ts +0 -9
- package/lib/components/viewer-core/fillProgress.d.ts +0 -12
- package/lib/components/viewer-core/format.d.ts +0 -1
- package/lib/components/viewer-core/fullscreenPreviews.d.ts +0 -8
- package/lib/components/viewer-core/itemIdentity.d.ts +0 -6
- package/lib/components/viewer-core/listCardAsset.d.ts +0 -4
- package/lib/components/viewer-core/listPreview.d.ts +0 -10
- package/lib/components/viewer-core/loadError.d.ts +0 -5
- package/lib/components/viewer-core/masonryBoundaryLock.d.ts +0 -5
- package/lib/components/viewer-core/masonryItemMutation.d.ts +0 -35
- package/lib/components/viewer-core/masonryLayout.d.ts +0 -64
- package/lib/components/viewer-core/masonryLayoutState.d.ts +0 -22
- package/lib/components/viewer-core/masonryPendingAppend.d.ts +0 -19
- package/lib/components/viewer-core/masonryScrollBehavior.d.ts +0 -37
- package/lib/components/viewer-core/masonryViewport.d.ts +0 -7
- package/lib/components/viewer-core/media.d.ts +0 -4
- package/lib/components/viewer-core/mediaPlayback.d.ts +0 -1
- package/lib/components/viewer-core/nativeVideoFullscreen.d.ts +0 -2
- package/lib/components/viewer-core/removalState.d.ts +0 -71
- package/lib/components/viewer-core/slotContent.d.ts +0 -2
- package/lib/components/viewer-core/surfaceSlots.d.ts +0 -33
- package/lib/components/viewer-core/surfaceStatus.d.ts +0 -18
- package/lib/components/viewer-core/theme.d.ts +0 -3
- package/lib/components/viewer-core/useActivation.d.ts +0 -8
- package/lib/components/viewer-core/useAssetLoadQueue.d.ts +0 -17
- package/lib/components/viewer-core/useAutoResolveSource.d.ts +0 -67
- package/lib/components/viewer-core/useController.d.ts +0 -98
- package/lib/components/viewer-core/useDataSource.d.ts +0 -98
- package/lib/components/viewer-core/useEdgeBoundary.d.ts +0 -18
- package/lib/components/viewer-core/useFullscreen.d.ts +0 -1
- package/lib/components/viewer-core/useFullscreenAssetEvents.d.ts +0 -24
- package/lib/components/viewer-core/useFullscreenDominantTone.d.ts +0 -12
- package/lib/components/viewer-core/useFullscreenPreloadController.d.ts +0 -18
- package/lib/components/viewer-core/useFullscreenSurfaceMedia.d.ts +0 -32
- package/lib/components/viewer-core/useListCardHealthCheck.d.ts +0 -20
- package/lib/components/viewer-core/useMasonryList.d.ts +0 -85
- package/lib/components/viewer-core/useMasonryMotion.d.ts +0 -76
- package/lib/components/viewer-core/useMedia.d.ts +0 -44
- package/lib/components/viewer-core/useSurfaceEmptyState.d.ts +0 -15
- package/lib/components/viewer-core/useVideoFullscreen.d.ts +0 -9
- package/lib/components/viewer-core/useViewer.d.ts +0 -78
- package/lib/components/viewer-core/virtualization.d.ts +0 -18
- package/lib/components/viewer.d.ts +0 -23
package/README.md
CHANGED
|
@@ -1,420 +1,643 @@
|
|
|
1
1
|
# Vibe
|
|
2
2
|
|
|
3
|
-
Vibe is an
|
|
3
|
+
Vibe is an initializable Vue 3 media feed with virtualized masonry and reel layouts.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @wyxos/vibe
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
Import the packaged styles once in the consuming application:
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
```ts
|
|
14
|
+
import '@wyxos/vibe/style.css'
|
|
15
|
+
```
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
## Remote feed
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
```ts
|
|
20
|
+
import { createVibe } from '@wyxos/vibe'
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
const vibe = createVibe({
|
|
23
|
+
target: '#gallery',
|
|
24
|
+
layout: 'responsive',
|
|
25
|
+
loadPage: async ({ cursor, signal }) => {
|
|
26
|
+
const response = await fetch(`/api/media?cursor=${cursor ?? ''}`, {
|
|
27
|
+
signal,
|
|
28
|
+
})
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
return response.json()
|
|
31
|
+
},
|
|
32
|
+
})
|
|
20
33
|
|
|
21
|
-
|
|
34
|
+
await vibe.mount()
|
|
35
|
+
```
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- built-in remove, restore, undo, retry, and asset-failure handling
|
|
28
|
-
- a small customization surface through slots instead of a headless rendering API
|
|
37
|
+
The target element must have a usable height. Vibe owns scrolling inside that target.
|
|
38
|
+
In responsive layout, Vibe observes the target and uses reels on phones while
|
|
39
|
+
keeping tablets and desktops in masonry. Use `masonry` or `reel` to force a
|
|
40
|
+
renderer instead.
|
|
29
41
|
|
|
30
|
-
|
|
42
|
+
Phone reels load `preview.src`. Reels on tablets and desktops load the full
|
|
43
|
+
`src`, including a reel forced with `layout: 'reel'` and a reel opened from a
|
|
44
|
+
masonry card.
|
|
31
45
|
|
|
32
|
-
|
|
46
|
+
`loadPage` receives `cursor: null` for the initial request. It returns normalized Vibe items, the next opaque cursor, and an optional total:
|
|
33
47
|
|
|
34
|
-
```
|
|
35
|
-
|
|
48
|
+
```ts
|
|
49
|
+
{
|
|
50
|
+
items: VibeItem[]
|
|
51
|
+
next: string | number | null
|
|
52
|
+
total?: number
|
|
53
|
+
}
|
|
36
54
|
```
|
|
37
55
|
|
|
56
|
+
## Static or preloaded feed
|
|
57
|
+
|
|
38
58
|
```ts
|
|
39
|
-
|
|
59
|
+
const vibe = createVibe({
|
|
60
|
+
target: document.querySelector('#gallery')!,
|
|
61
|
+
layout: 'reel',
|
|
62
|
+
initialPage: {
|
|
63
|
+
items,
|
|
64
|
+
next: null,
|
|
65
|
+
total: items.length,
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
await vibe.mount()
|
|
40
70
|
```
|
|
41
71
|
|
|
42
|
-
|
|
72
|
+
Provide both `initialPage` and `loadPage` when preloaded items can continue to another cursor.
|
|
43
73
|
|
|
44
|
-
|
|
74
|
+
## Custom card chrome
|
|
45
75
|
|
|
46
|
-
|
|
76
|
+
Use `cardHeader` and `cardFooter` to render application-owned regions above and
|
|
77
|
+
below media:
|
|
47
78
|
|
|
48
79
|
```ts
|
|
49
|
-
import
|
|
50
|
-
import
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
80
|
+
import MediaInfo from './MediaInfo.vue'
|
|
81
|
+
import MediaReactions from './MediaReactions.vue'
|
|
82
|
+
|
|
83
|
+
const vibe = createVibe({
|
|
84
|
+
target: '#gallery',
|
|
85
|
+
layout: 'responsive',
|
|
86
|
+
loadPage,
|
|
87
|
+
cardHeader: {
|
|
88
|
+
component: MediaInfo,
|
|
89
|
+
height: 40,
|
|
90
|
+
},
|
|
91
|
+
cardFooter: {
|
|
92
|
+
component: MediaReactions,
|
|
93
|
+
height: 48,
|
|
94
|
+
},
|
|
95
|
+
})
|
|
58
96
|
```
|
|
59
97
|
|
|
60
|
-
|
|
98
|
+
The heights are CSS pixels and let Vibe include both regions in masonry before
|
|
99
|
+
virtualized cards mount. In reel layout, one header and footer remain stationary
|
|
100
|
+
while only the media viewport scrolls between posts. Each component controls its
|
|
101
|
+
own content and alignment, and receives `item`, `layout`, `mediaSource`, zero-based `index`,
|
|
102
|
+
`loadedCount`, the active `mediaItem`, its zero-based `mediaIndex`, its inclusive
|
|
103
|
+
`mediaCount`, and the optional remote `total`. Import the
|
|
104
|
+
`VibeCardRegionProps` type for typed Vue props. Interacting with these regions
|
|
105
|
+
does not activate the underlying media.
|
|
61
106
|
|
|
62
|
-
|
|
107
|
+
When a feed item has entries in `item.items`, Vibe treats the parent media as
|
|
108
|
+
position one and the nested entries as the remaining positions. Masonry cards
|
|
109
|
+
show looping previous/next controls on hover or keyboard focus. Reel layout
|
|
110
|
+
keeps those controls visible and supports native horizontal-wheel or touch-swipe
|
|
111
|
+
navigation through grouped media. Media changes use a directional horizontal
|
|
112
|
+
transition, while vertical reel scrolling continues to move between posts.
|
|
63
113
|
|
|
64
|
-
|
|
114
|
+
## Reel URLs with Vue Router
|
|
65
115
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
import {
|
|
69
|
-
VibeLayout,
|
|
70
|
-
type VibeResolveParams,
|
|
71
|
-
type VibeResolveResult,
|
|
72
|
-
} from '@wyxos/vibe'
|
|
73
|
-
import '@wyxos/vibe/style.css'
|
|
116
|
+
Pass a Vue Router instance when the application should reflect the active reel
|
|
117
|
+
in its URL. The consumer owns the route names and the item-to-location mapping:
|
|
74
118
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
119
|
+
```ts
|
|
120
|
+
import { useRouter } from 'vue-router'
|
|
121
|
+
import { createVibe } from '@wyxos/vibe'
|
|
122
|
+
|
|
123
|
+
const router = useRouter()
|
|
124
|
+
const vibe = createVibe({
|
|
125
|
+
target: '#gallery',
|
|
126
|
+
loadPage,
|
|
127
|
+
routing: {
|
|
128
|
+
router,
|
|
129
|
+
feed: { name: 'gallery' },
|
|
130
|
+
reel: ({ item }) => ({
|
|
131
|
+
name: 'gallery-file',
|
|
132
|
+
params: { fileId: String(item.postId) },
|
|
133
|
+
}),
|
|
134
|
+
},
|
|
135
|
+
})
|
|
84
136
|
```
|
|
85
137
|
|
|
86
|
-
|
|
138
|
+
Vibe pushes the first reel location, replaces it as the active reel changes,
|
|
139
|
+
and replaces it with `feed` when the masonry-origin reel closes or responsive
|
|
140
|
+
layout returns to masonry. The reel callback also receives the zero-based
|
|
141
|
+
`index`, `loadedCount`, optional remote `total`, and whether the reel originated
|
|
142
|
+
from `masonry` or the base `reel` layout. Return `null` to skip a URL update for
|
|
143
|
+
an item. Vue Router is an optional peer dependency and is only needed when this
|
|
144
|
+
integration is used.
|
|
145
|
+
|
|
146
|
+
## Autofill
|
|
87
147
|
|
|
88
|
-
|
|
148
|
+
Autofill targets a number of new top-level cards after grouping and
|
|
149
|
+
deduplication. `autofill.pageSize` is the minimum card target for one Vibe load
|
|
150
|
+
cycle, not the server's own response size. For example, a server may return up
|
|
151
|
+
to 30 cards per request while Vibe is configured with `pageSize: 40`; Vibe then
|
|
152
|
+
follows cursors until the cycle contains at least 40 cards.
|
|
89
153
|
|
|
90
|
-
|
|
154
|
+
### Frontend autofill
|
|
155
|
+
|
|
156
|
+
Frontend autofill follows cursors and commits the collected batch once the
|
|
157
|
+
target is reached or the source is exhausted. Restore committed items with
|
|
158
|
+
`initialPage`; its `next` value must follow the last restored item:
|
|
91
159
|
|
|
92
160
|
```ts
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
161
|
+
const restoredPage = await feedCache.read() // VibePage | null
|
|
162
|
+
|
|
163
|
+
const vibe = createVibe({
|
|
164
|
+
target: '#gallery',
|
|
165
|
+
initialPage: restoredPage ?? undefined,
|
|
166
|
+
loadPage,
|
|
167
|
+
autofill: {
|
|
168
|
+
strategy: 'frontend',
|
|
169
|
+
pageSize: 40,
|
|
170
|
+
maxAdditionalPages: 10,
|
|
171
|
+
delayStepMs: 2_000,
|
|
172
|
+
delayMaxMs: 10_000,
|
|
173
|
+
},
|
|
174
|
+
onStateChange(state) {
|
|
175
|
+
void feedCache.write({
|
|
176
|
+
items: [...state.items],
|
|
177
|
+
next: state.next,
|
|
178
|
+
total: state.total ?? undefined,
|
|
179
|
+
})
|
|
180
|
+
},
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
await vibe.mount()
|
|
112
184
|
```
|
|
113
185
|
|
|
114
|
-
|
|
186
|
+
There is no frontend session snapshot. With 25 restored cards and
|
|
187
|
+
`pageSize: 40`, Vibe continues from `initialPage.next` until the load cycle has
|
|
188
|
+
at least 40 unique cards. Without `initialPage`, Vibe starts at cursor `null`.
|
|
115
189
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
190
|
+
The first request is immediate. Each subsequent request waits
|
|
191
|
+
`min(completedRequests * delayStepMs, delayMaxMs)`, so the defaults produce
|
|
192
|
+
0s, 2s, 4s, 6s, 8s, then 10s between requests. Set both values to `0` when a
|
|
193
|
+
frontend source should run without pacing. During a wait,
|
|
194
|
+
`state.autofill.delayRemainingMs` counts down and `nextRequestAt` contains the
|
|
195
|
+
absolute browser timestamp.
|
|
121
196
|
|
|
122
|
-
###
|
|
197
|
+
### Backend autofill
|
|
123
198
|
|
|
124
|
-
|
|
199
|
+
Backend autofill lets the consuming application enqueue and cancel durable
|
|
200
|
+
work. A restoration endpoint should return the currently committed page and,
|
|
201
|
+
when work remains active, its persisted session:
|
|
125
202
|
|
|
126
203
|
```ts
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
signal?: AbortSignal
|
|
204
|
+
interface BackendRestoration {
|
|
205
|
+
initialPage?: VibePage
|
|
206
|
+
session?: VibeAutofillSessionSnapshot
|
|
131
207
|
}
|
|
132
208
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
209
|
+
const restoration: BackendRestoration = await getBackendRestoration()
|
|
210
|
+
|
|
211
|
+
const vibe = createVibe({
|
|
212
|
+
target: '#gallery',
|
|
213
|
+
initialPage: restoration.initialPage,
|
|
214
|
+
loadPage,
|
|
215
|
+
autofill: {
|
|
216
|
+
strategy: 'backend',
|
|
217
|
+
pageSize: 40,
|
|
218
|
+
feedKey: 'latest-images',
|
|
219
|
+
initialSession: restoration.session,
|
|
220
|
+
async onUnderfilled(context) {
|
|
221
|
+
const { signal, ...payload } = context
|
|
222
|
+
return await startBackendAutofill(payload, { signal })
|
|
223
|
+
},
|
|
224
|
+
onCancel: cancelBackendAutofill,
|
|
225
|
+
},
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
const unsubscribe = subscribeToAutofillUpdates((update) => {
|
|
229
|
+
vibe.applyAutofillUpdate(update)
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
await vibe.mount()
|
|
138
233
|
```
|
|
139
234
|
|
|
140
|
-
|
|
235
|
+
Subscribe before mounting so a fast Reverb, WebSocket, or polling update is not
|
|
236
|
+
missed. A waiting update reports sequenced progress without items. A `complete`
|
|
237
|
+
or `exhausted` update supplies the entire buffered batch and appends it once:
|
|
141
238
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
- duplicate cursor protection
|
|
147
|
-
- initial retry state
|
|
148
|
-
- removal-aware feed navigation
|
|
239
|
+
The backend owns its delay policy and job scheduling. Return or emit the
|
|
240
|
+
absolute `nextRequestAt` timestamp for a waiting job; Vibe derives the live
|
|
241
|
+
`delayRemainingMs` countdown locally. Persist the timestamp in session
|
|
242
|
+
snapshots so a refreshed client can resume the same countdown.
|
|
149
243
|
|
|
150
|
-
|
|
244
|
+
```ts
|
|
245
|
+
vibe.applyAutofillUpdate({
|
|
246
|
+
feedKey: 'latest-images',
|
|
247
|
+
sessionId: 'job-456',
|
|
248
|
+
sequence: 2,
|
|
249
|
+
requests: 2,
|
|
250
|
+
received: 48,
|
|
251
|
+
nextRequestAt: null,
|
|
252
|
+
status: 'complete',
|
|
253
|
+
items: completedBatch,
|
|
254
|
+
next: 'cursor-after-completed-batch',
|
|
255
|
+
total: 500,
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
await vibe.cancelAutofill()
|
|
259
|
+
unsubscribe()
|
|
260
|
+
vibe.destroy()
|
|
261
|
+
```
|
|
151
262
|
|
|
152
|
-
|
|
263
|
+
Load-time restoration should provide both `initialPage` and
|
|
264
|
+
`autofill.initialSession`; this renders committed items immediately and avoids
|
|
265
|
+
starting a duplicate backend job. The snapshot `feedKey` and `pageSize` must
|
|
266
|
+
match the configured backend autofill options. Use
|
|
267
|
+
`vibe.restoreAutofillSession(snapshot)` only when a session becomes available
|
|
268
|
+
after construction; it restores autofill lifecycle state, not a complete
|
|
269
|
+
visible page.
|
|
153
270
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
271
|
+
`state.autofill` exposes the strategy, lifecycle status, target, received and
|
|
272
|
+
missing counts, request count, countdown, session identity, sequence, and error. Backend
|
|
273
|
+
results are appended only when an update is `complete` or `exhausted`; stale,
|
|
274
|
+
duplicate, mismatched-feed, and post-cancellation updates are ignored.
|
|
275
|
+
Terminal backend updates must include `next`, using `null` when the source is
|
|
276
|
+
exhausted. Vibe resumes ordinary pagination from that terminal cursor after
|
|
277
|
+
autofill completes. A terminal update without `next` is rejected so an older
|
|
278
|
+
cursor cannot be requested again.
|
|
162
279
|
|
|
163
|
-
|
|
280
|
+
## Manual fill
|
|
164
281
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
:show-end-badge="false"
|
|
172
|
-
:show-status-badges="false"
|
|
173
|
-
/>
|
|
282
|
+
Manual fill is separate from autofill. It starts only when the consumer calls
|
|
283
|
+
`vibe.fill()` and targets additional page requests rather than an item count:
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
await vibe.fill({ pages: 3 })
|
|
287
|
+
await vibe.fill({ until: 'end' })
|
|
174
288
|
```
|
|
175
289
|
|
|
176
|
-
|
|
290
|
+
`{ pages: 3 }` follows three cursors from the currently loaded page. It becomes
|
|
291
|
+
`exhausted` if the source ends before all three requests complete. `{ until:
|
|
292
|
+
'end' }` continues until a response returns `next: null`; reaching that end is
|
|
293
|
+
a successful `complete` result. Duplicate post IDs are still removed, but they
|
|
294
|
+
do not change how page requests are counted.
|
|
177
295
|
|
|
178
|
-
|
|
296
|
+
### Frontend fill
|
|
179
297
|
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
298
|
+
```ts
|
|
299
|
+
const restoredPage = await feedCache.read()
|
|
300
|
+
|
|
301
|
+
const vibe = createVibe({
|
|
302
|
+
target: '#gallery',
|
|
303
|
+
initialPage: restoredPage ?? undefined,
|
|
304
|
+
loadPage,
|
|
305
|
+
fill: {
|
|
306
|
+
strategy: 'frontend',
|
|
307
|
+
delayStepMs: 2_000,
|
|
308
|
+
delayMaxMs: 10_000,
|
|
309
|
+
},
|
|
310
|
+
onStateChange(state) {
|
|
311
|
+
renderFillState(state.fill)
|
|
312
|
+
void feedCache.write({
|
|
313
|
+
items: [...state.items],
|
|
314
|
+
next: state.next,
|
|
315
|
+
total: state.total ?? undefined,
|
|
316
|
+
})
|
|
317
|
+
},
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
await vibe.mount()
|
|
321
|
+
await vibe.fill({ pages: 3 })
|
|
322
|
+
// or: await vibe.fill({ until: 'end' })
|
|
191
323
|
```
|
|
192
324
|
|
|
193
|
-
|
|
325
|
+
Frontend fill invokes `loadPage` in the browser, exposes progress through
|
|
326
|
+
`state.fill`, and appends one buffered batch when the requested pages or end is
|
|
327
|
+
reached. `await vibe.cancelFill()` aborts the active request and discards the
|
|
328
|
+
uncommitted batch. A restored `initialPage.next` is the cursor from which the
|
|
329
|
+
next manual fill starts. The first fill request is immediate; subsequent
|
|
330
|
+
requests use the same capped incremental delay and expose
|
|
331
|
+
`state.fill.delayRemainingMs` plus `state.fill.nextRequestAt`.
|
|
194
332
|
|
|
195
|
-
|
|
196
|
-
- when `resolve` is present, Vibe continues paging from the seeded cursors
|
|
333
|
+
### Backend fill
|
|
197
334
|
|
|
198
|
-
|
|
335
|
+
```ts
|
|
336
|
+
interface FillRestoration {
|
|
337
|
+
initialPage?: VibePage
|
|
338
|
+
session?: VibeFillSessionSnapshot
|
|
339
|
+
}
|
|
199
340
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
341
|
+
const restoration: FillRestoration = await getFillRestoration()
|
|
342
|
+
|
|
343
|
+
const vibe = createVibe({
|
|
344
|
+
target: '#gallery',
|
|
345
|
+
initialPage: restoration.initialPage,
|
|
346
|
+
loadPage,
|
|
347
|
+
fill: {
|
|
348
|
+
strategy: 'backend',
|
|
349
|
+
feedKey: 'latest-images',
|
|
350
|
+
initialSession: restoration.session,
|
|
351
|
+
async onStart(context) {
|
|
352
|
+
const { signal, ...payload } = context
|
|
353
|
+
return await startBackendFill(payload, { signal })
|
|
354
|
+
},
|
|
355
|
+
onCancel: cancelBackendFill,
|
|
356
|
+
},
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
const unsubscribe = subscribeToFillUpdates((update) => {
|
|
360
|
+
vibe.applyFillUpdate(update)
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
await vibe.mount()
|
|
364
|
+
await vibe.fill({ pages: 3 })
|
|
365
|
+
```
|
|
206
366
|
|
|
207
|
-
|
|
367
|
+
The backend callback should enqueue durable work and return a `sessionId`
|
|
368
|
+
without waiting for every page. Each completed endpoint call can emit a
|
|
369
|
+
sequenced `waiting` update containing `completedPages` and `received`. The
|
|
370
|
+
backend remains responsible for delaying and dispatching each job. Include its
|
|
371
|
+
absolute `nextRequestAt` in the returned session and waiting updates so Vibe can
|
|
372
|
+
render the countdown. The terminal update supplies the entire buffered batch
|
|
373
|
+
and its cursor boundary:
|
|
208
374
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
375
|
+
```ts
|
|
376
|
+
vibe.applyFillUpdate({
|
|
377
|
+
feedKey: 'latest-images',
|
|
378
|
+
sessionId: 'fill-job-456',
|
|
379
|
+
sequence: 3,
|
|
380
|
+
completedPages: 3,
|
|
381
|
+
received: 86,
|
|
382
|
+
nextRequestAt: null,
|
|
383
|
+
status: 'complete',
|
|
384
|
+
items: completedBatch,
|
|
385
|
+
lastCursor: 'cursor-used-for-page-3',
|
|
386
|
+
next: 'cursor-after-page-3',
|
|
387
|
+
total: 500,
|
|
388
|
+
})
|
|
389
|
+
```
|
|
213
390
|
|
|
214
|
-
|
|
391
|
+
For load-time restoration, provide both the committed `initialPage` and the
|
|
392
|
+
persisted `fill.initialSession`. Subscribe to backend events before mounting.
|
|
393
|
+
Use `vibe.restoreFillSession(snapshot)` when a session arrives after
|
|
394
|
+
construction. `state.fill` exposes the strategy, target, status, completed page
|
|
395
|
+
count, received unique-card count, countdown, session identity, sequence, and error.
|
|
396
|
+
Ordinary pagination resumes from the terminal `next` cursor. Stale,
|
|
397
|
+
mismatched-feed, invalid-terminal, and post-cancellation updates are ignored.
|
|
215
398
|
|
|
216
|
-
|
|
399
|
+
## Auto scroll
|
|
217
400
|
|
|
218
|
-
|
|
401
|
+
Auto scroll advances the masonry gallery smoothly in pixels per second. It is
|
|
402
|
+
disabled by default and can be configured when the instance is created:
|
|
219
403
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
404
|
+
```ts
|
|
405
|
+
const vibe = createVibe({
|
|
406
|
+
target: '#gallery',
|
|
407
|
+
layout: 'masonry',
|
|
408
|
+
loadPage,
|
|
409
|
+
autoScroll: {
|
|
410
|
+
speedPxPerSecond: 80,
|
|
411
|
+
minSpeedPxPerSecond: 20,
|
|
412
|
+
maxSpeedPxPerSecond: 240,
|
|
413
|
+
},
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
await vibe.mount()
|
|
417
|
+
vibe.setAutoScroll(true, 120)
|
|
418
|
+
vibe.pauseAutoScroll()
|
|
419
|
+
vibe.resumeAutoScroll()
|
|
420
|
+
vibe.setAutoScrollSpeed(160)
|
|
421
|
+
vibe.setAutoScroll(false)
|
|
422
|
+
```
|
|
230
423
|
|
|
231
|
-
|
|
424
|
+
The default speed is 80 px/s within a default 20–240 px/s range. Consumer
|
|
425
|
+
values outside the configured range are clamped. `state.autoScroll` exposes the
|
|
426
|
+
effective speed, bounds, and `enabled` / `paused` flags. The controller uses the
|
|
427
|
+
gallery's animation frames rather than repeated smooth-scroll commands, so
|
|
428
|
+
movement remains continuous and responds immediately to speed changes.
|
|
232
429
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
</template>
|
|
238
|
-
|
|
239
|
-
<template #grid-item-overlay="{ item, hovered }">
|
|
240
|
-
<div v-if="hovered" class="absolute inset-x-0 bottom-0 p-3">
|
|
241
|
-
<div class="pointer-events-auto bg-black/45 px-3 py-2 backdrop-blur">
|
|
242
|
-
Like {{ item.title }}
|
|
243
|
-
</div>
|
|
244
|
-
</div>
|
|
245
|
-
</template>
|
|
246
|
-
|
|
247
|
-
<template #grid-footer>
|
|
248
|
-
<div class="bg-black/55 px-4 py-3 backdrop-blur">
|
|
249
|
-
Custom footer UI
|
|
250
|
-
</div>
|
|
251
|
-
</template>
|
|
252
|
-
</VibeLayout>
|
|
253
|
-
```
|
|
430
|
+
Auto scroll waits while reel layout or a masonry-origin reel viewer is active.
|
|
431
|
+
It resumes without a position jump when masonry becomes active again. Infinite
|
|
432
|
+
scroll continues to work normally because the gallery remains the only scroll
|
|
433
|
+
owner.
|
|
254
434
|
|
|
255
|
-
|
|
435
|
+
## Reel auto advance
|
|
256
436
|
|
|
257
|
-
|
|
437
|
+
Reel auto advance renders a countdown progress strip at the bottom of an active
|
|
438
|
+
reel and moves to the next post when the configured interval expires:
|
|
258
439
|
|
|
259
440
|
```ts
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
441
|
+
const vibe = createVibe({
|
|
442
|
+
target: '#gallery',
|
|
443
|
+
layout: 'reel',
|
|
444
|
+
loadPage,
|
|
445
|
+
reelAutoAdvance: {
|
|
446
|
+
enabled: true,
|
|
447
|
+
intervalMs: 5_000,
|
|
448
|
+
includePostItems: false,
|
|
449
|
+
},
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
await vibe.mount()
|
|
453
|
+
vibe.setReelAutoAdvance(false)
|
|
454
|
+
vibe.setReelAutoAdvance({
|
|
455
|
+
enabled: true,
|
|
456
|
+
intervalMs: 8_000,
|
|
457
|
+
includePostItems: true,
|
|
458
|
+
})
|
|
264
459
|
```
|
|
265
460
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
461
|
+
The default interval is five seconds and grouped post items are excluded by
|
|
462
|
+
default. When `includePostItems` is enabled, Vibe counts through the parent media
|
|
463
|
+
and then each item in its `items` array before moving vertically to the next post.
|
|
464
|
+
Still images use the countdown. Audio and video stop looping while auto advance
|
|
465
|
+
is enabled and advance from their playback `ended` event instead, so pausing the
|
|
466
|
+
media also pauses progression. Manual vertical or horizontal navigation restarts
|
|
467
|
+
the countdown for the newly active image. The feature is inactive in masonry and
|
|
468
|
+
becomes visible in either a base reel layout or a reel opened from masonry.
|
|
269
469
|
|
|
270
|
-
|
|
470
|
+
`state.reelAutoAdvance` exposes `enabled`, `intervalMs`, and `includePostItems`.
|
|
471
|
+
At the loaded boundary, normal pagination is requested when another cursor is
|
|
472
|
+
available; the load-more lock continues to prevent that request when active.
|
|
271
473
|
|
|
272
|
-
|
|
273
|
-
- `unlockPageLoading()`
|
|
274
|
-
- `loadNext()`
|
|
275
|
-
- `loadPrevious()`
|
|
276
|
-
- `retry()`
|
|
277
|
-
- `cancel()`
|
|
278
|
-
- `remove(ids)`
|
|
279
|
-
- `restore(ids)`
|
|
280
|
-
- `undo()`
|
|
281
|
-
- `getRemovedIds()`
|
|
282
|
-
- `clearRemoved()`
|
|
474
|
+
## Reel runtime controls
|
|
283
475
|
|
|
284
|
-
|
|
476
|
+
Use the instance to drive the active reel through the same state paths as
|
|
477
|
+
horizontal and vertical reel gestures:
|
|
285
478
|
|
|
286
479
|
```ts
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
errorMessage: string | null
|
|
291
|
-
fillCollectedCount: number | null
|
|
292
|
-
fillCursor: string | null
|
|
293
|
-
fillDelayRemainingMs: number | null
|
|
294
|
-
fillTargetCount: number | null
|
|
295
|
-
hasNextPage: boolean
|
|
296
|
-
hasPreviousPage: boolean
|
|
297
|
-
itemCount: number
|
|
298
|
-
loadState: 'failed' | 'loaded' | 'loading'
|
|
299
|
-
nextBoundaryLoadProgress: number
|
|
300
|
-
nextCursor: string | null
|
|
301
|
-
pageLoadingLocked: boolean
|
|
302
|
-
phase: 'failed' | 'filling' | 'idle' | 'initializing' | 'loading' | 'refreshing'
|
|
303
|
-
previousBoundaryLoadProgress: number
|
|
304
|
-
previousCursor: string | null
|
|
305
|
-
removedCount: number
|
|
306
|
-
removedIds: readonly string[]
|
|
307
|
-
surfaceMode: 'fullscreen' | 'list'
|
|
308
|
-
}
|
|
309
|
-
```
|
|
480
|
+
// Media items within the active post (horizontal reel navigation).
|
|
481
|
+
vibe.nextReelMediaItem()
|
|
482
|
+
vibe.previousReelMediaItem()
|
|
310
483
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
vibe.value?.lockPageLoading()
|
|
315
|
-
vibe.value?.remove(['item-2', 'item-5'])
|
|
316
|
-
vibe.value?.undo()
|
|
317
|
-
vibe.value?.unlockPageLoading()
|
|
318
|
-
console.log(vibe.value?.status.itemCount)
|
|
319
|
-
console.log(vibe.value?.status.pageLoadingLocked)
|
|
320
|
-
console.log(vibe.value?.status.nextBoundaryLoadProgress)
|
|
321
|
-
console.log(vibe.value?.status.removedIds)
|
|
484
|
+
// Posts within the reel (vertical reel navigation).
|
|
485
|
+
vibe.nextReelPost()
|
|
486
|
+
vibe.previousReelPost()
|
|
322
487
|
```
|
|
323
488
|
|
|
324
|
-
|
|
489
|
+
Each navigation method returns `true` when it accepted a navigation and `false`
|
|
490
|
+
when it was a no-op. Media-item navigation loops from the first item to the last
|
|
491
|
+
and from the last item to the first, matching reel swipes; a post with only one
|
|
492
|
+
media item returns `false`. Post navigation does not loop and returns `false` at
|
|
493
|
+
the first or last currently loaded post. It does not implicitly load another
|
|
494
|
+
page.
|
|
325
495
|
|
|
326
|
-
`
|
|
496
|
+
All four methods return `false` before mount, during the initial loading or empty
|
|
497
|
+
state, after destroy, and while the instance is showing only its masonry feed.
|
|
498
|
+
They work in both a base reel layout and an open masonry-origin reel viewer.
|
|
499
|
+
Successful navigation updates `state.activeReelPostId`, the selected media item,
|
|
500
|
+
card-region props, route synchronization, and an open information sheet's
|
|
501
|
+
consumer context through the same handlers used by gestures and keyboard input.
|
|
327
502
|
|
|
328
|
-
|
|
329
|
-
- `update:surfaceMode`
|
|
330
|
-
- `asset-loads`
|
|
331
|
-
- `asset-errors`
|
|
503
|
+
## Reel information sheet
|
|
332
504
|
|
|
333
|
-
|
|
505
|
+
Provide an application-owned Vue component when a reel should offer a
|
|
506
|
+
customizable information sheet. The component is fixed for the lifetime of the
|
|
507
|
+
Vibe instance, while its open state can be initialized and changed at runtime:
|
|
334
508
|
|
|
335
509
|
```ts
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
510
|
+
import ReelInformationSheet from './ReelInformationSheet.vue'
|
|
511
|
+
|
|
512
|
+
const vibe = createVibe({
|
|
513
|
+
target: '#gallery',
|
|
514
|
+
layout: 'responsive',
|
|
515
|
+
loadPage,
|
|
516
|
+
reelInfoSheet: {
|
|
517
|
+
component: ReelInformationSheet,
|
|
518
|
+
enabled: false,
|
|
519
|
+
},
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
await vibe.mount()
|
|
523
|
+
vibe.setReelInfoSheet(true)
|
|
524
|
+
vibe.setReelInfoSheet(false)
|
|
342
525
|
```
|
|
343
526
|
|
|
344
|
-
`
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
occurrenceKey: string
|
|
350
|
-
url: string
|
|
351
|
-
kind: 'generic' | 'not-found'
|
|
352
|
-
surface: 'grid' | 'fullscreen'
|
|
353
|
-
}
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
Example:
|
|
527
|
+
Import `VibeReelInfoSheetProps` to type the consumer component. It receives the
|
|
528
|
+
active `item`, zero-based post and media indexes, the active media item and
|
|
529
|
+
source, inclusive media count, loaded and optional total counts, `layout:
|
|
530
|
+
'reel'`, whether the reel originated from `reel` or `masonry`, and a `close()`
|
|
531
|
+
callback. The component owns its visible controls and content:
|
|
357
532
|
|
|
358
533
|
```vue
|
|
359
534
|
<script setup lang="ts">
|
|
360
|
-
import type {
|
|
535
|
+
import type { VibeReelInfoSheetProps } from '@wyxos/vibe'
|
|
361
536
|
|
|
362
|
-
|
|
363
|
-
console.log(loads)
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function onAssetErrors(errors: VibeAssetErrorEvent[]) {
|
|
367
|
-
console.log(errors)
|
|
368
|
-
}
|
|
537
|
+
defineProps<VibeReelInfoSheetProps>()
|
|
369
538
|
</script>
|
|
370
539
|
|
|
371
540
|
<template>
|
|
372
|
-
<
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
/>
|
|
541
|
+
<aside>
|
|
542
|
+
<button type="button" @click="close">Close</button>
|
|
543
|
+
<h2>Post {{ item.postId }}</h2>
|
|
544
|
+
</aside>
|
|
377
545
|
</template>
|
|
378
546
|
```
|
|
379
547
|
|
|
380
|
-
|
|
548
|
+
Phones render the sheet as a full-width modal overlay and make the reel inert
|
|
549
|
+
until it closes. Tablets through 1024px render a full-width non-overlay sheet
|
|
550
|
+
below the reel. Larger layouts render it beside the reel at 25% width, changing
|
|
551
|
+
to 40% at a 1920px viewport. Non-phone sheets stay mounted while vertical reel
|
|
552
|
+
swipes update their active context. All presentations animate open and closed
|
|
553
|
+
and honor reduced-motion preferences.
|
|
554
|
+
|
|
555
|
+
`state.reelInfoSheet.enabled` reports the requested open state. Enabling without
|
|
556
|
+
a configured component throws an error; disabling is always safe. In a base
|
|
557
|
+
reel, Escape closes an open sheet. In a masonry-origin reel, Escape closes the
|
|
558
|
+
viewer without disabling the sheet, so it is open again when another masonry
|
|
559
|
+
card opens a reel. The consumer close control or `setReelInfoSheet(false)`
|
|
560
|
+
explicitly disables it. The underlying masonry renderer stays mounted, so
|
|
561
|
+
closing either surface preserves its scroll, media selections, and focus target.
|
|
562
|
+
Calling `setReelInfoSheet()` before mount, during loading, or outside an active
|
|
563
|
+
reel still updates this requested state; the sheet becomes visible when a valid
|
|
564
|
+
reel context exists. This preserves the existing persistent sheet-state
|
|
565
|
+
behavior across masonry viewer closes.
|
|
566
|
+
|
|
567
|
+
## Load-more lock
|
|
568
|
+
|
|
569
|
+
Pause ordinary forward pagination without disabling the feed or cancelling a request
|
|
570
|
+
that is already running:
|
|
381
571
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
- fullscreen load events report `item.url`
|
|
385
|
-
- multiple failures that happen close together are batched into one event
|
|
386
|
-
- identical failures are deduped inside the same batch
|
|
387
|
-
- if the same item fails again later, it can emit again in a later batch
|
|
388
|
-
- built-in Vibe error surfaces allow retrying `generic` failures, but not `not-found`
|
|
572
|
+
```ts
|
|
573
|
+
vibe.setLoadMoreLocked(true)
|
|
389
574
|
|
|
390
|
-
|
|
575
|
+
// Existing items remain interactive. Later, allow pagination again.
|
|
576
|
+
vibe.setLoadMoreLocked(false)
|
|
577
|
+
```
|
|
391
578
|
|
|
392
|
-
|
|
579
|
+
While locked, Vibe ignores `loadNext()`, infinite-scroll boundary triggers, the
|
|
580
|
+
load-more action, and the end-of-feed retry. `reload()`, initial loading, autofill,
|
|
581
|
+
and explicit `fill()` operations keep their own lifecycles. If infinite scroll is
|
|
582
|
+
enabled when the instance is unlocked, Vibe immediately checks the current bottom
|
|
583
|
+
boundary so the consumer does not need to nudge the scroll position.
|
|
393
584
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
585
|
+
Read `state.loadMoreLocked` through `getState()` or `onStateChange` when application
|
|
586
|
+
chrome needs to reflect the gate. The built-in pagination action is disabled and
|
|
587
|
+
shown as `Loading paused` while the gate is active.
|
|
588
|
+
|
|
589
|
+
## Feed state
|
|
590
|
+
|
|
591
|
+
Use `onStateChange` to render request and layout state outside Vibe, such as an
|
|
592
|
+
application-header lifecycle indicator:
|
|
397
593
|
|
|
398
|
-
|
|
594
|
+
```ts
|
|
595
|
+
const vibe = createVibe({
|
|
596
|
+
target: '#gallery',
|
|
597
|
+
loadPage,
|
|
598
|
+
onStateChange: (state) => {
|
|
599
|
+
renderFeedState({
|
|
600
|
+
layout: state.layout,
|
|
601
|
+
lifecycle: state.lifecycle,
|
|
602
|
+
})
|
|
603
|
+
},
|
|
604
|
+
})
|
|
605
|
+
```
|
|
399
606
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
607
|
+
The callback receives an initial snapshot when the instance is created and a
|
|
608
|
+
new snapshot whenever its public state changes. The typed `lifecycle` value is
|
|
609
|
+
`loading`, `loaded`, or `error`; it covers initial and pagination requests.
|
|
610
|
+
Individual media preview errors do not mark the feed request as failed.
|
|
404
611
|
|
|
405
|
-
##
|
|
612
|
+
## Instance lifecycle
|
|
406
613
|
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
614
|
+
```ts
|
|
615
|
+
vibe.setLayout('reel')
|
|
616
|
+
vibe.setLayout('responsive')
|
|
617
|
+
vibe.setInfiniteScroll(false)
|
|
618
|
+
vibe.setLoadMoreLocked(true)
|
|
619
|
+
vibe.setLoadMoreLocked(false)
|
|
620
|
+
vibe.setReelAutoAdvance(true)
|
|
621
|
+
vibe.setReelAutoAdvance({ includePostItems: true, intervalMs: 8_000 })
|
|
622
|
+
vibe.setReelInfoSheet(true)
|
|
623
|
+
vibe.setReelInfoSheet(false)
|
|
624
|
+
vibe.nextReelMediaItem()
|
|
625
|
+
vibe.previousReelMediaItem()
|
|
626
|
+
vibe.nextReelPost()
|
|
627
|
+
vibe.previousReelPost()
|
|
628
|
+
vibe.setAutoScroll(true, 80)
|
|
629
|
+
vibe.pauseAutoScroll()
|
|
630
|
+
vibe.resumeAutoScroll()
|
|
631
|
+
vibe.setAutoScroll(false)
|
|
632
|
+
await vibe.loadNext()
|
|
633
|
+
await vibe.reload()
|
|
634
|
+
vibe.getState()
|
|
635
|
+
vibe.destroy()
|
|
414
636
|
```
|
|
415
637
|
|
|
416
|
-
|
|
638
|
+
Destroying an instance unmounts its Vue tree and aborts its active page request.
|
|
417
639
|
|
|
418
|
-
|
|
419
|
-
-
|
|
420
|
-
|
|
640
|
+
In masonry layout, activating a card opens that post in a reel layered over the
|
|
641
|
+
still-mounted masonry feed. Pressing Escape returns to the same masonry scroll
|
|
642
|
+
position. `getState()` reports the viewer session through `reelOrigin` and
|
|
643
|
+
`activeReelPostId`; Escape does not exit a reel configured as the base layout.
|