Skip to content

Commit a5394fe

Browse files
committed
feat(studio): GSAP tween editing in Design panel (#1092)
Add an ANIMATION section to the Design panel that lets users inspect and edit GSAP tween properties directly — no code editing required. - Recast AST parser replaces regex for GSAP script manipulation - Collapsible per-tween cards with video-editor language - Editable properties with add/remove, easing curve preview - DOMParser for HTML script extraction (no regex) - Selection position fix: force reapply before rect reads - GSAP translate bake fix: MutationObserver on offset elements strips doubled translate instantly; disconnects when seek wrappers install - PropertyPanel visual offset: X/Y show actual position - Input validation: duration/position reject negatives - Feature flag: VITE_STUDIO_ENABLE_GSAP_PANEL (default false)
1 parent d8ce2e4 commit a5394fe

18 files changed

Lines changed: 1441 additions & 258 deletions

bun.lock

Lines changed: 19 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/package.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@
5858
"import": "./src/registry/index.ts",
5959
"types": "./src/registry/index.ts"
6060
},
61+
"./gsap-parser": {
62+
"import": "./src/parsers/gsapParser.ts",
63+
"types": "./src/parsers/gsapParser.ts"
64+
},
6165
"./schemas/registry.json": "./schemas/registry.json",
6266
"./schemas/registry-item.json": "./schemas/registry-item.json"
6367
},
@@ -105,6 +109,10 @@
105109
"import": "./dist/registry/index.js",
106110
"types": "./dist/registry/index.d.ts"
107111
},
112+
"./gsap-parser": {
113+
"import": "./dist/parsers/gsapParser.js",
114+
"types": "./dist/parsers/gsapParser.d.ts"
115+
},
108116
"./schemas/registry.json": "./schemas/registry.json",
109117
"./schemas/registry-item.json": "./schemas/registry-item.json"
110118
},
@@ -134,8 +142,10 @@
134142
"prepublishOnly": "echo skip"
135143
},
136144
"dependencies": {
145+
"@babel/parser": "^7.27.0",
137146
"@chenglou/pretext": "^0.0.5",
138-
"postcss": "^8.5.8"
147+
"postcss": "^8.5.8",
148+
"recast": "^0.23.11"
139149
},
140150
"devDependencies": {
141151
"@types/jsdom": "^28.0.0",

packages/core/src/parsers/gsapParser.test.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,7 @@ describe("parseGsapScript", () => {
7979
expect(anim.position).toBe(2);
8080
});
8181

82-
it("parseObjectLiteral does not match negative numbers (known limitation)", () => {
83-
// The regex in parseObjectLiteral only matches [\d.]+, not negative numbers.
84-
// Negative values like x: -100 won't be parsed by the object literal parser.
82+
it("parses negative numbers in property values", () => {
8583
const script = `
8684
const tl = gsap.timeline({ paused: true });
8785
tl.fromTo("#el5", { opacity: 0, x: -100 }, { opacity: 1, x: 0, duration: 1 }, 0);
@@ -92,8 +90,7 @@ describe("parseGsapScript", () => {
9290
const anim = result.animations[0];
9391
expect(anim.fromProperties).toBeDefined();
9492
expect(anim.fromProperties?.opacity).toBe(0);
95-
// -100 is not parseable by the regex, so x won't be in fromProperties
96-
expect(anim.fromProperties?.x).toBeUndefined();
93+
expect(anim.fromProperties?.x).toBe(-100);
9794
});
9895

9996
it("handles an empty script", () => {
@@ -142,7 +139,7 @@ describe("parseGsapScript", () => {
142139
expect(result.animations[2].method).toBe("to");
143140
});
144141

145-
it("filters out unsupported properties from animations", () => {
142+
it("extracts all GSAP properties including non-standard ones", () => {
146143
const script = `
147144
const tl = gsap.timeline({ paused: true });
148145
tl.to("#el1", { opacity: 1, backgroundColor: "red", x: 50, duration: 0.5 }, 0);
@@ -151,8 +148,7 @@ describe("parseGsapScript", () => {
151148

152149
expect(result.animations[0].properties.opacity).toBe(1);
153150
expect(result.animations[0].properties.x).toBe(50);
154-
// backgroundColor is not in SUPPORTED_PROPS, so it's filtered out
155-
expect(result.animations[0].properties.backgroundColor).toBeUndefined();
151+
expect(result.animations[0].properties.backgroundColor).toBe("red");
156152
});
157153

158154
it("extracts ease from properties", () => {
@@ -244,7 +240,7 @@ describe("gsapAnimationsToKeyframes", () => {
244240
targetSelector: "#el1",
245241
method: "set",
246242
position: 5,
247-
properties: { x: 0, y: 0, scale: 1 },
243+
properties: { x: 0, y: 0 },
248244
},
249245
{
250246
id: "anim-2",
@@ -258,7 +254,6 @@ describe("gsapAnimationsToKeyframes", () => {
258254

259255
const keyframes = gsapAnimationsToKeyframes(animations, 5, { skipBaseSet: true });
260256

261-
// The set at position 5 (time=0) with x=0, y=0, scale=1 (base values) should be skipped
262257
expect(keyframes).toHaveLength(1);
263258
expect(keyframes[0].id).toBe("anim-2");
264259
});

0 commit comments

Comments
 (0)