@shortkitsdk/react-native 0.2.24 → 0.2.26

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 (43) hide show
  1. package/README.md +151 -0
  2. package/android/libs/shortkit-release.aar +0 -0
  3. package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +19 -1
  4. package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +43 -0
  5. package/ios/ReactCarouselOverlayHost.swift +51 -3
  6. package/ios/ReactOverlayHost.swift +67 -7
  7. package/ios/ReactVideoCarouselOverlayHost.swift +181 -19
  8. package/ios/SKFabricSurfaceWrapper.mm +7 -1
  9. package/ios/ShortKitBridge.swift +85 -5
  10. package/ios/ShortKitFeedView.swift +70 -4
  11. package/ios/ShortKitFeedViewManager.mm +3 -0
  12. package/ios/ShortKitModule.mm +46 -3
  13. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Info.plist +2 -2
  14. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +5273 -337
  15. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +151 -7
  16. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  17. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +151 -7
  18. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  19. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources +9 -9
  20. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Info.plist +2 -2
  21. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +5273 -337
  22. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +151 -7
  23. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  24. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +151 -7
  25. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +5273 -337
  26. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +151 -7
  27. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  28. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +151 -7
  29. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  30. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/_CodeSignature/CodeResources +17 -17
  31. package/package.json +1 -1
  32. package/src/ShortKitCarouselOverlaySurface.tsx +38 -10
  33. package/src/ShortKitCommands.ts +4 -0
  34. package/src/ShortKitFeed.tsx +65 -10
  35. package/src/ShortKitOverlaySurface.tsx +59 -23
  36. package/src/ShortKitProvider.tsx +2 -1
  37. package/src/ShortKitVideoCarouselOverlaySurface.tsx +51 -5
  38. package/src/index.ts +2 -0
  39. package/src/serialization.ts +37 -1
  40. package/src/specs/NativeShortKitModule.ts +68 -3
  41. package/src/specs/ShortKitFeedViewNativeComponent.ts +11 -0
  42. package/src/types.ts +85 -3
  43. package/src/useShortKitCarousel.ts +80 -0
@@ -96,6 +96,12 @@ type RefreshStateChangedEvent = Readonly<{
96
96
  progress: Double;
97
97
  }>;
98
98
 
99
+ type RefreshStateChangedPerFeedEvent = Readonly<{
100
+ feedId: string;
101
+ status: string;
102
+ progress: Double;
103
+ }>;
104
+
99
105
  type DidFetchContentItemsEvent = Readonly<{
100
106
  items: string; // JSON-serialized ContentItem[]
101
107
  }>;
@@ -109,6 +115,13 @@ type FeedReadyEvent = Readonly<{
109
115
  feedId: string;
110
116
  }>;
111
117
 
118
+ type VideoCarouselCellTapEvent = Readonly<{
119
+ feedId: string;
120
+ id: string;
121
+ index: Int32;
122
+ pageIndex: Int32;
123
+ }>;
124
+
112
125
  type DownloadStartedEvent = Readonly<{
113
126
  itemId: string;
114
127
  }>;
@@ -177,12 +190,38 @@ type CarouselActiveImageEvent = Readonly<{
177
190
  activeImageIndex: Int32;
178
191
  }>;
179
192
 
193
+ type CarouselItemChangedEvent = Readonly<{
194
+ surfaceId: string;
195
+ item: string; // JSON-serialized ImageCarouselItem (with local file:// URLs where cached)
196
+ isActive: boolean;
197
+ activeImageIndex: Int32;
198
+ }>;
199
+
180
200
  type VideoCarouselActiveVideoEvent = Readonly<{
181
201
  surfaceId: string;
182
202
  activeVideo: string;
183
203
  activeVideoIndex: Int32;
184
204
  }>;
185
205
 
