@xhub-reels/sdk 0.2.5 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -86,6 +86,72 @@ function MyFeed() {
86
86
  }
87
87
  ```
88
88
 
89
+ ## Thumbnail grid
90
+
91
+ `<ReelsFeedThumbnail>` is a headless grid component for the pre-drawer browsing UX
92
+ (e.g. a community feed that opens the player drawer on tap). It must be mounted
93
+ inside `<ReelsProvider>` and reads items from the same shared feed. The host
94
+ provides card visuals via `renderThumbnail`.
95
+
96
+ ```tsx
97
+ import { ReelsProvider, ReelsFeedThumbnail } from 'xhub-reels-sdk';
98
+
99
+ function CommunityPage() {
100
+ const [drawerOpen, setDrawerOpen] = useState(false);
101
+
102
+ return (
103
+ <ReelsProvider adapters={{ dataSource, interaction }}>
104
+ <ReelsFeedThumbnail
105
+ renderThumbnail={(item) => (
106
+ <article className="aspect-[3/2] rounded-lg overflow-hidden">
107
+ <img src={item.poster} alt="" />
108
+ <p>@{item.author.name}</p>
109
+ </article>
110
+ )}
111
+ onThumbnailClick={(id) => {
112
+ setDrawerOpen(true);
113
+ window.history.replaceState(null, '', `#reel_uuid=${id}`);
114
+ }}
115
+ />
116
+ {drawerOpen && <ReelsDrawer onClose={() => setDrawerOpen(false)} />}
117
+ </ReelsProvider>
118
+ );
119
+ }
120
+ ```
121
+
122
+ By default, clicking a card calls `setFocusedIndexImmediate(index)` on the
123
+ `ResourceGovernor` so the player opens instantly without a scroll-to-index. To
124
+ disable this glue (e.g. if the host manages focus separately), pass
125
+ `setFocusOnClick={false}`.
126
+
127
+ ### Props
128
+
129
+ | Prop | Type | Default | Description |
130
+ |---|---|---|---|
131
+ | `renderThumbnail` | `(item, index) => ReactNode` | required | Card visual for a single item |
132
+ | `onThumbnailClick` | `(id, item, index) => void` | — | Click handler |
133
+ | `renderLoading` | `() => ReactNode` | — | Shown while loading with no items |
134
+ | `renderEmpty` | `() => ReactNode` | — | Shown when feed is empty |
135
+ | `renderError` | `({ message, retry }) => ReactNode` | — | Shown on error with no items |
136
+ | `className` | `string` | `'grid grid-cols-2 gap-3'` | Outer wrapper className |
137
+ | `wrap` | `boolean` | `true` | Set `false` to render without an outer `<div>` |
138
+ | `setFocusOnClick` | `boolean` | `true` | Pre-focus the slot before `onThumbnailClick` fires |
139
+ | `prefetchOnHover` | `boolean` | `false` | Opt-in HLS metadata prefetch on `pointerenter` |
140
+ | `getKey` | `(item, index) => string` | `item.id` | Key override for duplicate lists |
141
+
142
+ ### Hover prefetch
143
+
144
+ For hover-capable devices, opt into manifest prefetch so opening the player
145
+ feels instant:
146
+
147
+ ```tsx
148
+ <ReelsFeedThumbnail prefetchOnHover renderThumbnail={...} />
149
+ ```
150
+
151
+ The SDK uses `adapters.videoLoader?.preloadMetadata?.(url)` and dedupes per item
152
+ so a card only triggers one prefetch regardless of how many times it's hovered.
153
+ Off by default to avoid surprising bandwidth on touch devices.
154
+
89
155
  ## Gesture Engine
90
156
 
91
157
  ```tsx
