@shortkitsdk/react-native 0.2.4 → 0.2.6

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 (30) hide show
  1. package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +49 -11
  2. package/ios/ShortKitBridge.swift +85 -7
  3. package/ios/ShortKitCarouselOverlayBridge.swift +177 -12
  4. package/ios/ShortKitFeedView.swift +48 -25
  5. package/ios/ShortKitModule.mm +29 -4
  6. package/ios/ShortKitOverlayBridge.swift +2 -4
  7. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -0
  8. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +1635 -457
  9. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +50 -16
  10. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  11. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +50 -16
  12. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  13. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -0
  14. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +1635 -457
  15. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +50 -16
  16. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  17. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +50 -16
  18. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  19. package/package.json +1 -1
  20. package/plugin/build/withShortKitIOS.js +11 -2
  21. package/src/CarouselOverlayManager.tsx +0 -1
  22. package/src/OverlayManager.tsx +1 -1
  23. package/src/ShortKitContext.ts +11 -6
  24. package/src/ShortKitProvider.tsx +140 -66
  25. package/src/index.ts +4 -1
  26. package/src/serialization.ts +3 -3
  27. package/src/specs/NativeShortKitModule.ts +18 -16
  28. package/src/types.ts +26 -3
  29. package/src/useShortKitCarousel.ts +2 -2
  30. package/src/useShortKitPlayer.ts +0 -1
@@ -7,15 +7,17 @@ import type {
7
7
  ShortKitProviderProps,
8
8
  ContentItem,
9
9
  ImageCarouselItem,
10
- CustomFeedItem,
10
+ FeedInput,
11
11
  PlayerTime,
12
12
  PlayerState,
13
13
  CaptionTrack,
14
+ FeedScrollPhase,
14
15
  ContentSignal,
16
+ StoryboardData,
15
17
  } from './types';
