@wyxos/vibe 5.10.0 → 5.11.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 CHANGED
@@ -170,8 +170,11 @@ need to select exact grouped media independently of its array position.
170
170
  ## Media lifecycle hooks
171
171
 
172
172
  Use `onMediaReady` when an image has loaded or a video has enough metadata for
173
- Vibe to render it, and `onReelMediaChange` when a reel first selects media or
174
- moves to another parent, nested, or single-item post:
173
+ Vibe to render it. `onMediaVisible` reports each ready masonry media item once
174
+ per mount when it first intersects the viewport. `onMediaFullyVisible` reports
175
+ each ready media item once per layout: after the masonry card reaches its full
176
+ visibility threshold, or after the media is active and ready in a reel.
177
+ `onReelMediaChange` reports reel selection changes:
175
178
 
176
179
  ```ts
177
180
  const vibe = createVibe({
@@ -180,19 +183,29 @@ const vibe = createVibe({
180
183
  onMediaReady: ({ postId, mediaId, layout, origin }) => {
181
184
  recordMediaReady({ postId, mediaId, layout, origin })
182
185
  },
186
+ onMediaVisible: ({ postId, mediaIndex }) => {
187
+ recordMediaPreviewed({ postId, mediaIndex })
188
+ },
189
+ onMediaFullyVisible: ({ postId, mediaIndex, layout }) => {
190
+ recordMediaViewed({ postId, mediaIndex, layout })
191
+ },
183
192
  onReelMediaChange: ({ postId, mediaId, postIndex, mediaIndex }) => {
184
193
  updateActiveMedia({ postId, mediaId, postIndex, mediaIndex })
185
194
  },
186
195
  })
187
196
  ```
188
197
 
189
- Both optional callbacks receive `VibeMediaLifecycleContext`: stable `postId`
198
+ All lifecycle callbacks receive `VibeMediaLifecycleContext`: stable `postId`
190
199
  and `mediaId` identity, zero-based `postIndex` and `mediaIndex`, the complete
191
200
  `item` and selected `media`, the rendered `layout`, and `origin`. `mediaId` is
192
201
  `null` when the source asset does not define one. `origin` is `null` for the
193
202
  masonry grid, `reel` for the base reel, and `masonry` for a reel opened from a
194
203
  masonry card. The readiness hook may run again if media remounts and becomes
195
- ready again; reel-change events describe selection changes, not load state.
204
+ ready again. Visibility callbacks are deduplicated for the lifetime of a mount.
205
+ Normal masonry cards require their complete height; cards taller than the
206
+ viewport require 80% of their maximum attainable visible height. Reel fully
207
+ visible events depend on active-and-ready state rather than intersection.
208
+ Reel-change events describe selection changes, not load state.
196
209
 
197
210
  ## Reel URLs with Vue Router
198
211
 
