@shortkitsdk/react-native 0.2.12 → 0.2.14

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.
Files changed (48) hide show
  1. package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +47 -4
  2. package/android/src/main/java/com/shortkit/reactnative/ReactVideoCarouselOverlayHost.kt +431 -0
  3. package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +83 -0
  4. package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +2 -0
  5. package/ios/ReactCarouselOverlayHost.swift +37 -17
  6. package/ios/ReactOverlayHost.swift +20 -8
  7. package/ios/ReactVideoCarouselOverlayHost.swift +283 -0
  8. package/ios/ShortKitBridge.swift +42 -0
  9. package/ios/ShortKitModule.mm +2 -1
  10. package/ios/ShortKitSDK.xcframework/Info.plist +4 -4
  11. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +1833 -201
  12. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +51 -1
  13. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  14. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +51 -1
  15. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  16. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources +168 -0
  17. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +1833 -201
  18. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +51 -1
  19. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  20. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +51 -1
  21. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  22. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/_CodeSignature/CodeResources +168 -0
  23. package/ios/ShortKitSDK.xcframework.bak2/Info.plist +43 -0
  24. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +418 -0
  25. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64/ShortKitSDK.framework/Info.plist +16 -0
  26. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +31351 -0
  27. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +865 -0
  28. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  29. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +865 -0
  30. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64/ShortKitSDK.framework/Modules/module.modulemap +4 -0
  31. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  32. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +418 -0
  33. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64-simulator/ShortKitSDK.framework/Info.plist +16 -0
  34. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +31351 -0
  35. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +865 -0
  36. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  37. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +865 -0
  38. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64-simulator/ShortKitSDK.framework/Modules/module.modulemap +4 -0
  39. package/ios/ShortKitSDK.xcframework.bak2/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  40. package/package.json +1 -1
  41. package/src/ShortKitCarouselOverlaySurface.tsx +57 -2
  42. package/src/ShortKitFeed.tsx +5 -1
  43. package/src/ShortKitOverlaySurface.tsx +4 -5
  44. package/src/ShortKitVideoCarouselOverlaySurface.tsx +156 -0
  45. package/src/index.ts +4 -1
  46. package/src/serialization.ts +7 -0
  47. package/src/specs/NativeShortKitModule.ts +13 -0
  48. package/src/types.ts +39 -1
@@ -0,0 +1,4 @@
1
+ framework module ShortKitSDK {
2
+ header "ShortKitSDK-Swift.h"
3
+ export *
4
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shortkitsdk/react-native",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "ShortKit React Native SDK — short-form video feed",
5
5
  "react-native": "src/index",
6
6
  "source": "src/index",
@@ -1,6 +1,7 @@
1
- import React, { useMemo } from 'react';
1
+ import React, { useState, useEffect, useMemo } from 'react';
2
2
  import { AppRegistry } from 'react-native';
3
3
  import type { CarouselOverlayProps, ImageCarouselItem } from './types';
4
+ import NativeShortKitModule from './specs/NativeShortKitModule';
4
5
 
5
6
  const _carouselRegistry = new Map<string, React.ComponentType<CarouselOverlayProps>>();
6
7
 
@@ -24,7 +25,30 @@ export function registerCarouselOverlayComponent(
24
25
  });
25
26
  }
26
27
 
28
+ /** Subscribe to a named native event, filtered by surfaceId. */
29
+ function useOverlayEvent<T extends { surfaceId: string }>(
30
+ eventName: string,
31
+ surfaceId: string | undefined,
32
+ handler: (event: T) => void,
33
+ ) {
34
+ useEffect(() => {
35
+ if (!surfaceId) return;
36
+
37
+ let sub: { remove: () => void } | undefined;
38
+ const emitter = NativeShortKitModule?.[eventName as keyof typeof NativeShortKitModule];
39
+ if (typeof emitter === 'function') {
40
+ sub = (emitter as (cb: (e: T) => void) => { remove: () => void })((e: T) => {
41
+ if (e.surfaceId !== surfaceId) return;
42
+ handler(e);
43
+ });
44
+ }
45
+
46
+ return () => sub?.remove();
47
+ }, [surfaceId]);
48
+ }
49
+
27
50
  interface RawCarouselSurfaceProps {
51
+ surfaceId?: string;
28
52
  item?: string;
29
53
  }
30
54
 
@@ -38,6 +62,31 @@ function CarouselSurfaceInner(props: InnerProps) {
38
62
  return null;
39
63
  }
40
64
 
