@xhub-reels/sdk 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.cjs +53 -100
  2. package/dist/index.js +53 -100
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -1624,43 +1624,6 @@ function DefaultSkeleton() {
1624
1624
  }
1625
1625
  }
1626
1626
  ),
1627
- /* @__PURE__ */ jsxRuntime.jsxs(
1628
- "div",
1629
- {
1630
- style: {
1631
- position: "absolute",
1632
- bottom: 100,
1633
- left: 16,
1634
- display: "flex",
1635
- flexDirection: "column",
1636
- gap: 8
1637
- },
1638
- children: [
1639
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: skeletonBar(120, 14) }),
1640
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: skeletonBar(200, 12) }),
1641
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: skeletonBar(160, 12) })
1642
- ]
1643
- }
1644
- ),
1645
- /* @__PURE__ */ jsxRuntime.jsxs(
1646
- "div",
1647
- {
1648
- style: {
1649
- position: "absolute",
1650
- bottom: 100,
1651
- right: 16,
1652
- display: "flex",
1653
- flexDirection: "column",
1654
- gap: 20,
1655
- alignItems: "center"
1656
- },
1657
- children: [
1658
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: skeletonCircle(40) }),
1659
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: skeletonCircle(40) }),
1660
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: skeletonCircle(40) })
1661
- ]
1662
- }
1663
- ),
1664
1627
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
1665
1628
  @keyframes reels-sdk-shimmer {
1666
1629
  0% { transform: translateX(-100%); }
@@ -1671,22 +1634,6 @@ function DefaultSkeleton() {
1671
1634
  }
1672
1635
  );
1673
1636
  }
1674
- function skeletonBar(width, height) {
1675
- return {
1676
- width,
1677
- height,
1678
- borderRadius: height / 2,
1679
- background: "rgba(255,255,255,0.1)"
1680
- };
1681
- }
1682
- function skeletonCircle(size) {
1683
- return {
1684
- width: size,
1685
- height: size,
1686
- borderRadius: "50%",
1687
- background: "rgba(255,255,255,0.1)"
1688
- };
1689
- }
1690
1637
  function DefaultPauseAction() {
1691
1638
  return /* @__PURE__ */ jsxRuntime.jsx(
1692
1639
  "div",
@@ -1723,8 +1670,8 @@ function DefaultPauseAction() {
1723
1670
  }
1724
1671
  );
1725
1672
  }
1726
- var PLAY_AHEAD_MAX_CONCURRENT = 3;
1727
- var PLAY_AHEAD_STAGGER_MS = 50;
1673
+ var PLAY_AHEAD_MAX_CONCURRENT = 2;
1674
+ var PLAY_AHEAD_STAGGER_MS = 80;
1728
1675
  var _playAheadActive = 0;
1729
1676
  var _playAheadQueue = [];
1730
1677
  function acquirePlayAhead() {
@@ -1733,9 +1680,8 @@ function acquirePlayAhead() {
1733
1680
  return Promise.resolve();
1734
1681
  }
1735
1682
  return new Promise((resolve) => {
1736
- const queuePosition = _playAheadQueue.length;
1737
1683
  _playAheadQueue.push(() => {
1738
- setTimeout(resolve, PLAY_AHEAD_STAGGER_MS * queuePosition);
1684
+ setTimeout(resolve, PLAY_AHEAD_STAGGER_MS);
1739
1685
  });
1740
1686
  });
1741
1687
  }
@@ -2149,38 +2095,40 @@ function VideoSlotInner({
2149
2095
  children: renderPauseAction ? renderPauseAction(item, pauseActions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultPauseAction, {})
2150
2096
  }
2151
2097
  ),
2152
- /* @__PURE__ */ jsxRuntime.jsx(
2153
- "div",
2154
- {
2155
- style: {
2156
- position: "absolute",
2157
- bottom: 80,
2158
- left: 16,
2159
- right: 80,
2160
- pointerEvents: "none",
2161
- color: "#fff"
2162
- },
2163
- children: renderOverlay ? renderOverlay(item, actions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultOverlay, { item })
2164
- }
2165
- ),
2166
- /* @__PURE__ */ jsxRuntime.jsx(
2167
- "div",
2168
- {
2169
- style: {
2170
- position: "absolute",
2171
- bottom: 80,
2172
- right: 16,
2173
- display: "flex",
2174
- flexDirection: "column",
2175
- gap: 20,
2176
- alignItems: "center"
2177
- // Actions must be clickable; stop propagation so taps don't
2178
- // also trigger the video play/pause handler on the container.
2179
- },
2180
- onClick: (e) => e.stopPropagation(),
2181
- children: renderActions ? renderActions(item, actions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultActions, { item, actions })
2182
- }
2183
- ),
2098
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2099
+ position: "absolute",
2100
+ bottom: 0,
2101
+ left: 0,
2102
+ right: 0,
2103
+ top: 0,
2104
+ zIndex: 1e3
2105
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2106
+ position: "relative",
2107
+ width: "100%",
2108
+ height: "100%"
2109
+ }, children: [
2110
+ /* @__PURE__ */ jsxRuntime.jsx(
2111
+ "div",
2112
+ {
2113
+ style: {
2114
+ background: "linear-gradient(to bottom, transparent, rgba(0,0,0,0.5))",
2115
+ position: "absolute",
2116
+ width: "100%",
2117
+ height: "30%",
2118
+ bottom: 0,
2119
+ left: 0
2120
+ }
2121
+ }
2122
+ ),
2123
+ renderOverlay ? renderOverlay(item, actions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultOverlay, { item }),
2124
+ /* @__PURE__ */ jsxRuntime.jsx(
2125
+ "div",
2126
+ {
2127
+ onClick: (e) => e.stopPropagation(),
2128
+ children: renderActions ? renderActions(item, actions) : /* @__PURE__ */ jsxRuntime.jsx(DefaultActions, { item, actions })
2129
+ }
2130
+ )
2131
+ ] }) }),
2184
2132
  showFps && /* @__PURE__ */ jsxRuntime.jsx(FpsCounter, {})
2185
2133
  ]
2186
2134
  }
