@shortkitsdk/react-native 0.2.1 → 0.2.3

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 (40) hide show
  1. package/ShortKitReactNative.podspec +8 -1
  2. package/android/src/main/java/com/shortkit/reactnative/ShortKitCarouselOverlayBridge.kt +48 -0
  3. package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +48 -6
  4. package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +67 -5
  5. package/android/src/main/java/com/shortkit/reactnative/ShortKitOverlayBridge.kt +28 -1
  6. package/ios/ShortKitBridge.swift +47 -2
  7. package/ios/ShortKitCarouselOverlayBridge.swift +54 -0
  8. package/ios/ShortKitFeedView.swift +47 -8
  9. package/ios/ShortKitModule.mm +22 -2
  10. package/ios/ShortKitOverlayBridge.swift +24 -2
  11. package/ios/ShortKitPlayerNativeView.swift +1 -1
  12. package/ios/ShortKitSDK.xcframework/Info.plist +43 -0
  13. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +417 -0
  14. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Info.plist +16 -0
  15. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +27739 -0
  16. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +790 -0
  17. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  18. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +790 -0
  19. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/module.modulemap +4 -0
  20. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  21. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +417 -0
  22. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Info.plist +16 -0
  23. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +27739 -0
  24. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +790 -0
  25. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  26. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +790 -0
  27. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/module.modulemap +4 -0
  28. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  29. package/ios/ShortKitWidgetNativeView.swift +1 -1
  30. package/package.json +1 -1
  31. package/src/CarouselOverlayManager.tsx +71 -0
  32. package/src/ShortKitContext.ts +13 -0
  33. package/src/ShortKitFeed.tsx +3 -0
  34. package/src/ShortKitProvider.tsx +118 -2
  35. package/src/index.ts +3 -1
  36. package/src/serialization.ts +6 -2
  37. package/src/specs/NativeShortKitModule.ts +19 -0
  38. package/src/types.ts +4 -3
  39. package/src/useShortKitCarousel.ts +29 -0
  40. package/src/useShortKitPlayer.ts +10 -2
@@ -0,0 +1,4 @@
1
+ framework module ShortKitSDK {
2
+ header "ShortKitSDK-Swift.h"
3
+ export *
4
+ }
@@ -1,5 +1,5 @@
1
1
  import UIKit
2
- import ShortKit
2
+ import ShortKitSDK
3
3
 
4
4
  /// Fabric native view wrapping `ShortKitWidgetViewController` for use as
5
5
  /// a horizontal video carousel in React Native.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shortkitsdk/react-native",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "ShortKit React Native SDK — short-form video feed",
5
5
  "react-native": "src/index",
6
6
  "source": "src/index",
