@wyxos/vibe 2.2.5 → 3.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 +451 -95
- package/lib/components/FullscreenHeader.vue.d.ts +25 -0
- package/lib/components/FullscreenMediaBar.vue.d.ts +14 -0
- package/lib/components/FullscreenSurface.vue.d.ts +46 -0
- package/lib/components/Layout.vue.d.ts +42 -0
- package/lib/components/ListCard.vue.d.ts +44 -0
- package/lib/components/ListSurface.vue.d.ts +64 -0
- package/lib/components/viewer-core/assetErrors.d.ts +30 -0
- package/lib/components/viewer-core/assetState.d.ts +12 -0
- package/lib/components/viewer-core/autoBuckets.d.ts +18 -0
- package/lib/components/viewer-core/autoResolveHelpers.d.ts +19 -0
- package/lib/components/viewer-core/autoResolveState.d.ts +50 -0
- package/lib/components/viewer-core/dom.d.ts +2 -0
- package/lib/components/viewer-core/fillDelay.d.ts +9 -0
- package/lib/components/viewer-core/format.d.ts +1 -0
- package/lib/components/viewer-core/itemIdentity.d.ts +6 -0
- package/lib/components/viewer-core/listCardAsset.d.ts +4 -0
- package/lib/components/viewer-core/listPreview.d.ts +10 -0
- package/lib/components/viewer-core/loadError.d.ts +4 -0
- package/lib/components/viewer-core/masonryLayout.d.ts +37 -0
- package/lib/components/viewer-core/masonryViewport.d.ts +7 -0
- package/lib/components/viewer-core/media.d.ts +4 -0
- package/lib/components/viewer-core/mediaPlayback.d.ts +1 -0
- package/lib/components/viewer-core/removalState.d.ts +50 -0
- package/lib/components/viewer-core/slotContent.d.ts +2 -0
- package/lib/components/viewer-core/surfaceSlots.d.ts +22 -0
- package/lib/components/viewer-core/theme.d.ts +3 -0
- package/lib/components/viewer-core/useActivation.d.ts +8 -0
- package/lib/components/viewer-core/useAssetLoadQueue.d.ts +22 -0
- package/lib/components/viewer-core/useAutoResolveSource.d.ts +51 -0
- package/lib/components/viewer-core/useController.d.ts +71 -0
- package/lib/components/viewer-core/useDataSource.d.ts +100 -0
- package/lib/components/viewer-core/useEdgeBoundary.d.ts +16 -0
- package/lib/components/viewer-core/useFullscreen.d.ts +1 -0
- package/lib/components/viewer-core/useMasonryList.d.ts +43 -0
- package/lib/components/viewer-core/useMasonryMotion.d.ts +29 -0
- package/lib/components/viewer-core/useMedia.d.ts +39 -0
- package/lib/components/viewer-core/useViewer.d.ts +61 -0
- package/lib/components/viewer-core/virtualization.d.ts +14 -0
- package/lib/components/viewer.d.ts +16 -0
- package/lib/index.cjs +1 -1
- package/lib/index.d.ts +58 -5
- package/lib/index.js +3196 -1486
- package/lib/style.css +3 -0
- package/package.json +86 -83
- package/lib/App.vue.d.ts +0 -3
- package/lib/apple-touch-icon-114x114.png +0 -0
- package/lib/apple-touch-icon-120x120.png +0 -0
- package/lib/apple-touch-icon-144x144.png +0 -0
- package/lib/apple-touch-icon-152x152.png +0 -0
- package/lib/apple-touch-icon-180x180.png +0 -0
- package/lib/apple-touch-icon-57x57.png +0 -0
- package/lib/apple-touch-icon-60x60.png +0 -0
- package/lib/apple-touch-icon-72x72.png +0 -0
- package/lib/apple-touch-icon-76x76.png +0 -0
- package/lib/components/Masonry.vue.d.ts +0 -68
- package/lib/components/MasonryItem.vue.d.ts +0 -3
- package/lib/components/MasonryLoader.vue.d.ts +0 -27
- package/lib/components/MasonryVideoControls.vue.d.ts +0 -30
- package/lib/components/masonryItemRegistry.d.ts +0 -24
- package/lib/favicon-128x128.png +0 -0
- package/lib/favicon-16x16.png +0 -0
- package/lib/favicon-256x256.png +0 -0
- package/lib/favicon-32x32.png +0 -0
- package/lib/favicon-48x48.png +0 -0
- package/lib/favicon-64x64.png +0 -0
- package/lib/favicon.ico +0 -0
- package/lib/logo-dark.svg +0 -36
- package/lib/logo-light.svg +0 -29
- package/lib/logo.svg +0 -32
- package/lib/manifest.json +0 -41
- package/lib/masonry/backfill.d.ts +0 -53
- package/lib/masonry/layout.d.ts +0 -13
- package/lib/masonry/layoutEngine.d.ts +0 -33
- package/lib/masonry/types.d.ts +0 -95
package/README.md
CHANGED
|
@@ -1,95 +1,451 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
-
|
|
1
|
+
# Vibe
|
|
2
|
+
|
|
3
|
+
Vibe is a Vue 3 media viewer for large feeds of mixed image, video, audio, and other items.
|
|
4
|
+
|
|
5
|
+
It ships two built-in surfaces:
|
|
6
|
+
|
|
7
|
+
- Desktop: a virtualized masonry grid that opens into fullscreen
|
|
8
|
+
- Mobile and tablet: fullscreen only
|
|
9
|
+
|
|
10
|
+
The current `3.0.0` rebuild focuses on a strict item contract, library-owned loading, strong demo coverage, and a small customization surface.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm i @wyxos/vibe
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Import the bundled stylesheet once:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import '@wyxos/vibe/style.css'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Tailwind scanning is not required for the package UI.
|
|
25
|
+
|
|
26
|
+
## Plugin install
|
|
27
|
+
|
|
28
|
+
The default export is the plugin:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { createApp } from 'vue'
|
|
32
|
+
import App from './App.vue'
|
|
33
|
+
|
|
34
|
+
import Vibe from '@wyxos/vibe'
|
|
35
|
+
import '@wyxos/vibe/style.css'
|
|
36
|
+
|
|
37
|
+
createApp(App)
|
|
38
|
+
.use(Vibe)
|
|
39
|
+
.mount('#app')
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That registers the global component as `VibeLayout`.
|
|
43
|
+
|
|
44
|
+
## Direct import
|
|
45
|
+
|
|
46
|
+
If you prefer local registration, import `VibeLayout` directly:
|
|
47
|
+
|
|
48
|
+
```vue
|
|
49
|
+
<script setup lang="ts">
|
|
50
|
+
import {
|
|
51
|
+
VibeLayout,
|
|
52
|
+
type VibeResolveParams,
|
|
53
|
+
type VibeResolveResult,
|
|
54
|
+
} from '@wyxos/vibe'
|
|
55
|
+
import '@wyxos/vibe/style.css'
|
|
56
|
+
|
|
57
|
+
async function resolve({ cursor, pageSize }: VibeResolveParams): Promise<VibeResolveResult> {
|
|
58
|
+
const response = await fetch(`/api/feed?cursor=${cursor ?? ''}&pageSize=${pageSize}`)
|
|
59
|
+
return await response.json()
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<VibeLayout :resolve="resolve" />
|
|
65
|
+
</template>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Optional auto-mode pacing props:
|
|
69
|
+
|
|
70
|
+
```vue
|
|
71
|
+
<VibeLayout
|
|
72
|
+
:resolve="resolve"
|
|
73
|
+
:fill-delay-ms="1000"
|
|
74
|
+
:fill-delay-step-ms="250"
|
|
75
|
+
/>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- `fill-delay-ms`: base delay before the first chained fill request
|
|
79
|
+
- `fill-delay-step-ms`: extra delay added for each additional chained fill request in the same fill cycle
|
|
80
|
+
|
|
81
|
+
Optional auto-mode feed strategy:
|
|
82
|
+
|
|
83
|
+
```vue
|
|
84
|
+
<VibeLayout
|
|
85
|
+
:resolve="resolve"
|
|
86
|
+
mode="dynamic"
|
|
87
|
+
/>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- `dynamic` is the default
|
|
91
|
+
- `static` reloads the current boundary cursor before advancing when the currently visible boundary page is underfilled
|
|
92
|
+
|
|
93
|
+
## What Vibe does
|
|
94
|
+
|
|
95
|
+
- Desktop masonry list with virtualization and staged page growth
|
|
96
|
+
- Fullscreen viewer with swipe, wheel, keyboard, and custom media controls
|
|
97
|
+
- Auto-loading mode with internal pagination ownership
|
|
98
|
+
- Controlled mode for advanced/manual integrations
|
|
99
|
+
- Remove, restore, and undo by item `id`
|
|
100
|
+
- Grid customization through slots for icons, overlays, and footer UI
|
|
101
|
+
- Built-in loading and preload error states, including explicit `404` when known
|
|
102
|
+
- Built-in retry UI for non-404 asset failures in grid and fullscreen
|
|
103
|
+
|
|
104
|
+
## Item contract
|
|
105
|
+
|
|
106
|
+
Vibe only requires a minimal item shape:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
type VibeViewerItem = {
|
|
110
|
+
id: string
|
|
111
|
+
type: 'image' | 'video' | 'audio' | 'other'
|
|
112
|
+
title?: string
|
|
113
|
+
url: string
|
|
114
|
+
width?: number
|
|
115
|
+
height?: number
|
|
116
|
+
preview?: {
|
|
117
|
+
url: string
|
|
118
|
+
width?: number
|
|
119
|
+
height?: number
|
|
120
|
+
}
|
|
121
|
+
[key: string]: unknown
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Notes:
|
|
126
|
+
|
|
127
|
+
- Grid mode prefers `preview.url`, then falls back to `url`
|
|
128
|
+
- Fullscreen mode uses `url`
|
|
129
|
+
- Grid layout prefers `preview.width/height`, then root `width/height`, then a square fallback tile
|
|
130
|
+
- `other` is intentionally broad so the consuming app can layer its own subtypes and icon logic on top
|
|
131
|
+
|
|
132
|
+
## Loading modes
|
|
133
|
+
|
|
134
|
+
### Auto mode
|
|
135
|
+
|
|
136
|
+
Use `resolve` when you want Vibe to own the paging loop:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
type VibeResolveParams = {
|
|
140
|
+
cursor: string | null
|
|
141
|
+
pageSize: number
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
type VibeResolveResult = {
|
|
145
|
+
items: VibeViewerItem[]
|
|
146
|
+
nextPage: string | null
|
|
147
|
+
previousPage?: string | null
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```vue
|
|
152
|
+
<script setup lang="ts">
|
|
153
|
+
import {
|
|
154
|
+
VibeLayout,
|
|
155
|
+
type VibeResolveParams,
|
|
156
|
+
type VibeResolveResult,
|
|
157
|
+
} from '@wyxos/vibe'
|
|
158
|
+
|
|
159
|
+
async function resolve({ cursor, pageSize }: VibeResolveParams): Promise<VibeResolveResult> {
|
|
160
|
+
const response = await fetch(`/api/feed?cursor=${cursor ?? ''}&pageSize=${pageSize}`)
|
|
161
|
+
return await response.json()
|
|
162
|
+
}
|
|
163
|
+
</script>
|
|
164
|
+
|
|
165
|
+
<template>
|
|
166
|
+
<VibeLayout :resolve="resolve" />
|
|
167
|
+
</template>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
In this mode, Vibe owns:
|
|
171
|
+
|
|
172
|
+
- loaded items
|
|
173
|
+
- active index
|
|
174
|
+
- next-page prefetch
|
|
175
|
+
- optional previous-page loading
|
|
176
|
+
- duplicate cursor protection
|
|
177
|
+
- initial retry state
|
|
178
|
+
|
|
179
|
+
Auto mode also supports two feed strategies:
|
|
180
|
+
|
|
181
|
+
- `dynamic`:
|
|
182
|
+
- default behavior
|
|
183
|
+
- if a resolve returns fewer items than `pageSize`, Vibe enters a fill loop
|
|
184
|
+
- it waits `fillDelayMs`, then `fillDelayMs + fillDelayStepMs`, and so on for each chained request
|
|
185
|
+
- it keeps accumulating results until the collected count reaches `pageSize` or there is no further cursor
|
|
186
|
+
- then it commits that batch into the layout once
|
|
187
|
+
- `static`:
|
|
188
|
+
- before advancing at the bottom or top, Vibe checks whether the current boundary page is underfilled after local removals
|
|
189
|
+
- if it is, Vibe reloads that same cursor in place first
|
|
190
|
+
- only once that boundary page is full again will the next edge hit advance to the next or previous cursor
|
|
191
|
+
|
|
192
|
+
### Controlled mode
|
|
193
|
+
|
|
194
|
+
Use controlled mode when the app needs to own item aggregation or paging policy:
|
|
195
|
+
|
|
196
|
+
```vue
|
|
197
|
+
<script setup lang="ts">
|
|
198
|
+
import { ref } from 'vue'
|
|
199
|
+
import { VibeLayout, type VibeViewerItem } from '@wyxos/vibe'
|
|
200
|
+
|
|
201
|
+
const items = ref<VibeViewerItem[]>([])
|
|
202
|
+
const activeIndex = ref(0)
|
|
203
|
+
const loading = ref(false)
|
|
204
|
+
const hasNextPage = ref(true)
|
|
205
|
+
const hasPreviousPage = ref(false)
|
|
206
|
+
|
|
207
|
+
async function requestNextPage() {
|
|
208
|
+
// app-owned fetch and append
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function requestPreviousPage() {
|
|
212
|
+
// app-owned fetch and prepend
|
|
213
|
+
}
|
|
214
|
+
</script>
|
|
215
|
+
|
|
216
|
+
<template>
|
|
217
|
+
<VibeLayout
|
|
218
|
+
:items="items"
|
|
219
|
+
:active-index="activeIndex"
|
|
220
|
+
:loading="loading"
|
|
221
|
+
:has-next-page="hasNextPage"
|
|
222
|
+
:has-previous-page="hasPreviousPage"
|
|
223
|
+
pagination-detail="P10-12 · V11"
|
|
224
|
+
:request-next-page="requestNextPage"
|
|
225
|
+
:request-previous-page="requestPreviousPage"
|
|
226
|
+
@update:active-index="activeIndex = $event"
|
|
227
|
+
/>
|
|
228
|
+
</template>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Slots
|
|
232
|
+
|
|
233
|
+
`VibeLayout` exposes three customization slots:
|
|
234
|
+
|
|
235
|
+
### `#item-icon`
|
|
236
|
+
|
|
237
|
+
Lets the app replace the fallback icon, especially useful for `other` items.
|
|
238
|
+
|
|
239
|
+
Slot props:
|
|
240
|
+
|
|
241
|
+
- `item`
|
|
242
|
+
- `icon`
|
|
243
|
+
|
|
244
|
+
```vue
|
|
245
|
+
<VibeLayout :resolve="resolve">
|
|
246
|
+
<template #item-icon="{ item, icon }">
|
|
247
|
+
<component :is="item.type === 'other' ? MyCustomIcon : icon" />
|
|
248
|
+
</template>
|
|
249
|
+
</VibeLayout>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### `#grid-item-overlay`
|
|
253
|
+
|
|
254
|
+
Desktop grid-only overlay content for reactions, menus, badges, and similar controls.
|
|
255
|
+
|
|
256
|
+
Slot props:
|
|
257
|
+
|
|
258
|
+
- `item`
|
|
259
|
+
- `index`
|
|
260
|
+
- `active`
|
|
261
|
+
- `hovered`
|
|
262
|
+
- `focused`
|
|
263
|
+
- `openFullscreen`
|
|
264
|
+
|
|
265
|
+
```vue
|
|
266
|
+
<VibeLayout :resolve="resolve">
|
|
267
|
+
<template #grid-item-overlay="{ item, hovered }">
|
|
268
|
+
<div v-if="hovered" class="absolute inset-x-0 bottom-0 p-3">
|
|
269
|
+
<div class="pointer-events-auto bg-black/45 px-3 py-2 backdrop-blur">
|
|
270
|
+
{{ item.title }}
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
</template>
|
|
274
|
+
</VibeLayout>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### `#grid-footer`
|
|
278
|
+
|
|
279
|
+
Desktop grid-only footer surface for app-owned status bars or controls.
|
|
280
|
+
|
|
281
|
+
```vue
|
|
282
|
+
<VibeLayout :resolve="resolve">
|
|
283
|
+
<template #grid-footer>
|
|
284
|
+
<div class="bg-black/55 px-4 py-3 backdrop-blur">
|
|
285
|
+
Custom footer UI
|
|
286
|
+
</div>
|
|
287
|
+
</template>
|
|
288
|
+
</VibeLayout>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Exposed handle
|
|
292
|
+
|
|
293
|
+
Get a component ref and use `VibeHandle`:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import { ref } from 'vue'
|
|
297
|
+
import type { VibeHandle } from '@wyxos/vibe'
|
|
298
|
+
|
|
299
|
+
const vibe = ref<VibeHandle | null>(null)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
```vue
|
|
303
|
+
<VibeLayout ref="vibe" :resolve="resolve" />
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Available methods:
|
|
307
|
+
|
|
308
|
+
- `remove(ids)`
|
|
309
|
+
- `restore(ids)`
|
|
310
|
+
- `undo()`
|
|
311
|
+
- `getRemovedIds()`
|
|
312
|
+
- `clearRemoved()`
|
|
313
|
+
|
|
314
|
+
Available state:
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
type VibeStatus = {
|
|
318
|
+
activeIndex: number
|
|
319
|
+
currentCursor: string | null
|
|
320
|
+
errorMessage: string | null
|
|
321
|
+
fillCollectedCount: number | null
|
|
322
|
+
fillDelayRemainingMs: number | null
|
|
323
|
+
fillTargetCount: number | null
|
|
324
|
+
hasNextPage: boolean
|
|
325
|
+
hasPreviousPage: boolean
|
|
326
|
+
isAutoMode: boolean
|
|
327
|
+
itemCount: number
|
|
328
|
+
loadState: 'failed' | 'loaded' | 'loading'
|
|
329
|
+
mode: 'dynamic' | 'static' | null
|
|
330
|
+
nextCursor: string | null
|
|
331
|
+
phase: 'failed' | 'filling' | 'idle' | 'loading' | 'reloading'
|
|
332
|
+
previousCursor: string | null
|
|
333
|
+
removedCount: number
|
|
334
|
+
surfaceMode: 'fullscreen' | 'list'
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Example:
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
vibe.value?.remove(['item-2', 'item-5'])
|
|
342
|
+
vibe.value?.undo()
|
|
343
|
+
console.log(vibe.value?.status.itemCount)
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Events
|
|
347
|
+
|
|
348
|
+
`VibeLayout` emits:
|
|
349
|
+
|
|
350
|
+
- `update:activeIndex`
|
|
351
|
+
- `asset-loads`
|
|
352
|
+
- `asset-errors`
|
|
353
|
+
|
|
354
|
+
`asset-loads` is micro-batched and emits an array payload:
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
type VibeAssetLoadEvent = {
|
|
358
|
+
item: VibeViewerItem
|
|
359
|
+
occurrenceKey: string
|
|
360
|
+
url: string
|
|
361
|
+
surface: 'grid' | 'fullscreen'
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
`asset-errors` is micro-batched and emits an array payload:
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
type VibeAssetErrorEvent = {
|
|
369
|
+
item: VibeViewerItem
|
|
370
|
+
occurrenceKey: string
|
|
371
|
+
url: string
|
|
372
|
+
kind: 'generic' | 'not-found'
|
|
373
|
+
surface: 'grid' | 'fullscreen'
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Example:
|
|
378
|
+
|
|
379
|
+
```vue
|
|
380
|
+
<script setup lang="ts">
|
|
381
|
+
import type { VibeAssetErrorEvent, VibeAssetLoadEvent } from '@wyxos/vibe'
|
|
382
|
+
|
|
383
|
+
function onAssetLoads(loads: VibeAssetLoadEvent[]) {
|
|
384
|
+
console.log(loads)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function onAssetErrors(errors: VibeAssetErrorEvent[]) {
|
|
388
|
+
console.log(errors)
|
|
389
|
+
}
|
|
390
|
+
</script>
|
|
391
|
+
|
|
392
|
+
<template>
|
|
393
|
+
<VibeLayout
|
|
394
|
+
:resolve="resolve"
|
|
395
|
+
@asset-loads="onAssetLoads"
|
|
396
|
+
@asset-errors="onAssetErrors"
|
|
397
|
+
/>
|
|
398
|
+
</template>
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Notes:
|
|
402
|
+
|
|
403
|
+
- successful loads that happen close together are batched into one event
|
|
404
|
+
- grid load events usually report `preview.url` when a preview exists
|
|
405
|
+
- fullscreen load events report `item.url`
|
|
406
|
+
- multiple failures that happen close together are batched into one event
|
|
407
|
+
- identical failures are deduped inside the same batch
|
|
408
|
+
- if the same item fails again later, it can emit again in a later batch
|
|
409
|
+
- built-in Vibe error surfaces allow retrying `generic` failures, but not `not-found`
|
|
410
|
+
|
|
411
|
+
## Surface behavior
|
|
412
|
+
|
|
413
|
+
- Desktop starts in the masonry grid
|
|
414
|
+
- Clicking a grid tile opens fullscreen
|
|
415
|
+
- `Escape` returns from fullscreen to grid on desktop
|
|
416
|
+
- Mobile and tablet always force fullscreen
|
|
417
|
+
- Grid uses preview assets and in-view loading
|
|
418
|
+
- Fullscreen uses the original asset and shows a spinner until ready
|
|
419
|
+
|
|
420
|
+
## Local demo routes
|
|
421
|
+
|
|
422
|
+
Run:
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
npm run dev
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Routes:
|
|
429
|
+
|
|
430
|
+
- `/` – clean default demo
|
|
431
|
+
- `/documentation` – in-app documentation
|
|
432
|
+
- `/demo/dynamic-feed` – dynamic feed fill-loop demo
|
|
433
|
+
- `/demo/advanced-integration` – advanced static integration demo
|
|
434
|
+
- `/debug/fake-server` – fake-server inspection route
|
|
435
|
+
|
|
436
|
+
## Local development
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
npm run check
|
|
440
|
+
npm run build
|
|
441
|
+
npm run build:lib
|
|
442
|
+
npm run build:types
|
|
443
|
+
npm run test:unit
|
|
444
|
+
npm run test:e2e
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Notes
|
|
448
|
+
|
|
449
|
+
- `lib/` is generated output
|
|
450
|
+
- Source of truth is under `src/`
|
|
451
|
+
- The package ships compiled CSS at `@wyxos/vibe/style.css`
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
currentIndex: number;
|
|
3
|
+
paginationDetail?: string | null;
|
|
4
|
+
showBackToList?: boolean;
|
|
5
|
+
showEndBadge?: boolean;
|
|
6
|
+
title?: string | null;
|
|
7
|
+
total: number;
|
|
8
|
+
};
|
|
9
|
+
declare var __VLS_6: {};
|
|
10
|
+
type __VLS_Slots = {} & {
|
|
11
|
+
actions?: (props: typeof __VLS_6) => any;
|
|
12
|
+
};
|
|
13
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
14
|
+
"back-to-list": () => any;
|
|
15
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
16
|
+
"onBack-to-list"?: (() => any) | undefined;
|
|
17
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
19
|
+
declare const _default: typeof __VLS_export;
|
|
20
|
+
export default _default;
|
|
21
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
22
|
+
new (): {
|
|
23
|
+
$slots: S;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
currentTime: number;
|
|
3
|
+
currentTimeLabel: string;
|
|
4
|
+
duration: number;
|
|
5
|
+
durationLabel: string;
|
|
6
|
+
progress: number;
|
|
7
|
+
};
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"seek-input": (event: Event) => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
11
|
+
"onSeek-input"?: ((event: Event) => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
declare const _default: typeof __VLS_export;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Component } from 'vue';
|
|
2
|
+
import type { VibeViewerItem } from './viewer';
|
|
3
|
+
import type { VibeAssetErrorReporter, VibeAssetLoadReporter } from './viewer-core/assetErrors';
|
|
4
|
+
import type { VibeFullscreenStatusSlotProps, VibeSurfaceSlotProps } from './viewer-core/surfaceSlots';
|
|
5
|
+
import type { VibeControlledProps } from './viewer-core/useViewer';
|
|
6
|
+
import './viewer-core/fullscreenMediaBar.css';
|
|
7
|
+
type __VLS_Props = VibeControlledProps & {
|
|
8
|
+
active?: boolean;
|
|
9
|
+
reportAssetError?: VibeAssetErrorReporter | null;
|
|
10
|
+
reportAssetLoad?: VibeAssetLoadReporter | null;
|
|
11
|
+
showBackToList?: boolean;
|
|
12
|
+
};
|
|
13
|
+
type __VLS_Slots = {
|
|
14
|
+
'fullscreen-aside'?: (props: VibeSurfaceSlotProps) => unknown;
|
|
15
|
+
'fullscreen-header-actions'?: (props: VibeSurfaceSlotProps) => unknown;
|
|
16
|
+
'fullscreen-overlay'?: (props: VibeSurfaceSlotProps) => unknown;
|
|
17
|
+
'fullscreen-status'?: (props: VibeFullscreenStatusSlotProps) => unknown;
|
|
18
|
+
'item-icon'?: (props: {
|
|
19
|
+
icon: Component;
|
|
20
|
+
item: VibeViewerItem;
|
|
21
|
+
}) => unknown;
|
|
22
|
+
};
|
|
23
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
24
|
+
"update:activeIndex": (value: number) => any;
|
|
25
|
+
"back-to-list": () => any;
|
|
26
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
27
|
+
"onUpdate:activeIndex"?: ((value: number) => any) | undefined;
|
|
28
|
+
"onBack-to-list"?: (() => any) | undefined;
|
|
29
|
+
}>, {
|
|
30
|
+
loading: boolean;
|
|
31
|
+
activeIndex: number;
|
|
32
|
+
hasNextPage: boolean;
|
|
33
|
+
paginationDetail: string | null;
|
|
34
|
+
showBackToList: boolean;
|
|
35
|
+
active: boolean;
|
|
36
|
+
reportAssetError: VibeAssetErrorReporter | null;
|
|
37
|
+
reportAssetLoad: VibeAssetLoadReporter | null;
|
|
38
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
39
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
40
|
+
declare const _default: typeof __VLS_export;
|
|
41
|
+
export default _default;
|
|
42
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
43
|
+
new (): {
|
|
44
|
+
$slots: S;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Component } from 'vue';
|
|
2
|
+
import type { VibeViewerItem } from './viewer';
|
|
3
|
+
import { type VibeAssetErrorEvent, type VibeAssetLoadEvent } from './viewer-core/assetErrors';
|
|
4
|
+
import type { VibeFullscreenStatusSlotProps, VibeGridStatusSlotProps, VibeSurfaceSlotProps } from './viewer-core/surfaceSlots';
|
|
5
|
+
import type { VibeHandle } from './viewer-core/useViewer';
|
|
6
|
+
type __VLS_Slots = {
|
|
7
|
+
'fullscreen-aside'?: (props: VibeSurfaceSlotProps) => unknown;
|
|
8
|
+
'fullscreen-header-actions'?: (props: VibeSurfaceSlotProps) => unknown;
|
|
9
|
+
'fullscreen-overlay'?: (props: VibeSurfaceSlotProps) => unknown;
|
|
10
|
+
'fullscreen-status'?: (props: VibeFullscreenStatusSlotProps) => unknown;
|
|
11
|
+
'grid-footer'?: () => unknown;
|
|
12
|
+
'grid-item-overlay'?: (props: {
|
|
13
|
+
active: boolean;
|
|
14
|
+
focused: boolean;
|
|
15
|
+
hovered: boolean;
|
|
16
|
+
index: number;
|
|
17
|
+
item: VibeViewerItem;
|
|
18
|
+
openFullscreen: () => void;
|
|
19
|
+
}) => unknown;
|
|
20
|
+
'grid-status'?: (props: VibeGridStatusSlotProps) => unknown;
|
|
21
|
+
'item-icon'?: (props: {
|
|
22
|
+
icon: Component;
|
|
23
|
+
item: VibeViewerItem;
|
|
24
|
+
}) => unknown;
|
|
25
|
+
};
|
|
26
|
+
declare const __VLS_base: import("vue").DefineComponent<import("..").VibeControlledProps | import("..").VibeAutoProps, VibeHandle, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
27
|
+
"update:activeIndex": (value: number) => any;
|
|
28
|
+
"asset-errors": (errors: VibeAssetErrorEvent[]) => any;
|
|
29
|
+
"asset-loads": (loads: VibeAssetLoadEvent[]) => any;
|
|
30
|
+
}, string, import("vue").PublicProps, Readonly<import("..").VibeControlledProps | import("..").VibeAutoProps> & Readonly<{
|
|
31
|
+
"onUpdate:activeIndex"?: ((value: number) => any) | undefined;
|
|
32
|
+
"onAsset-errors"?: ((errors: VibeAssetErrorEvent[]) => any) | undefined;
|
|
33
|
+
"onAsset-loads"?: ((loads: VibeAssetLoadEvent[]) => any) | undefined;
|
|
34
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
35
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
36
|
+
declare const _default: typeof __VLS_export;
|
|
37
|
+
export default _default;
|
|
38
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
39
|
+
new (): {
|
|
40
|
+
$slots: S;
|
|
41
|
+
};
|
|
42
|
+
};
|