@@ -2270,7 +2218,6 @@ function ReelsFeed({
2270
2218
  const slotCacheRef = react.useRef(/* @__PURE__ */ new Map());
2271
2219
  const activeIndexRef = react.useRef(0);
2272
2220
  activeIndexRef.current = focusedIndex;
2273
- const [isSnapping, setIsSnapping] = react.useState(false);
2274
2221
  const { animateSnap, animateBounceBack, cancelAnimation } = useSnapAnimation({
2275
2222
  duration: snapConfig?.duration ?? 260,
2276
2223
  easing: snapConfig?.easing ?? "cubic-bezier(0.25, 0.46, 0.45, 0.94)"
@@ -2376,13 +2323,7 @@ function ReelsFeed({
2376
2323
  return;
2377
2324
  }
2378
2325
  cancelAnimation();
2379
- setIsSnapping(true);
2380
2326
  setPrefetchIndex(null);
2381
- setFocusedIndexImmediate(next);
2382
- const nextItem = items[next];
2383
- if (nextItem) {
2384
- onSlotChange?.(next, nextItem, current);
2385
- }
2386
2327
  const h = containerHeight.current;
2387
2328
  const targets = [];
2388
2329
  for (const [idx, el] of slotCacheRef.current) {
@@ -2393,7 +2334,13 @@ function ReelsFeed({
2393
2334
  });
2394
2335
  }
2395
2336
  animateSnap(targets);
2396
- setTimeout(() => setIsSnapping(false), 300);
2337
+ requestAnimationFrame(() => {
2338
+ setFocusedIndexImmediate(next);
2339
+ const nextItem = items[next];
2340
+ if (nextItem) {
2341
+ onSlotChange?.(next, nextItem, current);
2342
+ }
2343
+ });
2397
2344
  },
2398
2345
  [items, animateSnap, animateBounceBack, cancelAnimation, setFocusedIndexImmediate, setPrefetchIndex, onSlotChange]
2399
2346
  );
@@ -2415,13 +2362,13 @@ function ReelsFeed({
2415
2362
  setIsDragMuted(true);
2416
2363
  }, []);
2417
2364
  const handleDragEnd = react.useCallback(() => {
2418
- setTimeout(() => setIsDragMuted(false), 50);
2365
+ setTimeout(() => setIsDragMuted(false), 300);
2419
2366
  }, []);
2420
2367
  const { bind } = usePointerGesture({
2421
2368
  axis: "y",
2422
2369
  velocityThreshold: gestureConfig?.velocityThreshold ?? 0.3,
2423
2370
  distanceThreshold: gestureConfig?.distanceThreshold ?? 80,
2424
- disabled: isSnapping,
2371
+ disabled: false,
2425
2372
  containerSize: containerHeight.current,
2426
2373
  dragThresholdRatio: gestureConfig?.dragThresholdRatio ?? 0.5,
2427
2374
  onDragOffset: (offset) => {
@@ -2495,7 +2442,13 @@ function ReelsFeed({
2495
2442
  const wrapperStyle = {
2496
2443
  position: "absolute",
2497
2444
  inset: 0,
2498
- contain: "layout style paint",
2445
+ // Fix 4: 'strict' = layout + style + paint + size containment.
2446
+ // Tells browser this slot is fully self-contained — no layout
2447
+ // escapes to parent. Eliminates layout thrash during animation.
2448
+ contain: "strict",
2449
+ // Fix 4: willChange only on active ±1 (3 slots max).
2450
+ // Previously ±1 already, keep it. Avoids unnecessary GPU layers
2451
+ // on warm/cold slots that are never animated directly.
2499
2452
  willChange: distFromFocus <= 1 ? "transform" : "auto",
2500
2453
  transform: getInitialTransformPx(index)
2501
2454
  };
package/dist/index.js CHANGED
@@ -1618,43 +1618,6 @@ function DefaultSkeleton() {
1618
1618
  }
1619
1619
  }
1620
1620
  ),
1621
- /* @__PURE__ */ jsxs(
1622
- "div",
1623
- {
1624
- style: {
1625
- position: "absolute",
1626
- bottom: 100,
1627
- left: 16,
1628
- display: "flex",
1629
- flexDirection: "column",
1630
- gap: 8
1631
- },
1632
- children: [
1633
- /* @__PURE__ */ jsx("div", { style: skeletonBar(120, 14) }),
1634
- /* @__PURE__ */ jsx("div", { style: skeletonBar(200, 12) }),
1635
- /* @__PURE__ */ jsx("div", { style: skeletonBar(160, 12) })
1636
- ]
1637
- }
1638
- ),
1639
- /* @__PURE__ */ jsxs(
1640
- "div",
1641
- {
1642
- style: {
1643
- position: "absolute",
1644
- bottom: 100,
1645
- right: 16,
1646
- display: "flex",
1647
- flexDirection: "column",
1648
- gap: 20,
1649
- alignItems: "center"
1650
- },
1651
- children: [
1652
- /* @__PURE__ */ jsx("div", { style: skeletonCircle(40) }),
1653
- /* @__PURE__ */ jsx("div", { style: skeletonCircle(40) }),
1654
- /* @__PURE__ */ jsx("div", { style: skeletonCircle(40) })
1655
- ]
1656
- }
1657
- ),
1658
1621
  /* @__PURE__ */ jsx("style", { children: `
1659
1622
  @keyframes reels-sdk-shimmer {
1660
1623
  0% { transform: translateX(-100%); }
@@ -1665,22 +1628,6 @@ function DefaultSkeleton() {
1665
1628
  }
1666
1629
  );
1667
1630
  }
1668
- function skeletonBar(width, height) {
1669
- return {
1670
- width,
1671
- height,
1672
- borderRadius: height / 2,
1673
- background: "rgba(255,255,255,0.1)"
1674
- };
1675
- }
1676
- function skeletonCircle(size) {
1677
- return {
1678
- width: size,
1679
- height: size,
1680
- borderRadius: "50%",
1681
- background: "rgba(255,255,255,0.1)"
1682
- };
1683
- }
1684
1631
  function DefaultPauseAction() {
1685
1632
  return /* @__PURE__ */ jsx(
1686
1633
  "div",
@@ -1717,8 +1664,8 @@ function DefaultPauseAction() {
1717
1664
  }
1718
1665
  );
1719
1666
  }
1720
- var PLAY_AHEAD_MAX_CONCURRENT = 3;
1721
- var PLAY_AHEAD_STAGGER_MS = 50;
1667
+ var PLAY_AHEAD_MAX_CONCURRENT = 2;
1668
+ var PLAY_AHEAD_STAGGER_MS = 80;
1722
1669
  var _playAheadActive = 0;
1723
1670
  var _playAheadQueue = [];
1724
1671
  function acquirePlayAhead() {
@@ -1727,9 +1674,8 @@ function acquirePlayAhead() {
1727
1674
  return Promise.resolve();
1728
1675
  }
1729
1676
  return new Promise((resolve) => {
1730
- const queuePosition = _playAheadQueue.length;
1731
1677
  _playAheadQueue.push(() => {
1732
- setTimeout(resolve, PLAY_AHEAD_STAGGER_MS * queuePosition);
1678
+ setTimeout(resolve, PLAY_AHEAD_STAGGER_MS);
1733
1679
  });
1734
1680
  });
1735
1681
  }
@@ -2143,38 +2089,40 @@ function VideoSlotInner({
2143
2089
  children: renderPauseAction ? renderPauseAction(item, pauseActions) : /* @__PURE__ */ jsx(DefaultPauseAction, {})
2144
2090
  }
2145
2091
  ),
2146
- /* @__PURE__ */ jsx(
2147
- "div",
2148
- {
2149
- style: {
2150
- position: "absolute",
2151
- bottom: 80,
2152
- left: 16,
2153
- right: 80,
2154
- pointerEvents: "none",
2155
- color: "#fff"
2156
- },
2157
- children: renderOverlay ? renderOverlay(item, actions) : /* @__PURE__ */ jsx(DefaultOverlay, { item })
2158
- }
2159
- ),
2160
- /* @__PURE__ */ jsx(
2161
- "div",
2162
- {
2163
- style: {
2164
- position: "absolute",
2165
- bottom: 80,
2166
- right: 16,
2167
- display: "flex",
2168
- flexDirection: "column",
2169
- gap: 20,
2170
- alignItems: "center"
2171
- // Actions must be clickable; stop propagation so taps don't
2172
- // also trigger the video play/pause handler on the container.
2173
- },
2174
- onClick: (e) => e.stopPropagation(),
2175
- children: renderActions ? renderActions(item, actions) : /* @__PURE__ */ jsx(DefaultActions, { item, actions })
2176
- }
2177
- ),
2092
+ /* @__PURE__ */ jsx("div", { style: {
2093
+ position: "absolute",
2094
+ bottom: 0,
2095
+ left: 0,
2096
+ right: 0,
2097
+ top: 0,
2098
+ zIndex: 1e3
2099
+ }, children: /* @__PURE__ */ jsxs("div", { style: {
2100
+ position: "relative",
2101
+ width: "100%",
2102
+ height: "100%"
2103
+ }, children: [
2104
+ /* @__PURE__ */ jsx(
2105
+ "div",
2106
+ {
2107
+ style: {
2108
+ background: "linear-gradient(to bottom, transparent, rgba(0,0,0,0.5))",
2109
+ position: "absolute",
2110
+ width: "100%",
2111
+ height: "30%",
2112
+ bottom: 0,
2113
+ left: 0
2114
+ }
2115
+ }
2116
+ ),
2117
+ renderOverlay ? renderOverlay(item, actions) : /* @__PURE__ */ jsx(DefaultOverlay, { item }),
2118
+ /* @__PURE__ */ jsx(
2119
+ "div",
2120
+ {
2121
+ onClick: (e) => e.stopPropagation(),
2122
+ children: renderActions ? renderActions(item, actions) : /* @__PURE__ */ jsx(DefaultActions, { item, actions })
2123
+ }
2124
+ )
2125
+ ] }) }),
2178
2126
  showFps && /* @__PURE__ */ jsx(FpsCounter, {})
2179
2127
  ]
2180
2128
  }
@@ -2264,7 +2212,6 @@ function ReelsFeed({
2264
2212
  const slotCacheRef = useRef(/* @__PURE__ */ new Map());
2265
2213
  const activeIndexRef = useRef(0);
2266
2214
  activeIndexRef.current = focusedIndex;
2267
- const [isSnapping, setIsSnapping] = useState(false);
2268
2215
  const { animateSnap, animateBounceBack, cancelAnimation } = useSnapAnimation({
2269
2216
  duration: snapConfig?.duration ?? 260,
2270
2217
  easing: snapConfig?.easing ?? "cubic-bezier(0.25, 0.46, 0.45, 0.94)"
@@ -2370,13 +2317,7 @@ function ReelsFeed({
2370
2317
  return;
2371
2318
  }
2372
2319
  cancelAnimation();
2373
- setIsSnapping(true);
2374
2320
  setPrefetchIndex(null);
2375
- setFocusedIndexImmediate(next);
2376
- const nextItem = items[next];
2377
- if (nextItem) {
2378
- onSlotChange?.(next, nextItem, current);
2379
- }
2380
2321
  const h = containerHeight.current;
2381
2322
  const targets = [];
2382
2323
  for (const [idx, el] of slotCacheRef.current) {
@@ -2387,7 +2328,13 @@ function ReelsFeed({
2387
2328
  });
2388
2329
  }
2389
2330
  animateSnap(targets);
2390
- setTimeout(() => setIsSnapping(false), 300);
2331
+ requestAnimationFrame(() => {
2332
+ setFocusedIndexImmediate(next);
2333
+ const nextItem = items[next];
2334
+ if (nextItem) {
2335
+ onSlotChange?.(next, nextItem, current);
2336
+ }
2337
+ });
2391
2338
  },
2392
2339
  [items, animateSnap, animateBounceBack, cancelAnimation, setFocusedIndexImmediate, setPrefetchIndex, onSlotChange]
2393
2340
  );
@@ -2409,13 +2356,13 @@ function ReelsFeed({
2409
2356
  setIsDragMuted(true);
2410
2357
  }, []);
2411
2358
  const handleDragEnd = useCallback(() => {
2412
- setTimeout(() => setIsDragMuted(false), 50);
2359
+ setTimeout(() => setIsDragMuted(false), 300);
2413
2360
  }, []);
2414
2361
  const { bind } = usePointerGesture({
2415
2362
  axis: "y",
2416
2363
  velocityThreshold: gestureConfig?.velocityThreshold ?? 0.3,
2417
2364
  distanceThreshold: gestureConfig?.distanceThreshold ?? 80,
2418
- disabled: isSnapping,
2365
+ disabled: false,
2419
2366
  containerSize: containerHeight.current,
2420
2367
  dragThresholdRatio: gestureConfig?.dragThresholdRatio ?? 0.5,
2421
2368
  onDragOffset: (offset) => {
@@ -2489,7 +2436,13 @@ function ReelsFeed({
2489
2436
  const wrapperStyle = {
2490
2437
  position: "absolute",
2491
2438
  inset: 0,
2492
- contain: "layout style paint",
2439
+ // Fix 4: 'strict' = layout + style + paint + size containment.
2440
+ // Tells browser this slot is fully self-contained — no layout
2441
+ // escapes to parent. Eliminates layout thrash during animation.
2442
+ contain: "strict",
2443
+ // Fix 4: willChange only on active ±1 (3 slots max).
2444
+ // Previously ±1 already, keep it. Avoids unnecessary GPU layers
2445
+ // on warm/cold slots that are never animated directly.
2493
2446
  willChange: distFromFocus <= 1 ? "transform" : "auto",
2494
2447
  transform: getInitialTransformPx(index)
2495
2448
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xhub-reels/sdk",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "High-performance Short Video / Reels SDK for React — optimized for Flutter WebView",
5
5
  "license": "MIT",
6
6
  "type": "module",