@xhub-reels/sdk 0.2.11 → 0.2.13

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.
package/dist/index.d.cts CHANGED
@@ -443,6 +443,7 @@ declare class PlayerEngine {
443
443
  private circuitResetTimer;
444
444
  private watchTimeInterval;
445
445
  private lastStatus;
446
+ private _destroyed;
446
447
  constructor(config?: PlayerConfig, analytics?: IAnalytics, logger?: ILogger);
447
448
  /**
448
449
  * Load a video and prepare for playback.
@@ -498,11 +499,14 @@ declare class FeedManager {
498
499
  private inFlightRequests;
499
500
  /** LRU tracking: itemId → lastAccessTime */
500
501
  private accessOrder;
502
+ /** Idempotency guard for destroy() */
503
+ private _destroyed;
501
504
  /** Prefetch cache — instance-scoped (not static) */
502
505
  private prefetchCache;
503
506
  constructor(dataSource: IDataSource, config?: FeedConfig, logger?: ILogger);
504
507
  getDataSource(): IDataSource;
505
508
  setDataSource(dataSource: IDataSource, reset?: boolean): void;
509
+ setInitialItems(items: ContentItem[]): void;
506
510
  prefetch(ttlMs?: number): Promise<void>;
507
511
  hasPrefetchCache(): boolean;
508
512
  clearPrefetchCache(): void;
@@ -556,6 +560,8 @@ declare class OptimisticManager {
556
560
  private likeDebounceTimers;
557
561
  /** Pending like direction: contentId → final intended state */
558
562
  private pendingLikeState;
563
+ /** Idempotency guard for destroy() */
564
+ private _destroyed;
559
565
  constructor(interaction: Partial<IInteraction>, logger?: ILogger);
560
566
  /**
561
567
  * Debounced like toggle — prevents rapid API spam on double-tap.
@@ -626,6 +632,7 @@ declare class ResourceGovernor {
626
632
  private readonly logger?;
627
633
  private focusDebounceTimer;
628
634
  private networkUnsubscribe?;
635
+ private _destroyed;
629
636
  constructor(config?: ResourceConfig, videoLoader?: IVideoLoader, network?: INetworkAdapter, logger?: ILogger);
630
637
  activate(): Promise<void>;
631
638
  deactivate(): void;
@@ -658,6 +665,103 @@ declare class ResourceGovernor {
658
665
  private recalculate;
659
666
  }
660
667
 
668
+ /**
669
+ * NavigationManager — Owns the open/close lifecycle of the SDK reels viewer.
670
+ *
671
+ * Framework-agnostic class (zustand/vanilla) mirroring FeedManager /
672
+ * ResourceGovernor / PlayerEngine conventions. It is the single source of
673
+ * truth for "is the reels viewer open, and at which index".
674
+ *
675
+ * WHY THIS EXISTS:
676
+ * Previously the host app owned the modal entirely (via onThumbnailClick).
677
+ * That meant the SDK could not warm a video before the host mounted
678
+ * <ReelsFeed>, producing the "poster stuck ~3s" stall. By owning the
679
+ * open/close phase, the SDK can:
680
+ * 1. mount <ReelsFeed> early (hidden behind the animating container),
681
+ * 2. prewarm the focused video DURING the open animation,
682
+ * 3. reveal an already-decoded first frame → no poster stall.
683
+ *
684
+ * PHASE MODEL (state machine):
685
+ * closed → opening : open(index) called (e.g. from a thumbnail tap)
686
+ * opening → open : ReelsModal finished its enter animation
687
+ * open → closing : close() called
688
+ * closing → closed : ReelsModal finished its exit animation
689
+ *
690
+ * Invalid transitions are dropped (see isValidNavTransition). This mirrors
691
+ * the PlayerEngine state-machine discipline used elsewhere in the SDK.
692
+ *
693
+ * The component layer (ReelsModal) drives the animation timing and calls
694
+ * `setPhase('open')` / `setPhase('closed')` when its Web Animations API
695
+ * transitions finish. NavigationManager itself holds no timers tied to
696
+ * animation — it only holds a safety fallback timer so a missing/janky
697
+ * animationend can never strand the phase forever.
698
+ */
699
+
700
+ type NavigationPhase = 'closed' | 'opening' | 'open' | 'closing';
701
+ interface NavigationState {
702
+ /** Current lifecycle phase of the reels viewer. */
703
+ phase: NavigationPhase;
704
+ /**
705
+ * Index the viewer was opened at. Held through the whole open lifecycle
706
+ * (opening → open → closing) and cleared back to null only once fully
707
+ * closed. ReelsModal mounts <ReelsFeed> whenever this is non-null so the
708
+ * feed can warm during `opening`.
709
+ */
710
+ openIndex: number | null;
711
+ }
712
+ interface NavigationConfig {
713
+ /**
714
+ * Safety fallback (ms). If the component never reports its enter/exit
715
+ * animation finished, the manager force-advances the phase so the viewer
716
+ * can never get stuck in `opening`/`closing`. Set generously above the
717
+ * real animation duration. Default: 1200.
718
+ */
719
+ phaseTimeoutMs?: number;
720
+ }
721
+ declare const DEFAULT_NAVIGATION_CONFIG: Required<NavigationConfig>;
722
+ declare function isValidNavTransition(from: NavigationPhase, to: NavigationPhase): boolean;
723
+ declare class NavigationManager {
724
+ readonly store: StoreApi<NavigationState>;
725
+ private readonly config;
726
+ private readonly logger?;
727
+ /** Fallback timer guarding against a missing animationend report. */
728
+ private phaseTimer;
729
+ private _destroyed;
730
+ constructor(config?: NavigationConfig, logger?: ILogger);
731
+ /**
732
+ * Open the reels viewer at `index`. Safe to call from a thumbnail click
733
+ * handler — it transitions into `opening`, at which point ReelsModal mounts
734
+ * <ReelsFeed> hidden and begins prewarming.
735
+ *
736
+ * Calling open() while already open/opening just retargets the index
737
+ * (e.g. user taps a different thumbnail before the animation settles).
738
+ */
739
+ open(index: number): void;
740
+ /**
741
+ * Begin closing the viewer. ReelsModal animates out then calls
742
+ * `setPhase('closed')`. Idempotent if already closing/closed.
743
+ */
744
+ close(): void;
745
+ /**
746
+ * Report an animation-driven phase change from the component layer.
747
+ * Invalid transitions are dropped. When reaching `closed`, openIndex is
748
+ * cleared so <ReelsFeed> unmounts.
749
+ */
750
+ setPhase(next: NavigationPhase): void;
751
+ getPhase(): NavigationPhase;
752
+ getOpenIndex(): number | null;
753
+ isOpen(): boolean;
754
+ /** Whether <ReelsFeed> should be mounted (any non-closed phase). */
755
+ shouldMount(): boolean;
756
+ destroy(): void;
757
+ /**
758
+ * Apply a guarded phase transition. Returns true if it was applied.
759
+ * Manages the fallback timer for transient phases (opening/closing).
760
+ */
761
+ private transition;
762
+ private clearPhaseTimer;
763
+ }
764
+
661
765
  /**
662
766
  * Player State Machine — Guarded transitions
663
767
  *
@@ -776,15 +880,20 @@ interface SDKContextValue {
776
880
  playerEngine: PlayerEngine;
777
881
  resourceGovernor: ResourceGovernor;
778
882
  optimisticManager: OptimisticManager;
883
+ navigationManager: NavigationManager;
779
884
  adapters: Required<Omit<SDKAdapters, 'dataSource'>> & Pick<SDKAdapters, 'dataSource'>;
780
885
  }
781
886
  interface ReelsProviderProps {
782
887
  children: ReactNode;
783
888
  adapters: SDKAdapters;
889
+ /** Seed initial items into feedManager */
890
+ initialItems?: ContentItem[];
784
891
  /** Enable verbose logging (default: false) */
785
892
  debug?: boolean;
893
+ /** Config for the owned reels viewer open/close lifecycle (used by ReelsModal) */
894
+ navigationConfig?: NavigationConfig;
786
895
  }
787
- declare function ReelsProvider({ children, adapters, debug }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
896
+ declare function ReelsProvider({ children, adapters, initialItems, debug, navigationConfig, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
788
897
  declare function useSDK(): SDKContextValue;
789
898
 
790
899
  declare function ReelsFeed({ renderOverlay, renderActions, renderPauseAction, renderLoading, renderEmpty, renderError: _renderError, showFps, loadMoreThreshold, onSlotChange, gestureConfig, snapConfig, initialMuted, onAutoplayBlocked, }: ReelsFeedProps): string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null | undefined;
@@ -809,12 +918,97 @@ interface ReelsFeedThumbnailProps {
809
918
  wrap?: boolean;
810
919
  /** Enable click → update focused index on ReelsProvider before host reacts. Default: true. */
811
920
  setFocusOnClick?: boolean;
812
- /** Prefetch metadata for this slot on pointer enter. Off by default. */
921
+ /**
922
+ * Open the SDK-owned <ReelsModal> on click (calls NavigationManager.open).
923
+ * This is the recommended path — the SDK controls modal timing and prewarms
924
+ * the video during the open animation, eliminating the "poster stuck ~3s"
925
+ * stall. Requires a <ReelsModal> mounted under the same <ReelsProvider>.
926
+ *
927
+ * Leave false (default) to keep the headless behaviour where the host owns
928
+ * the modal and reacts via `onThumbnailClick`.
929
+ *
930
+ * @default false
931
+ */
932
+ openOnClick?: boolean;
933
+ /**
934
+ * Prefetch HLS metadata for this slot on pointer enter (desktop hover).
935
+ * Useless on mobile WebView (no hover) — use `prewarmOnClick` there.
936
+ * Off by default.
937
+ */
813
938
  prefetchOnHover?: boolean;
939
+ /**
940
+ * Prefetch HLS metadata for the tapped slot synchronously on click, inside
941
+ * the tap gesture. This is the mobile-friendly counterpart to
942
+ * `prefetchOnHover` and starts buffering the instant the user taps — before
943
+ * any modal mounts. Recommended for mobile WebView. Default: true.
944
+ */
945
+ prewarmOnClick?: boolean;
814
946
  /** Key override — useful when host renders multiple lists from the same feed. */
815
947
  getKey?: (item: ContentItem, index: number) => string;
816
948
  }
817
- declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
949
+ declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, openOnClick, prefetchOnHover, prewarmOnClick, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
950
+
951
+ interface ReelsModalAnimationConfig {
952
+ /** Enter/exit duration in ms (default: 300) */
953
+ duration?: number;
954
+ /** CSS easing (default: 'cubic-bezier(0.22, 1, 0.36, 1)') */
955
+ easing?: string;
956
+ /**
957
+ * Panel travel direction:
958
+ * - 'up' (default): slides up from the bottom edge (bottom-sheet drawer)
959
+ * - 'down': slides down from the top edge
960
+ * - 'fade': no translate, opacity only
961
+ */
962
+ direction?: 'up' | 'down' | 'fade';
963
+ }
964
+ /** State handed to render-prop customisation callbacks. */
965
+ interface ReelsModalRenderState {
966
+ phase: NavigationPhase;
967
+ openIndex: number | null;
968
+ /** Begin closing the viewer (runs the exit animation). */
969
+ close: () => void;
970
+ }
971
+ interface ReelsModalProps {
972
+ /** Props forwarded to the inner <ReelsFeed> (render callbacks, initialMuted, etc.). */
973
+ feedProps?: ReelsFeedProps;
974
+ /** Enter/exit animation overrides. Defaults applied when omitted. */
975
+ animationConfig?: ReelsModalAnimationConfig;
976
+ /**
977
+ * Custom backdrop visual. Rendered inside the SDK-owned backdrop layer,
978
+ * whose opacity the SDK animates. If omitted, a semi-transparent black
979
+ * backdrop is used. Return null to suppress the default visual.
980
+ */
981
+ renderBackdrop?: (state: ReelsModalRenderState) => ReactNode;
982
+ /**
983
+ * Custom close affordance, rendered above <ReelsFeed> inside the panel.
984
+ * If omitted, a top-right ✕ button is shown. Return null to hide it
985
+ * (e.g. when the host relies on swipe-to-dismiss or its own chrome).
986
+ */
987
+ renderCloseButton?: (state: ReelsModalRenderState) => ReactNode;
988
+ /** Fired when the enter animation completes (viewer fully open). */
989
+ onOpen?: (index: number | null) => void;
990
+ /** Fired when the exit animation completes (viewer fully closed). */
991
+ onClose?: () => void;
992
+ /** Close when the backdrop is clicked. Default: true. */
993
+ closeOnBackdropClick?: boolean;
994
+ /** Close when Escape is pressed. Default: true. */
995
+ closeOnEscape?: boolean;
996
+ /** Lock body scroll while open. Default: true. */
997
+ lockBodyScroll?: boolean;
998
+ /**
999
+ * How many slots to prewarm on open. `prewarmForward` items ahead of the
1000
+ * focused index plus 1 behind get an immediate preloadMetadata() call so
1001
+ * buffering starts in the same tick as the tap. Default: 2.
1002
+ */
1003
+ prewarmForward?: number;
1004
+ /** className on the sliding panel element. */
1005
+ className?: string;
1006
+ /** z-index for the modal root. Default: 1000. */
1007
+ zIndex?: number;
1008
+ /** Portal target. Default: document.body. */
1009
+ portalTarget?: HTMLElement | null;
1010
+ }
1011
+ declare function ReelsModal({ feedProps, animationConfig, renderBackdrop, renderCloseButton, onOpen, onClose, closeOnBackdropClick, closeOnEscape, lockBodyScroll, prewarmForward, className, zIndex, portalTarget, }: ReelsModalProps): react.ReactPortal | null;
818
1012
 
819
1013
  /**
820
1014
  * useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
@@ -1001,6 +1195,30 @@ declare function useResource(): {
1001
1195
  getTier: (index: number) => 3 | 1 | 2 | 4 | null;
1002
1196
  };
1003
1197
 
1198
+ /**
1199
+ * useNavigation — React hook for NavigationManager
1200
+ *
1201
+ * Subscribes to the reels viewer open/close lifecycle via useSyncExternalStore.
1202
+ * Mirrors the useResource / useFeed selector pattern — no Zustand React bindings.
1203
+ *
1204
+ * Primary consumers:
1205
+ * - ReelsModal: reads `phase` / `openIndex` to mount + animate the viewer,
1206
+ * and calls `setPhase` when its enter/exit animation finishes.
1207
+ * - ReelsFeedThumbnail: calls `open(index)` on click when `openOnClick` is set.
1208
+ * - Host app: can read `isOpen` / call `close()` to drive its own chrome.
1209
+ */
1210
+
1211
+ declare function useNavigationSelector<T>(selector: (state: NavigationState) => T): T;
1212
+ declare function useNavigation(): {
1213
+ phase: NavigationPhase;
1214
+ openIndex: number | null;
1215
+ isOpen: boolean;
1216
+ shouldMount: boolean;
1217
+ open: (index: number) => void;
1218
+ close: () => void;
1219
+ setPhase: (next: NavigationPhase) => void;
1220
+ };
1221
+
1004
1222
  /**
1005
1223
  * Mock adapters for development and testing
1006
1224
  */
@@ -1201,4 +1419,4 @@ declare class HttpError extends Error {
1201
1419
  constructor(status: number, message: string, body?: string | undefined);
1202
1420
  }
1203
1421
 
1204
- export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NetworkType, OptimisticManager, type PauseSlotActions, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsFeedThumbnail, type ReelsFeedThumbnailProps, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
1422
+ export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_NAVIGATION_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NavigationConfig, NavigationManager, type NavigationPhase, type NavigationState, type NetworkType, OptimisticManager, type PauseSlotActions, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsFeedThumbnail, type ReelsFeedThumbnailProps, ReelsModal, type ReelsModalAnimationConfig, type ReelsModalProps, type ReelsModalRenderState, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidNavTransition, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, useNavigation, useNavigationSelector, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
package/dist/index.d.ts CHANGED
@@ -443,6 +443,7 @@ declare class PlayerEngine {
443
443
  private circuitResetTimer;
444
444
  private watchTimeInterval;
445
445
  private lastStatus;
446
+ private _destroyed;
446
447
  constructor(config?: PlayerConfig, analytics?: IAnalytics, logger?: ILogger);
447
448
  /**
448
449
  * Load a video and prepare for playback.
@@ -498,11 +499,14 @@ declare class FeedManager {
498
499
  private inFlightRequests;
499
500
  /** LRU tracking: itemId → lastAccessTime */
500
501
  private accessOrder;
502
+ /** Idempotency guard for destroy() */
503
+ private _destroyed;
501
504
  /** Prefetch cache — instance-scoped (not static) */
502
505
  private prefetchCache;
503
506
  constructor(dataSource: IDataSource, config?: FeedConfig, logger?: ILogger);
504
507
  getDataSource(): IDataSource;
505
508
  setDataSource(dataSource: IDataSource, reset?: boolean): void;
509
+ setInitialItems(items: ContentItem[]): void;
506
510
  prefetch(ttlMs?: number): Promise<void>;
507
511
  hasPrefetchCache(): boolean;
508
512
  clearPrefetchCache(): void;
@@ -556,6 +560,8 @@ declare class OptimisticManager {
556
560
  private likeDebounceTimers;
557
561
  /** Pending like direction: contentId → final intended state */
558
562
  private pendingLikeState;
563
+ /** Idempotency guard for destroy() */
564
+ private _destroyed;
559
565
  constructor(interaction: Partial<IInteraction>, logger?: ILogger);
560
566
  /**
561
567
  * Debounced like toggle — prevents rapid API spam on double-tap.
@@ -626,6 +632,7 @@ declare class ResourceGovernor {
626
632
  private readonly logger?;
627
633
  private focusDebounceTimer;
628
634
  private networkUnsubscribe?;
635
+ private _destroyed;
629
636
  constructor(config?: ResourceConfig, videoLoader?: IVideoLoader, network?: INetworkAdapter, logger?: ILogger);
630
637
  activate(): Promise<void>;
631
638
  deactivate(): void;
@@ -658,6 +665,103 @@ declare class ResourceGovernor {
658
665
  private recalculate;
659
666
  }
660
667
 
668
+ /**
669
+ * NavigationManager — Owns the open/close lifecycle of the SDK reels viewer.
670
+ *
671
+ * Framework-agnostic class (zustand/vanilla) mirroring FeedManager /
672
+ * ResourceGovernor / PlayerEngine conventions. It is the single source of
673
+ * truth for "is the reels viewer open, and at which index".
674
+ *
675
+ * WHY THIS EXISTS:
676
+ * Previously the host app owned the modal entirely (via onThumbnailClick).
677
+ * That meant the SDK could not warm a video before the host mounted
678
+ * <ReelsFeed>, producing the "poster stuck ~3s" stall. By owning the
679
+ * open/close phase, the SDK can:
680
+ * 1. mount <ReelsFeed> early (hidden behind the animating container),
681
+ * 2. prewarm the focused video DURING the open animation,
682
+ * 3. reveal an already-decoded first frame → no poster stall.
683
+ *
684
+ * PHASE MODEL (state machine):
685
+ * closed → opening : open(index) called (e.g. from a thumbnail tap)
686
+ * opening → open : ReelsModal finished its enter animation
687
+ * open → closing : close() called
688
+ * closing → closed : ReelsModal finished its exit animation
689
+ *
690
+ * Invalid transitions are dropped (see isValidNavTransition). This mirrors
691
+ * the PlayerEngine state-machine discipline used elsewhere in the SDK.
692
+ *
693
+ * The component layer (ReelsModal) drives the animation timing and calls
694
+ * `setPhase('open')` / `setPhase('closed')` when its Web Animations API
695
+ * transitions finish. NavigationManager itself holds no timers tied to
696
+ * animation — it only holds a safety fallback timer so a missing/janky
697
+ * animationend can never strand the phase forever.
698
+ */
699
+
700
+ type NavigationPhase = 'closed' | 'opening' | 'open' | 'closing';
701
+ interface NavigationState {
702
+ /** Current lifecycle phase of the reels viewer. */
703
+ phase: NavigationPhase;
704
+ /**
705
+ * Index the viewer was opened at. Held through the whole open lifecycle
706
+ * (opening → open → closing) and cleared back to null only once fully
707
+ * closed. ReelsModal mounts <ReelsFeed> whenever this is non-null so the
708
+ * feed can warm during `opening`.
709
+ */
710
+ openIndex: number | null;
711
+ }
712
+ interface NavigationConfig {
713
+ /**
714
+ * Safety fallback (ms). If the component never reports its enter/exit
715
+ * animation finished, the manager force-advances the phase so the viewer
716
+ * can never get stuck in `opening`/`closing`. Set generously above the
717
+ * real animation duration. Default: 1200.
718
+ */
719
+ phaseTimeoutMs?: number;
720
+ }
721
+ declare const DEFAULT_NAVIGATION_CONFIG: Required<NavigationConfig>;
722
+ declare function isValidNavTransition(from: NavigationPhase, to: NavigationPhase): boolean;
723
+ declare class NavigationManager {
724
+ readonly store: StoreApi<NavigationState>;
725
+ private readonly config;
726
+ private readonly logger?;
727
+ /** Fallback timer guarding against a missing animationend report. */
728
+ private phaseTimer;
729
+ private _destroyed;
730
+ constructor(config?: NavigationConfig, logger?: ILogger);
731
+ /**
732
+ * Open the reels viewer at `index`. Safe to call from a thumbnail click
733
+ * handler — it transitions into `opening`, at which point ReelsModal mounts
734
+ * <ReelsFeed> hidden and begins prewarming.
735
+ *
736
+ * Calling open() while already open/opening just retargets the index
737
+ * (e.g. user taps a different thumbnail before the animation settles).
738
+ */
739
+ open(index: number): void;
740
+ /**
741
+ * Begin closing the viewer. ReelsModal animates out then calls
742
+ * `setPhase('closed')`. Idempotent if already closing/closed.
743
+ */
744
+ close(): void;
745
+ /**
746
+ * Report an animation-driven phase change from the component layer.
747
+ * Invalid transitions are dropped. When reaching `closed`, openIndex is
748
+ * cleared so <ReelsFeed> unmounts.
749
+ */
750
+ setPhase(next: NavigationPhase): void;
751
+ getPhase(): NavigationPhase;
752
+ getOpenIndex(): number | null;
753
+ isOpen(): boolean;
754
+ /** Whether <ReelsFeed> should be mounted (any non-closed phase). */
755
+ shouldMount(): boolean;
756
+ destroy(): void;
757
+ /**
758
+ * Apply a guarded phase transition. Returns true if it was applied.
759
+ * Manages the fallback timer for transient phases (opening/closing).
760
+ */
761
+ private transition;
762
+ private clearPhaseTimer;
763
+ }
764
+
661
765
  /**
662
766
  * Player State Machine — Guarded transitions
663
767
  *
@@ -776,15 +880,20 @@ interface SDKContextValue {
776
880
  playerEngine: PlayerEngine;
777
881
  resourceGovernor: ResourceGovernor;
778
882
  optimisticManager: OptimisticManager;
883
+ navigationManager: NavigationManager;
779
884
  adapters: Required<Omit<SDKAdapters, 'dataSource'>> & Pick<SDKAdapters, 'dataSource'>;
780
885
  }
781
886
  interface ReelsProviderProps {
782
887
  children: ReactNode;
783
888
  adapters: SDKAdapters;
889
+ /** Seed initial items into feedManager */
890
+ initialItems?: ContentItem[];
784
891
  /** Enable verbose logging (default: false) */
785
892
  debug?: boolean;
893
+ /** Config for the owned reels viewer open/close lifecycle (used by ReelsModal) */
894
+ navigationConfig?: NavigationConfig;
786
895
  }
787
- declare function ReelsProvider({ children, adapters, debug }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
896
+ declare function ReelsProvider({ children, adapters, initialItems, debug, navigationConfig, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
788
897
  declare function useSDK(): SDKContextValue;
789
898
 
790
899
  declare function ReelsFeed({ renderOverlay, renderActions, renderPauseAction, renderLoading, renderEmpty, renderError: _renderError, showFps, loadMoreThreshold, onSlotChange, gestureConfig, snapConfig, initialMuted, onAutoplayBlocked, }: ReelsFeedProps): string | number | bigint | boolean | Iterable<react.ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<react.ReactNode> | null | undefined> | react_jsx_runtime.JSX.Element | null | undefined;
@@ -809,12 +918,97 @@ interface ReelsFeedThumbnailProps {
809
918
  wrap?: boolean;
810
919
  /** Enable click → update focused index on ReelsProvider before host reacts. Default: true. */
811
920
  setFocusOnClick?: boolean;
812
- /** Prefetch metadata for this slot on pointer enter. Off by default. */
921
+ /**
922
+ * Open the SDK-owned <ReelsModal> on click (calls NavigationManager.open).
923
+ * This is the recommended path — the SDK controls modal timing and prewarms
924
+ * the video during the open animation, eliminating the "poster stuck ~3s"
925
+ * stall. Requires a <ReelsModal> mounted under the same <ReelsProvider>.
926
+ *
927
+ * Leave false (default) to keep the headless behaviour where the host owns
928
+ * the modal and reacts via `onThumbnailClick`.
929
+ *
930
+ * @default false
931
+ */
932
+ openOnClick?: boolean;
933
+ /**
934
+ * Prefetch HLS metadata for this slot on pointer enter (desktop hover).
935
+ * Useless on mobile WebView (no hover) — use `prewarmOnClick` there.
936
+ * Off by default.
937
+ */
813
938
  prefetchOnHover?: boolean;
939
+ /**
940
+ * Prefetch HLS metadata for the tapped slot synchronously on click, inside
941
+ * the tap gesture. This is the mobile-friendly counterpart to
942
+ * `prefetchOnHover` and starts buffering the instant the user taps — before
943
+ * any modal mounts. Recommended for mobile WebView. Default: true.
944
+ */
945
+ prewarmOnClick?: boolean;
814
946
  /** Key override — useful when host renders multiple lists from the same feed. */
815
947
  getKey?: (item: ContentItem, index: number) => string;
816
948
  }
817
- declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
949
+ declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, openOnClick, prefetchOnHover, prewarmOnClick, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
950
+
951
+ interface ReelsModalAnimationConfig {
952
+ /** Enter/exit duration in ms (default: 300) */
953
+ duration?: number;
954
+ /** CSS easing (default: 'cubic-bezier(0.22, 1, 0.36, 1)') */
955
+ easing?: string;
956
+ /**
957
+ * Panel travel direction:
958
+ * - 'up' (default): slides up from the bottom edge (bottom-sheet drawer)
959
+ * - 'down': slides down from the top edge
960
+ * - 'fade': no translate, opacity only
961
+ */
962
+ direction?: 'up' | 'down' | 'fade';
963
+ }
964
+ /** State handed to render-prop customisation callbacks. */
965
+ interface ReelsModalRenderState {
966
+ phase: NavigationPhase;
967
+ openIndex: number | null;
968
+ /** Begin closing the viewer (runs the exit animation). */
969
+ close: () => void;
970
+ }
971
+ interface ReelsModalProps {
972
+ /** Props forwarded to the inner <ReelsFeed> (render callbacks, initialMuted, etc.). */
973
+ feedProps?: ReelsFeedProps;
974
+ /** Enter/exit animation overrides. Defaults applied when omitted. */
975
+ animationConfig?: ReelsModalAnimationConfig;
976
+ /**
977
+ * Custom backdrop visual. Rendered inside the SDK-owned backdrop layer,
978
+ * whose opacity the SDK animates. If omitted, a semi-transparent black
979
+ * backdrop is used. Return null to suppress the default visual.
980
+ */
981
+ renderBackdrop?: (state: ReelsModalRenderState) => ReactNode;
982
+ /**
983
+ * Custom close affordance, rendered above <ReelsFeed> inside the panel.
984
+ * If omitted, a top-right ✕ button is shown. Return null to hide it
985
+ * (e.g. when the host relies on swipe-to-dismiss or its own chrome).
986
+ */
987
+ renderCloseButton?: (state: ReelsModalRenderState) => ReactNode;
988
+ /** Fired when the enter animation completes (viewer fully open). */
989
+ onOpen?: (index: number | null) => void;
990
+ /** Fired when the exit animation completes (viewer fully closed). */
991
+ onClose?: () => void;
992
+ /** Close when the backdrop is clicked. Default: true. */
993
+ closeOnBackdropClick?: boolean;
994
+ /** Close when Escape is pressed. Default: true. */
995
+ closeOnEscape?: boolean;
996
+ /** Lock body scroll while open. Default: true. */
997
+ lockBodyScroll?: boolean;
998
+ /**
999
+ * How many slots to prewarm on open. `prewarmForward` items ahead of the
1000
+ * focused index plus 1 behind get an immediate preloadMetadata() call so
1001
+ * buffering starts in the same tick as the tap. Default: 2.
1002
+ */
1003
+ prewarmForward?: number;
1004
+ /** className on the sliding panel element. */
1005
+ className?: string;
1006
+ /** z-index for the modal root. Default: 1000. */
1007
+ zIndex?: number;
1008
+ /** Portal target. Default: document.body. */
1009
+ portalTarget?: HTMLElement | null;
1010
+ }
1011
+ declare function ReelsModal({ feedProps, animationConfig, renderBackdrop, renderCloseButton, onOpen, onClose, closeOnBackdropClick, closeOnEscape, lockBodyScroll, prewarmForward, className, zIndex, portalTarget, }: ReelsModalProps): react.ReactPortal | null;
818
1012
 
819
1013
  /**
820
1014
  * useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
@@ -1001,6 +1195,30 @@ declare function useResource(): {
1001
1195
  getTier: (index: number) => 3 | 1 | 2 | 4 | null;
1002
1196
  };
1003
1197
 
1198
+ /**
1199
+ * useNavigation — React hook for NavigationManager
1200
+ *
1201
+ * Subscribes to the reels viewer open/close lifecycle via useSyncExternalStore.
1202
+ * Mirrors the useResource / useFeed selector pattern — no Zustand React bindings.
1203
+ *
1204
+ * Primary consumers:
1205
+ * - ReelsModal: reads `phase` / `openIndex` to mount + animate the viewer,
1206
+ * and calls `setPhase` when its enter/exit animation finishes.
1207
+ * - ReelsFeedThumbnail: calls `open(index)` on click when `openOnClick` is set.
1208
+ * - Host app: can read `isOpen` / call `close()` to drive its own chrome.
1209
+ */
1210
+
1211
+ declare function useNavigationSelector<T>(selector: (state: NavigationState) => T): T;
1212
+ declare function useNavigation(): {
1213
+ phase: NavigationPhase;
1214
+ openIndex: number | null;
1215
+ isOpen: boolean;
1216
+ shouldMount: boolean;
1217
+ open: (index: number) => void;
1218
+ close: () => void;
1219
+ setPhase: (next: NavigationPhase) => void;
1220
+ };
1221
+
1004
1222
  /**
1005
1223
  * Mock adapters for development and testing
1006
1224
  */
@@ -1201,4 +1419,4 @@ declare class HttpError extends Error {
1201
1419
  constructor(status: number, message: string, body?: string | undefined);
1202
1420
  }
1203
1421
 
1204
- export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NetworkType, OptimisticManager, type PauseSlotActions, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsFeedThumbnail, type ReelsFeedThumbnailProps, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
1422
+ export { type Article, type ArticleImage, type Author, type BufferTier, type CommentItem, type CommentPage, type ContentItem, type ContentStats, DEFAULT_FEED_CONFIG, DEFAULT_NAVIGATION_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, type FeedConfig, type FeedError, FeedManager, type FeedPage, type FeedState, HttpDataSource, type HttpDataSourceConfig, HttpError, type IAnalytics, type ICommentAdapter, type IDataSource, type IInteraction, type ILogger, type INetworkAdapter, type ISessionStorage, type IVideoLoader, type InteractionState, type LogLevel, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, type NavigationConfig, NavigationManager, type NavigationPhase, type NavigationState, type NetworkType, OptimisticManager, type PauseSlotActions, type PlayerConfig, PlayerEngine, type PlayerError, type PlayerEvent, type PlayerEventListener, type PlayerState, PlayerStatus, type PointerGestureConfig, type PreloadResult, type PreloadStatus, ReelsFeed, type ReelsFeedProps, ReelsFeedThumbnail, type ReelsFeedThumbnailProps, ReelsModal, type ReelsModalAnimationConfig, type ReelsModalProps, type ReelsModalRenderState, ReelsProvider, type ReelsProviderProps, type ResourceConfig, ResourceGovernor, type ResourceState, type SDKAdapters, type SDKContextValue, type SlotActions, type SnapTarget, type UseHlsOptions, type UseHlsReturn, VALID_TRANSITIONS, type VideoItem, type VideoQuality, VideoSlot, type VideoSource, canPause, canPlay, canSeek, isArticle, isValidNavTransition, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, useNavigation, useNavigationSelector, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };