@hyperframes/studio 0.8.113 → 0.8.115
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-DzJbtQVO.js → hyperframes-player-C-9N8nk0.js} +1 -1
- package/dist/assets/{index-n5FUn1xv.js → index-BoUM6Xqb.js} +1 -1
- package/dist/assets/{index-DRhF3v4Z.js → index-CAvZ0AA-.js} +204 -204
- package/dist/assets/{index-DgwDQK5G.js → index-CVuorIMv.js} +1 -1
- package/dist/assets/index-t1LH6H5H.css +1 -0
- package/dist/{chunk-K47E2ZJA.js → chunk-5EKWJZLM.js} +504 -17
- package/dist/chunk-5EKWJZLM.js.map +1 -0
- package/dist/{chunk-VWT7STY3.js → chunk-J6Q572FA.js} +2 -2
- package/dist/{chunk-BIUXP3PG.js → chunk-TXZMMOMA.js} +43 -276
- package/dist/chunk-TXZMMOMA.js.map +1 -0
- package/dist/{domEditingLayers-V67MS4WP.js → domEditingLayers-D7VYFJJD.js} +3 -5
- package/dist/index.d.ts +46 -22
- package/dist/index.html +2 -2
- package/dist/index.js +3899 -3487
- package/dist/index.js.map +1 -1
- package/dist/player.js +2 -2
- package/dist/styles.css +1 -1
- package/package.json +10 -8
- package/src/App.tsx +84 -86
- package/src/components/EditorShell.tsx +3 -3
- package/src/components/LinkedSelectionToggle.tsx +3 -2
- package/src/components/StudioHeader.tsx +4 -4
- package/src/components/StudioLeftPanels.tsx +3 -3
- package/src/components/StudioOverlays.tsx +4 -4
- package/src/components/StudioRightPanels.tsx +14 -9
- package/src/components/StudioToast.tsx +1 -1
- package/src/components/TimelineToolbar.tsx +3 -3
- package/src/components/dock/Dock.tsx +5 -7
- package/src/components/editor/CanvasContextMenu.tsx +3 -2
- package/src/components/editor/DomEditCropHandles.test.tsx +27 -0
- package/src/components/editor/DomEditCropHandles.tsx +7 -4
- package/src/components/editor/DomEditOverlay.tsx +5 -1
- package/src/components/editor/FileTreeNodes.tsx +13 -10
- package/src/components/editor/InlineTextToolbar.tsx +11 -27
- package/src/components/editor/MotionPathOverlay.tsx +7 -2
- package/src/components/editor/PropertyPanelEmptyState.tsx +12 -2
- package/src/components/editor/SnapToolbar.test.tsx +100 -1
- package/src/components/editor/SnapToolbar.tsx +43 -5
- package/src/components/editor/anchoredResizeCommitFeedsOffset.test.ts +48 -1
- package/src/components/editor/audioFxTelemetry.ts +2 -3
- package/src/components/editor/colorBrowserCanvas.test.ts +116 -0
- package/src/components/editor/colorValue.test.ts +16 -0
- package/src/components/editor/colorValue.ts +100 -24
- package/src/components/editor/commitPreflightHandles.test.tsx +2 -2
- package/src/components/editor/cropResize.test.ts +11 -0
- package/src/components/editor/cropResize.ts +6 -6
- package/src/components/editor/domEditingDom.ts +4 -0
- package/src/components/editor/domEditingLayers.ts +1 -5
- package/src/components/editor/gradientValue.ts +28 -36
- package/src/components/editor/gsapAnimatesProperty.ts +0 -54
- package/src/components/editor/inlineTextStyleRange.test.ts +31 -1
- package/src/components/editor/inlineTextStyleRange.ts +5 -2
- package/src/components/editor/inlineTextStyleRead.ts +9 -0
- package/src/components/editor/manualEdits.ts +1 -0
- package/src/components/editor/manualEditsDom.ts +2 -2
- package/src/components/editor/manualEditsParsing.ts +7 -0
- package/src/components/editor/manualEditsSeekReapply.ts +2 -2
- package/src/components/editor/marqueeCommit.test.tsx +106 -37
- package/src/components/editor/marqueeCommit.ts +24 -8
- package/src/components/editor/motionPathCommit.test.ts +29 -1
- package/src/components/editor/motionPathCommit.ts +21 -9
- package/src/components/editor/probeSourceElement.test.ts +45 -0
- package/src/components/editor/probeSourceElement.ts +39 -15
- package/src/components/editor/propertyPanelColor.tsx +1 -1
- package/src/components/editor/propertyPanelFlatTextSection.tsx +10 -7
- package/src/components/editor/propertyPanelFxAddMenu.tsx +11 -2
- package/src/components/editor/propertyPanelFxCarveModule.tsx +15 -5
- package/src/components/editor/propertyPanelFxNodeOpenBody.tsx +3 -0
- package/src/components/editor/propertyPanelFxNodeParams.tsx +15 -2
- package/src/components/editor/propertyPanelFxNodeRow.tsx +3 -0
- package/src/components/editor/propertyPanelFxPresetRun.tsx +10 -2
- package/src/components/editor/propertyPanelFxSection.test.tsx +56 -0
- package/src/components/editor/propertyPanelFxSection.tsx +30 -28
- package/src/components/editor/propertyPanelTypes.ts +6 -3
- package/src/components/editor/reapplyPositionEditsAfterSeek.test.ts +25 -0
- package/src/components/editor/useDomEditNudge.test.tsx +37 -0
- package/src/components/editor/useDomEditNudge.ts +2 -0
- package/src/components/editor/useDomEditOverlayGestures.ts +5 -0
- package/src/components/editor/useDomEditZOrder.ts +12 -2
- package/src/components/nle/useTimelineEditCallbacks.test.tsx +1 -0
- package/src/components/nle/useTimelineEditCallbacks.ts +7 -1
- package/src/components/sidebar/AssetContextMenu.tsx +4 -4
- package/src/components/ui/menuStyle.test.ts +61 -0
- package/src/components/ui/menuStyle.ts +10 -0
- package/src/contexts/DomEditContext.tsx +1 -1
- package/src/contexts/TimelineEditContext.tsx +1 -0
- package/src/domEditExports.test.tsx +9 -1
- package/src/hooks/domEditCommitRunner.ts +1 -1
- package/src/hooks/elementOffsetStager.ts +5 -4
- package/src/hooks/gsapDragCommit.ts +2 -20
- package/src/hooks/gsapDragPositionCommit.ts +18 -3
- package/src/hooks/gsapGestureOutcome.test.ts +161 -0
- package/src/hooks/gsapGestureOutcome.ts +64 -0
- package/src/hooks/gsapKeyframeCommit.ts +3 -12
- package/src/hooks/gsapResizeIntercept.test.ts +33 -0
- package/src/hooks/gsapResizeIntercept.ts +1 -0
- package/src/hooks/gsapRuntimeKeyframes.ts +1 -1
- package/src/hooks/gsapRuntimeReaders.test.ts +48 -0
- package/src/hooks/gsapRuntimeReaders.ts +3 -90
- package/src/hooks/gsapScriptCommitTypes.ts +5 -0
- package/src/hooks/gsapShared.writeTarget.test.ts +20 -0
- package/src/hooks/keyframeCacheAstLoad.ts +6 -5
- package/src/hooks/plainRotation.ts +3 -2
- package/src/hooks/previewPromotionSelection.test.tsx +1 -1
- package/src/hooks/refreshTimelineRowText.test.ts +65 -0
- package/src/hooks/refreshTimelineRowText.ts +29 -0
- package/src/hooks/useAnimatedPropertyCommit.test.tsx +46 -5
- package/src/hooks/useAnimatedPropertyCommit.ts +2 -2
- package/src/hooks/useAppHotkeys.ts +11 -5
- package/src/hooks/useBlockedTimelineEditToast.ts +12 -3
- package/src/hooks/useDomEditPositionPatchCommit.ts +18 -23
- package/src/hooks/useDomEditTextCommits.test.tsx +18 -0
- package/src/hooks/useDomEditTextCommits.ts +5 -1
- package/src/hooks/useDomEditWiring.ts +4 -2
- package/src/hooks/useDomGeometryCommit.test.tsx +25 -6
- package/src/hooks/useDomGeometryCommit.ts +7 -4
- package/src/hooks/useDomGeometryCommits.test.tsx +1 -0
- package/src/hooks/useDomGeometryCommits.ts +3 -2
- package/src/hooks/useDomSelection.test.ts +26 -0
- package/src/hooks/useDomSelection.ts +10 -3
- package/src/hooks/useDomSelectionTypes.ts +6 -1
- package/src/hooks/useEnableKeyframes.test.ts +2 -2
- package/src/hooks/useEnableKeyframes.ts +5 -3
- package/src/hooks/useExternalFileChangeCoordinator.test.tsx +102 -0
- package/src/hooks/useExternalFileChangeCoordinator.ts +31 -6
- package/src/hooks/useFrameCapture.ts +15 -8
- package/src/hooks/useGestureCommit.test.tsx +225 -55
- package/src/hooks/useGestureCommit.ts +90 -56
- package/src/hooks/useGestureTrailOverlay.tsx +33 -0
- package/src/hooks/useGsapAwareEditing.test.tsx +11 -6
- package/src/hooks/useGsapAwareEditing.ts +96 -195
- package/src/hooks/useGsapAwareGroupMove.ts +179 -0
- package/src/hooks/useGsapKeyframeOps.sdkUsage.test.tsx +114 -0
- package/src/hooks/useGsapKeyframeOps.ts +36 -10
- package/src/hooks/useGsapScriptCommits.test.tsx +57 -0
- package/src/hooks/useGsapScriptCommits.ts +3 -0
- package/src/hooks/useGsapSelectionHandlers.test.tsx +1 -1
- package/src/hooks/useGsapSelectionHandlers.ts +24 -18
- package/src/hooks/useGsapTweenCache.ts +18 -12
- package/src/hooks/usePanelLayout.ts +5 -1
- package/src/hooks/usePopulateKeyframeCacheForFile.test.tsx +76 -2
- package/src/hooks/usePreviewInteraction.test.ts +21 -0
- package/src/hooks/usePreviewInteraction.ts +2 -0
- package/src/hooks/useRenderClipContent.test.ts +18 -0
- package/src/hooks/useRenderClipContent.ts +4 -5
- package/src/hooks/useShellSlices.ts +53 -0
- package/src/hooks/useStableHandlers.test.tsx +77 -0
- package/src/hooks/useStableHandlers.ts +44 -0
- package/src/hooks/useStudioSdkSessions.ts +2 -1
- package/src/hooks/useStudioSidePanels.test.tsx +44 -0
- package/src/hooks/useStudioSidePanels.tsx +23 -0
- package/src/hooks/useTimelineAddAtPlayhead.ts +16 -8
- package/src/hooks/useTimelineDeleteOps.test.tsx +30 -22
- package/src/hooks/useTimelineDeleteOps.ts +21 -27
- package/src/hooks/useTimelineEditing.test.tsx +1 -1
- package/src/hooks/useTimelineEditing.ts +33 -27
- package/src/player/components/AutomationSelectionMenu.tsx +3 -2
- package/src/player/components/ClipContextMenu.test.tsx +73 -0
- package/src/player/components/ClipContextMenu.tsx +33 -31
- package/src/player/components/KeyframeDiamondContextMenu.tsx +8 -9
- package/src/player/components/OutOfSyncBadge.tsx +5 -5
- package/src/player/components/TextClipContent.test.ts +19 -0
- package/src/player/components/TextClipContent.tsx +27 -0
- package/src/player/components/TrackGapContextMenu.tsx +3 -6
- package/src/player/components/clipMenuToolItems.test.tsx +23 -0
- package/src/player/components/clipMenuToolItems.tsx +13 -9
- package/src/player/components/timelineCallbacks.ts +1 -0
- package/src/player/components/timelineClipDragGestureLifecycle.ts +1 -1
- package/src/player/components/timelineClipGestureHandlers.test.ts +94 -0
- package/src/player/components/timelineClipGestureHandlers.ts +20 -2
- package/src/player/components/timelineEditing.ts +3 -1
- package/src/player/components/timelineLinkSelection.ts +35 -1
- package/src/player/components/useTimelineClipDrag.keyboard.test.tsx +14 -0
- package/src/player/components/useTimelineClipDrag.ts +6 -7
- package/src/player/hooks/timelinePlayerSync.test.ts +6 -0
- package/src/player/hooks/timelinePlayerSync.ts +6 -1
- package/src/player/lib/timelineElementHelpers.test.ts +87 -1
- package/src/player/lib/timelineElementHelpers.ts +6 -15
- package/src/player/lib/timelineText.ts +40 -0
- package/src/player/store/timelineElement.ts +12 -0
- package/src/styles/theme.css +3 -0
- package/src/utils/frameCapture.ts +3 -1
- package/src/utils/gsapUndoRestore.browser.test.ts +4 -2
- package/src/utils/keyframeUsage.ts +79 -0
- package/src/utils/previewFeatureUsage.test.ts +32 -0
- package/src/utils/previewFeatureUsage.ts +46 -0
- package/src/utils/studioTelemetry.test.ts +107 -0
- package/src/utils/studioTelemetry.ts +49 -4
- package/src/webmcp/StudioAgentTools.tsx +2 -2
- package/dist/assets/index-DbfKVTId.css +0 -1
- package/dist/chunk-BIUXP3PG.js.map +0 -1
- package/dist/chunk-K47E2ZJA.js.map +0 -1
- /package/dist/{chunk-VWT7STY3.js.map → chunk-J6Q572FA.js.map} +0 -0
- /package/dist/{domEditingLayers-V67MS4WP.js.map → domEditingLayers-D7VYFJJD.js.map} +0 -0
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
|
-
import { act } from "react";
|
|
2
|
+
import { act, useLayoutEffect } from "react";
|
|
3
3
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
5
|
+
import { trackStudioEvent } from "../utils/studioTelemetry";
|
|
6
|
+
import { trackKeyframeCommit } from "../utils/keyframeUsage";
|
|
4
7
|
import type { DomEditSelection } from "../components/editor/domEditing";
|
|
5
8
|
import { usePlayerStore } from "../player";
|
|
6
9
|
import { mountReactHarness } from "./domSelectionTestHarness";
|
|
7
10
|
import type { CommitMutationOptions } from "./gsapScriptCommitTypes";
|
|
11
|
+
import { useGsapAwareEditing } from "./useGsapAwareEditing";
|
|
8
12
|
import { useGestureCommit } from "./useGestureCommit";
|
|
9
13
|
|
|
14
|
+
vi.mock("../utils/studioTelemetry", () => ({ trackStudioEvent: vi.fn() }));
|
|
15
|
+
|
|
10
16
|
const gestureRecording = vi.hoisted(() => ({
|
|
11
17
|
startRecording: vi.fn(),
|
|
12
18
|
stopRecording: vi.fn(() => [
|
|
@@ -84,44 +90,88 @@ function makeSelection(element: HTMLElement): DomEditSelection {
|
|
|
84
90
|
};
|
|
85
91
|
}
|
|
86
92
|
|
|
93
|
+
function mountRecording(
|
|
94
|
+
writer: (
|
|
95
|
+
mutation: Record<string, unknown>,
|
|
96
|
+
options: CommitMutationOptions,
|
|
97
|
+
selection?: DomEditSelection,
|
|
98
|
+
) => Promise<void>,
|
|
99
|
+
animations: GsapAnimation[] = [],
|
|
100
|
+
readOnlyPreview = false,
|
|
101
|
+
) {
|
|
102
|
+
const iframe = document.createElement("iframe");
|
|
103
|
+
document.body.append(iframe);
|
|
104
|
+
const element = document.createElement("div");
|
|
105
|
+
element.id = "card";
|
|
106
|
+
const session = {
|
|
107
|
+
current: {
|
|
108
|
+
domEditSelection: makeSelection(element),
|
|
109
|
+
selectedGsapAnimations: animations,
|
|
110
|
+
commitMutation: writer,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
let hook: ReturnType<typeof useGestureCommit> | null = null;
|
|
114
|
+
function Probe() {
|
|
115
|
+
const editing = useGsapAwareEditing({
|
|
116
|
+
domEditSelection: session.current.domEditSelection,
|
|
117
|
+
selectedGsapAnimations: session.current.selectedGsapAnimations,
|
|
118
|
+
gsapCommitMutation: (selection, mutation, options) => writer(mutation, options, selection),
|
|
119
|
+
previewIframeRef: { current: iframe },
|
|
120
|
+
showToast: vi.fn(),
|
|
121
|
+
bumpGsapCache: vi.fn(),
|
|
122
|
+
makeFetchFallback: () => vi.fn(),
|
|
123
|
+
trackGsapInteractionFailure: vi.fn(),
|
|
124
|
+
stageElementPositionOffset: vi.fn(),
|
|
125
|
+
handleDomBoxSizeCommit: vi.fn(),
|
|
126
|
+
handleDomRotationCommit: vi.fn(),
|
|
127
|
+
commitPositionPatchToHtml: vi.fn(),
|
|
128
|
+
addGsapAnimation: vi.fn(),
|
|
129
|
+
convertToKeyframes: vi.fn(),
|
|
130
|
+
setArcPath: vi.fn(),
|
|
131
|
+
updateArcSegment: vi.fn(),
|
|
132
|
+
});
|
|
133
|
+
const currentHook = useGestureCommit({
|
|
134
|
+
domEditSessionRef: session,
|
|
135
|
+
previewIframeRef: { current: iframe },
|
|
136
|
+
showToast: vi.fn(),
|
|
137
|
+
isGestureRecordingRef: { current: false },
|
|
138
|
+
readOnlyPreview,
|
|
139
|
+
});
|
|
140
|
+
useLayoutEffect(() => {
|
|
141
|
+
session.current.commitMutation = editing.commitMutation;
|
|
142
|
+
hook = currentHook;
|
|
143
|
+
});
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const root = mountReactHarness(<Probe />);
|
|
147
|
+
cleanup = () => act(() => root.unmount());
|
|
148
|
+
return Object.assign(
|
|
149
|
+
() => {
|
|
150
|
+
if (!hook) throw new Error("hook did not initialize");
|
|
151
|
+
return hook;
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
select: (selection: DomEditSelection, nextAnimations: GsapAnimation[]) => {
|
|
155
|
+
session.current.domEditSelection = selection;
|
|
156
|
+
session.current.selectedGsapAnimations = nextAnimations;
|
|
157
|
+
act(() => root.render(<Probe />));
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
87
163
|
describe("useGestureCommit", () => {
|
|
88
164
|
it("coalesces property-group commits and reloads only the terminal group", async () => {
|
|
89
|
-
const
|
|
90
|
-
document.body.append(iframe);
|
|
91
|
-
const element = document.createElement("div");
|
|
92
|
-
element.id = "card";
|
|
93
|
-
const commitMutation = vi.fn<
|
|
165
|
+
const writer = vi.fn<
|
|
94
166
|
(mutation: Record<string, unknown>, options: CommitMutationOptions) => Promise<void>
|
|
95
167
|
>(async () => {});
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
selectedGsapAnimations: [],
|
|
100
|
-
commitMutation,
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
const captured: { hook: ReturnType<typeof useGestureCommit> | null } = { hook: null };
|
|
104
|
-
function Probe() {
|
|
105
|
-
captured.hook = useGestureCommit({
|
|
106
|
-
domEditSessionRef: sessionRef,
|
|
107
|
-
previewIframeRef: { current: iframe },
|
|
108
|
-
showToast: vi.fn(),
|
|
109
|
-
isGestureRecordingRef: { current: false },
|
|
110
|
-
readOnlyPreview: false,
|
|
111
|
-
});
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
const root = mountReactHarness(<Probe />);
|
|
115
|
-
cleanup = () => act(() => root.unmount());
|
|
116
|
-
if (!captured.hook) throw new Error("hook did not initialize");
|
|
117
|
-
|
|
118
|
-
act(() => captured.hook?.handleToggleRecording());
|
|
119
|
-
act(() => captured.hook?.handleToggleRecording());
|
|
168
|
+
const hook = mountRecording(writer);
|
|
169
|
+
act(() => hook().handleToggleRecording());
|
|
170
|
+
act(() => hook().handleToggleRecording());
|
|
120
171
|
await act(async () => {
|
|
121
|
-
await vi.waitFor(() => expect(
|
|
172
|
+
await vi.waitFor(() => expect(writer).toHaveBeenCalledTimes(2));
|
|
122
173
|
});
|
|
123
|
-
|
|
124
|
-
const options = commitMutation.mock.calls.map((call) => call[1]);
|
|
174
|
+
const options = writer.mock.calls.map((call) => call[1]);
|
|
125
175
|
expect(new Set(options.map((entry) => entry.coalesceKey)).size).toBe(1);
|
|
126
176
|
expect(options[0]).toEqual(expect.objectContaining({ coalesceMs: Infinity, skipReload: true }));
|
|
127
177
|
expect(options[0]).not.toHaveProperty("softReload");
|
|
@@ -130,30 +180,150 @@ describe("useGestureCommit", () => {
|
|
|
130
180
|
});
|
|
131
181
|
|
|
132
182
|
it("does not start a recording while the preview is read-only", () => {
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
183
|
+
const writer = vi.fn(async () => {});
|
|
184
|
+
const hook = mountRecording(writer, [], true);
|
|
185
|
+
act(() => hook().handleToggleRecording());
|
|
186
|
+
expect(gestureRecording.startRecording).not.toHaveBeenCalled();
|
|
187
|
+
expect(writer).not.toHaveBeenCalled();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it.each([
|
|
192
|
+
["new mixed-property recording", undefined, true, true, 1, "button"],
|
|
193
|
+
["keyboard recording", undefined, true, true, 1, "keyboard"],
|
|
194
|
+
["static hold replacement", "set", true, true, 1, "button"],
|
|
195
|
+
["overlapping tween replacement", "to", true, true, 1, "button"],
|
|
196
|
+
["unchanged recording", undefined, true, false, 0, "button"],
|
|
197
|
+
["refused recording", undefined, false, true, 0, "button"],
|
|
198
|
+
] as const)(
|
|
199
|
+
"counts writer receipts once for %s",
|
|
200
|
+
async (_name, method, ok, changed, count, inputMethod) => {
|
|
201
|
+
const animations = method
|
|
202
|
+
? [
|
|
203
|
+
{
|
|
204
|
+
id: "card-position",
|
|
205
|
+
targetSelector: "#card",
|
|
206
|
+
propertyGroup: "position",
|
|
207
|
+
method,
|
|
208
|
+
properties: { x: 0, y: 0 },
|
|
209
|
+
resolvedStart: 0,
|
|
210
|
+
position: 0,
|
|
211
|
+
duration: method === "set" ? 0 : 2,
|
|
212
|
+
keyframes: {
|
|
213
|
+
keyframes: [
|
|
214
|
+
{ percentage: 0, properties: { x: 0 } },
|
|
215
|
+
{ percentage: 100, properties: { x: 100 } },
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
} as unknown as GsapAnimation,
|
|
219
|
+
]
|
|
220
|
+
: [];
|
|
221
|
+
const writer = vi.fn(
|
|
222
|
+
async (mutation: Record<string, unknown>, options: CommitMutationOptions) => {
|
|
223
|
+
const result = { ok, changed };
|
|
224
|
+
trackKeyframeCommit([mutation], result, options);
|
|
225
|
+
options.onResult?.(result);
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
const hook = mountRecording(writer, animations);
|
|
229
|
+
act(() => hook().handleToggleRecording(inputMethod));
|
|
230
|
+
await act(async () => {
|
|
231
|
+
hook().handleToggleRecording();
|
|
232
|
+
await vi.waitFor(() => expect(writer).toHaveBeenCalled());
|
|
233
|
+
});
|
|
234
|
+
expect(trackStudioEvent).toHaveBeenCalledTimes(count * 2);
|
|
235
|
+
if (count) {
|
|
236
|
+
expect(trackStudioEvent).toHaveBeenCalledWith("keyframe", { action: "add" });
|
|
237
|
+
expect(trackStudioEvent).toHaveBeenCalledWith("feature_used", {
|
|
238
|
+
feature: "gesture_recording",
|
|
239
|
+
surface: "preview",
|
|
240
|
+
method: inputMethod,
|
|
149
241
|
});
|
|
150
|
-
return null;
|
|
151
242
|
}
|
|
152
|
-
|
|
153
|
-
|
|
243
|
+
},
|
|
244
|
+
);
|
|
154
245
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
246
|
+
it("keeps a pending recording isolated until its writer settles", async () => {
|
|
247
|
+
let release!: () => void;
|
|
248
|
+
const pending = new Promise<void>((resolve) => {
|
|
249
|
+
release = resolve;
|
|
250
|
+
});
|
|
251
|
+
const writer = vi.fn(
|
|
252
|
+
async (_mutation: Record<string, unknown>, options: CommitMutationOptions) => {
|
|
253
|
+
await pending;
|
|
254
|
+
options.onResult?.({ ok: true, changed: true });
|
|
255
|
+
},
|
|
256
|
+
);
|
|
257
|
+
const hook = mountRecording(writer);
|
|
258
|
+
act(() => hook().handleToggleRecording("keyboard"));
|
|
259
|
+
act(() => hook().handleToggleRecording());
|
|
260
|
+
await vi.waitFor(() => expect(writer).toHaveBeenCalledTimes(1));
|
|
261
|
+
act(() => hook().handleToggleRecording("button"));
|
|
262
|
+
expect(gestureRecording.startRecording).toHaveBeenCalledTimes(1);
|
|
263
|
+
await act(async () => {
|
|
264
|
+
release();
|
|
265
|
+
await vi.waitFor(() =>
|
|
266
|
+
expect(trackStudioEvent).toHaveBeenCalledWith("feature_used", {
|
|
267
|
+
feature: "gesture_recording",
|
|
268
|
+
surface: "preview",
|
|
269
|
+
method: "keyboard",
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
expect(gestureRecording.clearSamples).toHaveBeenCalledTimes(1);
|
|
274
|
+
act(() => hook().handleToggleRecording("button"));
|
|
275
|
+
expect(gestureRecording.startRecording).toHaveBeenCalledTimes(2);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("writes one recording to its original selection after selecting another source", async () => {
|
|
279
|
+
const original = {
|
|
280
|
+
id: "original-position",
|
|
281
|
+
targetSelector: "#card",
|
|
282
|
+
propertyGroup: "position",
|
|
283
|
+
method: "set",
|
|
284
|
+
properties: { x: 0, y: 0 },
|
|
285
|
+
resolvedStart: 0,
|
|
286
|
+
position: 0,
|
|
287
|
+
duration: 0,
|
|
288
|
+
} as GsapAnimation;
|
|
289
|
+
const writer = vi.fn(
|
|
290
|
+
async (
|
|
291
|
+
_mutation: Record<string, unknown>,
|
|
292
|
+
options: CommitMutationOptions,
|
|
293
|
+
_selection?: DomEditSelection,
|
|
294
|
+
) => {
|
|
295
|
+
options.onResult?.({ ok: true, changed: true });
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
const hook = mountRecording(writer, [original]);
|
|
299
|
+
act(() => hook().handleToggleRecording("keyboard"));
|
|
300
|
+
const other = document.createElement("div");
|
|
301
|
+
other.id = "other";
|
|
302
|
+
hook.select(
|
|
303
|
+
{ ...makeSelection(other), sourceFile: "other.html", compositionPath: "other.html" },
|
|
304
|
+
[],
|
|
305
|
+
);
|
|
306
|
+
await act(async () => {
|
|
307
|
+
hook().handleToggleRecording();
|
|
308
|
+
await vi.waitFor(() => expect(trackStudioEvent).toHaveBeenCalledTimes(2));
|
|
309
|
+
});
|
|
310
|
+
expect(writer).toHaveBeenCalledExactlyOnceWith(
|
|
311
|
+
expect.objectContaining({
|
|
312
|
+
type: "replace-with-keyframes",
|
|
313
|
+
animationId: "original-position",
|
|
314
|
+
targetSelector: "#card",
|
|
315
|
+
}),
|
|
316
|
+
expect.objectContaining({ keyframeAction: "add" }),
|
|
317
|
+
expect.objectContaining({
|
|
318
|
+
id: "card",
|
|
319
|
+
sourceFile: "index.html",
|
|
320
|
+
compositionPath: "index.html",
|
|
321
|
+
}),
|
|
322
|
+
);
|
|
323
|
+
expect(trackStudioEvent).toHaveBeenCalledWith("keyframe", { action: "add" });
|
|
324
|
+
expect(trackStudioEvent).toHaveBeenCalledWith("feature_used", {
|
|
325
|
+
feature: "gesture_recording",
|
|
326
|
+
surface: "preview",
|
|
327
|
+
method: "keyboard",
|
|
158
328
|
});
|
|
159
329
|
});
|
|
@@ -11,9 +11,12 @@ import { usePlayerStore } from "../player";
|
|
|
11
11
|
import type { DomEditSelection } from "../components/editor/domEditing";
|
|
12
12
|
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
13
13
|
import type { CommitMutationOptions } from "./gsapScriptCommitTypes";
|
|
14
|
+
import { trackPreviewEditResult } from "../utils/previewFeatureUsage";
|
|
15
|
+
import { observeGsapGesture } from "./gsapGestureOutcome";
|
|
14
16
|
import { roundTo3 } from "../utils/rounding";
|
|
15
17
|
import { classifyPropertyGroup } from "@hyperframes/core/gsap-parser";
|
|
16
18
|
import { isInstantHold, idSelector, writeTargetSelector, tweenTargetsElement } from "./gsapShared";
|
|
19
|
+
import { useStableHandlers } from "./useStableHandlers";
|
|
17
20
|
|
|
18
21
|
type RecordedKeyframe = {
|
|
19
22
|
percentage: number;
|
|
@@ -71,6 +74,7 @@ interface GestureSessionRef {
|
|
|
71
74
|
commitMutation?: (
|
|
72
75
|
mutation: Record<string, unknown>,
|
|
73
76
|
options: CommitMutationOptions,
|
|
77
|
+
selection?: DomEditSelection,
|
|
74
78
|
) => Promise<void>;
|
|
75
79
|
}
|
|
76
80
|
|
|
@@ -80,9 +84,16 @@ function reloadOnlyLast(index: number, count: number): Partial<CommitMutationOpt
|
|
|
80
84
|
return index === count - 1 ? { softReload: true } : { skipReload: true };
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
function recordingEnd(selection: DomEditSelection): number | undefined {
|
|
88
|
+
const start = Number.parseFloat(selection.dataAttributes?.start ?? "0") || 0;
|
|
89
|
+
const duration = Number.parseFloat(selection.dataAttributes?.duration ?? "0") || 0;
|
|
90
|
+
return duration > 0 ? start + duration : undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
83
93
|
let gestureRecordingCommitCounter = 0;
|
|
84
94
|
|
|
85
95
|
interface UseGestureCommitParams {
|
|
96
|
+
projectId?: string | null;
|
|
86
97
|
domEditSessionRef: React.MutableRefObject<GestureSessionRef>;
|
|
87
98
|
previewIframeRef: React.RefObject<HTMLIFrameElement | null>;
|
|
88
99
|
showToast: (message: string, tone?: "error" | "info") => void;
|
|
@@ -93,11 +104,12 @@ interface UseGestureCommitParams {
|
|
|
93
104
|
export interface UseGestureCommitResult {
|
|
94
105
|
gestureState: "idle" | "recording";
|
|
95
106
|
gestureRecording: ReturnType<typeof useGestureRecording>;
|
|
96
|
-
handleToggleRecording: () => void;
|
|
107
|
+
handleToggleRecording: (method?: "button" | "keyboard") => void;
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
// fallow-ignore-next-line complexity
|
|
100
111
|
export function useGestureCommit({
|
|
112
|
+
projectId,
|
|
101
113
|
domEditSessionRef,
|
|
102
114
|
previewIframeRef,
|
|
103
115
|
showToast,
|
|
@@ -108,11 +120,13 @@ export function useGestureCommit({
|
|
|
108
120
|
const [gestureState, setGestureState] = useState<"idle" | "recording">("idle");
|
|
109
121
|
const gestureStateRef = useRef<"idle" | "recording">("idle");
|
|
110
122
|
const recordingAutoStopRef = useRef<ReturnType<typeof setInterval>>(undefined);
|
|
123
|
+
const recordingMethodRef = useRef<"button" | "keyboard">("button");
|
|
111
124
|
const recordingStartTimeRef = useRef(0);
|
|
112
125
|
const commitInFlightRef = useRef(false);
|
|
113
126
|
// Capture selection at recording start so commit always targets the recorded element,
|
|
114
127
|
// even if the user's selection changes mid-recording.
|
|
115
128
|
const capturedSelectionRef = useRef<DomEditSelection | null>(null);
|
|
129
|
+
const capturedAnimationsRef = useRef<GsapAnimation[]>([]);
|
|
116
130
|
|
|
117
131
|
// Unmount: clear auto-stop interval
|
|
118
132
|
useEffect(() => () => clearInterval(recordingAutoStopRef.current), []);
|
|
@@ -203,6 +217,12 @@ export function useGestureCommit({
|
|
|
203
217
|
return;
|
|
204
218
|
}
|
|
205
219
|
if (liveSession.commitMutation) {
|
|
220
|
+
const writer = liveSession.commitMutation;
|
|
221
|
+
const outcome = observeGsapGesture((_selection, mutation, options) =>
|
|
222
|
+
writer(mutation, options, sel),
|
|
223
|
+
);
|
|
224
|
+
const commit = (mutation: Record<string, unknown>, options: CommitMutationOptions) =>
|
|
225
|
+
outcome.commit!(sel, mutation, options);
|
|
206
226
|
const recStart = recordingStartTimeRef.current;
|
|
207
227
|
const rawKeyframes = sortedPcts.map((pct) => ({
|
|
208
228
|
percentage: pct,
|
|
@@ -213,7 +233,7 @@ export function useGestureCommit({
|
|
|
213
233
|
const hasPositionProps = keyframes.some((kf) =>
|
|
214
234
|
Object.keys(kf.properties).some((k) => classifyPropertyGroup(k) === "position"),
|
|
215
235
|
);
|
|
216
|
-
const allAnims =
|
|
236
|
+
const allAnims = capturedAnimationsRef.current;
|
|
217
237
|
const existingPositionTween = hasPositionProps
|
|
218
238
|
? allAnims.find(
|
|
219
239
|
(a) =>
|
|
@@ -225,7 +245,7 @@ export function useGestureCommit({
|
|
|
225
245
|
if (isInstantHold(existingPositionTween)) {
|
|
226
246
|
// An instant hold is not a tween to merge into — replace it with the
|
|
227
247
|
// recorded motion (which already starts from the held position).
|
|
228
|
-
await
|
|
248
|
+
await commit(
|
|
229
249
|
{
|
|
230
250
|
type: "replace-with-keyframes",
|
|
231
251
|
animationId: existingPositionTween.id,
|
|
@@ -234,7 +254,7 @@ export function useGestureCommit({
|
|
|
234
254
|
duration: roundTo3(duration),
|
|
235
255
|
keyframes,
|
|
236
256
|
},
|
|
237
|
-
{ label: "Gesture recording (replace set)", softReload: true },
|
|
257
|
+
{ label: "Gesture recording (replace set)", softReload: true, keyframeAction: "add" },
|
|
238
258
|
);
|
|
239
259
|
} else {
|
|
240
260
|
const tweenStart = existingPositionTween.resolvedStart ?? 0;
|
|
@@ -271,7 +291,7 @@ export function useGestureCommit({
|
|
|
271
291
|
|
|
272
292
|
const merged = [...preserved, ...mapped].sort((a, b) => a.percentage - b.percentage);
|
|
273
293
|
|
|
274
|
-
await
|
|
294
|
+
await commit(
|
|
275
295
|
{
|
|
276
296
|
type: "replace-with-keyframes",
|
|
277
297
|
animationId: existingPositionTween.id,
|
|
@@ -283,7 +303,7 @@ export function useGestureCommit({
|
|
|
283
303
|
duration: tweenDur,
|
|
284
304
|
keyframes: merged,
|
|
285
305
|
},
|
|
286
|
-
{ label: "Gesture recording (merge)", softReload: true },
|
|
306
|
+
{ label: "Gesture recording (merge)", softReload: true, keyframeAction: "add" },
|
|
287
307
|
);
|
|
288
308
|
} else {
|
|
289
309
|
// Emit one tween per property group so a mixed-prop gesture (e.g.
|
|
@@ -291,7 +311,7 @@ export function useGestureCommit({
|
|
|
291
311
|
// tween that the position-only drag intercept can't edit.
|
|
292
312
|
const keyframeGroups = partitionKeyframesByGroup(keyframes);
|
|
293
313
|
for (const [index, groupKfs] of keyframeGroups.entries()) {
|
|
294
|
-
await
|
|
314
|
+
await commit(
|
|
295
315
|
{
|
|
296
316
|
type: "add-with-keyframes",
|
|
297
317
|
targetSelector: writeSelector,
|
|
@@ -317,7 +337,7 @@ export function useGestureCommit({
|
|
|
317
337
|
// No existing tween — same per-group split as the new-range branch above.
|
|
318
338
|
const keyframeGroups = partitionKeyframesByGroup(keyframes);
|
|
319
339
|
for (const [index, groupKfs] of keyframeGroups.entries()) {
|
|
320
|
-
await
|
|
340
|
+
await commit(
|
|
321
341
|
{
|
|
322
342
|
type: "add-with-keyframes",
|
|
323
343
|
targetSelector: writeSelector,
|
|
@@ -335,6 +355,7 @@ export function useGestureCommit({
|
|
|
335
355
|
);
|
|
336
356
|
}
|
|
337
357
|
}
|
|
358
|
+
trackPreviewEditResult("gesture_recording", recordingMethodRef.current, outcome.finish());
|
|
338
359
|
}
|
|
339
360
|
showToast(`Recorded ${sortedPcts.length} keyframes`, "info");
|
|
340
361
|
} catch (err) {
|
|
@@ -348,58 +369,71 @@ export function useGestureCommit({
|
|
|
348
369
|
}
|
|
349
370
|
}, [gestureRecording, showToast, isGestureRecordingRef, domEditSessionRef]);
|
|
350
371
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
372
|
+
const armAutoStop = useCallback(
|
|
373
|
+
(elementEnd: number | undefined) => {
|
|
374
|
+
clearInterval(recordingAutoStopRef.current);
|
|
375
|
+
const autoStopAt = elementEnd ?? Infinity;
|
|
376
|
+
recordingAutoStopRef.current = setInterval(() => {
|
|
377
|
+
const { currentTime: t, duration: d } = usePlayerStore.getState();
|
|
378
|
+
const limit = Math.min(autoStopAt, d);
|
|
379
|
+
if (limit > 0 && t >= limit - 0.05) {
|
|
380
|
+
void stopAndCommitRecording();
|
|
381
|
+
}
|
|
382
|
+
}, 100);
|
|
383
|
+
},
|
|
384
|
+
[stopAndCommitRecording],
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
const startRecording = useCallback(
|
|
388
|
+
(method: "button" | "keyboard") => {
|
|
389
|
+
if (readOnlyPreview) return;
|
|
390
|
+
const sel = domEditSessionRef.current.domEditSelection;
|
|
391
|
+
if (!sel) {
|
|
392
|
+
showToast("Select an element first", "error");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const iframe = previewIframeRef.current;
|
|
396
|
+
if (!iframe) {
|
|
397
|
+
showToast("Preview not ready — try again", "error");
|
|
356
398
|
return;
|
|
357
399
|
}
|
|
358
|
-
void stopAndCommitRecording();
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
if (readOnlyPreview) return;
|
|
362
|
-
const sel = domEditSessionRef.current.domEditSelection;
|
|
363
|
-
if (!sel) {
|
|
364
|
-
showToast("Select an element first", "error");
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
const iframe = previewIframeRef.current;
|
|
368
|
-
if (!iframe) {
|
|
369
|
-
showToast("Preview not ready — try again", "error");
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
400
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
401
|
+
const store = usePlayerStore.getState();
|
|
402
|
+
recordingMethodRef.current = method;
|
|
403
|
+
recordingStartTimeRef.current = store.currentTime;
|
|
404
|
+
const elementEnd = recordingEnd(sel);
|
|
405
|
+
capturedSelectionRef.current = sel;
|
|
406
|
+
capturedAnimationsRef.current = domEditSessionRef.current.selectedGsapAnimations ?? [];
|
|
407
|
+
gestureRecording.startRecording(sel.element, iframe, elementEnd);
|
|
408
|
+
gestureStateRef.current = "recording";
|
|
409
|
+
isGestureRecordingRef.current = true;
|
|
410
|
+
setGestureState("recording");
|
|
383
411
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
412
|
+
armAutoStop(elementEnd);
|
|
413
|
+
},
|
|
414
|
+
[
|
|
415
|
+
gestureRecording,
|
|
416
|
+
showToast,
|
|
417
|
+
previewIframeRef,
|
|
418
|
+
domEditSessionRef,
|
|
419
|
+
isGestureRecordingRef,
|
|
420
|
+
readOnlyPreview,
|
|
421
|
+
armAutoStop,
|
|
422
|
+
],
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
const handleToggleRecording = useCallback(
|
|
426
|
+
(method: "button" | "keyboard" = "button") => {
|
|
427
|
+
if (commitInFlightRef.current) return;
|
|
428
|
+
if (gestureStateRef.current === "recording") {
|
|
429
|
+
if (readOnlyPreview) cancelRecording();
|
|
430
|
+
else void stopAndCommitRecording();
|
|
431
|
+
return;
|
|
391
432
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
stopAndCommitRecording,
|
|
397
|
-
cancelRecording,
|
|
398
|
-
previewIframeRef,
|
|
399
|
-
domEditSessionRef,
|
|
400
|
-
isGestureRecordingRef,
|
|
401
|
-
readOnlyPreview,
|
|
402
|
-
]);
|
|
433
|
+
startRecording(method);
|
|
434
|
+
},
|
|
435
|
+
[cancelRecording, readOnlyPreview, startRecording, stopAndCommitRecording],
|
|
436
|
+
);
|
|
403
437
|
|
|
404
|
-
return { gestureState, gestureRecording, handleToggleRecording };
|
|
438
|
+
return useStableHandlers({ gestureState, gestureRecording, handleToggleRecording }, projectId);
|
|
405
439
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from "react";
|
|
2
|
+
import { GestureTrailOverlay } from "../components/editor/GestureTrailOverlay";
|
|
3
|
+
import type { UseGestureCommitResult } from "./useGestureCommit";
|
|
4
|
+
|
|
5
|
+
/** The live gesture trail drawn over the preview while a gesture is recording, or nothing. */
|
|
6
|
+
export function useGestureTrailOverlay(
|
|
7
|
+
{
|
|
8
|
+
gestureState,
|
|
9
|
+
gestureRecording,
|
|
10
|
+
}: Pick<UseGestureCommitResult, "gestureState" | "gestureRecording">,
|
|
11
|
+
previewIframe: HTMLIFrameElement | null,
|
|
12
|
+
compositionSize: { width: number; height: number } | null,
|
|
13
|
+
) {
|
|
14
|
+
const canvasRectRef = useRef<DOMRect | null>(null);
|
|
15
|
+
useLayoutEffect(() => {
|
|
16
|
+
if (gestureState !== "recording" || !previewIframe) {
|
|
17
|
+
canvasRectRef.current = null;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
canvasRectRef.current = previewIframe.getBoundingClientRect();
|
|
21
|
+
}, [gestureState, previewIframe]);
|
|
22
|
+
if (gestureState !== "recording" || !previewIframe) return undefined;
|
|
23
|
+
return (
|
|
24
|
+
<GestureTrailOverlay
|
|
25
|
+
samples={gestureRecording.samplesRef.current}
|
|
26
|
+
sampleCount={gestureRecording.samplesRef.current.length}
|
|
27
|
+
trail={gestureRecording.trailRef.current}
|
|
28
|
+
canvasRect={canvasRectRef.current!}
|
|
29
|
+
compositionSize={compositionSize ?? undefined}
|
|
30
|
+
mode="recording"
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -74,7 +74,7 @@ function mountResizeHandler(
|
|
|
74
74
|
size: { width: number; height: number },
|
|
75
75
|
offset?: { x: number; y: number },
|
|
76
76
|
restore?: () => void,
|
|
77
|
-
) => Promise<void>)
|
|
77
|
+
) => Promise<void | import("../utils/previewFeatureUsage").GeometryCommitResult>)
|
|
78
78
|
| null = null;
|
|
79
79
|
let property: ReturnType<typeof useGsapAwareEditing>["commitAnimatedProperty"] | null = null;
|
|
80
80
|
function Harness() {
|
|
@@ -131,13 +131,18 @@ function mountGroupHandler({
|
|
|
131
131
|
"trackGsapInteractionFailure" | "stageElementPositionOffset" | "handleDomRotationCommit"
|
|
132
132
|
>
|
|
133
133
|
>) {
|
|
134
|
-
let groupCommit!: (
|
|
134
|
+
let groupCommit!: (
|
|
135
|
+
updates: DomEditGroupPathOffsetCommit[],
|
|
136
|
+
) => Promise<void | import("../utils/previewFeatureUsage").GeometryCommitResult>;
|
|
135
137
|
let pathOffsetCommit!: (
|
|
136
138
|
selection: DomEditSelection,
|
|
137
139
|
next: { x: number; y: number },
|
|
138
140
|
route?: { plainTranslate?: boolean },
|
|
139
|
-
) => Promise<void>;
|
|
140
|
-
let rotationCommit!: (
|
|
141
|
+
) => Promise<void | import("../utils/previewFeatureUsage").GeometryCommitResult>;
|
|
142
|
+
let rotationCommit!: (
|
|
143
|
+
selection: DomEditSelection,
|
|
144
|
+
next: RotationCommit,
|
|
145
|
+
) => Promise<void | import("../utils/previewFeatureUsage").GeometryCommitResult>;
|
|
141
146
|
function Harness() {
|
|
142
147
|
const editing = useGsapAwareEditing({
|
|
143
148
|
domEditSelection: null,
|
|
@@ -589,7 +594,7 @@ describe("useGsapAwareEditing anchored resize", () => {
|
|
|
589
594
|
h.selection.element.setAttribute("data-hf-drag-initial-offset-x", "0");
|
|
590
595
|
h.selection.element.setAttribute("data-hf-drag-initial-offset-y", "0");
|
|
591
596
|
|
|
592
|
-
let commit!: Promise<void>;
|
|
597
|
+
let commit!: Promise<void | import("../utils/previewFeatureUsage").GeometryCommitResult>;
|
|
593
598
|
act(() => {
|
|
594
599
|
commit = h.resize(h.selection, { width: 300, height: 200 }, { x: -50.2, y: -25.6 });
|
|
595
600
|
});
|
|
@@ -636,7 +641,7 @@ describe("useGsapAwareEditing anchored resize", () => {
|
|
|
636
641
|
s: DomEditSelection,
|
|
637
642
|
m: unknown,
|
|
638
643
|
o: { coalesceKey?: string; label?: string; softReload?: boolean },
|
|
639
|
-
) => Promise<void>,
|
|
644
|
+
) => Promise<void | import("../utils/previewFeatureUsage").GeometryCommitResult>,
|
|
640
645
|
_fetch: unknown,
|
|
641
646
|
options?: { preflightOnly?: boolean },
|
|
642
647
|
) => {
|