@hyperframes/studio 0.8.103 → 0.8.104
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-DG795Hwu.js → hyperframes-player-DAoaajuM.js} +1 -1
- package/dist/assets/{index-BiEtDkae.js → index-BDK4rTMb.js} +1 -1
- package/dist/assets/{index-BPJRf2X9.js → index-De2aO4XM.js} +178 -178
- package/dist/assets/{index-DuCZ3pQw.js → index-WV9LroAD.js} +1 -1
- package/dist/index.d.ts +181 -168
- package/dist/index.html +1 -1
- package/dist/index.js +428 -305
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/components/editor/DomEditOverlay.tsx +5 -6
- package/src/components/editor/PropertyPanel.tsx +3 -3
- package/src/components/editor/anchoredResizeCommitFeedsOffset.test.ts +46 -6
- package/src/components/editor/domEditOverlayGestures.ts +13 -19
- package/src/components/editor/domEditOverlayStartGesture.test.ts +170 -13
- package/src/components/editor/domEditOverlayStartGesture.ts +15 -25
- package/src/components/editor/manualEdits.test.ts +3 -9
- package/src/components/editor/manualEdits.ts +0 -1
- package/src/components/editor/manualEditsDom.ts +0 -10
- package/src/components/editor/manualEditsDomPatches.test.ts +1 -45
- package/src/components/editor/manualEditsDomPatches.ts +1 -23
- package/src/components/editor/manualEditsSnapshot.ts +2 -0
- package/src/components/editor/manualEditsTypes.ts +2 -0
- package/src/components/editor/manualOffsetDrag.test.ts +41 -0
- package/src/components/editor/manualOffsetDrag.ts +6 -27
- package/src/components/editor/plainTranslate.ts +9 -4
- package/src/components/editor/propertyPanelTransformCommit.test.ts +29 -0
- package/src/components/editor/propertyPanelTransformCommit.ts +8 -5
- package/src/components/editor/propertyPanelTypes.ts +1 -0
- package/src/components/editor/rotateInlineSave.test.ts +111 -0
- package/src/components/editor/rotationDraft.test.ts +111 -0
- package/src/components/editor/rotationDraft.ts +145 -0
- package/src/components/editor/useDomEditNudge.test.tsx +33 -0
- package/src/components/editor/useDomEditNudge.ts +6 -2
- package/src/components/editor/useDomEditOverlayGestures.ts +22 -24
- package/src/hooks/draggedGsapPosition.ts +28 -0
- package/src/hooks/elementOffsetStager.ts +26 -6
- package/src/hooks/gsapRuntimeBridge.ts +5 -3
- package/src/hooks/gsapRuntimeKeyframes.ts +36 -2
- package/src/hooks/plainRotation.test.ts +122 -0
- package/src/hooks/plainRotation.ts +42 -0
- package/src/hooks/useDomEditSession.ts +5 -5
- package/src/hooks/useDomGeometryCommit.test.tsx +75 -0
- package/src/hooks/useDomGeometryCommit.ts +17 -17
- package/src/hooks/useDomGeometryCommits.test.tsx +56 -1
- package/src/hooks/useDomGeometryCommits.ts +28 -36
- package/src/hooks/useGsapAwareEditing.test.tsx +194 -11
- package/src/hooks/useGsapAwareEditing.ts +43 -42
- package/src/hooks/useInlineTextEdit.test.tsx +33 -3
- package/src/hooks/useInlineTextEdit.ts +2 -2
|
@@ -24,8 +24,7 @@ import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
|
24
24
|
import { membersForDelete, timelineElementsForDelete } from "./domEditDeleteMembers";
|
|
25
25
|
import type { RecordEditInput } from "./domEditDeleteMembers";
|
|
26
26
|
import type { DomEditTimelineParams } from "./useDomSelectionTypes";
|
|
27
|
-
// Re-exported: the delete rule lives in its own module
|
|
28
|
-
// own test) have always imported it from here.
|
|
27
|
+
// Re-exported: the delete rule lives in its own module; callers and its test import it from here.
|
|
29
28
|
export { membersForDelete };
|
|
30
29
|
|
|
31
30
|
export interface UseDomEditSessionParams extends DomEditTimelineParams {
|
|
@@ -232,6 +231,7 @@ export function useDomEditSession({
|
|
|
232
231
|
stageElementPositionOffset,
|
|
233
232
|
commitPositionPatchToHtml,
|
|
234
233
|
handleDomBoxSizeCommit,
|
|
234
|
+
handleDomRotationCommit: handleDomCssRotationCommit,
|
|
235
235
|
handleDomManualEditsReset,
|
|
236
236
|
handleDomEditElementsDelete,
|
|
237
237
|
handleDomZIndexReorderCommit,
|
|
@@ -256,9 +256,8 @@ export function useDomEditSession({
|
|
|
256
256
|
readOnlyPreview,
|
|
257
257
|
onTrySdkPersist: sdkSession
|
|
258
258
|
? (selection, operations, originalContent, targetPath, options) => {
|
|
259
|
-
// Decoupled tripwire,
|
|
260
|
-
//
|
|
261
|
-
// nodes); the paths let a cross-file edit skip instead of a false not-found.
|
|
259
|
+
// Decoupled tripwire, regardless of the cutover flag. originalContent lets the runtime-node
|
|
260
|
+
// filter drop hf-ids absent from source; the paths skip a cross-file edit, not a not-found.
|
|
262
261
|
runResolverShadow(sdkSession, selection.hfId, operations, originalContent, {
|
|
263
262
|
targetPath,
|
|
264
263
|
compositionPath: activeCompPath,
|
|
@@ -491,6 +490,7 @@ export function useDomEditSession({
|
|
|
491
490
|
trackGsapInteractionFailure,
|
|
492
491
|
stageElementPositionOffset,
|
|
493
492
|
handleDomBoxSizeCommit,
|
|
493
|
+
handleDomRotationCommit: handleDomCssRotationCommit,
|
|
494
494
|
commitPositionPatchToHtml,
|
|
495
495
|
addGsapAnimation,
|
|
496
496
|
convertToKeyframes,
|
|
@@ -176,6 +176,28 @@ describe("useDomGeometryCommit, from the package entry", () => {
|
|
|
176
176
|
unmount();
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
+
it("saves a GSAP-free resize as its inline size, with no GSAP script and no animation read", async () => {
|
|
180
|
+
const calls = stubPatchServer();
|
|
181
|
+
const { element, recordEdit, hook, unmount } = renderHost();
|
|
182
|
+
|
|
183
|
+
await expect(
|
|
184
|
+
hook().commitBoxSize(makeSelection("card", element), { width: 300, height: 90 }),
|
|
185
|
+
).resolves.toEqual({ ok: true });
|
|
186
|
+
|
|
187
|
+
expect(element.style.getPropertyValue("width")).toBe("300px");
|
|
188
|
+
expect(calls.patches).toEqual([
|
|
189
|
+
expect.objectContaining({
|
|
190
|
+
operations: expect.arrayContaining([
|
|
191
|
+
{ type: "inline-style", property: "width", value: "300px" },
|
|
192
|
+
{ type: "inline-style", property: "height", value: "90px" },
|
|
193
|
+
]),
|
|
194
|
+
}),
|
|
195
|
+
]);
|
|
196
|
+
expect(calls.urls.filter((url) => url.includes("gsap"))).toEqual([]);
|
|
197
|
+
expect(recordEdit).toHaveBeenCalledTimes(1);
|
|
198
|
+
unmount();
|
|
199
|
+
});
|
|
200
|
+
|
|
179
201
|
it("puts a GSAP-free element's translate back when its move cannot be saved", async () => {
|
|
180
202
|
stubPatchServer(500);
|
|
181
203
|
const { element, hook, unmount } = renderHost();
|
|
@@ -188,6 +210,57 @@ describe("useDomGeometryCommit, from the package entry", () => {
|
|
|
188
210
|
unmount();
|
|
189
211
|
});
|
|
190
212
|
|
|
213
|
+
it("refuses a CSS move GSAP has folded into its x/y, writes nothing, and hands the translate back", async () => {
|
|
214
|
+
const calls = stubPatchServer();
|
|
215
|
+
const { element, hook, unmount } = renderHost();
|
|
216
|
+
element.style.setProperty("translate", "none");
|
|
217
|
+
Object.assign(element, { _gsap: { renderTransform: () => {}, x: "94px", y: "66px" } });
|
|
218
|
+
const set = vi.fn();
|
|
219
|
+
Object.assign(element.ownerDocument.defaultView!, { gsap: { set } });
|
|
220
|
+
|
|
221
|
+
await expect(
|
|
222
|
+
hook().commitPathOffset(
|
|
223
|
+
makeSelection("card", element),
|
|
224
|
+
{ x: 1, y: 2 },
|
|
225
|
+
{ plainTranslate: true },
|
|
226
|
+
),
|
|
227
|
+
).rejects.toThrow(/animation took over/);
|
|
228
|
+
delete (element.ownerDocument.defaultView as { gsap?: unknown }).gsap;
|
|
229
|
+
expect(calls.patches).toEqual([]);
|
|
230
|
+
expect(set).toHaveBeenCalledWith(element, { x: 0, y: 0, xPercent: 0, yPercent: 0 });
|
|
231
|
+
unmount();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("saves a CSS move GSAP has only parsed, with nothing folded into its x/y", async () => {
|
|
235
|
+
const calls = stubPatchServer();
|
|
236
|
+
const { element, hook, unmount } = renderHost();
|
|
237
|
+
Object.assign(element, {
|
|
238
|
+
_gsap: { renderTransform: () => {}, x: "0px", y: "0px", xPercent: 0 },
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
await expect(
|
|
242
|
+
hook().commitPathOffset(
|
|
243
|
+
makeSelection("card", element),
|
|
244
|
+
{ x: 1, y: 2 },
|
|
245
|
+
{ plainTranslate: true },
|
|
246
|
+
),
|
|
247
|
+
).resolves.toEqual({ ok: true });
|
|
248
|
+
expect(calls.patches).toHaveLength(1);
|
|
249
|
+
unmount();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("leaves a later move's translate alone when an earlier move's save fails", async () => {
|
|
253
|
+
stubPatchServer(500);
|
|
254
|
+
const { element, hook, unmount } = renderHost();
|
|
255
|
+
element.style.setProperty("translate", "40px 30px");
|
|
256
|
+
|
|
257
|
+
const saving = hook().commitPathOffset(makeSelection("card", element), { x: 1, y: 2 });
|
|
258
|
+
element.style.setProperty("translate", "7px 8px");
|
|
259
|
+
await expect(saving).rejects.toThrow();
|
|
260
|
+
expect(element.style.getPropertyValue("translate")).toBe("7px 8px");
|
|
261
|
+
unmount();
|
|
262
|
+
});
|
|
263
|
+
|
|
191
264
|
it("keeps the same commits across renders, so the overlay's handlers stay put", () => {
|
|
192
265
|
stubServer();
|
|
193
266
|
const { hook, rerender, unmount } = renderHost();
|
|
@@ -216,6 +289,8 @@ describe("useDomGeometryCommit, from the package entry", () => {
|
|
|
216
289
|
const showToast = vi.fn();
|
|
217
290
|
const restore = vi.fn();
|
|
218
291
|
const { element, recordEdit, hook, unmount } = renderHost({ showToast });
|
|
292
|
+
// GSAP renders the card's transform, so its resize reads animations first.
|
|
293
|
+
Object.assign(element, { _gsap: { renderTransform: () => undefined } });
|
|
219
294
|
|
|
220
295
|
await expect(
|
|
221
296
|
hook().commitBoxSize(
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import type { RotationCommit } from "../components/editor/rotationDraft";
|
|
1
2
|
import { useCallback, useMemo, useRef, useState } from "react";
|
|
2
3
|
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
3
4
|
import type { DomEditSelection } from "../components/editor/domEditing";
|
|
4
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
DomEditGroupPathOffsetCommit,
|
|
7
|
+
MoveCommitOptions,
|
|
8
|
+
} from "../components/editor/DomEditOverlay";
|
|
5
9
|
import { isPreviewBooted } from "../player/store/playerStore";
|
|
6
10
|
import type { DomEditCommitOutcome } from "./domEditCommitRunner";
|
|
7
11
|
import type { UseDomStyleCommitOptions } from "./useDomStyleCommit";
|
|
@@ -13,8 +17,8 @@ import { useGsapCacheVersion } from "./useGsapTweenCache";
|
|
|
13
17
|
import { createDomEditSaveQueue } from "../utils/domEditSaveQueue";
|
|
14
18
|
import { useDomEditPersist } from "./useDomEditPersist";
|
|
15
19
|
import { useDomEditPositionPatchCommit } from "./useDomEditPositionPatchCommit";
|
|
20
|
+
import { useDomGeometryCommits } from "./useDomGeometryCommits";
|
|
16
21
|
import { useMountEffect } from "./useMountEffect";
|
|
17
|
-
import { stageElementOffset } from "./elementOffsetStager";
|
|
18
22
|
|
|
19
23
|
/**
|
|
20
24
|
* Studio's `Player` must show the project, with `beginTimelineSession(projectId)` run before it
|
|
@@ -29,7 +33,7 @@ export interface DomGeometryCommits {
|
|
|
29
33
|
commitPathOffset: (
|
|
30
34
|
selection: DomEditSelection,
|
|
31
35
|
next: { x: number; y: number },
|
|
32
|
-
modifiers?:
|
|
36
|
+
modifiers?: MoveCommitOptions,
|
|
33
37
|
) => Promise<DomEditCommitOutcome>;
|
|
34
38
|
commitGroupPathOffset: (updates: DomEditGroupPathOffsetCommit[]) => Promise<DomEditCommitOutcome>;
|
|
35
39
|
commitBoxSize: (
|
|
@@ -40,15 +44,13 @@ export interface DomGeometryCommits {
|
|
|
40
44
|
) => Promise<DomEditCommitOutcome>;
|
|
41
45
|
commitRotation: (
|
|
42
46
|
selection: DomEditSelection,
|
|
43
|
-
next:
|
|
47
|
+
next: RotationCommit,
|
|
44
48
|
) => Promise<DomEditCommitOutcome>;
|
|
45
49
|
waitForPendingSaves: () => Promise<void>;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
const noop = () => {};
|
|
49
53
|
const NO_SELECTED_ANIMATIONS: GsapAnimation[] = [];
|
|
50
|
-
// ponytail: unreachable, the GSAP writer always exists so a resize never takes the DOM route.
|
|
51
|
-
const noDomBoxSizeRoute = () => Promise.reject(new Error("Resize has no DOM route here"));
|
|
52
54
|
|
|
53
55
|
/**
|
|
54
56
|
* Saves canvas moves, resizes and rotations through Studio's own GSAP-aware commits, for a host
|
|
@@ -107,16 +109,13 @@ export function useDomGeometryCommit({
|
|
|
107
109
|
},
|
|
108
110
|
[commitPositionPatchToHtml, queue],
|
|
109
111
|
);
|
|
110
|
-
const stageElementPositionOffset =
|
|
111
|
-
(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
),
|
|
118
|
-
[commitWithFreshQueue, showToast],
|
|
119
|
-
);
|
|
112
|
+
const { stageElementPositionOffset, handleDomBoxSizeCommit, handleDomRotationCommit } =
|
|
113
|
+
useDomGeometryCommits({
|
|
114
|
+
previewIframeRef: iframeRef,
|
|
115
|
+
showToast,
|
|
116
|
+
commitPositionPatchToHtml: commitWithFreshQueue,
|
|
117
|
+
readOnlyPreview: false,
|
|
118
|
+
});
|
|
120
119
|
const makeFetchFallback = useGsapAnimationFetchFallback(projectId);
|
|
121
120
|
const trackGsapInteractionFailure = useGsapInteractionFailureTelemetry(activeCompPath, showToast);
|
|
122
121
|
const {
|
|
@@ -135,7 +134,8 @@ export function useDomGeometryCommit({
|
|
|
135
134
|
makeFetchFallback,
|
|
136
135
|
trackGsapInteractionFailure,
|
|
137
136
|
stageElementPositionOffset,
|
|
138
|
-
handleDomBoxSizeCommit
|
|
137
|
+
handleDomBoxSizeCommit,
|
|
138
|
+
handleDomRotationCommit,
|
|
139
139
|
commitPositionPatchToHtml: commitWithFreshQueue,
|
|
140
140
|
addGsapAnimation: gsap.addGsapAnimation,
|
|
141
141
|
convertToKeyframes: gsap.convertToKeyframes,
|
|
@@ -162,7 +162,7 @@ describe("useDomGeometryCommits element position offset", () => {
|
|
|
162
162
|
.mockResolvedValue(undefined);
|
|
163
163
|
const { commits, unmount } = mountCommits(commitPositionPatchToHtml);
|
|
164
164
|
|
|
165
|
-
await commits().stageElementPositionOffset(selection, { x: 40, y: 20 }).save();
|
|
165
|
+
await commits().stageElementPositionOffset(selection, { x: 40, y: 20 }, false).save();
|
|
166
166
|
|
|
167
167
|
const patches = commitPositionPatchToHtml.mock.calls[0]![1];
|
|
168
168
|
expect(patches).toEqual([
|
|
@@ -175,6 +175,61 @@ describe("useDomGeometryCommits element position offset", () => {
|
|
|
175
175
|
});
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
+
describe("useDomGeometryCommits resize anchor", () => {
|
|
179
|
+
it("saves the anchor as the element's own plain px translate, sub-pixel, with the size", async () => {
|
|
180
|
+
const element = document.createElement("div");
|
|
181
|
+
element.id = "anchored";
|
|
182
|
+
element.style.setProperty("translate", "40px 30px");
|
|
183
|
+
document.body.append(element);
|
|
184
|
+
const selection = {
|
|
185
|
+
id: "anchored",
|
|
186
|
+
selector: "#anchored",
|
|
187
|
+
element,
|
|
188
|
+
} as unknown as DomEditSelection;
|
|
189
|
+
const commit = vi
|
|
190
|
+
.fn<UseDomGeometryCommitsParams["commitPositionPatchToHtml"]>()
|
|
191
|
+
.mockResolvedValue(undefined);
|
|
192
|
+
const { commits, unmount } = mountCommits(commit);
|
|
193
|
+
|
|
194
|
+
await commits().handleDomBoxSizeCommit(
|
|
195
|
+
selection,
|
|
196
|
+
{ width: 340, height: 227 },
|
|
197
|
+
{ x: -10.25, y: 3.5 },
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(commit).toHaveBeenCalledTimes(1);
|
|
201
|
+
const patches = commit.mock.calls[0]![1];
|
|
202
|
+
expect(patches).toContainEqual({ type: "inline-style", property: "width", value: "340px" });
|
|
203
|
+
expect(patches).toContainEqual({
|
|
204
|
+
type: "inline-style",
|
|
205
|
+
property: "translate",
|
|
206
|
+
value: "-10.25px 3.5px",
|
|
207
|
+
});
|
|
208
|
+
expect(patches.some((p) => p.property.startsWith("--hf-studio-offset"))).toBe(false);
|
|
209
|
+
expect(element.style.getPropertyValue("translate")).toBe("-10.25px 3.5px");
|
|
210
|
+
unmount();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe("useDomGeometryCommits resize rollback", () => {
|
|
215
|
+
it("rolls the whole gesture back once when the resize save fails", async () => {
|
|
216
|
+
const element = document.createElement("div");
|
|
217
|
+
document.body.append(element);
|
|
218
|
+
const selection = { id: "r", selector: "#r", element } as unknown as DomEditSelection;
|
|
219
|
+
const failure = new Error("save failed");
|
|
220
|
+
const commit = vi
|
|
221
|
+
.fn<UseDomGeometryCommitsParams["commitPositionPatchToHtml"]>()
|
|
222
|
+
.mockRejectedValue(failure);
|
|
223
|
+
const restore = vi.fn();
|
|
224
|
+
const { commits, unmount } = mountCommits(commit);
|
|
225
|
+
await expect(
|
|
226
|
+
commits().handleDomBoxSizeCommit(selection, { width: 50, height: 40 }, undefined, restore),
|
|
227
|
+
).rejects.toBe(failure);
|
|
228
|
+
expect(restore).toHaveBeenCalledTimes(1);
|
|
229
|
+
unmount();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
178
233
|
describe("useDomGeometryCommits resize of a cropped element", () => {
|
|
179
234
|
it("saves the crop scaled per axis with the box in the resize's own commit", async () => {
|
|
180
235
|
const element = withInlineLayoutBox(document.createElement("div"));
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { RotationCommit } from "../components/editor/rotationDraft";
|
|
1
2
|
import { useCallback } from "react";
|
|
2
3
|
import { getDomEditTargetKey, type DomEditSelection } from "../components/editor/domEditing";
|
|
3
4
|
import {
|
|
4
5
|
applyStudioPathOffset,
|
|
5
6
|
applyStudioBoxSize,
|
|
6
|
-
applyStudioRotation,
|
|
7
7
|
captureStudioPathOffset,
|
|
8
8
|
captureStudioBoxSize,
|
|
9
9
|
captureStudioRotation,
|
|
@@ -15,11 +15,12 @@ import {
|
|
|
15
15
|
clearStudioRotation,
|
|
16
16
|
} from "../components/editor/manualEdits";
|
|
17
17
|
import { stageElementOffset } from "./elementOffsetStager";
|
|
18
|
+
import { savePlainRotation } from "./plainRotation";
|
|
18
19
|
import { prepareCropResize } from "../components/editor/cropResize";
|
|
20
|
+
import { translatePatch, writeTranslatePx } from "../components/editor/plainTranslate";
|
|
19
21
|
import {
|
|
20
22
|
buildPathOffsetPatches,
|
|
21
23
|
buildBoxSizePatches,
|
|
22
|
-
buildRotationPatches,
|
|
23
24
|
buildClearPathOffsetPatches,
|
|
24
25
|
buildClearBoxSizePatches,
|
|
25
26
|
buildClearRotationPatches,
|
|
@@ -61,11 +62,17 @@ export function useDomGeometryCommits({
|
|
|
61
62
|
readOnlyPreview,
|
|
62
63
|
}: UseDomGeometryCommitsParams) {
|
|
63
64
|
const stageElementPositionOffset = useCallback(
|
|
64
|
-
(
|
|
65
|
+
(
|
|
66
|
+
selection: DomEditSelection,
|
|
67
|
+
next: { x: number; y: number },
|
|
68
|
+
plainTranslate: boolean,
|
|
69
|
+
coalesceKey?: string,
|
|
70
|
+
) =>
|
|
65
71
|
stageElementOffset(
|
|
66
72
|
{ commitPositionPatchToHtml, showToast, readOnlyPreview },
|
|
67
73
|
selection,
|
|
68
74
|
next,
|
|
75
|
+
plainTranslate,
|
|
69
76
|
coalesceKey,
|
|
70
77
|
),
|
|
71
78
|
[commitPositionPatchToHtml, readOnlyPreview, showToast],
|
|
@@ -98,56 +105,41 @@ export function useDomGeometryCommits({
|
|
|
98
105
|
selection: DomEditSelection,
|
|
99
106
|
next: { width: number; height: number },
|
|
100
107
|
offset?: { x: number; y: number },
|
|
108
|
+
restore?: () => void,
|
|
101
109
|
) => {
|
|
102
110
|
if (readOnlyPreview) return Promise.resolve();
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
applyStudioBoxSize(selection.element, next);
|
|
111
|
+
const element = selection.element;
|
|
112
|
+
const beforeSize = captureStudioBoxSize(element);
|
|
113
|
+
const beforeTranslate = element.style.getPropertyValue("translate");
|
|
114
|
+
const stageCrop = prepareCropResize(element);
|
|
115
|
+
applyStudioBoxSize(element, next);
|
|
109
116
|
const crop = stageCrop();
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
// intermediate re-stamp where the new size is in source but the anchor
|
|
114
|
-
// offset is not (that frame was the release "jump"). Both builders read
|
|
115
|
-
// the already-mutated live element, so concatenation is safe.
|
|
116
|
-
const patches = buildBoxSizePatches(selection.element);
|
|
117
|
+
// One commit, one undo entry: the size, the crop that follows it, and the translate
|
|
118
|
+
// (as a move writes it) that keeps the centre planted.
|
|
119
|
+
const patches = buildBoxSizePatches(element);
|
|
117
120
|
if (crop) patches.push(crop.patch);
|
|
118
121
|
if (offset) {
|
|
119
|
-
|
|
120
|
-
patches.push(
|
|
122
|
+
writeTranslatePx(element, offset);
|
|
123
|
+
patches.push(translatePatch(offset));
|
|
121
124
|
}
|
|
122
125
|
return commitPositionPatchToHtml(selection, patches, {
|
|
123
126
|
label: "Resize layer box",
|
|
124
127
|
coalesceKey: `box-size:${getDomEditTargetKey(selection)}`,
|
|
125
128
|
}).catch((error) => {
|
|
126
|
-
restoreStudioBoxSize(
|
|
127
|
-
if (
|
|
129
|
+
restoreStudioBoxSize(element, beforeSize);
|
|
130
|
+
if (offset) element.style.setProperty("translate", beforeTranslate);
|
|
128
131
|
crop?.revert();
|
|
132
|
+
restore?.();
|
|
129
133
|
throw error;
|
|
130
134
|
});
|
|
131
135
|
},
|
|
132
|
-
[commitPositionPatchToHtml,
|
|
136
|
+
[commitPositionPatchToHtml, readOnlyPreview],
|
|
133
137
|
);
|
|
134
138
|
|
|
135
139
|
const handleDomRotationCommit = useCallback(
|
|
136
|
-
(selection: DomEditSelection, next:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (gsapFallback) return gsapFallback;
|
|
140
|
-
const before = captureStudioRotation(selection.element);
|
|
141
|
-
applyStudioRotation(selection.element, next);
|
|
142
|
-
return commitPositionPatchToHtml(selection, buildRotationPatches(selection.element), {
|
|
143
|
-
label: "Rotate layer",
|
|
144
|
-
coalesceKey: `rotation:${getDomEditTargetKey(selection)}`,
|
|
145
|
-
}).catch((error) => {
|
|
146
|
-
restoreStudioRotation(selection.element, before);
|
|
147
|
-
throw error;
|
|
148
|
-
});
|
|
149
|
-
},
|
|
150
|
-
[commitPositionPatchToHtml, previewIframeRef, showToast, readOnlyPreview],
|
|
140
|
+
(selection: DomEditSelection, next: RotationCommit) =>
|
|
141
|
+
savePlainRotation({ commitPositionPatchToHtml, readOnlyPreview }, selection, next),
|
|
142
|
+
[commitPositionPatchToHtml, readOnlyPreview],
|
|
151
143
|
);
|
|
152
144
|
|
|
153
145
|
const handleDomManualEditsReset = useCallback(
|