@shortkitsdk/react-native 0.2.6 → 0.2.12

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 +17 -1
  3. package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +379 -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 +570 -0
  6. package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +1029 -0
  7. package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +212 -219
  8. package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +17 -3
  9. package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +157 -742
  10. package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +11 -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 +444 -0
  15. package/ios/SKFabricSurfaceWrapper.h +18 -0
  16. package/ios/SKFabricSurfaceWrapper.mm +57 -0
  17. package/ios/ShortKitBridge.swift +220 -63
  18. package/ios/ShortKitFeedView.swift +82 -228
  19. package/ios/ShortKitFeedViewManager.mm +3 -2
  20. package/ios/ShortKitModule.mm +69 -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 +3683 -1249
  25. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +56 -15
  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 +56 -15
  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 +3683 -1249
  31. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +56 -15
  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 +56 -15
  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 -24
  57. package/src/ShortKitFeed.tsx +124 -41
  58. package/src/ShortKitLoadingSurface.tsx +24 -0
  59. package/src/ShortKitOverlaySurface.tsx +313 -0
  60. package/src/ShortKitPlayer.tsx +30 -9
  61. package/src/ShortKitProvider.tsx +28 -285
  62. package/src/index.ts +9 -3
  63. package/src/serialization.ts +20 -39
  64. package/src/specs/NativeShortKitModule.ts +74 -45
  65. package/src/specs/ShortKitFeedViewNativeComponent.ts +3 -2
  66. package/src/types.ts +84 -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,7 +6,8 @@ import type { ShortKitContextValue } from './ShortKitContext';
