@xhub-reels/sdk 0.1.21 → 0.2.1
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 +42 -0
- package/dist/index.cjs +95 -16
- package/dist/index.d.cts +36 -1
- package/dist/index.d.ts +36 -1
- package/dist/index.js +95 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,6 +111,48 @@ function SwipeableFeed() {
|
|
|
111
111
|
}
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
## Thumbnail grid
|
|
115
|
+
|
|
116
|
+
Headless pre-drawer browsing UX. Mount inside `<ReelsProvider>`. Host provides the
|
|
117
|
+
card visuals via `renderThumbnail`; the SDK ships no default card.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { ReelsFeedThumbnail } from 'xhub-reels-sdk';
|
|
121
|
+
|
|
122
|
+
<ReelsFeedThumbnail
|
|
123
|
+
renderThumbnail={(videoData) => (
|
|
124
|
+
<article className="aspect-[3/2] rounded-lg overflow-hidden">
|
|
125
|
+
<img src={videoData.poster} alt="" />
|
|
126
|
+
<p>@{videoData.author.name}</p>
|
|
127
|
+
</article>
|
|
128
|
+
)}
|
|
129
|
+
onThumbnailClick={(id) => openDrawer(id)}
|
|
130
|
+
/>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Opt-in hover prefetch (warms HLS manifest via `adapters.videoLoader.preloadMetadata`):
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<ReelsFeedThumbnail prefetchOnHover renderThumbnail={...} />
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
On click the SDK calls `ResourceGovernor.setFocusedIndexImmediate(index)` before
|
|
140
|
+
invoking `onThumbnailClick`, so opening `<ReelsFeed>` lands on the tapped item
|
|
141
|
+
without a scroll-to-index flash. Disable with `setFocusOnClick={false}`.
|
|
142
|
+
|
|
143
|
+
Custom slots for the zero-items branches:
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
<ReelsFeedThumbnail
|
|
147
|
+
renderThumbnail={(item) => <Card item={item} />}
|
|
148
|
+
renderLoading={() => <SkeletonGrid />}
|
|
149
|
+
renderEmpty={() => <EmptyState />}
|
|
150
|
+
renderError={({ message, retry }) => (
|
|
151
|
+
<ErrorBox message={message} onRetry={retry} />
|
|
152
|
+
)}
|
|
153
|
+
/>
|
|
154
|
+
```
|
|
155
|
+
|
|
114
156
|
## Architecture
|
|
115
157
|
|
|
116
158
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -2052,7 +2052,7 @@ function VideoSlotInner({
|
|
|
2052
2052
|
inset: 0,
|
|
2053
2053
|
backgroundImage: `url(${item.poster})`,
|
|
2054
2054
|
backgroundSize: "cover",
|
|
2055
|
-
backgroundPosition: "center",
|
|
2055
|
+
backgroundPosition: "center center",
|
|
2056
2056
|
opacity: showPosterOverlay ? 1 : 0,
|
|
2057
2057
|
transition: "opacity 0.15s ease",
|
|
2058
2058
|
pointerEvents: "none"
|
|
@@ -2499,6 +2499,88 @@ function parsePxTranslateY(el) {
|
|
|
2499
2499
|
if (!match || !match[1]) return 0;
|
|
2500
2500
|
return Number.parseFloat(match[1]);
|
|
2501
2501
|
}
|
|
2502
|
+
var DEFAULT_CLASSNAME = "grid grid-cols-2 gap-3";
|
|
2503
|
+
var defaultGetKey = (item) => item.id;
|
|
2504
|
+
var buttonResetStyle = {
|
|
2505
|
+
all: "unset",
|
|
2506
|
+
display: "block",
|
|
2507
|
+
cursor: "pointer",
|
|
2508
|
+
width: "100%",
|
|
2509
|
+
boxSizing: "border-box"
|
|
2510
|
+
};
|
|
2511
|
+
function ReelsFeedThumbnail({
|
|
2512
|
+
renderThumbnail,
|
|
2513
|
+
onThumbnailClick,
|
|
2514
|
+
renderLoading,
|
|
2515
|
+
renderEmpty,
|
|
2516
|
+
renderError,
|
|
2517
|
+
className = DEFAULT_CLASSNAME,
|
|
2518
|
+
wrap = true,
|
|
2519
|
+
setFocusOnClick = true,
|
|
2520
|
+
prefetchOnHover = false,
|
|
2521
|
+
getKey = defaultGetKey
|
|
2522
|
+
}) {
|
|
2523
|
+
const { items, loading, error, refresh } = useFeed();
|
|
2524
|
+
const { setFocusedIndexImmediate } = useResource();
|
|
2525
|
+
const { adapters } = useSDK();
|
|
2526
|
+
const prefetchedRef = react.useRef(/* @__PURE__ */ new Set());
|
|
2527
|
+
const handleClick = react.useCallback(
|
|
2528
|
+
(item, index) => {
|
|
2529
|
+
if (setFocusOnClick) {
|
|
2530
|
+
setFocusedIndexImmediate(index);
|
|
2531
|
+
}
|
|
2532
|
+
onThumbnailClick?.(item.id, item, index);
|
|
2533
|
+
},
|
|
2534
|
+
[onThumbnailClick, setFocusOnClick, setFocusedIndexImmediate]
|
|
2535
|
+
);
|
|
2536
|
+
const handlePointerEnter = react.useCallback(
|
|
2537
|
+
(item) => {
|
|
2538
|
+
if (!prefetchOnHover) return;
|
|
2539
|
+
if (!isVideoItem(item)) return;
|
|
2540
|
+
if (item.source.type !== "hls") return;
|
|
2541
|
+
if (prefetchedRef.current.has(item.id)) return;
|
|
2542
|
+
const loader = adapters.videoLoader;
|
|
2543
|
+
if (!loader?.preloadMetadata) return;
|
|
2544
|
+
if (loader.isPreloaded(item.id)) {
|
|
2545
|
+
prefetchedRef.current.add(item.id);
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
loader.preloadMetadata(item.source.url);
|
|
2549
|
+
prefetchedRef.current.add(item.id);
|
|
2550
|
+
},
|
|
2551
|
+
[prefetchOnHover, adapters.videoLoader]
|
|
2552
|
+
);
|
|
2553
|
+
const children = react.useMemo(
|
|
2554
|
+
() => items.map((item, index) => {
|
|
2555
|
+
const key = getKey(item, index);
|
|
2556
|
+
const onEnter = prefetchOnHover ? (_e) => handlePointerEnter(item) : void 0;
|
|
2557
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2558
|
+
"button",
|
|
2559
|
+
{
|
|
2560
|
+
type: "button",
|
|
2561
|
+
style: buttonResetStyle,
|
|
2562
|
+
"data-thumbnail-index": index,
|
|
2563
|
+
"data-thumbnail-id": item.id,
|
|
2564
|
+
onClick: () => handleClick(item, index),
|
|
2565
|
+
onPointerEnter: onEnter,
|
|
2566
|
+
children: renderThumbnail(item, index)
|
|
2567
|
+
},
|
|
2568
|
+
key
|
|
2569
|
+
);
|
|
2570
|
+
}),
|
|
2571
|
+
[items, getKey, prefetchOnHover, handlePointerEnter, handleClick, renderThumbnail]
|
|
2572
|
+
);
|
|
2573
|
+
if (items.length === 0) {
|
|
2574
|
+
if (loading && renderLoading) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderLoading() });
|
|
2575
|
+
if (error && renderError) {
|
|
2576
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderError({ message: error.message, retry: refresh }) });
|
|
2577
|
+
}
|
|
2578
|
+
if (!loading && !error && renderEmpty) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderEmpty() });
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
if (!wrap) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
2582
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, children });
|
|
2583
|
+
}
|
|
2502
2584
|
function usePlayerSelector(selector) {
|
|
2503
2585
|
const { playerEngine } = useSDK();
|
|
2504
2586
|
const selectorRef = react.useRef(selector);
|
|
@@ -2919,16 +3001,12 @@ function transformVideoItem(raw) {
|
|
|
2919
3001
|
const stats = transformStats(obj);
|
|
2920
3002
|
const interaction = transformInteraction(obj);
|
|
2921
3003
|
let poster;
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
if (Array.isArray(mediaArr2) && mediaArr2.length > 0) {
|
|
2929
|
-
const first = mediaArr2[0];
|
|
2930
|
-
poster = toStr(first["poster"], "") || void 0;
|
|
2931
|
-
}
|
|
3004
|
+
let duration = 0;
|
|
3005
|
+
const mediaArr = obj["media"];
|
|
3006
|
+
if (Array.isArray(mediaArr) && mediaArr.length > 0) {
|
|
3007
|
+
const first = mediaArr[0];
|
|
3008
|
+
poster = toStr(first["poster"], "") || void 0;
|
|
3009
|
+
duration = toNum(first["duration"], 0);
|
|
2932
3010
|
}
|
|
2933
3011
|
if (!poster) {
|
|
2934
3012
|
poster = toStr(
|
|
@@ -2936,11 +3014,11 @@ function transformVideoItem(raw) {
|
|
|
2936
3014
|
void 0
|
|
2937
3015
|
) || void 0;
|
|
2938
3016
|
}
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
3017
|
+
if (!poster) {
|
|
3018
|
+
const thumbnailObj = obj["thumbnail"];
|
|
3019
|
+
if (thumbnailObj && thumbnailObj["url"]) {
|
|
3020
|
+
poster = toStr(thumbnailObj["url"], "") || void 0;
|
|
3021
|
+
}
|
|
2944
3022
|
}
|
|
2945
3023
|
if (duration === 0) {
|
|
2946
3024
|
duration = toNum(tryFields(obj, "duration", "duration_seconds", "length", "video_duration"), 0);
|
|
@@ -3135,6 +3213,7 @@ exports.OptimisticManager = OptimisticManager;
|
|
|
3135
3213
|
exports.PlayerEngine = PlayerEngine;
|
|
3136
3214
|
exports.PlayerStatus = PlayerStatus;
|
|
3137
3215
|
exports.ReelsFeed = ReelsFeed;
|
|
3216
|
+
exports.ReelsFeedThumbnail = ReelsFeedThumbnail;
|
|
3138
3217
|
exports.ReelsProvider = ReelsProvider;
|
|
3139
3218
|
exports.ResourceGovernor = ResourceGovernor;
|
|
3140
3219
|
exports.VALID_TRANSITIONS = VALID_TRANSITIONS;
|
package/dist/index.d.cts
CHANGED
|
@@ -875,6 +875,41 @@ interface VideoSlotProps {
|
|
|
875
875
|
}
|
|
876
876
|
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, onAutoplayBlocked, showFps, isDragging, renderOverlay, renderActions, renderPauseAction, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
877
877
|
|
|
878
|
+
interface ReelsFeedThumbnailProps {
|
|
879
|
+
/** Render function for a single item's visual card. */
|
|
880
|
+
renderThumbnail: (videoData: ContentItem, index: number) => ReactNode;
|
|
881
|
+
/** Click handler — receives the item id, full data, and index in the feed. */
|
|
882
|
+
onThumbnailClick?: (id: string, videoData: ContentItem, index: number) => void;
|
|
883
|
+
/** Rendered when the feed is loading and has zero items yet. */
|
|
884
|
+
renderLoading?: () => ReactNode;
|
|
885
|
+
/** Rendered when the feed has loaded but is empty. */
|
|
886
|
+
renderEmpty?: () => ReactNode;
|
|
887
|
+
/** Rendered when the feed fails with no items — receives a retry callback. */
|
|
888
|
+
renderError?: (error: {
|
|
889
|
+
message: string;
|
|
890
|
+
retry: () => void;
|
|
891
|
+
}) => ReactNode;
|
|
892
|
+
/** Outer wrapper className. Defaults to `grid grid-cols-2 gap-3`. */
|
|
893
|
+
className?: string;
|
|
894
|
+
/** If the host wants to render its own wrapper, pass `false`. */
|
|
895
|
+
wrap?: boolean;
|
|
896
|
+
/**
|
|
897
|
+
* Update ResourceGovernor's focused index on click so the drawer/feed opens
|
|
898
|
+
* already pointing at the tapped item.
|
|
899
|
+
* @default true
|
|
900
|
+
*/
|
|
901
|
+
setFocusOnClick?: boolean;
|
|
902
|
+
/**
|
|
903
|
+
* Prefetch HLS metadata for this slot on `pointerenter`. Off by default;
|
|
904
|
+
* only useful on hover-capable devices.
|
|
905
|
+
* @default false
|
|
906
|
+
*/
|
|
907
|
+
prefetchOnHover?: boolean;
|
|
908
|
+
/** Key override — useful when host renders multiple lists from the same feed. */
|
|
909
|
+
getKey?: (item: ContentItem, index: number) => string;
|
|
910
|
+
}
|
|
911
|
+
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
912
|
+
|
|
878
913
|
declare function DefaultOverlay({ item }: {
|
|
879
914
|
item: ContentItem;
|
|
880
915
|
}): react_jsx_runtime.JSX.Element;
|
|
@@ -1174,4 +1209,4 @@ declare class HttpError extends Error {
|
|
|
1174
1209
|
constructor(status: number, message: string, body?: string | undefined);
|
|
1175
1210
|
}
|
|
1176
1211
|
|
|
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 };
|
|
1212
|
+
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
|
@@ -875,6 +875,41 @@ interface VideoSlotProps {
|
|
|
875
875
|
}
|
|
876
876
|
declare function VideoSlot({ item, index, isActive, isPrefetch, isPreloaded, bufferTier, isMuted, onToggleMute, onAutoplayBlocked, showFps, isDragging, renderOverlay, renderActions, renderPauseAction, }: VideoSlotProps): react_jsx_runtime.JSX.Element;
|
|
877
877
|
|
|
878
|
+
interface ReelsFeedThumbnailProps {
|
|
879
|
+
/** Render function for a single item's visual card. */
|
|
880
|
+
renderThumbnail: (videoData: ContentItem, index: number) => ReactNode;
|
|
881
|
+
/** Click handler — receives the item id, full data, and index in the feed. */
|
|
882
|
+
onThumbnailClick?: (id: string, videoData: ContentItem, index: number) => void;
|
|
883
|
+
/** Rendered when the feed is loading and has zero items yet. */
|
|
884
|
+
renderLoading?: () => ReactNode;
|
|
885
|
+
/** Rendered when the feed has loaded but is empty. */
|
|
886
|
+
renderEmpty?: () => ReactNode;
|
|
887
|
+
/** Rendered when the feed fails with no items — receives a retry callback. */
|
|
888
|
+
renderError?: (error: {
|
|
889
|
+
message: string;
|
|
890
|
+
retry: () => void;
|
|
891
|
+
}) => ReactNode;
|
|
892
|
+
/** Outer wrapper className. Defaults to `grid grid-cols-2 gap-3`. */
|
|
893
|
+
className?: string;
|
|
894
|
+
/** If the host wants to render its own wrapper, pass `false`. */
|
|
895
|
+
wrap?: boolean;
|
|
896
|
+
/**
|
|
897
|
+
* Update ResourceGovernor's focused index on click so the drawer/feed opens
|
|
898
|
+
* already pointing at the tapped item.
|
|
899
|
+
* @default true
|
|
900
|
+
*/
|
|
901
|
+
setFocusOnClick?: boolean;
|
|
902
|
+
/**
|
|
903
|
+
* Prefetch HLS metadata for this slot on `pointerenter`. Off by default;
|
|
904
|
+
* only useful on hover-capable devices.
|
|
905
|
+
* @default false
|
|
906
|
+
*/
|
|
907
|
+
prefetchOnHover?: boolean;
|
|
908
|
+
/** Key override — useful when host renders multiple lists from the same feed. */
|
|
909
|
+
getKey?: (item: ContentItem, index: number) => string;
|
|
910
|
+
}
|
|
911
|
+
declare function ReelsFeedThumbnail({ renderThumbnail, onThumbnailClick, renderLoading, renderEmpty, renderError, className, wrap, setFocusOnClick, prefetchOnHover, getKey, }: ReelsFeedThumbnailProps): react_jsx_runtime.JSX.Element | null;
|
|
912
|
+
|
|
878
913
|
declare function DefaultOverlay({ item }: {
|
|
879
914
|
item: ContentItem;
|
|
880
915
|
}): react_jsx_runtime.JSX.Element;
|
|
@@ -1174,4 +1209,4 @@ declare class HttpError extends Error {
|
|
|
1174
1209
|
constructor(status: number, message: string, body?: string | undefined);
|
|
1175
1210
|
}
|
|
1176
1211
|
|
|
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 };
|
|
1212
|
+
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
|
@@ -2046,7 +2046,7 @@ function VideoSlotInner({
|
|
|
2046
2046
|
inset: 0,
|
|
2047
2047
|
backgroundImage: `url(${item.poster})`,
|
|
2048
2048
|
backgroundSize: "cover",
|
|
2049
|
-
backgroundPosition: "center",
|
|
2049
|
+
backgroundPosition: "center center",
|
|
2050
2050
|
opacity: showPosterOverlay ? 1 : 0,
|
|
2051
2051
|
transition: "opacity 0.15s ease",
|
|
2052
2052
|
pointerEvents: "none"
|
|
@@ -2493,6 +2493,88 @@ function parsePxTranslateY(el) {
|
|
|
2493
2493
|
if (!match || !match[1]) return 0;
|
|
2494
2494
|
return Number.parseFloat(match[1]);
|
|
2495
2495
|
}
|
|
2496
|
+
var DEFAULT_CLASSNAME = "grid grid-cols-2 gap-3";
|
|
2497
|
+
var defaultGetKey = (item) => item.id;
|
|
2498
|
+
var buttonResetStyle = {
|
|
2499
|
+
all: "unset",
|
|
2500
|
+
display: "block",
|
|
2501
|
+
cursor: "pointer",
|
|
2502
|
+
width: "100%",
|
|
2503
|
+
boxSizing: "border-box"
|
|
2504
|
+
};
|
|
2505
|
+
function ReelsFeedThumbnail({
|
|
2506
|
+
renderThumbnail,
|
|
2507
|
+
onThumbnailClick,
|
|
2508
|
+
renderLoading,
|
|
2509
|
+
renderEmpty,
|
|
2510
|
+
renderError,
|
|
2511
|
+
className = DEFAULT_CLASSNAME,
|
|
2512
|
+
wrap = true,
|
|
2513
|
+
setFocusOnClick = true,
|
|
2514
|
+
prefetchOnHover = false,
|
|
2515
|
+
getKey = defaultGetKey
|
|
2516
|
+
}) {
|
|
2517
|
+
const { items, loading, error, refresh } = useFeed();
|
|
2518
|
+
const { setFocusedIndexImmediate } = useResource();
|
|
2519
|
+
const { adapters } = useSDK();
|
|
2520
|
+
const prefetchedRef = useRef(/* @__PURE__ */ new Set());
|
|
2521
|
+
const handleClick = useCallback(
|
|
2522
|
+
(item, index) => {
|
|
2523
|
+
if (setFocusOnClick) {
|
|
2524
|
+
setFocusedIndexImmediate(index);
|
|
2525
|
+
}
|
|
2526
|
+
onThumbnailClick?.(item.id, item, index);
|
|
2527
|
+
},
|
|
2528
|
+
[onThumbnailClick, setFocusOnClick, setFocusedIndexImmediate]
|
|
2529
|
+
);
|
|
2530
|
+
const handlePointerEnter = useCallback(
|
|
2531
|
+
(item) => {
|
|
2532
|
+
if (!prefetchOnHover) return;
|
|
2533
|
+
if (!isVideoItem(item)) return;
|
|
2534
|
+
if (item.source.type !== "hls") return;
|
|
2535
|
+
if (prefetchedRef.current.has(item.id)) return;
|
|
2536
|
+
const loader = adapters.videoLoader;
|
|
2537
|
+
if (!loader?.preloadMetadata) return;
|
|
2538
|
+
if (loader.isPreloaded(item.id)) {
|
|
2539
|
+
prefetchedRef.current.add(item.id);
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
loader.preloadMetadata(item.source.url);
|
|
2543
|
+
prefetchedRef.current.add(item.id);
|
|
2544
|
+
},
|
|
2545
|
+
[prefetchOnHover, adapters.videoLoader]
|
|
2546
|
+
);
|
|
2547
|
+
const children = useMemo(
|
|
2548
|
+
() => items.map((item, index) => {
|
|
2549
|
+
const key = getKey(item, index);
|
|
2550
|
+
const onEnter = prefetchOnHover ? (_e) => handlePointerEnter(item) : void 0;
|
|
2551
|
+
return /* @__PURE__ */ jsx(
|
|
2552
|
+
"button",
|
|
2553
|
+
{
|
|
2554
|
+
type: "button",
|
|
2555
|
+
style: buttonResetStyle,
|
|
2556
|
+
"data-thumbnail-index": index,
|
|
2557
|
+
"data-thumbnail-id": item.id,
|
|
2558
|
+
onClick: () => handleClick(item, index),
|
|
2559
|
+
onPointerEnter: onEnter,
|
|
2560
|
+
children: renderThumbnail(item, index)
|
|
2561
|
+
},
|
|
2562
|
+
key
|
|
2563
|
+
);
|
|
2564
|
+
}),
|
|
2565
|
+
[items, getKey, prefetchOnHover, handlePointerEnter, handleClick, renderThumbnail]
|
|
2566
|
+
);
|
|
2567
|
+
if (items.length === 0) {
|
|
2568
|
+
if (loading && renderLoading) return /* @__PURE__ */ jsx(Fragment, { children: renderLoading() });
|
|
2569
|
+
if (error && renderError) {
|
|
2570
|
+
return /* @__PURE__ */ jsx(Fragment, { children: renderError({ message: error.message, retry: refresh }) });
|
|
2571
|
+
}
|
|
2572
|
+
if (!loading && !error && renderEmpty) return /* @__PURE__ */ jsx(Fragment, { children: renderEmpty() });
|
|
2573
|
+
return null;
|
|
2574
|
+
}
|
|
2575
|
+
if (!wrap) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
2576
|
+
return /* @__PURE__ */ jsx("div", { className, children });
|
|
2577
|
+
}
|
|
2496
2578
|
function usePlayerSelector(selector) {
|
|
2497
2579
|
const { playerEngine } = useSDK();
|
|
2498
2580
|
const selectorRef = useRef(selector);
|
|
@@ -2913,16 +2995,12 @@ function transformVideoItem(raw) {
|
|
|
2913
2995
|
const stats = transformStats(obj);
|
|
2914
2996
|
const interaction = transformInteraction(obj);
|
|
2915
2997
|
let poster;
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
if (Array.isArray(mediaArr2) && mediaArr2.length > 0) {
|
|
2923
|
-
const first = mediaArr2[0];
|
|
2924
|
-
poster = toStr(first["poster"], "") || void 0;
|
|
2925
|
-
}
|
|
2998
|
+
let duration = 0;
|
|
2999
|
+
const mediaArr = obj["media"];
|
|
3000
|
+
if (Array.isArray(mediaArr) && mediaArr.length > 0) {
|
|
3001
|
+
const first = mediaArr[0];
|
|
3002
|
+
poster = toStr(first["poster"], "") || void 0;
|
|
3003
|
+
duration = toNum(first["duration"], 0);
|
|
2926
3004
|
}
|
|
2927
3005
|
if (!poster) {
|
|
2928
3006
|
poster = toStr(
|
|
@@ -2930,11 +3008,11 @@ function transformVideoItem(raw) {
|
|
|
2930
3008
|
void 0
|
|
2931
3009
|
) || void 0;
|
|
2932
3010
|
}
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
3011
|
+
if (!poster) {
|
|
3012
|
+
const thumbnailObj = obj["thumbnail"];
|
|
3013
|
+
if (thumbnailObj && thumbnailObj["url"]) {
|
|
3014
|
+
poster = toStr(thumbnailObj["url"], "") || void 0;
|
|
3015
|
+
}
|
|
2938
3016
|
}
|
|
2939
3017
|
if (duration === 0) {
|
|
2940
3018
|
duration = toNum(tryFields(obj, "duration", "duration_seconds", "length", "video_duration"), 0);
|
|
@@ -3107,4 +3185,4 @@ var HttpError = class extends Error {
|
|
|
3107
3185
|
}
|
|
3108
3186
|
};
|
|
3109
3187
|
|
|
3110
|
-
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 };
|
|
3188
|
+
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 };
|