@@ -0,0 +1,71 @@
1
+ import React, { useContext, useMemo } from 'react';
2
+ import { View, StyleSheet } from 'react-native';
3
+ import type { CarouselOverlayConfig } from './types';
4
+ import { ShortKitContext } from './ShortKitContext';
5
+ import type { ShortKitContextValue } from './ShortKitContext';
6
+
7
+ interface CarouselOverlayManagerProps {
8
+ carouselOverlay: CarouselOverlayConfig;
9
+ }
10
+
11
+ /**
12
+ * Internal component that renders TWO instances of the developer's custom
13
+ * carousel overlay component — one for the current cell and one for the next.
14
+ *
15
+ * Works identically to `OverlayManager` but for image carousel cells.
16
+ * The native side finds these views by their `nativeID` and applies
17
+ * scroll-tracking transforms so each overlay moves with its respective
18
+ * carousel cell during swipe transitions.
19
+ */
20
+ export function CarouselOverlayManager({ carouselOverlay }: CarouselOverlayManagerProps) {
21
+ if (
22
+ carouselOverlay === 'none' ||
23
+ typeof carouselOverlay === 'string' ||
24
+ carouselOverlay.type !== 'custom' ||
25
+ !('component' in carouselOverlay)
26
+ ) {
27
+ return null;
28
+ }
29
+
30
+ const CarouselComponent = carouselOverlay.component;
31
+
32
+ return (
33
+ <>
34
+ <View style={StyleSheet.absoluteFill} nativeID="carousel-overlay-current" pointerEvents="box-none">
35
+ <CarouselComponent />
36
+ </View>
37
+ <View style={StyleSheet.absoluteFill} nativeID="carousel-overlay-next" pointerEvents="box-none">
38
+ <NextCarouselOverlayProvider>
39
+ <CarouselComponent />
40
+ </NextCarouselOverlayProvider>
41
+ </View>
42
+ </>
43
+ );
44
+ }
45
+
46
+ /**
47
+ * Wraps children with a modified ShortKitContext where `currentCarouselItem`
48
+ * is set to the provider's `nextCarouselItem`.
49
+ */
50
+ function NextCarouselOverlayProvider({ children }: { children: React.ReactNode }) {
51
+ const context = useContext(ShortKitContext);
52
+
53
+ const nextValue: ShortKitContextValue | null = useMemo(() => {
54
+ if (!context) return null;
55
+
56
+ return {
57
+ ...context,
58
+ currentCarouselItem: context.nextCarouselItem,
59
+ isCarouselActive: context.nextCarouselItem != null,
60
+ isCarouselTransitioning: false,
61
+ };
62
+ }, [context]);
63
+
64
+ if (!nextValue) return <>{children}</>;
65
+
66
+ return (
67
+ <ShortKitContext.Provider value={nextValue}>
68
+ {children}
69
+ </ShortKitContext.Provider>
70
+ );
71
+ }
@@ -1,12 +1,14 @@
1
1
  import { createContext } from 'react';
2
2
  import type {
3
3
  ContentItem,
4
+ ImageCarouselItem,
4
5
  CustomFeedItem,
5
6
  PlayerTime,
6
7
  PlayerState,
7
8
  CaptionTrack,
8
9
  ContentSignal,
9
10
  OverlayConfig,
11
+ CarouselOverlayConfig,
10
12
  } from './types';
11
13
 
12
14
  export interface ShortKitContextValue {
@@ -48,9 +50,20 @@ export interface ShortKitContextValue {
48
50
  appendFeedItems: (items: CustomFeedItem[]) => void;
49
51
  fetchContent: (limit?: number) => Promise<ContentItem[]>;
50
52
 
53
+ // Carousel overlay state
54
+ currentCarouselItem: ImageCarouselItem | null;
55
+ nextCarouselItem: ImageCarouselItem | null;
56
+ isCarouselActive: boolean;
57
+ isCarouselTransitioning: boolean;
58
+
59
+ // Active cell type — used by overlay managers to show/hide
60
+ activeCellType: 'video' | 'carousel' | null;
61
+
51
62
  // Internal — used by ShortKitFeed to render custom overlays
52
63
  /** @internal */
53
64
  _overlayConfig: OverlayConfig;
65
+ /** @internal */
66
+ _carouselOverlayConfig: CarouselOverlayConfig;
54
67
  }
55
68
 
56
69
  export const ShortKitContext = createContext<ShortKitContextValue | null>(null);
@@ -4,6 +4,7 @@ import type { ShortKitFeedProps } from './types';
4
4
  import ShortKitFeedView from './specs/ShortKitFeedViewNativeComponent';
5
5
  import NativeShortKitModule from './specs/NativeShortKitModule';
6
6
  import { OverlayManager } from './OverlayManager';
7
+ import { CarouselOverlayManager } from './CarouselOverlayManager';
7
8
  import { ShortKitContext } from './ShortKitContext';
8
9
  import { deserializeContentItem } from './serialization';
9
10
 
@@ -38,6 +39,7 @@ export function ShortKitFeed(props: ShortKitFeedProps) {
38
39
  }
39
40
 
40
41
  const overlayConfig = context._overlayConfig;
42
+ const carouselOverlayConfig = context._carouselOverlayConfig;
41
43
 
42
44
  // ---------------------------------------------------------------------------
43
45
  // Subscribe to feed-level events and forward to callback props
@@ -143,6 +145,7 @@ export function ShortKitFeed(props: ShortKitFeedProps) {
143
145
  config="{}"
144
146
  />
145
147
  <OverlayManager overlay={overlayConfig} />
148
+ <CarouselOverlayManager carouselOverlay={carouselOverlayConfig} />
146
149
  </View>
147
150
  );
148
151
  }