65
+ const sid = props.surfaceId;
66
+
67
+ const [isActive, setIsActive] = useState(false);
68
+ const [activeImageIndex, setActiveImageIndex] = useState(0);
69
+
70
+ // isActive delivered via onOverlayFullState (same event as video overlay)
71
+ useOverlayEvent<{
72
+ surfaceId: string;
73
+ isActive: boolean;
74
+ playerState: string;
75
+ isMuted: boolean;
76
+ playbackRate: number;
77
+ captionsEnabled: boolean;
78
+ activeCue: string | null;
79
+ feedScrollPhase: string | null;
80
+ }>('onOverlayFullState', sid, (e) => {
81
+ setIsActive(e.isActive);
82
+ });
83
+
84
+ // activeImageIndex delivered via dedicated event
85
+ useOverlayEvent<{ surfaceId: string; activeImageIndex: number }>(
86
+ 'onCarouselActiveImageChanged', sid,
87
+ (e) => setActiveImageIndex(e.activeImageIndex),
88
+ );
89
+
41
90
  const item: ImageCarouselItem | null = useMemo(() => {
42
91
  if (!props.item) {
43
92
  return null;
@@ -51,5 +100,11 @@ function CarouselSurfaceInner(props: InnerProps) {
51
100
 
52
101
  if (!item) return null;
53
102
 
54
- return <Component item={item} />;
103
+ return (
104
+ <Component
105
+ item={item}
106
+ isActive={isActive}
107
+ activeImageIndex={activeImageIndex}
108
+ />
109
+ );
55
110
  }
@@ -7,6 +7,7 @@ import { ShortKitContext } from './ShortKitContext';
7
7
  import { deserializeContentItem, serializeFeedConfig, serializeFeedInputs } from './serialization';
8
8
  import { registerOverlayComponent } from './ShortKitOverlaySurface';
9
9
  import { registerCarouselOverlayComponent } from './ShortKitCarouselOverlaySurface';
10
+ import { registerVideoCarouselOverlayComponent } from './ShortKitVideoCarouselOverlaySurface';
10
11
 
11
12
  function generateFeedId(): string {
12
13
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
@@ -103,7 +104,10 @@ export const ShortKitFeed = forwardRef<ShortKitFeedHandle, ShortKitFeedProps>(
103
104
  if (config?.carouselOverlay && config.carouselOverlay !== 'none') {
104
105
  registerCarouselOverlayComponent(config.carouselOverlay.name, config.carouselOverlay.component);
105
106
  }
106
- }, [config?.overlay, config?.carouselOverlay]);
107
+ if (config?.videoCarouselOverlay && config.videoCarouselOverlay !== 'none') {
108
+ registerVideoCarouselOverlayComponent(config.videoCarouselOverlay.name, config.videoCarouselOverlay.component);
109
+ }
110
+ }, [config?.overlay, config?.carouselOverlay, config?.videoCarouselOverlay]);
107
111
 
108
112
  const serializedConfig = useMemo(
109
113
  () => (config ? serializeFeedConfig(config) : '{}'),
@@ -252,11 +252,10 @@ function ShortKitOverlaySurfaceInner(props: InnerProps) {
252
252
  },
253
253
  );
254
254
 
255
- // EXPERIMENT: time updates disabled to measure impact on swipe perf
256
- // useOverlayEvent<{ surfaceId: string; current: number; duration: number; buffered: number }>(
257
- // 'onOverlayTimeUpdate', sid,
258
- // (e) => setTime({ current: e.current, duration: e.duration, buffered: e.buffered }),
259
- // );
255
+ useOverlayEvent<{ surfaceId: string; current: number; duration: number; buffered: number }>(
256
+ 'onOverlayTimeUpdate', sid,
257
+ (e) => setTime({ current: e.current, duration: e.duration, buffered: e.buffered }),
258
+ );
260
259
 
261
260
  // Batched full-state sync — replaces 7 individual events on swipe settle.
262
261
  // All setState calls within one handler = one React render (auto-batched).
@@ -0,0 +1,156 @@
1
+ import React, { useState, useEffect, useMemo } from 'react';
2
+ import { AppRegistry } from 'react-native';
3
+ import type { VideoCarouselOverlayProps, VideoCarouselItem, ContentItem, PlayerTime, PlayerState } from './types';
4
+ import NativeShortKitModule from './specs/NativeShortKitModule';
5
+
6
+ const _videoCarouselRegistry = new Map<string, React.ComponentType<VideoCarouselOverlayProps>>();
7
+
8
+ /**
9
+ * Register a named video carousel overlay component for rendering inside feed cells.
10
+ * Called by ShortKitFeed on mount via useLayoutEffect.
11
+ * Idempotent — registering the same name twice is a no-op.
12
+ */
13
+ export function registerVideoCarouselOverlayComponent(
14
+ name: string,
15
+ component: React.ComponentType<VideoCarouselOverlayProps>,
16
+ ) {
17
+ if (_videoCarouselRegistry.has(name)) return;
18
+ _videoCarouselRegistry.set(name, component);
19
+
20
+ const moduleName = `ShortKitVideoCarouselOverlay_${name}`;
21
+ AppRegistry.registerComponent(moduleName, () => {
22
+ return function NamedVideoCarouselSurface(props: RawProps) {
23
+ return <VideoCarouselSurfaceInner {...props} overlayName={name} />;
24
+ };
25
+ });
26
+ }
27
+
28
+ /** Subscribe to a named native event, filtered by surfaceId. */
29
+ function useOverlayEvent<T extends { surfaceId: string }>(
30
+ eventName: string,
31
+ surfaceId: string | undefined,
32
+ handler: (event: T) => void,
33
+ ) {
34
+ useEffect(() => {
35
+ if (!surfaceId) return;
36
+
37
+ let sub: { remove: () => void } | undefined;
38
+ const emitter = NativeShortKitModule?.[eventName as keyof typeof NativeShortKitModule];
39
+ if (typeof emitter === 'function') {
40
+ sub = (emitter as (cb: (e: T) => void) => { remove: () => void })((e: T) => {
41
+ if (e.surfaceId !== surfaceId) return;
42
+ handler(e);
43
+ });
44
+ }
45
+
46
+ return () => sub?.remove();
47
+ }, [surfaceId]);
48
+ }
49
+
50
+ interface RawProps {
51
+ surfaceId?: string;
52
+ carouselItem?: string;
53
+ activeVideo?: string;
54
+ activeVideoIndex?: number;
55
+ isActive?: boolean;
56
+ playerState?: string;
57
+ isMuted?: boolean;
58
+ }
59
+
60
+ interface InnerProps extends RawProps {
61
+ overlayName: string;
62
+ }
63
+
64
+ function VideoCarouselSurfaceInner(props: InnerProps) {
65
+ const Component = _videoCarouselRegistry.get(props.overlayName);
66
+ if (!Component) return null;
67
+
68
+ const sid = props.surfaceId;
69
+
70
+ // Playback state — initialized from surface props, updated via events
71
+ const [isActive, setIsActive] = useState(props.isActive ?? false);
72
+ const [playerState, setPlayerState] = useState<PlayerState>((props.playerState as PlayerState) ?? 'idle');
73
+ const [isMuted, setIsMuted] = useState(props.isMuted ?? true);
74
+ const [time, setTime] = useState<PlayerTime>({ current: 0, duration: 0, buffered: 0 });
75
+
76
+ // Batched full-state sync
77
+ useOverlayEvent<{
78
+ surfaceId: string;
79
+ isActive: boolean;
80
+ playerState: string;
81
+ isMuted: boolean;
82
+ playbackRate: number;
83
+ captionsEnabled: boolean;
84
+ activeCue: string | null;
85
+ feedScrollPhase: string | null;
86
+ }>('onOverlayFullState', sid, (e) => {
87
+ setIsActive(e.isActive);
88
+ setPlayerState(e.playerState as PlayerState);
89
+ setIsMuted(e.isMuted);
90
+ });
91
+
92
+ // Individual state updates
93
+ useOverlayEvent<{ surfaceId: string; playerState: string }>(
94
+ 'onOverlayPlayerStateChanged', sid,
95
+ (e) => setPlayerState(e.playerState as PlayerState),
96
+ );
97
+
98
+ useOverlayEvent<{ surfaceId: string; isMuted: boolean }>(
99
+ 'onOverlayMutedChanged', sid,
100
+ (e) => setIsMuted(e.isMuted),
101
+ );
102
+
103
+ // Time updates (250ms coalesced)
104
+ useOverlayEvent<{ surfaceId: string; current: number; duration: number; buffered: number }>(
105
+ 'onOverlayTimeUpdate', sid,
106
+ (e) => setTime({ current: e.current, duration: e.duration, buffered: e.buffered }),
107
+ );
108
+
109
+ // Carousel item — from surface props (set once in configure())
110
+ const carouselItem: VideoCarouselItem | null = useMemo(() => {
111
+ if (!props.carouselItem) return null;
112
+ try { return JSON.parse(props.carouselItem); } catch { return null; }
113
+ }, [props.carouselItem]);
114
+
115
+ // Active video — initialized from surface props, updated via events on swipe
116
+ const initialActiveVideo: ContentItem | null = useMemo(() => {
117
+ if (!props.activeVideo) return null;
118
+ try { return JSON.parse(props.activeVideo); } catch { return null; }
119
+ }, [props.activeVideo]);
120
+
121
+ const [activeVideo, setActiveVideo] = useState<ContentItem | null>(initialActiveVideo);
122
+ const [activeVideoIndex, setActiveVideoIndex] = useState(props.activeVideoIndex ?? 0);
123
+
124
+ // Sync from props when surface is reconfigured (new carousel item)
125
+ useEffect(() => {
126
+ setActiveVideo(initialActiveVideo);
127
+ setActiveVideoIndex(props.activeVideoIndex ?? 0);
128
+ }, [initialActiveVideo]);
129
+
130
+ // Active video changes via event (no Fabric remount)
131
+ useOverlayEvent<{ surfaceId: string; activeVideo: string; activeVideoIndex: number }>(
132
+ 'onVideoCarouselActiveVideoChanged', sid,
133
+ (e) => {
134
+ try {
135
+ setActiveVideo(JSON.parse(e.activeVideo));
136
+ } catch {
137
+ // ignore parse errors
138
+ }
139
+ setActiveVideoIndex(e.activeVideoIndex);
140
+ },
141
+ );
142
+
143
+ if (!carouselItem || !activeVideo) return null;
144
+
145
+ return (
146
+ <Component
147
+ carouselItem={carouselItem}
148
+ activeVideo={activeVideo}
149
+ activeVideoIndex={activeVideoIndex}
150
+ isActive={isActive}
151
+ time={time}
152
+ playerState={playerState}
153
+ isMuted={isMuted}
154
+ />
155
+ );
156
+ }
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ export type {
11
11
  FeedSource,
12
12
  OverlayConfig,
13
13
  CarouselOverlayConfig,
14
+ VideoCarouselOverlayConfig,
14
15
  SurveyMode,
15
16
 
16
17
  PlayerConfig,
@@ -20,6 +21,7 @@ export type {
20
21
  ContentItem,
21
22
  CarouselImage,
22
23
  ImageCarouselItem,
24
+ VideoCarouselItem,
23
25
  FeedInput,
24
26
  JSONValue,
25
27
  CaptionTrack,
@@ -46,4 +48,5 @@ export { ShortKitCommands } from './ShortKitCommands';
46
48
  export { default as NativeShortKitModule } from './specs/NativeShortKitModule';
47
49
  export { registerOverlayComponent } from './ShortKitOverlaySurface';
48
50
  export { registerCarouselOverlayComponent } from './ShortKitCarouselOverlaySurface';
49
- export type { OverlayProps, CarouselOverlayProps } from './types';
51
+ export { registerVideoCarouselOverlayComponent } from './ShortKitVideoCarouselOverlaySurface';
52
+ export type { OverlayProps, CarouselOverlayProps, VideoCarouselOverlayProps } from './types';
@@ -25,11 +25,18 @@ export function serializeFeedConfig(config: FeedConfig): string {
25
25
  return JSON.stringify({ type: 'custom', name: raw.name });
26
26
  })();
27
27
 
28
+ const videoCarouselOverlay = (() => {
29
+ const raw = config.videoCarouselOverlay ?? 'none';
30
+ if (typeof raw === 'string') return JSON.stringify(raw);
31
+ return JSON.stringify({ type: 'custom', name: raw.name });
32
+ })();
33
+
28
34
  return JSON.stringify({
29
35
  feedHeight: JSON.stringify(config.feedHeight ?? { type: 'fullscreen' }),
30
36
  scrollAxis: config.scrollAxis ?? 'vertical',
31
37
  overlay,
32
38
  carouselOverlay,
39
+ videoCarouselOverlay,
33
40
  surveyMode: JSON.stringify(config.surveyMode ?? 'none'),
34
41
  muteOnStart: config.muteOnStart ?? true,
35
42
  feedSource: config.feedSource ?? 'algorithmic',
@@ -156,6 +156,17 @@ type OverlayTimeUpdateEvent = Readonly<{
156
156
  buffered: Double;
157
157
  }>;
158
158
 
159
+ type CarouselActiveImageEvent = Readonly<{
160
+ surfaceId: string;
161
+ activeImageIndex: Int32;
162
+ }>;
163
+
164
+ type VideoCarouselActiveVideoEvent = Readonly<{
165
+ surfaceId: string;
166
+ activeVideo: string;
167
+ activeVideoIndex: Int32;
168
+ }>;
169
+
159
170
  type OverlayFullStateEvent = Readonly<{
160
171
  surfaceId: string;
161
172
  isActive: boolean;
@@ -239,6 +250,8 @@ export interface Spec extends TurboModule {
239
250
  readonly onOverlayFeedScrollPhaseChanged: EventEmitter<OverlayFeedScrollPhaseEvent>;
240
251
  readonly onOverlayTimeUpdate: EventEmitter<OverlayTimeUpdateEvent>;
241
252
  readonly onOverlayFullState: EventEmitter<OverlayFullStateEvent>;
253
+ readonly onCarouselActiveImageChanged: EventEmitter<CarouselActiveImageEvent>;
254
+ readonly onVideoCarouselActiveVideoChanged: EventEmitter<VideoCarouselActiveVideoEvent>;
242
255
  }
243
256
 
244
257
  export default TurboModuleRegistry.getEnforcing<Spec>('ShortKitModule');
package/src/types.ts CHANGED
@@ -20,6 +20,7 @@ export interface FeedConfig {
20
20
  scrollAxis?: ScrollAxis;
21
21
  overlay?: OverlayConfig;
22
22
  carouselOverlay?: CarouselOverlayConfig;
23
+ videoCarouselOverlay?: VideoCarouselOverlayConfig;
23
24
  surveyMode?: SurveyMode;
24
25
  muteOnStart?: boolean;
25
26
  feedSource?: FeedSource;
@@ -41,6 +42,10 @@ export type CarouselOverlayConfig =
41
42
  | 'none'
42
43
  | { type: 'custom'; name: string; component: React.ComponentType<CarouselOverlayProps> };
43
44
 
45
+ export type VideoCarouselOverlayConfig =
46
+ | 'none'
47
+ | { type: 'custom'; name: string; component: React.ComponentType<VideoCarouselOverlayProps> };
48
+
44
49
  export type SurveyMode =
45
50
  | 'none'
46
51
  | { type: 'template'; name: 'default' };
@@ -81,9 +86,20 @@ export interface ImageCarouselItem {
81
86
  articleUrl?: string;
82
87
  }
83
88
 
89
+ export interface VideoCarouselItem {
90
+ id: string;
91
+ videos: ContentItem[];
92
+ title?: string;
93
+ description?: string;
94
+ author?: string;
95
+ section?: string;
96
+ articleUrl?: string;
97
+ }
98
+
84
99
  export type FeedInput =
85
100
  | { type: 'video'; playbackId: string; fallbackUrl?: string }
86
- | { type: 'imageCarousel'; item: ImageCarouselItem };
101
+ | { type: 'imageCarousel'; item: ImageCarouselItem }
102
+ | { type: 'videoCarousel'; item: VideoCarouselItem };
87
103
 
88
104
  export type JSONValue =
89
105
  | string
@@ -195,6 +211,28 @@ export interface OverlayProps {
195
211
  export interface CarouselOverlayProps {
196
212
  /** The carousel item for this cell. */
197
213
  item: ImageCarouselItem;
214
+ /** Whether this carousel cell is the active (on-screen) cell. */
215
+ isActive: boolean;
216
+ /** Index of the currently visible image within the carousel. */
217
+ activeImageIndex: number;
218
+ }
219
+
220
+ /** Props passed to video carousel overlay components rendered inside feed cells. */
221
+ export interface VideoCarouselOverlayProps {
222
+ /** The video carousel item for this cell. */
223
+ carouselItem: VideoCarouselItem;
224
+ /** The currently active video within the carousel. */
225
+ activeVideo: ContentItem;
226
+ /** Index of the currently active video within the carousel. */
227
+ activeVideoIndex: number;
228
+ /** Whether this carousel cell is the active (on-screen) cell. */
229
+ isActive: boolean;
230
+ /** Current playback time for the active video. */
231
+ time: PlayerTime;
232
+ /** Current player state for the active video. */
233
+ playerState: PlayerState;
234
+ /** Whether audio is muted. */
235
+ isMuted: boolean;
198
236
  }
199
237
 
200
238
  // --- Provider Props ---