@hyperframes/studio 0.6.81 → 0.6.82
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/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
7
|
<title>HyperFrames Studio</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-l2BH41kD.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-DHcptK1_.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.82",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"@codemirror/view": "6.40.0",
|
|
32
32
|
"@phosphor-icons/react": "^2.1.10",
|
|
33
33
|
"mediabunny": "^1.45.3",
|
|
34
|
-
"@hyperframes/player": "0.6.
|
|
35
|
-
"@hyperframes/core": "0.6.
|
|
34
|
+
"@hyperframes/player": "0.6.82",
|
|
35
|
+
"@hyperframes/core": "0.6.82"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/react": "19",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"vite": "^6.4.2",
|
|
47
47
|
"vitest": "^3.2.4",
|
|
48
48
|
"zustand": "^5.0.0",
|
|
49
|
-
"@hyperframes/producer": "0.6.
|
|
49
|
+
"@hyperframes/producer": "0.6.82"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": "19",
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect } from "vitest";
|
|
4
|
+
import type { PatchOperation } from "../../utils/sourcePatcher";
|
|
5
|
+
import {
|
|
6
|
+
STUDIO_OFFSET_X_PROP,
|
|
7
|
+
STUDIO_OFFSET_Y_PROP,
|
|
8
|
+
STUDIO_WIDTH_PROP,
|
|
9
|
+
STUDIO_HEIGHT_PROP,
|
|
10
|
+
STUDIO_ROTATION_PROP,
|
|
11
|
+
STUDIO_PATH_OFFSET_ATTR,
|
|
12
|
+
STUDIO_BOX_SIZE_ATTR,
|
|
13
|
+
STUDIO_ROTATION_ATTR,
|
|
14
|
+
STUDIO_ROTATION_DRAFT_ATTR,
|
|
15
|
+
STUDIO_ORIGINAL_TRANSLATE_ATTR,
|
|
16
|
+
STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR,
|
|
17
|
+
STUDIO_ORIGINAL_WIDTH_ATTR,
|
|
18
|
+
STUDIO_ORIGINAL_HEIGHT_ATTR,
|
|
19
|
+
STUDIO_ORIGINAL_MIN_WIDTH_ATTR,
|
|
20
|
+
STUDIO_ORIGINAL_MIN_HEIGHT_ATTR,
|
|
21
|
+
STUDIO_ORIGINAL_MAX_WIDTH_ATTR,
|
|
22
|
+
STUDIO_ORIGINAL_MAX_HEIGHT_ATTR,
|
|
23
|
+
STUDIO_ORIGINAL_FLEX_BASIS_ATTR,
|
|
24
|
+
STUDIO_ORIGINAL_FLEX_GROW_ATTR,
|
|
25
|
+
STUDIO_ORIGINAL_FLEX_SHRINK_ATTR,
|
|
26
|
+
STUDIO_ORIGINAL_BOX_SIZING_ATTR,
|
|
27
|
+
STUDIO_ORIGINAL_SCALE_ATTR,
|
|
28
|
+
STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR,
|
|
29
|
+
STUDIO_ORIGINAL_DISPLAY_ATTR,
|
|
30
|
+
STUDIO_ORIGINAL_ROTATE_ATTR,
|
|
31
|
+
STUDIO_ORIGINAL_INLINE_ROTATE_ATTR,
|
|
32
|
+
STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR,
|
|
33
|
+
STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR,
|
|
34
|
+
} from "./manualEditsTypes";
|
|
35
|
+
import {
|
|
36
|
+
STUDIO_MOTION_ATTR,
|
|
37
|
+
STUDIO_MOTION_ORIGINAL_TRANSFORM_ATTR,
|
|
38
|
+
STUDIO_MOTION_ORIGINAL_OPACITY_ATTR,
|
|
39
|
+
STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR,
|
|
40
|
+
} from "./studioMotionTypes";
|
|
41
|
+
import {
|
|
42
|
+
buildPathOffsetPatches,
|
|
43
|
+
buildClearPathOffsetPatches,
|
|
44
|
+
buildBoxSizePatches,
|
|
45
|
+
buildClearBoxSizePatches,
|
|
46
|
+
buildRotationPatches,
|
|
47
|
+
buildClearRotationPatches,
|
|
48
|
+
buildMotionPatches,
|
|
49
|
+
buildClearMotionPatches,
|
|
50
|
+
} from "./manualEditsDomPatches";
|
|
51
|
+
|
|
52
|
+
/* ── helpers ── */
|
|
53
|
+
|
|
54
|
+
function div(): HTMLElement {
|
|
55
|
+
return document.createElement("div");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function opKey(op: PatchOperation): string {
|
|
59
|
+
return `${op.type}:${op.property}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function assertClearCoversKeys(buildOps: PatchOperation[], clearOps: PatchOperation[]): void {
|
|
63
|
+
const clearKeys = new Set(clearOps.map(opKey));
|
|
64
|
+
for (const op of buildOps) {
|
|
65
|
+
expect(clearKeys.has(opKey(op)), `clear missing key "${opKey(op)}"`).toBe(true);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ── Path offset ─────────────────────────────────────────────────────────── */
|
|
70
|
+
|
|
71
|
+
describe("buildPathOffsetPatches / buildClearPathOffsetPatches", () => {
|
|
72
|
+
function populatedPathEl(): HTMLElement {
|
|
73
|
+
const e = div();
|
|
74
|
+
e.style.setProperty(STUDIO_OFFSET_X_PROP, "10px");
|
|
75
|
+
e.style.setProperty(STUDIO_OFFSET_Y_PROP, "20px");
|
|
76
|
+
e.style.setProperty("translate", "10px 20px");
|
|
77
|
+
e.setAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR, "5px 10px");
|
|
78
|
+
e.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, "3px");
|
|
79
|
+
e.style.setProperty("display", "flex");
|
|
80
|
+
e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "block");
|
|
81
|
+
return e;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
it("populated: captures offset styles, attrs, display, and transform-display marker in declaration order", () => {
|
|
85
|
+
const ops = buildPathOffsetPatches(populatedPathEl());
|
|
86
|
+
expect(ops).toEqual([
|
|
87
|
+
{ type: "inline-style", property: STUDIO_OFFSET_X_PROP, value: "10px" },
|
|
88
|
+
{ type: "inline-style", property: STUDIO_OFFSET_Y_PROP, value: "20px" },
|
|
89
|
+
{ type: "inline-style", property: "translate", value: "10px 20px" },
|
|
90
|
+
{ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" },
|
|
91
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSLATE_ATTR, value: "5px 10px" },
|
|
92
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, value: "3px" },
|
|
93
|
+
{ type: "inline-style", property: "display", value: "flex" },
|
|
94
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: "block" },
|
|
95
|
+
]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("empty: bare element yields only the path-offset marker", () => {
|
|
99
|
+
expect(buildPathOffsetPatches(div())).toEqual([
|
|
100
|
+
{ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: "true" },
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("clear: restores translate from STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR and display from STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR", () => {
|
|
105
|
+
const e = div();
|
|
106
|
+
e.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, "5px");
|
|
107
|
+
e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "grid");
|
|
108
|
+
const ops = buildClearPathOffsetPatches(e);
|
|
109
|
+
expect(ops).toEqual([
|
|
110
|
+
{ type: "inline-style", property: STUDIO_OFFSET_X_PROP, value: null },
|
|
111
|
+
{ type: "inline-style", property: STUDIO_OFFSET_Y_PROP, value: null },
|
|
112
|
+
{ type: "inline-style", property: "translate", value: "5px" },
|
|
113
|
+
{ type: "attribute", property: STUDIO_PATH_OFFSET_ATTR, value: null },
|
|
114
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSLATE_ATTR, value: null },
|
|
115
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, value: null },
|
|
116
|
+
{ type: "inline-style", property: "display", value: "grid" },
|
|
117
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: null },
|
|
118
|
+
]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("clear: empty STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR coerces to null (translate not set to empty string)", () => {
|
|
122
|
+
const e = div();
|
|
123
|
+
e.setAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR, "");
|
|
124
|
+
const ops = buildClearPathOffsetPatches(e);
|
|
125
|
+
expect(ops.find((o) => o.property === "translate")?.value).toBeNull();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("build/clear symmetry: clear addresses every {type,property} key that build emits", () => {
|
|
129
|
+
const e = populatedPathEl();
|
|
130
|
+
assertClearCoversKeys(buildPathOffsetPatches(e), buildClearPathOffsetPatches(e));
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
/* ── Box size ────────────────────────────────────────────────────────────── */
|
|
135
|
+
|
|
136
|
+
describe("buildBoxSizePatches / buildClearBoxSizePatches", () => {
|
|
137
|
+
function populatedBoxEl(): HTMLElement {
|
|
138
|
+
const e = div();
|
|
139
|
+
e.style.setProperty(STUDIO_WIDTH_PROP, "300px");
|
|
140
|
+
e.style.setProperty(STUDIO_HEIGHT_PROP, "200px");
|
|
141
|
+
e.style.setProperty("width", "300px");
|
|
142
|
+
e.style.setProperty("height", "200px");
|
|
143
|
+
e.style.setProperty("min-width", "100px");
|
|
144
|
+
e.style.setProperty("min-height", "50px");
|
|
145
|
+
e.style.setProperty("max-width", "500px");
|
|
146
|
+
e.style.setProperty("max-height", "400px");
|
|
147
|
+
e.style.setProperty("flex-basis", "auto");
|
|
148
|
+
e.style.setProperty("flex-grow", "1");
|
|
149
|
+
e.style.setProperty("flex-shrink", "0");
|
|
150
|
+
e.style.setProperty("box-sizing", "border-box");
|
|
151
|
+
e.style.setProperty("scale", "1.5");
|
|
152
|
+
e.style.setProperty("transform-origin", "center");
|
|
153
|
+
e.style.setProperty("display", "block");
|
|
154
|
+
e.setAttribute(STUDIO_ORIGINAL_WIDTH_ATTR, "250px");
|
|
155
|
+
e.setAttribute(STUDIO_ORIGINAL_HEIGHT_ATTR, "150px");
|
|
156
|
+
e.setAttribute(STUDIO_ORIGINAL_MIN_WIDTH_ATTR, "0px");
|
|
157
|
+
e.setAttribute(STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, "0px");
|
|
158
|
+
e.setAttribute(STUDIO_ORIGINAL_MAX_WIDTH_ATTR, "none");
|
|
159
|
+
e.setAttribute(STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, "none");
|
|
160
|
+
e.setAttribute(STUDIO_ORIGINAL_FLEX_BASIS_ATTR, "0px");
|
|
161
|
+
e.setAttribute(STUDIO_ORIGINAL_FLEX_GROW_ATTR, "0");
|
|
162
|
+
e.setAttribute(STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, "1");
|
|
163
|
+
e.setAttribute(STUDIO_ORIGINAL_BOX_SIZING_ATTR, "content-box");
|
|
164
|
+
e.setAttribute(STUDIO_ORIGINAL_SCALE_ATTR, "1");
|
|
165
|
+
e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR, "50% 50%");
|
|
166
|
+
e.setAttribute(STUDIO_ORIGINAL_DISPLAY_ATTR, "flex");
|
|
167
|
+
e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "");
|
|
168
|
+
return e;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
it("populated: captures studio-width/height, all BOX_SIZE_STYLE_PROPS, marker, and all orig attrs", () => {
|
|
172
|
+
const ops = buildBoxSizePatches(populatedBoxEl());
|
|
173
|
+
expect(ops).toEqual([
|
|
174
|
+
{ type: "inline-style", property: STUDIO_WIDTH_PROP, value: "300px" },
|
|
175
|
+
{ type: "inline-style", property: STUDIO_HEIGHT_PROP, value: "200px" },
|
|
176
|
+
{ type: "inline-style", property: "width", value: "300px" },
|
|
177
|
+
{ type: "inline-style", property: "height", value: "200px" },
|
|
178
|
+
{ type: "inline-style", property: "min-width", value: "100px" },
|
|
179
|
+
{ type: "inline-style", property: "min-height", value: "50px" },
|
|
180
|
+
{ type: "inline-style", property: "max-width", value: "500px" },
|
|
181
|
+
{ type: "inline-style", property: "max-height", value: "400px" },
|
|
182
|
+
{ type: "inline-style", property: "flex-basis", value: "auto" },
|
|
183
|
+
{ type: "inline-style", property: "flex-grow", value: "1" },
|
|
184
|
+
{ type: "inline-style", property: "flex-shrink", value: "0" },
|
|
185
|
+
{ type: "inline-style", property: "box-sizing", value: "border-box" },
|
|
186
|
+
{ type: "inline-style", property: "scale", value: "1.5" },
|
|
187
|
+
{ type: "inline-style", property: "transform-origin", value: "center" },
|
|
188
|
+
{ type: "inline-style", property: "display", value: "block" },
|
|
189
|
+
{ type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: "true" },
|
|
190
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_WIDTH_ATTR, value: "250px" },
|
|
191
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_HEIGHT_ATTR, value: "150px" },
|
|
192
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_MIN_WIDTH_ATTR, value: "0px" },
|
|
193
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, value: "0px" },
|
|
194
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_MAX_WIDTH_ATTR, value: "none" },
|
|
195
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, value: "none" },
|
|
196
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_FLEX_BASIS_ATTR, value: "0px" },
|
|
197
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_FLEX_GROW_ATTR, value: "0" },
|
|
198
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, value: "1" },
|
|
199
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_BOX_SIZING_ATTR, value: "content-box" },
|
|
200
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_SCALE_ATTR, value: "1" },
|
|
201
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR, value: "50% 50%" },
|
|
202
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_DISPLAY_ATTR, value: "flex" },
|
|
203
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: "" },
|
|
204
|
+
]);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("empty: bare element yields only the box-size marker", () => {
|
|
208
|
+
expect(buildBoxSizePatches(div())).toEqual([
|
|
209
|
+
{ type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: "true" },
|
|
210
|
+
]);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("clear(populated): ops follow interleaved restore-then-null order for every orig attr", () => {
|
|
214
|
+
const ops = buildClearBoxSizePatches(populatedBoxEl());
|
|
215
|
+
expect(ops).toEqual([
|
|
216
|
+
{ type: "inline-style", property: STUDIO_WIDTH_PROP, value: null },
|
|
217
|
+
{ type: "inline-style", property: STUDIO_HEIGHT_PROP, value: null },
|
|
218
|
+
{ type: "attribute", property: STUDIO_BOX_SIZE_ATTR, value: null },
|
|
219
|
+
{ type: "inline-style", property: "width", value: "250px" },
|
|
220
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_WIDTH_ATTR, value: null },
|
|
221
|
+
{ type: "inline-style", property: "height", value: "150px" },
|
|
222
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_HEIGHT_ATTR, value: null },
|
|
223
|
+
{ type: "inline-style", property: "min-width", value: "0px" },
|
|
224
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_MIN_WIDTH_ATTR, value: null },
|
|
225
|
+
{ type: "inline-style", property: "min-height", value: "0px" },
|
|
226
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_MIN_HEIGHT_ATTR, value: null },
|
|
227
|
+
{ type: "inline-style", property: "max-width", value: "none" },
|
|
228
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_MAX_WIDTH_ATTR, value: null },
|
|
229
|
+
{ type: "inline-style", property: "max-height", value: "none" },
|
|
230
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_MAX_HEIGHT_ATTR, value: null },
|
|
231
|
+
{ type: "inline-style", property: "flex-basis", value: "0px" },
|
|
232
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_FLEX_BASIS_ATTR, value: null },
|
|
233
|
+
{ type: "inline-style", property: "flex-grow", value: "0" },
|
|
234
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_FLEX_GROW_ATTR, value: null },
|
|
235
|
+
{ type: "inline-style", property: "flex-shrink", value: "1" },
|
|
236
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_FLEX_SHRINK_ATTR, value: null },
|
|
237
|
+
{ type: "inline-style", property: "box-sizing", value: "content-box" },
|
|
238
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_BOX_SIZING_ATTR, value: null },
|
|
239
|
+
{ type: "inline-style", property: "scale", value: "1" },
|
|
240
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_SCALE_ATTR, value: null },
|
|
241
|
+
{ type: "inline-style", property: "transform-origin", value: "50% 50%" },
|
|
242
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_ORIGIN_ATTR, value: null },
|
|
243
|
+
{ type: "inline-style", property: "display", value: "flex" },
|
|
244
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_DISPLAY_ATTR, value: null },
|
|
245
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: null },
|
|
246
|
+
]);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("clear: empty orig attr coerces to null (style is removed rather than set to empty string)", () => {
|
|
250
|
+
const e = div();
|
|
251
|
+
e.setAttribute(STUDIO_ORIGINAL_WIDTH_ATTR, "");
|
|
252
|
+
const ops = buildClearBoxSizePatches(e);
|
|
253
|
+
expect(ops.find((o) => o.property === "width")?.value).toBeNull();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("clear: bare element emits only null ops — no style restores fire when orig attrs are absent", () => {
|
|
257
|
+
const ops = buildClearBoxSizePatches(div());
|
|
258
|
+
// 3 fixed (studio-width, studio-height, box-size marker) + 14 attr-null pushes (one per BOX_SIZE_ORIG_ATTR)
|
|
259
|
+
expect(ops).toHaveLength(17);
|
|
260
|
+
expect(ops.every((op) => op.value === null)).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("build/clear symmetry: clear addresses every {type,property} key that build emits", () => {
|
|
264
|
+
const e = populatedBoxEl();
|
|
265
|
+
assertClearCoversKeys(buildBoxSizePatches(e), buildClearBoxSizePatches(e));
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
/* ── Rotation ────────────────────────────────────────────────────────────── */
|
|
270
|
+
|
|
271
|
+
describe("buildRotationPatches / buildClearRotationPatches", () => {
|
|
272
|
+
function populatedRotEl(): HTMLElement {
|
|
273
|
+
const e = div();
|
|
274
|
+
e.style.setProperty(STUDIO_ROTATION_PROP, "45");
|
|
275
|
+
e.style.setProperty("rotate", "45deg");
|
|
276
|
+
e.style.setProperty("transform-origin", "left center");
|
|
277
|
+
e.style.setProperty("display", "block");
|
|
278
|
+
e.setAttribute(STUDIO_ORIGINAL_ROTATE_ATTR, "0deg");
|
|
279
|
+
e.setAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, "0deg");
|
|
280
|
+
e.setAttribute(STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, "center center");
|
|
281
|
+
e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "flex");
|
|
282
|
+
return e;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
it("populated: captures rotation styles, attrs, and transform-display marker in declaration order", () => {
|
|
286
|
+
const ops = buildRotationPatches(populatedRotEl());
|
|
287
|
+
expect(ops).toEqual([
|
|
288
|
+
{ type: "inline-style", property: STUDIO_ROTATION_PROP, value: "45" },
|
|
289
|
+
{ type: "inline-style", property: "rotate", value: "45deg" },
|
|
290
|
+
{ type: "inline-style", property: "transform-origin", value: "left center" },
|
|
291
|
+
{ type: "inline-style", property: "display", value: "block" },
|
|
292
|
+
{ type: "attribute", property: STUDIO_ROTATION_ATTR, value: "true" },
|
|
293
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_ROTATE_ATTR, value: "0deg" },
|
|
294
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, value: "0deg" },
|
|
295
|
+
{
|
|
296
|
+
type: "attribute",
|
|
297
|
+
property: STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR,
|
|
298
|
+
value: "center center",
|
|
299
|
+
},
|
|
300
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: "flex" },
|
|
301
|
+
]);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("empty: bare element yields only the rotation marker", () => {
|
|
305
|
+
expect(buildRotationPatches(div())).toEqual([
|
|
306
|
+
{ type: "attribute", property: STUDIO_ROTATION_ATTR, value: "true" },
|
|
307
|
+
]);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("clear: restores rotate and transform-origin from orig attrs, nulls draft attr", () => {
|
|
311
|
+
const e = div();
|
|
312
|
+
e.setAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, "30deg");
|
|
313
|
+
e.setAttribute(STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, "top left");
|
|
314
|
+
e.setAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, "grid");
|
|
315
|
+
const ops = buildClearRotationPatches(e);
|
|
316
|
+
expect(ops).toEqual([
|
|
317
|
+
{ type: "inline-style", property: STUDIO_ROTATION_PROP, value: null },
|
|
318
|
+
{ type: "inline-style", property: "rotate", value: "30deg" },
|
|
319
|
+
{ type: "inline-style", property: "transform-origin", value: "top left" },
|
|
320
|
+
{ type: "attribute", property: STUDIO_ROTATION_ATTR, value: null },
|
|
321
|
+
{ type: "attribute", property: STUDIO_ROTATION_DRAFT_ATTR, value: null },
|
|
322
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_ROTATE_ATTR, value: null },
|
|
323
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, value: null },
|
|
324
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR, value: null },
|
|
325
|
+
{ type: "inline-style", property: "display", value: "grid" },
|
|
326
|
+
{ type: "attribute", property: STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR, value: null },
|
|
327
|
+
]);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("clear: absent STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR yields null for transform-origin", () => {
|
|
331
|
+
const ops = buildClearRotationPatches(div());
|
|
332
|
+
expect(ops.find((o) => o.property === "transform-origin")?.value).toBeNull();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("clear: empty STUDIO_ORIGINAL_INLINE_ROTATE_ATTR coerces to null (rotate not set to empty string)", () => {
|
|
336
|
+
const e = div();
|
|
337
|
+
e.setAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR, "");
|
|
338
|
+
const ops = buildClearRotationPatches(e);
|
|
339
|
+
expect(ops.find((o) => o.property === "rotate")?.value).toBeNull();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("build/clear symmetry: clear addresses every {type,property} key that build emits", () => {
|
|
343
|
+
const e = populatedRotEl();
|
|
344
|
+
assertClearCoversKeys(buildRotationPatches(e), buildClearRotationPatches(e));
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
/* ── Motion ──────────────────────────────────────────────────────────────── */
|
|
349
|
+
|
|
350
|
+
describe("buildMotionPatches / buildClearMotionPatches", () => {
|
|
351
|
+
const MOTION_JSON = '{"kind":"gsap-motion","start":0,"duration":1}';
|
|
352
|
+
|
|
353
|
+
function populatedMotionEl(): HTMLElement {
|
|
354
|
+
const e = div();
|
|
355
|
+
e.setAttribute(STUDIO_MOTION_ATTR, MOTION_JSON);
|
|
356
|
+
e.setAttribute(STUDIO_MOTION_ORIGINAL_TRANSFORM_ATTR, "translateX(0)");
|
|
357
|
+
e.setAttribute(STUDIO_MOTION_ORIGINAL_OPACITY_ATTR, "1");
|
|
358
|
+
e.setAttribute(STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR, "visible");
|
|
359
|
+
return e;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
it("populated: captures motion JSON and all three original attrs when motion attr is present", () => {
|
|
363
|
+
const ops = buildMotionPatches(populatedMotionEl());
|
|
364
|
+
expect(ops).toEqual([
|
|
365
|
+
{ type: "attribute", property: STUDIO_MOTION_ATTR, value: MOTION_JSON },
|
|
366
|
+
{
|
|
367
|
+
type: "attribute",
|
|
368
|
+
property: STUDIO_MOTION_ORIGINAL_TRANSFORM_ATTR,
|
|
369
|
+
value: "translateX(0)",
|
|
370
|
+
},
|
|
371
|
+
{ type: "attribute", property: STUDIO_MOTION_ORIGINAL_OPACITY_ATTR, value: "1" },
|
|
372
|
+
{ type: "attribute", property: STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR, value: "visible" },
|
|
373
|
+
]);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("empty: returns [] when STUDIO_MOTION_ATTR is absent", () => {
|
|
377
|
+
expect(buildMotionPatches(div())).toEqual([]);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("clear: always nulls all four motion attrs regardless of element state", () => {
|
|
381
|
+
const expected = [
|
|
382
|
+
{ type: "attribute", property: STUDIO_MOTION_ATTR, value: null },
|
|
383
|
+
{ type: "attribute", property: STUDIO_MOTION_ORIGINAL_TRANSFORM_ATTR, value: null },
|
|
384
|
+
{ type: "attribute", property: STUDIO_MOTION_ORIGINAL_OPACITY_ATTR, value: null },
|
|
385
|
+
{ type: "attribute", property: STUDIO_MOTION_ORIGINAL_VISIBILITY_ATTR, value: null },
|
|
386
|
+
];
|
|
387
|
+
expect(buildClearMotionPatches(div())).toEqual(expected);
|
|
388
|
+
expect(buildClearMotionPatches(populatedMotionEl())).toEqual(expected);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("build/clear symmetry: clear addresses every {type,property} key that build emits", () => {
|
|
392
|
+
const e = populatedMotionEl();
|
|
393
|
+
assertClearCoversKeys(buildMotionPatches(e), buildClearMotionPatches(e));
|
|
394
|
+
});
|
|
395
|
+
});
|
|
@@ -516,3 +516,18 @@ describe("motion attribute round-trip via sourcePatcher", () => {
|
|
|
516
516
|
expect(JSON.parse(readBack!)).toEqual(motion);
|
|
517
517
|
});
|
|
518
518
|
});
|
|
519
|
+
|
|
520
|
+
// T3 — id-based targeting (spec for R1).
|
|
521
|
+
// R1 adds `hfId?: string` to PatchTarget and a `[data-hf-id="…"]` lookup branch
|
|
522
|
+
// in findTagByTarget. Convert from it.todo to real assertions in the R1 PR.
|
|
523
|
+
describe("T3 — hfId targeting (spec for R1)", () => {
|
|
524
|
+
it.todo("updates inline style by data-hf-id");
|
|
525
|
+
|
|
526
|
+
it.todo("updates text content by data-hf-id");
|
|
527
|
+
|
|
528
|
+
it.todo("updates attribute by data-hf-id");
|
|
529
|
+
|
|
530
|
+
it.todo("data-hf-id attribute is preserved after a style patch");
|
|
531
|
+
|
|
532
|
+
it.todo("hfId lookup falls through to selector when hfId not found");
|
|
533
|
+
});
|