@@ -12,6 +12,7 @@ declare const __VLS_export: import("vue").DefineComponent<MasonryFeedProps, {
12
12
  retryEnd: () => any;
13
13
  activate: (postId: VibeItemId, input: "keyboard" | "pointer") => any;
14
14
  mediaChange: (postId: VibeItemId, mediaIndex: number) => any;
15
+ fullyVisible: (postId: VibeItemId, mediaIndex: number) => any;
15
16
  visible: (postId: VibeItemId, mediaIndex: number) => any;
16
17
  }, string, import("vue").PublicProps, Readonly<MasonryFeedProps> & Readonly<{
17
18
  onReady?: ((postId: VibeItemId, mediaIndex: number) => any) | undefined;
@@ -20,6 +21,7 @@ declare const __VLS_export: import("vue").DefineComponent<MasonryFeedProps, {
20
21
  onRetryEnd?: (() => any) | undefined;
21
22
  onActivate?: ((postId: VibeItemId, input: "keyboard" | "pointer") => any) | undefined;
22
23
  onMediaChange?: ((postId: VibeItemId, mediaIndex: number) => any) | undefined;
24
+ onFullyVisible?: ((postId: VibeItemId, mediaIndex: number) => any) | undefined;
23
25
  onVisible?: ((postId: VibeItemId, mediaIndex: number) => any) | undefined;
24
26
  }>, {
25
27
  leavingPostIds: ReadonlySet<VibeItemId>;
@@ -36,6 +36,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
36
36
  retryForward: () => any;
37
37
  activeReelChange: (postId: VibeItemId) => any;
38
38
  closeReel: () => any;
39
+ mediaFullyVisible: (context: VibeMediaLifecycleContext) => any;
39
40
  mediaReady: (context: VibeMediaLifecycleContext) => any;
40
41
  mediaVisible: (context: VibeMediaLifecycleContext) => any;
41
42
  openReel: (postId: VibeItemId) => any;
@@ -48,6 +49,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
48
49
  onRetryForward?: (() => any) | undefined;
49
50
  onActiveReelChange?: ((postId: VibeItemId) => any) | undefined;
50
51
  onCloseReel?: (() => any) | undefined;
52
+ onMediaFullyVisible?: ((context: VibeMediaLifecycleContext) => any) | undefined;
51
53
  onMediaReady?: ((context: VibeMediaLifecycleContext) => any) | undefined;
52
54
  onMediaVisible?: ((context: VibeMediaLifecycleContext) => any) | undefined;
53
55
  onOpenReel?: ((postId: VibeItemId) => any) | undefined;
@@ -0,0 +1,13 @@
1
+ import type { MediaPreviewState } from '../core/mediaPreview';
2
+ import type { VibeItem, VibeItemId } from '../types';
3
+ interface MasonryMediaVisibilityOptions {
4
+ fullyVisibleIndices: () => readonly number[];
5
+ items: () => readonly VibeItem[];
6
+ mediaIndices: () => ReadonlyMap<VibeItemId, number>;
7
+ onFullyVisible: (postId: VibeItemId, mediaIndex: number) => void;
8
+ onVisible: (postId: VibeItemId, mediaIndex: number) => void;
9
+ previewStates: () => ReadonlyMap<string, MediaPreviewState>;
10
+ visibleIndices: () => readonly number[];
11
+ }
12
+ export declare function useMasonryMediaVisibility(options: MasonryMediaVisibilityOptions): void;
13
+ export {};
@@ -2,11 +2,13 @@ import type { MediaPreviewState } from '../core/mediaPreview';
2
2
  import type { VibeRuntimeState } from '../core/runtime';
3
3
  import type { VibeItemId, VibeMediaLifecycleContext } from '../types';
4
4
  interface MediaLifecycleEvents {
5
+ fullyVisible: (context: VibeMediaLifecycleContext) => void;
5
6
  ready: (context: VibeMediaLifecycleContext) => void;
6
7
  reelChange: (context: VibeMediaLifecycleContext) => void;
7
8
  visible: (context: VibeMediaLifecycleContext) => void;
8
9
  }
9
10
  export declare function useMediaLifecycle(state: VibeRuntimeState, events: MediaLifecycleEvents): {
11
+ markMasonryFullyVisible: (postId: VibeItemId, mediaIndex: number) => void;
10
12
  markMasonryVisible: (postId: VibeItemId, mediaIndex: number) => void;
11
13
  markPreviewError: (postId: VibeItemId, mediaIndex: number) => void;
12
14
  markPreviewReady: (postId: VibeItemId, mediaIndex: number) => void;
@@ -2,6 +2,7 @@ import { type Ref } from 'vue';
2
2
  import type { VibeLayout } from '../types';
3
3
  interface ReelTimedMediaActivityOptions {
4
4
  active: () => boolean | undefined;
5
+ inViewport: () => boolean;
5
6
  layout: () => VibeLayout;
6
7
  mediaElement: Ref<HTMLMediaElement | null>;
7
8
  mediaIsMuted: Ref<boolean>;
@@ -2,6 +2,7 @@ import { type Ref } from 'vue';
2
2
  import type { VibeLayout, VibeMediaCardOptions, VibeReelAudioState } from '../types';
3
3
  interface TimedMediaCardOptions {
4
4
  active: () => boolean | undefined;
5
+ inViewport: () => boolean;
5
6
  layout: () => VibeLayout;
6
7
  mediaCard: () => VibeMediaCardOptions | undefined;
7
8
  mediaElement: Ref<HTMLMediaElement | null>;
@@ -26,6 +26,10 @@ export interface MasonryViewportOptions {
26
26
  viewportHeight: number;
27
27
  overscan: number;
28
28
  }
29
+ export interface MasonryVisibilityOptions {
30
+ scrollTop: number;
31
+ viewportHeight: number;
32
+ }
29
33
  interface MasonryOptions {
30
34
  additionalHeight?: number;
31
35
  gap: number;
@@ -33,5 +37,6 @@ interface MasonryOptions {
33
37
  }
34
38
  export declare function calculateMasonryEntryOffset(options: MasonryEntryOptions): number;
35
39
  export declare function calculateVisibleMasonryIndices(items: MasonryPosition[], options: MasonryViewportOptions): number[];
40
+ export declare function calculateFullyVisibleMasonryIndices(items: readonly MasonryPosition[], candidates: readonly number[], options: MasonryVisibilityOptions): number[];
36
41
  export declare function calculateMasonryLayout(media: readonly MasonryMediaDimensions[], containerWidth: number, options: MasonryOptions): MasonryLayout;
37
42
  export {};