@xhub-short/ui 1.0.0-beta.21 → 1.0.0-beta.22

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.
@@ -176,6 +176,136 @@ function useDoubleTap({
176
176
  lastPosition: lastPositionRef
177
177
  };
178
178
  }
179
+ var DEFAULT_HOLD_DELAY = 2e3;
180
+ var DEFAULT_BOOST_SPEED = 2;
181
+ var DEFAULT_PULL_DOWN_THRESHOLD = 50;
182
+ var DEFAULT_MOVEMENT_THRESHOLD2 = 15;
183
+ function useLongPressSpeed({
184
+ onSpeedBoost,
185
+ onSpeedReset,
186
+ onSpeedLock,
187
+ boostSpeed = DEFAULT_BOOST_SPEED,
188
+ holdDelay = DEFAULT_HOLD_DELAY,
189
+ pullDownThreshold = DEFAULT_PULL_DOWN_THRESHOLD,
190
+ disabled = false,
191
+ movementThreshold = DEFAULT_MOVEMENT_THRESHOLD2
192
+ }) {
193
+ const [isSpeedBoosted, setIsSpeedBoosted] = useState(false);
194
+ const [isSpeedLocked, setIsSpeedLocked] = useState(false);
195
+ const holdTimerRef = useRef(null);
196
+ const isBoostedRef = useRef(false);
197
+ const isLockedRef = useRef(false);
198
+ const pointerStartRef = useRef(null);
199
+ const boostStartYRef = useRef(null);
200
+ const preboostMovementRef = useRef(0);
201
+ const isTrackingRef = useRef(false);
202
+ const cancelTimer = useCallback(() => {
203
+ if (holdTimerRef.current) {
204
+ clearTimeout(holdTimerRef.current);
205
+ holdTimerRef.current = null;
206
+ }
207
+ }, []);
208
+ useEffect(() => {
209
+ return () => {
210
+ cancelTimer();
211
+ };
212
+ }, [cancelTimer]);
213
+ useEffect(() => {
214
+ if (disabled) {
215
+ cancelTimer();
216
+ if (isBoostedRef.current) {
217
+ isBoostedRef.current = false;
218
+ setIsSpeedBoosted(false);
219
+ }
220
+ }
221
+ }, [disabled, cancelTimer]);
222
+ const handlePointerDown = useCallback(
223
+ (e) => {
224
+ if (e.button !== 0) return;
225
+ if (disabled) return;
226
+ if (isLockedRef.current) return;
227
+ pointerStartRef.current = { x: e.clientX, y: e.clientY };
228
+ preboostMovementRef.current = 0;
229
+ isTrackingRef.current = true;
230
+ cancelTimer();
231
+ holdTimerRef.current = setTimeout(() => {
232
+ holdTimerRef.current = null;
233
+ isBoostedRef.current = true;
234
+ setIsSpeedBoosted(true);
235
+ boostStartYRef.current = pointerStartRef.current?.y ?? e.clientY;
236
+ onSpeedBoost?.(boostSpeed);
237
+ }, holdDelay);
238
+ },
239
+ [disabled, boostSpeed, holdDelay, cancelTimer, onSpeedBoost]
240
+ );
241
+ const handlePointerMove = useCallback(
242
+ (e) => {
243
+ if (!isTrackingRef.current) return;
244
+ if (!pointerStartRef.current) return;
245
+ if (!isBoostedRef.current) {
246
+ preboostMovementRef.current += Math.abs(e.movementX) + Math.abs(e.movementY);
247
+ if (preboostMovementRef.current > movementThreshold * 2) {
248
+ cancelTimer();
249
+ isTrackingRef.current = false;
250
+ }
251
+ } else {
252
+ if (boostStartYRef.current !== null && !isLockedRef.current) {
253
+ const deltaY = e.clientY - boostStartYRef.current;
254
+ if (deltaY > pullDownThreshold) {
255
+ isLockedRef.current = true;
256
+ setIsSpeedLocked(true);
257
+ onSpeedLock?.(boostSpeed);
258
+ }
259
+ }
260
+ }
261
+ },
262
+ [movementThreshold, pullDownThreshold, boostSpeed, cancelTimer, onSpeedLock]
263
+ );
264
+ const handlePointerUp = useCallback(
265
+ (_e) => {
266
+ cancelTimer();
267
+ isTrackingRef.current = false;
268
+ pointerStartRef.current = null;
269
+ boostStartYRef.current = null;
270
+ if (isBoostedRef.current && !isLockedRef.current) {
271
+ isBoostedRef.current = false;
272
+ setIsSpeedBoosted(false);
273
+ onSpeedReset?.();
274
+ } else if (isBoostedRef.current && isLockedRef.current) {
275
+ isBoostedRef.current = false;
276
+ setIsSpeedBoosted(false);
277
+ }
278
+ },
279
+ [cancelTimer, onSpeedReset]
280
+ );
281
+ const handlePointerCancel = useCallback(
282
+ (_e) => {
283
+ cancelTimer();
284
+ isTrackingRef.current = false;
285
+ pointerStartRef.current = null;
286
+ boostStartYRef.current = null;
287
+ if (isBoostedRef.current && !isLockedRef.current) {
288
+ isBoostedRef.current = false;
289
+ setIsSpeedBoosted(false);
290
+ onSpeedReset?.();
291
+ } else if (isBoostedRef.current) {
292
+ isBoostedRef.current = false;
293
+ setIsSpeedBoosted(false);
294
+ }
295
+ },
296
+ [cancelTimer, onSpeedReset]
297
+ );
298
+ return {
299
+ handlers: {
300
+ onPointerDown: handlePointerDown,
301
+ onPointerMove: handlePointerMove,
302
+ onPointerUp: handlePointerUp,
303
+ onPointerCancel: handlePointerCancel
304
+ },
305
+ isSpeedBoosted,
306
+ isSpeedLocked
307
+ };
308
+ }
179
309
  function invariantContextError(hookName, componentName) {
180
310
  const baseMessage = `${hookName} must be used within a VideoSlot component.`;
181
311
  if (process.env.NODE_ENV !== "production") {
@@ -365,4 +495,4 @@ function VideoSlotPlayIndicator(props) {
365
495
  }
366
496
  VideoSlotPlayIndicator.displayName = "VideoSlotPlayIndicator";
367
497
 
368
- export { VideoSlotContext, VideoSlotPlayIndicator, VideoSlotPlayIndicatorInner, useDoubleTap, useOptionalVideoSlotContext, useVideoSlotContext, useVideoSlotIsActive, useVideoSlotIsPlaying, useVideoSlotPlayer, useVideoSlotResource, useVideoSlotVideo };
498
+ export { VideoSlotContext, VideoSlotPlayIndicator, VideoSlotPlayIndicatorInner, useDoubleTap, useLongPressSpeed, useOptionalVideoSlotContext, useVideoSlotContext, useVideoSlotIsActive, useVideoSlotIsPlaying, useVideoSlotPlayer, useVideoSlotResource, useVideoSlotVideo };
@@ -1,13 +1,72 @@
1
- import { useDoubleTap, VideoSlotContext, useVideoSlotContext } from './chunk-VS54DRIS.js';
1
+ import { useDoubleTap, useLongPressSpeed, VideoSlotContext, useVideoSlotContext } from './chunk-3LINB7GV.js';
2
2
  import { HeartFilledIcon } from './chunk-ANCP53F3.js';
3
3
  import { clsx2 } from './chunk-EDWS2IPH.js';
4
4
  import { injectComponentCSS } from './chunk-CAWE42LH.js';
5
- import { memo, useMemo, useState, useCallback, useEffect, useInsertionEffect, useRef } from 'react';
5
+ import { memo, useMemo, useState, useCallback, useEffect, useRef, useInsertionEffect } from 'react';
6
6
  import { jsx, jsxs } from 'react/jsx-runtime';
7
7
 
8
8
  // src/components/VideoSlot/VideoSlot.css.ts
9
9
  var VIDEO_SLOT_CSS = `.sv-video-slot{position:relative;width:100%;height:100%;overflow:hidden;background-color:var(--sv-bg-primary,#000);touch-action:none;user-select:none;-webkit-user-select:none;will-change:contents;contain:layout style paint}.sv-video-slot--active{z-index:1}.sv-video-slot--loading .sv-video-slot__video{opacity:0}.sv-video-slot--loading .sv-video-slot__poster{opacity:1}.sv-video-slot--playing .sv-video-slot__video{opacity:1}.sv-video-slot--playing .sv-video-slot__poster{opacity:0}.sv-video-slot--paused .sv-video-slot__video{opacity:1}.sv-video-slot--paused .sv-video-slot__poster{opacity:0}.sv-video-slot__poster--hidden{opacity:0;pointer-events:none}.sv-video-slot--error{display:flex;align-items:center;justify-content:center}.sv-video-slot__video-container{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.sv-video-slot__video{width:100%;height:100%;object-fit:cover;background-color:transparent;transition:opacity var(--sv-transition-duration,300ms)ease-out}.sv-video-slot__video[data-loaded="true"]{opacity:1}.sv-video-slot__poster{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background-color:var(--sv-bg-primary,#000);z-index:var(--sv-z-poster,3);transition:opacity var(--sv-transition-duration,300ms)ease-out;pointer-events:none}.sv-video-slot__poster-image{width:100%;height:100%;object-fit:cover}.sv-video-slot__poster--first-frame .sv-video-slot__poster-image{image-rendering:auto}.sv-video-slot__poster--skeleton{background:linear-gradient(110deg,var(--sv-skeleton-bg,rgba(255,255,255,.05))0%,var(--sv-skeleton-shimmer,rgba(255,255,255,.1))50%,var(--sv-skeleton-bg,rgba(255,255,255,.05))100%);background-size:200% 100%;animation:sv-slot-skeleton-shimmer 1.5s ease-in-out infinite}@keyframes sv-slot-skeleton-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}.sv-video-slot__overlay{position:absolute;inset:0;z-index:var(--sv-z-overlay,2);background:linear-gradient(to top,rgba(0,0,0,.6)0%,rgba(0,0,0,.3)20%,transparent 40%,transparent 60%,rgba(0,0,0,.2)80%,rgba(0,0,0,.4)100%);pointer-events:none}.sv-video-slot__overlay-top,.sv-video-slot__overlay-bottom,.sv-video-slot__overlay-left,.sv-video-slot__overlay-right,.sv-video-slot__actions,.sv-video-slot__author{pointer-events:none}.sv-video-slot__overlay button,.sv-video-slot__overlay a,.sv-video-slot__overlay [role="button"],.sv-video-slot__overlay input,.sv-video-slot__overlay label,.sv-video-slot__overlay .sv-progress-bar,.sv-video-slot__overlay .sv-author-info,.sv-video-slot__overlay .sv-video-info,.sv-video-slot__overlay .sv-action-bar{pointer-events:auto}.sv-video-slot__overlay-top{position:absolute;top:0;left:0;right:0;padding:var(--sv-spacing-md,16px);padding-top:calc(var(--sv-safe-area-top,0)+var(--sv-spacing-md,16px))}.sv-video-slot__overlay-bottom{position:absolute;bottom:0;left:0;right:0;padding:var(--sv-spacing-md,16px);padding-bottom:calc(var(--sv-safe-area-bottom,0)+var(--sv-spacing-md,16px));display:flex;flex-direction:column;gap:var(--sv-spacing-sm,8px)}.sv-video-slot__overlay-left{position:absolute;top:50%;left:0;transform:translateY(-50%);padding:var(--sv-spacing-md,16px)}.sv-video-slot__overlay-right{position:absolute;top:50%;right:0;transform:translateY(-50%);padding:var(--sv-spacing-md,16px)}.sv-video-slot__actions{position:absolute;right:var(--sv-spacing-sm,8px);bottom:calc(var(--sv-safe-area-bottom,0)+var(--sv-actions-bottom,80px)+var(--sv-playlist-bar-offset,0));display:flex;flex-direction:column;align-items:center;transition:bottom .3s ease-in-out}.sv-video-slot__author{position:absolute;left:var(--sv-spacing-md,16px);bottom:calc(var(--sv-safe-area-bottom,0)+var(--sv-author-bottom,100px)+var(--sv-playlist-bar-offset,0));right:80px;max-width:calc(100% - 100px);transition:bottom .3s ease-in-out}.sv-video-slot__error{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background-color:var(--sv-bg-primary,#000);color:var(--sv-text-secondary,#999);text-align:center;padding:var(--sv-spacing-lg,20px);gap:var(--sv-spacing-md,16px);z-index:var(--sv-z-indicator,3)}.sv-video-slot__error-icon{font-size:48px;opacity:.6}.sv-video-slot__error-message{font-size:var(--sv-font-size-md,14px);max-width:200px}.sv-video-slot__error-retry{padding:var(--sv-spacing-sm,8px)var(--sv-spacing-md,16px);background-color:var(--sv-color-primary,#fe2c55);color:#fff;border:0;border-radius:var(--sv-radius-md,8px);font-size:var(--sv-font-size-sm,12px);font-weight:600;cursor:pointer;transition:background-color 150ms ease}.sv-video-slot__error-retry:hover{background-color:var(--sv-color-primary-hover,#e02850)}.sv-video-slot__error-retry:active{transform:scale(.96)}.sv-video-slot__spinner{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:var(--sv-z-slot-spinner,2)}.sv-video-slot__spinner-circle{width:40px;height:40px;border:3px solid rgba(255,255,255,.2);border-top-color:var(--sv-color-primary,#fe2c55);border-radius:50%;animation:sv-slot-spin .8s linear infinite}@keyframes sv-slot-spin{to{transform:rotate(360deg)}}.sv-video-slot__play-indicator{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:64px;height:64px;display:flex;align-items:center;justify-content:center;border-radius:50%;z-index:var(--sv-z-indicator,3);opacity:0;transition:opacity 200ms ease-out;pointer-events:none}.sv-video-slot__play-indicator--visible{opacity:.65}.sv-video-slot__play-indicator--persist{opacity:.65}.sv-video-slot__play-indicator--animating-out{animation:sv-slot-indicator-out var(--sv-indicator-duration,200ms)ease-out forwards}@keyframes sv-slot-indicator-out{0%{opacity:1;transform:translate(-50%,-50%)scale(1)}100%{opacity:0;transform:translate(-50%,-50%)scale(1.5)}}.sv-video-slot__play-indicator-icon{color:#fff;font-size:28px;width:100%;height:100%;display:flex;align-items:center;justify-content:center}.sv-video-slot__progress{position:absolute;bottom:0;left:0;right:0;height:3px;background-color:rgba(255,255,255,.2);z-index:var(--sv-z-progress,4)}.sv-video-slot__progress-bar{height:100%;background-color:var(--sv-color-primary,#fe2c55);transition:width 100ms linear}.sv-video-slot__progress-buffered{position:absolute;top:0;left:0;height:100%;background-color:rgba(255,255,255,.3)}`;
10
10
 
11
+ // src/components/VideoSlot/SpeedBoostIndicator.css.ts
12
+ var SPEED_BOOST_INDICATOR_CSS = `.sv-speed-boost{position:absolute;top:calc(var(--sv-safe-area-top,0)+60px);left:50%;transform:translateX(-50%);z-index:20;display:flex;align-items:center;gap:6px;padding:6px 14px;border-radius:20px;background:rgba(0,0,0,.65);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);color:#fff;font-size:13px;font-weight:600;letter-spacing:.02em;white-space:nowrap;pointer-events:none;user-select:none;animation:sv-speed-boost-in 200ms ease-out both}.sv-speed-boost--locked{background:rgba(0,0,0,.75);border:1px solid rgba(255,255,255,.15)}.sv-speed-boost--exit{animation:sv-speed-boost-out 200ms ease-in forwards}.sv-speed-boost__icon{display:flex;align-items:center;font-size:14px;animation:sv-speed-boost-pulse 600ms ease-in-out infinite alternate}.sv-speed-boost--locked .sv-speed-boost__icon{animation:none}.sv-speed-boost__lock{display:flex;align-items:center;font-size:11px;margin-left:2px}.sv-speed-boost__hint{font-size:11px;font-weight:normal;opacity:.7;margin-left:4px}@keyframes sv-speed-boost-in{0%{opacity:0;transform:translateX(-50%)scale(.8)translateY(-8px)}100%{opacity:1;transform:translateX(-50%)scale(1)translateY(0)}}@keyframes sv-speed-boost-out{0%{opacity:1;transform:translateX(-50%)scale(1)translateY(0)}100%{opacity:0;transform:translateX(-50%)scale(.9)translateY(-4px)}}@keyframes sv-speed-boost-pulse{0%{opacity:.7}100%{opacity:1}}`;
13
+ function SpeedBoostIndicatorBase({
14
+ isVisible,
15
+ isLocked,
16
+ speed = 2,
17
+ showHint = true
18
+ }) {
19
+ useEffect(() => {
20
+ return injectComponentCSS("speed-boost-indicator", SPEED_BOOST_INDICATOR_CSS);
21
+ }, []);
22
+ const [shouldRender, setShouldRender] = useState(false);
23
+ const [isExiting, setIsExiting] = useState(false);
24
+ const exitTimerRef = useRef(null);
25
+ useEffect(() => {
26
+ if (isVisible) {
27
+ setIsExiting(false);
28
+ setShouldRender(true);
29
+ if (exitTimerRef.current) {
30
+ clearTimeout(exitTimerRef.current);
31
+ exitTimerRef.current = null;
32
+ }
33
+ } else if (shouldRender && !isLocked) {
34
+ setIsExiting(true);
35
+ exitTimerRef.current = setTimeout(() => {
36
+ setShouldRender(false);
37
+ setIsExiting(false);
38
+ exitTimerRef.current = null;
39
+ }, 200);
40
+ } else if (!isVisible && !isLocked) {
41
+ setShouldRender(false);
42
+ setIsExiting(false);
43
+ }
44
+ return () => {
45
+ if (exitTimerRef.current) {
46
+ clearTimeout(exitTimerRef.current);
47
+ }
48
+ };
49
+ }, [isVisible, isLocked, shouldRender]);
50
+ const isVisibleOrLocked = shouldRender || isLocked;
51
+ if (!isVisibleOrLocked) return null;
52
+ const classNames = [
53
+ "sv-speed-boost",
54
+ isLocked && "sv-speed-boost--locked",
55
+ isExiting && !isLocked && "sv-speed-boost--exit"
56
+ ].filter(Boolean).join(" ");
57
+ return /* @__PURE__ */ jsxs("div", { className: classNames, "aria-live": "polite", role: "status", children: [
58
+ /* @__PURE__ */ jsx("span", { className: "sv-speed-boost__icon", "aria-hidden": "true", children: "\u25B6\u25B6" }),
59
+ /* @__PURE__ */ jsxs("span", { children: [
60
+ speed,
61
+ "x"
62
+ ] }),
63
+ isLocked && /* @__PURE__ */ jsx("span", { className: "sv-speed-boost__lock", "aria-label": "Speed locked", children: "locked" }),
64
+ showHint && isVisible && !isLocked && /* @__PURE__ */ jsx("span", { className: "sv-speed-boost__hint", children: "\u2193 pull down to lock" })
65
+ ] });
66
+ }
67
+ var SpeedBoostIndicator = memo(SpeedBoostIndicatorBase);
68
+ SpeedBoostIndicator.displayName = "SpeedBoostIndicator";
69
+
11
70
  // src/components/VideoSlot/constants.ts
12
71
  var VIDEO_ID_ATTR = "data-video-id";
13
72
  var VIDEO_ID_DATASET_KEY = "videoId";
@@ -64,7 +123,11 @@ function VideoSlotHeadlessBase({
64
123
  renderError,
65
124
  renderLoading,
66
125
  restoreFrame,
67
- style
126
+ onSpeedBoost,
127
+ onSpeedReset,
128
+ onSpeedLock,
129
+ style,
130
+ disableSpeedBoost
68
131
  }) {
69
132
  useInsertionEffect(() => {
70
133
  return injectComponentCSS("video-slot", VIDEO_SLOT_CSS);
@@ -136,6 +199,33 @@ function VideoSlotHeadlessBase({
136
199
  onDoubleTap: handleDoubleTap,
137
200
  onLongPress: onLongPress ? handleLongPress : void 0
138
201
  });
202
+ const { handlers: speedHandlers, isSpeedBoosted, isSpeedLocked } = useLongPressSpeed({
203
+ onSpeedBoost,
204
+ onSpeedReset,
205
+ onSpeedLock,
206
+ disabled: disableSpeedBoost || !onSpeedBoost
207
+ });
208
+ const mergedHandlers = useMemo(() => {
209
+ if (disableSpeedBoost || !onSpeedBoost) {
210
+ return tapHandlers;
211
+ }
212
+ return {
213
+ onPointerDown: (e) => {
214
+ tapHandlers.onPointerDown(e);
215
+ speedHandlers.onPointerDown(e);
216
+ },
217
+ onPointerMove: (e) => {
218
+ tapHandlers.onPointerMove(e);
219
+ speedHandlers.onPointerMove(e);
220
+ },
221
+ onPointerUp: (e) => {
222
+ tapHandlers.onPointerUp(e);
223
+ speedHandlers.onPointerUp(e);
224
+ },
225
+ onClick: tapHandlers.onClick,
226
+ onPointerCancel: speedHandlers.onPointerCancel
227
+ };
228
+ }, [tapHandlers, speedHandlers, disableSpeedBoost, onSpeedBoost]);
139
229
  const handleRetry = useCallback(() => {
140
230
  playerControls.play();
141
231
  }, [playerControls]);
@@ -181,7 +271,7 @@ function VideoSlotHeadlessBase({
181
271
  {
182
272
  ref: containerRef,
183
273
  className: clsx2(...stateClasses, className),
184
- ...tapHandlers,
274
+ ...mergedHandlers,
185
275
  ...{
186
276
  [VIDEO_ID_ATTR]: video.id,
187
277
  [SLOT_ACTIVE_ATTR]: resourceState.isActive ? "true" : "false",
@@ -208,6 +298,13 @@ function VideoSlotHeadlessBase({
208
298
  "aria-hidden": "true"
209
299
  }
210
300
  ),
301
+ onSpeedBoost && /* @__PURE__ */ jsx(
302
+ SpeedBoostIndicator,
303
+ {
304
+ isVisible: isSpeedBoosted,
305
+ isLocked: isSpeedLocked
306
+ }
307
+ ),
211
308
  playerState.error && (renderError ? renderError(playerState.error, handleRetry) : /* @__PURE__ */ jsx(DefaultErrorUI, { error: playerState.error, onRetry: handleRetry })),
212
309
  playerState.isLoading && !playerState.error && !restoreFrame && (renderLoading ? renderLoading() : /* @__PURE__ */ jsx(DefaultLoadingUI, {})),
213
310
  children
@@ -1,4 +1,4 @@
1
- import { useDoubleTap, VideoSlotPlayIndicatorInner } from './chunk-VS54DRIS.js';
1
+ import { useDoubleTap, VideoSlotPlayIndicatorInner } from './chunk-3LINB7GV.js';
2
2
  import { ImageCarouselHeadless } from './chunk-GSNIZ6DF.js';
3
3
  import { clsx2 } from './chunk-EDWS2IPH.js';
4
4
  import { injectComponentCSS } from './chunk-CAWE42LH.js';
@@ -1 +1 @@
1
- export { ARTICLE_ACTIVE_ATTR, ARTICLE_ID_ATTR, ARTICLE_SLOT_ACTIVE_CLASS, ARTICLE_SLOT_CLASS, ARTICLE_SLOT_CLEAN_MODE_CLASS, ARTICLE_SLOT_CSS, ArticleSlotActions, ArticleSlotBottom, ArticleSlotCaption, ArticleSlotHeadless, ArticleSlotMusic, ArticleSlotOverlay, ArticleSlotPlayIndicator, ArticleSlotReadMore, ARTICLE_ACTIVE_ATTR as IMAGE_POST_ACTIVE_ATTR, ARTICLE_ID_ATTR as IMAGE_POST_ID_ATTR, ARTICLE_SLOT_ACTIVE_CLASS as IMAGE_POST_SLOT_ACTIVE_CLASS, ARTICLE_SLOT_CLASS as IMAGE_POST_SLOT_CLASS, ARTICLE_SLOT_CLEAN_MODE_CLASS as IMAGE_POST_SLOT_CLEAN_MODE_CLASS, ARTICLE_SLOT_CSS as IMAGE_POST_SLOT_CSS, ArticleSlotActions as ImagePostSlotActions, ArticleSlotBottom as ImagePostSlotBottom, ArticleSlotCaption as ImagePostSlotCaption, ArticleSlotHeadless as ImagePostSlotHeadless, ArticleSlotMusic as ImagePostSlotMusic, ArticleSlotOverlay as ImagePostSlotOverlay, ArticleSlotPlayIndicator as ImagePostSlotPlayIndicator, ArticleSlotReadMore as ImagePostSlotReadMore, useArticleSlotContext, useArticleSlotContext as useImagePostSlotContext, useOptionalArticleSlotContext, useOptionalArticleSlotContext as useOptionalImagePostSlotContext } from '../../chunk-TX3D3C2M.js';
1
+ export { ARTICLE_ACTIVE_ATTR, ARTICLE_ID_ATTR, ARTICLE_SLOT_ACTIVE_CLASS, ARTICLE_SLOT_CLASS, ARTICLE_SLOT_CLEAN_MODE_CLASS, ARTICLE_SLOT_CSS, ArticleSlotActions, ArticleSlotBottom, ArticleSlotCaption, ArticleSlotHeadless, ArticleSlotMusic, ArticleSlotOverlay, ArticleSlotPlayIndicator, ArticleSlotReadMore, ARTICLE_ACTIVE_ATTR as IMAGE_POST_ACTIVE_ATTR, ARTICLE_ID_ATTR as IMAGE_POST_ID_ATTR, ARTICLE_SLOT_ACTIVE_CLASS as IMAGE_POST_SLOT_ACTIVE_CLASS, ARTICLE_SLOT_CLASS as IMAGE_POST_SLOT_CLASS, ARTICLE_SLOT_CLEAN_MODE_CLASS as IMAGE_POST_SLOT_CLEAN_MODE_CLASS, ARTICLE_SLOT_CSS as IMAGE_POST_SLOT_CSS, ArticleSlotActions as ImagePostSlotActions, ArticleSlotBottom as ImagePostSlotBottom, ArticleSlotCaption as ImagePostSlotCaption, ArticleSlotHeadless as ImagePostSlotHeadless, ArticleSlotMusic as ImagePostSlotMusic, ArticleSlotOverlay as ImagePostSlotOverlay, ArticleSlotPlayIndicator as ImagePostSlotPlayIndicator, ArticleSlotReadMore as ImagePostSlotReadMore, useArticleSlotContext, useArticleSlotContext as useImagePostSlotContext, useOptionalArticleSlotContext, useOptionalArticleSlotContext as useOptionalImagePostSlotContext } from '../../chunk-OTCSGHTG.js';
@@ -27,6 +27,12 @@ interface VideoSlotHeadlessExtendedProps extends VideoSlotHeadlessProps {
27
27
  loopCount?: number;
28
28
  /** Custom styles */
29
29
  style?: React.CSSProperties;
30
+ /**
31
+ * Disable long-press speed boost gesture.
32
+ * When true, holding won't trigger 2x speed.
33
+ * @default false
34
+ */
35
+ disableSpeedBoost?: boolean;
30
36
  }
31
37
  /**
32
38
  * VideoSlotHeadless Component
@@ -59,7 +65,7 @@ interface VideoSlotHeadlessExtendedProps extends VideoSlotHeadlessProps {
59
65
  *
60
66
  * Wrapped with React.memo to prevent unnecessary re-renders.
61
67
  */
62
- declare function VideoSlotHeadlessBase({ video, resourceState, playerState, playerControls, className, children, onVisible: _onVisible, onHidden: _onHidden, onTap, onDoubleTap, onLongPress, disableTap, renderError, renderLoading, restoreFrame, style, }: VideoSlotHeadlessExtendedProps): React.ReactElement;
68
+ declare function VideoSlotHeadlessBase({ video, resourceState, playerState, playerControls, className, children, onVisible: _onVisible, onHidden: _onHidden, onTap, onDoubleTap, onLongPress, disableTap, renderError, renderLoading, restoreFrame, onSpeedBoost, onSpeedReset, onSpeedLock, style, disableSpeedBoost, }: VideoSlotHeadlessExtendedProps): React.ReactElement;
63
69
  /**
64
70
  * VideoSlotHeadless - Memoized for performance
65
71
  *
@@ -1,2 +1,2 @@
1
- export { LOADING_ATTR, LOADING_DATASET_KEY, PLAYER_STATE_ATTR, PLAYER_STATE_DATASET_KEY, ReportedVideoOverlay, SLOT_ACTIVE_ATTR, SLOT_ACTIVE_CLASS, SLOT_ACTIVE_DATASET_KEY, SLOT_CLASS, SLOT_ERROR_CLASS, SLOT_LOADING_CLASS, SLOT_PAUSED_CLASS, SLOT_PLAYING_CLASS, VIDEO_ID_ATTR, VIDEO_ID_DATASET_KEY, VIDEO_SLOT_CSS, VIDEO_SLOT_LIKE_ANIMATION_CSS, VideoSlotHeadless, VideoSlotLikeAnimation, VideoSlotOverlay, VideoSlotPoster, VideoSlotSkeleton, Z_INDEX, Z_INDEX_CSS_VARS } from '../../chunk-6JOXPX7S.js';
2
- export { VideoSlotContext, VideoSlotPlayIndicator, VideoSlotPlayIndicatorInner, useOptionalVideoSlotContext, useVideoSlotContext, useVideoSlotIsActive, useVideoSlotIsPlaying, useVideoSlotPlayer, useVideoSlotResource, useVideoSlotVideo } from '../../chunk-VS54DRIS.js';
1
+ export { LOADING_ATTR, LOADING_DATASET_KEY, PLAYER_STATE_ATTR, PLAYER_STATE_DATASET_KEY, ReportedVideoOverlay, SLOT_ACTIVE_ATTR, SLOT_ACTIVE_CLASS, SLOT_ACTIVE_DATASET_KEY, SLOT_CLASS, SLOT_ERROR_CLASS, SLOT_LOADING_CLASS, SLOT_PAUSED_CLASS, SLOT_PLAYING_CLASS, VIDEO_ID_ATTR, VIDEO_ID_DATASET_KEY, VIDEO_SLOT_CSS, VIDEO_SLOT_LIKE_ANIMATION_CSS, VideoSlotHeadless, VideoSlotLikeAnimation, VideoSlotOverlay, VideoSlotPoster, VideoSlotSkeleton, Z_INDEX, Z_INDEX_CSS_VARS } from '../../chunk-GRO2POPB.js';
2
+ export { VideoSlotContext, VideoSlotPlayIndicator, VideoSlotPlayIndicatorInner, useOptionalVideoSlotContext, useVideoSlotContext, useVideoSlotIsActive, useVideoSlotIsPlaying, useVideoSlotPlayer, useVideoSlotResource, useVideoSlotVideo } from '../../chunk-3LINB7GV.js';
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ export { Skeleton, SkeletonAvatar, SkeletonText, SkeletonVideo } from './chunk-4
2
2
  export { DEFAULT_SPEEDS, SPEED_PICKER_CSS, SpeedPickerHeadless } from './chunk-QCRRF76W.js';
3
3
  export { VIDEO_INFO_CSS, VideoAuthorName, VideoCaption, VideoHashtags, VideoInfoContext, VideoInfoHeadless, VideoLocation, VideoMusic, useOptionalVideoInfoContext, useVideoInfoContext } from './chunk-7WXAQHJI.js';
4
4
  export { BUFFERING_STATE_CLASS, ENDED_CLASS, ERROR_CLASS, ERROR_STATE_CLASS, LOADING_CLASS, LOADING_STATE_ATTR, LOADING_STATE_CLASS, PAUSED_CLASS, PLAYBACK_STATE_ATTR, PLAYER_CLASS, PLAYING_CLASS, POSTER_CLASS, READY_CLASS, VIDEO_CLASS, VIDEO_PLAYER_CSS, Z_INDEX as VIDEO_PLAYER_Z_INDEX, Z_INDEX_CSS_VARS as VIDEO_PLAYER_Z_INDEX_CSS_VARS, VIDEO_TYPE_ATTR, VIDEO_WRAPPER_CLASS, VideoElementError, VideoPlayerHeadless, createFirstFrameCache, firstFrameCache, useAutoFirstFrameCapture, useFirstFrameCapture, useVideoElement } from './chunk-OM4L7RE5.js';
5
- export { LOADING_ATTR, LOADING_DATASET_KEY, PLAYER_STATE_ATTR, PLAYER_STATE_DATASET_KEY, ReportedVideoOverlay, SLOT_CLASS, SLOT_ERROR_CLASS, SLOT_LOADING_CLASS, SLOT_PAUSED_CLASS, SLOT_PLAYING_CLASS, VIDEO_ID_ATTR, VIDEO_ID_DATASET_KEY, VIDEO_SLOT_CSS, VIDEO_SLOT_LIKE_ANIMATION_CSS, VideoSlotHeadless, VideoSlotLikeAnimation, VideoSlotOverlay, VideoSlotPoster, VideoSlotSkeleton, Z_INDEX, Z_INDEX_CSS_VARS } from './chunk-6JOXPX7S.js';
5
+ export { LOADING_ATTR, LOADING_DATASET_KEY, PLAYER_STATE_ATTR, PLAYER_STATE_DATASET_KEY, ReportedVideoOverlay, SLOT_CLASS, SLOT_ERROR_CLASS, SLOT_LOADING_CLASS, SLOT_PAUSED_CLASS, SLOT_PLAYING_CLASS, VIDEO_ID_ATTR, VIDEO_ID_DATASET_KEY, VIDEO_SLOT_CSS, VIDEO_SLOT_LIKE_ANIMATION_CSS, VideoSlotHeadless, VideoSlotLikeAnimation, VideoSlotOverlay, VideoSlotPoster, VideoSlotSkeleton, Z_INDEX, Z_INDEX_CSS_VARS } from './chunk-GRO2POPB.js';
6
6
  export { ZOOMABLE_CONTAINER_CSS, ZoomableContainer } from './chunk-TJCPW4AO.js';
7
7
  export { DETAIL_VIEW_CSS, DetailViewAuthor, DetailViewCaption, DetailViewGallery, DetailViewHeadless, DetailViewMusic, DetailViewStats, useDetailViewContext } from './chunk-BADA7OLG.js';
8
8
  export { ErrorBoundary } from './chunk-IWSBYOSS.js';
@@ -14,8 +14,8 @@ export { QUALITY_PICKER_CSS, QualityPickerHeadless } from './chunk-DR7KR7OT.js';
14
14
  export { REPORT_SHEET_CSS, ReportSheetHeadless, injectReportSheetCSS } from './chunk-DGKMO3AE.js';
15
15
  export { ACTION_BAR_CSS, CompoundBookmark as ActionBarBookmark, CompoundComment as ActionBarComment, ActionBarHeadless, CompoundLike as ActionBarLike, CompoundShare as ActionBarShare, ActionButton, BookmarkButton, CommentButton, LikeButton, ShareButton } from './chunk-NJXIYSDZ.js';
16
16
  export { ADVANCE_MENU_CSS, AdvanceMenuHeadless } from './chunk-HXQPEZRG.js';
17
- export { ARTICLE_ACTIVE_ATTR, ARTICLE_ID_ATTR, ARTICLE_SLOT_ACTIVE_CLASS, ARTICLE_SLOT_CLASS, ARTICLE_SLOT_CLEAN_MODE_CLASS, ARTICLE_SLOT_CSS, ArticleSlotActions, ArticleSlotBottom, ArticleSlotCaption, ArticleSlotHeadless, ArticleSlotMusic, ArticleSlotOverlay, ArticleSlotPlayIndicator, ArticleSlotReadMore, ARTICLE_ACTIVE_ATTR as IMAGE_POST_ACTIVE_ATTR, ARTICLE_ID_ATTR as IMAGE_POST_ID_ATTR, ARTICLE_SLOT_ACTIVE_CLASS as IMAGE_POST_SLOT_ACTIVE_CLASS, ARTICLE_SLOT_CLASS as IMAGE_POST_SLOT_CLASS, ARTICLE_SLOT_CLEAN_MODE_CLASS as IMAGE_POST_SLOT_CLEAN_MODE_CLASS, ARTICLE_SLOT_CSS as IMAGE_POST_SLOT_CSS, ArticleSlotActions as ImagePostSlotActions, ArticleSlotBottom as ImagePostSlotBottom, ArticleSlotCaption as ImagePostSlotCaption, ArticleSlotHeadless as ImagePostSlotHeadless, ArticleSlotMusic as ImagePostSlotMusic, ArticleSlotOverlay as ImagePostSlotOverlay, ArticleSlotPlayIndicator as ImagePostSlotPlayIndicator, ArticleSlotReadMore as ImagePostSlotReadMore, useArticleSlotContext, useArticleSlotContext as useImagePostSlotContext, useOptionalArticleSlotContext, useOptionalArticleSlotContext as useOptionalImagePostSlotContext } from './chunk-TX3D3C2M.js';
18
- export { VideoSlotContext, VideoSlotPlayIndicator, useDoubleTap, useOptionalVideoSlotContext, useVideoSlotContext, useVideoSlotIsActive, useVideoSlotIsPlaying, useVideoSlotPlayer, useVideoSlotResource, useVideoSlotVideo } from './chunk-VS54DRIS.js';
17
+ export { ARTICLE_ACTIVE_ATTR, ARTICLE_ID_ATTR, ARTICLE_SLOT_ACTIVE_CLASS, ARTICLE_SLOT_CLASS, ARTICLE_SLOT_CLEAN_MODE_CLASS, ARTICLE_SLOT_CSS, ArticleSlotActions, ArticleSlotBottom, ArticleSlotCaption, ArticleSlotHeadless, ArticleSlotMusic, ArticleSlotOverlay, ArticleSlotPlayIndicator, ArticleSlotReadMore, ARTICLE_ACTIVE_ATTR as IMAGE_POST_ACTIVE_ATTR, ARTICLE_ID_ATTR as IMAGE_POST_ID_ATTR, ARTICLE_SLOT_ACTIVE_CLASS as IMAGE_POST_SLOT_ACTIVE_CLASS, ARTICLE_SLOT_CLASS as IMAGE_POST_SLOT_CLASS, ARTICLE_SLOT_CLEAN_MODE_CLASS as IMAGE_POST_SLOT_CLEAN_MODE_CLASS, ARTICLE_SLOT_CSS as IMAGE_POST_SLOT_CSS, ArticleSlotActions as ImagePostSlotActions, ArticleSlotBottom as ImagePostSlotBottom, ArticleSlotCaption as ImagePostSlotCaption, ArticleSlotHeadless as ImagePostSlotHeadless, ArticleSlotMusic as ImagePostSlotMusic, ArticleSlotOverlay as ImagePostSlotOverlay, ArticleSlotPlayIndicator as ImagePostSlotPlayIndicator, ArticleSlotReadMore as ImagePostSlotReadMore, useArticleSlotContext, useArticleSlotContext as useImagePostSlotContext, useOptionalArticleSlotContext, useOptionalArticleSlotContext as useOptionalImagePostSlotContext } from './chunk-OTCSGHTG.js';
18
+ export { VideoSlotContext, VideoSlotPlayIndicator, useDoubleTap, useOptionalVideoSlotContext, useVideoSlotContext, useVideoSlotIsActive, useVideoSlotIsPlaying, useVideoSlotPlayer, useVideoSlotResource, useVideoSlotVideo } from './chunk-3LINB7GV.js';
19
19
  export { usePinchZoom } from './chunk-UTLVQ3FL.js';
20
20
  export { IMAGE_CAROUSEL_CSS, ImageCarouselHeadless } from './chunk-GSNIZ6DF.js';
21
21
  export { AUTHOR_INFO_CSS, AuthorAvatar, AuthorDescription, AuthorInfoContext, AuthorInfoHeadless, AuthorName, FollowButton, useAuthorInfoContext, useOptionalAuthorInfoContext } from './chunk-3OB3OVYR.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xhub-short/ui",
3
- "version": "1.0.0-beta.21",
3
+ "version": "1.0.0-beta.22",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -73,7 +73,7 @@
73
73
  ],
74
74
  "dependencies": {
75
75
  "clsx": "^2.1.0",
76
- "@xhub-short/contracts": "1.0.0-beta.21"
76
+ "@xhub-short/contracts": "1.0.0-beta.22"
77
77
  },
78
78
  "peerDependencies": {
79
79
  "react": "^19.0.0",
@@ -97,8 +97,8 @@
97
97
  "tsup": "^8.3.0",
98
98
  "typescript": "^5.7.0",
99
99
  "vitest": "^2.1.0",
100
- "@xhub-short/tsconfig": "0.0.1-beta.2",
101
- "@xhub-short/vitest-config": "0.1.0-beta.13"
100
+ "@xhub-short/tsconfig": "0.1.0-beta.3",
101
+ "@xhub-short/vitest-config": "0.1.0-beta.14"
102
102
  },
103
103
  "scripts": {
104
104
  "build": "tsup",