feat(studio): add a global toggle to stop manual edits from auto-recording keyframes

Adds a control-bar toggle (next to the Add Keyframe diamond) that, when off,
makes a manual drag/resize/rotate/panel edit on an already-keyframed element
shift the whole tween by the edit's delta instead of inserting or updating a
keyframe at the playhead. The animation's shape is preserved, just moved.

Wired into every path that can auto-record a keyframe:
- canvas drag-to-move (tryGsapDragIntercept, reuses the existing Alt-drag
  "shift whole path" behavior)
- canvas resize/rotate (tryGsapResizeIntercept, tryGsapRotationIntercept)
- design-panel property edits (useAnimatedPropertyCommit)
- motion-path keyframe-node dragging (MotionPathOverlay), the path a plain
  click-drag on a keyframed element's canvas shape actually takes, since the
  element renders exactly at its current keyframe's position

The shared shift helper reuses synthesizeFlatTweenKeyframes for materializing
a flat tween instead of hand-rolling it, and lives in its own file
(gsapWholePropertyOffsetCommit.ts) to keep gsapDragCommit.ts under the
600-line cap, mirroring the existing gsapDragPositionCommit.ts split.

Fixes #1808
This commit is contained in:
Miguel Angel Simon Sierra
2026-07-01 19:26:03 -07:00
parent 1b8b2ac425
commit 636d5dd6c5
10 changed files with 608 additions and 40 deletions
@@ -17,6 +17,7 @@ import type { SetPatchProps } from "./gsapRuntimePatch";
import { selectorFromSelection, computeElementPercentage } from "./gsapShared";
import { resolveTweenStart, resolveTweenDuration } from "../utils/globalTimeCompiler";
import { roundTo3 } from "../utils/rounding";
import { commitWholePropertyOffset } from "./gsapWholePropertyOffsetCommit";
interface CommitAnimatedPropertyDeps {
selectedGsapAnimations: GsapAnimation[];
@@ -341,6 +342,27 @@ export function useAnimatedPropertyCommit(deps: CommitAnimatedPropertyDeps) {
// keyframe would never land (the bug: scrolling depth on a keyframed element
// just changed the constant instead of dropping a keyframe).
if (elementHasKeyframes && anim) {
// With auto-keyframe off (#1808), nudge the whole tween instead of
// adding/updating a keyframe at the playhead.
if (!usePlayerStore.getState().autoKeyframeEnabled) {
const pct = computeElementPercentage(
usePlayerStore.getState().currentTime,
selection,
anim,
);
await commitWholePropertyOffset(
selection,
anim,
Object.fromEntries(
propEntries.filter((e): e is [string, number] => typeof e[1] === "number"),
),
pct,
iframe,
{ commitMutation: gsapCommitMutation },
`Edit ${primaryProp} (whole animation)`,
);
return;
}
await commitKeyframeProps(
selection,
anim,