@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.
- package/dist/{chunk-VS54DRIS.js → chunk-3LINB7GV.js} +131 -1
- package/dist/{chunk-6JOXPX7S.js → chunk-GRO2POPB.js} +101 -4
- package/dist/{chunk-TX3D3C2M.js → chunk-OTCSGHTG.js} +1 -1
- package/dist/components/ArticleSlot/index.js +1 -1
- package/dist/components/VideoSlot/index.d.ts +7 -1
- package/dist/components/VideoSlot/index.js +2 -2
- package/dist/index.js +3 -3
- package/package.json +4 -4
|
@@ -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-
|
|
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,
|
|
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
|
-
|
|
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
|
-
...
|
|
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-
|
|
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-
|
|
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-
|
|
2
|
-
export { VideoSlotContext, VideoSlotPlayIndicator, VideoSlotPlayIndicatorInner, useOptionalVideoSlotContext, useVideoSlotContext, useVideoSlotIsActive, useVideoSlotIsPlaying, useVideoSlotPlayer, useVideoSlotResource, useVideoSlotVideo } from '../../chunk-
|
|
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-
|
|
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-
|
|
18
|
-
export { VideoSlotContext, VideoSlotPlayIndicator, useDoubleTap, useOptionalVideoSlotContext, useVideoSlotContext, useVideoSlotIsActive, useVideoSlotIsPlaying, useVideoSlotPlayer, useVideoSlotResource, useVideoSlotVideo } from './chunk-
|
|
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.
|
|
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.
|
|
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
|
|
101
|
-
"@xhub-short/vitest-config": "0.1.0-beta.
|
|
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",
|