@hyperframes/studio 0.8.99 → 0.8.100
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-Rml34FRW.js → hyperframes-player-fWt87gyh.js} +1 -1
- package/dist/assets/{index-C_fi-jTe.js → index-D34Oy5DN.js} +1 -1
- package/dist/assets/{index-CVDArg3m.js → index-D6TjRAvz.js} +176 -176
- package/dist/assets/{index-D9YzDuBx.js → index-DofdzdxJ.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/index.js +423 -312
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/components/editor/PropertyPanel.tsx +3 -3
- package/src/components/editor/domEditOverlayStartGesture.test.ts +83 -0
- package/src/components/editor/domEditOverlayStartGesture.ts +6 -4
- package/src/components/editor/manualOffsetDrag.test.ts +70 -0
- package/src/components/editor/manualOffsetDrag.ts +50 -40
- package/src/components/editor/plainTranslate.test.ts +62 -0
- package/src/components/editor/plainTranslate.ts +85 -0
- package/src/components/editor/propertyPanelTransformCommit.ts +3 -2
- package/src/components/editor/useDomEditNudge.ts +1 -1
- package/src/hooks/elementOffsetStager.ts +33 -2
- package/src/hooks/gsapRuntimeBridge.ts +1 -17
- package/src/hooks/gsapRuntimeKeyframes.ts +35 -0
- package/src/hooks/useDomEditCommits.test.tsx +12 -3
- package/src/hooks/useDomEditCommits.ts +2 -1
- package/src/hooks/useDomEditTextCommits.test.tsx +62 -5
- package/src/hooks/useDomEditTextCommits.ts +35 -35
- package/src/hooks/useDomGeometryCommit.test.tsx +62 -4
- package/src/hooks/useDomGeometryCommits.test.tsx +4 -2
- package/src/hooks/useGsapAwareEditing.test.tsx +46 -18
- package/src/hooks/useGsapAwareEditing.ts +11 -11
|
@@ -22,7 +22,6 @@ const mocks = vi.hoisted(() => ({
|
|
|
22
22
|
|
|
23
23
|
vi.mock("./gsapResizeIntercept", () => ({ tryGsapResizeIntercept: mocks.resize }));
|
|
24
24
|
vi.mock("./gsapRuntimeBridge", () => ({
|
|
25
|
-
POSITION_CHANNELS: ["x", "y"],
|
|
26
25
|
tryGsapDragIntercept: mocks.drag,
|
|
27
26
|
tryGsapRotationIntercept: vi.fn(),
|
|
28
27
|
}));
|
|
@@ -47,6 +46,11 @@ afterEach(() => {
|
|
|
47
46
|
vi.clearAllMocks();
|
|
48
47
|
});
|
|
49
48
|
|
|
49
|
+
/** An element GSAP already renders the transform of, so moves take the GSAP route. */
|
|
50
|
+
function gsapPositioned(tag: string): HTMLElement {
|
|
51
|
+
return Object.assign(document.createElement(tag), { _gsap: { renderTransform: () => {} } });
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
function mountResizeHandler(
|
|
51
55
|
animations: GsapAnimation[],
|
|
52
56
|
targetAnimations: GsapAnimation[] = animations,
|
|
@@ -150,6 +154,28 @@ function mountGroupHandler({
|
|
|
150
154
|
};
|
|
151
155
|
}
|
|
152
156
|
|
|
157
|
+
describe("useGsapAwareEditing moves of an element GSAP does not position", () => {
|
|
158
|
+
it("saves the move on the element itself, with no GSAP write and no animation read", async () => {
|
|
159
|
+
const save = vi.fn().mockResolvedValue(undefined);
|
|
160
|
+
const stageElementPositionOffset = vi.fn(() => ({ save, rollback: vi.fn() }));
|
|
161
|
+
const gsapCommitMutation = vi.fn();
|
|
162
|
+
const makeFetchFallback = vi.fn();
|
|
163
|
+
const { pathOffsetCommit, root } = mountGroupHandler({
|
|
164
|
+
gsapCommitMutation,
|
|
165
|
+
makeFetchFallback,
|
|
166
|
+
stageElementPositionOffset,
|
|
167
|
+
});
|
|
168
|
+
const box = { element: document.createElement("div"), id: "box", selector: "#box" };
|
|
169
|
+
await act(() => pathOffsetCommit(box as unknown as DomEditSelection, { x: 130.25, y: 90 }));
|
|
170
|
+
expect(stageElementPositionOffset).toHaveBeenCalledWith(box, { x: 130.25, y: 90 });
|
|
171
|
+
expect(save).toHaveBeenCalledTimes(1);
|
|
172
|
+
expect(gsapCommitMutation).not.toHaveBeenCalled();
|
|
173
|
+
expect(makeFetchFallback).not.toHaveBeenCalled();
|
|
174
|
+
expect(mocks.drag).not.toHaveBeenCalled();
|
|
175
|
+
act(() => root.unmount());
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
153
179
|
describe("useGsapAwareEditing shared-tween moves", () => {
|
|
154
180
|
it("saves a single drag through the element's own offset", async () => {
|
|
155
181
|
mocks.drag.mockResolvedValue({ status: "element-offset" });
|
|
@@ -161,7 +187,7 @@ describe("useGsapAwareEditing shared-tween moves", () => {
|
|
|
161
187
|
makeFetchFallback: () => vi.fn().mockResolvedValue([]),
|
|
162
188
|
stageElementPositionOffset,
|
|
163
189
|
});
|
|
164
|
-
const word = { element:
|
|
190
|
+
const word = { element: gsapPositioned("span"), hfId: "w0", selector: ".w" };
|
|
165
191
|
await act(() => pathOffsetCommit(word as unknown as DomEditSelection, { x: 40, y: 20 }));
|
|
166
192
|
expect(stageElementPositionOffset).toHaveBeenCalledWith(word, { x: 40, y: 20 });
|
|
167
193
|
expect(save).toHaveBeenCalledTimes(1);
|
|
@@ -169,7 +195,7 @@ describe("useGsapAwareEditing shared-tween moves", () => {
|
|
|
169
195
|
act(() => root.unmount());
|
|
170
196
|
});
|
|
171
197
|
|
|
172
|
-
it("saves
|
|
198
|
+
it("saves shared-tween and GSAP-free group members on themselves, under the group key", async () => {
|
|
173
199
|
mocks.drag.mockImplementation(async (selection, _next, _a, _i, commit, _f, options) => {
|
|
174
200
|
if (selection.hfId === "w0") return { status: "element-offset" };
|
|
175
201
|
if (!options?.preflightOnly) await commit(selection, { type: "move" }, { label: "Move" });
|
|
@@ -182,19 +208,20 @@ describe("useGsapAwareEditing shared-tween moves", () => {
|
|
|
182
208
|
makeFetchFallback: () => vi.fn().mockResolvedValue([]),
|
|
183
209
|
stageElementPositionOffset,
|
|
184
210
|
});
|
|
185
|
-
const word = { element:
|
|
186
|
-
const box = { element:
|
|
211
|
+
const word = { element: gsapPositioned("span"), hfId: "w0", selector: ".w" };
|
|
212
|
+
const box = { element: gsapPositioned("div"), id: "box", selector: "#box" };
|
|
213
|
+
const plain = { element: document.createElement("div"), id: "plain", selector: "#plain" };
|
|
187
214
|
await act(() =>
|
|
188
215
|
groupCommit([
|
|
189
216
|
{ selection: word, next: { x: 40, y: 20 } },
|
|
190
217
|
{ selection: box, next: { x: 40, y: 20 } },
|
|
218
|
+
{ selection: plain, next: { x: 40, y: 20 } },
|
|
191
219
|
] as unknown as DomEditGroupPathOffsetCommit[]),
|
|
192
220
|
);
|
|
193
|
-
expect(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
);
|
|
221
|
+
const groupKey = expect.stringMatching(/^group-drag:\d+$/);
|
|
222
|
+
expect(stageElementPositionOffset).toHaveBeenCalledWith(word, { x: 40, y: 20 }, groupKey);
|
|
223
|
+
expect(stageElementPositionOffset).toHaveBeenCalledWith(plain, { x: 40, y: 20 }, groupKey);
|
|
224
|
+
expect(mocks.drag.mock.calls.some((call) => call[0] === plain)).toBe(false);
|
|
198
225
|
act(() => root.unmount());
|
|
199
226
|
});
|
|
200
227
|
});
|
|
@@ -207,7 +234,7 @@ describe("useGsapAwareEditing anchored resize", () => {
|
|
|
207
234
|
const h = mountResizeHandler([humanAnimation], [targetAnimation]);
|
|
208
235
|
const target = {
|
|
209
236
|
...h.selection,
|
|
210
|
-
element:
|
|
237
|
+
element: gsapPositioned("div"),
|
|
211
238
|
id: "agent-target",
|
|
212
239
|
selector: "#agent-target",
|
|
213
240
|
} as DomEditSelection;
|
|
@@ -447,11 +474,11 @@ describe("useGsapAwareEditing anchored resize", () => {
|
|
|
447
474
|
});
|
|
448
475
|
const updates = [
|
|
449
476
|
{
|
|
450
|
-
selection: { element:
|
|
477
|
+
selection: { element: gsapPositioned("div"), id: "a", selector: "#a" },
|
|
451
478
|
next: { x: 10, y: 10 },
|
|
452
479
|
},
|
|
453
480
|
{
|
|
454
|
-
selection: { element:
|
|
481
|
+
selection: { element: gsapPositioned("div"), id: "b", selector: "#b" },
|
|
455
482
|
next: { x: 10, y: 10 },
|
|
456
483
|
},
|
|
457
484
|
] as unknown as DomEditGroupPathOffsetCommit[];
|
|
@@ -485,12 +512,12 @@ describe("useGsapAwareEditing anchored resize", () => {
|
|
|
485
512
|
});
|
|
486
513
|
const updates = [
|
|
487
514
|
{
|
|
488
|
-
selection: { element:
|
|
515
|
+
selection: { element: gsapPositioned("div"), id: "ok", selector: "#ok" },
|
|
489
516
|
next: { x: 10, y: 10 },
|
|
490
517
|
},
|
|
491
518
|
{
|
|
492
519
|
selection: {
|
|
493
|
-
element:
|
|
520
|
+
element: gsapPositioned("div"),
|
|
494
521
|
id: "blocked",
|
|
495
522
|
selector: "#blocked",
|
|
496
523
|
},
|
|
@@ -526,7 +553,7 @@ describe("useGsapAwareEditing anchored resize", () => {
|
|
|
526
553
|
});
|
|
527
554
|
const updates = [
|
|
528
555
|
{
|
|
529
|
-
selection: { element:
|
|
556
|
+
selection: { element: gsapPositioned("div"), id: "a", selector: "#a" },
|
|
530
557
|
next: { x: 10, y: 10 },
|
|
531
558
|
},
|
|
532
559
|
] as unknown as DomEditGroupPathOffsetCommit[];
|
|
@@ -551,11 +578,11 @@ describe("useGsapAwareEditing anchored resize", () => {
|
|
|
551
578
|
});
|
|
552
579
|
const updates = [
|
|
553
580
|
{
|
|
554
|
-
selection: { element:
|
|
581
|
+
selection: { element: gsapPositioned("div"), id: "a", selector: "#a" },
|
|
555
582
|
next: { x: 10, y: 10 },
|
|
556
583
|
},
|
|
557
584
|
{
|
|
558
|
-
selection: { element:
|
|
585
|
+
selection: { element: gsapPositioned("div"), id: "b", selector: "#b" },
|
|
559
586
|
next: { x: 20, y: 20 },
|
|
560
587
|
},
|
|
561
588
|
] as unknown as DomEditGroupPathOffsetCommit[];
|
|
@@ -605,6 +632,7 @@ describe("useGsapAwareEditing anchored resize", () => {
|
|
|
605
632
|
*/
|
|
606
633
|
it("applies the anchor when the resize leaves the drop point to the caller", async () => {
|
|
607
634
|
mocks.resize.mockResolvedValue({ status: "persisted" });
|
|
635
|
+
mocks.drag.mockResolvedValue({ status: "persisted" });
|
|
608
636
|
const scale = { propertyGroup: "scale" } as GsapAnimation;
|
|
609
637
|
const h = mountResizeHandler([scale]);
|
|
610
638
|
await act(() => h.resize(h.selection, { width: 300, height: 200 }, { x: -50, y: -25 }));
|
|
@@ -10,11 +10,7 @@
|
|
|
10
10
|
import { useCallback } from "react";
|
|
11
11
|
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
12
12
|
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
13
|
-
import {
|
|
14
|
-
POSITION_CHANNELS,
|
|
15
|
-
tryGsapDragIntercept,
|
|
16
|
-
tryGsapRotationIntercept,
|
|
17
|
-
} from "./gsapRuntimeBridge";
|
|
13
|
+
import { tryGsapDragIntercept, tryGsapRotationIntercept } from "./gsapRuntimeBridge";
|
|
18
14
|
import { tryGsapResizeIntercept } from "./gsapResizeIntercept";
|
|
19
15
|
import { computeDraggedGsapPosition } from "./draggedGsapPosition";
|
|
20
16
|
import { readGsapPositionFromIframe } from "./gsapPositionDetection";
|
|
@@ -33,7 +29,11 @@ import { setElementGsapPosition } from "../utils/elementGsap";
|
|
|
33
29
|
import { logResize, logResizeSettle } from "../utils/resizeDebug";
|
|
34
30
|
import type { DomEditGroupPathOffsetCommit } from "../components/editor/DomEditOverlay";
|
|
35
31
|
import { runGestureTransaction } from "./gestureTransaction";
|
|
36
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
gsapWritesPosition,
|
|
34
|
+
hasNonHoldTweenForElement,
|
|
35
|
+
POSITION_CHANNELS,
|
|
36
|
+
} from "./gsapRuntimeKeyframes";
|
|
37
37
|
import { assertGsapEditPersisted, saveMove } from "./gsapEditOutcome";
|
|
38
38
|
import type { GsapAnimationFetchOptions } from "./useGsapAnimationFetchFallback";
|
|
39
39
|
import type { ElementOffsetStagerDeps } from "./elementOffsetStager";
|
|
@@ -153,6 +153,8 @@ export function useGsapAwareEditing({
|
|
|
153
153
|
next: { x: number; y: number },
|
|
154
154
|
modifiers?: { altKey?: boolean },
|
|
155
155
|
) => {
|
|
156
|
+
if (!gsapWritesPosition(selection.element))
|
|
157
|
+
return stageElementPositionOffset(selection, next).save();
|
|
156
158
|
if (gsapCommitMutation) {
|
|
157
159
|
try {
|
|
158
160
|
const ownedAnimations = getGsapAnimationsForSelection(selection);
|
|
@@ -185,11 +187,8 @@ export function useGsapAwareEditing({
|
|
|
185
187
|
],
|
|
186
188
|
);
|
|
187
189
|
|
|
188
|
-
// Multi-select (group) drag:
|
|
189
|
-
//
|
|
190
|
-
// gsap.set) — NEVER the deprecated `--hf-studio-offset` CSS var, and GSAP-animated
|
|
191
|
-
// elements are no longer blocked in a group. No CSS fallback: with no GSAP
|
|
192
|
-
// composition there's nothing to write (a no-op, exactly like the single-drag path).
|
|
190
|
+
// Multi-select (group) drag: each member takes the single drag's writer, so a member GSAP
|
|
191
|
+
// does not position is saved on itself and the rest go through the GSAP intercept.
|
|
193
192
|
const handleGsapAwareGroupPathOffsetCommit = useCallback(
|
|
194
193
|
async (updates: DomEditGroupPathOffsetCommit[]) => {
|
|
195
194
|
if (!gsapCommitMutation || updates.length === 0) return;
|
|
@@ -247,6 +246,7 @@ export function useGsapAwareEditing({
|
|
|
247
246
|
// turns N sequential round trips into one.
|
|
248
247
|
const preflightResults = await Promise.allSettled(
|
|
249
248
|
updates.map(async ({ selection }) => {
|
|
249
|
+
if (!gsapWritesPosition(selection.element)) return void offsetMembers.add(selection);
|
|
250
250
|
const animations = await makeFetchFallback(selection, { failOnFetchError: true })();
|
|
251
251
|
preflightAnimations.set(selection, animations);
|
|
252
252
|
const outcome = await tryGsapDragIntercept(
|