mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
fix(studio): static-position drag no longer freezes an element beside an animated rotation (#2016)
## What Brief description of the change. ## Why Why is this change needed? ## How How was this implemented? Any notable design decisions? ## Test plan How was this tested? - [ ] Unit tests added/updated - [ ] Manual testing performed - [ ] Documentation updated (if applicable)
This commit is contained in:
@@ -5,6 +5,7 @@ import { commitGsapPositionFromDrag } from "./gsapDragPositionCommit";
|
||||
import {
|
||||
commitStaticGsapPosition,
|
||||
commitStaticGsapRotation,
|
||||
findExistingPositionWrite,
|
||||
parkPlayheadOnKeyframe,
|
||||
type GsapDragCommitCallbacks,
|
||||
} from "./gsapDragCommit";
|
||||
@@ -75,6 +76,7 @@ describe("commitGsapPositionFromDrag — flat tween", () => {
|
||||
});
|
||||
|
||||
it("extends the existing tween (never spawns a parallel one) when dragged OUTSIDE its range", async () => {
|
||||
// fallow-ignore-next-line code-duplication
|
||||
usePlayerStore.setState({ currentTime: 6 }); // outside [1.2, 3.4]
|
||||
const { types, callbacks } = recordingCallbacks();
|
||||
|
||||
@@ -94,7 +96,9 @@ describe("commitGsapPositionFromDrag — flat tween", () => {
|
||||
});
|
||||
|
||||
it("adds a keyframe at the playhead when dragged INSIDE its range", async () => {
|
||||
// fallow-ignore-next-line code-duplication
|
||||
usePlayerStore.setState({ currentTime: 2 }); // inside [1.2, 3.4]
|
||||
// fallow-ignore-next-line code-duplication
|
||||
const { types, callbacks } = recordingCallbacks();
|
||||
|
||||
await commitGsapPositionFromDrag(
|
||||
@@ -115,6 +119,7 @@ describe("commitGsapPositionFromDrag — flat tween", () => {
|
||||
// User clicked the 100% diamond (activeKeyframePct=100), playhead drifted past
|
||||
// the end. Expect: convert + add-keyframe AT 100% — not replace-with-keyframes.
|
||||
usePlayerStore.setState({ currentTime: 6, activeKeyframePct: 100 }); // outside [1.2, 3.4]
|
||||
// fallow-ignore-next-line code-duplication
|
||||
const { types, mutations, callbacks } = recordingCallbacks();
|
||||
|
||||
await commitGsapPositionFromDrag(
|
||||
@@ -338,6 +343,7 @@ describe("commitStaticGsapPosition — instantPatch (value-only set)", () => {
|
||||
await commitStaticGsapPosition(
|
||||
selection(),
|
||||
{ x: -50, y: 30 },
|
||||
// fallow-ignore-next-line code-duplication
|
||||
{ x: 0, y: 0 },
|
||||
"#puck-a",
|
||||
null, // no existing set → `add` a new base gsap.set
|
||||
@@ -352,6 +358,55 @@ describe("commitStaticGsapPosition — instantPatch (value-only set)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// A degenerate `tl.to("#el",{keyframes:{...},duration:0})` — what a pre-fix drag
|
||||
// left behind when it routed a STATIC position hold (sitting beside a keyframed
|
||||
// rotation) into the keyframe branch. A duration-0 keyframed tween renders its
|
||||
// final keyframe at every playhead, so the element froze and "couldn't move".
|
||||
const keyframedZeroDurationHold = (): GsapAnimation =>
|
||||
({
|
||||
id: "#puck-a-frozen",
|
||||
targetSelector: "#puck-a",
|
||||
method: "to",
|
||||
propertyGroup: "position",
|
||||
duration: 0,
|
||||
keyframes: {
|
||||
keyframes: [
|
||||
{ percentage: 0, properties: { x: 100, y: 50 } },
|
||||
{ percentage: 100, properties: { x: -260, y: -70 } },
|
||||
],
|
||||
},
|
||||
properties: {},
|
||||
}) as unknown as GsapAnimation;
|
||||
|
||||
describe("static position hold recognition + heal (frozen duration-0 keyframed tween)", () => {
|
||||
beforeEach(() => usePlayerStore.setState({ currentTime: 0, activeKeyframePct: null }));
|
||||
|
||||
it("findExistingPositionWrite recognizes a keyframed zero-duration position hold", () => {
|
||||
const found = findExistingPositionWrite([keyframedZeroDurationHold()], "#puck-a");
|
||||
expect(found?.id).toBe("#puck-a-frozen");
|
||||
});
|
||||
|
||||
it("commitStaticGsapPosition heals a keyframed hold by delete + clean add-set (never update-property)", async () => {
|
||||
const { commits, callbacks } = optionRecordingCallbacks();
|
||||
|
||||
await commitStaticGsapPosition(
|
||||
selection(),
|
||||
{ x: -50, y: 30 },
|
||||
{ x: 0, y: 0 },
|
||||
"#puck-a",
|
||||
keyframedZeroDurationHold(),
|
||||
callbacks,
|
||||
);
|
||||
|
||||
const types = commits.map((c) => c.mutation.type);
|
||||
// Can't update-property into keyframes — must delete the frozen tween and
|
||||
// write a clean static set, so the element becomes freely movable.
|
||||
expect(types).toEqual(["delete", "add"]);
|
||||
expect(types).not.toContain("update-property");
|
||||
expect((commits[1].mutation as { method?: string }).method).toBe("set");
|
||||
});
|
||||
});
|
||||
|
||||
describe("commitStaticGsapRotation — instantPatch (value-only set)", () => {
|
||||
beforeEach(() => usePlayerStore.setState({ currentTime: 0, activeKeyframePct: null }));
|
||||
|
||||
@@ -377,6 +432,7 @@ describe("commitStaticGsapRotation — instantPatch (value-only set)", () => {
|
||||
it("ADDS a global gsap.set with a global-set instantPatch (off-timeline, no flash)", async () => {
|
||||
const { commits, callbacks } = optionRecordingCallbacks();
|
||||
|
||||
// fallow-ignore-next-line code-duplication
|
||||
await commitStaticGsapRotation(selection(), 42, "#puck-a", null, callbacks);
|
||||
|
||||
expect(commits).toHaveLength(1);
|
||||
|
||||
Reference in New Issue
Block a user