@hyperframes/studio 0.8.96 → 0.8.97
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/{hyperframes-player-ClfxdXeC.js → hyperframes-player-Biu5_W0l.js} +1 -1
- package/dist/assets/{index-D8ztHPd3.js → index-B5Z-1RW9.js} +1 -1
- package/dist/assets/{index-CSHEHeOo.js → index-BdV5T-QI.js} +200 -194
- package/dist/assets/{index-BNQLP5_v.js → index-CWuozokW.js} +1 -1
- package/dist/assets/{index-Dx79V1Se.css → index-xB5O0hPw.css} +1 -1
- package/dist/{chunk-QZ6HNNFG.js → chunk-6GAO75IU.js} +16 -4
- package/dist/chunk-6GAO75IU.js.map +1 -0
- package/dist/{chunk-PQFEGZWM.js → chunk-MTAVNAGW.js} +2 -2
- package/dist/chunk-MTAVNAGW.js.map +1 -0
- package/dist/{chunk-QPSOTK6M.js → chunk-U3EE4ACX.js} +5 -5
- package/dist/chunk-U3EE4ACX.js.map +1 -0
- package/dist/{domEditingLayers-3BAP6NNO.js → domEditingLayers-NU6Z5QWA.js} +2 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.html +2 -2
- package/dist/index.js +1981 -1779
- package/dist/index.js.map +1 -1
- package/dist/player.js +2 -2
- package/dist/styles.css +1 -1
- package/dist/ui.d.ts +3 -1
- package/dist/ui.js +1 -1
- package/package.json +8 -8
- package/src/components/TimelineToolPicker.tsx +19 -15
- package/src/components/TimelineToolbar.test.tsx +62 -0
- package/src/components/TimelineToolbar.tsx +3 -1
- package/src/components/editor/domEditing.test.ts +9 -1
- package/src/components/editor/domEditingDom.ts +2 -1
- package/src/components/editor/fontAssets.test.ts +69 -0
- package/src/components/editor/fontAssets.ts +11 -2
- package/src/components/ui/Menu.test.tsx +21 -0
- package/src/components/ui/Popover.tsx +21 -1
- package/src/hooks/domSelectionTimelineMirror.ts +5 -4
- package/src/hooks/domStyleCommit.fonts.test.ts +55 -0
- package/src/hooks/useAppHotkeys.test.ts +7 -5
- package/src/hooks/useClipboard.ts +25 -7
- package/src/hooks/useDomSelection.test.ts +56 -32
- package/src/hooks/useDomSelection.ts +4 -3
- package/src/player/components/BeatStrip.tsx +6 -3
- package/src/player/components/Timeline.readOnly.test.tsx +670 -0
- package/src/player/components/Timeline.rulerSeek.test.tsx +1 -25
- package/src/player/components/TimelineAutomationLane.test.tsx +23 -0
- package/src/player/components/TimelineClipDiamonds.tsx +7 -2
- package/src/player/components/TimelineFxButton.tsx +3 -0
- package/src/player/components/TimelineLanes.tsx +3 -2
- package/src/player/components/TimelineProvider.tsx +23 -7
- package/src/player/components/TimelineTypes.ts +2 -0
- package/src/player/components/studioShortcuts.ts +1 -1
- package/src/player/components/timelineClipGestureHandlers.ts +3 -1
- package/src/player/components/timelineDragDrop.ts +11 -1
- package/src/player/components/timelineEditCapabilities.ts +1 -0
- package/src/player/components/timelineMountTestEnv.ts +24 -0
- package/src/player/components/timelineReadOnly.ts +64 -0
- package/src/player/components/useAutomationLaneGestures.ts +13 -2
- package/src/player/components/useTimelineKeyframeHandlers.ts +7 -0
- package/src/player/components/useTimelineProviderState.tsx +42 -14
- package/src/player/hooks/useShadowPreviewReload.ts +2 -2
- package/src/player/hooks/useTimelinePlayer.shadowReload.test.ts +69 -0
- package/src/player/hooks/useTimelinePlayer.ts +6 -0
- package/src/player/lib/timelineDOM.test.ts +25 -0
- package/src/player/lib/timelineDOM.ts +4 -2
- package/src/player/lib/timelineIframeHelpers.test.ts +15 -0
- package/src/player/store/playerStore.ts +4 -4
- package/src/utils/clipboardLook.test.ts +130 -0
- package/src/utils/clipboardLook.ts +82 -0
- package/src/utils/clipboardPayload.ts +8 -2
- package/src/utils/studioFontHelpers.ts +6 -3
- package/dist/chunk-PQFEGZWM.js.map +0 -1
- package/dist/chunk-QPSOTK6M.js.map +0 -1
- package/dist/chunk-QZ6HNNFG.js.map +0 -1
- /package/dist/{domEditingLayers-3BAP6NNO.js.map → domEditingLayers-NU6Z5QWA.js.map} +0 -0
|
@@ -29,9 +29,13 @@ function renderHarness(
|
|
|
29
29
|
cleanup: () => void;
|
|
30
30
|
timeline: TimelineSpies;
|
|
31
31
|
} {
|
|
32
|
+
// Reads back what was last published, as the timeline store does.
|
|
33
|
+
let publishedSet: ReadonlySet<string> = new Set();
|
|
32
34
|
const timeline: TimelineSpies = {
|
|
33
35
|
setSelectedTimelineElementId: vi.fn(),
|
|
34
|
-
setTimelineSelectionSet: vi.fn()
|
|
36
|
+
setTimelineSelectionSet: vi.fn((ids: Set<string>) => {
|
|
37
|
+
publishedSet = ids;
|
|
38
|
+
}),
|
|
35
39
|
};
|
|
36
40
|
const host = document.createElement("div");
|
|
37
41
|
document.body.append(host);
|
|
@@ -47,7 +51,7 @@ function renderHarness(
|
|
|
47
51
|
captionEditMode: false,
|
|
48
52
|
previewIframeRef: { current: null },
|
|
49
53
|
timelineElements: options.timelineElements ?? [],
|
|
50
|
-
getTimelineSelectionSet: () =>
|
|
54
|
+
getTimelineSelectionSet: () => publishedSet,
|
|
51
55
|
setSelectedTimelineElementId: timeline.setSelectedTimelineElementId,
|
|
52
56
|
setTimelineSelectionSet: timeline.setTimelineSelectionSet,
|
|
53
57
|
setRightCollapsed: vi.fn(),
|
|
@@ -107,6 +111,17 @@ function timelineElement(domId: string): TimelineElement {
|
|
|
107
111
|
} as TimelineElement;
|
|
108
112
|
}
|
|
109
113
|
|
|
114
|
+
function renderCardAndChip() {
|
|
115
|
+
const card = makeSelection("Card", Object.assign(document.createElement("div"), { id: "card" }));
|
|
116
|
+
const chip = makeSelection("Chip", Object.assign(document.createElement("div"), { id: "chip" }));
|
|
117
|
+
document.body.append(card.element, chip.element);
|
|
118
|
+
const harness = renderHarness(
|
|
119
|
+
{ activeCompPath: "index.html", projectId: "project-1", refreshKey: 0 },
|
|
120
|
+
{ timelineElements: [timelineElement("card"), timelineElement("chip")] },
|
|
121
|
+
);
|
|
122
|
+
return { harness, card, chip };
|
|
123
|
+
}
|
|
124
|
+
|
|
110
125
|
/**
|
|
111
126
|
* A marquee builds the group correctly and then used to lose it: it announced only
|
|
112
127
|
* the primary to the timeline, the timeline is the source of truth for what is
|
|
@@ -116,24 +131,9 @@ function timelineElement(domId: string): TimelineElement {
|
|
|
116
131
|
*/
|
|
117
132
|
describe("useDomSelection marquee", () => {
|
|
118
133
|
it("announces every marquee'd element to the timeline, anchored on the primary", () => {
|
|
119
|
-
const
|
|
120
|
-
first.id = "card";
|
|
121
|
-
const second = document.createElement("div");
|
|
122
|
-
second.id = "chip";
|
|
123
|
-
document.body.append(first, second);
|
|
124
|
-
const harness = renderHarness(
|
|
125
|
-
{ activeCompPath: "index.html", projectId: "project-1", refreshKey: 0 },
|
|
126
|
-
{ timelineElements: [timelineElement("card"), timelineElement("chip")] },
|
|
127
|
-
);
|
|
134
|
+
const { harness, card, chip } = renderCardAndChip();
|
|
128
135
|
|
|
129
|
-
act(() =>
|
|
130
|
-
harness
|
|
131
|
-
.current()
|
|
132
|
-
.applyMarqueeSelection(
|
|
133
|
-
[makeSelection("Card", first), makeSelection("Chip", second)],
|
|
134
|
-
false,
|
|
135
|
-
),
|
|
136
|
-
);
|
|
136
|
+
act(() => harness.current().applyMarqueeSelection([card, chip], false));
|
|
137
137
|
|
|
138
138
|
expect(harness.current().domEditGroupSelections).toHaveLength(2);
|
|
139
139
|
expect(harness.timeline.setTimelineSelectionSet).toHaveBeenCalledWith(
|
|
@@ -181,20 +181,10 @@ describe("useDomSelection marquee", () => {
|
|
|
181
181
|
*/
|
|
182
182
|
describe("useDomSelection additive", () => {
|
|
183
183
|
it("announces both members when a second element joins the selection", () => {
|
|
184
|
-
const
|
|
185
|
-
first.id = "card";
|
|
186
|
-
const second = document.createElement("div");
|
|
187
|
-
second.id = "chip";
|
|
188
|
-
document.body.append(first, second);
|
|
189
|
-
const harness = renderHarness(
|
|
190
|
-
{ activeCompPath: "index.html", projectId: "project-1", refreshKey: 0 },
|
|
191
|
-
{ timelineElements: [timelineElement("card"), timelineElement("chip")] },
|
|
192
|
-
);
|
|
184
|
+
const { harness, card, chip } = renderCardAndChip();
|
|
193
185
|
|
|
194
|
-
act(() => harness.current().applyDomSelection(
|
|
195
|
-
act(() =>
|
|
196
|
-
harness.current().applyDomSelection(makeSelection("Chip", second), { additive: true }),
|
|
197
|
-
);
|
|
186
|
+
act(() => harness.current().applyDomSelection(card));
|
|
187
|
+
act(() => harness.current().applyDomSelection(chip, { additive: true }));
|
|
198
188
|
|
|
199
189
|
expect(harness.current().domEditGroupSelections).toHaveLength(2);
|
|
200
190
|
expect(harness.timeline.setTimelineSelectionSet).toHaveBeenLastCalledWith(
|
|
@@ -205,6 +195,40 @@ describe("useDomSelection additive", () => {
|
|
|
205
195
|
});
|
|
206
196
|
harness.cleanup();
|
|
207
197
|
});
|
|
198
|
+
|
|
199
|
+
// The timeline syncs its set back onto the canvas, so a set left at two re-adds the member.
|
|
200
|
+
it.each([
|
|
201
|
+
["shift+click removes a member", "toggle"],
|
|
202
|
+
["a marquee catches one member of a live group", "marquee"],
|
|
203
|
+
] as const)("publishes the smaller set when %s", (_, shrink) => {
|
|
204
|
+
const { harness, card, chip } = renderCardAndChip();
|
|
205
|
+
|
|
206
|
+
act(() => harness.current().applyMarqueeSelection([card, chip], false));
|
|
207
|
+
act(() =>
|
|
208
|
+
shrink === "toggle"
|
|
209
|
+
? harness.current().applyDomSelection(chip, { additive: true })
|
|
210
|
+
: harness.current().applyMarqueeSelection([card], false),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
expect(harness.current().domEditGroupSelections).toHaveLength(1);
|
|
214
|
+
expect(harness.timeline.setTimelineSelectionSet).toHaveBeenLastCalledWith(new Set(["card"]));
|
|
215
|
+
expect(harness.timeline.setSelectedTimelineElementId).toHaveBeenLastCalledWith("card", {
|
|
216
|
+
preserveSet: true,
|
|
217
|
+
});
|
|
218
|
+
harness.cleanup();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("keeps the live set when a single member is re-announced", () => {
|
|
222
|
+
const { harness, card, chip } = renderCardAndChip();
|
|
223
|
+
|
|
224
|
+
act(() => harness.current().applyMarqueeSelection([card, chip], false));
|
|
225
|
+
act(() => harness.current().applyDomSelection(chip));
|
|
226
|
+
|
|
227
|
+
expect(harness.timeline.setTimelineSelectionSet).toHaveBeenLastCalledWith(
|
|
228
|
+
new Set(["card", "chip"]),
|
|
229
|
+
);
|
|
230
|
+
harness.cleanup();
|
|
231
|
+
});
|
|
208
232
|
});
|
|
209
233
|
|
|
210
234
|
describe("useDomSelection", () => {
|
|
@@ -83,7 +83,7 @@ export function useDomSelection({
|
|
|
83
83
|
// ── Callbacks ──
|
|
84
84
|
|
|
85
85
|
const announceTimelineSelection = useCallback(
|
|
86
|
-
(group: DomEditSelection[], primary: DomEditSelection | null) =>
|
|
86
|
+
(group: DomEditSelection[], primary: DomEditSelection | null, replaceSet?: boolean) =>
|
|
87
87
|
announceSelectionToTimeline(
|
|
88
88
|
{
|
|
89
89
|
timelineElements,
|
|
@@ -93,6 +93,7 @@ export function useDomSelection({
|
|
|
93
93
|
},
|
|
94
94
|
group,
|
|
95
95
|
primary,
|
|
96
|
+
replaceSet,
|
|
96
97
|
),
|
|
97
98
|
[
|
|
98
99
|
getTimelineSelectionSet,
|
|
@@ -179,7 +180,7 @@ export function useDomSelection({
|
|
|
179
180
|
setRightPanelTab("design");
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
|
-
announceTimelineSelection(nextGroup, nextSelection);
|
|
183
|
+
announceTimelineSelection(nextGroup, nextSelection, isAdditiveSelection);
|
|
183
184
|
return;
|
|
184
185
|
}
|
|
185
186
|
|
|
@@ -510,7 +511,7 @@ export function useDomSelection({
|
|
|
510
511
|
domEditGroupSelectionsRef.current = nextGroup;
|
|
511
512
|
setDomEditSelection(nextSelection);
|
|
512
513
|
setDomEditGroupSelections(nextGroup);
|
|
513
|
-
announceTimelineSelection(nextGroup, nextSelection);
|
|
514
|
+
announceTimelineSelection(nextGroup, nextSelection, true);
|
|
514
515
|
},
|
|
515
516
|
[applyDomSelection, announceTimelineSelection],
|
|
516
517
|
);
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
resolveTimelineAutoScrollLoopAction,
|
|
13
13
|
} from "./timelineEditing";
|
|
14
14
|
import { getTimelineElementIndexes } from "../lib/timelineElementIndexes";
|
|
15
|
+
import { useTimelineReadOnlyPress } from "./timelineReadOnly";
|
|
15
16
|
|
|
16
17
|
export const BEAT_BAND_H = 14; // dark band height at top of track
|
|
17
18
|
const BEAT_HIT_W = 24; // grab width per beat (px) — ≥24px pointer target
|
|
@@ -88,7 +89,7 @@ function claimBeatDrag(pointerId?: number): BeatDragActor | null {
|
|
|
88
89
|
return actor;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
function cancelBeatDrag(): void {
|
|
92
|
+
export function cancelBeatDrag(): void {
|
|
92
93
|
claimBeatDrag();
|
|
93
94
|
}
|
|
94
95
|
|
|
@@ -377,6 +378,7 @@ export const BeatStrip = memo(function BeatStrip({
|
|
|
377
378
|
);
|
|
378
379
|
const sessionEpoch = usePlayerStore((state) => state.timelineSessionEpoch);
|
|
379
380
|
const projectId = usePlayerStore((state) => state.timelineProjectId);
|
|
381
|
+
const readOnlyPress = useTimelineReadOnlyPress();
|
|
380
382
|
|
|
381
383
|
if (!beatTimes || beatsTooDense(beatTimes, pps)) return null;
|
|
382
384
|
const activeBeatIndex = activeActor
|
|
@@ -420,7 +422,7 @@ export const BeatStrip = memo(function BeatStrip({
|
|
|
420
422
|
top: 0,
|
|
421
423
|
width: BEAT_HIT_W,
|
|
422
424
|
height: BEAT_BAND_H,
|
|
423
|
-
cursor: "ew-resize",
|
|
425
|
+
cursor: readOnlyPress ? "default" : "ew-resize",
|
|
424
426
|
pointerEvents: "auto",
|
|
425
427
|
touchAction: "none",
|
|
426
428
|
}}
|
|
@@ -429,13 +431,14 @@ export const BeatStrip = memo(function BeatStrip({
|
|
|
429
431
|
// selection (which otherwise "selects" the whole panel mid-drag).
|
|
430
432
|
e.preventDefault();
|
|
431
433
|
e.stopPropagation();
|
|
434
|
+
if (readOnlyPress) return readOnlyPress();
|
|
432
435
|
// ⌥ starts no drag: the delete lands on release below, so a
|
|
433
436
|
// slipped ⌥-drag abandons instead of destroying the beat.
|
|
434
437
|
if (e.altKey) return;
|
|
435
438
|
beginBeatDrag(e, t, pps);
|
|
436
439
|
}}
|
|
437
440
|
onClick={(e) => {
|
|
438
|
-
if (!e.altKey) return;
|
|
441
|
+
if (!e.altKey || readOnlyPress) return;
|
|
439
442
|
// ⌥-click deletes. Deliberately NOT double-click: a stuttered drag
|
|
440
443
|
// attempt reads as a double-click and would destroy the beat.
|
|
441
444
|
e.stopPropagation();
|