@hyperframes/studio 0.8.114 → 0.8.116
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-aXNJKiIg.js → hyperframes-player-mGYvPVpw.js} +1 -1
- package/dist/assets/{index-Bn2RJNTJ.js → index-BWHt0-ir.js} +1 -1
- package/dist/assets/{index-Dz7doz9l.js → index-DYNaXPVf.js} +203 -203
- package/dist/assets/{index-C9N-DGjC.js → index-Dr3hNpvS.js} +1 -1
- package/dist/assets/{index-BYnsJnJV.css → index-t1LH6H5H.css} +1 -1
- package/dist/{chunk-BIUXP3PG.js → chunk-3F47EFCT.js} +142 -277
- package/dist/chunk-3F47EFCT.js.map +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/{domEditingLayers-V67MS4WP.js → domEditingLayers-OL7GLSS4.js} +3 -5
- package/dist/index.d.ts +146 -40
- package/dist/index.html +2 -2
- package/dist/index.js +4021 -3591
- package/dist/index.js.map +1 -1
- package/dist/player.js +2 -2
- package/dist/styles.css +1 -1
- package/package.json +9 -8
- package/src/App.tsx +2 -2
- package/src/captions/hooks/useCaptionSync.ts +10 -6
- package/src/components/LinkedSelectionToggle.tsx +3 -2
- package/src/components/ProjectUnreachableBanner.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/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/commitPreflightHandles.test.tsx +2 -2
- package/src/components/editor/cropResize.ts +4 -3
- package/src/components/editor/domEditingDom.ts +4 -0
- package/src/components/editor/domEditingLayers.ts +1 -5
- package/src/components/editor/domGroupMoveBy.test.tsx +252 -0
- package/src/components/editor/domGroupMoveBy.ts +95 -0
- package/src/components/editor/dragUndoPaint.test.ts +5 -5
- package/src/components/editor/gestureHoldsPreviewReload.test.tsx +33 -0
- package/src/components/editor/manualEditingAvailability.ts +8 -0
- package/src/components/editor/manualEdits.ts +1 -0
- package/src/components/editor/manualEditsParsing.ts +7 -0
- package/src/components/editor/manualOffsetDrag.ts +5 -6
- 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/motionPathSelection.test.ts +6 -31
- package/src/components/editor/probeSourceElement.test.ts +45 -0
- package/src/components/editor/probeSourceElement.ts +41 -16
- package/src/components/editor/propertyPanelFlatTextSection.tsx +10 -7
- package/src/components/editor/propertyPanelFont.tsx +3 -2
- 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/useColorGradingController.ts +2 -1
- 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/editor/useFxCarveNodes.ts +2 -1
- package/src/components/editor/useFxLevelling.ts +2 -1
- package/src/components/feedback/projectProvenance.ts +2 -1
- package/src/components/nle/NLEContext.test.ts +1 -0
- package/src/components/nle/NLEContext.tsx +2 -1
- package/src/components/nle/NLEPreview.tsx +12 -10
- package/src/components/nle/PreviewPoster.tsx +2 -1
- package/src/components/nle/useTimelineEditCallbacks.test.tsx +1 -0
- package/src/components/nle/useTimelineEditCallbacks.ts +7 -1
- package/src/components/renders/useFfmpegStatus.ts +2 -1
- package/src/components/renders/useRenderQueue.ts +5 -4
- package/src/components/sidebar/AssetContextMenu.tsx +4 -4
- package/src/components/sidebar/AssetsTab.tsx +2 -1
- package/src/components/sidebar/GlobalAssetsView.tsx +2 -1
- package/src/components/ui/menuStyle.test.ts +61 -0
- package/src/components/ui/menuStyle.ts +10 -0
- package/src/contexts/DomEditContext.tsx +5 -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/domSelectionTestHarness.ts +23 -0
- package/src/hooks/domStyleCommit.propertyNames.test.ts +73 -0
- package/src/hooks/domStyleCommit.ts +8 -1
- package/src/hooks/draggedGsapPosition.ts +10 -0
- package/src/hooks/elementOffsetStager.ts +5 -4
- package/src/hooks/gsapDragCommit.test.ts +75 -164
- package/src/hooks/gsapDragCommit.ts +4 -125
- package/src/hooks/gsapDragPositionCommit.ts +36 -384
- package/src/hooks/gsapEditOutcome.ts +22 -2
- package/src/hooks/gsapGestureOutcome.test.ts +161 -0
- package/src/hooks/gsapGestureOutcome.ts +64 -0
- package/src/hooks/gsapGroupPreflight.ts +34 -0
- package/src/hooks/gsapKeyframeCommit.ts +3 -12
- package/src/hooks/gsapMutationClient.ts +4 -3
- package/src/hooks/gsapParsedTween.test-helpers.ts +75 -0
- package/src/hooks/gsapParsedTween.test.ts +62 -0
- package/src/hooks/gsapParsedTween.ts +243 -0
- package/src/hooks/gsapPositionDetection.test.ts +78 -0
- package/src/hooks/gsapPositionDetection.ts +52 -33
- package/src/hooks/gsapResizeDropPoint.test.ts +4 -3
- package/src/hooks/gsapResizeElementSize.test.tsx +1 -0
- package/src/hooks/gsapResizeGeometrySweep.test.ts +13 -25
- package/src/hooks/gsapResizeIntercept.test.ts +96 -11
- package/src/hooks/gsapResizeIntercept.ts +79 -168
- package/src/hooks/gsapResizeSweep.test.ts +16 -33
- package/src/hooks/gsapRuntimeBridge.sharedTween.test.ts +6 -8
- package/src/hooks/gsapRuntimeBridge.test.ts +41 -6
- package/src/hooks/gsapRuntimeBridge.ts +105 -131
- package/src/hooks/gsapScriptCommitHelpers.ts +2 -1
- package/src/hooks/gsapScriptCommitTypes.ts +13 -0
- package/src/hooks/gsapShared.ts +7 -0
- package/src/hooks/gsapShared.writeTarget.test.ts +45 -57
- package/src/hooks/gsapValueAtPlayhead.browser.test.ts +83 -0
- package/src/hooks/gsapValueAtPlayhead.realGsap.test.ts +179 -0
- package/src/hooks/gsapValueAtPlayhead.test.ts +552 -0
- package/src/hooks/gsapValueAtPlayhead.ts +292 -0
- package/src/hooks/gsapWholePropertyOffsetCommit.test.ts +43 -0
- package/src/hooks/gsapWholePropertyOffsetCommit.ts +2 -3
- package/src/hooks/keyframeCacheAstLoad.ts +8 -6
- package/src/hooks/newTweenTarget.test.ts +41 -76
- 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/timelineTimingSync.test.ts +1 -0
- package/src/hooks/timelineTimingSync.ts +2 -1
- package/src/hooks/useAnimatedPropertyCommit.test.tsx +46 -5
- package/src/hooks/useAnimatedPropertyCommit.ts +2 -2
- package/src/hooks/useAskAgentModal.ts +2 -1
- package/src/hooks/useBlockCatalog.ts +2 -1
- package/src/hooks/useBlockedTimelineEditToast.ts +12 -3
- package/src/hooks/useCaptionDetection.ts +2 -1
- package/src/hooks/useCompositionContentLoader.ts +2 -1
- package/src/hooks/useDomEditCommitsHelpers.ts +3 -2
- package/src/hooks/useDomEditPositionPatchCommit.ts +18 -23
- package/src/hooks/useDomEditSession.test.tsx +30 -1
- package/src/hooks/useDomEditSession.ts +10 -0
- package/src/hooks/useDomEditTextCommits.test.tsx +41 -0
- package/src/hooks/useDomEditTextCommits.ts +13 -3
- package/src/hooks/useDomEditWiring.ts +4 -2
- package/src/hooks/useDomGeometryCommit.test.tsx +25 -6
- package/src/hooks/useDomGeometryCommit.ts +8 -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/useEditHistoryActions.paint.test.tsx +82 -20
- package/src/hooks/useEditHistoryActions.test.tsx +15 -0
- package/src/hooks/useEditHistoryActions.ts +16 -5
- package/src/hooks/useElementLifecycleOps.ts +2 -1
- package/src/hooks/useEnableKeyframes.test.ts +34 -3
- package/src/hooks/useEnableKeyframes.ts +7 -4
- package/src/hooks/useExternalFileChangeCoordinator.test.tsx +121 -0
- package/src/hooks/useExternalFileChangeCoordinator.ts +35 -8
- package/src/hooks/useFileManager.projectOwnership.test.tsx +6 -1
- package/src/hooks/useFileManager.ts +14 -10
- package/src/hooks/useFileTree.ts +3 -2
- package/src/hooks/useFrameCapture.ts +5 -1
- package/src/hooks/useFreezeFrame.ts +3 -2
- package/src/hooks/useGestureCommit.test.tsx +225 -55
- package/src/hooks/useGestureCommit.ts +86 -55
- package/src/hooks/useGroupCommits.ts +2 -1
- package/src/hooks/useGsapAwareEditing.groupPlan.test.tsx +160 -0
- package/src/hooks/useGsapAwareEditing.test.tsx +28 -35
- package/src/hooks/useGsapAwareEditing.testHelpers.tsx +42 -0
- package/src/hooks/useGsapAwareEditing.ts +101 -195
- package/src/hooks/useGsapAwareGroupMove.ts +179 -0
- package/src/hooks/useGsapInteractionFailureTelemetry.ts +10 -4
- package/src/hooks/useGsapKeyframeOps.sdkUsage.test.tsx +114 -0
- package/src/hooks/useGsapKeyframeOps.ts +36 -10
- package/src/hooks/useGsapScriptCommits.test.tsx +60 -1
- package/src/hooks/useGsapScriptCommits.ts +15 -10
- 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/useLintModal.ts +2 -1
- package/src/hooks/usePersistentEditHistory.test.ts +277 -1
- package/src/hooks/usePersistentEditHistory.ts +47 -8
- 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/usePreviewPersistence.history.test.tsx +14 -3
- package/src/hooks/usePreviewPersistence.ts +13 -11
- package/src/hooks/useProjectFileWriter.ts +20 -19
- package/src/hooks/useRemoveBackground.ts +2 -1
- package/src/hooks/useRenderClipContent.test.ts +18 -0
- package/src/hooks/useRenderClipContent.ts +4 -5
- package/src/hooks/useSdkSession.ts +2 -1
- package/src/hooks/useServerConnection.ts +4 -3
- package/src/hooks/useStudioSelectionPublisher.ts +2 -1
- package/src/hooks/useThumbnailStill.test.ts +100 -0
- package/src/hooks/useThumbnailStill.ts +37 -0
- package/src/hooks/useTimelineDeleteOps.test.tsx +30 -22
- package/src/hooks/useTimelineDeleteOps.ts +22 -27
- package/src/hooks/useTimelineEditing.test.tsx +1 -1
- package/src/index.ts +4 -0
- package/src/player/components/AudioWaveform.tsx +3 -2
- 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/CompositionThumbnail.tsx +2 -1
- 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/clipMenuNormalize.ts +2 -1
- package/src/player/components/clipMenuToolItems.test.tsx +23 -0
- package/src/player/components/clipMenuToolItems.tsx +13 -9
- package/src/player/components/clipPeakMap.ts +2 -1
- 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/hooks/useTimelinePlayer.ts +5 -1
- package/src/player/lib/thumbnailScheduler.test.ts +16 -0
- package/src/player/lib/thumbnailScheduler.ts +10 -2
- 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/sceneSwap.ts +2 -1
- package/src/player/store/timelineElement.ts +12 -0
- package/src/styles/theme.css +3 -0
- package/src/telemetry/client.ts +2 -1
- package/src/utils/blockInstaller.ts +2 -1
- package/src/utils/elementGsap.test.ts +9 -0
- package/src/utils/elementGsap.ts +7 -0
- package/src/utils/gsapSoftReload.ts +2 -3
- package/src/utils/gsapUndoRestore.browser.test.ts +51 -45
- package/src/utils/gsapUndoRestore.ts +3 -6
- 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/previewPrefetch.ts +2 -1
- package/src/utils/razorSplitTransaction.ts +12 -8
- package/src/utils/softReloadTargets.ts +1 -3
- package/src/utils/studioApiFetch.test.ts +75 -0
- package/src/utils/studioApiFetch.ts +13 -0
- package/src/utils/studioFileHistory.ts +4 -1
- package/src/utils/studioHelpers.ts +7 -1
- package/src/utils/studioPendingEdits.test.ts +23 -6
- package/src/utils/studioPendingEdits.ts +32 -8
- package/src/utils/studioTelemetry.test.ts +107 -0
- package/src/utils/studioTelemetry.ts +51 -5
- package/src/utils/timelineCompositionInsert.ts +3 -2
- package/src/webmcp/StudioAgentTools.tsx +4 -3
- 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-OL7GLSS4.js.map} +0 -0
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type GsapDragCommitCallbacks,
|
|
15
15
|
} from "./gsapDragCommit";
|
|
16
16
|
import { usePlayerStore } from "../player/store/playerStore";
|
|
17
|
+
import { liveTween, previewWith } from "./gsapParsedTween.test-helpers";
|
|
17
18
|
|
|
18
19
|
// Minimal selection whose element has no drag-baseline attributes (origX/Y = 0).
|
|
19
20
|
const selection = (): DomEditSelection =>
|
|
@@ -111,175 +112,86 @@ function recordingCallbacks(): {
|
|
|
111
112
|
};
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
describe("commitGsapPositionFromDrag —
|
|
115
|
-
beforeEach(() => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
expect(types).toContain("convert-to-keyframes");
|
|
135
|
-
expect(types).toContain("replace-with-keyframes"); // the extend
|
|
136
|
-
expect(types).not.toContain("add-with-keyframes"); // regression: no parallel tween
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it("adds a keyframe at the playhead when dragged INSIDE its range", async () => {
|
|
140
|
-
// fallow-ignore-next-line code-duplication
|
|
141
|
-
usePlayerStore.setState({ currentTime: 2 }); // inside [1.2, 3.4]
|
|
142
|
-
// fallow-ignore-next-line code-duplication
|
|
143
|
-
const { types, callbacks } = recordingCallbacks();
|
|
144
|
-
|
|
145
|
-
await commitGsapPositionFromDrag(
|
|
146
|
-
selection(),
|
|
147
|
-
flatTween(),
|
|
148
|
-
{ x: -100, y: 0 },
|
|
149
|
-
{ x: 0, y: 0 },
|
|
150
|
-
null,
|
|
151
|
-
"#puck-a",
|
|
152
|
-
callbacks,
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
expect(types).toContain("add-keyframe");
|
|
156
|
-
expect(types).not.toContain("add-with-keyframes");
|
|
157
|
-
});
|
|
115
|
+
describe("commitGsapPositionFromDrag — at a selected keyframe or past the tween", () => {
|
|
116
|
+
beforeEach(() => usePlayerStore.setState({ currentTime: 0, activeKeyframePct: null }));
|
|
117
|
+
const puck = () => {
|
|
118
|
+
const element = document.createElement("div");
|
|
119
|
+
element.id = "puck-a";
|
|
120
|
+
return { id: "puck-a", selector: "#puck-a", element } as unknown as DomEditSelection;
|
|
121
|
+
};
|
|
122
|
+
const preview = (
|
|
123
|
+
sel: DomEditSelection,
|
|
124
|
+
anim: GsapAnimation,
|
|
125
|
+
ends: Record<string, [number, number]>,
|
|
126
|
+
from = false,
|
|
127
|
+
) =>
|
|
128
|
+
previewWith(sel.element, [
|
|
129
|
+
liveTween(
|
|
130
|
+
sel.element,
|
|
131
|
+
{ start: anim.resolvedStart!, duration: anim.duration!, vars: anim.properties, ends },
|
|
132
|
+
{ from },
|
|
133
|
+
),
|
|
134
|
+
]);
|
|
158
135
|
|
|
159
|
-
it("
|
|
160
|
-
// User clicked the 100% diamond (activeKeyframePct=100), playhead drifted past
|
|
161
|
-
// the end. Expect: convert + add-keyframe AT 100% — not replace-with-keyframes.
|
|
136
|
+
it("changes the selected keyframe without extending, even with the playhead past the end", async () => {
|
|
162
137
|
usePlayerStore.setState({ currentTime: 6, activeKeyframePct: 100 }); // outside [1.2, 3.4]
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
138
|
+
const { mutations, callbacks } = recordingCallbacks();
|
|
139
|
+
const sel = puck();
|
|
166
140
|
await commitGsapPositionFromDrag(
|
|
167
|
-
|
|
141
|
+
sel,
|
|
168
142
|
flatTween(),
|
|
169
143
|
{ x: -100, y: 0 },
|
|
170
144
|
{ x: 0, y: 0 },
|
|
171
|
-
|
|
172
|
-
"#puck-a",
|
|
145
|
+
preview(sel, flatTween(), { x: [0, -260] }),
|
|
173
146
|
callbacks,
|
|
174
147
|
);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
148
|
+
expect(mutations).toEqual([
|
|
149
|
+
expect.objectContaining({
|
|
150
|
+
type: "replace-with-keyframes",
|
|
151
|
+
position: 1.2,
|
|
152
|
+
duration: 2.2,
|
|
153
|
+
easeEach: "power1.out",
|
|
154
|
+
keyframes: [{ percentage: 100, properties: { x: -100, y: 0 } }],
|
|
155
|
+
}),
|
|
156
|
+
]);
|
|
181
157
|
expect(usePlayerStore.getState().activeKeyframePct).toBeNull();
|
|
182
|
-
// parked the playhead on the edited keyframe (1.2 start + 100% * 2.2 dur),
|
|
183
|
-
// so the edit is visible instead of rendering the base pose
|
|
184
158
|
expect(usePlayerStore.getState().requestedSeekTime).toBe(3.4);
|
|
185
159
|
});
|
|
186
|
-
});
|
|
187
160
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const keyframedTween = (): GsapAnimation =>
|
|
194
|
-
({
|
|
195
|
-
id: "#puck-a-kf",
|
|
196
|
-
targetSelector: "#puck-a",
|
|
197
|
-
method: "to",
|
|
198
|
-
resolvedStart: 1.2,
|
|
199
|
-
duration: 2.2,
|
|
200
|
-
keyframes: {
|
|
201
|
-
keyframes: [
|
|
202
|
-
{ percentage: 0, properties: { x: 0 } },
|
|
203
|
-
{ percentage: 100, properties: { x: -260 } },
|
|
204
|
-
],
|
|
205
|
-
},
|
|
206
|
-
}) as unknown as GsapAnimation;
|
|
207
|
-
|
|
208
|
-
it("passes backfillDefaults so a newly-introduced prop doesn't move the other keyframes", async () => {
|
|
209
|
-
// Drag the 0% keyframe DOWN (introduces y on an x-only tween). The add-keyframe
|
|
210
|
-
// must carry backfillDefaults at the element's base so 100% gets y:0, not y:780.
|
|
211
|
-
usePlayerStore.setState({ currentTime: 1.2, activeKeyframePct: 0 });
|
|
212
|
-
const { mutations, callbacks } = recordingCallbacks();
|
|
213
|
-
|
|
214
|
-
await commitGsapPositionFromDrag(
|
|
215
|
-
selection(),
|
|
216
|
-
keyframedTween(),
|
|
217
|
-
{ x: 0, y: 780 }, // studioOffset: dragged straight down
|
|
218
|
-
{ x: 0, y: 0 }, // gsapPos → base falls back to {0,0} (selection has no base attrs)
|
|
219
|
-
null,
|
|
220
|
-
"#puck-a",
|
|
221
|
-
callbacks,
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
const addKf = mutations.find((m) => m.type === "add-keyframe");
|
|
225
|
-
expect(addKf).toBeDefined();
|
|
226
|
-
expect(addKf?.percentage).toBe(0); // edited the selected 0% keyframe
|
|
227
|
-
expect(addKf?.properties).toMatchObject({ y: 780 });
|
|
228
|
-
expect(addKf?.backfillDefaults).toEqual({ x: 0, y: 0 }); // base → 100% gets y:0
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
describe("commitGsapPositionFromDrag — from() tween dragged outside its range", () => {
|
|
233
|
-
beforeEach(() => usePlayerStore.setState({ currentTime: 0, activeKeyframePct: null }));
|
|
234
|
-
|
|
235
|
-
const fromTween = (): GsapAnimation =>
|
|
236
|
-
({
|
|
237
|
-
id: "#title-from-400",
|
|
161
|
+
it("extends a from() tween to the playhead, keeping its parsed end as the old end", async () => {
|
|
162
|
+
usePlayerStore.setState({ currentTime: 2.2 }); // outside [0.4, 1.3]
|
|
163
|
+
const fromTween = {
|
|
164
|
+
id: "#title-from-400-position",
|
|
238
165
|
targetSelector: "#title",
|
|
239
166
|
method: "from",
|
|
167
|
+
propertyGroup: "position",
|
|
240
168
|
resolvedStart: 0.4,
|
|
241
169
|
duration: 0.9,
|
|
170
|
+
ease: "none",
|
|
242
171
|
properties: { y: 70 },
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
usePlayerStore.setState({ currentTime: 2.13 }); // outside [0.4, 1.3]
|
|
247
|
-
const types: string[] = [];
|
|
248
|
-
const mutations: Array<Record<string, unknown>> = [];
|
|
249
|
-
const callbacks: GsapDragCommitCallbacks = {
|
|
250
|
-
commitMutation: async (_s, m) => {
|
|
251
|
-
types.push(m.type as string);
|
|
252
|
-
mutations.push(m);
|
|
253
|
-
},
|
|
254
|
-
// After split-into-property-groups, the position group is a from() tween (no keyframes).
|
|
255
|
-
fetchAnimations: async () => [
|
|
256
|
-
{
|
|
257
|
-
id: "#title-from-400-position",
|
|
258
|
-
targetSelector: "#title",
|
|
259
|
-
method: "from",
|
|
260
|
-
propertyGroup: "position",
|
|
261
|
-
resolvedStart: 0.4,
|
|
262
|
-
duration: 0.9,
|
|
263
|
-
properties: { y: 70 },
|
|
264
|
-
} as unknown as GsapAnimation,
|
|
265
|
-
],
|
|
266
|
-
};
|
|
267
|
-
|
|
172
|
+
} as unknown as GsapAnimation;
|
|
173
|
+
const { types, mutations, callbacks } = recordingCallbacks();
|
|
174
|
+
const sel = puck();
|
|
268
175
|
await commitGsapPositionFromDrag(
|
|
269
|
-
|
|
270
|
-
fromTween
|
|
176
|
+
sel,
|
|
177
|
+
fromTween,
|
|
271
178
|
{ x: 0, y: -333 },
|
|
272
179
|
{ x: 0, y: 0 },
|
|
273
|
-
|
|
274
|
-
"#title",
|
|
180
|
+
preview(sel, fromTween, { y: [70, 0] }, true),
|
|
275
181
|
callbacks,
|
|
276
182
|
);
|
|
277
|
-
|
|
278
|
-
expect(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
183
|
+
expect(types).toEqual(["replace-with-keyframes"]);
|
|
184
|
+
expect(mutations[0]).toMatchObject({
|
|
185
|
+
animationId: "#title-from-400-position",
|
|
186
|
+
position: 0.4,
|
|
187
|
+
duration: 1.8,
|
|
188
|
+
easeEach: "none",
|
|
189
|
+
keyframes: [
|
|
190
|
+
{ percentage: 0, properties: { y: 70, x: 0 } },
|
|
191
|
+
{ percentage: 50, properties: { y: 0, x: 0 } },
|
|
192
|
+
{ percentage: 100, properties: { y: -333, x: 0 } },
|
|
193
|
+
],
|
|
194
|
+
});
|
|
283
195
|
});
|
|
284
196
|
});
|
|
285
197
|
|
|
@@ -581,32 +493,31 @@ describe("commitStaticGsapRotation — instantPatch (value-only set)", () => {
|
|
|
581
493
|
});
|
|
582
494
|
});
|
|
583
495
|
|
|
584
|
-
describe("commitGsapPositionFromDrag — keyframe
|
|
496
|
+
describe("commitGsapPositionFromDrag — keyframe commits omit instantPatch", () => {
|
|
585
497
|
beforeEach(() => usePlayerStore.setState({ currentTime: 0, activeKeyframePct: null }));
|
|
586
498
|
|
|
587
|
-
it("a
|
|
588
|
-
usePlayerStore.setState({ currentTime: 2 }); // inside [1.2, 3.4]
|
|
499
|
+
it("a keyframe drag relies on the soft reload, not the instant patch", async () => {
|
|
500
|
+
usePlayerStore.setState({ currentTime: 2 }); // inside [1.2, 3.4]
|
|
589
501
|
const { commits, callbacks } = optionRecordingCallbacks();
|
|
590
|
-
|
|
502
|
+
const element = document.createElement("div");
|
|
503
|
+
const sel = { id: "puck-a", selector: "#puck-a", element } as unknown as DomEditSelection;
|
|
504
|
+
const live = liveTween(element, {
|
|
505
|
+
start: 1.2,
|
|
506
|
+
duration: 2.2,
|
|
507
|
+
vars: { x: -260 },
|
|
508
|
+
ends: { x: [0, -260] },
|
|
509
|
+
});
|
|
591
510
|
await commitGsapPositionFromDrag(
|
|
592
|
-
|
|
511
|
+
sel,
|
|
593
512
|
flatTween(),
|
|
594
513
|
{ x: -100, y: 0 },
|
|
595
514
|
{ x: 0, y: 0 },
|
|
596
|
-
|
|
597
|
-
"#puck-a",
|
|
515
|
+
previewWith(element, [live]),
|
|
598
516
|
callbacks,
|
|
599
517
|
);
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
expect(commits.length).toBeGreaterThan(0);
|
|
604
|
-
for (const c of commits) {
|
|
605
|
-
expect(c.options.instantPatch).toBeUndefined();
|
|
606
|
-
}
|
|
607
|
-
const types = commits.map((c) => c.mutation.type);
|
|
608
|
-
expect(types).toContain("convert-to-keyframes");
|
|
609
|
-
expect(types).toContain("add-keyframe");
|
|
518
|
+
expect(commits.map((c) => c.mutation.type)).toEqual(["replace-with-keyframes"]);
|
|
519
|
+
expect(commits[0]!.options.instantPatch).toBeUndefined();
|
|
520
|
+
expect(commits[0]!.options.softReload).toBe(true);
|
|
610
521
|
});
|
|
611
522
|
});
|
|
612
523
|
|
|
@@ -4,16 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
|
6
6
|
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
|
7
|
-
import {
|
|
8
|
-
STUDIO_ORIGINAL_WIDTH_ATTR,
|
|
9
|
-
STUDIO_ORIGINAL_HEIGHT_ATTR,
|
|
10
|
-
} from "../components/editor/manualEditsTypes";
|
|
11
7
|
import { usePlayerStore } from "../player/store/playerStore";
|
|
12
8
|
import { resolveTweenStart, resolveTweenDuration } from "../utils/globalTimeCompiler";
|
|
13
9
|
import { roundTo3, roundToLayoutPx } from "../utils/rounding";
|
|
14
|
-
import { computeElementPercentage, writeTargetSelector } from "./gsapShared";
|
|
10
|
+
import { computeElementPercentage, keyframeEases, writeTargetSelector } from "./gsapShared";
|
|
15
11
|
import { computeDraggedGsapPosition } from "./draggedGsapPosition";
|
|
16
|
-
import type {
|
|
12
|
+
import type { CommitMutation } from "./gsapScriptCommitTypes";
|
|
17
13
|
import { isGestureTransactionCommit, runGestureTransaction } from "./gestureTransaction";
|
|
18
14
|
import { setPatchFromUpdateProperty } from "./gsapDragStaticSetHelpers";
|
|
19
15
|
import { GsapEditBlockedError } from "./gsapEditOutcome";
|
|
@@ -23,25 +19,7 @@ export {
|
|
|
23
19
|
findSizeSetAnimation,
|
|
24
20
|
} from "./gsapDragStaticSetHelpers";
|
|
25
21
|
export interface GsapDragCommitCallbacks {
|
|
26
|
-
commitMutation:
|
|
27
|
-
selection: DomEditSelection,
|
|
28
|
-
mutation: Record<string, unknown>,
|
|
29
|
-
options: {
|
|
30
|
-
label: string;
|
|
31
|
-
coalesceKey?: string;
|
|
32
|
-
coalesceMs?: number;
|
|
33
|
-
softReload?: boolean;
|
|
34
|
-
skipReload?: boolean;
|
|
35
|
-
beforeReload?: () => void;
|
|
36
|
-
/**
|
|
37
|
-
* Value-only fast path: when set, `runCommit` patches the changed tween in
|
|
38
|
-
* the preview runtime in place (instant, no re-run) and only falls back to
|
|
39
|
-
* the soft reload if the patch can't be safely applied. Attached only to
|
|
40
|
-
* value-only `set` commits; structural/keyframe commits omit it.
|
|
41
|
-
*/
|
|
42
|
-
instantPatch?: { selector: string; change: RuntimeTweenChange };
|
|
43
|
-
},
|
|
44
|
-
) => Promise<void>;
|
|
22
|
+
commitMutation: CommitMutation;
|
|
45
23
|
fetchAnimations?: () => Promise<GsapAnimation[]>;
|
|
46
24
|
}
|
|
47
25
|
|
|
@@ -313,104 +291,6 @@ export async function commitStaticGsapSize(
|
|
|
313
291
|
);
|
|
314
292
|
}
|
|
315
293
|
|
|
316
|
-
/** Rounded `n` when it's a positive finite number, else `fallback`. */
|
|
317
|
-
function positiveOr(n: number, fallback: number): number {
|
|
318
|
-
return Number.isFinite(n) && n > 0 ? roundToLayoutPx(n) : fallback;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Prior size for a keyframed resize: the existing global set's value, else the
|
|
323
|
-
* element's pre-resize size (the draft saved it on the element before mutating
|
|
324
|
-
* el.style.width/height). Falls back to the new size when neither is available.
|
|
325
|
-
*/
|
|
326
|
-
function resolvePriorSize(
|
|
327
|
-
sizeSet: GsapAnimation | null,
|
|
328
|
-
el: Element | null | undefined,
|
|
329
|
-
fallbackW: number,
|
|
330
|
-
fallbackH: number,
|
|
331
|
-
): { width: number; height: number } {
|
|
332
|
-
if (sizeSet) {
|
|
333
|
-
return {
|
|
334
|
-
width: positiveOr(Number(sizeSet.properties.width), fallbackW),
|
|
335
|
-
height: positiveOr(Number(sizeSet.properties.height), fallbackH),
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
const ow = Number.parseFloat(el?.getAttribute(STUDIO_ORIGINAL_WIDTH_ATTR) ?? "");
|
|
339
|
-
const oh = Number.parseFloat(el?.getAttribute(STUDIO_ORIGINAL_HEIGHT_ATTR) ?? "");
|
|
340
|
-
return { width: positiveOr(ow, fallbackW), height: positiveOr(oh, fallbackH) };
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Resize an *animated* element by keyframing its size at the current playhead,
|
|
345
|
-
* instead of a global `gsap.set` hold. Builds a width/height keyframe tween
|
|
346
|
-
* aligned to the element's existing animation: every base keyframe keeps the
|
|
347
|
-
* prior size, only the keyframe nearest the playhead gets the new size — so
|
|
348
|
-
* resizing one keyframe leaves the others unchanged. Replaces any prior global
|
|
349
|
-
* size set. Returns false when there's no usable range (caller falls back to the
|
|
350
|
-
* static set).
|
|
351
|
-
*/
|
|
352
|
-
export async function commitKeyframedSizeFromResize(
|
|
353
|
-
selection: DomEditSelection,
|
|
354
|
-
size: { width: number; height: number },
|
|
355
|
-
selector: string,
|
|
356
|
-
sizeSet: GsapAnimation | null,
|
|
357
|
-
animatedTween: GsapAnimation,
|
|
358
|
-
callbacks: GsapDragCommitCallbacks,
|
|
359
|
-
): Promise<boolean> {
|
|
360
|
-
const ts = resolveTweenStart(animatedTween) ?? 0;
|
|
361
|
-
const td = resolveTweenDuration(animatedTween);
|
|
362
|
-
if (!(td > 0)) return false;
|
|
363
|
-
|
|
364
|
-
const newW = roundToLayoutPx(size.width);
|
|
365
|
-
const newH = roundToLayoutPx(size.height);
|
|
366
|
-
const prior = resolvePriorSize(sizeSet, selection.element, newW, newH);
|
|
367
|
-
|
|
368
|
-
const ct = usePlayerStore.getState().currentTime;
|
|
369
|
-
const pct = Math.max(0, Math.min(100, Math.round(((ct - ts) / td) * 1000) / 10));
|
|
370
|
-
|
|
371
|
-
// Base keyframe percentages from the animated tween (flat tween → 0 & 100),
|
|
372
|
-
// plus the endpoints and the playhead. Each keeps the prior size except the
|
|
373
|
-
// keyframe at the playhead, which gets the new size.
|
|
374
|
-
const pcts = new Set<number>(
|
|
375
|
-
animatedTween.keyframes?.keyframes.map((k) => k.percentage) ?? [0, 100],
|
|
376
|
-
);
|
|
377
|
-
pcts.add(0);
|
|
378
|
-
pcts.add(100);
|
|
379
|
-
pcts.add(pct);
|
|
380
|
-
const keyframes = Array.from(pcts)
|
|
381
|
-
.sort((a, b) => a - b)
|
|
382
|
-
.map((p) => ({
|
|
383
|
-
percentage: p,
|
|
384
|
-
properties: Math.abs(p - pct) < 0.05 ? { width: newW, height: newH } : { ...prior },
|
|
385
|
-
}));
|
|
386
|
-
|
|
387
|
-
// Add the size keyframe tween FIRST, then delete the old global hold. The gesture
|
|
388
|
-
// transport applies both in one ordered batch; a plain commit fallback keeps the
|
|
389
|
-
// same recoverable ordering. Only the transaction's result triggers the reload.
|
|
390
|
-
const addLabel = `Resize (size keyframe ${pct.toFixed(0)}%)`;
|
|
391
|
-
const target = newTweenTarget(selection);
|
|
392
|
-
if (!target) return false;
|
|
393
|
-
await callbacks.commitMutation(
|
|
394
|
-
selection,
|
|
395
|
-
{
|
|
396
|
-
type: "add-with-keyframes",
|
|
397
|
-
targetSelector: target,
|
|
398
|
-
position: roundTo3(ts),
|
|
399
|
-
duration: roundTo3(td),
|
|
400
|
-
keyframes,
|
|
401
|
-
},
|
|
402
|
-
sizeSet ? { label: addLabel, skipReload: true } : { label: addLabel, softReload: true },
|
|
403
|
-
);
|
|
404
|
-
if (sizeSet) {
|
|
405
|
-
await callbacks.commitMutation(
|
|
406
|
-
selection,
|
|
407
|
-
{ type: "delete", animationId: sizeSet.id },
|
|
408
|
-
{ label: "Resize layer", softReload: true },
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
return true;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
294
|
// ── Whole-path offset (plain drag on animated element) ──────────────────
|
|
415
295
|
|
|
416
296
|
/**
|
|
@@ -456,7 +336,6 @@ export async function commitWholePathOffset(
|
|
|
456
336
|
|
|
457
337
|
const ts = resolveTweenStart(effectiveAnim);
|
|
458
338
|
const td = resolveTweenDuration(effectiveAnim);
|
|
459
|
-
const ease = effectiveAnim.keyframes?.easeEach ?? effectiveAnim.ease;
|
|
460
339
|
|
|
461
340
|
let kfs = effectiveAnim.keyframes?.keyframes ?? [];
|
|
462
341
|
if (kfs.length === 0) {
|
|
@@ -493,7 +372,7 @@ export async function commitWholePathOffset(
|
|
|
493
372
|
position: roundTo3(ts ?? 0),
|
|
494
373
|
duration: roundTo3(td || 1),
|
|
495
374
|
keyframes: shifted,
|
|
496
|
-
|
|
375
|
+
...keyframeEases(effectiveAnim),
|
|
497
376
|
},
|
|
498
377
|
{ label: "Move animation path", softReload: true, beforeReload: restoreOffset },
|
|
499
378
|
);
|