6
6
  import type {
7
7
  ShortKitProviderProps,
8
8
  ContentItem,
9
- ImageCarouselItem,
9
+ FeedConfig,
10
+ FeedFilter,
10
11
  FeedInput,
11
12
  PlayerTime,
12
13
  PlayerState,
@@ -16,13 +17,13 @@ import type {
16
17
  StoryboardData,
17
18
  } from './types';
18
19
  import {
19
- serializeFeedConfigForModule,
20
- serializeFeedInputs,
20
+ serializeFeedConfig,
21
21
  deserializePlayerState,
22
22
  deserializeContentItem,
23
23
  deserializePlayerTime,
24
24
  } from './serialization';
25
25
  import NativeShortKitModule from './specs/NativeShortKitModule';
26
+ import { registerLoadingComponent } from './ShortKitLoadingSurface';
26
27
 
27
28
  // ---------------------------------------------------------------------------
28
29
  // State & Reducer
@@ -31,7 +32,6 @@ import NativeShortKitModule from './specs/NativeShortKitModule';
31
32
  interface State {
32
33
  playerState: PlayerState;
33
34
  currentItem: ContentItem | null;
34
- nextItem: ContentItem | null;
35
35
  time: PlayerTime;
36
36
  isMuted: boolean;
37
37
  playbackRate: number;
@@ -39,22 +39,13 @@ interface State {
39
39
  activeCaptionTrack: CaptionTrack | null;
40
40
  activeCue: { text: string; startTime: number; endTime: number } | null;
41
41
  prefetchedAheadCount: number;
42
- remainingContentCount: number;
43
42
  isActive: boolean;
44
43
  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
44
  }
53
45
 
54
46
  const initialState: State = {
55
47
  playerState: 'idle',
56
48
  currentItem: null,
57
- nextItem: null,
58
49
  time: { current: 0, duration: 0, buffered: 0 },
59
50
  isMuted: true,
60
51
  playbackRate: 1.0,
@@ -62,16 +53,8 @@ const initialState: State = {
62
53
  activeCaptionTrack: null,
63
54
  activeCue: null,
64
55
  prefetchedAheadCount: 0,
65
- remainingContentCount: 0,
66
56
  isActive: false,
67
57
  feedScrollPhase: null,
68
- lastOverlayTap: 0,
69
- lastOverlayDoubleTap: null,
70
- currentCarouselItem: null,
71
- nextCarouselItem: null,
72
- isCarouselActive: false,
73
- currentCarouselPage: 0,
74
- activeCellType: null,
75
58
  };
76
59
 
77
60
  type Action =
@@ -84,38 +67,20 @@ type Action =
84
67
  | { type: 'CAPTION_TRACK'; payload: CaptionTrack | null }
85
68
  | { type: 'CUE'; payload: { text: string; startTime: number; endTime: number } | null }
86
69
  | { 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
70
  | { type: 'FEED_SCROLL_PHASE'; payload: FeedScrollPhase };
100
71
 
101
72
  function reducer(state: State, action: Action): State {
102
73
  switch (action.type) {
103
74
  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
75
  const isPlaybackActive =
108
76
  action.payload === 'playing' ||
109
77
  action.payload === 'paused' ||
110
78
  action.payload === 'buffering' ||
111
79
  action.payload === 'seeking';
112
- const becameActive = !state.isActive && isPlaybackActive;
113
80
  return {
114
81
  ...state,
115
82
  playerState: action.payload,
116
83
  isActive: state.isActive || isPlaybackActive,
117
- // First playback means a video cell is active (initial load)
118
- activeCellType: becameActive ? 'video' : state.activeCellType,
119
84
  };
120
85
  }
121
86
  case 'CURRENT_ITEM':
@@ -134,84 +99,6 @@ function reducer(state: State, action: Action): State {
134
99
  return { ...state, activeCue: action.payload };
135
100
  case 'PREFETCH_COUNT':
136
101
  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
102
  case 'FEED_SCROLL_PHASE':
216
103
  return { ...state, feedScrollPhase: action.payload };
217
104
  default:
@@ -225,11 +112,11 @@ function reducer(state: State, action: Action): State {
225
112
 
226
113
  export function ShortKitProvider({
227
114
  apiKey,
228
- config,
229
115
  userId,
230
116
  clientAppName,
231
117
  clientAppVersion,
232
118
  customDimensions,
119
+ loadingViewComponent,
233
120
  children,
234
121
  }: ShortKitProviderProps): React.JSX.Element {
235
122
  const [state, dispatch] = useReducer(reducer, initialState);
@@ -241,14 +128,17 @@ export function ShortKitProvider({
241
128
  useEffect(() => {
242
129
  if (!NativeShortKitModule) return;
243
130
 
244
- const serializedConfig = serializeFeedConfigForModule(config);
131
+ if (loadingViewComponent) {
132
+ registerLoadingComponent(loadingViewComponent);
133
+ }
134
+
245
135
  const serializedDimensions = customDimensions
246
136
  ? JSON.stringify(customDimensions)
247
137
  : undefined;
248
138
 
249
139
  NativeShortKitModule.initialize(
250
140
  apiKey,
251
- serializedConfig,
141
+ !!loadingViewComponent,
252
142
  clientAppName,
253
143
  clientAppVersion,
254
144
  serializedDimensions,
@@ -341,6 +231,7 @@ export function ShortKitProvider({
341
231
  author: event.author,
342
232
  articleUrl: event.articleUrl,
343
233
  commentCount: event.commentCount,
234
+ fallbackUrl: event.fallbackUrl,
344
235
  };
345
236
  dispatch({ type: 'CURRENT_ITEM', payload: item });
346
237
  }),
@@ -409,96 +300,11 @@ export function ShortKitProvider({
409
300
  }),
410
301
  );
411
302
 
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
-
303
+ // Feed transition
499
304
  subscriptions.push(
500
- NativeShortKitModule.onCarouselOverlayReset((_event) => {
501
- dispatch({ type: 'CAROUSEL_OVERLAY_RESET' });
305
+ NativeShortKitModule.onFeedTransition((_event) => {
306
+ // Feed transition events are consumed by ShortKitFeed callback props.
307
+ // No state to update here in the simplified provider.
502
308
  }),
503
309
  );
504
310
 
@@ -520,7 +326,7 @@ export function ShortKitProvider({
520
326
  );
521
327
 
522
328
  // Note: Feed-level callback events (onDidLoop, onFeedTransition,
523
- // onShareTapped, etc.) are subscribed by the ShortKitFeed component
329
+ // onDismiss, etc.) are subscribed by the ShortKitFeed component
524
330
  // not here. The provider only manages state-driving events.
525
331
 
526
332
  return () => {
@@ -530,40 +336,6 @@ export function ShortKitProvider({
530
336
  };
531
337
  }, []);
532
338
 
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
339
  // -----------------------------------------------------------------------
568
340
  // Commands (stable refs)
569
341
  // -----------------------------------------------------------------------
@@ -623,17 +395,10 @@ export function ShortKitProvider({
623
395
  NativeShortKitModule?.clearUserId();
624
396
  }, []);
625
397
 
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[]> => {
398
+ const fetchContentCmd = useCallback(async (limit: number = 10, filter?: FeedFilter): Promise<ContentItem[]> => {
635
399
  if (!NativeShortKitModule) return [];
636
- const json = await NativeShortKitModule.fetchContent(limit);
400
+ const filterJSON = filter ? JSON.stringify(filter) : null;
401
+ const json = await NativeShortKitModule.fetchContent(limit, filterJSON);
637
402
  try {
638
403
  return JSON.parse(json) as ContentItem[];
639
404
  } catch {
@@ -656,6 +421,13 @@ export function ShortKitProvider({
656
421
  }
657
422
  }, []);
658
423
 
424
+ const preloadFeedCmd = useCallback(async (config?: Partial<FeedConfig>, items?: FeedInput[]): Promise<string> => {
425
+ if (!NativeShortKitModule) return '';
426
+ const configJSON = config ? serializeFeedConfig(config as FeedConfig) : '{}';
427
+ const itemsJSON = items ? JSON.stringify(items) : null;
428
+ return NativeShortKitModule.preloadFeed(configJSON, itemsJSON);
429
+ }, []);
430
+
659
431
  // -----------------------------------------------------------------------
660
432
  // Context value (memoized to avoid unnecessary re-renders)
661
433
  // -----------------------------------------------------------------------
@@ -664,7 +436,6 @@ export function ShortKitProvider({
664
436
  // State
665
437
  playerState: state.playerState,
666
438
  currentItem: state.currentItem,
667
- nextItem: state.nextItem,
668
439
  time: state.time,
669
440
  isMuted: state.isMuted,
670
441
  playbackRate: state.playbackRate,
@@ -672,11 +443,8 @@ export function ShortKitProvider({
672
443
  activeCaptionTrack: state.activeCaptionTrack,
673
444
  activeCue: state.activeCue,
674
445
  prefetchedAheadCount: state.prefetchedAheadCount,
675
- remainingContentCount: state.remainingContentCount,
676
446
  isActive: state.isActive,
677
447
  feedScrollPhase: state.feedScrollPhase,
678
- lastOverlayTap: state.lastOverlayTap,
679
- lastOverlayDoubleTap: state.lastOverlayDoubleTap,
680
448
 
681
449
  // Commands
682
450
  play,
@@ -693,28 +461,14 @@ export function ShortKitProvider({
693
461
  setMaxBitrate,
694
462
  setUserId: setUserIdCmd,
695
463
  clearUserId: clearUserIdCmd,
696
- setFeedItems: setFeedItemsCmd,
697
- appendFeedItems: appendFeedItemsCmd,
698
464
  fetchContent: fetchContentCmd,
465
+ preloadFeed: preloadFeedCmd,
699
466
  prefetchStoryboard: prefetchStoryboardCmd,
700
467
  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
468
  }),
714
469
  [
715
470
  state.playerState,
716
471
  state.currentItem,
717
- state.nextItem,
718
472
  state.time,
719
473
  state.isMuted,
720
474
  state.playbackRate,
@@ -722,16 +476,8 @@ export function ShortKitProvider({
722
476
  state.activeCaptionTrack,
723
477
  state.activeCue,
724
478
  state.prefetchedAheadCount,
725
- state.remainingContentCount,
726
479
  state.isActive,
727
480
  state.feedScrollPhase,
728
- state.lastOverlayTap,
729
- state.lastOverlayDoubleTap,
730
- state.activeCellType,
731
- state.currentCarouselItem,
732
- state.nextCarouselItem,
733
- state.isCarouselActive,
734
- state.currentCarouselPage,
735
481
  play,
736
482
  pause,
737
483
  seek,
@@ -746,13 +492,10 @@ export function ShortKitProvider({
746
492
  setMaxBitrate,
747
493
  setUserIdCmd,
748
494
  clearUserIdCmd,
749
- setFeedItemsCmd,
750
- appendFeedItemsCmd,
751
495
  fetchContentCmd,
496
+ preloadFeedCmd,
752
497
  prefetchStoryboardCmd,
753
498
  getStoryboardDataCmd,
754
- config.overlay,
755
- config.carouselOverlay,
756
499
  ],
757
500
  );
758
501
 
package/src/index.ts CHANGED
@@ -4,10 +4,10 @@ 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,
10
+ ScrollAxis,
11
11
  FeedSource,
12
12
  OverlayConfig,
13
13
  CarouselOverlayConfig,
@@ -31,13 +31,19 @@ export type {
31
31
  FormatChangeEvent,
32
32
  ContentSignal,
33
33
  SurveyOption,
34
- ShortKitError,
34
+ FeedFilter,
35
+ CaptionSource,
35
36
  ShortKitProviderProps,
36
37
  ShortKitFeedProps,
38
+ ShortKitFeedHandle,
37
39
  ShortKitPlayerProps,
38
40
  ShortKitWidgetProps,
39
41
  ShortKitPlayerState,
40
42
  StoryboardData,
41
43
  StoryboardTile,
42
44
  } from './types';
43
- export type { ShortKitCarouselState } from './useShortKitCarousel';
45
+ export { ShortKitCommands } from './ShortKitCommands';
46
+ export { default as NativeShortKitModule } from './specs/NativeShortKitModule';
47
+ export { registerOverlayComponent } from './ShortKitOverlaySurface';
48
+ export { registerCarouselOverlayComponent } from './ShortKitCarouselOverlaySurface';
49
+ export type { OverlayProps, CarouselOverlayProps } from './types';
@@ -7,54 +7,35 @@ 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' }),
30
+ scrollAxis: config.scrollAxis ?? 'vertical',
39
31
  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
- })(),
32
+ carouselOverlay,
45
33
  surveyMode: JSON.stringify(config.surveyMode ?? 'none'),
46
34
  muteOnStart: config.muteOnStart ?? true,
47
35
  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);
36
+ autoplay: config.autoplay ?? true,
37
+ filter: config.filter ? JSON.stringify(config.filter) : undefined,
38
+ });
58
39
  }
59
40
 
60
41
  /**