@hyperframes/studio 0.4.33 → 0.4.35
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/assets/index-Bj3m6A02.js +93 -0
- package/dist/assets/index-_h8opaGY.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/captions/components/CaptionOverlay.tsx +2 -1
- package/src/captions/keyboard.test.ts +38 -0
- package/src/captions/keyboard.ts +8 -0
- package/src/components/nle/NLEPreview.tsx +5 -1
- package/src/player/components/PlayerControls.tsx +120 -11
- package/src/player/components/Timeline.test.ts +71 -0
- package/src/player/components/Timeline.tsx +146 -9
- package/src/player/components/timelineZoom.test.ts +21 -0
- package/src/player/components/timelineZoom.ts +11 -0
- package/src/player/hooks/useTimelinePlayer.test.ts +79 -0
- package/src/player/hooks/useTimelinePlayer.ts +270 -18
- package/src/player/lib/time.test.ts +19 -1
- package/src/player/lib/time.ts +20 -0
- package/src/player/store/playerStore.test.ts +11 -1
- package/src/player/store/playerStore.ts +5 -1
- package/dist/assets/index-DeztUnf4.css +0 -1
- package/dist/assets/index-DxwbBcYY.js +0 -93
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
type TimelineTrackStyle,
|
|
27
27
|
type TimelineTheme,
|
|
28
28
|
} from "./timelineTheme";
|
|
29
|
-
import { getTimelinePixelsPerSecond } from "./timelineZoom";
|
|
29
|
+
import { getPinchTimelineZoomPercent, getTimelinePixelsPerSecond } from "./timelineZoom";
|
|
30
30
|
import { TIMELINE_ASSET_MIME } from "../../utils/timelineAssetDrop";
|
|
31
31
|
|
|
32
32
|
/* ── Layout ─────────────────────────────────────────────────────── */
|
|
@@ -88,16 +88,47 @@ function getStyle(tag: string): TrackVisualStyle {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/* ── Tick Generation ────────────────────────────────────────────── */
|
|
91
|
-
|
|
91
|
+
function getMajorTickInterval(duration: number, pixelsPerSecond?: number): number {
|
|
92
|
+
const zoomIntervals = [0.25, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600];
|
|
93
|
+
if (Number.isFinite(pixelsPerSecond) && (pixelsPerSecond ?? 0) > 0) {
|
|
94
|
+
const targetMajorPx = 128;
|
|
95
|
+
return (
|
|
96
|
+
zoomIntervals.find((interval) => interval * (pixelsPerSecond ?? 0) >= targetMajorPx) ?? 600
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const durationIntervals = [0.25, 0.5, 1, 2, 5, 10, 15, 30, 60];
|
|
100
|
+
const target = duration / 6;
|
|
101
|
+
return durationIntervals.find((interval) => interval >= target) ?? 60;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getMinorTickInterval(majorInterval: number, pixelsPerSecond?: number): number {
|
|
105
|
+
let interval = majorInterval / 2;
|
|
106
|
+
if (majorInterval >= 30) interval = majorInterval / 6;
|
|
107
|
+
else if (majorInterval >= 15) interval = majorInterval / 3;
|
|
108
|
+
else if (majorInterval >= 5) interval = majorInterval / 5;
|
|
109
|
+
else if (majorInterval >= 1) interval = majorInterval / 4;
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
Number.isFinite(pixelsPerSecond) &&
|
|
113
|
+
(pixelsPerSecond ?? 0) > 0 &&
|
|
114
|
+
interval * (pixelsPerSecond ?? 0) < 20
|
|
115
|
+
) {
|
|
116
|
+
return Math.max(0.25, majorInterval / 2);
|
|
117
|
+
}
|
|
118
|
+
return Math.max(0.25, interval);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function generateTicks(
|
|
122
|
+
duration: number,
|
|
123
|
+
pixelsPerSecond?: number,
|
|
124
|
+
): { major: number[]; minor: number[] } {
|
|
92
125
|
if (duration <= 0 || !Number.isFinite(duration) || duration > 7200)
|
|
93
126
|
return { major: [], minor: [] };
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
const majorInterval = intervals.find((i) => i >= target) ?? 60;
|
|
97
|
-
const minorInterval = Math.max(0.25, majorInterval / 2);
|
|
127
|
+
const majorInterval = getMajorTickInterval(duration, pixelsPerSecond);
|
|
128
|
+
const minorInterval = getMinorTickInterval(majorInterval, pixelsPerSecond);
|
|
98
129
|
const major: number[] = [];
|
|
99
130
|
const minor: number[] = [];
|
|
100
|
-
const maxTicks =
|
|
131
|
+
const maxTicks = 2000; // Safety cap to prevent runaway tick generation
|
|
101
132
|
for (
|
|
102
133
|
let t = 0;
|
|
103
134
|
t <= duration + 0.001 && major.length + minor.length < maxTicks;
|
|
@@ -113,6 +144,25 @@ export function generateTicks(duration: number): { major: number[]; minor: numbe
|
|
|
113
144
|
return { major, minor };
|
|
114
145
|
}
|
|
115
146
|
|
|
147
|
+
export function formatTimelineTickLabel(time: number, duration: number, majorInterval: number) {
|
|
148
|
+
if (!Number.isFinite(time)) return "0:00";
|
|
149
|
+
const safeTime = Math.max(0, time);
|
|
150
|
+
if (majorInterval < 1) {
|
|
151
|
+
const totalTenths = Math.round(safeTime * 10);
|
|
152
|
+
const wholeSeconds = Math.floor(totalTenths / 10);
|
|
153
|
+
const tenth = totalTenths % 10;
|
|
154
|
+
return `${formatTime(wholeSeconds)}.${tenth}`;
|
|
155
|
+
}
|
|
156
|
+
if (duration >= 3600 || safeTime >= 3600) {
|
|
157
|
+
const totalSeconds = Math.floor(safeTime);
|
|
158
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
159
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
160
|
+
const seconds = totalSeconds % 60;
|
|
161
|
+
return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
|
|
162
|
+
}
|
|
163
|
+
return formatTime(safeTime);
|
|
164
|
+
}
|
|
165
|
+
|
|
116
166
|
export function shouldAutoScrollTimeline(
|
|
117
167
|
zoomMode: ZoomMode,
|
|
118
168
|
scrollWidth: number,
|
|
@@ -132,6 +182,31 @@ export function getTimelineScrollLeftForZoomTransition(
|
|
|
132
182
|
return currentScrollLeft;
|
|
133
183
|
}
|
|
134
184
|
|
|
185
|
+
export function getTimelineScrollLeftForZoomAnchor(input: {
|
|
186
|
+
pointerX: number;
|
|
187
|
+
currentScrollLeft: number;
|
|
188
|
+
gutter: number;
|
|
189
|
+
currentPixelsPerSecond: number;
|
|
190
|
+
nextPixelsPerSecond: number;
|
|
191
|
+
duration: number;
|
|
192
|
+
}): number {
|
|
193
|
+
const currentPps = Math.max(0, input.currentPixelsPerSecond);
|
|
194
|
+
const nextPps = Math.max(0, input.nextPixelsPerSecond);
|
|
195
|
+
if (
|
|
196
|
+
!Number.isFinite(input.pointerX) ||
|
|
197
|
+
!Number.isFinite(input.currentScrollLeft) ||
|
|
198
|
+
!Number.isFinite(input.duration) ||
|
|
199
|
+
input.duration <= 0 ||
|
|
200
|
+
currentPps <= 0 ||
|
|
201
|
+
nextPps <= 0
|
|
202
|
+
) {
|
|
203
|
+
return Math.max(0, input.currentScrollLeft);
|
|
204
|
+
}
|
|
205
|
+
const timelineX = Math.max(0, input.currentScrollLeft + input.pointerX - input.gutter);
|
|
206
|
+
const timeAtPointer = Math.max(0, Math.min(input.duration, timelineX / currentPps));
|
|
207
|
+
return Math.max(0, input.gutter + timeAtPointer * nextPps - input.pointerX);
|
|
208
|
+
}
|
|
209
|
+
|
|
135
210
|
export function getTimelinePlayheadLeft(time: number, pixelsPerSecond: number): number {
|
|
136
211
|
if (!Number.isFinite(time) || !Number.isFinite(pixelsPerSecond)) return GUTTER;
|
|
137
212
|
return GUTTER + Math.max(0, time) * Math.max(0, pixelsPerSecond);
|
|
@@ -306,6 +381,8 @@ export const Timeline = memo(function Timeline({
|
|
|
306
381
|
const currentTime = usePlayerStore((s) => s.currentTime);
|
|
307
382
|
const zoomMode = usePlayerStore((s) => s.zoomMode);
|
|
308
383
|
const manualZoomPercent = usePlayerStore((s) => s.manualZoomPercent);
|
|
384
|
+
const setZoomMode = usePlayerStore((s) => s.setZoomMode);
|
|
385
|
+
const setManualZoomPercent = usePlayerStore((s) => s.setManualZoomPercent);
|
|
309
386
|
const playheadRef = useRef<HTMLDivElement>(null);
|
|
310
387
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
311
388
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
@@ -435,7 +512,11 @@ export const Timeline = memo(function Timeline({
|
|
|
435
512
|
const trackContentWidth = Math.max(0, effectiveDuration * pps);
|
|
436
513
|
const zoomModeRef = useRef(zoomMode);
|
|
437
514
|
zoomModeRef.current = zoomMode;
|
|
515
|
+
const manualZoomPercentRef = useRef(manualZoomPercent);
|
|
516
|
+
manualZoomPercentRef.current = manualZoomPercent;
|
|
438
517
|
const previousZoomModeRef = useRef<ZoomMode | null>(zoomMode);
|
|
518
|
+
const fitPpsRef = useRef(fitPps);
|
|
519
|
+
fitPpsRef.current = fitPps;
|
|
439
520
|
|
|
440
521
|
const durationRef = useRef(effectiveDuration);
|
|
441
522
|
durationRef.current = effectiveDuration;
|
|
@@ -925,7 +1006,12 @@ export const Timeline = memo(function Timeline({
|
|
|
925
1006
|
cancelAnimationFrame(dragScrollRaf.current);
|
|
926
1007
|
}, []);
|
|
927
1008
|
|
|
928
|
-
const { major, minor } = useMemo(
|
|
1009
|
+
const { major, minor } = useMemo(
|
|
1010
|
+
() => generateTicks(effectiveDuration, pps),
|
|
1011
|
+
[effectiveDuration, pps],
|
|
1012
|
+
);
|
|
1013
|
+
const majorTickInterval =
|
|
1014
|
+
major.length >= 2 ? Math.max(0.25, major[1] - major[0]) : effectiveDuration;
|
|
929
1015
|
const getPreviewElement = useCallback(
|
|
930
1016
|
(element: TimelineElement): TimelineElement => {
|
|
931
1017
|
if (resizingClip?.element.id === element.id) {
|
|
@@ -1011,6 +1097,57 @@ export const Timeline = memo(function Timeline({
|
|
|
1011
1097
|
[onAssetDrop, onFileDrop],
|
|
1012
1098
|
);
|
|
1013
1099
|
|
|
1100
|
+
const handlePinchWheel = useCallback(
|
|
1101
|
+
(e: WheelEvent) => {
|
|
1102
|
+
if (!e.ctrlKey) return;
|
|
1103
|
+
const scroll = scrollRef.current;
|
|
1104
|
+
if (!scroll || durationRef.current <= 0 || fitPpsRef.current <= 0 || ppsRef.current <= 0) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
e.preventDefault();
|
|
1109
|
+
e.stopPropagation();
|
|
1110
|
+
|
|
1111
|
+
const rect = scroll.getBoundingClientRect();
|
|
1112
|
+
const pointerX = e.clientX - rect.left;
|
|
1113
|
+
const nextZoomPercent = getPinchTimelineZoomPercent(
|
|
1114
|
+
e.deltaY,
|
|
1115
|
+
zoomModeRef.current,
|
|
1116
|
+
manualZoomPercentRef.current,
|
|
1117
|
+
);
|
|
1118
|
+
if (nextZoomPercent === manualZoomPercentRef.current && zoomModeRef.current === "manual") {
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
const nextPps = fitPpsRef.current * (nextZoomPercent / 100);
|
|
1123
|
+
const nextScrollLeft = getTimelineScrollLeftForZoomAnchor({
|
|
1124
|
+
pointerX,
|
|
1125
|
+
currentScrollLeft: scroll.scrollLeft,
|
|
1126
|
+
gutter: GUTTER,
|
|
1127
|
+
currentPixelsPerSecond: ppsRef.current,
|
|
1128
|
+
nextPixelsPerSecond: nextPps,
|
|
1129
|
+
duration: durationRef.current,
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
setZoomMode("manual");
|
|
1133
|
+
setManualZoomPercent(nextZoomPercent);
|
|
1134
|
+
requestAnimationFrame(() => {
|
|
1135
|
+
const maxScrollLeft = Math.max(0, scroll.scrollWidth - scroll.clientWidth);
|
|
1136
|
+
scroll.scrollLeft = Math.min(maxScrollLeft, nextScrollLeft);
|
|
1137
|
+
});
|
|
1138
|
+
},
|
|
1139
|
+
[setManualZoomPercent, setZoomMode],
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
useEffect(() => {
|
|
1143
|
+
const scroll = scrollRef.current;
|
|
1144
|
+
if (!scroll) return;
|
|
1145
|
+
scroll.addEventListener("wheel", handlePinchWheel, { passive: false, capture: true });
|
|
1146
|
+
return () => {
|
|
1147
|
+
scroll.removeEventListener("wheel", handlePinchWheel, { capture: true });
|
|
1148
|
+
};
|
|
1149
|
+
}, [handlePinchWheel, timelineReady, elements.length]);
|
|
1150
|
+
|
|
1014
1151
|
if (!timelineReady || elements.length === 0) {
|
|
1015
1152
|
return (
|
|
1016
1153
|
<div
|
|
@@ -1242,7 +1379,7 @@ export const Timeline = memo(function Timeline({
|
|
|
1242
1379
|
className="text-[9px] font-mono tabular-nums leading-none mb-0.5"
|
|
1243
1380
|
style={{ color: theme.tickText }}
|
|
1244
1381
|
>
|
|
1245
|
-
{
|
|
1382
|
+
{formatTimelineTickLabel(t, effectiveDuration, majorTickInterval)}
|
|
1246
1383
|
</span>
|
|
1247
1384
|
<div className="w-px h-[5px]" style={{ background: theme.tickMajor }} />
|
|
1248
1385
|
</div>
|
|
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import {
|
|
3
3
|
clampTimelineZoomPercent,
|
|
4
4
|
getNextTimelineZoomPercent,
|
|
5
|
+
getPinchTimelineZoomPercent,
|
|
5
6
|
getTimelinePixelsPerSecond,
|
|
6
7
|
getTimelineZoomPercent,
|
|
7
8
|
MAX_TIMELINE_ZOOM_PERCENT,
|
|
@@ -60,3 +61,23 @@ describe("getNextTimelineZoomPercent", () => {
|
|
|
60
61
|
);
|
|
61
62
|
});
|
|
62
63
|
});
|
|
64
|
+
|
|
65
|
+
describe("getPinchTimelineZoomPercent", () => {
|
|
66
|
+
it("zooms in for upward pinch wheel deltas", () => {
|
|
67
|
+
expect(getPinchTimelineZoomPercent(-80, "fit", 100)).toBeGreaterThan(100);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("zooms out for downward pinch wheel deltas", () => {
|
|
71
|
+
expect(getPinchTimelineZoomPercent(80, "manual", 200)).toBeLessThan(200);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("keeps the current zoom for zero or invalid deltas", () => {
|
|
75
|
+
expect(getPinchTimelineZoomPercent(0, "manual", 180)).toBe(180);
|
|
76
|
+
expect(getPinchTimelineZoomPercent(Number.NaN, "manual", 180)).toBe(180);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("clamps pinch zoom to the supported range", () => {
|
|
80
|
+
expect(getPinchTimelineZoomPercent(10000, "manual", 100)).toBe(MIN_TIMELINE_ZOOM_PERCENT);
|
|
81
|
+
expect(getPinchTimelineZoomPercent(-10000, "manual", 100)).toBe(MAX_TIMELINE_ZOOM_PERCENT);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -4,6 +4,7 @@ export const MIN_TIMELINE_ZOOM_PERCENT = 10;
|
|
|
4
4
|
export const MAX_TIMELINE_ZOOM_PERCENT = 2000;
|
|
5
5
|
const ZOOM_OUT_FACTOR = 0.8;
|
|
6
6
|
const ZOOM_IN_FACTOR = 1.25;
|
|
7
|
+
const PINCH_ZOOM_SENSITIVITY = 0.0035;
|
|
7
8
|
|
|
8
9
|
export function clampTimelineZoomPercent(percent: number): number {
|
|
9
10
|
if (!Number.isFinite(percent)) return 100;
|
|
@@ -36,3 +37,13 @@ export function getNextTimelineZoomPercent(
|
|
|
36
37
|
const next = direction === "in" ? current * ZOOM_IN_FACTOR : current * ZOOM_OUT_FACTOR;
|
|
37
38
|
return clampTimelineZoomPercent(next);
|
|
38
39
|
}
|
|
40
|
+
|
|
41
|
+
export function getPinchTimelineZoomPercent(
|
|
42
|
+
deltaY: number,
|
|
43
|
+
zoomMode: ZoomMode,
|
|
44
|
+
manualZoomPercent: number,
|
|
45
|
+
): number {
|
|
46
|
+
const current = getTimelineZoomPercent(zoomMode, manualZoomPercent);
|
|
47
|
+
if (!Number.isFinite(deltaY) || deltaY === 0) return current;
|
|
48
|
+
return clampTimelineZoomPercent(current * Math.exp(-deltaY * PINCH_ZOOM_SENSITIVITY));
|
|
49
|
+
}
|
|
@@ -3,8 +3,30 @@ import {
|
|
|
3
3
|
buildStandaloneRootTimelineElement,
|
|
4
4
|
mergeTimelineElementsPreservingDowngrades,
|
|
5
5
|
resolveStandaloneRootCompositionSrc,
|
|
6
|
+
shouldIgnorePlaybackShortcutEvent,
|
|
7
|
+
shouldIgnorePlaybackShortcutTarget,
|
|
6
8
|
} from "./useTimelinePlayer";
|
|
7
9
|
|
|
10
|
+
function mockTargetMatching(selectorNeedle: string): EventTarget {
|
|
11
|
+
return {
|
|
12
|
+
closest: (selector: string) => (selector.includes(selectorNeedle) ? ({} as Element) : null),
|
|
13
|
+
} as unknown as EventTarget;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function mockKeyboardEvent(
|
|
17
|
+
code: string,
|
|
18
|
+
overrides: Partial<Pick<KeyboardEvent, "altKey" | "ctrlKey" | "metaKey" | "target">> = {},
|
|
19
|
+
): Pick<KeyboardEvent, "altKey" | "ctrlKey" | "metaKey" | "code" | "target"> {
|
|
20
|
+
return {
|
|
21
|
+
altKey: false,
|
|
22
|
+
ctrlKey: false,
|
|
23
|
+
metaKey: false,
|
|
24
|
+
code,
|
|
25
|
+
target: mockTargetMatching("[data-missing]"),
|
|
26
|
+
...overrides,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
8
30
|
describe("buildStandaloneRootTimelineElement", () => {
|
|
9
31
|
it("includes selector and source metadata for standalone composition fallback clips", () => {
|
|
10
32
|
expect(
|
|
@@ -94,3 +116,60 @@ describe("mergeTimelineElementsPreservingDowngrades", () => {
|
|
|
94
116
|
).toEqual([{ id: "hero", tag: "div", start: 0, duration: 4, track: 0 }]);
|
|
95
117
|
});
|
|
96
118
|
});
|
|
119
|
+
|
|
120
|
+
describe("shouldIgnorePlaybackShortcutTarget", () => {
|
|
121
|
+
it("ignores focused toolbar buttons so Space can activate the button itself", () => {
|
|
122
|
+
expect(shouldIgnorePlaybackShortcutTarget(mockTargetMatching("button"))).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("ignores the seek slider so ArrowRight reaches the slider key handler", () => {
|
|
126
|
+
expect(shouldIgnorePlaybackShortcutTarget(mockTargetMatching("[role='slider']"))).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("allows non-interactive preview targets to use playback shortcuts", () => {
|
|
130
|
+
expect(shouldIgnorePlaybackShortcutTarget(mockTargetMatching("[data-missing]"))).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("shouldIgnorePlaybackShortcutEvent", () => {
|
|
135
|
+
it("ignores modified playback shortcuts so browser and app chords can handle them", () => {
|
|
136
|
+
expect(
|
|
137
|
+
shouldIgnorePlaybackShortcutEvent(mockKeyboardEvent("ArrowLeft", { altKey: true })),
|
|
138
|
+
).toBe(true);
|
|
139
|
+
expect(shouldIgnorePlaybackShortcutEvent(mockKeyboardEvent("KeyK", { ctrlKey: true }))).toBe(
|
|
140
|
+
true,
|
|
141
|
+
);
|
|
142
|
+
expect(shouldIgnorePlaybackShortcutEvent(mockKeyboardEvent("KeyL", { metaKey: true }))).toBe(
|
|
143
|
+
true,
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("defers Arrow frame shortcuts while caption edit mode has selected words", () => {
|
|
148
|
+
const captionSelection = { isCaptionEditMode: true, selectedCaptionSegmentCount: 1 };
|
|
149
|
+
|
|
150
|
+
expect(
|
|
151
|
+
shouldIgnorePlaybackShortcutEvent(mockKeyboardEvent("ArrowLeft"), captionSelection),
|
|
152
|
+
).toBe(true);
|
|
153
|
+
expect(
|
|
154
|
+
shouldIgnorePlaybackShortcutEvent(mockKeyboardEvent("ArrowRight"), captionSelection),
|
|
155
|
+
).toBe(true);
|
|
156
|
+
expect(shouldIgnorePlaybackShortcutEvent(mockKeyboardEvent("KeyJ"), captionSelection)).toBe(
|
|
157
|
+
false,
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("allows Arrow frame shortcuts when captions are not selected", () => {
|
|
162
|
+
expect(
|
|
163
|
+
shouldIgnorePlaybackShortcutEvent(mockKeyboardEvent("ArrowRight"), {
|
|
164
|
+
isCaptionEditMode: true,
|
|
165
|
+
selectedCaptionSegmentCount: 0,
|
|
166
|
+
}),
|
|
167
|
+
).toBe(false);
|
|
168
|
+
expect(
|
|
169
|
+
shouldIgnorePlaybackShortcutEvent(mockKeyboardEvent("ArrowRight"), {
|
|
170
|
+
isCaptionEditMode: false,
|
|
171
|
+
selectedCaptionSegmentCount: 1,
|
|
172
|
+
}),
|
|
173
|
+
).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
});
|