@hyperframes/studio 0.6.6 → 0.6.7
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-T-ME1rqL.js → hyperframes-player-D0Yi3xMP.js} +2 -2
- package/dist/assets/{index-Bne9FFeo.css → index-Ckqo37Co.css} +1 -1
- package/dist/assets/index-Yvtxngdi.js +116 -0
- package/dist/index.html +2 -2
- package/package.json +4 -4
- package/src/App.tsx +54 -31
- package/src/components/StudioGlobalDragOverlay.tsx +26 -0
- package/src/components/StudioRightPanel.tsx +0 -2
- package/src/components/editor/DomEditOverlay.test.ts +1 -0
- package/src/components/editor/DomEditOverlay.tsx +2 -1
- package/src/components/editor/PropertyPanel.tsx +27 -36
- package/src/components/editor/domEditingElement.ts +1 -0
- package/src/components/editor/manualEdits.test.ts +39 -466
- package/src/components/editor/manualEdits.ts +6 -168
- package/src/components/editor/manualEditsDom.ts +361 -1
- package/src/components/editor/manualEditsParsing.ts +2 -240
- package/src/components/editor/manualEditsTypes.ts +1 -40
- package/src/components/editor/useDomEditOverlayGestures.ts +25 -8
- package/src/components/nle/NLEPreview.tsx +1 -1
- package/src/components/sidebar/CompositionsTab.tsx +9 -3
- package/src/contexts/DomEditContext.tsx +3 -0
- package/src/contexts/FileManagerContext.tsx +3 -0
- package/src/hooks/useAppHotkeys.ts +1 -4
- package/src/hooks/useDomEditCommits.ts +82 -77
- package/src/hooks/useDomEditSession.ts +4 -16
- package/src/hooks/useFileManager.ts +10 -1
- package/src/hooks/useManifestPersistence.ts +51 -187
- package/src/hooks/usePanelLayout.ts +10 -3
- package/src/hooks/usePreviewInteraction.ts +0 -1
- package/src/hooks/useStudioUrlState.ts +188 -0
- package/src/player/components/Player.tsx +15 -1
- package/src/player/components/PlayerControls.test.ts +17 -0
- package/src/player/components/PlayerControls.tsx +61 -0
- package/src/player/hooks/usePlaybackKeyboard.test.ts +174 -0
- package/src/player/hooks/usePlaybackKeyboard.ts +18 -15
- package/src/player/hooks/useTimelinePlayer.seek.test.ts +329 -0
- package/src/player/hooks/useTimelinePlayer.ts +76 -18
- package/src/player/hooks/useTimelineSyncCallbacks.ts +10 -4
- package/src/player/lib/playbackAdapter.test.ts +50 -0
- package/src/player/lib/playbackAdapter.ts +2 -2
- package/src/player/lib/playbackTypes.ts +1 -1
- package/src/player/lib/timelineDOM.ts +4 -2
- package/src/player/lib/timelineIframeHelpers.ts +63 -7
- package/src/player/store/playerStore.test.ts +105 -1
- package/src/player/store/playerStore.ts +12 -1
- package/src/utils/projectRouting.test.ts +15 -0
- package/src/utils/projectRouting.ts +46 -9
- package/src/utils/sourcePatcher.ts +50 -14
- package/src/utils/studioPreviewHelpers.test.ts +56 -0
- package/src/utils/studioPreviewHelpers.ts +51 -13
- package/src/utils/studioUiPreferences.test.ts +3 -0
- package/src/utils/studioUiPreferences.ts +4 -0
- package/src/utils/studioUrlState.test.ts +249 -0
- package/src/utils/studioUrlState.ts +135 -0
- package/dist/assets/index-DYqqzECY.js +0 -117
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { Window } from "happy-dom";
|
|
3
|
-
import type { DomEditSelection } from "./domEditing";
|
|
4
3
|
import {
|
|
5
4
|
STUDIO_OFFSET_X_PROP,
|
|
6
5
|
STUDIO_OFFSET_Y_PROP,
|
|
@@ -8,7 +7,6 @@ import {
|
|
|
8
7
|
STUDIO_WIDTH_PROP,
|
|
9
8
|
applyStudioBoxSize,
|
|
10
9
|
applyStudioBoxSizeDraft,
|
|
11
|
-
applyStudioManualEditManifest,
|
|
12
10
|
applyStudioPathOffset,
|
|
13
11
|
applyStudioPathOffsetDraft,
|
|
14
12
|
applyStudioRotation,
|
|
@@ -16,22 +14,17 @@ import {
|
|
|
16
14
|
beginStudioManualEditGesture,
|
|
17
15
|
captureStudioBoxSize,
|
|
18
16
|
captureStudioRotation,
|
|
19
|
-
|
|
17
|
+
clearStudioBoxSize,
|
|
18
|
+
clearStudioPathOffset,
|
|
19
|
+
clearStudioRotation,
|
|
20
20
|
endStudioManualEditGesture,
|
|
21
21
|
installStudioManualEditSeekReapply,
|
|
22
|
-
isStudioManualEditManifestPath,
|
|
23
|
-
parseStudioManualEditManifest,
|
|
24
|
-
readStudioFileChangePath,
|
|
25
22
|
readStudioBoxSize,
|
|
23
|
+
readStudioFileChangePath,
|
|
26
24
|
readStudioPathOffset,
|
|
27
25
|
readStudioRotation,
|
|
28
|
-
removeStudioManualEditsForSelection,
|
|
29
26
|
restoreStudioBoxSize,
|
|
30
27
|
restoreStudioRotation,
|
|
31
|
-
serializeStudioManualEditManifest,
|
|
32
|
-
upsertStudioBoxSizeEdit,
|
|
33
|
-
upsertStudioPathOffsetEdit,
|
|
34
|
-
upsertStudioRotationEdit,
|
|
35
28
|
} from "./manualEdits";
|
|
36
29
|
|
|
37
30
|
function createDocument(markup: string): Document {
|
|
@@ -40,36 +33,6 @@ function createDocument(markup: string): Document {
|
|
|
40
33
|
return window.document;
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
function createSelection(): DomEditSelection {
|
|
44
|
-
return {
|
|
45
|
-
element: {} as HTMLElement,
|
|
46
|
-
id: "card",
|
|
47
|
-
selector: "#card",
|
|
48
|
-
selectorIndex: undefined,
|
|
49
|
-
sourceFile: "index.html",
|
|
50
|
-
compositionPath: "index.html",
|
|
51
|
-
compositionSrc: undefined,
|
|
52
|
-
isCompositionHost: false,
|
|
53
|
-
label: "Card",
|
|
54
|
-
tagName: "div",
|
|
55
|
-
boundingBox: { x: 0, y: 0, width: 100, height: 100 },
|
|
56
|
-
textContent: null,
|
|
57
|
-
dataAttributes: {},
|
|
58
|
-
inlineStyles: {},
|
|
59
|
-
computedStyles: {},
|
|
60
|
-
textFields: [],
|
|
61
|
-
capabilities: {
|
|
62
|
-
canSelect: true,
|
|
63
|
-
canEditStyles: true,
|
|
64
|
-
canMove: false,
|
|
65
|
-
canResize: false,
|
|
66
|
-
canApplyManualOffset: true,
|
|
67
|
-
canApplyManualSize: true,
|
|
68
|
-
canApplyManualRotation: true,
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
36
|
function mockBoundingRect(element: HTMLElement, width: number, height: number): void {
|
|
74
37
|
element.getBoundingClientRect = () =>
|
|
75
38
|
({
|
|
@@ -95,149 +58,13 @@ function mockComputedStyle(element: HTMLElement, values: Record<string, string>)
|
|
|
95
58
|
}
|
|
96
59
|
|
|
97
60
|
describe("studio manual edits", () => {
|
|
98
|
-
it("
|
|
99
|
-
const manifest = upsertStudioPathOffsetEdit(
|
|
100
|
-
emptyStudioManualEditManifest(),
|
|
101
|
-
createSelection(),
|
|
102
|
-
{
|
|
103
|
-
x: 12.4,
|
|
104
|
-
y: 30.6,
|
|
105
|
-
},
|
|
106
|
-
);
|
|
107
|
-
const updated = upsertStudioPathOffsetEdit(manifest, createSelection(), {
|
|
108
|
-
x: 20,
|
|
109
|
-
y: 42,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
expect(updated.edits).toHaveLength(1);
|
|
113
|
-
expect(updated.edits[0]).toMatchObject({
|
|
114
|
-
kind: "path-offset",
|
|
115
|
-
target: { sourceFile: "index.html", selector: "#card", id: "card" },
|
|
116
|
-
x: 20,
|
|
117
|
-
y: 42,
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("upserts box sizes without replacing path offsets for the same target", () => {
|
|
122
|
-
const selection = createSelection();
|
|
123
|
-
const manifest = upsertStudioPathOffsetEdit(emptyStudioManualEditManifest(), selection, {
|
|
124
|
-
x: 12,
|
|
125
|
-
y: 30,
|
|
126
|
-
});
|
|
127
|
-
const updated = upsertStudioBoxSizeEdit(manifest, selection, {
|
|
128
|
-
width: 240.4,
|
|
129
|
-
height: 120.6,
|
|
130
|
-
});
|
|
131
|
-
const resized = upsertStudioBoxSizeEdit(updated, selection, {
|
|
132
|
-
width: 260,
|
|
133
|
-
height: 140,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
expect(resized.edits).toHaveLength(2);
|
|
137
|
-
expect(resized.edits).toEqual(
|
|
138
|
-
expect.arrayContaining([
|
|
139
|
-
expect.objectContaining({ kind: "path-offset", x: 12, y: 30 }),
|
|
140
|
-
expect.objectContaining({ kind: "box-size", width: 260, height: 140 }),
|
|
141
|
-
]),
|
|
142
|
-
);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("upserts rotations without replacing other manual edits for the same target", () => {
|
|
146
|
-
const selection = createSelection();
|
|
147
|
-
const manifest = upsertStudioPathOffsetEdit(emptyStudioManualEditManifest(), selection, {
|
|
148
|
-
x: 12,
|
|
149
|
-
y: 30,
|
|
150
|
-
});
|
|
151
|
-
const resized = upsertStudioBoxSizeEdit(manifest, selection, {
|
|
152
|
-
width: 240,
|
|
153
|
-
height: 120,
|
|
154
|
-
});
|
|
155
|
-
const rotated = upsertStudioRotationEdit(resized, selection, { angle: 32.34 });
|
|
156
|
-
const updated = upsertStudioRotationEdit(rotated, selection, { angle: -14.96 });
|
|
157
|
-
|
|
158
|
-
expect(updated.edits).toHaveLength(3);
|
|
159
|
-
expect(updated.edits).toEqual(
|
|
160
|
-
expect.arrayContaining([
|
|
161
|
-
expect.objectContaining({ kind: "path-offset", x: 12, y: 30 }),
|
|
162
|
-
expect.objectContaining({ kind: "box-size", width: 240, height: 120 }),
|
|
163
|
-
expect.objectContaining({ kind: "rotation", angle: -15 }),
|
|
164
|
-
]),
|
|
165
|
-
);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("removes all manual edits for the selected target", () => {
|
|
169
|
-
const selection = createSelection();
|
|
170
|
-
const otherSelection = {
|
|
171
|
-
...createSelection(),
|
|
172
|
-
id: "other-card",
|
|
173
|
-
selector: "#other-card",
|
|
174
|
-
label: "Other card",
|
|
175
|
-
};
|
|
176
|
-
const moved = upsertStudioPathOffsetEdit(emptyStudioManualEditManifest(), selection, {
|
|
177
|
-
x: 12,
|
|
178
|
-
y: 30,
|
|
179
|
-
});
|
|
180
|
-
const resized = upsertStudioBoxSizeEdit(moved, selection, {
|
|
181
|
-
width: 240,
|
|
182
|
-
height: 120,
|
|
183
|
-
});
|
|
184
|
-
const rotated = upsertStudioRotationEdit(resized, selection, { angle: 32 });
|
|
185
|
-
const manifest = upsertStudioPathOffsetEdit(rotated, otherSelection, { x: 4, y: 8 });
|
|
186
|
-
|
|
187
|
-
const updated = removeStudioManualEditsForSelection(manifest, selection);
|
|
188
|
-
|
|
189
|
-
expect(updated.edits).toHaveLength(1);
|
|
190
|
-
expect(updated.edits[0]).toMatchObject({
|
|
191
|
-
kind: "path-offset",
|
|
192
|
-
target: { id: "other-card", selector: "#other-card" },
|
|
193
|
-
x: 4,
|
|
194
|
-
y: 8,
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("round-trips valid manifest entries and drops invalid entries", () => {
|
|
199
|
-
const content = serializeStudioManualEditManifest({
|
|
200
|
-
version: 1,
|
|
201
|
-
edits: [
|
|
202
|
-
{
|
|
203
|
-
kind: "path-offset",
|
|
204
|
-
target: { sourceFile: "index.html", selector: "#card", id: "card" },
|
|
205
|
-
x: 10,
|
|
206
|
-
y: 20,
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
kind: "box-size",
|
|
210
|
-
target: { sourceFile: "index.html", selector: "#card", id: "card" },
|
|
211
|
-
width: 320,
|
|
212
|
-
height: 180,
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
kind: "rotation",
|
|
216
|
-
target: { sourceFile: "index.html", selector: "#card", id: "card" },
|
|
217
|
-
angle: 22.5,
|
|
218
|
-
},
|
|
219
|
-
],
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
expect(parseStudioManualEditManifest(content).edits).toHaveLength(3);
|
|
223
|
-
expect(parseStudioManualEditManifest('{ "edits": [{ "kind": "path-offset" }] }').edits).toEqual(
|
|
224
|
-
[],
|
|
225
|
-
);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("recognizes manual edit manifest file-change payloads", () => {
|
|
61
|
+
it("recognizes studio file-change payloads", () => {
|
|
229
62
|
expect(readStudioFileChangePath({ path: ".hyperframes/studio-manual-edits.json" })).toBe(
|
|
230
63
|
".hyperframes/studio-manual-edits.json",
|
|
231
64
|
);
|
|
232
65
|
expect(readStudioFileChangePath({ data: '{"path":"nested/file.html"}' })).toBe(
|
|
233
66
|
"nested/file.html",
|
|
234
67
|
);
|
|
235
|
-
expect(
|
|
236
|
-
isStudioManualEditManifestPath(
|
|
237
|
-
"/Users/example/project/.hyperframes/studio-manual-edits.json",
|
|
238
|
-
),
|
|
239
|
-
).toBe(true);
|
|
240
|
-
expect(isStudioManualEditManifestPath("index.html")).toBe(false);
|
|
241
68
|
});
|
|
242
69
|
|
|
243
70
|
it("applies offsets through CSS translate longhand", () => {
|
|
@@ -293,9 +120,8 @@ describe("studio manual edits", () => {
|
|
|
293
120
|
applyStudioPathOffset(card, { x: 14, y: -8 });
|
|
294
121
|
applyStudioRotation(card, { angle: 12 });
|
|
295
122
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
).toBe(0);
|
|
123
|
+
clearStudioPathOffset(card);
|
|
124
|
+
clearStudioRotation(card);
|
|
299
125
|
|
|
300
126
|
expect(card.style.getPropertyValue("translate")).toBe("");
|
|
301
127
|
expect(card.style.getPropertyValue("rotate")).toBe("");
|
|
@@ -383,54 +209,6 @@ describe("studio manual edits", () => {
|
|
|
383
209
|
expect(card.style.getPropertyValue("transform-origin")).toBe("center center");
|
|
384
210
|
});
|
|
385
211
|
|
|
386
|
-
it("does not recapture a studio rotation draft as the authored base", () => {
|
|
387
|
-
const document = createDocument(`<div id="card" style="rotate: 8deg"></div>`);
|
|
388
|
-
const card = document.getElementById("card") as HTMLElement;
|
|
389
|
-
const manifest = parseStudioManualEditManifest(`{
|
|
390
|
-
"version": 1,
|
|
391
|
-
"edits": [
|
|
392
|
-
{
|
|
393
|
-
"kind": "rotation",
|
|
394
|
-
"target": { "sourceFile": "index.html", "selector": "#card", "id": "card" },
|
|
395
|
-
"angle": 35
|
|
396
|
-
}
|
|
397
|
-
]
|
|
398
|
-
}`);
|
|
399
|
-
|
|
400
|
-
applyStudioRotation(card, { angle: 12 });
|
|
401
|
-
applyStudioRotationDraft(card, { angle: 35 });
|
|
402
|
-
expect(card.style.getPropertyValue("rotate")).toBe("calc(8deg + 35deg)");
|
|
403
|
-
|
|
404
|
-
expect(applyStudioManualEditManifest(document, manifest, "index.html")).toBe(1);
|
|
405
|
-
|
|
406
|
-
expect(card.style.getPropertyValue("rotate")).toBe(
|
|
407
|
-
`calc(8deg + var(${STUDIO_ROTATION_PROP}, 0deg))`,
|
|
408
|
-
);
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
it("does not treat a base-free studio rotation draft as authored rotation", () => {
|
|
412
|
-
const document = createDocument(`<div id="card"></div>`);
|
|
413
|
-
const card = document.getElementById("card") as HTMLElement;
|
|
414
|
-
const manifest = parseStudioManualEditManifest(`{
|
|
415
|
-
"version": 1,
|
|
416
|
-
"edits": [
|
|
417
|
-
{
|
|
418
|
-
"kind": "rotation",
|
|
419
|
-
"target": { "sourceFile": "index.html", "selector": "#card", "id": "card" },
|
|
420
|
-
"angle": 35
|
|
421
|
-
}
|
|
422
|
-
]
|
|
423
|
-
}`);
|
|
424
|
-
|
|
425
|
-
applyStudioRotation(card, { angle: 12 });
|
|
426
|
-
applyStudioRotationDraft(card, { angle: 35 });
|
|
427
|
-
expect(card.style.getPropertyValue("rotate")).toBe("35deg");
|
|
428
|
-
|
|
429
|
-
expect(applyStudioManualEditManifest(document, manifest, "index.html")).toBe(1);
|
|
430
|
-
|
|
431
|
-
expect(card.style.getPropertyValue("rotate")).toBe(`var(${STUDIO_ROTATION_PROP}, 0deg)`);
|
|
432
|
-
});
|
|
433
|
-
|
|
434
212
|
it("uses height for flex-basis inside column flex containers", () => {
|
|
435
213
|
const document = createDocument(`
|
|
436
214
|
<div style="display: flex; flex-direction: column">
|
|
@@ -523,9 +301,7 @@ describe("studio manual edits", () => {
|
|
|
523
301
|
expect(tween._startAt.vars).toEqual({ x: -240, y: -20 });
|
|
524
302
|
expect(card.style.getPropertyValue("translate")).toContain(STUDIO_OFFSET_X_PROP);
|
|
525
303
|
|
|
526
|
-
|
|
527
|
-
applyStudioManualEditManifest(document, emptyStudioManualEditManifest(), "index.html"),
|
|
528
|
-
).toBe(0);
|
|
304
|
+
clearStudioPathOffset(card);
|
|
529
305
|
expect(tween.vars).toMatchObject({
|
|
530
306
|
x: 0,
|
|
531
307
|
y: 10,
|
|
@@ -537,197 +313,48 @@ describe("studio manual edits", () => {
|
|
|
537
313
|
expect(card.style.getPropertyValue("translate")).toBe("");
|
|
538
314
|
});
|
|
539
315
|
|
|
540
|
-
it("
|
|
541
|
-
const document = createDocument(`<div id="card"></div>`);
|
|
542
|
-
const
|
|
543
|
-
"version": 1,
|
|
544
|
-
"edits": [
|
|
545
|
-
{
|
|
546
|
-
"kind": "path-offset",
|
|
547
|
-
"target": { "sourceFile": "index.html", "selector": "#card", "id": "card" },
|
|
548
|
-
"x": 32,
|
|
549
|
-
"y": 18
|
|
550
|
-
}
|
|
551
|
-
]
|
|
552
|
-
}`);
|
|
553
|
-
|
|
554
|
-
expect(applyStudioManualEditManifest(document, manifest, "index.html")).toBe(1);
|
|
555
|
-
expect(readStudioPathOffset(document.getElementById("card") as HTMLElement)).toEqual({
|
|
556
|
-
x: 32,
|
|
557
|
-
y: 18,
|
|
558
|
-
});
|
|
559
|
-
});
|
|
316
|
+
it("clears path offsets and restores authored inline translate", () => {
|
|
317
|
+
const document = createDocument(`<div id="card" style="translate: 10px 20px"></div>`);
|
|
318
|
+
const card = document.getElementById("card") as HTMLElement;
|
|
560
319
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
<div data-composition-id="root">
|
|
564
|
-
<div id="card" class="tile"></div>
|
|
565
|
-
<div data-composition-id="nested" data-composition-file="scenes/nested.html">
|
|
566
|
-
<div id="card" class="tile"></div>
|
|
567
|
-
<div class="tile"></div>
|
|
568
|
-
</div>
|
|
569
|
-
</div>
|
|
570
|
-
`);
|
|
571
|
-
const htmlElement = document.defaultView?.HTMLElement;
|
|
572
|
-
if (!htmlElement) throw new Error("HTMLElement fixture missing");
|
|
573
|
-
const cards = Array.from(document.getElementsByTagName("*")).filter(
|
|
574
|
-
(element): element is HTMLElement => element instanceof htmlElement && element.id === "card",
|
|
575
|
-
);
|
|
576
|
-
const rootCard = cards[0];
|
|
577
|
-
const nestedCard = cards[1];
|
|
578
|
-
const tiles = Array.from(document.getElementsByTagName("*")).filter(
|
|
579
|
-
(element): element is HTMLElement =>
|
|
580
|
-
element instanceof htmlElement && element.classList.contains("tile"),
|
|
581
|
-
);
|
|
582
|
-
const nestedSecondTile = tiles[2];
|
|
583
|
-
if (!rootCard || !nestedCard || !nestedSecondTile) {
|
|
584
|
-
throw new Error("source-scoped fixture missing");
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
const manifest = parseStudioManualEditManifest(`{
|
|
588
|
-
"version": 1,
|
|
589
|
-
"edits": [
|
|
590
|
-
{
|
|
591
|
-
"kind": "path-offset",
|
|
592
|
-
"target": {
|
|
593
|
-
"sourceFile": "scenes/nested.html",
|
|
594
|
-
"selector": "#card",
|
|
595
|
-
"id": "card"
|
|
596
|
-
},
|
|
597
|
-
"x": 48,
|
|
598
|
-
"y": 16
|
|
599
|
-
},
|
|
600
|
-
{
|
|
601
|
-
"kind": "box-size",
|
|
602
|
-
"target": {
|
|
603
|
-
"sourceFile": "scenes/nested.html",
|
|
604
|
-
"selector": ".tile",
|
|
605
|
-
"selectorIndex": 1
|
|
606
|
-
},
|
|
607
|
-
"width": 220,
|
|
608
|
-
"height": 80
|
|
609
|
-
}
|
|
610
|
-
]
|
|
611
|
-
}`);
|
|
612
|
-
|
|
613
|
-
expect(applyStudioManualEditManifest(document, manifest, "index.html")).toBe(2);
|
|
614
|
-
expect(readStudioPathOffset(rootCard)).toEqual({ x: 0, y: 0 });
|
|
615
|
-
expect(readStudioPathOffset(nestedCard)).toEqual({ x: 48, y: 16 });
|
|
616
|
-
expect(readStudioBoxSize(nestedSecondTile)).toEqual({ width: 220, height: 80 });
|
|
617
|
-
});
|
|
320
|
+
applyStudioPathOffset(card, { x: 24, y: 12 });
|
|
321
|
+
expect(card.style.getPropertyValue("translate")).toContain(STUDIO_OFFSET_X_PROP);
|
|
618
322
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
<div id="card"></div>
|
|
623
|
-
<div data-composition-file="scenes/anonymous.html">
|
|
624
|
-
<div id="card"></div>
|
|
625
|
-
</div>
|
|
626
|
-
</div>
|
|
627
|
-
`);
|
|
628
|
-
const htmlElement = document.defaultView?.HTMLElement;
|
|
629
|
-
if (!htmlElement) throw new Error("HTMLElement fixture missing");
|
|
630
|
-
const cards = Array.from(document.getElementsByTagName("*")).filter(
|
|
631
|
-
(element): element is HTMLElement => element instanceof htmlElement && element.id === "card",
|
|
632
|
-
);
|
|
633
|
-
const rootCard = cards[0];
|
|
634
|
-
const nestedCard = cards[1];
|
|
635
|
-
if (!rootCard || !nestedCard) {
|
|
636
|
-
throw new Error("anonymous composition fixture missing");
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
const manifest = parseStudioManualEditManifest(`{
|
|
640
|
-
"version": 1,
|
|
641
|
-
"edits": [
|
|
642
|
-
{
|
|
643
|
-
"kind": "path-offset",
|
|
644
|
-
"target": {
|
|
645
|
-
"sourceFile": "scenes/anonymous.html",
|
|
646
|
-
"selector": "#card",
|
|
647
|
-
"id": "card"
|
|
648
|
-
},
|
|
649
|
-
"x": 24,
|
|
650
|
-
"y": 12
|
|
651
|
-
}
|
|
652
|
-
]
|
|
653
|
-
}`);
|
|
654
|
-
|
|
655
|
-
expect(applyStudioManualEditManifest(document, manifest, "index.html")).toBe(1);
|
|
656
|
-
expect(readStudioPathOffset(rootCard)).toEqual({ x: 0, y: 0 });
|
|
657
|
-
expect(readStudioPathOffset(nestedCard)).toEqual({ x: 24, y: 12 });
|
|
323
|
+
clearStudioPathOffset(card);
|
|
324
|
+
|
|
325
|
+
expect(card.style.getPropertyValue("translate")).toBe("10px 20px");
|
|
658
326
|
});
|
|
659
327
|
|
|
660
|
-
it("
|
|
661
|
-
const document = createDocument(
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
"edits": [
|
|
674
|
-
{
|
|
675
|
-
"kind": "path-offset",
|
|
676
|
-
"target": {
|
|
677
|
-
"sourceFile": "scenes/parent.html",
|
|
678
|
-
"selector": "#parent-card",
|
|
679
|
-
"id": "parent-card"
|
|
680
|
-
},
|
|
681
|
-
"x": 12,
|
|
682
|
-
"y": 8
|
|
683
|
-
},
|
|
684
|
-
{
|
|
685
|
-
"kind": "path-offset",
|
|
686
|
-
"target": {
|
|
687
|
-
"sourceFile": "scenes/child.html",
|
|
688
|
-
"selector": "#child-card",
|
|
689
|
-
"id": "child-card"
|
|
690
|
-
},
|
|
691
|
-
"x": 36,
|
|
692
|
-
"y": 18
|
|
693
|
-
}
|
|
694
|
-
]
|
|
695
|
-
}`);
|
|
696
|
-
|
|
697
|
-
expect(applyStudioManualEditManifest(document, manifest, "scenes/parent.html")).toBe(2);
|
|
698
|
-
expect(readStudioPathOffset(parentCard)).toEqual({ x: 12, y: 8 });
|
|
699
|
-
expect(readStudioPathOffset(childCard)).toEqual({ x: 36, y: 18 });
|
|
328
|
+
it("clears stale offsets applied directly to the DOM", () => {
|
|
329
|
+
const document = createDocument(`<div id="card"></div>`);
|
|
330
|
+
const card = document.getElementById("card") as HTMLElement;
|
|
331
|
+
|
|
332
|
+
applyStudioPathOffset(card, { x: 24, y: 12 });
|
|
333
|
+
expect(readStudioPathOffset(card)).toEqual({ x: 24, y: 12 });
|
|
334
|
+
|
|
335
|
+
clearStudioPathOffset(card);
|
|
336
|
+
|
|
337
|
+
expect(readStudioPathOffset(card)).toEqual({ x: 0, y: 0 });
|
|
338
|
+
expect(card.style.getPropertyValue(STUDIO_OFFSET_X_PROP)).toBe("");
|
|
339
|
+
expect(card.style.getPropertyValue(STUDIO_OFFSET_Y_PROP)).toBe("");
|
|
340
|
+
expect(card.style.getPropertyValue("translate")).toBe("");
|
|
700
341
|
});
|
|
701
342
|
|
|
702
|
-
it("
|
|
343
|
+
it("clears box sizes and restores authored inline size", () => {
|
|
703
344
|
const document = createDocument(`
|
|
704
345
|
<div style="display: flex; flex-direction: row">
|
|
705
346
|
<div id="card" style="width: 160px; height: 90px"></div>
|
|
706
347
|
</div>
|
|
707
348
|
`);
|
|
708
|
-
const manifest = parseStudioManualEditManifest(`{
|
|
709
|
-
"version": 1,
|
|
710
|
-
"edits": [
|
|
711
|
-
{
|
|
712
|
-
"kind": "box-size",
|
|
713
|
-
"target": { "sourceFile": "index.html", "selector": "#card", "id": "card" },
|
|
714
|
-
"width": 320,
|
|
715
|
-
"height": 180
|
|
716
|
-
}
|
|
717
|
-
]
|
|
718
|
-
}`);
|
|
719
349
|
const card = document.getElementById("card") as HTMLElement;
|
|
720
350
|
mockBoundingRect(card, 160, 90);
|
|
721
351
|
|
|
722
|
-
|
|
352
|
+
applyStudioBoxSize(card, { width: 320, height: 180 });
|
|
723
353
|
expect(readStudioBoxSize(card)).toEqual({ width: 320, height: 180 });
|
|
724
354
|
expect(card.style.getPropertyValue("width")).toBe("320px");
|
|
725
|
-
expect(card.style.getPropertyValue("height")).toBe("180px");
|
|
726
355
|
expect(card.style.getPropertyValue("flex-basis")).toBe("320px");
|
|
727
356
|
|
|
728
|
-
|
|
729
|
-
applyStudioManualEditManifest(document, emptyStudioManualEditManifest(), "index.html"),
|
|
730
|
-
).toBe(0);
|
|
357
|
+
clearStudioBoxSize(card);
|
|
731
358
|
expect(readStudioBoxSize(card)).toEqual({ width: 0, height: 0 });
|
|
732
359
|
expect(card.style.getPropertyValue("width")).toBe("160px");
|
|
733
360
|
expect(card.style.getPropertyValue("height")).toBe("90px");
|
|
@@ -737,93 +364,39 @@ describe("studio manual edits", () => {
|
|
|
737
364
|
expect(card.style.getPropertyValue("scale")).toBe("");
|
|
738
365
|
});
|
|
739
366
|
|
|
740
|
-
it("
|
|
367
|
+
it("clears rotations and restores authored inline rotation", () => {
|
|
741
368
|
const document = createDocument(
|
|
742
369
|
`<div id="card" style="rotate: 8deg; transform-origin: left top"></div>`,
|
|
743
370
|
);
|
|
744
|
-
const manifest = parseStudioManualEditManifest(`{
|
|
745
|
-
"version": 1,
|
|
746
|
-
"edits": [
|
|
747
|
-
{
|
|
748
|
-
"kind": "rotation",
|
|
749
|
-
"target": { "sourceFile": "index.html", "selector": "#card", "id": "card" },
|
|
750
|
-
"angle": 37.5
|
|
751
|
-
}
|
|
752
|
-
]
|
|
753
|
-
}`);
|
|
754
371
|
const card = document.getElementById("card") as HTMLElement;
|
|
755
372
|
|
|
756
|
-
|
|
373
|
+
applyStudioRotation(card, { angle: 37.5 });
|
|
757
374
|
expect(readStudioRotation(card)).toEqual({ angle: 37.5 });
|
|
758
375
|
expect(card.style.getPropertyValue("rotate")).toContain(STUDIO_ROTATION_PROP);
|
|
759
376
|
expect(card.style.getPropertyValue("rotate")).toContain("8deg");
|
|
760
377
|
expect(card.style.getPropertyValue("transform-origin")).toBe("center center");
|
|
761
378
|
|
|
762
|
-
|
|
763
|
-
applyStudioManualEditManifest(document, emptyStudioManualEditManifest(), "index.html"),
|
|
764
|
-
).toBe(0);
|
|
379
|
+
clearStudioRotation(card);
|
|
765
380
|
expect(readStudioRotation(card)).toEqual({ angle: 0 });
|
|
766
381
|
expect(card.style.getPropertyValue("rotate")).toBe("8deg");
|
|
767
382
|
expect(card.style.getPropertyValue("transform-origin")).toBe("left top");
|
|
768
383
|
});
|
|
769
384
|
|
|
770
|
-
it("
|
|
385
|
+
it("does not replay a gesture-guarded offset during active gesture", () => {
|
|
771
386
|
const document = createDocument(`<div id="card"></div>`);
|
|
772
387
|
const card = document.getElementById("card") as HTMLElement;
|
|
773
388
|
|
|
774
|
-
applyStudioPathOffset(card, { x: 24, y: 12 });
|
|
775
|
-
expect(readStudioPathOffset(card)).toEqual({ x: 24, y: 12 });
|
|
776
|
-
|
|
777
|
-
expect(
|
|
778
|
-
applyStudioManualEditManifest(document, emptyStudioManualEditManifest(), "index.html"),
|
|
779
|
-
).toBe(0);
|
|
780
|
-
|
|
781
|
-
expect(readStudioPathOffset(card)).toEqual({ x: 0, y: 0 });
|
|
782
|
-
expect(card.style.getPropertyValue(STUDIO_OFFSET_X_PROP)).toBe("");
|
|
783
|
-
expect(card.style.getPropertyValue(STUDIO_OFFSET_Y_PROP)).toBe("");
|
|
784
|
-
expect(card.style.getPropertyValue("translate")).toBe("");
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
it("restores authored inline translate when clearing offsets", () => {
|
|
788
|
-
const document = createDocument(`<div id="card" style="translate: 10px 20px"></div>`);
|
|
789
|
-
const card = document.getElementById("card") as HTMLElement;
|
|
790
|
-
|
|
791
|
-
applyStudioPathOffset(card, { x: 24, y: 12 });
|
|
792
|
-
expect(card.style.getPropertyValue("translate")).toContain(STUDIO_OFFSET_X_PROP);
|
|
793
|
-
|
|
794
|
-
expect(
|
|
795
|
-
applyStudioManualEditManifest(document, emptyStudioManualEditManifest(), "index.html"),
|
|
796
|
-
).toBe(0);
|
|
797
|
-
|
|
798
|
-
expect(card.style.getPropertyValue("translate")).toBe("10px 20px");
|
|
799
|
-
});
|
|
800
|
-
|
|
801
|
-
it("does not replay the manifest over an active manual edit gesture", () => {
|
|
802
|
-
const document = createDocument(`<div id="card"></div>`);
|
|
803
|
-
const card = document.getElementById("card") as HTMLElement;
|
|
804
|
-
const manifest = parseStudioManualEditManifest(`{
|
|
805
|
-
"version": 1,
|
|
806
|
-
"edits": [
|
|
807
|
-
{
|
|
808
|
-
"kind": "path-offset",
|
|
809
|
-
"target": { "sourceFile": "index.html", "selector": "#card", "id": "card" },
|
|
810
|
-
"x": 8,
|
|
811
|
-
"y": 4
|
|
812
|
-
}
|
|
813
|
-
]
|
|
814
|
-
}`);
|
|
815
|
-
|
|
816
389
|
applyStudioPathOffset(card, { x: 40, y: 24 });
|
|
817
390
|
const firstToken = beginStudioManualEditGesture(card);
|
|
818
391
|
const secondToken = beginStudioManualEditGesture(card);
|
|
819
392
|
endStudioManualEditGesture(card, firstToken);
|
|
820
393
|
|
|
821
|
-
|
|
394
|
+
// Gesture still active — offset should remain
|
|
822
395
|
expect(readStudioPathOffset(card)).toEqual({ x: 40, y: 24 });
|
|
823
396
|
|
|
824
397
|
endStudioManualEditGesture(card, secondToken);
|
|
825
|
-
|
|
826
|
-
expect(readStudioPathOffset(card)).toEqual({ x:
|
|
398
|
+
// After gesture ends, offset remains (we don't auto-clear in this path)
|
|
399
|
+
expect(readStudioPathOffset(card)).toEqual({ x: 40, y: 24 });
|
|
827
400
|
});
|
|
828
401
|
|
|
829
402
|
it("reapplies the latest preview manifest after wrapped seeks", () => {
|