@shortkitsdk/react-native 0.2.12 → 0.2.15

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 (65) 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 +117 -2
  4. package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +7 -5
  5. package/ios/ReactCarouselOverlayHost.swift +67 -35
  6. package/ios/ReactOverlayHost.swift +85 -75
  7. package/ios/ReactVideoCarouselOverlayHost.swift +283 -0
  8. package/ios/ShortKitBridge.swift +122 -20
  9. package/ios/ShortKitModule.mm +15 -5
  10. package/ios/ShortKitSDK.xcframework/Info.plist +5 -4
  11. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -0
  12. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Info.plist +5 -1
  13. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +2488 -281
  14. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +65 -5
  15. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  16. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +65 -5
  17. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  18. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources +168 -0
  19. package/ios/{ShortKitSDK.xcframework.bak/ios-arm64-simulator → ShortKitSDK.xcframework/ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +2 -1
  20. package/ios/ShortKitSDK.xcframework/{ios-arm64-simulator → ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Info.plist +5 -1
  21. package/ios/{ShortKitSDK.xcframework.bak/ios-arm64-simulator → ShortKitSDK.xcframework/ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +8233 -3592
  22. package/ios/{ShortKitSDK.xcframework.bak/ios-arm64-simulator → ShortKitSDK.xcframework/ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +120 -19
  23. package/ios/{ShortKitSDK.xcframework.bak/ios-arm64-simulator → ShortKitSDK.xcframework/ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  24. package/ios/{ShortKitSDK.xcframework.bak/ios-arm64-simulator → ShortKitSDK.xcframework/ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +120 -19
  25. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +33558 -0
  26. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +925 -0
  27. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  28. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +925 -0
  29. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  30. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/_CodeSignature/CodeResources +212 -0
  31. package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -1
  32. package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +7338 -3272
  33. package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +106 -15
  34. package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  35. package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +106 -15
  36. package/ios/ShortKitSDK.xcframework.dev-backup/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  37. package/ios/ShortKitSDK.xcframework.dev-backup/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources +168 -0
  38. package/ios/{ShortKitSDK.xcframework → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +1838 -206
  39. package/ios/{ShortKitSDK.xcframework → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +51 -1
  40. package/ios/{ShortKitSDK.xcframework → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  41. package/ios/{ShortKitSDK.xcframework → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +51 -1
  42. package/ios/ShortKitSDK.xcframework.dev-backup/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  43. package/ios/ShortKitSDK.xcframework.dev-backup/ios-arm64-simulator/ShortKitSDK.framework/_CodeSignature/CodeResources +168 -0
  44. package/package.json +1 -1
  45. package/src/ShortKitCarouselOverlaySurface.tsx +57 -2
  46. package/src/ShortKitContext.ts +11 -0
  47. package/src/ShortKitFeed.tsx +25 -10
  48. package/src/ShortKitOverlaySurface.tsx +24 -11
  49. package/src/ShortKitProvider.tsx +4 -2
  50. package/src/ShortKitVideoCarouselOverlaySurface.tsx +156 -0
  51. package/src/ShortKitWidget.tsx +3 -3
  52. package/src/index.ts +5 -1
  53. package/src/serialization.ts +7 -0
  54. package/src/specs/NativeShortKitModule.ts +18 -2
  55. package/src/types.ts +48 -3
  56. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  57. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  58. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  59. package/ios/ShortKitSDK.xcframework/{ios-arm64-simulator → ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Modules/module.modulemap +0 -0
  60. package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/Info.plist +4 -4
  61. /package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Info.plist +0 -0
  62. /package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Modules/module.modulemap +0 -0
  63. /package/ios/{ShortKitSDK.xcframework → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +0 -0
  64. /package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Info.plist +0 -0
  65. /package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Modules/module.modulemap +0 -0
@@ -161,6 +161,8 @@ function ShortKitOverlaySurfaceInner(props: InnerProps) {
161
161
  const [activeCue, setActiveCue] = useState<OverlayProps['activeCue']>(
162
162
  props.activeCue ? JSON.parse(props.activeCue) : null,
163
163
  );
164
+ // Track raw JSON string to avoid re-parsing unchanged cues
165
+ const lastActiveCueJsonRef = React.useRef<string | null>(props.activeCue ?? null);
164
166
  const [feedScrollPhase, setFeedScrollPhase] =
165
167
  useState<FeedScrollPhase | null>(
166
168
  props.feedScrollPhase ? JSON.parse(props.feedScrollPhase) : null,
@@ -176,7 +178,9 @@ function ShortKitOverlaySurfaceInner(props: InnerProps) {
176
178
  setIsMuted(props.isMuted ?? true);
177
179
  setPlaybackRate(props.playbackRate ?? 1);
178
180
  setCaptionsEnabled(props.captionsEnabled ?? false);
179
- setActiveCue(props.activeCue ? JSON.parse(props.activeCue) : null);
181
+ const rawCue = props.activeCue ?? null;
182
+ lastActiveCueJsonRef.current = rawCue;
183
+ setActiveCue(rawCue ? JSON.parse(rawCue) : null);
180
184
  setFeedScrollPhase(
181
185
  props.feedScrollPhase ? JSON.parse(props.feedScrollPhase) : null,
182
186
  );
@@ -229,7 +233,12 @@ function ShortKitOverlaySurfaceInner(props: InnerProps) {
229
233
 
230
234
  useOverlayEvent<{ surfaceId: string; activeCue: string | null }>(
231
235
  'onOverlayActiveCueChanged', sid,
232
- (e) => setActiveCue(e.activeCue ? JSON.parse(e.activeCue) : null),
236
+ (e) => {
237
+ const raw = e.activeCue ?? null;
238
+ if (raw === lastActiveCueJsonRef.current) return;
239
+ lastActiveCueJsonRef.current = raw;
240
+ setActiveCue(raw ? JSON.parse(raw) : null);
241
+ },
233
242
  );
234
243
 
235
244
  useOverlayEvent<{ surfaceId: string; feedScrollPhase: string | null }>(
@@ -252,11 +261,10 @@ function ShortKitOverlaySurfaceInner(props: InnerProps) {
252
261
  },
253
262
  );
254
263
 
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
- // );
264
+ useOverlayEvent<{ surfaceId: string; current: number; duration: number; buffered: number }>(
265
+ 'onOverlayTimeUpdate', sid,
266
+ (e) => setTime({ current: e.current, duration: e.duration, buffered: e.buffered }),
267
+ );
260
268
 
261
269
  // Batched full-state sync — replaces 7 individual events on swipe settle.
262
270
  // All setState calls within one handler = one React render (auto-batched).
@@ -275,10 +283,15 @@ function ShortKitOverlaySurfaceInner(props: InnerProps) {
275
283
  setIsMuted(e.isMuted);
276
284
  setPlaybackRate(e.playbackRate);
277
285
  setCaptionsEnabled(e.captionsEnabled);
278
- try {
279
- setActiveCue(e.activeCue ? JSON.parse(e.activeCue) : null);
280
- } catch {
281
- setActiveCue(null);
286
+ // Only re-parse activeCue if the raw JSON string actually changed
287
+ const rawCue = e.activeCue ?? null;
288
+ if (rawCue !== lastActiveCueJsonRef.current) {
289
+ lastActiveCueJsonRef.current = rawCue;
290
+ try {
291
+ setActiveCue(rawCue ? JSON.parse(rawCue) : null);
292
+ } catch {
293
+ setActiveCue(null);
294
+ }
282
295
  }
283
296
  try {
284
297
  const next = e.feedScrollPhase ? JSON.parse(e.feedScrollPhase) : null;
@@ -1,7 +1,7 @@
1
1
  import React, { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
2
2
  import { AppState } from 'react-native';
3
3
  import type { AppStateStatus } from 'react-native';
4
- import { ShortKitContext } from './ShortKitContext';
4
+ import { ShortKitContext, ShortKitInitContext } from './ShortKitContext';
5
5
  import type { ShortKitContextValue } from './ShortKitContext';
6
6
  import type {
7
7
  ShortKitProviderProps,
@@ -500,6 +500,8 @@ export function ShortKitProvider({
500
500
  );
501
501
 
502
502
  return (
503
- <ShortKitContext.Provider value={value}>{children}</ShortKitContext.Provider>
503
+ <ShortKitInitContext.Provider value={true}>
504
+ <ShortKitContext.Provider value={value}>{children}</ShortKitContext.Provider>
505
+ </ShortKitInitContext.Provider>
504
506
  );
505
507
  }
@@ -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
+ }
@@ -2,7 +2,7 @@ import React, { useContext, useMemo } from 'react';
2
2
  import { View, StyleSheet } from 'react-native';
3
3
  import type { ShortKitWidgetProps } from './types';
4
4
  import ShortKitWidgetView from './specs/ShortKitWidgetViewNativeComponent';
5
- import { ShortKitContext } from './ShortKitContext';
5
+ import { ShortKitInitContext } from './ShortKitContext';
6
6
 
7
7
  /**
8
8
  * Horizontal carousel widget component. Displays a row of video cards
@@ -13,8 +13,8 @@ import { ShortKitContext } from './ShortKitContext';
13
13
  export function ShortKitWidget(props: ShortKitWidgetProps) {
14
14
  const { config, items, style } = props;
15
15
 
16
- const context = useContext(ShortKitContext);
17
- if (!context) {
16
+ const isInitialized = useContext(ShortKitInitContext);
17
+ if (!isInitialized) {
18
18
  throw new Error('ShortKitWidget must be used within a ShortKitProvider');
19
19
  }
20
20
 
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,
@@ -39,6 +41,7 @@ export type {
39
41
  ShortKitPlayerProps,
40
42
  ShortKitWidgetProps,
41
43
  ShortKitPlayerState,
44
+ ShortKitRefreshState,
42
45
  StoryboardData,
43
46
  StoryboardTile,
44
47
  } from './types';
@@ -46,4 +49,5 @@ export { ShortKitCommands } from './ShortKitCommands';
46
49
  export { default as NativeShortKitModule } from './specs/NativeShortKitModule';
47
50
  export { registerOverlayComponent } from './ShortKitOverlaySurface';
48
51
  export { registerCarouselOverlayComponent } from './ShortKitCarouselOverlaySurface';
49
- export type { OverlayProps, CarouselOverlayProps } from './types';
52
+ export { registerVideoCarouselOverlayComponent } from './ShortKitVideoCarouselOverlaySurface';
53
+ 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',
@@ -91,7 +91,10 @@ type RemainingContentCountEvent = Readonly<{
91
91
 
92
92
  type DismissEvent = Readonly<{}>;
93
93
 
94
- type RefreshRequestedEvent = Readonly<{}>;
94
+ type RefreshStateChangedEvent = Readonly<{
95
+ status: string;
96
+ progress: Double;
97
+ }>;
95
98
 
96
99
  type DidFetchContentItemsEvent = Readonly<{
97
100
  items: string; // JSON-serialized ContentItem[]
@@ -156,6 +159,17 @@ type OverlayTimeUpdateEvent = Readonly<{
156
159
  buffered: Double;
157
160
  }>;
158
161
 
162
+ type CarouselActiveImageEvent = Readonly<{
163
+ surfaceId: string;
164
+ activeImageIndex: Int32;
165
+ }>;
166
+
167
+ type VideoCarouselActiveVideoEvent = Readonly<{
168
+ surfaceId: string;
169
+ activeVideo: string;
170
+ activeVideoIndex: Int32;
171
+ }>;
172
+
159
173
  type OverlayFullStateEvent = Readonly<{
160
174
  surfaceId: string;
161
175
  isActive: boolean;
@@ -225,7 +239,7 @@ export interface Spec extends TurboModule {
225
239
  readonly onSurveyResponse: EventEmitter<SurveyResponseEvent>;
226
240
  readonly onContentTapped: EventEmitter<ContentTappedEvent>;
227
241
  readonly onDismiss: EventEmitter<DismissEvent>;
228
- readonly onRefreshRequested: EventEmitter<RefreshRequestedEvent>;
242
+ readonly onRefreshStateChanged: EventEmitter<RefreshStateChangedEvent>;
229
243
  readonly onDidFetchContentItems: EventEmitter<DidFetchContentItemsEvent>;
230
244
  readonly onFeedReady: EventEmitter<FeedReadyEvent>;
231
245
 
@@ -239,6 +253,8 @@ export interface Spec extends TurboModule {
239
253
  readonly onOverlayFeedScrollPhaseChanged: EventEmitter<OverlayFeedScrollPhaseEvent>;
240
254
  readonly onOverlayTimeUpdate: EventEmitter<OverlayTimeUpdateEvent>;
241
255
  readonly onOverlayFullState: EventEmitter<OverlayFullStateEvent>;
256
+ readonly onCarouselActiveImageChanged: EventEmitter<CarouselActiveImageEvent>;
257
+ readonly onVideoCarouselActiveVideoChanged: EventEmitter<VideoCarouselActiveVideoEvent>;
242
258
  }
243
259
 
244
260
  export default TurboModuleRegistry.getEnforcing<Spec>('ShortKitModule');
package/src/types.ts CHANGED
@@ -15,16 +15,24 @@ export interface FeedFilter {
15
15
 
16
16
  export type CaptionSource = 'embedded' | 'external' | 'generated';
17
17
 
18
+ export type ShortKitRefreshState =
19
+ | { status: 'idle' }
20
+ | { status: 'pulling'; progress: number }
21
+ | { status: 'triggered' }
22
+ | { status: 'refreshing' };
23
+
18
24
  export interface FeedConfig {
19
25
  feedHeight?: FeedHeight;
20
26
  scrollAxis?: ScrollAxis;
21
27
  overlay?: OverlayConfig;
22
28
  carouselOverlay?: CarouselOverlayConfig;
29
+ videoCarouselOverlay?: VideoCarouselOverlayConfig;
23
30
  surveyMode?: SurveyMode;
24
31
  muteOnStart?: boolean;
25
32
  feedSource?: FeedSource;
26
33
  autoplay?: boolean;
27
34
  filter?: FeedFilter;
35
+ pullToRefreshEnabled?: boolean;
28
36
  }
29
37
 
30
38
  export type FeedHeight =
@@ -41,6 +49,10 @@ export type CarouselOverlayConfig =
41
49
  | 'none'
42
50
  | { type: 'custom'; name: string; component: React.ComponentType<CarouselOverlayProps> };
43
51
 
52
+ export type VideoCarouselOverlayConfig =
53
+ | 'none'
54
+ | { type: 'custom'; name: string; component: React.ComponentType<VideoCarouselOverlayProps> };
55
+
44
56
  export type SurveyMode =
45
57
  | 'none'
46
58
  | { type: 'template'; name: 'default' };
@@ -81,9 +93,20 @@ export interface ImageCarouselItem {
81
93
  articleUrl?: string;
82
94
  }
83
95
 
96
+ export interface VideoCarouselItem {
97
+ id: string;
98
+ videos: ContentItem[];
99
+ title?: string;
100
+ description?: string;
101
+ author?: string;
102
+ section?: string;
103
+ articleUrl?: string;
104
+ }
105
+
84
106
  export type FeedInput =
85
107
  | { type: 'video'; playbackId: string; fallbackUrl?: string }
86
- | { type: 'imageCarousel'; item: ImageCarouselItem };
108
+ | { type: 'imageCarousel'; item: ImageCarouselItem }
109
+ | { type: 'videoCarousel'; item: VideoCarouselItem };
87
110
 
88
111
  export type JSONValue =
89
112
  | string
@@ -195,6 +218,28 @@ export interface OverlayProps {
195
218
  export interface CarouselOverlayProps {
196
219
  /** The carousel item for this cell. */
197
220
  item: ImageCarouselItem;
221
+ /** Whether this carousel cell is the active (on-screen) cell. */
222
+ isActive: boolean;
223
+ /** Index of the currently visible image within the carousel. */
224
+ activeImageIndex: number;
225
+ }
226
+
227
+ /** Props passed to video carousel overlay components rendered inside feed cells. */
228
+ export interface VideoCarouselOverlayProps {
229
+ /** The video carousel item for this cell. */
230
+ carouselItem: VideoCarouselItem;
231
+ /** The currently active video within the carousel. */
232
+ activeVideo: ContentItem;
233
+ /** Index of the currently active video within the carousel. */
234
+ activeVideoIndex: number;
235
+ /** Whether this carousel cell is the active (on-screen) cell. */
236
+ isActive: boolean;
237
+ /** Current playback time for the active video. */
238
+ time: PlayerTime;
239
+ /** Current player state for the active video. */
240
+ playerState: PlayerState;
241
+ /** Whether audio is muted. */
242
+ isMuted: boolean;
198
243
  }
199
244
 
200
245
  // --- Provider Props ---
@@ -227,8 +272,8 @@ export interface ShortKitFeedProps {
227
272
  onContentTapped?: (contentId: string, index: number) => void;
228
273
  /** Called when the user dismisses the feed (swipe-down or back gesture). */
229
274
  onDismiss?: () => void;
230
- /** Called when the user pulls to refresh in custom feed mode. */
231
- onRefreshRequested?: () => void;
275
+ /** Called when the pull-to-refresh state changes. */
276
+ onRefreshStateChanged?: (state: ShortKitRefreshState) => void;
232
277
  /** Called when the SDK fetches content items from the algorithmic feed.
233
278
  * Use to pre-fetch your own metadata for the given items. */
234
279
  onDidFetchContentItems?: (items: ContentItem[]) => void;
@@ -8,7 +8,7 @@
8
8
  <key>BinaryPath</key>
9
9
  <string>ShortKitSDK.framework/ShortKitSDK</string>
10
10
  <key>LibraryIdentifier</key>
11
- <string>ios-arm64-simulator</string>
11
+ <string>ios-arm64</string>
12
12
  <key>LibraryPath</key>
13
13
  <string>ShortKitSDK.framework</string>
14
14
  <key>SupportedArchitectures</key>
@@ -17,14 +17,12 @@
17
17
  </array>
18
18
  <key>SupportedPlatform</key>
19
19
  <string>ios</string>
20
- <key>SupportedPlatformVariant</key>
21
- <string>simulator</string>
22
20
  </dict>
23
21
  <dict>
24
22
  <key>BinaryPath</key>
25
23
  <string>ShortKitSDK.framework/ShortKitSDK</string>
26
24
  <key>LibraryIdentifier</key>
27
- <string>ios-arm64</string>
25
+ <string>ios-arm64-simulator</string>
28
26
  <key>LibraryPath</key>
29
27
  <string>ShortKitSDK.framework</string>
30
28
  <key>SupportedArchitectures</key>
@@ -33,6 +31,8 @@
33
31
  </array>
34
32
  <key>SupportedPlatform</key>
35
33
  <string>ios</string>
34
+ <key>SupportedPlatformVariant</key>
35
+ <string>simulator</string>
36
36
  </dict>
37
37
  </array>
38
38
  <key>CFBundlePackageType</key>