@shortkitsdk/react-native 0.2.6 → 0.2.11

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 (75) hide show
  1. package/ShortKitReactNative.podspec +1 -0
  2. package/android/build.gradle.kts +5 -1
  3. package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +319 -0
  4. package/android/src/main/java/com/shortkit/reactnative/ReactLoadingHost.kt +40 -0
  5. package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +559 -0
  6. package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +984 -0
  7. package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +88 -220
  8. package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +12 -3
  9. package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +123 -741
  10. package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +2 -2
  11. package/android/src/main/java/com/shortkit/reactnative/ShortKitWidgetNativeView.kt +2 -2
  12. package/ios/ReactCarouselOverlayHost.swift +177 -0
  13. package/ios/ReactLoadingHost.swift +38 -0
  14. package/ios/ReactOverlayHost.swift +458 -0
  15. package/ios/SKFabricSurfaceWrapper.h +18 -0
  16. package/ios/SKFabricSurfaceWrapper.mm +57 -0
  17. package/ios/ShortKitBridge.swift +186 -63
  18. package/ios/ShortKitFeedView.swift +62 -229
  19. package/ios/ShortKitFeedViewManager.mm +3 -2
  20. package/ios/ShortKitModule.mm +66 -37
  21. package/ios/ShortKitPlayerNativeView.swift +39 -8
  22. package/ios/ShortKitReactNative-Bridging-Header.h +2 -0
  23. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -1
  24. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +2380 -522
  25. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +39 -12
  26. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  27. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +39 -12
  28. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  29. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -1
  30. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +2380 -522
  31. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +39 -12
  32. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  33. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +39 -12
  34. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  35. package/ios/ShortKitSDK.xcframework.bak/Info.plist +43 -0
  36. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +418 -0
  37. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Info.plist +16 -0
  38. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +28917 -0
  39. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +824 -0
  40. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  41. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +824 -0
  42. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/module.modulemap +4 -0
  43. package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  44. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +418 -0
  45. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Info.plist +16 -0
  46. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +28917 -0
  47. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +824 -0
  48. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  49. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +824 -0
  50. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/module.modulemap +4 -0
  51. package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  52. package/ios/ShortKitWidgetNativeView.swift +3 -3
  53. package/package.json +1 -1
  54. package/src/ShortKitCarouselOverlaySurface.tsx +55 -0
  55. package/src/ShortKitCommands.ts +31 -0
  56. package/src/ShortKitContext.ts +6 -25
  57. package/src/ShortKitFeed.tsx +110 -41
  58. package/src/ShortKitLoadingSurface.tsx +24 -0
  59. package/src/ShortKitOverlaySurface.tsx +205 -0
  60. package/src/ShortKitPlayer.tsx +6 -7
  61. package/src/ShortKitProvider.tsx +27 -286
  62. package/src/index.ts +5 -3
  63. package/src/serialization.ts +19 -39
  64. package/src/specs/NativeShortKitModule.ts +58 -46
  65. package/src/specs/ShortKitFeedViewNativeComponent.ts +3 -2
  66. package/src/types.ts +78 -16
  67. package/src/useShortKit.ts +1 -3
  68. package/src/useShortKitPlayer.ts +7 -7
  69. package/android/src/main/java/com/shortkit/reactnative/ShortKitCarouselOverlayBridge.kt +0 -48
  70. package/android/src/main/java/com/shortkit/reactnative/ShortKitOverlayBridge.kt +0 -128
  71. package/ios/ShortKitCarouselOverlayBridge.swift +0 -219
  72. package/ios/ShortKitOverlayBridge.swift +0 -111
  73. package/src/CarouselOverlayManager.tsx +0 -70
  74. package/src/OverlayManager.tsx +0 -87
  75. package/src/useShortKitCarousel.ts +0 -29
