@xhub-reels/sdk 0.2.12 → 0.2.14

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,6 +499,8 @@ 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);
@@ -557,6 +560,8 @@ declare class OptimisticManager {
557
560
  private likeDebounceTimers;
558
561
  /** Pending like direction: contentId → final intended state */
559
562
  private pendingLikeState;
563
+ /** Idempotency guard for destroy() */
564
+ private _destroyed;
560
565
  constructor(interaction: Partial<IInteraction>, logger?: ILogger);
561
566
  /**
562
567
  * Debounced like toggle — prevents rapid API spam on double-tap.
@@ -627,6 +632,7 @@ declare class ResourceGovernor {
627
632
  private readonly logger?;
628
633
  private focusDebounceTimer;
629
634
  private networkUnsubscribe?;
635
+ private _destroyed;
630
636
  constructor(config?: ResourceConfig, videoLoader?: IVideoLoader, network?: INetworkAdapter, logger?: ILogger);
631
637
  activate(): Promise<void>;
632
638
  deactivate(): void;
@@ -659,6 +665,103 @@ declare class ResourceGovernor {
659
665
  private recalculate;
660
666
  }
661
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
+
662
765
  /**
663
766
  * Player State Machine — Guarded transitions
664
767
  *
@@ -777,6 +880,7 @@ interface SDKContextValue {
777
880
  playerEngine: PlayerEngine;
778
881
  resourceGovernor: ResourceGovernor;
779
882
  optimisticManager: OptimisticManager;
883
+ navigationManager: NavigationManager;
780
884
  adapters: Required<Omit<SDKAdapters, 'dataSource'>> & Pick<SDKAdapters, 'dataSource'>;
781
885
  }
782
886
  interface ReelsProviderProps {
@@ -786,8 +890,10 @@ interface ReelsProviderProps {
786
890
  initialItems?: ContentItem[];
787
891
  /** Enable verbose logging (default: false) */
788
892
  debug?: boolean;
893
+ /** Config for the owned reels viewer open/close lifecycle (used by ReelsModal) */
894
+ navigationConfig?: NavigationConfig;
789
895
  }
790
- declare function ReelsProvider({ children, adapters, initialItems, debug, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
896
+ declare function ReelsProvider({ children, adapters, initialItems, debug, navigationConfig, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
791
897
  declare function useSDK(): SDKContextValue;
792
898
 
793
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;
@@ -812,12 +918,97 @@ interface ReelsFeedThumbnailProps {
812
918
  wrap?: boolean;
813
919
  /** Enable click → update focused index on ReelsProvider before host reacts. Default: true. */
814
920
  setFocusOnClick?: boolean;
815
- /** 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
+ */
816
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;
817
946
  /** Key override — useful when host renders multiple lists from the same feed. */
818
947
  getKey?: (item: ContentItem, index: number) => string;
819
948
  }
820
- 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;
821
1012
 
822
1013
  /**
823
1014
  * useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
@@ -1004,6 +1195,30 @@ declare function useResource(): {
1004
1195
  getTier: (index: number) => 3 | 1 | 2 | 4 | null;
1005
1196
  };
1006
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
+
1007
1222
  /**
1008
1223
  * Mock adapters for development and testing
1009
1224
  */
@@ -1204,4 +1419,4 @@ declare class HttpError extends Error {
1204
1419
  constructor(status: number, message: string, body?: string | undefined);
1205
1420
  }
1206
1421
 
1207
- 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,6 +499,8 @@ 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);
@@ -557,6 +560,8 @@ declare class OptimisticManager {
557
560
  private likeDebounceTimers;
558
561
  /** Pending like direction: contentId → final intended state */
559
562
  private pendingLikeState;
563
+ /** Idempotency guard for destroy() */
564
+ private _destroyed;
560
565
  constructor(interaction: Partial<IInteraction>, logger?: ILogger);
561
566
  /**
562
567
  * Debounced like toggle — prevents rapid API spam on double-tap.
@@ -627,6 +632,7 @@ declare class ResourceGovernor {
627
632
  private readonly logger?;
628
633
  private focusDebounceTimer;
629
634
  private networkUnsubscribe?;
635
+ private _destroyed;
630
636
  constructor(config?: ResourceConfig, videoLoader?: IVideoLoader, network?: INetworkAdapter, logger?: ILogger);
631
637
  activate(): Promise<void>;
632
638
  deactivate(): void;
@@ -659,6 +665,103 @@ declare class ResourceGovernor {
659
665
  private recalculate;
660
666
  }
661
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
+
662
765
  /**
663
766
  * Player State Machine — Guarded transitions
664
767
  *
@@ -777,6 +880,7 @@ interface SDKContextValue {
777
880
  playerEngine: PlayerEngine;
778
881
  resourceGovernor: ResourceGovernor;
779
882
  optimisticManager: OptimisticManager;
883
+ navigationManager: NavigationManager;
780
884
  adapters: Required<Omit<SDKAdapters, 'dataSource'>> & Pick<SDKAdapters, 'dataSource'>;
781
885
  }
782
886
  interface ReelsProviderProps {
@@ -786,8 +890,10 @@ interface ReelsProviderProps {
786
890
  initialItems?: ContentItem[];
787
891
  /** Enable verbose logging (default: false) */
788
892
  debug?: boolean;
893
+ /** Config for the owned reels viewer open/close lifecycle (used by ReelsModal) */
894
+ navigationConfig?: NavigationConfig;
789
895
  }
790
- declare function ReelsProvider({ children, adapters, initialItems, debug, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
896
+ declare function ReelsProvider({ children, adapters, initialItems, debug, navigationConfig, }: ReelsProviderProps): react_jsx_runtime.JSX.Element;
791
897
  declare function useSDK(): SDKContextValue;
792
898
 
793
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;
@@ -812,12 +918,97 @@ interface ReelsFeedThumbnailProps {
812
918
  wrap?: boolean;
813
919
  /** Enable click → update focused index on ReelsProvider before host reacts. Default: true. */
814
920
  setFocusOnClick?: boolean;
815
- /** 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
+ */
816
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;
817
946
  /** Key override — useful when host renders multiple lists from the same feed. */
818
947
  getKey?: (item: ContentItem, index: number) => string;
819
948
  }
820
- 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;
821
1012
 
822
1013
  /**
823
1014
  * useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
@@ -1004,6 +1195,30 @@ declare function useResource(): {
1004
1195
  getTier: (index: number) => 3 | 1 | 2 | 4 | null;
1005
1196
  };
1006
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
+
1007
1222
  /**
1008
1223
  * Mock adapters for development and testing
1009
1224
  */
@@ -1204,4 +1419,4 @@ declare class HttpError extends Error {
1204
1419
  constructor(status: number, message: string, body?: string | undefined);
1205
1420
  }
1206
1421
 
1207
- 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 };