@@ -6,6 +6,7 @@ import type { ShortKitContextValue } from './ShortKitContext';
6
6
  import type {
7
7
  ShortKitProviderProps,
8
8
  ContentItem,
9
+ ImageCarouselItem,
9
10
  CustomFeedItem,
10
11
  PlayerTime,
11
12
  PlayerState,
@@ -41,6 +42,11 @@ interface State {
41
42
  isTransitioning: boolean;
42
43
  lastOverlayTap: number;
43
44
  lastOverlayDoubleTap: { x: number; y: number; id: number } | null;
45
+ currentCarouselItem: ImageCarouselItem | null;
46
+ nextCarouselItem: ImageCarouselItem | null;
47
+ isCarouselActive: boolean;
48
+ isCarouselTransitioning: boolean;
49
+ activeCellType: 'video' | 'carousel' | null;
44
50
  }
45
51
 
46
52
  const initialState: State = {
@@ -59,6 +65,11 @@ const initialState: State = {
59
65
  isTransitioning: false,
60
66
  lastOverlayTap: 0,
61
67
  lastOverlayDoubleTap: null,
68
+ currentCarouselItem: null,
69
+ nextCarouselItem: null,
70
+ isCarouselActive: false,
71
+ isCarouselTransitioning: false,
72
+ activeCellType: null,
62
73
  };
63
74
 
64
75
  type Action =
@@ -78,7 +89,13 @@ type Action =
78
89
  | { type: 'OVERLAY_FADE_OUT' }
79
90
  | { type: 'OVERLAY_RESTORE' }
80
91
  | { type: 'OVERLAY_TAP' }
81
- | { type: 'OVERLAY_DOUBLE_TAP'; payload: { x: number; y: number } };
92
+ | { type: 'OVERLAY_DOUBLE_TAP'; payload: { x: number; y: number } }
93
+ | { type: 'ACTIVE_CELL_TYPE'; payload: 'video' | 'carousel' }
94
+ | { type: 'CAROUSEL_OVERLAY_CONFIGURE'; payload: ImageCarouselItem }
95
+ | { type: 'CAROUSEL_OVERLAY_ACTIVATE'; payload: ImageCarouselItem }
96
+ | { type: 'CAROUSEL_OVERLAY_RESET' }
97
+ | { type: 'CAROUSEL_OVERLAY_FADE_OUT' }
98
+ | { type: 'CAROUSEL_OVERLAY_RESTORE' };
82
99
 
83
100
  function reducer(state: State, action: Action): State {
84
101
  switch (action.type) {
@@ -91,10 +108,13 @@ function reducer(state: State, action: Action): State {
91
108
  action.payload === 'paused' ||
92
109
  action.payload === 'buffering' ||
93
110
  action.payload === 'seeking';
111
+ const becameActive = !state.isActive && isPlaybackActive;
94
112
  return {
95
113
  ...state,
96
114
  playerState: action.payload,
97
115
  isActive: state.isActive || isPlaybackActive,
116
+ // First playback means a video cell is active (initial load)
117
+ activeCellType: becameActive ? 'video' : state.activeCellType,
98
118
  };
99
119
  }
100
120
  case 'CURRENT_ITEM':
@@ -118,7 +138,20 @@ function reducer(state: State, action: Action): State {
118
138
  case 'OVERLAY_CONFIGURE':
119
139
  return { ...state, nextItem: action.payload };
120
140
  case 'OVERLAY_ACTIVATE':
121
- return { ...state, currentItem: action.payload, isActive: true };
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.
147
+ return {
148
+ ...state,
149
+ currentItem: action.payload,
150
+ isActive: true,
151
+ time: { current: 0, duration: action.payload.duration, buffered: 0 },
152
+ activeCue: null,
153
+ isTransitioning: false,
154
+ };
122
155
  case 'OVERLAY_RESET':
123
156
  // Don't set isActive = false — the overlay stays mounted during
124
157
  // transitions. In the native SDK each cell has its own overlay
@@ -140,6 +173,18 @@ function reducer(state: State, action: Action): State {
140
173
  id: (state.lastOverlayDoubleTap?.id ?? 0) + 1,
141
174
  },
142
175
  };
176
+ case 'ACTIVE_CELL_TYPE':
177
+ return { ...state, activeCellType: action.payload };
178
+ case 'CAROUSEL_OVERLAY_CONFIGURE':
179
+ return { ...state, nextCarouselItem: action.payload };
180
+ case 'CAROUSEL_OVERLAY_ACTIVATE':
181
+ return { ...state, currentCarouselItem: action.payload, isCarouselActive: true };
182
+ 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 };
143
188
  default:
144
189
  return state;
145
190
  }
@@ -397,6 +442,61 @@ export function ShortKitProvider({
397
442
  }),
398
443
  );
399
444
 
445
+ // Feed transition — track active cell type
446
+ subscriptions.push(
447
+ NativeShortKitModule.onFeedTransition((event) => {
448
+ if (event.phase === 'ended') {
449
+ // toItem is null when the destination cell is non-video (carousel, survey, ad)
450
+ const isVideo = event.toItem != null;
451
+ dispatch({
452
+ type: 'ACTIVE_CELL_TYPE',
453
+ payload: isVideo ? 'video' : 'carousel',
454
+ });
455
+ }
456
+ }),
457
+ );
458
+
459
+ // Carousel overlay lifecycle events
460
+ subscriptions.push(
461
+ NativeShortKitModule.onCarouselOverlayConfigure((event) => {
462
+ try {
463
+ const item = JSON.parse(event.item) as ImageCarouselItem;
464
+ dispatch({ type: 'CAROUSEL_OVERLAY_CONFIGURE', payload: item });
465
+ } catch {
466
+ // Ignore malformed JSON
467
+ }
468
+ }),
469
+ );
470
+
471
+ subscriptions.push(
472
+ NativeShortKitModule.onCarouselOverlayActivate((event) => {
473
+ try {
474
+ const item = JSON.parse(event.item) as ImageCarouselItem;
475
+ dispatch({ type: 'CAROUSEL_OVERLAY_ACTIVATE', payload: item });
476
+ } catch {
477
+ // Ignore malformed JSON
478
+ }
479
+ }),
480
+ );
481
+
482
+ subscriptions.push(
483
+ NativeShortKitModule.onCarouselOverlayReset((_event) => {
484
+ dispatch({ type: 'CAROUSEL_OVERLAY_RESET' });
485
+ }),
486
+ );
487
+
488
+ 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' });
497
+ }),
498
+ );
499
+
400
500
  // Note: Feed-level callback events (onDidLoop, onFeedTransition,
401
501
  // onShareTapped, etc.) are subscribed by the ShortKitFeed component
402
502
  // (Task 11), not here. The provider only manages state-driving events.
@@ -526,8 +626,18 @@ export function ShortKitProvider({
526
626
  appendFeedItems: appendFeedItemsCmd,
527
627
  fetchContent: fetchContentCmd,
528
628
 
629
+ // Active cell type
630
+ activeCellType: state.activeCellType,
631
+
632
+ // Carousel overlay state
633
+ currentCarouselItem: state.currentCarouselItem,
634
+ nextCarouselItem: state.nextCarouselItem,
635
+ isCarouselActive: state.isCarouselActive,
636
+ isCarouselTransitioning: state.isCarouselTransitioning,
637
+
529
638
  // Internal — consumed by ShortKitFeed to pass to OverlayManager
530
639
  _overlayConfig: config.overlay ?? 'none',
640
+ _carouselOverlayConfig: config.carouselOverlay ?? 'none',
531
641
  }),
532
642
  [
533
643
  state.playerState,
@@ -545,6 +655,11 @@ export function ShortKitProvider({
545
655
  state.isTransitioning,
546
656
  state.lastOverlayTap,
547
657
  state.lastOverlayDoubleTap,
658
+ state.activeCellType,
659
+ state.currentCarouselItem,
660
+ state.nextCarouselItem,
661
+ state.isCarouselActive,
662
+ state.isCarouselTransitioning,
548
663
  play,
549
664
  pause,
550
665
  seek,
@@ -563,6 +678,7 @@ export function ShortKitProvider({
563
678
  appendFeedItemsCmd,
564
679
  fetchContentCmd,
565
680
  config.overlay,
681
+ config.carouselOverlay,
566
682
  ],
567
683
  );
568
684
 
package/src/index.ts CHANGED
@@ -4,12 +4,13 @@ export { ShortKitPlayer } from './ShortKitPlayer';
4
4
  export { ShortKitWidget } from './ShortKitWidget';
5
5
  export { useShortKitPlayer } from './useShortKitPlayer';
6
6
  export { useShortKit } from './useShortKit';
7
+ export { useShortKitCarousel } from './useShortKitCarousel';
7
8
  export type {
8
9
  FeedConfig,
9
10
  FeedHeight,
10
11
  FeedSource,
11
12
  OverlayConfig,
12
- CarouselMode,
13
+ CarouselOverlayConfig,
13
14
  SurveyMode,
14
15
 
15
16
  PlayerConfig,
@@ -36,3 +37,4 @@ export type {
36
37
  ShortKitWidgetProps,
37
38
  ShortKitPlayerState,
38
39
  } from './types';
40
+ export type { ShortKitCarouselState } from './useShortKitCarousel';
@@ -13,7 +13,7 @@ import type {
13
13
  export interface SerializedFeedConfig {
14
14
  feedHeight: string;
15
15
  overlay: string;
16
- carouselMode: string;
16
+ carouselOverlay: string;
17
17
  surveyMode: string;
18
18
  muteOnStart: boolean;
19
19
  feedSource: string;
@@ -37,7 +37,11 @@ export function serializeFeedConfig(config: FeedConfig): SerializedFeedConfig {
37
37
  return {
38
38
  feedHeight: JSON.stringify(config.feedHeight ?? { type: 'fullscreen' }),
39
39
  overlay,
40
- carouselMode: JSON.stringify(config.carouselMode ?? 'none'),
40
+ carouselOverlay: (() => {
41
+ const raw = config.carouselOverlay ?? 'none';
42
+ if (typeof raw === 'string') return JSON.stringify(raw);
43
+ return JSON.stringify({ type: 'custom' });
44
+ })(),
41
45
  surveyMode: JSON.stringify(config.surveyMode ?? 'none'),
42
46
  muteOnStart: config.muteOnStart ?? true,
43
47
  feedSource: config.feedSource ?? 'algorithmic',
@@ -122,6 +122,20 @@ type ContentTappedEvent = Readonly<{
122
122
  index: Int32;
123
123
  }>;
124
124
 
125
+ type CarouselOverlayConfigureEvent = Readonly<{
126
+ item: string; // JSON-serialized ImageCarouselItem
127
+ }>;
128
+
129
+ type CarouselOverlayActivateEvent = Readonly<{
130
+ item: string; // JSON-serialized ImageCarouselItem
131
+ }>;
132
+
133
+ type CarouselOverlayResetEvent = Readonly<{}>;
134
+
135
+ type CarouselOverlayFadeOutEvent = Readonly<{}>;
136
+
137
+ type CarouselOverlayRestoreEvent = Readonly<{}>;
138
+
125
139
  type OverlayTapEvent = Readonly<{}>;
126
140
 
127
141
  type OverlayDoubleTapEvent = Readonly<{
@@ -189,6 +203,11 @@ export interface Spec extends TurboModule {
189
203
  readonly onOverlayTap: EventEmitter<OverlayTapEvent>;
190
204
  readonly onOverlayDoubleTap: EventEmitter<OverlayDoubleTapEvent>;
191
205
  readonly onContentTapped: EventEmitter<ContentTappedEvent>;
206
+ readonly onCarouselOverlayConfigure: EventEmitter<CarouselOverlayConfigureEvent>;
207
+ readonly onCarouselOverlayActivate: EventEmitter<CarouselOverlayActivateEvent>;
208
+ readonly onCarouselOverlayReset: EventEmitter<CarouselOverlayResetEvent>;
209
+ readonly onCarouselOverlayFadeOut: EventEmitter<CarouselOverlayFadeOutEvent>;
210
+ readonly onCarouselOverlayRestore: EventEmitter<CarouselOverlayRestoreEvent>;
192
211
  }
193
212
 
194
213
  export default TurboModuleRegistry.getEnforcing<Spec>('ShortKitModule');
package/src/types.ts CHANGED
@@ -7,7 +7,7 @@ export type FeedSource = 'algorithmic' | 'custom';
7
7
  export interface FeedConfig {
8
8
  feedHeight?: FeedHeight;
9
9
  overlay?: OverlayConfig;
10
- carouselMode?: CarouselMode;
10
+ carouselOverlay?: CarouselOverlayConfig;
11
11
  surveyMode?: SurveyMode;
12
12
  muteOnStart?: boolean;
13
13
  feedSource?: FeedSource;
@@ -21,9 +21,9 @@ export type OverlayConfig =
21
21
  | 'none'
22
22
  | { type: 'custom'; component: React.ComponentType };
23
23
 
24
- export type CarouselMode =
24
+ export type CarouselOverlayConfig =
25
25
  | 'none'
26
- | { type: 'template'; name: 'default' };
26
+ | { type: 'custom'; component: React.ComponentType };
27
27
 
28
28
  export type SurveyMode =
29
29
  | 'none'
@@ -212,6 +212,7 @@ export interface ShortKitPlayerState {
212
212
  playerState: PlayerState;
213
213
  currentItem: ContentItem | null;
214
214
  nextItem: ContentItem | null;
215
+ activeCellType: 'video' | 'carousel' | null;
215
216
  time: PlayerTime;
216
217
  isMuted: boolean;
217
218
  playbackRate: number;
@@ -0,0 +1,29 @@
1
+ import { useContext } from 'react';
2
+ import { ShortKitContext } from './ShortKitContext';
3
+ import type { ImageCarouselItem } from './types';
4
+
5
+ export interface ShortKitCarouselState {
6
+ currentCarouselItem: ImageCarouselItem | null;
7
+ isCarouselActive: boolean;
8
+ isCarouselTransitioning: boolean;
9
+ }
10
+
11
+ /**
12
+ * Hook to access carousel overlay state from the nearest ShortKitProvider.
13
+ *
14
+ * Use this inside a custom carousel overlay component to get the current
15
+ * `ImageCarouselItem` data (images, title, description, etc.).
16
+ *
17
+ * Must be used within a `<ShortKitProvider>`.
18
+ */
19
+ export function useShortKitCarousel(): ShortKitCarouselState {
20
+ const context = useContext(ShortKitContext);
21
+ if (!context) {
22
+ throw new Error('useShortKitCarousel must be used within a ShortKitProvider');
23
+ }
24
+ return {
25
+ currentCarouselItem: context.currentCarouselItem,
26
+ isCarouselActive: context.isCarouselActive,
27
+ isCarouselTransitioning: context.isCarouselTransitioning,
28
+ };
29
+ }
@@ -16,12 +16,20 @@ export function useShortKitPlayer(): ShortKitPlayerState {
16
16
  throw new Error('useShortKitPlayer must be used within a ShortKitProvider');
17
17
  }
18
18
 
19
- // Return only player-related state and commands (exclude SDK operations
20
- // and internal fields)
19
+ // Return only player-related state and commands (exclude SDK operations,
20
+ // carousel state, and internal fields)
21
21
  const {
22
22
  setUserId: _setUserId,
23
23
  clearUserId: _clearUserId,
24
+ setFeedItems: _setFeedItems,
25
+ appendFeedItems: _appendFeedItems,
26
+ fetchContent: _fetchContent,
27
+ currentCarouselItem: _currentCarouselItem,
28
+ nextCarouselItem: _nextCarouselItem,
29
+ isCarouselActive: _isCarouselActive,
30
+ isCarouselTransitioning: _isCarouselTransitioning,
24
31
  _overlayConfig: _overlay,
32
+ _carouselOverlayConfig: _carouselOverlay,
25
33
  ...playerState
26
34
  } = context;
27
35