package/dist/index.cjs CHANGED
@@ -593,16 +593,23 @@ var FeedManager = class {
593
593
  applyItems(incoming, _nextCursor, append) {
594
594
  const { itemsById, displayOrder } = this.store.getState();
595
595
  const nextById = new Map(itemsById);
596
- const existingIds = new Set(displayOrder);
597
- const newIds = [];
598
596
  for (const item of incoming) {
599
- if (!existingIds.has(item.id)) {
600
- newIds.push(item.id);
601
- }
602
597
  nextById.set(item.id, item);
603
598
  this.accessOrder.set(item.id, Date.now());
604
599
  }
605
- const nextOrder = append ? [...displayOrder, ...newIds] : newIds;
600
+ let nextOrder;
601
+ if (append) {
602
+ const existingIds = new Set(displayOrder);
603
+ const newIds = [];
604
+ for (const item of incoming) {
605
+ if (!existingIds.has(item.id)) {
606
+ newIds.push(item.id);
607
+ }
608
+ }
609
+ nextOrder = [...displayOrder, ...newIds];
610
+ } else {
611
+ nextOrder = incoming.map((item) => item.id);
612
+ }
606
613
  if (nextById.size > this.config.maxCacheSize) {
607
614
  this.evictLRU(nextById, nextOrder);
608
615
  }
@@ -2520,6 +2527,81 @@ function parsePxTranslateY(el) {
2520
2527
  if (!match || !match[1]) return 0;
2521
2528
  return Number.parseFloat(match[1]);
2522
2529
  }
2530
+ function ReelsFeedThumbnail({
2531
+ renderThumbnail,
2532
+ onThumbnailClick,
2533
+ renderLoading,
2534
+ renderEmpty,
2535
+ renderError,
2536
+ className = "grid grid-cols-2 gap-3",
2537
+ wrap = true,
2538
+ setFocusOnClick = true,
2539
+ prefetchOnHover = false,
2540
+ getKey
2541
+ }) {
2542
+ const { items, loading, error, refresh } = useFeed();
2543
+ const { setFocusedIndexImmediate } = useResource();
2544
+ const { adapters } = useSDK();
2545
+ const prefetchedRef = react.useRef(/* @__PURE__ */ new Set());
2546
+ const handleClick = react.useCallback(
2547
+ (id, item, index) => {
2548
+ if (setFocusOnClick) {
2549
+ setFocusedIndexImmediate(index);
2550
+ }
2551
+ onThumbnailClick?.(id, item, index);
2552
+ },
2553
+ [setFocusOnClick, setFocusedIndexImmediate, onThumbnailClick]
2554
+ );
2555
+ const handlePointerEnter = react.useCallback(
2556
+ (item) => {
2557
+ if (!prefetchOnHover) return;
2558
+ if (!isVideoItem(item)) return;
2559
+ if (item.source.type !== "hls") return;
2560
+ const url = item.source.url;
2561
+ if (prefetchedRef.current.has(url)) return;
2562
+ prefetchedRef.current.add(url);
2563
+ adapters.videoLoader?.preloadMetadata?.(url);
2564
+ },
2565
+ [prefetchOnHover, adapters.videoLoader]
2566
+ );
2567
+ if (loading && items.length === 0) {
2568
+ if (renderLoading) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderLoading() });
2569
+ return null;
2570
+ }
2571
+ if (error && items.length === 0) {
2572
+ if (renderError) {
2573
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderError({ message: error.message, retry: refresh }) });
2574
+ }
2575
+ return null;
2576
+ }
2577
+ if (!loading && items.length === 0) {
2578
+ if (renderEmpty) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderEmpty() });
2579
+ return null;
2580
+ }
2581
+ const content = items.map((item, index) => {
2582
+ const key = getKey ? getKey(item, index) : item.id;
2583
+ return /* @__PURE__ */ jsxRuntime.jsx(
2584
+ "button",
2585
+ {
2586
+ type: "button",
2587
+ onClick: () => handleClick(item.id, item, index),
2588
+ onPointerEnter: () => handlePointerEnter(item),
2589
+ style: {
2590
+ all: "unset",
2591
+ cursor: "pointer",
2592
+ display: "block",
2593
+ width: "100%"
2594
+ },
2595
+ children: renderThumbnail(item, index)
2596
+ },
2597
+ key
2598
+ );
2599
+ });
2600
+ if (!wrap) {
2601
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: content });
2602
+ }
2603
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children: content });
2604
+ }
2523
2605
  function usePlayerSelector(selector) {
2524
2606
  const { playerEngine } = useSDK();
2525
2607
  const selectorRef = react.useRef(selector);
@@ -3156,6 +3238,7 @@ exports.OptimisticManager = OptimisticManager;
3156
3238
  exports.PlayerEngine = PlayerEngine;
3157
3239
  exports.PlayerStatus = PlayerStatus;
3158
3240
  exports.ReelsFeed = ReelsFeed;
3241
+ exports.ReelsFeedThumbnail = ReelsFeedThumbnail;
3159
3242
  exports.ReelsProvider = ReelsProvider;
3160
3243
  exports.ResourceGovernor = ResourceGovernor;
3161
3244
  exports.VALID_TRANSITIONS = VALID_TRANSITIONS;
package/dist/index.d.cts CHANGED
@@ -789,6 +789,33 @@ declare function useSDK(): SDKContextValue;
789
789
 
790
790
  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;
791
791
 
792
+ interface ReelsFeedThumbnailProps {
793
+ /** Render function for a single item's visual card. */
794
+ renderThumbnail: (videoData: ContentItem, index: number) => ReactNode;
795
+ /** Click handler — receives the item id, full data, and index in the feed. */
796
+ onThumbnailClick?: (id: string, videoData: ContentItem, index: number) => void;
797
+ /** Rendered when the feed is loading and has zero items yet. */
798
+ renderLoading?: () => ReactNode;
799
+ /** Rendered when the feed has loaded but is empty. */
800
+ renderEmpty?: () => ReactNode;
801
+ /** Rendered when the feed fails with no items — receives a retry callback. */
802
+ renderError?: (error: {
803
+ message: string;
804
+ retry: () => void;
805
+ }) => ReactNode;
806
+ /** Outer wrapper className. Defaults to `grid grid-cols-2 gap-3`. */
807
+ className?: string;
808
+ /** If the host wants to render its own wrapper, pass `false`. Default: true. */
809
+ wrap?: boolean;
810
+ /** Enable click → update focused index on ReelsProvider before host reacts. Default: true. */
811
+ setFocusOnClick?: boolean;
812
+ /** Prefetch metadata for this slot on pointer enter. Off by default. */
813
+ prefetchOnHover?: boolean;
814
+ /** Key override — useful when host renders multiple lists from the same feed. */
815
+ getKey?: (item: ContentItem, index: number) => string;
816
+ }
817
+ declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
818
+
792
819
  /**
793
820
  * useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
794
821
  *
@@ -1174,4 +1201,4 @@ declare class HttpError extends Error {
1174
1201
  constructor(status: number, message: string, body?: string | undefined);
1175
1202
  }
1176
1203
 
1177
- 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, 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 };
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 };
package/dist/index.d.ts CHANGED
@@ -789,6 +789,33 @@ declare function useSDK(): SDKContextValue;
789
789
 
790
790
  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;
791
791
 
792
+ interface ReelsFeedThumbnailProps {
793
+ /** Render function for a single item's visual card. */
794
+ renderThumbnail: (videoData: ContentItem, index: number) => ReactNode;
795
+ /** Click handler — receives the item id, full data, and index in the feed. */
796
+ onThumbnailClick?: (id: string, videoData: ContentItem, index: number) => void;
797
+ /** Rendered when the feed is loading and has zero items yet. */
798
+ renderLoading?: () => ReactNode;
799
+ /** Rendered when the feed has loaded but is empty. */
800
+ renderEmpty?: () => ReactNode;
801
+ /** Rendered when the feed fails with no items — receives a retry callback. */
802
+ renderError?: (error: {
803
+ message: string;
804
+ retry: () => void;
805
+ }) => ReactNode;
806
+ /** Outer wrapper className. Defaults to `grid grid-cols-2 gap-3`. */
807
+ className?: string;
808
+ /** If the host wants to render its own wrapper, pass `false`. Default: true. */
809
+ wrap?: boolean;
810
+ /** Enable click → update focused index on ReelsProvider before host reacts. Default: true. */
811
+ setFocusOnClick?: boolean;
812
+ /** Prefetch metadata for this slot on pointer enter. Off by default. */
813
+ prefetchOnHover?: boolean;
814
+ /** Key override — useful when host renders multiple lists from the same feed. */
815
+ getKey?: (item: ContentItem, index: number) => string;
816
+ }
817
+ declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
818
+
792
819
  /**
793
820
  * useHls — React hook for hls.js lifecycle management (3-Tier buffer support)
794
821
  *
@@ -1174,4 +1201,4 @@ declare class HttpError extends Error {
1174
1201
  constructor(status: number, message: string, body?: string | undefined);
1175
1202
  }
1176
1203
 
1177
- 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, 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 };
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 };
package/dist/index.js CHANGED
@@ -587,16 +587,23 @@ var FeedManager = class {
587
587
  applyItems(incoming, _nextCursor, append) {
588
588
  const { itemsById, displayOrder } = this.store.getState();
589
589
  const nextById = new Map(itemsById);
590
- const existingIds = new Set(displayOrder);
591
- const newIds = [];
592
590
  for (const item of incoming) {
593
- if (!existingIds.has(item.id)) {
594
- newIds.push(item.id);
595
- }
596
591
  nextById.set(item.id, item);
597
592
  this.accessOrder.set(item.id, Date.now());
598
593
  }
599
- const nextOrder = append ? [...displayOrder, ...newIds] : newIds;
594
+ let nextOrder;
595
+ if (append) {
596
+ const existingIds = new Set(displayOrder);
597
+ const newIds = [];
598
+ for (const item of incoming) {
599
+ if (!existingIds.has(item.id)) {
600
+ newIds.push(item.id);
601
+ }
602
+ }
603
+ nextOrder = [...displayOrder, ...newIds];
604
+ } else {
605
+ nextOrder = incoming.map((item) => item.id);
606
+ }
600
607
  if (nextById.size > this.config.maxCacheSize) {
601
608
  this.evictLRU(nextById, nextOrder);
602
609
  }
@@ -2514,6 +2521,81 @@ function parsePxTranslateY(el) {
2514
2521
  if (!match || !match[1]) return 0;
2515
2522
  return Number.parseFloat(match[1]);
2516
2523
  }
2524
+ function ReelsFeedThumbnail({
2525
+ renderThumbnail,
2526
+ onThumbnailClick,
2527
+ renderLoading,
2528
+ renderEmpty,
2529
+ renderError,
2530
+ className = "grid grid-cols-2 gap-3",
2531
+ wrap = true,
2532
+ setFocusOnClick = true,
2533
+ prefetchOnHover = false,
2534
+ getKey
2535
+ }) {
2536
+ const { items, loading, error, refresh } = useFeed();
2537
+ const { setFocusedIndexImmediate } = useResource();
2538
+ const { adapters } = useSDK();
2539
+ const prefetchedRef = useRef(/* @__PURE__ */ new Set());
2540
+ const handleClick = useCallback(
2541
+ (id, item, index) => {
2542
+ if (setFocusOnClick) {
2543
+ setFocusedIndexImmediate(index);
2544
+ }
2545
+ onThumbnailClick?.(id, item, index);
2546
+ },
2547
+ [setFocusOnClick, setFocusedIndexImmediate, onThumbnailClick]
2548
+ );
2549
+ const handlePointerEnter = useCallback(
2550
+ (item) => {
2551
+ if (!prefetchOnHover) return;
2552
+ if (!isVideoItem(item)) return;
2553
+ if (item.source.type !== "hls") return;
2554
+ const url = item.source.url;
2555
+ if (prefetchedRef.current.has(url)) return;
2556
+ prefetchedRef.current.add(url);
2557
+ adapters.videoLoader?.preloadMetadata?.(url);
2558
+ },
2559
+ [prefetchOnHover, adapters.videoLoader]
2560
+ );
2561
+ if (loading && items.length === 0) {
2562
+ if (renderLoading) return /* @__PURE__ */ jsx(Fragment, { children: renderLoading() });
2563
+ return null;
2564
+ }
2565
+ if (error && items.length === 0) {
2566
+ if (renderError) {
2567
+ return /* @__PURE__ */ jsx(Fragment, { children: renderError({ message: error.message, retry: refresh }) });
2568
+ }
2569
+ return null;
2570
+ }
2571
+ if (!loading && items.length === 0) {
2572
+ if (renderEmpty) return /* @__PURE__ */ jsx(Fragment, { children: renderEmpty() });
2573
+ return null;
2574
+ }
2575
+ const content = items.map((item, index) => {
2576
+ const key = getKey ? getKey(item, index) : item.id;
2577
+ return /* @__PURE__ */ jsx(
2578
+ "button",
2579
+ {
2580
+ type: "button",
2581
+ onClick: () => handleClick(item.id, item, index),
2582
+ onPointerEnter: () => handlePointerEnter(item),
2583
+ style: {
2584
+ all: "unset",
2585
+ cursor: "pointer",
2586
+ display: "block",
2587
+ width: "100%"
2588
+ },
2589
+ children: renderThumbnail(item, index)
2590
+ },
2591
+ key
2592
+ );
2593
+ });
2594
+ if (!wrap) {
2595
+ return /* @__PURE__ */ jsx(Fragment, { children: content });
2596
+ }
2597
+ return /* @__PURE__ */ jsx("div", { className, children: content });
2598
+ }
2517
2599
  function usePlayerSelector(selector) {
2518
2600
  const { playerEngine } = useSDK();
2519
2601
  const selectorRef = useRef(selector);
@@ -3128,4 +3210,4 @@ var HttpError = class extends Error {
3128
3210
  }
3129
3211
  };