206
+ type VideoCarouselItemChangedEvent = Readonly<{
207
+ surfaceId: string;
208
+ carouselItem: string; // JSON-serialized VideoCarouselItem
209
+ activeVideo: string | null; // JSON-serialized ContentItem; null when item has no videos
210
+ activeVideoIndex: Int32;
211
+ isActive: boolean;
212
+ playerState: string;
213
+ isMuted: boolean;
214
+ }>;
215
+
216
+ type CarouselActiveVideoCompletedEvent = Readonly<{
217
+ surfaceId: string;
218
+ contentItem: string; // JSON-serialized ContentItem
219
+ indexInCarousel: Int32;
220
+ carouselItem: string; // JSON-serialized VideoCarouselItem
221
+ wasLast: boolean;
222
+ willAutoAdvance: boolean;
223
+ }>;
224
+
186
225
  type OverlayFullStateEvent = Readonly<{
187
226
  surfaceId: string;
188
227
  isActive: boolean;
@@ -190,8 +229,20 @@ type OverlayFullStateEvent = Readonly<{
190
229
  isMuted: boolean;
191
230
  playbackRate: Double;
192
231
  captionsEnabled: boolean;
193
- activeCue: string;
194
- feedScrollPhase: string;
232
+ activeCue: string | null;
233
+ feedScrollPhase: string | null;
234
+ }>;
235
+
236
+ type OverlayItemChangedEvent = Readonly<{
237
+ surfaceId: string;
238
+ item: string; // JSON-serialized ContentItem
239
+ isActive: boolean;
240
+ playerState: string;
241
+ isMuted: boolean;
242
+ playbackRate: Double;
243
+ captionsEnabled: boolean;
244
+ activeCue: string | null;
245
+ feedScrollPhase: string | null;
195
246
  }>;
196
247
 
197
248
  export interface Spec extends TurboModule {
@@ -217,6 +268,9 @@ export interface Spec extends TurboModule {
217
268
  seekAndPlay(seconds: Double): void;
218
269
  skipToNext(): void;
219
270
  skipToPrevious(): void;
271
+ carouselNext(): boolean;
272
+ carouselPrevious(): boolean;
273
+ carouselSetActiveIndex(index: Int32): boolean;
220
274
  setMuted(muted: boolean): void;
221
275
  setPlaybackRate(rate: Double): void;
222
276
  setCaptionsEnabled(enabled: boolean): void;
@@ -225,7 +279,8 @@ export interface Spec extends TurboModule {
225
279
  setMaxBitrate(bitrate: Double): void;
226
280
 
227
281
  // --- Custom feed ---
228
- setFeedItems(feedId: string, items: string): void;
282
+ setFeedItems(feedId: string, items: string, startAtId: string | null): void;
283
+ scrollFeedToItem(feedId: string, id: string, animated: boolean): void;
229
284
  appendFeedItems(feedId: string, items: string): void;
230
285
  fetchContent(limit: Int32, filterJSON: string | null): Promise<string>;
231
286
  applyFilter(feedId: string, filterJSON: string | null): void;
@@ -239,6 +294,10 @@ export interface Spec extends TurboModule {
239
294
  downloadVideo(itemId: string, mode: string): Promise<string>;
240
295
  cancelDownload(): void;
241
296
 
297
+ // --- Carousel accessors ---
298
+ getCarouselActiveIndex(): Int32;
299
+ getCarouselVideoCount(): Int32;
300
+
242
301
  // --- Event emitters ---
243
302
  readonly onPlayerStateChanged: EventEmitter<PlayerStateEvent>;
244
303
  readonly onCurrentItemChanged: EventEmitter<CurrentItemEvent>;
@@ -257,8 +316,10 @@ export interface Spec extends TurboModule {
257
316
  readonly onContentTapped: EventEmitter<ContentTappedEvent>;
258
317
  readonly onDismiss: EventEmitter<DismissEvent>;
259
318
  readonly onRefreshStateChanged: EventEmitter<RefreshStateChangedEvent>;
319
+ readonly onRefreshStateChangedPerFeed: EventEmitter<RefreshStateChangedPerFeedEvent>;
260
320
  readonly onDidFetchContentItems: EventEmitter<DidFetchContentItemsEvent>;
261
321
  readonly onFeedReady: EventEmitter<FeedReadyEvent>;
322
+ readonly onVideoCarouselCellTap: EventEmitter<VideoCarouselCellTapEvent>;
262
323
 
263
324
  // --- Overlay per-surface events ---
264
325
  readonly onOverlayActiveChanged: EventEmitter<OverlayActiveEvent>;
@@ -270,8 +331,12 @@ export interface Spec extends TurboModule {
270
331
  readonly onOverlayFeedScrollPhaseChanged: EventEmitter<OverlayFeedScrollPhaseEvent>;
271
332
  readonly onOverlayTimeUpdate: EventEmitter<OverlayTimeUpdateEvent>;
272
333
  readonly onOverlayFullState: EventEmitter<OverlayFullStateEvent>;
334
+ readonly onOverlayItemChanged: EventEmitter<OverlayItemChangedEvent>;
273
335
  readonly onCarouselActiveImageChanged: EventEmitter<CarouselActiveImageEvent>;
336
+ readonly onCarouselItemChanged: EventEmitter<CarouselItemChangedEvent>;
274
337
  readonly onVideoCarouselActiveVideoChanged: EventEmitter<VideoCarouselActiveVideoEvent>;
338
+ readonly onVideoCarouselItemChanged: EventEmitter<VideoCarouselItemChangedEvent>;
339
+ readonly onCarouselActiveVideoCompleted: EventEmitter<CarouselActiveVideoCompletedEvent>;
275
340
 
276
341
  // --- Download events ---
277
342
  readonly onDownloadStarted: EventEmitter<DownloadStartedEvent>;
@@ -6,6 +6,17 @@ export interface NativeProps extends ViewProps {
6
6
  feedId?: string;
7
7
  startAtItemId?: string;
8
8
  preloadId?: string;
9
+ /**
10
+ * URL of a thumbnail already rendered/cached on the host side (e.g. the
11
+ * grid tile the user tapped to open this feed). When expo-image or FastImage
12
+ * is used on iOS, SDK fetches the decoded UIImage from SDWebImage's memory
13
+ * cache and paints it onto the first cell with zero network and zero
14
+ * latency. Falls back to network fetch for non-SDWebImage clients.
15
+ */
16
+ seedThumbnailUrl?: string;
17
+ /** JSON-serialized FeedInput[] for initial custom-feed items at mount time. */
18
+ feedItemsJSON?: string;
19
+ active?: boolean;
9
20
  }
10
21
 
11
22
  export default codegenNativeComponent<NativeProps>(
package/src/types.ts CHANGED
@@ -113,6 +113,8 @@ export interface VideoCarouselItem {
113
113
  articleUrl?: string;
114
114
  }
115
115
 
116
+ export type ContentOrigin = 'ios_upload' | 'other';
117
+
116
118
  /**
117
119
  * Per-slide input for a VideoCarouselInput. Mirrors the `{ type: 'video' }`
118
120
  * FeedInput variant — host apps pass a playback ID and the SDK constructs
@@ -120,6 +122,7 @@ export interface VideoCarouselItem {
120
122
  */
121
123
  export interface VideoCarouselVideoInput {
122
124
  playbackId: string;
125
+ origin?: ContentOrigin;
123
126
  fallbackUrl?: string;
124
127
  }
125
128
 
@@ -142,7 +145,7 @@ export interface VideoCarouselInput {
142
145
  }
143
146
 
144
147
  export type FeedInput =
145
- | { type: 'video'; playbackId: string; fallbackUrl?: string }
148
+ | { type: 'video'; playbackId: string; origin?: ContentOrigin; fallbackUrl?: string }
146
149
  | { type: 'imageCarousel'; item: ImageCarouselItem }
147
150
  | { type: 'videoCarousel'; item: VideoCarouselInput };
148
151
 
@@ -300,6 +303,45 @@ export interface ShortKitFeedProps {
300
303
  style?: ViewStyle;
301
304
  /** Item ID to scroll to on initial load. */
302
305
  startAtItemId?: string;
306
+ /**
307
+ * URL of the thumbnail already displayed in your UI (e.g. the grid tile
308
+ * the user just tapped). If your app uses expo-image or FastImage, the SDK
309
+ * will extract the already-decoded UIImage from SDWebImage's memory cache
310
+ * and paint it on the first feed cell with zero network overhead — closing
311
+ * the black-frame gap before video playback begins.
312
+ *
313
+ * Falls back silently (no extra network beyond the existing fallback) for
314
+ * apps on React Native's core <Image> or other image libraries.
315
+ */
316
+ seedThumbnailUrl?: string;
317
+ /**
318
+ * Initial feed items for custom feed mode (`feedSource: 'custom'`).
319
+ *
320
+ * Items are delivered to the native view as construction-time props so the
321
+ * SDK renders the first cell on the very first run-loop tick — no
322
+ * ref-attach race, no async bridge hop, no loading-state flash.
323
+ *
324
+ * Use for supplying known items at mount (e.g. a cached last-watched
325
+ * video). For post-mount updates (pagination, refresh), use the imperative
326
+ * `setFeedItems()` / `appendFeedItems()` methods on the ref.
327
+ *
328
+ * When both `feedItems` and `preloadId` are set, `preloadId` takes
329
+ * precedence (it carries prefetched HLS manifests / segment 0).
330
+ */
331
+ feedItems?: FeedInput[];
332
+ /**
333
+ * Whether the feed is the active/focused surface. When `false`, the SDK
334
+ * suspends playback and yields players so an adjacent feed (e.g. another
335
+ * tab) can use them without cross-surface contamination.
336
+ *
337
+ * Pass `useIsFocused()` from React Navigation. When omitted, the SDK
338
+ * infers suspension from UIKit's `willMove(toWindow:)` lifecycle —
339
+ * which works for push/pop navigation but NOT for tab/pager navigation
340
+ * where the view stays in the window hierarchy.
341
+ *
342
+ * @default true
343
+ */
344
+ active?: boolean;
303
345
  onLoop?: (event: LoopEvent) => void;
304
346
  onFeedTransition?: (event: FeedTransitionEvent) => void;
305
347
  onFormatChange?: (event: FormatChangeEvent) => void;
@@ -316,6 +358,26 @@ export interface ShortKitFeedProps {
316
358
  /** Called once when this feed has loaded content and assigned a player
317
359
  * to the first cell. Use to dismiss a splash screen or loading overlay. */
318
360
  onFeedReady?: () => void;
361
+ /** Called when the active video in a video carousel completes playback. */
362
+ onCarouselActiveVideoCompleted?: (event: {
363
+ surfaceId: string;
364
+ contentItem: ContentItem;
365
+ indexInCarousel: number;
366
+ carouselItem: VideoCarouselItem;
367
+ wasLast: boolean;
368
+ willAutoAdvance: boolean;
369
+ }) => void;
370
+ /**
371
+ * Fires when the user taps a video-carousel cell without triggering the
372
+ * cell's horizontal pan. Use with `useShortKit().setMuted(...)` to
373
+ * implement tap-to-mute on video-carousel overlays. Single-video and
374
+ * image-carousel cells don't need this — JS `<Pressable>` works for them.
375
+ */
376
+ onVideoCarouselCellTap?: (event: {
377
+ id: string;
378
+ index: number;
379
+ pageIndex: number;
380
+ }) => void;
319
381
  }
320
382
 
321
383
  /**
@@ -323,8 +385,25 @@ export interface ShortKitFeedProps {
323
385
  * Obtained via `ref` on `<ShortKitFeed>`.
324
386
  */
325
387
  export interface ShortKitFeedHandle {
326
- /** Replace all items in this feed instance. */
327
- setFeedItems: (items: FeedInput[]) => void;
388
+ /**
389
+ * Replace the feed's items atomically.
390
+ *
391
+ * @param items The new feed contents.
392
+ * @param options Optional. `startAt` specifies the item id to focus after
393
+ * the items apply. Overrides the `startAtItemId` prop for
394
+ * this call only — the prop's value is preserved for
395
+ * subsequent calls that omit this option.
396
+ */
397
+ setFeedItems: (items: FeedInput[], options?: { startAt?: string }) => void;
398
+
399
+ /**
400
+ * Imperatively scroll the feed to the item with the given id. Silently
401
+ * no-ops if the id is not in the current items. Call after `onFeedReady`
402
+ * for the initial mount; for an atomic set-and-scroll, use
403
+ * `setFeedItems(items, { startAt })` instead.
404
+ */
405
+ scrollToItem: (id: string, options?: { animated?: boolean }) => void;
406
+
328
407
  /** Append items to this feed instance. */
329
408
  appendFeedItems: (items: FeedInput[]) => void;
330
409
  /** Apply a content filter to this feed instance. Pass null to clear. */
@@ -382,6 +461,9 @@ export interface ShortKitWidgetProps {
382
461
 
383
462
  // --- Hook Return Types ---
384
463
 
464
+ export type { ShortKitCarouselState } from './useShortKitCarousel';
465
+
466
+
385
467
  export interface ShortKitPlayerState {
386
468
  playerState: PlayerState;
387
469
  currentItem: ContentItem | null;
@@ -0,0 +1,80 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import NativeShortKitModule from './specs/NativeShortKitModule';
3
+ import { ShortKitCommands } from './ShortKitCommands';
4
+ import type { VideoCarouselItem } from './types';
5
+
6
+ export interface ShortKitCarouselState {
7
+ /** Index of the currently active video in the carousel, or null if no carousel is active. */
8
+ activeIndex: number | null;
9
+ /** Number of videos in the active carousel item, or 0 if none. */
10
+ videoCount: number;
11
+ /** The active carousel item, or null if no carousel is active. */
12
+ activeCarouselItem: VideoCarouselItem | null;
13
+ /** Advance to the next video in the carousel. Returns true if the command was dispatched. */
14
+ next: () => boolean;
15
+ /** Go back to the previous video in the carousel. Returns true if the command was dispatched. */
16
+ previous: () => boolean;
17
+ /** Jump to a specific index in the carousel. Returns true if the command was dispatched. */
18
+ setActiveIndex: (index: number) => boolean;
19
+ }
20
+
21
+ /**
22
+ * Hook to access video carousel state and commands.
23
+ *
24
+ * Subscribes to `onVideoCarouselItemChanged` and
25
+ * `onVideoCarouselActiveVideoChanged` from the native TurboModule and surfaces
26
+ * carousel state as reactive values. The imperative commands (`next`,
27
+ * `previous`, `setActiveIndex`) delegate to `ShortKitCommands`.
28
+ *
29
+ * Does not require a `<ShortKitProvider>` — it subscribes directly to native
30
+ * events and is suitable for use inside overlay components.
31
+ */
32
+ export function useShortKitCarousel(): ShortKitCarouselState {
33
+ const [activeIndex, setActiveIndexState] = useState<number | null>(() => {
34
+ const v = NativeShortKitModule?.getCarouselActiveIndex?.() ?? -1;
35
+ return v < 0 ? null : v;
36
+ });
37
+ const [activeCarouselItem, setActiveCarouselItem] = useState<VideoCarouselItem | null>(null);
38
+
39
+ useEffect(() => {
40
+ if (!NativeShortKitModule) return;
41
+ const subItem = NativeShortKitModule.onVideoCarouselItemChanged((e: any) => {
42
+ try {
43
+ const item = e.carouselItem
44
+ ? (JSON.parse(e.carouselItem) as VideoCarouselItem)
45
+ : null;
46
+ setActiveCarouselItem(item);
47
+ setActiveIndexState(
48
+ typeof e.activeVideoIndex === 'number' ? e.activeVideoIndex : null,
49
+ );
50
+ } catch {
51
+ // ignore parse errors
52
+ }
53
+ });
54
+ const subIdx = NativeShortKitModule.onVideoCarouselActiveVideoChanged((e: any) => {
55
+ setActiveIndexState(
56
+ typeof e.activeVideoIndex === 'number' ? e.activeVideoIndex : null,
57
+ );
58
+ });
59
+ return () => {
60
+ subItem?.remove?.();
61
+ subIdx?.remove?.();
62
+ };
63
+ }, []);
64
+
65
+ const next = useCallback(() => ShortKitCommands.carouselNext(), []);
66
+ const previous = useCallback(() => ShortKitCommands.carouselPrevious(), []);
67
+ const setActiveIndex = useCallback(
68
+ (i: number) => ShortKitCommands.carouselSetActiveIndex(i),
69
+ [],
70
+ );
71
+
72
+ return {
73
+ activeIndex,
74
+ videoCount: activeCarouselItem?.videos?.length ?? 0,
75
+ activeCarouselItem,
76
+ next,
77
+ previous,
78
+ setActiveIndex,
79
+ };
80
+ }