@hyperframes/studio 0.6.50 → 0.6.52
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-B4Cr7MVx.js → index-Bvy50smZ.js} +32 -32
- package/dist/index.html +1 -1
- package/package.json +4 -4
- package/src/components/editor/PropertyPanel.tsx +17 -6
- package/src/components/editor/manualEdits.ts +1 -0
- package/src/components/editor/manualEditsDom.ts +14 -0
- package/src/components/editor/manualOffsetDrag.test.ts +81 -0
- package/src/components/editor/manualOffsetDrag.ts +7 -1
- package/src/hooks/useDomEditCommits.ts +7 -5
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-Bvy50smZ.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-SKRp8mGz.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.52",
|
|
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/core": "0.6.
|
|
35
|
-
"@hyperframes/player": "0.6.
|
|
34
|
+
"@hyperframes/core": "0.6.52",
|
|
35
|
+
"@hyperframes/player": "0.6.52"
|
|
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.52"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": "19",
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { memo } from "react";
|
|
2
2
|
import { Clock, Eye, Layers, MessageSquare, Move, X } from "../../icons/SystemIcons";
|
|
3
3
|
import { type DomEditSelection } from "./domEditing";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
readStudioBoxSize,
|
|
6
|
+
readStudioPathOffset,
|
|
7
|
+
readStudioRotation,
|
|
8
|
+
readGsapTranslateFromTransform,
|
|
9
|
+
} from "./manualEdits";
|
|
5
10
|
import type { ImportedFontAsset } from "./fontAssets";
|
|
6
11
|
import {
|
|
7
12
|
EMPTY_STYLES,
|
|
@@ -181,6 +186,11 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
181
186
|
const sourceLabel = element.id ? `#${element.id}` : element.selector;
|
|
182
187
|
const showEditableSections = element.capabilities.canEditStyles;
|
|
183
188
|
const manualOffset = readStudioPathOffset(element.element);
|
|
189
|
+
const gsapTranslate = readGsapTranslateFromTransform(element.element);
|
|
190
|
+
const visualOffset = {
|
|
191
|
+
x: manualOffset.x + gsapTranslate.x,
|
|
192
|
+
y: manualOffset.y + gsapTranslate.y,
|
|
193
|
+
};
|
|
184
194
|
const manualSize = readStudioBoxSize(element.element);
|
|
185
195
|
const resolvedWidth =
|
|
186
196
|
manualSize.width > 0
|
|
@@ -194,10 +204,11 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
194
204
|
const commitManualOffset = (axis: "x" | "y", nextValue: string) => {
|
|
195
205
|
const parsed = parsePxMetricValue(nextValue);
|
|
196
206
|
if (parsed == null) return;
|
|
197
|
-
const
|
|
207
|
+
const currentRaw = readStudioPathOffset(element.element);
|
|
208
|
+
const currentGsap = readGsapTranslateFromTransform(element.element);
|
|
198
209
|
onSetManualOffset(element, {
|
|
199
|
-
x: axis === "x" ? parsed :
|
|
200
|
-
y: axis === "y" ? parsed :
|
|
210
|
+
x: axis === "x" ? parsed - currentGsap.x : currentRaw.x,
|
|
211
|
+
y: axis === "y" ? parsed - currentGsap.y : currentRaw.y,
|
|
201
212
|
});
|
|
202
213
|
};
|
|
203
214
|
|
|
@@ -289,14 +300,14 @@ export const PropertyPanel = memo(function PropertyPanel({
|
|
|
289
300
|
<div className={RESPONSIVE_GRID}>
|
|
290
301
|
<MetricField
|
|
291
302
|
label="X"
|
|
292
|
-
value={formatPxMetricValue(
|
|
303
|
+
value={formatPxMetricValue(visualOffset.x)}
|
|
293
304
|
disabled={manualOffsetEditingDisabled}
|
|
294
305
|
scrub
|
|
295
306
|
onCommit={(next) => commitManualOffset("x", next)}
|
|
296
307
|
/>
|
|
297
308
|
<MetricField
|
|
298
309
|
label="Y"
|
|
299
|
-
value={formatPxMetricValue(
|
|
310
|
+
value={formatPxMetricValue(visualOffset.y)}
|
|
300
311
|
disabled={manualOffsetEditingDisabled}
|
|
301
312
|
scrub
|
|
302
313
|
onCommit={(next) => commitManualOffset("y", next)}
|
|
@@ -219,6 +219,20 @@ function isIdentityAfterTranslateStrip(m: DOMMatrix): boolean {
|
|
|
219
219
|
return m.is2D && m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
export function readGsapTranslateFromTransform(element: HTMLElement): { x: number; y: number } {
|
|
223
|
+
const transform = element.style.getPropertyValue("transform");
|
|
224
|
+
if (!transform || transform === "none") return { x: 0, y: 0 };
|
|
225
|
+
const DOMMatrixCtor = (element.ownerDocument.defaultView as (Window & typeof globalThis) | null)
|
|
226
|
+
?.DOMMatrix;
|
|
227
|
+
if (!DOMMatrixCtor) return { x: 0, y: 0 };
|
|
228
|
+
try {
|
|
229
|
+
const m = new DOMMatrixCtor(transform);
|
|
230
|
+
return { x: m.m41, y: m.m42 };
|
|
231
|
+
} catch {
|
|
232
|
+
return { x: 0, y: 0 };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
222
236
|
function stripGsapTranslateFromTransform(element: HTMLElement): void {
|
|
223
237
|
const transform = element.style.getPropertyValue("transform");
|
|
224
238
|
if (!transform || transform === "none") return;
|
|
@@ -2,6 +2,7 @@ import { Window } from "happy-dom";
|
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
import {
|
|
4
4
|
applyManualOffsetDragMatrix,
|
|
5
|
+
createManualOffsetDragMember,
|
|
5
6
|
invertManualOffsetDragMatrix,
|
|
6
7
|
measureManualOffsetDragScreenToOffsetMatrix,
|
|
7
8
|
resolveManualOffsetForPointerDelta,
|
|
@@ -138,3 +139,83 @@ describe("measureManualOffsetDragScreenToOffsetMatrix", () => {
|
|
|
138
139
|
expect(measured.ok).toBe(false);
|
|
139
140
|
});
|
|
140
141
|
});
|
|
142
|
+
|
|
143
|
+
describe("createManualOffsetDragMember GSAP translate compensation", () => {
|
|
144
|
+
it("folds GSAP translate from element.style.transform into initialOffset", () => {
|
|
145
|
+
const window = new Window();
|
|
146
|
+
const element = window.document.createElement("div");
|
|
147
|
+
window.document.body.append(element);
|
|
148
|
+
|
|
149
|
+
element.style.setProperty("transform", "translate(0px, -20px)");
|
|
150
|
+
|
|
151
|
+
element.getBoundingClientRect = () => {
|
|
152
|
+
const offsetX = Number.parseFloat(element.style.getPropertyValue(STUDIO_OFFSET_X_PROP)) || 0;
|
|
153
|
+
const offsetY = Number.parseFloat(element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP)) || 0;
|
|
154
|
+
return new window.DOMRect(10 + offsetX, 20 + offsetY, 100, 50);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const result = createManualOffsetDragMember({
|
|
158
|
+
key: "test",
|
|
159
|
+
selection: { element } as never,
|
|
160
|
+
element,
|
|
161
|
+
rect: { left: 10, top: 20, width: 100, height: 50, editScaleX: 1, editScaleY: 1 },
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(result.ok).toBe(true);
|
|
165
|
+
if (!result.ok) return;
|
|
166
|
+
expect(result.member.initialOffset.x).toBe(0);
|
|
167
|
+
expect(result.member.initialOffset.y).toBe(-20);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("leaves initialOffset unchanged when no GSAP transform is present", () => {
|
|
171
|
+
const window = new Window();
|
|
172
|
+
const element = window.document.createElement("div");
|
|
173
|
+
window.document.body.append(element);
|
|
174
|
+
|
|
175
|
+
element.getBoundingClientRect = () => {
|
|
176
|
+
const offsetX = Number.parseFloat(element.style.getPropertyValue(STUDIO_OFFSET_X_PROP)) || 0;
|
|
177
|
+
const offsetY = Number.parseFloat(element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP)) || 0;
|
|
178
|
+
return new window.DOMRect(10 + offsetX, 20 + offsetY, 100, 50);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const result = createManualOffsetDragMember({
|
|
182
|
+
key: "test",
|
|
183
|
+
selection: { element } as never,
|
|
184
|
+
element,
|
|
185
|
+
rect: { left: 10, top: 20, width: 100, height: 50, editScaleX: 1, editScaleY: 1 },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(result.ok).toBe(true);
|
|
189
|
+
if (!result.ok) return;
|
|
190
|
+
expect(result.member.initialOffset.x).toBe(0);
|
|
191
|
+
expect(result.member.initialOffset.y).toBe(0);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("combines existing manual offset with GSAP translate", () => {
|
|
195
|
+
const window = new Window();
|
|
196
|
+
const element = window.document.createElement("div");
|
|
197
|
+
window.document.body.append(element);
|
|
198
|
+
|
|
199
|
+
element.style.setProperty(STUDIO_OFFSET_X_PROP, "30px");
|
|
200
|
+
element.style.setProperty(STUDIO_OFFSET_Y_PROP, "10px");
|
|
201
|
+
element.style.setProperty("transform", "translate(50px, -15px)");
|
|
202
|
+
|
|
203
|
+
element.getBoundingClientRect = () => {
|
|
204
|
+
const offsetX = Number.parseFloat(element.style.getPropertyValue(STUDIO_OFFSET_X_PROP)) || 0;
|
|
205
|
+
const offsetY = Number.parseFloat(element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP)) || 0;
|
|
206
|
+
return new window.DOMRect(10 + offsetX, 20 + offsetY, 100, 50);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const result = createManualOffsetDragMember({
|
|
210
|
+
key: "test",
|
|
211
|
+
selection: { element } as never,
|
|
212
|
+
element,
|
|
213
|
+
rect: { left: 10, top: 20, width: 100, height: 50, editScaleX: 1, editScaleY: 1 },
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(result.ok).toBe(true);
|
|
217
|
+
if (!result.ok) return;
|
|
218
|
+
expect(result.member.initialOffset.x).toBe(80);
|
|
219
|
+
expect(result.member.initialOffset.y).toBe(-5);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
beginStudioManualEditGesture,
|
|
6
6
|
captureStudioPathOffset,
|
|
7
7
|
endStudioManualEditGesture,
|
|
8
|
+
readGsapTranslateFromTransform,
|
|
8
9
|
readStudioPathOffset,
|
|
9
10
|
restoreStudioPathOffset,
|
|
10
11
|
type StudioPathOffsetSnapshot,
|
|
@@ -231,7 +232,12 @@ export function createManualOffsetDragMember(input: {
|
|
|
231
232
|
element: HTMLElement;
|
|
232
233
|
rect: ManualOffsetDragRect;
|
|
233
234
|
}): ManualOffsetDragMemberResult {
|
|
234
|
-
const
|
|
235
|
+
const rawOffset = readStudioPathOffset(input.element);
|
|
236
|
+
const gsapTranslate = readGsapTranslateFromTransform(input.element);
|
|
237
|
+
const initialOffset = {
|
|
238
|
+
x: rawOffset.x + gsapTranslate.x,
|
|
239
|
+
y: rawOffset.y + gsapTranslate.y,
|
|
240
|
+
};
|
|
235
241
|
const initialPathOffset = captureStudioPathOffset(input.element);
|
|
236
242
|
const gestureToken = beginStudioManualEditGesture(input.element);
|
|
237
243
|
const measured = measureManualOffsetDragScreenToOffsetMatrix(input.element, initialOffset);
|
|
@@ -155,6 +155,11 @@ export function useDomEditCommits({
|
|
|
155
155
|
selectorIndex: selection.selectorIndex,
|
|
156
156
|
};
|
|
157
157
|
|
|
158
|
+
// Mark the save timestamp before the file write so the SSE file-change
|
|
159
|
+
// handler suppresses the reload even if the event arrives before the
|
|
160
|
+
// response (the server writes the file and emits SSE during the fetch).
|
|
161
|
+
domEditSaveTimestampRef.current = Date.now();
|
|
162
|
+
|
|
158
163
|
const patchResponse = await fetch(
|
|
159
164
|
`/api/projects/${pid}/file-mutations/patch-element/${encodeURIComponent(targetPath)}`,
|
|
160
165
|
{
|
|
@@ -193,9 +198,7 @@ export function useDomEditCommits({
|
|
|
193
198
|
files: { [targetPath]: { before: originalContent, after: finalContent } },
|
|
194
199
|
});
|
|
195
200
|
|
|
196
|
-
if (options?.skipRefresh) {
|
|
197
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
198
|
-
} else {
|
|
201
|
+
if (!options?.skipRefresh) {
|
|
199
202
|
reloadPreview();
|
|
200
203
|
}
|
|
201
204
|
},
|
|
@@ -427,6 +430,7 @@ export function useDomEditCommits({
|
|
|
427
430
|
throw new Error("Selected element has no patchable target");
|
|
428
431
|
}
|
|
429
432
|
|
|
433
|
+
domEditSaveTimestampRef.current = Date.now();
|
|
430
434
|
const removeResponse = await fetch(
|
|
431
435
|
`/api/projects/${pid}/file-mutations/remove-element/${encodeURIComponent(targetPath)}`,
|
|
432
436
|
{
|
|
@@ -440,8 +444,6 @@ export function useDomEditCommits({
|
|
|
440
444
|
const removeData = (await removeResponse.json()) as { changed?: boolean; content?: string };
|
|
441
445
|
const patchedContent =
|
|
442
446
|
typeof removeData.content === "string" ? removeData.content : originalContent;
|
|
443
|
-
|
|
444
|
-
domEditSaveTimestampRef.current = Date.now();
|
|
445
447
|
await saveProjectFilesWithHistory({
|
|
446
448
|
projectId: pid,
|
|
447
449
|
label: "Delete element",
|