3130
3212
 
3131
- export { DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, FeedManager, HttpDataSource, HttpError, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, OptimisticManager, PlayerEngine, PlayerStatus, ReelsFeed, ReelsProvider, ResourceGovernor, VALID_TRANSITIONS, VideoSlot, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
3213
+ export { DEFAULT_FEED_CONFIG, DEFAULT_PLAYER_CONFIG, DEFAULT_RESOURCE_CONFIG, DefaultActions, DefaultOverlay, DefaultPauseAction, DefaultSkeleton, FeedManager, HttpDataSource, HttpError, MockAnalytics, MockCommentAdapter, MockDataSource, MockInteraction, MockLogger, MockNetworkAdapter, MockSessionStorage, MockVideoLoader, OptimisticManager, PlayerEngine, PlayerStatus, ReelsFeed, ReelsFeedThumbnail, ReelsProvider, ResourceGovernor, VALID_TRANSITIONS, VideoSlot, canPause, canPlay, canSeek, isArticle, isValidTransition, isVideoItem, useFeed, useFeedSelector, useHls, usePlayer, usePlayerSelector, usePointerGesture, useResource, useResourceSelector, useSDK, useSnapAnimation };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xhub-reels/sdk",
3
- "version": "0.2.5",
3
+ "version": "0.2.11",
4
4
  "description": "High-performance Short Video / Reels SDK for React — optimized for Flutter WebView",
5
5
  "license": "MIT",
6
6
  "type": "module",