mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 23:03:09 +00:00
fix(studio): don't crash resizing an element whose keyframes were removed
commitWholePropertyOffset reduced the tween's keyframe list to find the "nearest" stop without an initial value. When a to()/from() tween had been collapsed to a zero-duration immediateRender hold (what removeAllKeyframes leaves behind), synthesizeFlatTweenKeyframes correctly treats it as a static hold and returns null, leaving an empty keyframe list — so the reduce threw "Reduce of empty array with no initial value". Reachable by resizing such an element with auto-keyframe recording off. With no keyframe shape to preserve, persist the flat value directly via an update-properties mutation instead.
This commit is contained in:
@@ -133,4 +133,30 @@ describe("commitWholePropertyOffset", () => {
|
||||
});
|
||||
expect(keyframes[1]).toEqual({ percentage: 100, properties: { x: 120, opacity: 0.5 } });
|
||||
});
|
||||
|
||||
it("persists a flat update instead of crashing when the tween has no synthesizable shape", async () => {
|
||||
// Regression: removeAllKeyframesFromScript collapses a keyframed tween into a
|
||||
// zero-duration immediateRender hold — synthesizeFlatTweenKeyframes treats that
|
||||
// as a static hold (returns null), so `kfs` is empty. Resizing this element with
|
||||
// auto-keyframe off used to call `kfs.reduce(...)` with no initial value, which
|
||||
// throws "Reduce of empty array with no initial value".
|
||||
const anim = {
|
||||
id: "#box-hold",
|
||||
targetSelector: "#box",
|
||||
method: "to",
|
||||
resolvedStart: 0,
|
||||
duration: 0,
|
||||
properties: { width: 200 },
|
||||
extras: { immediateRender: "__raw:true" },
|
||||
} as unknown as GsapAnimation;
|
||||
|
||||
const { mutations, callbacks } = recordingCallbacks();
|
||||
await expect(
|
||||
commitWholePropertyOffset(selection(), anim, { width: 300 }, 0, null, callbacks, "Resize"),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(mutations).toEqual([
|
||||
{ type: "update-properties", animationId: "#box-hold", properties: { width: 300 } },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,9 +43,18 @@ export async function commitWholePropertyOffset(
|
||||
typeof props[key] === "number" ? (props[key] as number) : (PROPERTY_DEFAULTS[key] ?? 0);
|
||||
|
||||
const kfs =
|
||||
effectiveAnim.keyframes?.keyframes ??
|
||||
synthesizeFlatTweenKeyframes(effectiveAnim)?.keyframes ??
|
||||
[];
|
||||
effectiveAnim.keyframes?.keyframes ?? synthesizeFlatTweenKeyframes(effectiveAnim)?.keyframes;
|
||||
if (!kfs || kfs.length === 0) {
|
||||
// A `to()`/`from()` collapsed to a zero-duration immediateRender hold (what
|
||||
// removeAllKeyframesFromScript leaves behind) has no shape to preserve —
|
||||
// just persist the flat value instead of replacing with an empty keyframe list.
|
||||
await callbacks.commitMutation(
|
||||
selection,
|
||||
{ type: "update-properties", animationId: effectiveAnim.id, properties: newValues },
|
||||
{ label, softReload: true },
|
||||
);
|
||||
return;
|
||||
}
|
||||
const nearest = kfs.reduce((best, kf) =>
|
||||
Math.abs(kf.percentage - currentPct) < Math.abs(best.percentage - currentPct) ? kf : best,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user