16
18
  import {
17
19
  serializeFeedConfigForModule,
18
- serializeCustomFeedItems,
20
+ serializeFeedInputs,
19
21
  deserializePlayerState,
20
22
  deserializeContentItem,
21
23
  deserializePlayerTime,
@@ -39,13 +41,13 @@ interface State {
39
41
  prefetchedAheadCount: number;
40
42
  remainingContentCount: number;
41
43
  isActive: boolean;
42
- isTransitioning: boolean;
44
+ feedScrollPhase: FeedScrollPhase | null;
43
45
  lastOverlayTap: number;
44
46
  lastOverlayDoubleTap: { x: number; y: number; id: number } | null;
45
47
  currentCarouselItem: ImageCarouselItem | null;
46
48
  nextCarouselItem: ImageCarouselItem | null;
47
49
  isCarouselActive: boolean;
48
- isCarouselTransitioning: boolean;
50
+ currentCarouselPage: number;
49
51
  activeCellType: 'video' | 'carousel' | null;
50
52
  }
51
53
 
@@ -62,13 +64,13 @@ const initialState: State = {
62
64
  prefetchedAheadCount: 0,
63
65
  remainingContentCount: 0,
64
66
  isActive: false,
65
- isTransitioning: false,
67
+ feedScrollPhase: null,
66
68
  lastOverlayTap: 0,
67
69
  lastOverlayDoubleTap: null,
68
70
  currentCarouselItem: null,
69
71
  nextCarouselItem: null,
70
72
  isCarouselActive: false,
71
- isCarouselTransitioning: false,
73
+ currentCarouselPage: 0,
72
74
  activeCellType: null,
73
75
  };
74
76
 
@@ -86,16 +88,15 @@ type Action =
86
88
  | { type: 'OVERLAY_CONFIGURE'; payload: ContentItem }
87
89
  | { type: 'OVERLAY_ACTIVATE'; payload: ContentItem }
88
90
  | { type: 'OVERLAY_RESET' }
89
- | { type: 'OVERLAY_FADE_OUT' }
90
- | { type: 'OVERLAY_RESTORE' }
91
91
  | { type: 'OVERLAY_TAP' }
92
92
  | { type: 'OVERLAY_DOUBLE_TAP'; payload: { x: number; y: number } }
93
- | { type: 'ACTIVE_CELL_TYPE'; payload: 'video' | 'carousel' }
93
+ | { type: 'ACTIVE_CELL_TYPE'; payload: 'video' | 'carousel' | null }
94
+ | { type: 'FEED_TRANSITION_ENDED'; payload: { isVideo: boolean } }
94
95
  | { type: 'CAROUSEL_OVERLAY_CONFIGURE'; payload: ImageCarouselItem }
95
96
  | { type: 'CAROUSEL_OVERLAY_ACTIVATE'; payload: ImageCarouselItem }
96
97
  | { type: 'CAROUSEL_OVERLAY_RESET' }
97
- | { type: 'CAROUSEL_OVERLAY_FADE_OUT' }
98
- | { type: 'CAROUSEL_OVERLAY_RESTORE' };
98
+ | { type: 'CAROUSEL_PAGE_CHANGED'; payload: number }
99
+ | { type: 'FEED_SCROLL_PHASE'; payload: FeedScrollPhase };
99
100
 
100
101
  function reducer(state: State, action: Action): State {
101
102
  switch (action.type) {
@@ -138,30 +139,29 @@ function reducer(state: State, action: Action): State {
138
139
  case 'OVERLAY_CONFIGURE':
139
140
  return { ...state, nextItem: action.payload };
140
141
  case 'OVERLAY_ACTIVATE':
141
- // Reset time/cue/transition state to prevent stale playback data from
142
- // the previous cell flashing on the new cell's overlay.
143
- // Note: nextItem is NOT cleared here the native-side deferred-configure
144
- // (ShortKitOverlayBridge) already prevents handleSwipe from polluting it.
145
- // Clearing it would cause overlay-next to unmount its content ~80ms before
146
- // the page settle swaps overlays, creating a visible dim-layer gap.
142
+ // Clear nextItem so overlay-next doesn't show stale data from the
143
+ // handleSwipe re-configure. The next OVERLAY_CONFIGURE (from UIKit
144
+ // prefetching cell N+2) will set it to the correct upcoming item.
145
+ // Also clear carousel state a video cell is now active.
146
+ console.log('[ShortKitProvider] OVERLAY_ACTIVATE (video):', action.payload.playbackId);
147
147
  return {
148
148
  ...state,
149
149
  currentItem: action.payload,
150
150
  isActive: true,
151
- time: { current: 0, duration: action.payload.duration, buffered: 0 },
152
- activeCue: null,
153
- isTransitioning: false,
151
+ time: { current: 0, duration: action.payload.duration ?? 0, buffered: 0 },
152
+ nextItem: null,
153
+ currentCarouselItem: null,
154
+ isCarouselActive: false,
155
+ nextCarouselItem: null,
156
+ currentCarouselPage: 0,
154
157
  };
155
158
  case 'OVERLAY_RESET':
156
159
  // Don't set isActive = false — the overlay stays mounted during
157
160
  // transitions. In the native SDK each cell has its own overlay
158
161
  // instance, so there's no gap. We replicate this by keeping the
159
162
  // single JS overlay mounted and updating its content on activate.
160
- return { ...state, isTransitioning: false };
161
- case 'OVERLAY_FADE_OUT':
162
- return { ...state, isTransitioning: true };
163
- case 'OVERLAY_RESTORE':
164
- return { ...state, isTransitioning: false };
163
+ console.log('[ShortKitProvider] OVERLAY_RESET (video)');
164
+ return { ...state, feedScrollPhase: null };
165
165
  case 'OVERLAY_TAP':
166
166
  return { ...state, lastOverlayTap: state.lastOverlayTap + 1 };
167
167
  case 'OVERLAY_DOUBLE_TAP':
@@ -174,17 +174,46 @@ function reducer(state: State, action: Action): State {
174
174
  },
175
175
  };
176
176
  case 'ACTIVE_CELL_TYPE':
177
+ console.log('[ShortKitProvider] ACTIVE_CELL_TYPE:', action.payload);
177
178
  return { ...state, activeCellType: action.payload };
179
+ case 'FEED_TRANSITION_ENDED': {
180
+ if (action.payload.isVideo) {
181
+ return {
182
+ ...state,
183
+ activeCellType: 'video',
184
+ currentCarouselItem: null,
185
+ isCarouselActive: false,
186
+ nextCarouselItem: null,
187
+ currentCarouselPage: 0,
188
+ };
189
+ }
190
+ // Non-video destination. If a carousel configure was received, activate it.
191
+ if (state.nextCarouselItem) {
192
+ console.log('[ShortKitProvider] FEED_TRANSITION_ENDED → carousel activate:', state.nextCarouselItem.id);
193
+ return {
194
+ ...state,
195
+ activeCellType: 'carousel',
196
+ currentCarouselItem: state.nextCarouselItem,
197
+ isCarouselActive: true,
198
+ nextCarouselItem: null,
199
+ };
200
+ }
201
+ // Unknown non-video type (survey, ad, etc.)
202
+ return { ...state, activeCellType: null };
203
+ }
178
204
  case 'CAROUSEL_OVERLAY_CONFIGURE':
205
+ console.log('[ShortKitProvider] CAROUSEL_OVERLAY_CONFIGURE:', action.payload.id);
179
206
  return { ...state, nextCarouselItem: action.payload };
180
207
  case 'CAROUSEL_OVERLAY_ACTIVATE':
181
- return { ...state, currentCarouselItem: action.payload, isCarouselActive: true };
208
+ console.log('[ShortKitProvider] CAROUSEL_OVERLAY_ACTIVATE:', action.payload.id);
209
+ return { ...state, currentCarouselItem: action.payload, isCarouselActive: true, nextCarouselItem: null };
182
210
  case 'CAROUSEL_OVERLAY_RESET':
183
- return { ...state, isCarouselTransitioning: false };
184
- case 'CAROUSEL_OVERLAY_FADE_OUT':
185
- return { ...state, isCarouselTransitioning: true };
186
- case 'CAROUSEL_OVERLAY_RESTORE':
187
- return { ...state, isCarouselTransitioning: false };
211
+ console.log('[ShortKitProvider] CAROUSEL_OVERLAY_RESET');
212
+ return { ...state, currentCarouselItem: null, isCarouselActive: false, nextCarouselItem: null, currentCarouselPage: 0 };
213
+ case 'CAROUSEL_PAGE_CHANGED':
214
+ return { ...state, currentCarouselPage: action.payload };
215
+ case 'FEED_SCROLL_PHASE':
216
+ return { ...state, feedScrollPhase: action.payload };
188
217
  default:
189
218
  return state;
190
219
  }
@@ -197,7 +226,6 @@ function reducer(state: State, action: Action): State {
197
226
  export function ShortKitProvider({
198
227
  apiKey,
199
228
  config,
200
- embedId,
201
229
  userId,
202
230
  clientAppName,
203
231
  clientAppVersion,
@@ -221,7 +249,6 @@ export function ShortKitProvider({
221
249
  NativeShortKitModule.initialize(
222
250
  apiKey,
223
251
  serializedConfig,
224
- embedId,
225
252
  clientAppName,
226
253
  clientAppVersion,
227
254
  serializedDimensions,
@@ -414,18 +441,6 @@ export function ShortKitProvider({
414
441
  }),
415
442
  );
416
443
 
417
- subscriptions.push(
418
- NativeShortKitModule.onOverlayFadeOut((_event) => {
419
- dispatch({ type: 'OVERLAY_FADE_OUT' });
420
- }),
421
- );
422
-
423
- subscriptions.push(
424
- NativeShortKitModule.onOverlayRestore((_event) => {
425
- dispatch({ type: 'OVERLAY_RESTORE' });
426
- }),
427
- );
428
-
429
444
  // Overlay tap events
430
445
  subscriptions.push(
431
446
  NativeShortKitModule.onOverlayTap((_event) => {
@@ -445,12 +460,14 @@ export function ShortKitProvider({
445
460
  // Feed transition — track active cell type
446
461
  subscriptions.push(
447
462
  NativeShortKitModule.onFeedTransition((event) => {
463
+ console.log('[ShortKitProvider] onFeedTransition:', event.phase, 'toItem:', event.toItem != null ? 'video' : 'non-video');
448
464
  if (event.phase === 'ended') {
449
- // toItem is null when the destination cell is non-video (carousel, survey, ad)
465
+ // toItem is null when the destination cell is non-video (carousel, survey, ad).
466
+ // The reducer checks nextCarouselItem to distinguish carousel from other types.
450
467
  const isVideo = event.toItem != null;
451
468
  dispatch({
452
- type: 'ACTIVE_CELL_TYPE',
453
- payload: isVideo ? 'video' : 'carousel',
469
+ type: 'FEED_TRANSITION_ENDED',
470
+ payload: { isVideo },
454
471
  });
455
472
  }
456
473
  }),
@@ -485,21 +502,26 @@ export function ShortKitProvider({
485
502
  }),
486
503
  );
487
504
 
505
+ // Feed scroll phase
488
506
  subscriptions.push(
489
- NativeShortKitModule.onCarouselOverlayFadeOut((_event) => {
490
- dispatch({ type: 'CAROUSEL_OVERLAY_FADE_OUT' });
491
- }),
492
- );
493
-
494
- subscriptions.push(
495
- NativeShortKitModule.onCarouselOverlayRestore((_event) => {
496
- dispatch({ type: 'CAROUSEL_OVERLAY_RESTORE' });
507
+ NativeShortKitModule.onFeedScrollPhase((event) => {
508
+ if (event.phase === 'dragging' && event.fromId) {
509
+ dispatch({
510
+ type: 'FEED_SCROLL_PHASE',
511
+ payload: { phase: 'dragging', fromId: event.fromId },
512
+ });
513
+ } else if (event.phase === 'settled') {
514
+ dispatch({
515
+ type: 'FEED_SCROLL_PHASE',
516
+ payload: { phase: 'settled' },
517
+ });
518
+ }
497
519
  }),
498
520
  );
499
521
 
500
522
  // Note: Feed-level callback events (onDidLoop, onFeedTransition,
501
523
  // onShareTapped, etc.) are subscribed by the ShortKitFeed component
502
- // (Task 11), not here. The provider only manages state-driving events.
524
+ // not here. The provider only manages state-driving events.
503
525
 
504
526
  return () => {
505
527
  for (const sub of subscriptions) {
@@ -508,6 +530,40 @@ export function ShortKitProvider({
508
530
  };
509
531
  }, []);
510
532
 
533
+ // -----------------------------------------------------------------------
534
+ // Overlay ready signal — tell native it's safe to swap overlay transforms.
535
+ // This fires after React has re-rendered overlay-current with the new
536
+ // content item (and the overlay component has reset its opacity).
537
+ // Child effects (e.g. NewsOverlay's content-change reset) fire before
538
+ // this parent effect, guaranteeing the overlay is visually ready.
539
+ // -----------------------------------------------------------------------
540
+ const currentItemIdRef = useRef<string | null>(null);
541
+ useEffect(() => {
542
+ const newId = state.currentItem?.id ?? null;
543
+ if (newId && newId !== currentItemIdRef.current) {
544
+ currentItemIdRef.current = newId;
545
+ NativeShortKitModule?.notifyOverlayReady();
546
+ }
547
+ }, [state.currentItem?.id]);
548
+
549
+ // Carousel overlay ready signal — same mechanism as video overlays.
550
+ const currentCarouselIdRef = useRef<string | null>(null);
551
+ useEffect(() => {
552
+ const newId = state.currentCarouselItem?.id ?? null;
553
+ if (newId && newId !== currentCarouselIdRef.current) {
554
+ currentCarouselIdRef.current = newId;
555
+ NativeShortKitModule?.notifyOverlayReady();
556
+ }
557
+ }, [state.currentCarouselItem?.id]);
558
+
559
+ // Safety net: if a carousel configure arrives before any feed transition
560
+ // (e.g. the feed starts on a carousel cell), activate after one render cycle.
561
+ useEffect(() => {
562
+ if (state.nextCarouselItem && !state.currentCarouselItem && !state.activeCellType) {
563
+ dispatch({ type: 'CAROUSEL_OVERLAY_ACTIVATE', payload: state.nextCarouselItem });
564
+ }
565
+ }, [state.nextCarouselItem, state.currentCarouselItem, state.activeCellType]);
566
+
511
567
  // -----------------------------------------------------------------------
512
568
  // Commands (stable refs)
513
569
  // -----------------------------------------------------------------------
@@ -567,12 +623,12 @@ export function ShortKitProvider({
567
623
  NativeShortKitModule?.clearUserId();
568
624
  }, []);
569
625
 
570
- const setFeedItemsCmd = useCallback((items: CustomFeedItem[]) => {
571
- NativeShortKitModule?.setFeedItems(serializeCustomFeedItems(items));
626
+ const setFeedItemsCmd = useCallback((items: FeedInput[]) => {
627
+ NativeShortKitModule?.setFeedItems(serializeFeedInputs(items));
572
628
  }, []);
573
629
 
574
- const appendFeedItemsCmd = useCallback((items: CustomFeedItem[]) => {
575
- NativeShortKitModule?.appendFeedItems(serializeCustomFeedItems(items));
630
+ const appendFeedItemsCmd = useCallback((items: FeedInput[]) => {
631
+ NativeShortKitModule?.appendFeedItems(serializeFeedInputs(items));
576
632
  }, []);
577
633
 
578
634
  const fetchContentCmd = useCallback(async (limit: number = 10): Promise<ContentItem[]> => {
@@ -585,6 +641,21 @@ export function ShortKitProvider({
585
641
  }
586
642
  }, []);
587
643
 
644
+ const prefetchStoryboardCmd = useCallback((playbackId: string) => {
645
+ NativeShortKitModule?.prefetchStoryboard(playbackId);
646
+ }, []);
647
+
648
+ const getStoryboardDataCmd = useCallback(async (playbackId: string): Promise<StoryboardData | null> => {
649
+ if (!NativeShortKitModule) return null;
650
+ const json = await NativeShortKitModule.getStoryboardData(playbackId);
651
+ if (!json) return null;
652
+ try {
653
+ return JSON.parse(json) as StoryboardData;
654
+ } catch {
655
+ return null;
656
+ }
657
+ }, []);
658
+
588
659
  // -----------------------------------------------------------------------
589
660
  // Context value (memoized to avoid unnecessary re-renders)
590
661
  // -----------------------------------------------------------------------
@@ -603,7 +674,7 @@ export function ShortKitProvider({
603
674
  prefetchedAheadCount: state.prefetchedAheadCount,
604
675
  remainingContentCount: state.remainingContentCount,
605
676
  isActive: state.isActive,
606
- isTransitioning: state.isTransitioning,
677
+ feedScrollPhase: state.feedScrollPhase,
607
678
  lastOverlayTap: state.lastOverlayTap,
608
679
  lastOverlayDoubleTap: state.lastOverlayDoubleTap,
609
680
 
@@ -625,6 +696,8 @@ export function ShortKitProvider({
625
696
  setFeedItems: setFeedItemsCmd,
626
697
  appendFeedItems: appendFeedItemsCmd,
627
698
  fetchContent: fetchContentCmd,
699
+ prefetchStoryboard: prefetchStoryboardCmd,
700
+ getStoryboardData: getStoryboardDataCmd,
628
701
 
629
702
  // Active cell type
630
703
  activeCellType: state.activeCellType,
@@ -633,8 +706,7 @@ export function ShortKitProvider({
633
706
  currentCarouselItem: state.currentCarouselItem,
634
707
  nextCarouselItem: state.nextCarouselItem,
635
708
  isCarouselActive: state.isCarouselActive,
636
- isCarouselTransitioning: state.isCarouselTransitioning,
637
-
709
+ currentCarouselPage: state.currentCarouselPage,
638
710
  // Internal — consumed by ShortKitFeed to pass to OverlayManager
639
711
  _overlayConfig: config.overlay ?? 'none',
640
712
  _carouselOverlayConfig: config.carouselOverlay ?? 'none',
@@ -652,14 +724,14 @@ export function ShortKitProvider({
652
724
  state.prefetchedAheadCount,
653
725
  state.remainingContentCount,
654
726
  state.isActive,
655
- state.isTransitioning,
727
+ state.feedScrollPhase,
656
728
  state.lastOverlayTap,
657
729
  state.lastOverlayDoubleTap,
658
730
  state.activeCellType,
659
731
  state.currentCarouselItem,
660
732
  state.nextCarouselItem,
661
733
  state.isCarouselActive,
662
- state.isCarouselTransitioning,
734
+ state.currentCarouselPage,
663
735
  play,
664
736
  pause,
665
737
  seek,
@@ -677,6 +749,8 @@ export function ShortKitProvider({
677
749
  setFeedItemsCmd,
678
750
  appendFeedItemsCmd,
679
751
  fetchContentCmd,
752
+ prefetchStoryboardCmd,
753
+ getStoryboardDataCmd,
680
754
  config.overlay,
681
755
  config.carouselOverlay,
682
756
  ],
package/src/index.ts CHANGED
@@ -20,13 +20,14 @@ export type {
20
20
  ContentItem,
21
21
  CarouselImage,
22
22
  ImageCarouselItem,
23
- CustomFeedItem,
23
+ FeedInput,
24
24
  JSONValue,
25
25
  CaptionTrack,
26
26
  PlayerTime,
27
27
  PlayerState,
28
28
  LoopEvent,
29
29
  FeedTransitionEvent,
30
+ FeedScrollPhase,
30
31
  FormatChangeEvent,
31
32
  ContentSignal,
32
33
  SurveyOption,
@@ -36,5 +37,7 @@ export type {
36
37
  ShortKitPlayerProps,
37
38
  ShortKitWidgetProps,
38
39
  ShortKitPlayerState,
40
+ StoryboardData,
41
+ StoryboardTile,
39
42
  } from './types';
40
43
  export type { ShortKitCarouselState } from './useShortKitCarousel';
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  FeedConfig,
3
3
  ContentItem,
4
- CustomFeedItem,
4
+ FeedInput,
5
5
  PlayerState,
6
6
  PlayerTime,
7
7
  } from './types';
@@ -102,8 +102,8 @@ export function deserializePlayerTime(event: {
102
102
  }
103
103
 
104
104
  /**
105
- * Serialize CustomFeedItem[] to a JSON string for the bridge.
105
+ * Serialize FeedInput[] to a JSON string for the bridge.
106
106
  */
107
- export function serializeCustomFeedItems(items: CustomFeedItem[]): string {
107
+ export function serializeFeedInputs(items: FeedInput[]): string {
108
108
  return JSON.stringify(items);
109
109
  }
@@ -66,6 +66,11 @@ type FeedTransitionEvent = Readonly<{
66
66
  direction: string;
67
67
  }>;
68
68
 
69
+ type FeedScrollPhaseEvent = Readonly<{
70
+ phase: string;
71
+ fromId?: string;
72
+ }>;
73
+
69
74
  type FormatChangeEvent = Readonly<{
70
75
  contentId: string;
71
76
  fromBitrate: Double;
@@ -109,14 +114,6 @@ type OverlayResetEvent = Readonly<{
109
114
  item: string; // JSON-serialized ContentItem
110
115
  }>;
111
116
 
112
- type OverlayFadeOutEvent = Readonly<{
113
- item: string; // JSON-serialized ContentItem
114
- }>;
115
-
116
- type OverlayRestoreEvent = Readonly<{
117
- item: string; // JSON-serialized ContentItem
118
- }>;
119
-
120
117
  type ContentTappedEvent = Readonly<{
121
118
  contentId: string;
122
119
  index: Int32;
@@ -132,9 +129,10 @@ type CarouselOverlayActivateEvent = Readonly<{
132
129
 
133
130
  type CarouselOverlayResetEvent = Readonly<{}>;
134
131
 
135
- type CarouselOverlayFadeOutEvent = Readonly<{}>;
136
-
137
- type CarouselOverlayRestoreEvent = Readonly<{}>;
132
+ type CarouselPageChangedEvent = Readonly<{
133
+ page: Int32;
134
+ pageCount: Int32;
135
+ }>;
138
136
 
139
137
  type OverlayTapEvent = Readonly<{}>;
140
138
 
@@ -148,7 +146,6 @@ export interface Spec extends TurboModule {
148
146
  initialize(
149
147
  apiKey: string,
150
148
  config: string, // JSON-serialized FeedConfig
151
- embedId?: string,
152
149
  clientAppName?: string,
153
150
  clientAppVersion?: string,
154
151
  customDimensions?: string, // JSON-serialized Record<string, string>
@@ -178,6 +175,13 @@ export interface Spec extends TurboModule {
178
175
  appendFeedItems(items: string): void;
179
176
  fetchContent(limit: Int32): Promise<string>;
180
177
 
178
+ // --- Storyboard / seek thumbnails ---
179
+ prefetchStoryboard(playbackId: string): void;
180
+ getStoryboardData(playbackId: string): Promise<string>;
181
+
182
+ // --- Overlay lifecycle ---
183
+ notifyOverlayReady(): void;
184
+
181
185
  // --- Event emitters ---
182
186
  readonly onPlayerStateChanged: EventEmitter<PlayerStateEvent>;
183
187
  readonly onCurrentItemChanged: EventEmitter<CurrentItemEvent>;
@@ -189,6 +193,7 @@ export interface Spec extends TurboModule {
189
193
  readonly onActiveCueChanged: EventEmitter<CueEvent>;
190
194
  readonly onDidLoop: EventEmitter<LoopEvent>;
191
195
  readonly onFeedTransition: EventEmitter<FeedTransitionEvent>;
196
+ readonly onFeedScrollPhase: EventEmitter<FeedScrollPhaseEvent>;
192
197
  readonly onFormatChange: EventEmitter<FormatChangeEvent>;
193
198
  readonly onPrefetchedAheadCountChanged: EventEmitter<PrefetchedAheadCountEvent>;
194
199
  readonly onRemainingContentCountChanged: EventEmitter<RemainingContentCountEvent>;
@@ -198,16 +203,13 @@ export interface Spec extends TurboModule {
198
203
  readonly onOverlayConfigure: EventEmitter<OverlayConfigureEvent>;
199
204
  readonly onOverlayActivate: EventEmitter<OverlayActivateEvent>;
200
205
  readonly onOverlayReset: EventEmitter<OverlayResetEvent>;
201
- readonly onOverlayFadeOut: EventEmitter<OverlayFadeOutEvent>;
202
- readonly onOverlayRestore: EventEmitter<OverlayRestoreEvent>;
203
206
  readonly onOverlayTap: EventEmitter<OverlayTapEvent>;
204
207
  readonly onOverlayDoubleTap: EventEmitter<OverlayDoubleTapEvent>;
205
208
  readonly onContentTapped: EventEmitter<ContentTappedEvent>;
206
209
  readonly onCarouselOverlayConfigure: EventEmitter<CarouselOverlayConfigureEvent>;
207
210
  readonly onCarouselOverlayActivate: EventEmitter<CarouselOverlayActivateEvent>;
208
211
  readonly onCarouselOverlayReset: EventEmitter<CarouselOverlayResetEvent>;
209
- readonly onCarouselOverlayFadeOut: EventEmitter<CarouselOverlayFadeOutEvent>;
210
- readonly onCarouselOverlayRestore: EventEmitter<CarouselOverlayRestoreEvent>;
212
+ readonly onCarouselPageChanged: EventEmitter<CarouselPageChangedEvent>;
211
213
  }
212
214
 
213
215
  export default TurboModuleRegistry.getEnforcing<Spec>('ShortKitModule');
package/src/types.ts CHANGED
@@ -65,7 +65,7 @@ export interface ImageCarouselItem {
65
65
  articleUrl?: string;
66
66
  }
67
67
 
68
- export type CustomFeedItem =
68
+ export type FeedInput =
69
69
  | { type: 'video'; playbackId: string }
70
70
  | { type: 'imageCarousel'; item: ImageCarouselItem };
71
71
 
@@ -76,6 +76,24 @@ export type JSONValue =
76
76
  | null
77
77
  | { [key: string]: JSONValue };
78
78
 
79
+ // --- Storyboard / Seek Thumbnails ---
80
+
81
+ export interface StoryboardTile {
82
+ start: number;
83
+ x: number;
84
+ y: number;
85
+ }
86
+
87
+ export interface StoryboardData {
88
+ url: string;
89
+ tileWidth: number;
90
+ tileHeight: number;
91
+ duration: number;
92
+ imageWidth: number;
93
+ imageHeight: number;
94
+ tiles: StoryboardTile[];
95
+ }
96
+
79
97
  export interface CaptionTrack {
80
98
  language: string;
81
99
  label: string;
@@ -111,6 +129,10 @@ export interface FeedTransitionEvent {
111
129
  direction: 'forward' | 'backward';
112
130
  }
113
131
 
132
+ export type FeedScrollPhase =
133
+ | { phase: 'dragging'; fromId: string }
134
+ | { phase: 'settled' };
135
+
114
136
  export interface FormatChangeEvent {
115
137
  contentId: string;
116
138
  fromBitrate: number;
@@ -136,7 +158,6 @@ export interface ShortKitError {
136
158
  export interface ShortKitProviderProps {
137
159
  apiKey: string;
138
160
  config: FeedConfig;
139
- embedId?: string;
140
161
  userId?: string;
141
162
 
142
163
  clientAppName?: string;
@@ -222,7 +243,7 @@ export interface ShortKitPlayerState {
222
243
  prefetchedAheadCount: number;
223
244
  remainingContentCount: number;
224
245
  isActive: boolean;
225
- isTransitioning: boolean;
246
+ feedScrollPhase: FeedScrollPhase | null;
226
247
  lastOverlayTap: number;
227
248
  lastOverlayDoubleTap: { x: number; y: number; id: number } | null;
228
249
 
@@ -238,4 +259,6 @@ export interface ShortKitPlayerState {
238
259
  selectCaptionTrack: (language: string) => void;
239
260
  sendContentSignal: (signal: ContentSignal) => void;
240
261
  setMaxBitrate: (bitrate: number) => void;
262
+ prefetchStoryboard: (playbackId: string) => void;
263
+ getStoryboardData: (playbackId: string) => Promise<StoryboardData | null>;
241
264
  }
@@ -5,7 +5,7 @@ import type { ImageCarouselItem } from './types';
5
5
  export interface ShortKitCarouselState {
6
6
  currentCarouselItem: ImageCarouselItem | null;
7
7
  isCarouselActive: boolean;
8
- isCarouselTransitioning: boolean;
8
+ currentCarouselPage: number;
9
9
  }
10
10
 
11
11
  /**
@@ -24,6 +24,6 @@ export function useShortKitCarousel(): ShortKitCarouselState {
24
24
  return {
25
25
  currentCarouselItem: context.currentCarouselItem,
26
26
  isCarouselActive: context.isCarouselActive,
27
- isCarouselTransitioning: context.isCarouselTransitioning,
27
+ currentCarouselPage: context.currentCarouselPage,
28
28
  };
29
29
  }
@@ -27,7 +27,6 @@ export function useShortKitPlayer(): ShortKitPlayerState {
27
27
  currentCarouselItem: _currentCarouselItem,
28
28
  nextCarouselItem: _nextCarouselItem,
29
29
  isCarouselActive: _isCarouselActive,
30
- isCarouselTransitioning: _isCarouselTransitioning,
31
30
  _overlayConfig: _overlay,
32
31
  _carouselOverlayConfig: _carouselOverlay,
33
32
  ...playerState