@wyxos/vibe 5.0.3 → 5.2.1

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 CHANGED
@@ -128,6 +128,33 @@ transition, while vertical reel scrolling continues to move between posts.
128
128
  Media assets may include a stable `mediaId` (`string | number`) when consumers
129
129
  need to select exact grouped media independently of its array position.
130
130
 
131
+ ## Media lifecycle hooks
132
+
133
+ Use `onMediaReady` when an image has loaded or a video has enough metadata for
134
+ Vibe to render it, and `onReelMediaChange` when a reel first selects media or
135
+ moves to another parent, nested, or single-item post:
136
+
137
+ ```ts
138
+ const vibe = createVibe({
139
+ target: '#gallery',
140
+ loadPage,
141
+ onMediaReady: ({ postId, mediaId, layout, origin }) => {
142
+ recordMediaReady({ postId, mediaId, layout, origin })
143
+ },
144
+ onReelMediaChange: ({ postId, mediaId, postIndex, mediaIndex }) => {
145
+ updateActiveMedia({ postId, mediaId, postIndex, mediaIndex })
146
+ },
147
+ })
148
+ ```
149
+
150
+ Both optional callbacks receive `VibeMediaLifecycleContext`: stable `postId`
151
+ and `mediaId` identity, zero-based `postIndex` and `mediaIndex`, the complete
152
+ `item` and selected `media`, the rendered `layout`, and `origin`. `mediaId` is
153
+ `null` when the source asset does not define one. `origin` is `null` for the
154
+ masonry grid, `reel` for the base reel, and `masonry` for a reel opened from a
155
+ masonry card. The readiness hook may run again if media remounts and becomes
156
+ ready again; reel-change events describe selection changes, not load state.
157
+
131
158
  ## Reel URLs with Vue Router
132
159
 
133
160
  Pass a Vue Router instance when the application should reflect the active reel
@@ -1,5 +1,5 @@
1
1
  import { type VibeRuntimeState } from '../core/runtime';