@@ -6,8 +6,8 @@ import type { ShortKitContextValue } from './ShortKitContext';
6
6
  import type {
7
7
  ShortKitProviderProps,
8
8
  ContentItem,
9
- ImageCarouselItem,
10
- FeedInput,
9
+ FeedConfig,
10
+ FeedFilter,
11
11
  PlayerTime,
12
12
  PlayerState,
13
13
  CaptionTrack,
@@ -16,13 +16,13 @@ import type {
16
16
  StoryboardData,
17
17
  } from './types';
18
18
  import {
19
- serializeFeedConfigForModule,
20
- serializeFeedInputs,
19
+ serializeFeedConfig,
21
20
  deserializePlayerState,
22
21
  deserializeContentItem,
23
22
  deserializePlayerTime,
24
23
  } from './serialization';
25
24
  import NativeShortKitModule from './specs/NativeShortKitModule';
25
+ import { registerLoadingComponent } from './ShortKitLoadingSurface';
26
26
 
27
27
  // ---------------------------------------------------------------------------
28
28
  // State & Reducer
@@ -31,7 +31,6 @@ import NativeShortKitModule from './specs/NativeShortKitModule';
31
31
  interface State {
32
32
  playerState: PlayerState;
33
33
  currentItem: ContentItem | null;
34
- nextItem: ContentItem | null;
35
34
  time: PlayerTime;
36
35
  isMuted: boolean;
37
36
  playbackRate: number;
@@ -39,22 +38,13 @@ interface State {
39
38
  activeCaptionTrack: CaptionTrack | null;
40
39
  activeCue: { text: string; startTime: number; endTime: number } | null;
41
40
  prefetchedAheadCount: number;
42
- remainingContentCount: number;
43
41
  isActive: boolean;
44
42
  feedScrollPhase: FeedScrollPhase | null;
45
- lastOverlayTap: number;
46
- lastOverlayDoubleTap: { x: number; y: number; id: number } | null;
47
- currentCarouselItem: ImageCarouselItem | null;
48
- nextCarouselItem: ImageCarouselItem | null;
49
- isCarouselActive: boolean;
50
- currentCarouselPage: number;
51
- activeCellType: 'video' | 'carousel' | null;
52
43
  }
53
44
 
54
45
  const initialState: State = {
55
46
  playerState: 'idle',
56
47
  currentItem: null,
57
- nextItem: null,
58
48
  time: { current: 0, duration: 0, buffered: 0 },
59
49
  isMuted: true,
60
50
  playbackRate: 1.0,
@@ -62,16 +52,8 @@ const initialState: State = {
62
52
  activeCaptionTrack: null,
63
53
  activeCue: null,
64
54
  prefetchedAheadCount: 0,
65
- remainingContentCount: 0,
66
55
  isActive: false,
67
56
  feedScrollPhase: null,
68
- lastOverlayTap: 0,
69
- lastOverlayDoubleTap: null,
70
- currentCarouselItem: null,
71
- nextCarouselItem: null,
72
- isCarouselActive: false,
73
- currentCarouselPage: 0,
74
- activeCellType: null,
75
57
  };
76
58
 
77
59
  type Action =
@@ -84,38 +66,20 @@ type Action =
84
66
  | { type: 'CAPTION_TRACK'; payload: CaptionTrack | null }
85
67
  | { type: 'CUE'; payload: { text: string; startTime: number; endTime: number } | null }
86
68
  | { type: 'PREFETCH_COUNT'; payload: number }
87
- | { type: 'REMAINING_COUNT'; payload: number }
88
- | { type: 'OVERLAY_CONFIGURE'; payload: ContentItem }
89
- | { type: 'OVERLAY_ACTIVATE'; payload: ContentItem }
90
- | { type: 'OVERLAY_RESET' }
91
- | { type: 'OVERLAY_TAP' }
92
- | { type: 'OVERLAY_DOUBLE_TAP'; payload: { x: number; y: number } }
93
- | { type: 'ACTIVE_CELL_TYPE'; payload: 'video' | 'carousel' | null }
94
- | { type: 'FEED_TRANSITION_ENDED'; payload: { isVideo: boolean } }
95
- | { type: 'CAROUSEL_OVERLAY_CONFIGURE'; payload: ImageCarouselItem }
96
- | { type: 'CAROUSEL_OVERLAY_ACTIVATE'; payload: ImageCarouselItem }
97
- | { type: 'CAROUSEL_OVERLAY_RESET' }
98
- | { type: 'CAROUSEL_PAGE_CHANGED'; payload: number }
99
69
  | { type: 'FEED_SCROLL_PHASE'; payload: FeedScrollPhase };
100
70
 
101
71
  function reducer(state: State, action: Action): State {
102
72
  switch (action.type) {
103
73
  case 'PLAYER_STATE': {
104
- // Only allow isActive to transition TO true from player state.
105
- // Once active, the overlay stays mounted — hiding during transient
106
- // states like "loading" between videos would cause a visible flash.
107
74
  const isPlaybackActive =
108
75
  action.payload === 'playing' ||
109
76
  action.payload === 'paused' ||
110
77
  action.payload === 'buffering' ||
111
78
  action.payload === 'seeking';
112
- const becameActive = !state.isActive && isPlaybackActive;
113
79
  return {
114
80
  ...state,
115
81
  playerState: action.payload,
116
82
  isActive: state.isActive || isPlaybackActive,
117
- // First playback means a video cell is active (initial load)
118
- activeCellType: becameActive ? 'video' : state.activeCellType,
119
83
  };
120
84
  }
121
85
  case 'CURRENT_ITEM':
@@ -134,84 +98,6 @@ function reducer(state: State, action: Action): State {
134
98
  return { ...state, activeCue: action.payload };
135
99
  case 'PREFETCH_COUNT':
136
100
  return { ...state, prefetchedAheadCount: action.payload };
137
- case 'REMAINING_COUNT':
138
- return { ...state, remainingContentCount: action.payload };
139
- case 'OVERLAY_CONFIGURE':
140
- return { ...state, nextItem: action.payload };
141
- case 'OVERLAY_ACTIVATE':
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
- return {
148
- ...state,
149
- currentItem: action.payload,
150
- isActive: true,
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,
157
- };
158
- case 'OVERLAY_RESET':
159
- // Don't set isActive = false — the overlay stays mounted during
160
- // transitions. In the native SDK each cell has its own overlay
161
- // instance, so there's no gap. We replicate this by keeping the
162
- // single JS overlay mounted and updating its content on activate.
163
- console.log('[ShortKitProvider] OVERLAY_RESET (video)');
164
- return { ...state, feedScrollPhase: null };
165
- case 'OVERLAY_TAP':
166
- return { ...state, lastOverlayTap: state.lastOverlayTap + 1 };
167
- case 'OVERLAY_DOUBLE_TAP':
168
- return {
169
- ...state,
170
- lastOverlayDoubleTap: {
171
- x: action.payload.x,
172
- y: action.payload.y,
173
- id: (state.lastOverlayDoubleTap?.id ?? 0) + 1,
174
- },
175
- };
176
- case 'ACTIVE_CELL_TYPE':
177
- console.log('[ShortKitProvider] ACTIVE_CELL_TYPE:', action.payload);
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
- }
204
- case 'CAROUSEL_OVERLAY_CONFIGURE':
205
- console.log('[ShortKitProvider] CAROUSEL_OVERLAY_CONFIGURE:', action.payload.id);
206
- return { ...state, nextCarouselItem: action.payload };
207
- case 'CAROUSEL_OVERLAY_ACTIVATE':
208
- console.log('[ShortKitProvider] CAROUSEL_OVERLAY_ACTIVATE:', action.payload.id);
209
- return { ...state, currentCarouselItem: action.payload, isCarouselActive: true, nextCarouselItem: null };
210
- case 'CAROUSEL_OVERLAY_RESET':
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
101
  case 'FEED_SCROLL_PHASE':
216
102
  return { ...state, feedScrollPhase: action.payload };
217
103
  default:
@@ -225,11 +111,11 @@ function reducer(state: State, action: Action): State {
225
111
 
226
112
  export function ShortKitProvider({
227
113
  apiKey,
228
- config,
229
114
  userId,
230
115
  clientAppName,
231
116
  clientAppVersion,
232
117
  customDimensions,
118
+ loadingViewComponent,
233
119
  children,
234
120
  }: ShortKitProviderProps): React.JSX.Element {
235
121
  const [state, dispatch] = useReducer(reducer, initialState);
@@ -241,14 +127,17 @@ export function ShortKitProvider({
241
127
  useEffect(() => {
242
128
  if (!NativeShortKitModule) return;
243
129
 
244
- const serializedConfig = serializeFeedConfigForModule(config);
130
+ if (loadingViewComponent) {
131
+ registerLoadingComponent(loadingViewComponent);
132
+ }
133
+
245
134
  const serializedDimensions = customDimensions
246
135
  ? JSON.stringify(customDimensions)
247
136
  : undefined;
248
137
 
249
138
  NativeShortKitModule.initialize(
250
139
  apiKey,
251
- serializedConfig,
140
+ !!loadingViewComponent,
252
141
  clientAppName,
253
142
  clientAppVersion,
254
143
  serializedDimensions,
@@ -341,6 +230,7 @@ export function ShortKitProvider({
341
230
  author: event.author,
342
231
  articleUrl: event.articleUrl,
343
232
  commentCount: event.commentCount,
233
+ fallbackUrl: event.fallbackUrl,
344
234
  };
345
235
  dispatch({ type: 'CURRENT_ITEM', payload: item });
346
236
  }),
@@ -409,96 +299,11 @@ export function ShortKitProvider({
409
299
  }),
410
300
  );
411
301
 
412
- // Remaining content count
413
- subscriptions.push(
414
- NativeShortKitModule.onRemainingContentCountChanged((event) => {
415
- dispatch({ type: 'REMAINING_COUNT', payload: event.count });
416
- }),
417
- );
418
-
419
- // Overlay lifecycle events
420
- subscriptions.push(
421
- NativeShortKitModule.onOverlayConfigure((event) => {
422
- const item = deserializeContentItem(event.item);
423
- if (item) {
424
- dispatch({ type: 'OVERLAY_CONFIGURE', payload: item });
425
- }
426
- }),
427
- );
428
-
429
- subscriptions.push(
430
- NativeShortKitModule.onOverlayActivate((event) => {
431
- const item = deserializeContentItem(event.item);
432
- if (item) {
433
- dispatch({ type: 'OVERLAY_ACTIVATE', payload: item });
434
- }
435
- }),
436
- );
437
-
438
- subscriptions.push(
439
- NativeShortKitModule.onOverlayReset((_event) => {
440
- dispatch({ type: 'OVERLAY_RESET' });
441
- }),
442
- );
443
-
444
- // Overlay tap events
445
- subscriptions.push(
446
- NativeShortKitModule.onOverlayTap((_event) => {
447
- dispatch({ type: 'OVERLAY_TAP' });
448
- }),
449
- );
450
-
451
- subscriptions.push(
452
- NativeShortKitModule.onOverlayDoubleTap((event) => {
453
- dispatch({
454
- type: 'OVERLAY_DOUBLE_TAP',
455
- payload: { x: event.x, y: event.y },
456
- });
457
- }),
458
- );
459
-
460
- // Feed transition — track active cell type
461
- subscriptions.push(
462
- NativeShortKitModule.onFeedTransition((event) => {
463
- console.log('[ShortKitProvider] onFeedTransition:', event.phase, 'toItem:', event.toItem != null ? 'video' : 'non-video');
464
- if (event.phase === 'ended') {
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.
467
- const isVideo = event.toItem != null;
468
- dispatch({
469
- type: 'FEED_TRANSITION_ENDED',
470
- payload: { isVideo },
471
- });
472
- }
473
- }),
474
- );
475
-
476
- // Carousel overlay lifecycle events
477
- subscriptions.push(
478
- NativeShortKitModule.onCarouselOverlayConfigure((event) => {
479
- try {
480
- const item = JSON.parse(event.item) as ImageCarouselItem;
481
- dispatch({ type: 'CAROUSEL_OVERLAY_CONFIGURE', payload: item });
482
- } catch {
483
- // Ignore malformed JSON
484
- }
485
- }),
486
- );
487
-
488
- subscriptions.push(
489
- NativeShortKitModule.onCarouselOverlayActivate((event) => {
490
- try {
491
- const item = JSON.parse(event.item) as ImageCarouselItem;
492
- dispatch({ type: 'CAROUSEL_OVERLAY_ACTIVATE', payload: item });
493
- } catch {
494
- // Ignore malformed JSON
495
- }
496
- }),
497
- );
498
-
302
+ // Feed transition
499
303
  subscriptions.push(
500
- NativeShortKitModule.onCarouselOverlayReset((_event) => {
501
- dispatch({ type: 'CAROUSEL_OVERLAY_RESET' });
304
+ NativeShortKitModule.onFeedTransition((_event) => {
305
+ // Feed transition events are consumed by ShortKitFeed callback props.
306
+ // No state to update here in the simplified provider.
502
307
  }),
503
308
  );
504
309
 
@@ -520,7 +325,7 @@ export function ShortKitProvider({
520
325
  );
521
326
 
522
327
  // Note: Feed-level callback events (onDidLoop, onFeedTransition,
523
- // onShareTapped, etc.) are subscribed by the ShortKitFeed component
328
+ // onDismiss, etc.) are subscribed by the ShortKitFeed component
524
329
  // not here. The provider only manages state-driving events.
525
330
 
526
331
  return () => {
@@ -530,40 +335,6 @@ export function ShortKitProvider({
530
335
  };
531
336
  }, []);
532
337
 
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
-
567
338
  // -----------------------------------------------------------------------
568
339
  // Commands (stable refs)
569
340
  // -----------------------------------------------------------------------
@@ -623,17 +394,10 @@ export function ShortKitProvider({
623
394
  NativeShortKitModule?.clearUserId();
624
395
  }, []);
625
396
 
626
- const setFeedItemsCmd = useCallback((items: FeedInput[]) => {
627
- NativeShortKitModule?.setFeedItems(serializeFeedInputs(items));
628
- }, []);
629
-
630
- const appendFeedItemsCmd = useCallback((items: FeedInput[]) => {
631
- NativeShortKitModule?.appendFeedItems(serializeFeedInputs(items));
632
- }, []);
633
-
634
- const fetchContentCmd = useCallback(async (limit: number = 10): Promise<ContentItem[]> => {
397
+ const fetchContentCmd = useCallback(async (limit: number = 10, filter?: FeedFilter): Promise<ContentItem[]> => {
635
398
  if (!NativeShortKitModule) return [];
636
- const json = await NativeShortKitModule.fetchContent(limit);
399
+ const filterJSON = filter ? JSON.stringify(filter) : null;
400
+ const json = await NativeShortKitModule.fetchContent(limit, filterJSON);
637
401
  try {
638
402
  return JSON.parse(json) as ContentItem[];
639
403
  } catch {
@@ -656,6 +420,12 @@ export function ShortKitProvider({
656
420
  }
657
421
  }, []);
658
422
 
423
+ const preloadFeedCmd = useCallback(async (config?: Partial<FeedConfig>): Promise<string> => {
424
+ if (!NativeShortKitModule) return '';
425
+ const configJSON = config ? serializeFeedConfig(config as FeedConfig) : '{}';
426
+ return NativeShortKitModule.preloadFeed(configJSON);
427
+ }, []);
428
+
659
429
  // -----------------------------------------------------------------------
660
430
  // Context value (memoized to avoid unnecessary re-renders)
661
431
  // -----------------------------------------------------------------------
@@ -664,7 +434,6 @@ export function ShortKitProvider({
664
434
  // State
665
435
  playerState: state.playerState,
666
436
  currentItem: state.currentItem,
667
- nextItem: state.nextItem,
668
437
  time: state.time,
669
438
  isMuted: state.isMuted,
670
439
  playbackRate: state.playbackRate,
@@ -672,11 +441,8 @@ export function ShortKitProvider({
672
441
  activeCaptionTrack: state.activeCaptionTrack,
673
442
  activeCue: state.activeCue,
674
443
  prefetchedAheadCount: state.prefetchedAheadCount,
675
- remainingContentCount: state.remainingContentCount,
676
444
  isActive: state.isActive,
677
445
  feedScrollPhase: state.feedScrollPhase,
678
- lastOverlayTap: state.lastOverlayTap,
679
- lastOverlayDoubleTap: state.lastOverlayDoubleTap,
680
446
 
681
447
  // Commands
682
448
  play,
@@ -693,28 +459,14 @@ export function ShortKitProvider({
693
459
  setMaxBitrate,
694
460
  setUserId: setUserIdCmd,
695
461
  clearUserId: clearUserIdCmd,
696
- setFeedItems: setFeedItemsCmd,
697
- appendFeedItems: appendFeedItemsCmd,
698
462
  fetchContent: fetchContentCmd,
463
+ preloadFeed: preloadFeedCmd,
699
464
  prefetchStoryboard: prefetchStoryboardCmd,
700
465
  getStoryboardData: getStoryboardDataCmd,
701
-
702
- // Active cell type
703
- activeCellType: state.activeCellType,
704
-
705
- // Carousel overlay state
706
- currentCarouselItem: state.currentCarouselItem,
707
- nextCarouselItem: state.nextCarouselItem,
708
- isCarouselActive: state.isCarouselActive,
709
- currentCarouselPage: state.currentCarouselPage,
710
- // Internal — consumed by ShortKitFeed to pass to OverlayManager
711
- _overlayConfig: config.overlay ?? 'none',
712
- _carouselOverlayConfig: config.carouselOverlay ?? 'none',
713
466
  }),
714
467
  [
715
468
  state.playerState,
716
469
  state.currentItem,
717
- state.nextItem,
718
470
  state.time,
719
471
  state.isMuted,
720
472
  state.playbackRate,
@@ -722,16 +474,8 @@ export function ShortKitProvider({
722
474
  state.activeCaptionTrack,
723
475
  state.activeCue,
724
476
  state.prefetchedAheadCount,
725
- state.remainingContentCount,
726
477
  state.isActive,
727
478
  state.feedScrollPhase,
728
- state.lastOverlayTap,
729
- state.lastOverlayDoubleTap,
730
- state.activeCellType,
731
- state.currentCarouselItem,
732
- state.nextCarouselItem,
733
- state.isCarouselActive,
734
- state.currentCarouselPage,
735
479
  play,
736
480
  pause,
737
481
  seek,
@@ -746,13 +490,10 @@ export function ShortKitProvider({
746
490
  setMaxBitrate,
747
491
  setUserIdCmd,
748
492
  clearUserIdCmd,
749
- setFeedItemsCmd,
750
- appendFeedItemsCmd,
751
493
  fetchContentCmd,
494
+ preloadFeedCmd,
752
495
  prefetchStoryboardCmd,
753
496
  getStoryboardDataCmd,
754
- config.overlay,
755
- config.carouselOverlay,
756
497
  ],
757
498
  );
758
499
 
package/src/index.ts CHANGED
@@ -4,7 +4,6 @@ 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';
8
7
  export type {
9
8
  FeedConfig,
10
9
  FeedHeight,
@@ -31,13 +30,16 @@ export type {
31
30
  FormatChangeEvent,
32
31
  ContentSignal,
33
32
  SurveyOption,
34
- ShortKitError,
33
+ FeedFilter,
34
+ CaptionSource,
35
35
  ShortKitProviderProps,
36
36
  ShortKitFeedProps,
37
+ ShortKitFeedHandle,
37
38
  ShortKitPlayerProps,
38
39
  ShortKitWidgetProps,
39
40
  ShortKitPlayerState,
40
41
  StoryboardData,
41
42
  StoryboardTile,
42
43
  } from './types';
43
- export type { ShortKitCarouselState } from './useShortKitCarousel';
44
+ export { ShortKitCommands } from './ShortKitCommands';
45
+ export type { OverlayProps, CarouselOverlayProps } from './types';
@@ -7,54 +7,34 @@ import type {
7
7
  } from './types';
8
8
 
9
9
  /**
10
- * Serialized FeedConfig with JSON strings for complex fields.
11
- * Used by ShortKitFeedView native component props.
12
- */
13
- export interface SerializedFeedConfig {
14
- feedHeight: string;
15
- overlay: string;
16
- carouselOverlay: string;
17
- surveyMode: string;
18
- muteOnStart: boolean;
19
- feedSource: string;
20
- }
21
-
22
- /**
23
- * Serialize FeedConfig for the bridge.
10
+ * Serialize FeedConfig into a JSON string for the native Fabric view prop.
24
11
  * Complex union types are JSON-stringified; the `component` field on custom
25
12
  * overlays is stripped because React component references cannot cross the bridge.
13
+ * The overlay `name` is included so native can look up the correct Fabric surface.
26
14
  */
27
- export function serializeFeedConfig(config: FeedConfig): SerializedFeedConfig {
28
- let overlay: string;
29
- const rawOverlay = config.overlay ?? 'none';
30
- if (typeof rawOverlay === 'string') {
31
- overlay = JSON.stringify(rawOverlay);
32
- } else {
33
- // Strip the component ref — it stays on the JS side for OverlayManager
34
- overlay = JSON.stringify({ type: 'custom' });
35
- }
15
+ export function serializeFeedConfig(config: FeedConfig): string {
16
+ const overlay = (() => {
17
+ const raw = config.overlay ?? 'none';
18
+ if (typeof raw === 'string') return JSON.stringify(raw);
19
+ return JSON.stringify({ type: 'custom', name: raw.name });
20
+ })();
36
21
 
37
- return {
22
+ const carouselOverlay = (() => {
23
+ const raw = config.carouselOverlay ?? 'none';
24
+ if (typeof raw === 'string') return JSON.stringify(raw);
25
+ return JSON.stringify({ type: 'custom', name: raw.name });
26
+ })();
27
+
28
+ return JSON.stringify({
38
29
  feedHeight: JSON.stringify(config.feedHeight ?? { type: 'fullscreen' }),
39
30
  overlay,
40
- carouselOverlay: (() => {
41
- const raw = config.carouselOverlay ?? 'none';
42
- if (typeof raw === 'string') return JSON.stringify(raw);
43
- return JSON.stringify({ type: 'custom' });
44
- })(),
31
+ carouselOverlay,
45
32
  surveyMode: JSON.stringify(config.surveyMode ?? 'none'),
46
33
  muteOnStart: config.muteOnStart ?? true,
47
34
  feedSource: config.feedSource ?? 'algorithmic',
48
- };
49
- }
50
-
51
- /**
52
- * Serialize a FeedConfig into a single JSON string for the TurboModule
53
- * `initialize(config:)` parameter.
54
- */
55
- export function serializeFeedConfigForModule(config: FeedConfig): string {
56
- const serialized = serializeFeedConfig(config);
57
- return JSON.stringify(serialized);
35
+ autoplay: config.autoplay ?? true,
36
+ filter: config.filter ? JSON.stringify(config.filter) : undefined,
37
+ });
58
38
  }
59
39
 
60
40
  /**