diff --git a/packages/studio/src/hooks/gsapParsedTween.ts b/packages/studio/src/hooks/gsapParsedTween.ts index a5ac930ea5..18a0f2277e 100644 --- a/packages/studio/src/hooks/gsapParsedTween.ts +++ b/packages/studio/src/hooks/gsapParsedTween.ts @@ -66,7 +66,8 @@ export function findParsedTween( const win = iframe?.contentWindow as GsapWindow | null; const start = resolveTweenStart(anim); if (!win?.__timelines || start == null) return null; - const props = Object.keys(anim.keyframes?.keyframes[0]?.properties ?? anim.properties); + const steps = anim.keyframes?.keyframes.map((kf) => kf.properties) ?? [anim.properties]; + const props = [...new Set(steps.flatMap((step) => Object.keys(step)))]; const keyframed = Boolean(anim.keyframes); for (const timeline of Object.values(win.__timelines)) { for (const tween of timeline?.getChildren?.(true) ?? []) { diff --git a/packages/studio/src/hooks/gsapValueAtPlayhead.realGsap.test.ts b/packages/studio/src/hooks/gsapValueAtPlayhead.realGsap.test.ts index 2fb3c9adfd..d7d40d0565 100644 --- a/packages/studio/src/hooks/gsapValueAtPlayhead.realGsap.test.ts +++ b/packages/studio/src/hooks/gsapValueAtPlayhead.realGsap.test.ts @@ -94,6 +94,34 @@ window.__timelines["t"] = tl;`; timeline.kill(); }); +it("saves a dragged keyframe tween with its authored start, not the drag's live value", () => { + const box = Object.assign(document.body.appendChild(document.createElement("div")), { id: "x" }); + const src = script( + "keyframes: { '0%': { opacity: 0 }, '100%': { opacity: 1, x: 300 } }, duration: 1, ease: 'none'", + ).replace("}, 0);", "}, 2);"); + const { timeline, iframe } = play(src, 0.5); + usePlayerStore.setState({ currentTime: 0.5, activeKeyframePct: null }); + gsap.set(box, { x: 77 }); + const anim = parseGsapScriptAcorn(src).animations[0]!; + const selection = { id: "x", selector: "#x", element: box } as DomEditSelection; + + const plan = planValueEdit(selection, anim, { x: 77 }, iframe); + timeline.kill(); + + expect(plan.ok).toBe(true); + if (!plan.ok) return; + const written = replaceTweenWithKeyframesInScript(src, anim.id, plan.mutation)!; + const at = (time: number) => { + const replay = play(written, time); + const x = gsap.getProperty(box, "x"); + replay.timeline.kill(); + return x; + }; + expect(at(0.5)).toBe(77); + expect(at(2)).toBe(0); + expect(at(3)).toBe(300); +}); + it("names only an ease GSAP built in, and refuses a custom function rather than guess", () => { const custom = { vars: { ease: (p: number) => p * p } }; const iframe = { contentWindow: { gsap } } as unknown as HTMLIFrameElement;