2
- import type { VibeCardRegion, VibeFeedFooter, VibeFeedFooterActions, VibeItemId, VibeMediaCardOptions, VibeReelInfoSheetOptions, VibeReelItemTarget, VibeReelNavigationResult } from '../types';
2
+ import type { VibeCardRegion, VibeFeedFooter, VibeFeedFooterActions, VibeItemId, VibeMediaCardOptions, VibeMediaLifecycleContext, VibeReelInfoSheetOptions, VibeReelItemTarget, VibeReelNavigationResult } from '../types';
3
3
  type __VLS_Props = {
4
4
  canRetryEnd: boolean;
5
5
  cardFooter?: VibeCardRegion;
@@ -31,7 +31,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
31
31
  retryForward: () => any;
32
32
  activeReelChange: (postId: VibeItemId) => any;
33
33
  closeReel: () => any;
34
+ mediaReady: (context: VibeMediaLifecycleContext) => any;
34
35
  openReel: (postId: VibeItemId) => any;
36
+ reelMediaChange: (context: VibeMediaLifecycleContext) => any;
35
37
  reelInfoSheetChange: (enabled: boolean) => any;
36
38
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
37
39
  onLoadMore?: (() => any) | undefined;
@@ -39,7 +41,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
39
41
  onRetryForward?: (() => any) | undefined;
40
42
  onActiveReelChange?: ((postId: VibeItemId) => any) | undefined;
41
43
  onCloseReel?: (() => any) | undefined;
44
+ onMediaReady?: ((context: VibeMediaLifecycleContext) => any) | undefined;
42
45
  onOpenReel?: ((postId: VibeItemId) => any) | undefined;
46
+ onReelMediaChange?: ((context: VibeMediaLifecycleContext) => any) | undefined;
43
47
  onReelInfoSheetChange?: ((enabled: boolean) => any) | undefined;
44
48
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
45
49
  declare const _default: typeof __VLS_export;
@@ -1,5 +1,6 @@
1
1
  import type { VibeAutofillOptions, VibeAutofillSessionSnapshot, VibeAutofillState, VibeCursor, VibeFrontendAutofillOptions, VibeItem, VibePageLoader } from '../types';
2
2
  import type { VibeRuntimeState } from './runtime';
3
+ import { type LoadedPageRecord } from './page';
3
4
  import { type RequestDelaySnapshot } from './requestDelay';
4
5
  export declare const DEFAULT_MAX_ADDITIONAL_PAGES = 10;
5
6
  export declare function frontendAutofillRequestLimit(options: VibeFrontendAutofillOptions, includeInitialRequest?: boolean): number;
@@ -15,6 +16,7 @@ export interface FrontendAutofillCollection extends FrontendAutofillProgress {
15
16
  total?: number;
16
17
  }
17
18
  export interface FrontendAutofillResult extends FrontendAutofillCollection {
19
+ pages: LoadedPageRecord[];
18
20
  status: 'complete' | 'exhausted';
19
21
  }
20
22
  interface CollectFrontendAutofillOptions {
@@ -3,6 +3,8 @@ import type { VibeRuntimeState } from './runtime';
3
3
  import type { VibeItemId, VibeMediaRemoval, VibeMediaTarget } from '../types';
4
4
  interface ExactMediaRemovalControllerOptions {
5
5
  onActivate: (postId: VibeItemId) => void;
6
+ onPostRemoved: (postId: VibeItemId) => void;
7
+ onPostRestored: (postId: VibeItemId) => void;
6
8
  reelForward: ReelForwardController;
7
9
  state: VibeRuntimeState;
8
10
  }
@@ -1,3 +1,4 @@
1
+ import { type LoadedPageRecord } from './page';
1
2
  import type { VibeFillOptions, VibeFillSessionSnapshot, VibeFillState, VibeFillTarget, VibeCursor, VibeFrontendFillOptions, VibeItem, VibePageLoader } from '../types';
2
3
  import { type RequestDelaySnapshot } from './requestDelay';
3
4
  export interface FrontendFillProgress {
@@ -8,6 +9,7 @@ export interface FrontendFillProgress {
8
9
  export interface FrontendFillResult extends FrontendFillProgress {
9
10
  items: VibeItem[];
10
11
  lastCursor: VibeCursor;
12
+ pages: LoadedPageRecord[];
11
13
  status: 'complete' | 'exhausted';
12
14
  total?: number;
13
15
  }
@@ -1,9 +1,11 @@
1
+ import type { LoadedPageRecord } from './page';
1
2
  import type { VibeRuntimeState } from './runtime';
2
3
  import type { VibeBackendFillUpdate, VibeCursor, VibeFillOptions, VibeFillSessionSnapshot, VibeFillTarget, VibePageLoader } from '../types';
3
4
  interface FillControllerOptions {
4
5
  fill?: VibeFillOptions;
5
6
  loadPage?: VibePageLoader;
6
7
  onLastCursor: (cursor: VibeCursor) => void;
8
+ onPages: (pages: readonly LoadedPageRecord[]) => void;
7
9
  state: VibeRuntimeState;
8
10
  }
9
11
  export declare class VibeFillController {
@@ -1,4 +1,5 @@
1
1
  import { type FrontendAutofillCollection } from './autofill';
2
+ import type { LoadedPageRecord } from './page';
2
3
  import type { VibeRuntimeState } from './runtime';
3
4
  import type { CreateVibeOptions, VibeCursor } from '../types';
4
5
  interface InitialAutofillContext {
@@ -6,9 +7,10 @@ interface InitialAutofillContext {
6
7
  isCurrent: () => boolean;
7
8
  onCollection: (collection: FrontendAutofillCollection) => void;
8
9
  onLastCursor: (cursor: VibeCursor) => void;
10
+ onPages: (pages: readonly LoadedPageRecord[]) => void;
9
11
  options: CreateVibeOptions;
10
12
  signal: AbortSignal;
11
13
  state: VibeRuntimeState;
12
14
  }
13
- export declare function autofillInitialPage({ cycleId, isCurrent, onCollection, onLastCursor, options, signal, state, }: InitialAutofillContext): Promise<void>;
15
+ export declare function autofillInitialPage({ cycleId, isCurrent, onCollection, onLastCursor, onPages, options, signal, state, }: InitialAutofillContext): Promise<void>;
14
16
  export {};
@@ -0,0 +1,4 @@
1
+ import type { VibeRuntimeState } from './runtime';
2
+ import type { VibeItemId, VibeLayout, VibeMediaLifecycleContext, VibeReelOrigin } from '../types';
3
+ export declare function mediaLifecycleContext(state: VibeRuntimeState, postId: VibeItemId, mediaIndex: number, layout: VibeLayout, origin: VibeReelOrigin | null): VibeMediaLifecycleContext | null;
4
+ export declare function useReelMediaChangeLifecycle(state: VibeRuntimeState, onChange: (context: VibeMediaLifecycleContext) => void): void;
@@ -1,3 +1,9 @@
1
- import type { VibeItem, VibePage } from '../types';
1
+ import type { VibeCursor, VibeItem, VibeItemId, VibePage } from '../types';
2
+ export interface LoadedPageRecord {
3
+ contributionIds: VibeItemId[];
4
+ cursor: VibeCursor;
5
+ next: VibeCursor;
6
+ returnedIds: VibeItemId[];
7
+ }
2
8
  export declare function validatePage(page: VibePage): VibePage;
3
9
  export declare function appendUniqueItems(current: readonly VibeItem[], incoming: readonly VibeItem[]): VibeItem[];
@@ -0,0 +1,18 @@
1
+ import type { CreateVibeOptions, VibeCursor, VibePageRequest } from '../types';
2
+ import type { VibeAutofillController } from './autofillController';
3
+ import { type LoadedPageRecord } from './page';
4
+ import type { VibeRuntimeState } from './runtime';
5
+ interface PageRequestOptions {
6
+ append: boolean;
7
+ autofillController: VibeAutofillController;
8
+ cursor: VibeCursor;
9
+ isCurrent: () => boolean;
10
+ loadPage: (request: VibePageRequest) => ReturnType<NonNullable<CreateVibeOptions['loadPage']>>;
11
+ onLastCursor: (cursor: VibeCursor) => void;
12
+ onPages: (pages: readonly LoadedPageRecord[]) => void;
13
+ options: CreateVibeOptions;
14
+ signal: AbortSignal;
15
+ state: VibeRuntimeState;
16
+ }
17
+ export declare function performPageRequest({ append, autofillController, cursor, isCurrent, loadPage, onLastCursor, onPages, options, signal, state, }: PageRequestOptions): Promise<void>;
18
+ export {};
@@ -9,6 +9,8 @@ interface RemovalControllerOptions {
9
9
  historyLimit: number | undefined;
10
10
  loadNext: () => Promise<void>;
11
11
  onActivate: (postId: VibeItemId) => void;
12
+ onItemsRemoved: (postIds: readonly VibeItemId[]) => void;
13
+ onItemsRestored: (postIds: readonly VibeItemId[]) => void;
12
14
  retryEnd: () => Promise<void>;
13
15
  state: VibeRuntimeState;
14
16
  surface: () => VibeSurfaceExpose | null;
@@ -0,0 +1,35 @@
1
+ import type { CreateVibeOptions, VibeCursor, VibeItem, VibeItemId, VibePageLoader } from '../types';
2
+ import { type LoadedPageRecord } from './page';
3
+ interface ReconciliationResult {
4
+ items: VibeItem[];
5
+ lastCursor: VibeCursor;
6
+ next: VibeCursor;
7
+ pages: LoadedPageRecord[];
8
+ replaceFrom: number;
9
+ total?: number;
10
+ }
11
+ interface ReconcileOptions {
12
+ existingItems: readonly VibeItem[];
13
+ loadPage: VibePageLoader;
14
+ signal: AbortSignal;
15
+ }
16
+ export declare class RemovalReconciliationController {
17
+ private readonly delay;
18
+ private readonly enabled;
19
+ private readonly maxReplayPages;
20
+ private readonly pageSize;
21
+ private pages;
22
+ private readonly tombstones;
23
+ constructor(options: CreateVibeOptions);
24
+ completeReconciliation(result: ReconciliationResult): void;
25
+ filterItems(items: readonly VibeItem[]): VibeItem[];
26
+ needsReconciliation(items: readonly VibeItem[]): boolean;
27
+ recordPages(records: readonly LoadedPageRecord[]): void;
28
+ remove(postIds: readonly VibeItemId[]): void;
29
+ reset(): void;
30
+ restore(postIds: readonly VibeItemId[]): void;
31
+ reconcile({ existingItems, loadPage, signal, }: ReconcileOptions): Promise<ReconciliationResult>;
32
+ private findUnderfilledPage;
33
+ private recordPage;
34
+ }
35
+ export {};