mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
fix(studio): author every new tween against one element
U3 fixed "add keyframe at playhead" widening a write to every sibling sharing a class, but wired writeTargetSelector into only two paths. The same bug was still reachable from the add-animation button, drag, resize, rotate, gesture recording, and the property panel: each derived its target from selectorFromSelection, which hands back a bare class for an id-less element, so one edit authored a tween over all five siblings and the timeline collapsed their rows into one. Route every path that authors a NEW tween through the existing ladder: - ensureElementAddressable now accepts selection.selector only when it addresses exactly one element, so the id-minting fallback right below it (previously unreachable whenever any selector was present) does the work. - gsapDragCommit's five new-tween branches go through one newTweenTarget helper; instant patches reuse the written target so the runtime moves the element the source write names. - useGestureCommit and useAnimatedPropertyCommit keep the existing selector for matching/retargeting and author new tweens with a separate write selector. Retargets of an EXISTING tween are deliberately untouched: they keep anim.targetSelector, so a tween aimed at a whole group stays aimed at it. Narrowing the write alone regressed idempotency, verified by test: the "is there already a write for this element" lookups matched targetSelector by string, so the next nudge missed the write it had just made and appended a second, conflicting one. The read half now falls back to the live DOM (tweenTargetsElement, same contract as getAnimationsForElement), which also still matches a deliberate group tween. Tests reproduce each site through a real writer, re-parse with the real parser, and resolve through resolveSelectorElementIds (what feeds the keyframe cache and the lanes), plus pins for the new-tween vs retarget-existing distinction so a future change cannot collapse the two.
This commit is contained in:
@@ -14,7 +14,13 @@ import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
||||
import { usePlayerStore } from "../player/store/playerStore";
|
||||
import { readAllAnimatedProperties, readGsapProperty } from "./gsapRuntimeBridge";
|
||||
import type { SetPatchProps } from "./gsapRuntimePatch";
|
||||
import { selectorFromSelection, computeElementPercentage, isInstantHold } from "./gsapShared";
|
||||
import {
|
||||
selectorFromSelection,
|
||||
computeElementPercentage,
|
||||
isInstantHold,
|
||||
writeTargetSelector,
|
||||
tweenTargetsElement,
|
||||
} from "./gsapShared";
|
||||
import { resolveTweenStart, resolveTweenDuration } from "../utils/globalTimeCompiler";
|
||||
import { roundTo3 } from "../utils/rounding";
|
||||
import { commitWholePropertyOffset } from "./gsapWholePropertyOffsetCommit";
|
||||
@@ -185,7 +191,9 @@ async function commitStaticSet(
|
||||
batch.push(entry);
|
||||
byGroup.set(group, batch);
|
||||
}
|
||||
const staticWrites = animations.filter((a) => isInstantHold(a) && a.targetSelector === selector);
|
||||
const staticWrites = animations.filter(
|
||||
(a) => isInstantHold(a) && tweenTargetsElement(a.targetSelector, selector, selection.element),
|
||||
);
|
||||
// Resolve every group's target BEFORE committing anything, and coalesce
|
||||
// groups that land on the SAME write into one commit: the snapshot is captured
|
||||
// once, so if two groups resolved to one legacy mixed write, a first
|
||||
@@ -243,11 +251,14 @@ async function addGlobalStaticSet(
|
||||
for (const [k, v] of batch) {
|
||||
if (typeof v === "number") numericProps[k as keyof SetPatchProps] = v;
|
||||
}
|
||||
// A brand-new write, so it must address ONE element: `selector` is the bare
|
||||
// class an id-less selection yields, which would hold every sibling.
|
||||
const target = writeTargetSelector(selection) ?? selector;
|
||||
await commit(
|
||||
selection,
|
||||
{
|
||||
type: "add",
|
||||
targetSelector: selector,
|
||||
targetSelector: target,
|
||||
method: "set",
|
||||
position: 0,
|
||||
properties: Object.fromEntries(batch),
|
||||
@@ -259,7 +270,7 @@ async function addGlobalStaticSet(
|
||||
...(Object.keys(numericProps).length > 0
|
||||
? {
|
||||
instantPatch: {
|
||||
selector,
|
||||
selector: target,
|
||||
change: { kind: "global-set" as const, props: numericProps },
|
||||
},
|
||||
}
|
||||
@@ -503,7 +514,7 @@ export function useAnimatedPropertyCommit(deps: CommitAnimatedPropertyDeps) {
|
||||
selection,
|
||||
{
|
||||
type: "add-with-keyframes",
|
||||
targetSelector: selector,
|
||||
targetSelector: writeTargetSelector(selection) ?? selector,
|
||||
position: roundTo3(tStart),
|
||||
duration: roundTo3(tDur),
|
||||
keyframes,
|
||||
|
||||
Reference in New Issue
Block a user