@hyperframes/parsers 0.7.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/dist/gsapConstants.d.ts +14 -0
- package/dist/gsapConstants.js +107 -0
- package/dist/gsapConstants.js.map +1 -0
- package/dist/gsapParser.d.ts +157 -0
- package/dist/gsapParser.js +2431 -0
- package/dist/gsapParser.js.map +1 -0
- package/dist/gsapParserAcorn.d.ts +51 -0
- package/dist/gsapParserAcorn.js +1294 -0
- package/dist/gsapParserAcorn.js.map +1 -0
- package/dist/gsapParserExports.d.ts +5 -0
- package/dist/gsapParserExports.js +1558 -0
- package/dist/gsapParserExports.js.map +1 -0
- package/dist/gsapSerialize-B_JRTCeV.d.ts +535 -0
- package/dist/gsapWriterAcorn.d.ts +93 -0
- package/dist/gsapWriterAcorn.js +2369 -0
- package/dist/gsapWriterAcorn.js.map +1 -0
- package/dist/hfIds.d.ts +18 -0
- package/dist/hfIds.js +74 -0
- package/dist/hfIds.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +2500 -0
- package/dist/index.js.map +1 -0
- package/dist/springEase.d.ts +30 -0
- package/dist/springEase.js +44 -0
- package/dist/springEase.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,2431 @@
|
|
|
1
|
+
// src/gsapParser.ts
|
|
2
|
+
import * as recast from "recast";
|
|
3
|
+
import { parse as babelParse } from "@babel/parser";
|
|
4
|
+
|
|
5
|
+
// src/gsapConstants.ts
|
|
6
|
+
var SUPPORTED_PROPS = [
|
|
7
|
+
// 2D Transforms
|
|
8
|
+
"x",
|
|
9
|
+
"y",
|
|
10
|
+
"scale",
|
|
11
|
+
"scaleX",
|
|
12
|
+
"scaleY",
|
|
13
|
+
"rotation",
|
|
14
|
+
"skewX",
|
|
15
|
+
"skewY",
|
|
16
|
+
// 3D Transforms
|
|
17
|
+
"z",
|
|
18
|
+
"rotationX",
|
|
19
|
+
"rotationY",
|
|
20
|
+
"rotationZ",
|
|
21
|
+
"perspective",
|
|
22
|
+
"transformPerspective",
|
|
23
|
+
"transformOrigin",
|
|
24
|
+
// Visibility
|
|
25
|
+
"opacity",
|
|
26
|
+
"visibility",
|
|
27
|
+
"autoAlpha",
|
|
28
|
+
// Dimensions
|
|
29
|
+
"width",
|
|
30
|
+
"height",
|
|
31
|
+
// Colors
|
|
32
|
+
"color",
|
|
33
|
+
"backgroundColor",
|
|
34
|
+
"borderColor",
|
|
35
|
+
// Box model
|
|
36
|
+
"borderRadius",
|
|
37
|
+
// Typography
|
|
38
|
+
"fontSize",
|
|
39
|
+
"letterSpacing",
|
|
40
|
+
// Filter & Clipping
|
|
41
|
+
"filter",
|
|
42
|
+
"clipPath",
|
|
43
|
+
// DOM content (number counters, text roll-ups)
|
|
44
|
+
"innerText"
|
|
45
|
+
];
|
|
46
|
+
var PROPERTY_GROUPS = {
|
|
47
|
+
position: /* @__PURE__ */ new Set(["x", "y", "xPercent", "yPercent"]),
|
|
48
|
+
scale: /* @__PURE__ */ new Set(["scale", "scaleX", "scaleY"]),
|
|
49
|
+
size: /* @__PURE__ */ new Set(["width", "height"]),
|
|
50
|
+
rotation: /* @__PURE__ */ new Set(["rotation", "skewX", "skewY"]),
|
|
51
|
+
visual: /* @__PURE__ */ new Set(["opacity", "autoAlpha"]),
|
|
52
|
+
other: /* @__PURE__ */ new Set()
|
|
53
|
+
};
|
|
54
|
+
var PROP_TO_GROUP = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const [group, props] of Object.entries(PROPERTY_GROUPS)) {
|
|
56
|
+
for (const p of props) PROP_TO_GROUP.set(p, group);
|
|
57
|
+
}
|
|
58
|
+
function classifyPropertyGroup(prop) {
|
|
59
|
+
return PROP_TO_GROUP.get(prop) ?? "other";
|
|
60
|
+
}
|
|
61
|
+
function classifyTweenPropertyGroup(properties) {
|
|
62
|
+
const groups = /* @__PURE__ */ new Set();
|
|
63
|
+
for (const key of Object.keys(properties)) {
|
|
64
|
+
if (key === "transformOrigin" || key === "_auto" || key === "data") continue;
|
|
65
|
+
const g = classifyPropertyGroup(key);
|
|
66
|
+
groups.add(g);
|
|
67
|
+
}
|
|
68
|
+
if (groups.size === 1) return groups.values().next().value;
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
var SUPPORTED_EASES = [
|
|
72
|
+
"none",
|
|
73
|
+
"power1.in",
|
|
74
|
+
"power1.out",
|
|
75
|
+
"power1.inOut",
|
|
76
|
+
"power2.in",
|
|
77
|
+
"power2.out",
|
|
78
|
+
"power2.inOut",
|
|
79
|
+
"power3.in",
|
|
80
|
+
"power3.out",
|
|
81
|
+
"power3.inOut",
|
|
82
|
+
"power4.in",
|
|
83
|
+
"power4.out",
|
|
84
|
+
"power4.inOut",
|
|
85
|
+
"back.in",
|
|
86
|
+
"back.out",
|
|
87
|
+
"back.inOut",
|
|
88
|
+
"elastic.in",
|
|
89
|
+
"elastic.out",
|
|
90
|
+
"elastic.inOut",
|
|
91
|
+
"bounce.in",
|
|
92
|
+
"bounce.out",
|
|
93
|
+
"bounce.inOut",
|
|
94
|
+
"expo.in",
|
|
95
|
+
"expo.out",
|
|
96
|
+
"expo.inOut",
|
|
97
|
+
"spring-gentle",
|
|
98
|
+
"spring-bouncy",
|
|
99
|
+
"spring-stiff",
|
|
100
|
+
"spring-wobbly",
|
|
101
|
+
"spring-heavy",
|
|
102
|
+
"steps(1)"
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
// src/gsapSerialize.ts
|
|
106
|
+
function serializeGsapAnimations(animations, timelineVar = "tl", options) {
|
|
107
|
+
const sorted = [...animations].sort((a, b) => {
|
|
108
|
+
const aNum = a.resolvedStart ?? (typeof a.position === "number" ? a.position : Number.MAX_SAFE_INTEGER);
|
|
109
|
+
const bNum = b.resolvedStart ?? (typeof b.position === "number" ? b.position : Number.MAX_SAFE_INTEGER);
|
|
110
|
+
return aNum - bNum;
|
|
111
|
+
});
|
|
112
|
+
const lines = sorted.map((anim) => {
|
|
113
|
+
const selector = `"${anim.targetSelector}"`;
|
|
114
|
+
const props = { ...anim.properties };
|
|
115
|
+
if (anim.duration !== void 0) props.duration = anim.duration;
|
|
116
|
+
if (anim.ease) props.ease = anim.ease;
|
|
117
|
+
let propsStr = serializeObject(props);
|
|
118
|
+
if (anim.extras && Object.keys(anim.extras).length > 0) {
|
|
119
|
+
const extrasStr = serializeExtras(anim.extras);
|
|
120
|
+
if (Object.keys(props).length === 0) {
|
|
121
|
+
propsStr = `{ ${extrasStr} }`;
|
|
122
|
+
} else {
|
|
123
|
+
propsStr = propsStr.slice(0, -2) + `, ${extrasStr} }`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const posStr = typeof anim.position === "string" ? `"${anim.position}"` : anim.position;
|
|
127
|
+
switch (anim.method) {
|
|
128
|
+
case "set":
|
|
129
|
+
return anim.global ? ` gsap.set(${selector}, ${propsStr});` : ` ${timelineVar}.set(${selector}, ${propsStr}, ${posStr});`;
|
|
130
|
+
case "to":
|
|
131
|
+
return ` ${timelineVar}.to(${selector}, ${propsStr}, ${posStr});`;
|
|
132
|
+
case "from":
|
|
133
|
+
return ` ${timelineVar}.from(${selector}, ${propsStr}, ${posStr});`;
|
|
134
|
+
case "fromTo": {
|
|
135
|
+
const fromStr = serializeObject(anim.fromProperties || {});
|
|
136
|
+
return ` ${timelineVar}.fromTo(${selector}, ${fromStr}, ${propsStr}, ${posStr});`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
let mediaSync = "";
|
|
141
|
+
if (options?.includeMediaSync) {
|
|
142
|
+
mediaSync = `
|
|
143
|
+
${timelineVar}.eventCallback("onUpdate", function() {
|
|
144
|
+
const time = ${timelineVar}.time();
|
|
145
|
+
document.querySelectorAll("video[data-start], audio[data-start]").forEach(function(media) {
|
|
146
|
+
const start = parseFloat(media.dataset.start);
|
|
147
|
+
const end = parseFloat(media.dataset.end) || Infinity;
|
|
148
|
+
const mediaTime = time - start;
|
|
149
|
+
if (time >= start && time < end) {
|
|
150
|
+
if (Math.abs(media.currentTime - mediaTime) > 0.1) {
|
|
151
|
+
media.currentTime = mediaTime;
|
|
152
|
+
}
|
|
153
|
+
if (media.paused && !${timelineVar}.paused()) {
|
|
154
|
+
media.play().catch(function() {});
|
|
155
|
+
}
|
|
156
|
+
} else if (!media.paused) {
|
|
157
|
+
media.pause();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});`;
|
|
161
|
+
}
|
|
162
|
+
const preamble = options?.preamble || `const ${timelineVar} = gsap.timeline({ paused: true });`;
|
|
163
|
+
const postamble = options?.postamble ? `
|
|
164
|
+
${options.postamble}` : "";
|
|
165
|
+
return `
|
|
166
|
+
${preamble}
|
|
167
|
+
${lines.join("\n")}${mediaSync}${postamble}
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
170
|
+
function serializeValue(value) {
|
|
171
|
+
if (typeof value === "string" && value.startsWith("__raw:")) {
|
|
172
|
+
return value.slice(6);
|
|
173
|
+
}
|
|
174
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
175
|
+
return String(value);
|
|
176
|
+
}
|
|
177
|
+
function safeJsKey(key) {
|
|
178
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
|
|
179
|
+
}
|
|
180
|
+
function serializeObject(obj) {
|
|
181
|
+
const entries = Object.entries(obj).map(([key, value]) => {
|
|
182
|
+
return `${safeJsKey(key)}: ${serializeValue(value)}`;
|
|
183
|
+
});
|
|
184
|
+
return `{ ${entries.join(", ")} }`;
|
|
185
|
+
}
|
|
186
|
+
function serializeExtras(extras) {
|
|
187
|
+
return Object.entries(extras).map(([key, value]) => {
|
|
188
|
+
return `${safeJsKey(key)}: ${serializeValue(value)}`;
|
|
189
|
+
}).join(", ");
|
|
190
|
+
}
|
|
191
|
+
function getAnimationsForElementId(animations, elementId) {
|
|
192
|
+
const selector = `#${elementId}`;
|
|
193
|
+
return animations.filter((a) => a.targetSelector === selector);
|
|
194
|
+
}
|
|
195
|
+
var FORBIDDEN_GSAP_PATTERNS = [
|
|
196
|
+
{ pattern: /\.call\s*\(/, message: "call() method not allowed" },
|
|
197
|
+
{ pattern: /\.add\s*\(/, message: "add() method not allowed" },
|
|
198
|
+
{ pattern: /\.addPause\s*\(/, message: "addPause() method not allowed" },
|
|
199
|
+
{ pattern: /gsap\.registerEffect\s*\(/, message: "registerEffect() not allowed" },
|
|
200
|
+
{ pattern: /ScrollTrigger/, message: "ScrollTrigger not allowed" },
|
|
201
|
+
{ pattern: /onComplete\s*:/, message: "onComplete callback not allowed" },
|
|
202
|
+
{ pattern: /onUpdate\s*:/, message: "onUpdate callback not allowed" },
|
|
203
|
+
{ pattern: /onStart\s*:/, message: "onStart callback not allowed" },
|
|
204
|
+
{ pattern: /onRepeat\s*:/, message: "onRepeat callback not allowed" },
|
|
205
|
+
{ pattern: /onReverseComplete\s*:/, message: "onReverseComplete callback not allowed" },
|
|
206
|
+
{ pattern: /repeat\s*:\s*-1/, message: "Infinite repeat (repeat: -1) not allowed" },
|
|
207
|
+
{ pattern: /Math\.random\s*\(/, message: "Random values (Math.random) not allowed" },
|
|
208
|
+
{ pattern: /Date\.now\s*\(/, message: "Date-dependent values (Date.now) not allowed" },
|
|
209
|
+
{ pattern: /new\s+Date\s*\(/, message: "Date constructor not allowed" },
|
|
210
|
+
{ pattern: /setTimeout\s*\(/, message: "setTimeout not allowed" },
|
|
211
|
+
{ pattern: /setInterval\s*\(/, message: "setInterval not allowed" },
|
|
212
|
+
{ pattern: /requestAnimationFrame\s*\(/, message: "requestAnimationFrame not allowed" }
|
|
213
|
+
];
|
|
214
|
+
function validateCompositionGsap(script) {
|
|
215
|
+
const errors = [];
|
|
216
|
+
const warnings = [];
|
|
217
|
+
for (const { pattern, message } of FORBIDDEN_GSAP_PATTERNS) {
|
|
218
|
+
if (pattern.test(script)) errors.push(message);
|
|
219
|
+
}
|
|
220
|
+
if (/yoyo\s*:\s*true/.test(script)) {
|
|
221
|
+
warnings.push("yoyo animations may behave unexpectedly when scrubbing");
|
|
222
|
+
}
|
|
223
|
+
if (/stagger\s*:/.test(script)) {
|
|
224
|
+
warnings.push("stagger animations may not serialize correctly");
|
|
225
|
+
}
|
|
226
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
227
|
+
}
|
|
228
|
+
function keyframesToGsapAnimations(elementId, keyframes, elementStartTime, base) {
|
|
229
|
+
const sorted = [...keyframes].sort((a, b) => a.time - b.time);
|
|
230
|
+
const animations = [];
|
|
231
|
+
const baseX = base?.x ?? 0;
|
|
232
|
+
const baseY = base?.y ?? 0;
|
|
233
|
+
const baseScale = base?.scale ?? 1;
|
|
234
|
+
sorted.forEach((kf, i) => {
|
|
235
|
+
const absoluteTime = elementStartTime + kf.time;
|
|
236
|
+
const isFirst = i === 0;
|
|
237
|
+
const prevKf = i > 0 ? sorted[i - 1] : null;
|
|
238
|
+
const duration = prevKf ? kf.time - prevKf.time : void 0;
|
|
239
|
+
const position = prevKf ? elementStartTime + prevKf.time : absoluteTime;
|
|
240
|
+
const properties = {};
|
|
241
|
+
for (const [key, value] of Object.entries(kf.properties)) {
|
|
242
|
+
if (typeof value !== "number") continue;
|
|
243
|
+
if (key === "x") properties.x = baseX + value;
|
|
244
|
+
else if (key === "y") properties.y = baseY + value;
|
|
245
|
+
else if (key === "scale") properties.scale = baseScale * value;
|
|
246
|
+
else properties[key] = value;
|
|
247
|
+
}
|
|
248
|
+
animations.push({
|
|
249
|
+
id: `${elementId}-kf-${kf.id}`,
|
|
250
|
+
targetSelector: `#${elementId}`,
|
|
251
|
+
method: isFirst ? "set" : "to",
|
|
252
|
+
position,
|
|
253
|
+
properties,
|
|
254
|
+
duration: isFirst ? void 0 : duration,
|
|
255
|
+
ease: kf.ease
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
return animations;
|
|
259
|
+
}
|
|
260
|
+
function gsapAnimationsToKeyframes(animations, elementStartTime, options) {
|
|
261
|
+
const validMethods = ["set", "to", "from", "fromTo"];
|
|
262
|
+
const baseX = options?.baseX ?? 0;
|
|
263
|
+
const baseY = options?.baseY ?? 0;
|
|
264
|
+
const baseScale = options?.baseScale ?? 1;
|
|
265
|
+
const clampTimeToZero = options?.clampTimeToZero ?? true;
|
|
266
|
+
const skipBaseSet = options?.skipBaseSet ?? false;
|
|
267
|
+
const baseTimeEpsilon = 1e-3;
|
|
268
|
+
const baseValueEpsilon = 1e-5;
|
|
269
|
+
return animations.filter(
|
|
270
|
+
(a) => validMethods.includes(a.method) && typeof a.position === "number"
|
|
271
|
+
).map((a) => {
|
|
272
|
+
const relativeTimeRaw = a.position - elementStartTime;
|
|
273
|
+
const time = clampTimeToZero ? Math.max(0, relativeTimeRaw) : relativeTimeRaw;
|
|
274
|
+
const properties = {};
|
|
275
|
+
for (const [key, value] of Object.entries(a.properties)) {
|
|
276
|
+
if (typeof value !== "number") continue;
|
|
277
|
+
if (key === "x") properties.x = value - baseX;
|
|
278
|
+
else if (key === "y") properties.y = value - baseY;
|
|
279
|
+
else if (key === "scale") {
|
|
280
|
+
properties.scale = baseScale !== 0 ? value / baseScale : value;
|
|
281
|
+
} else {
|
|
282
|
+
properties[key] = value;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (skipBaseSet && a.method === "set" && time < baseTimeEpsilon && Object.values(properties).every(
|
|
286
|
+
(v) => typeof v === "number" && Math.abs(v) < baseValueEpsilon
|
|
287
|
+
)) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
id: a.id.replace(/^.*-kf-/, ""),
|
|
292
|
+
time,
|
|
293
|
+
properties,
|
|
294
|
+
ease: a.ease
|
|
295
|
+
};
|
|
296
|
+
}).filter((kf) => kf !== null);
|
|
297
|
+
}
|
|
298
|
+
var CSS_IDENTITY = {
|
|
299
|
+
opacity: 1,
|
|
300
|
+
autoAlpha: 1,
|
|
301
|
+
scale: 1,
|
|
302
|
+
scaleX: 1,
|
|
303
|
+
scaleY: 1
|
|
304
|
+
};
|
|
305
|
+
function cssIdentityValue(prop) {
|
|
306
|
+
return CSS_IDENTITY[prop] ?? 0;
|
|
307
|
+
}
|
|
308
|
+
function buildIdentityMap(props) {
|
|
309
|
+
const identity = {};
|
|
310
|
+
for (const [key, val] of Object.entries(props)) {
|
|
311
|
+
if (val != null) identity[key] = typeof val === "number" ? cssIdentityValue(key) : val;
|
|
312
|
+
}
|
|
313
|
+
return identity;
|
|
314
|
+
}
|
|
315
|
+
function resolveConversionProps(anim, resolvedFromValues) {
|
|
316
|
+
if (anim.method === "set") {
|
|
317
|
+
return { fromProps: { ...anim.properties }, toProps: { ...anim.properties } };
|
|
318
|
+
}
|
|
319
|
+
if (anim.method === "to") {
|
|
320
|
+
const identity = buildIdentityMap(anim.properties);
|
|
321
|
+
const fromProps = resolvedFromValues ? { ...identity, ...resolvedFromValues } : identity;
|
|
322
|
+
return { fromProps, toProps: { ...anim.properties } };
|
|
323
|
+
}
|
|
324
|
+
if (anim.method === "from") {
|
|
325
|
+
const identity = buildIdentityMap(anim.properties);
|
|
326
|
+
const toProps2 = resolvedFromValues ? { ...identity, ...resolvedFromValues } : identity;
|
|
327
|
+
return { fromProps: { ...anim.properties }, toProps: toProps2 };
|
|
328
|
+
}
|
|
329
|
+
const toProps = resolvedFromValues ? { ...anim.properties, ...resolvedFromValues } : { ...anim.properties };
|
|
330
|
+
return { fromProps: { ...anim.fromProperties ?? {} }, toProps };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/springEase.ts
|
|
334
|
+
var SPRING_PRESETS = [
|
|
335
|
+
{ name: "spring-gentle", label: "Gentle", mass: 1, stiffness: 100, damping: 15 },
|
|
336
|
+
{ name: "spring-bouncy", label: "Bouncy", mass: 1, stiffness: 180, damping: 12 },
|
|
337
|
+
{ name: "spring-stiff", label: "Stiff", mass: 1, stiffness: 300, damping: 20 },
|
|
338
|
+
{ name: "spring-wobbly", label: "Wobbly", mass: 1, stiffness: 120, damping: 8 },
|
|
339
|
+
{ name: "spring-heavy", label: "Heavy", mass: 3, stiffness: 200, damping: 20 }
|
|
340
|
+
];
|
|
341
|
+
function generateSpringEaseData(mass, stiffness, damping, steps = 120) {
|
|
342
|
+
const w0 = Math.sqrt(stiffness / mass);
|
|
343
|
+
const zeta = damping / (2 * Math.sqrt(stiffness * mass));
|
|
344
|
+
let settleDuration;
|
|
345
|
+
if (zeta < 1) {
|
|
346
|
+
settleDuration = Math.min(5 / (zeta * w0), 10);
|
|
347
|
+
} else {
|
|
348
|
+
const decayRate = zeta * w0 - w0 * Math.sqrt(zeta * zeta - 1);
|
|
349
|
+
settleDuration = Math.min(4 / Math.max(decayRate, 0.01), 10);
|
|
350
|
+
}
|
|
351
|
+
const simDuration = Math.max(settleDuration, 1);
|
|
352
|
+
const segments = ["M0,0"];
|
|
353
|
+
for (let i = 1; i <= steps; i++) {
|
|
354
|
+
const t = i / steps;
|
|
355
|
+
const simT = t * simDuration;
|
|
356
|
+
let value;
|
|
357
|
+
if (zeta < 1) {
|
|
358
|
+
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
359
|
+
value = 1 - Math.exp(-zeta * w0 * simT) * (Math.cos(wd * simT) + zeta * w0 / wd * Math.sin(wd * simT));
|
|
360
|
+
} else if (zeta === 1) {
|
|
361
|
+
value = 1 - (1 + w0 * simT) * Math.exp(-w0 * simT);
|
|
362
|
+
} else {
|
|
363
|
+
const s1 = -w0 * (zeta - Math.sqrt(zeta * zeta - 1));
|
|
364
|
+
const s2 = -w0 * (zeta + Math.sqrt(zeta * zeta - 1));
|
|
365
|
+
value = 1 + (s1 * Math.exp(s2 * simT) - s2 * Math.exp(s1 * simT)) / (s2 - s1);
|
|
366
|
+
}
|
|
367
|
+
segments.push(`${t.toFixed(4)},${value.toFixed(4)}`);
|
|
368
|
+
}
|
|
369
|
+
segments[segments.length - 1] = "1,1";
|
|
370
|
+
return `${segments[0]} L${segments.slice(1).join(" ")}`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/gsapParser.ts
|
|
374
|
+
var GSAP_METHODS = /* @__PURE__ */ new Set(["set", "to", "from", "fromTo"]);
|
|
375
|
+
function parseScript(script) {
|
|
376
|
+
return recast.parse(script, {
|
|
377
|
+
parser: {
|
|
378
|
+
parse(source) {
|
|
379
|
+
return babelParse(source, { sourceType: "script", plugins: [], tokens: true });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function collectScopeBindings(ast) {
|
|
385
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
386
|
+
recast.types.visit(ast, {
|
|
387
|
+
visitVariableDeclarator(path) {
|
|
388
|
+
const name = path.node.id?.name;
|
|
389
|
+
const init = path.node.init;
|
|
390
|
+
if (name && init) {
|
|
391
|
+
const val = resolveNode(init, bindings);
|
|
392
|
+
if (val !== void 0) bindings.set(name, val);
|
|
393
|
+
}
|
|
394
|
+
this.traverse(path);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
return bindings;
|
|
398
|
+
}
|
|
399
|
+
function resolveNode(node, scope) {
|
|
400
|
+
if (!node) return void 0;
|
|
401
|
+
if (node.type === "NumericLiteral" || node.type === "Literal" && typeof node.value === "number")
|
|
402
|
+
return node.value;
|
|
403
|
+
if (node.type === "StringLiteral" || node.type === "Literal" && typeof node.value === "string")
|
|
404
|
+
return node.value;
|
|
405
|
+
if (node.type === "BooleanLiteral" || node.type === "Literal" && typeof node.value === "boolean")
|
|
406
|
+
return node.value;
|
|
407
|
+
if (node.type === "UnaryExpression" && node.operator === "-" && node.argument) {
|
|
408
|
+
const val = resolveNode(node.argument, scope);
|
|
409
|
+
return typeof val === "number" ? -val : void 0;
|
|
410
|
+
}
|
|
411
|
+
if (node.type === "BinaryExpression") {
|
|
412
|
+
const left = resolveNode(node.left, scope);
|
|
413
|
+
const right = resolveNode(node.right, scope);
|
|
414
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
415
|
+
switch (node.operator) {
|
|
416
|
+
case "+":
|
|
417
|
+
return left + right;
|
|
418
|
+
case "-":
|
|
419
|
+
return left - right;
|
|
420
|
+
case "*":
|
|
421
|
+
return left * right;
|
|
422
|
+
case "/":
|
|
423
|
+
return right !== 0 ? left / right : void 0;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (typeof left === "string" && node.operator === "+") return left + String(right ?? "");
|
|
427
|
+
if (typeof right === "string" && node.operator === "+") return String(left ?? "") + right;
|
|
428
|
+
}
|
|
429
|
+
if (node.type === "Identifier" && scope.has(node.name)) {
|
|
430
|
+
return scope.get(node.name);
|
|
431
|
+
}
|
|
432
|
+
if (node.type === "TemplateLiteral" && node.expressions?.length === 0) {
|
|
433
|
+
return node.quasis?.[0]?.value?.cooked ?? void 0;
|
|
434
|
+
}
|
|
435
|
+
return void 0;
|
|
436
|
+
}
|
|
437
|
+
function extractLiteralValue(node, scope) {
|
|
438
|
+
return resolveNode(node, scope);
|
|
439
|
+
}
|
|
440
|
+
var QUERY_METHODS = /* @__PURE__ */ new Set(["querySelector", "querySelectorAll"]);
|
|
441
|
+
var ITERATION_METHODS = /* @__PURE__ */ new Set(["forEach", "map"]);
|
|
442
|
+
var SCOPE_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
443
|
+
"Program",
|
|
444
|
+
"FunctionDeclaration",
|
|
445
|
+
"FunctionExpression",
|
|
446
|
+
"ArrowFunctionExpression"
|
|
447
|
+
]);
|
|
448
|
+
function selectorFromQueryCall(node, scope) {
|
|
449
|
+
if (node?.type !== "CallExpression") return null;
|
|
450
|
+
const callee = node.callee;
|
|
451
|
+
if (callee?.type !== "MemberExpression" || callee.property?.type !== "Identifier") return null;
|
|
452
|
+
const method = callee.property.name;
|
|
453
|
+
const argValue = resolveNode(node.arguments?.[0], scope);
|
|
454
|
+
if (typeof argValue !== "string" || argValue.length === 0) return null;
|
|
455
|
+
if (QUERY_METHODS.has(method) || method === "toArray") return argValue;
|
|
456
|
+
if (method === "getElementById") return `#${argValue}`;
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
function enclosingScopeNode(path) {
|
|
460
|
+
let p = path?.parentPath;
|
|
461
|
+
while (p) {
|
|
462
|
+
if (SCOPE_NODE_TYPES.has(p.node?.type)) return p.node;
|
|
463
|
+
p = p.parentPath;
|
|
464
|
+
}
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
function scopeChainOf(path) {
|
|
468
|
+
const chain = [];
|
|
469
|
+
let p = path;
|
|
470
|
+
while (p) {
|
|
471
|
+
if (SCOPE_NODE_TYPES.has(p.node?.type)) chain.push(p.node);
|
|
472
|
+
p = p.parentPath;
|
|
473
|
+
}
|
|
474
|
+
return chain;
|
|
475
|
+
}
|
|
476
|
+
function addBinding(bindings, scopeNode, name, selector) {
|
|
477
|
+
let scoped = bindings.get(scopeNode);
|
|
478
|
+
if (!scoped) {
|
|
479
|
+
scoped = /* @__PURE__ */ new Map();
|
|
480
|
+
bindings.set(scopeNode, scoped);
|
|
481
|
+
}
|
|
482
|
+
if (!scoped.has(name)) scoped.set(name, selector);
|
|
483
|
+
}
|
|
484
|
+
function collectTargetBindings(ast, scope) {
|
|
485
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
486
|
+
recast.types.visit(ast, {
|
|
487
|
+
visitVariableDeclarator(path) {
|
|
488
|
+
const name = path.node.id?.name;
|
|
489
|
+
const selector = selectorFromQueryCall(path.node.init, scope);
|
|
490
|
+
const scopeNode = enclosingScopeNode(path);
|
|
491
|
+
if (name && selector !== null && scopeNode) addBinding(bindings, scopeNode, name, selector);
|
|
492
|
+
this.traverse(path);
|
|
493
|
+
},
|
|
494
|
+
visitAssignmentExpression(path) {
|
|
495
|
+
const left = path.node.left;
|
|
496
|
+
const selector = selectorFromQueryCall(path.node.right, scope);
|
|
497
|
+
const scopeNode = enclosingScopeNode(path);
|
|
498
|
+
if (left?.type === "Identifier" && selector !== null && scopeNode) {
|
|
499
|
+
addBinding(bindings, scopeNode, left.name, selector);
|
|
500
|
+
}
|
|
501
|
+
this.traverse(path);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
recast.types.visit(ast, {
|
|
505
|
+
visitCallExpression(path) {
|
|
506
|
+
const node = path.node;
|
|
507
|
+
const callee = node.callee;
|
|
508
|
+
if (callee?.type === "MemberExpression" && callee.property?.type === "Identifier" && ITERATION_METHODS.has(callee.property.name)) {
|
|
509
|
+
const collectionSelector = resolveCollectionSelector(callee.object, path, scope, bindings);
|
|
510
|
+
const fn = node.arguments?.[0];
|
|
511
|
+
const param = fn?.params?.[0];
|
|
512
|
+
if (collectionSelector && param?.type === "Identifier" && isFunctionNode(fn)) {
|
|
513
|
+
addBinding(bindings, fn, param.name, collectionSelector);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
this.traverse(path);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
return bindings;
|
|
520
|
+
}
|
|
521
|
+
function isFunctionNode(node) {
|
|
522
|
+
return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression" || node?.type === "FunctionDeclaration";
|
|
523
|
+
}
|
|
524
|
+
function resolveCollectionSelector(node, callPath, scope, bindings) {
|
|
525
|
+
if (node?.type === "Identifier") return lookupBinding(node.name, callPath, bindings);
|
|
526
|
+
if (node?.type === "CallExpression") return selectorFromQueryCall(node, scope);
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
function lookupBinding(name, path, bindings) {
|
|
530
|
+
for (const scopeNode of scopeChainOf(path)) {
|
|
531
|
+
const selector = bindings.get(scopeNode)?.get(name);
|
|
532
|
+
if (selector !== void 0) return selector;
|
|
533
|
+
}
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
function resolveTargetSelector(node, path, scope, bindings) {
|
|
537
|
+
if (!node) return null;
|
|
538
|
+
if (node.type === "StringLiteral" || node.type === "Literal") {
|
|
539
|
+
return typeof node.value === "string" ? node.value : null;
|
|
540
|
+
}
|
|
541
|
+
if (node.type === "Identifier") {
|
|
542
|
+
return lookupBinding(node.name, path, bindings);
|
|
543
|
+
}
|
|
544
|
+
if (node.type === "CallExpression") {
|
|
545
|
+
return selectorFromQueryCall(node, scope);
|
|
546
|
+
}
|
|
547
|
+
if (node.type === "ArrayExpression") {
|
|
548
|
+
const parts = node.elements.map((el) => resolveTargetSelector(el, path, scope, bindings)).filter((s) => typeof s === "string" && s.length > 0);
|
|
549
|
+
return parts.length > 0 ? parts.join(", ") : null;
|
|
550
|
+
}
|
|
551
|
+
if (node.type === "MemberExpression" && node.object?.type === "Identifier") {
|
|
552
|
+
return lookupBinding(node.object.name, path, bindings);
|
|
553
|
+
}
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
function objectExpressionToRecord(node, scope) {
|
|
557
|
+
const result = {};
|
|
558
|
+
if (node?.type !== "ObjectExpression") return result;
|
|
559
|
+
for (const prop of node.properties ?? []) {
|
|
560
|
+
if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
|
|
561
|
+
const key = prop.key?.name ?? prop.key?.value;
|
|
562
|
+
if (!key) continue;
|
|
563
|
+
const resolved = resolveNode(prop.value, scope);
|
|
564
|
+
if (resolved !== void 0) {
|
|
565
|
+
result[key] = resolved;
|
|
566
|
+
} else {
|
|
567
|
+
result[key] = `__raw:${recast.print(prop.value).code}`;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
572
|
+
function isGsapTimelineCall(node) {
|
|
573
|
+
return node?.type === "CallExpression" && node.callee?.type === "MemberExpression" && node.callee.object?.name === "gsap" && node.callee.property?.name === "timeline";
|
|
574
|
+
}
|
|
575
|
+
function extractTimelineDefaults(callNode, scope) {
|
|
576
|
+
const arg = callNode.arguments?.[0];
|
|
577
|
+
if (!arg || arg.type !== "ObjectExpression") return void 0;
|
|
578
|
+
const defaultsProp = arg.properties?.find(
|
|
579
|
+
(p) => isObjectProperty(p) && propKeyName(p) === "defaults"
|
|
580
|
+
);
|
|
581
|
+
if (!defaultsProp?.value || defaultsProp.value.type !== "ObjectExpression") return void 0;
|
|
582
|
+
const record = objectExpressionToRecord(defaultsProp.value, scope);
|
|
583
|
+
const result = {};
|
|
584
|
+
if (typeof record.ease === "string") result.ease = record.ease;
|
|
585
|
+
if (typeof record.duration === "number") result.duration = record.duration;
|
|
586
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
587
|
+
}
|
|
588
|
+
function findTimelineVar(ast, scope) {
|
|
589
|
+
let timelineVar = null;
|
|
590
|
+
let timelineCount = 0;
|
|
591
|
+
let defaults;
|
|
592
|
+
const emptyScope = scope ?? /* @__PURE__ */ new Map();
|
|
593
|
+
recast.types.visit(ast, {
|
|
594
|
+
visitVariableDeclarator(path) {
|
|
595
|
+
if (isGsapTimelineCall(path.node.init)) {
|
|
596
|
+
timelineCount += 1;
|
|
597
|
+
if (!timelineVar) {
|
|
598
|
+
timelineVar = path.node.id?.name ?? null;
|
|
599
|
+
defaults = extractTimelineDefaults(path.node.init, emptyScope);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
this.traverse(path);
|
|
603
|
+
},
|
|
604
|
+
visitAssignmentExpression(path) {
|
|
605
|
+
if (isGsapTimelineCall(path.node.right)) {
|
|
606
|
+
timelineCount += 1;
|
|
607
|
+
if (!timelineVar) {
|
|
608
|
+
const left = path.node.left;
|
|
609
|
+
if (left?.type === "Identifier") timelineVar = left.name;
|
|
610
|
+
defaults = extractTimelineDefaults(path.node.right, emptyScope);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
this.traverse(path);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
return { timelineVar, timelineCount, defaults };
|
|
617
|
+
}
|
|
618
|
+
function isTimelineRootedCall(callNode, timelineVar) {
|
|
619
|
+
let obj = callNode.callee?.object;
|
|
620
|
+
while (obj?.type === "CallExpression") {
|
|
621
|
+
obj = obj.callee?.object;
|
|
622
|
+
}
|
|
623
|
+
return obj?.type === "Identifier" && obj.name === timelineVar;
|
|
624
|
+
}
|
|
625
|
+
function findAllTweenCalls(ast, timelineVar, scope, targetBindings) {
|
|
626
|
+
const results = [];
|
|
627
|
+
recast.types.visit(ast, {
|
|
628
|
+
visitCallExpression(path) {
|
|
629
|
+
const node = path.node;
|
|
630
|
+
const callee = node.callee;
|
|
631
|
+
const gsapSetArg = node.arguments?.[0];
|
|
632
|
+
const isGlobalSet = callee?.type === "MemberExpression" && callee.object?.type === "Identifier" && callee.object.name === "gsap" && callee.property?.type === "Identifier" && callee.property.name === "set" && (gsapSetArg?.type === "StringLiteral" || gsapSetArg?.type === "Literal" && typeof gsapSetArg.value === "string");
|
|
633
|
+
if (callee?.type === "MemberExpression" && callee.property?.type === "Identifier" && (isTimelineRootedCall(node, timelineVar) || isGlobalSet)) {
|
|
634
|
+
const method = callee.property.name;
|
|
635
|
+
if (!GSAP_METHODS.has(method)) {
|
|
636
|
+
this.traverse(path);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
const args = node.arguments;
|
|
640
|
+
if (args.length < 2) {
|
|
641
|
+
this.traverse(path);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
const selectorValue = resolveTargetSelector(args[0], path, scope, targetBindings) ?? "__unresolved__";
|
|
645
|
+
if (method === "fromTo") {
|
|
646
|
+
results.push({
|
|
647
|
+
path,
|
|
648
|
+
node,
|
|
649
|
+
method: "fromTo",
|
|
650
|
+
selector: selectorValue,
|
|
651
|
+
fromArg: args[1],
|
|
652
|
+
varsArg: args[2],
|
|
653
|
+
positionArg: args[3]
|
|
654
|
+
});
|
|
655
|
+
} else {
|
|
656
|
+
results.push({
|
|
657
|
+
path,
|
|
658
|
+
node,
|
|
659
|
+
method,
|
|
660
|
+
selector: selectorValue,
|
|
661
|
+
varsArg: args[1],
|
|
662
|
+
positionArg: args[2],
|
|
663
|
+
...isGlobalSet ? { global: true } : {}
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
this.traverse(path);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
return results;
|
|
671
|
+
}
|
|
672
|
+
var BUILTIN_VAR_KEYS = /* @__PURE__ */ new Set(["duration", "ease", "delay"]);
|
|
673
|
+
var DROPPED_VAR_KEYS = /* @__PURE__ */ new Set(["onComplete", "onStart", "onUpdate", "onRepeat"]);
|
|
674
|
+
var EXTRAS_KEYS = /* @__PURE__ */ new Set([
|
|
675
|
+
"stagger",
|
|
676
|
+
"yoyo",
|
|
677
|
+
"repeat",
|
|
678
|
+
"repeatDelay",
|
|
679
|
+
"snap",
|
|
680
|
+
"overwrite",
|
|
681
|
+
"immediateRender"
|
|
682
|
+
]);
|
|
683
|
+
function extractRawPropertySource(varsArgNode, key) {
|
|
684
|
+
const node = findPropertyNode(varsArgNode, key);
|
|
685
|
+
return node ? recast.print(node).code : void 0;
|
|
686
|
+
}
|
|
687
|
+
function findPropertyNode(varsArgNode, key) {
|
|
688
|
+
if (varsArgNode?.type !== "ObjectExpression") return void 0;
|
|
689
|
+
for (const prop of varsArgNode.properties ?? []) {
|
|
690
|
+
if (!isObjectProperty(prop)) continue;
|
|
691
|
+
if (propKeyName(prop) === key) return prop.value;
|
|
692
|
+
}
|
|
693
|
+
return void 0;
|
|
694
|
+
}
|
|
695
|
+
var PERCENTAGE_KEY_RE = /^(\d+(?:\.\d+)?)%$/;
|
|
696
|
+
function tryResolveStringProp(propValue, scope) {
|
|
697
|
+
const val = resolveNode(propValue, scope);
|
|
698
|
+
return typeof val === "string" ? val : void 0;
|
|
699
|
+
}
|
|
700
|
+
function parseKeyframesNode(node, scope) {
|
|
701
|
+
if (!node) return void 0;
|
|
702
|
+
if (node.type === "ArrayExpression") {
|
|
703
|
+
return parseObjectArrayKeyframes(node, scope);
|
|
704
|
+
}
|
|
705
|
+
if (node.type !== "ObjectExpression") return void 0;
|
|
706
|
+
const props = node.properties ?? [];
|
|
707
|
+
let hasPercentageKey = false;
|
|
708
|
+
let hasArrayValue = false;
|
|
709
|
+
for (const prop of props) {
|
|
710
|
+
if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
|
|
711
|
+
const key = prop.key?.value ?? prop.key?.name;
|
|
712
|
+
if (typeof key === "string" && PERCENTAGE_KEY_RE.test(key)) {
|
|
713
|
+
hasPercentageKey = true;
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
if (prop.value?.type === "ArrayExpression") {
|
|
717
|
+
hasArrayValue = true;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (hasPercentageKey) return parsePercentageKeyframes(node, scope);
|
|
721
|
+
if (hasArrayValue) return parseSimpleArrayKeyframes(node, scope);
|
|
722
|
+
return void 0;
|
|
723
|
+
}
|
|
724
|
+
function parsePercentageKeyframes(node, scope) {
|
|
725
|
+
const keyframes = [];
|
|
726
|
+
let ease;
|
|
727
|
+
let easeEach;
|
|
728
|
+
for (const prop of node.properties ?? []) {
|
|
729
|
+
if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
|
|
730
|
+
const key = prop.key?.value ?? prop.key?.name;
|
|
731
|
+
if (typeof key !== "string") continue;
|
|
732
|
+
const pctMatch = PERCENTAGE_KEY_RE.exec(key);
|
|
733
|
+
if (pctMatch) {
|
|
734
|
+
const percentage = Number.parseFloat(pctMatch[1]);
|
|
735
|
+
const record = objectExpressionToRecord(prop.value, scope);
|
|
736
|
+
const properties = {};
|
|
737
|
+
let kfEase;
|
|
738
|
+
for (const [k, v] of Object.entries(record)) {
|
|
739
|
+
if (k === "ease" && typeof v === "string") {
|
|
740
|
+
kfEase = v;
|
|
741
|
+
} else if (typeof v === "number" || typeof v === "string") {
|
|
742
|
+
properties[k] = v;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
keyframes.push({ percentage, properties, ...kfEase ? { ease: kfEase } : {} });
|
|
746
|
+
} else if (key === "ease") {
|
|
747
|
+
ease = tryResolveStringProp(prop.value, scope) ?? ease;
|
|
748
|
+
} else if (key === "easeEach") {
|
|
749
|
+
easeEach = tryResolveStringProp(prop.value, scope) ?? easeEach;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
keyframes.sort((a, b) => a.percentage - b.percentage);
|
|
753
|
+
return {
|
|
754
|
+
format: "percentage",
|
|
755
|
+
keyframes,
|
|
756
|
+
...ease ? { ease } : {},
|
|
757
|
+
...easeEach ? { easeEach } : {}
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
function computeKeyframesTotalDuration(varsNode, scope) {
|
|
761
|
+
const kfNode = (varsNode.properties ?? []).find(
|
|
762
|
+
(p) => (p.key?.name ?? p.key?.value) === "keyframes"
|
|
763
|
+
)?.value;
|
|
764
|
+
if (!kfNode || kfNode.type !== "ArrayExpression") return void 0;
|
|
765
|
+
let total = 0;
|
|
766
|
+
for (const el of kfNode.elements ?? []) {
|
|
767
|
+
if (!el || el.type !== "ObjectExpression") continue;
|
|
768
|
+
const r = objectExpressionToRecord(el, scope);
|
|
769
|
+
if (typeof r.duration === "number") total += r.duration;
|
|
770
|
+
}
|
|
771
|
+
return total > 0 ? total : void 0;
|
|
772
|
+
}
|
|
773
|
+
function parseObjectArrayKeyframes(node, scope) {
|
|
774
|
+
const elements = node.elements ?? [];
|
|
775
|
+
const raw = [];
|
|
776
|
+
for (const el of elements) {
|
|
777
|
+
if (!el || el.type !== "ObjectExpression" && el.type !== "ObjectProperty") {
|
|
778
|
+
if (el?.type !== "ObjectExpression") continue;
|
|
779
|
+
}
|
|
780
|
+
const record = objectExpressionToRecord(el, scope);
|
|
781
|
+
const properties = {};
|
|
782
|
+
let duration;
|
|
783
|
+
let ease;
|
|
784
|
+
for (const [k, v] of Object.entries(record)) {
|
|
785
|
+
if (k === "duration" && typeof v === "number") {
|
|
786
|
+
duration = v;
|
|
787
|
+
} else if (k === "ease" && typeof v === "string") {
|
|
788
|
+
ease = v;
|
|
789
|
+
} else if (typeof v === "number" || typeof v === "string") {
|
|
790
|
+
properties[k] = v;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
raw.push({ properties, duration, ease });
|
|
794
|
+
}
|
|
795
|
+
const totalDuration = raw.reduce((sum, r) => sum + (r.duration ?? 0), 0);
|
|
796
|
+
const keyframes = [];
|
|
797
|
+
if (totalDuration > 0) {
|
|
798
|
+
let cumulative = 0;
|
|
799
|
+
for (const entry of raw) {
|
|
800
|
+
cumulative += entry.duration ?? 0;
|
|
801
|
+
const percentage = Math.round(cumulative / totalDuration * 100);
|
|
802
|
+
keyframes.push({
|
|
803
|
+
percentage,
|
|
804
|
+
properties: entry.properties,
|
|
805
|
+
...entry.ease ? { ease: entry.ease } : {}
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
} else {
|
|
809
|
+
for (let i = 0; i < raw.length; i++) {
|
|
810
|
+
const entry = raw[i];
|
|
811
|
+
const percentage = raw.length > 1 ? Math.round(i / (raw.length - 1) * 100) : 0;
|
|
812
|
+
keyframes.push({
|
|
813
|
+
percentage,
|
|
814
|
+
properties: entry.properties,
|
|
815
|
+
...entry.ease ? { ease: entry.ease } : {}
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return { format: "object-array", keyframes };
|
|
820
|
+
}
|
|
821
|
+
function parseSimpleArrayKeyframes(node, scope) {
|
|
822
|
+
const arrayProps = /* @__PURE__ */ new Map();
|
|
823
|
+
let ease;
|
|
824
|
+
let easeEach;
|
|
825
|
+
for (const prop of node.properties ?? []) {
|
|
826
|
+
if (prop.type !== "ObjectProperty" && prop.type !== "Property") continue;
|
|
827
|
+
const key = prop.key?.name ?? prop.key?.value;
|
|
828
|
+
if (typeof key !== "string") continue;
|
|
829
|
+
if (prop.value?.type === "ArrayExpression") {
|
|
830
|
+
const values = [];
|
|
831
|
+
for (const el of prop.value.elements ?? []) {
|
|
832
|
+
const val = resolveNode(el, scope);
|
|
833
|
+
if (typeof val === "number" || typeof val === "string") {
|
|
834
|
+
values.push(val);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if (values.length > 0) arrayProps.set(key, values);
|
|
838
|
+
} else if (key === "ease") {
|
|
839
|
+
ease = tryResolveStringProp(prop.value, scope) ?? ease;
|
|
840
|
+
} else if (key === "easeEach") {
|
|
841
|
+
easeEach = tryResolveStringProp(prop.value, scope) ?? easeEach;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const maxLen = Math.max(...[...arrayProps.values()].map((a) => a.length), 0);
|
|
845
|
+
const keyframes = [];
|
|
846
|
+
for (let i = 0; i < maxLen; i++) {
|
|
847
|
+
const percentage = maxLen > 1 ? Math.round(i / (maxLen - 1) * 100) : 0;
|
|
848
|
+
const properties = {};
|
|
849
|
+
for (const [key, values] of arrayProps) {
|
|
850
|
+
if (i < values.length) properties[key] = values[i];
|
|
851
|
+
}
|
|
852
|
+
keyframes.push({ percentage, properties });
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
format: "simple-array",
|
|
856
|
+
keyframes,
|
|
857
|
+
...ease ? { ease } : {},
|
|
858
|
+
...easeEach ? { easeEach } : {}
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
function parseMotionPathNode(node, scope) {
|
|
862
|
+
if (!node) return void 0;
|
|
863
|
+
let pathNode;
|
|
864
|
+
let autoRotate = false;
|
|
865
|
+
let curviness = 1;
|
|
866
|
+
let isCubic = false;
|
|
867
|
+
if (node.type === "ObjectExpression") {
|
|
868
|
+
for (const prop of node.properties ?? []) {
|
|
869
|
+
if (!isObjectProperty(prop)) continue;
|
|
870
|
+
const key = propKeyName(prop);
|
|
871
|
+
if (key === "path") pathNode = prop.value;
|
|
872
|
+
else if (key === "autoRotate") {
|
|
873
|
+
const val = resolveNode(prop.value, scope);
|
|
874
|
+
autoRotate = typeof val === "number" ? val : val === true;
|
|
875
|
+
} else if (key === "curviness") {
|
|
876
|
+
const val = resolveNode(prop.value, scope);
|
|
877
|
+
if (typeof val === "number") curviness = val;
|
|
878
|
+
} else if (key === "type") {
|
|
879
|
+
const val = resolveNode(prop.value, scope);
|
|
880
|
+
if (val === "cubic") isCubic = true;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
} else if (node.type === "ArrayExpression") {
|
|
884
|
+
pathNode = node;
|
|
885
|
+
}
|
|
886
|
+
if (!pathNode || pathNode.type !== "ArrayExpression") return void 0;
|
|
887
|
+
const elements = pathNode.elements ?? [];
|
|
888
|
+
const coords = [];
|
|
889
|
+
for (const elem of elements) {
|
|
890
|
+
if (!elem || elem.type !== "ObjectExpression") continue;
|
|
891
|
+
const rec = objectExpressionToRecord(elem, scope);
|
|
892
|
+
const x = typeof rec.x === "number" ? rec.x : void 0;
|
|
893
|
+
const y = typeof rec.y === "number" ? rec.y : void 0;
|
|
894
|
+
if (x !== void 0 && y !== void 0) coords.push({ x, y });
|
|
895
|
+
}
|
|
896
|
+
if (coords.length < 2) return void 0;
|
|
897
|
+
let waypoints;
|
|
898
|
+
const segments = [];
|
|
899
|
+
if (isCubic && coords.length >= 4) {
|
|
900
|
+
waypoints = [];
|
|
901
|
+
waypoints.push(coords[0]);
|
|
902
|
+
for (let i = 1; i + 2 < coords.length; i += 3) {
|
|
903
|
+
const cp1 = coords[i];
|
|
904
|
+
const cp2 = coords[i + 1];
|
|
905
|
+
const anchor = coords[i + 2];
|
|
906
|
+
waypoints.push(anchor);
|
|
907
|
+
segments.push({ curviness, cp1, cp2 });
|
|
908
|
+
}
|
|
909
|
+
} else {
|
|
910
|
+
waypoints = coords;
|
|
911
|
+
for (let i = 0; i < waypoints.length - 1; i++) {
|
|
912
|
+
segments.push({ curviness });
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return {
|
|
916
|
+
arcPath: { enabled: true, autoRotate, segments },
|
|
917
|
+
waypoints
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
function tweenCallToAnimation(call, scope) {
|
|
921
|
+
const vars = objectExpressionToRecord(call.varsArg, scope);
|
|
922
|
+
const properties = {};
|
|
923
|
+
const extras = {};
|
|
924
|
+
let keyframesData;
|
|
925
|
+
let hasUnresolvedKeyframes = false;
|
|
926
|
+
let motionPathResult;
|
|
927
|
+
for (const [key, val] of Object.entries(vars)) {
|
|
928
|
+
if (BUILTIN_VAR_KEYS.has(key)) continue;
|
|
929
|
+
if (DROPPED_VAR_KEYS.has(key)) continue;
|
|
930
|
+
if (key === "keyframes") {
|
|
931
|
+
const kfNode = findPropertyNode(call.varsArg, "keyframes");
|
|
932
|
+
keyframesData = parseKeyframesNode(kfNode, scope);
|
|
933
|
+
if (!keyframesData && kfNode) hasUnresolvedKeyframes = true;
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
if (key === "motionPath") {
|
|
937
|
+
const mpNode = findPropertyNode(call.varsArg, "motionPath");
|
|
938
|
+
motionPathResult = parseMotionPathNode(mpNode, scope);
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
if (key === "easeEach") {
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
if (EXTRAS_KEYS.has(key)) {
|
|
945
|
+
const rawSource = extractRawPropertySource(call.varsArg, key);
|
|
946
|
+
if (rawSource !== void 0) {
|
|
947
|
+
extras[key] = `__raw:${rawSource}`;
|
|
948
|
+
} else if (val !== void 0) {
|
|
949
|
+
extras[key] = val;
|
|
950
|
+
}
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
if (typeof val === "number" || typeof val === "string") {
|
|
954
|
+
properties[key] = val;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
if (keyframesData && typeof vars.easeEach === "string") {
|
|
958
|
+
keyframesData.easeEach = vars.easeEach;
|
|
959
|
+
}
|
|
960
|
+
if (motionPathResult) {
|
|
961
|
+
const { waypoints } = motionPathResult;
|
|
962
|
+
if (!keyframesData) {
|
|
963
|
+
const kf = waypoints.map((wp, i) => ({
|
|
964
|
+
percentage: waypoints.length > 1 ? Math.round(i / (waypoints.length - 1) * 100) : 0,
|
|
965
|
+
properties: { x: wp.x, y: wp.y }
|
|
966
|
+
}));
|
|
967
|
+
keyframesData = { format: "percentage", keyframes: kf };
|
|
968
|
+
} else {
|
|
969
|
+
const kfs = keyframesData.keyframes;
|
|
970
|
+
if (kfs.length === waypoints.length) {
|
|
971
|
+
for (let i = 0; i < kfs.length; i++) {
|
|
972
|
+
kfs[i].properties.x = waypoints[i].x;
|
|
973
|
+
kfs[i].properties.y = waypoints[i].y;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
let fromProperties;
|
|
979
|
+
if (call.method === "fromTo" && call.fromArg) {
|
|
980
|
+
fromProperties = {};
|
|
981
|
+
const fromVars = objectExpressionToRecord(call.fromArg, scope);
|
|
982
|
+
for (const [key, val] of Object.entries(fromVars)) {
|
|
983
|
+
if (typeof val === "number" || typeof val === "string") {
|
|
984
|
+
fromProperties[key] = val;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
const hasPositionArg = !!call.positionArg;
|
|
989
|
+
const posVal = call.positionArg ? extractLiteralValue(call.positionArg, scope) : 0;
|
|
990
|
+
const position = typeof posVal === "number" ? posVal : typeof posVal === "string" ? posVal : 0;
|
|
991
|
+
let duration = typeof vars.duration === "number" ? vars.duration : void 0;
|
|
992
|
+
const ease = typeof vars.ease === "string" ? vars.ease : void 0;
|
|
993
|
+
if (duration === void 0 && keyframesData) {
|
|
994
|
+
duration = computeKeyframesTotalDuration(call.varsArg, scope);
|
|
995
|
+
}
|
|
996
|
+
const anim = {
|
|
997
|
+
targetSelector: call.selector,
|
|
998
|
+
method: call.method,
|
|
999
|
+
position,
|
|
1000
|
+
properties,
|
|
1001
|
+
fromProperties,
|
|
1002
|
+
duration,
|
|
1003
|
+
ease
|
|
1004
|
+
};
|
|
1005
|
+
if (!hasPositionArg) anim.implicitPosition = true;
|
|
1006
|
+
let group = classifyTweenPropertyGroup(properties);
|
|
1007
|
+
if (!group && keyframesData) {
|
|
1008
|
+
const kfProps = {};
|
|
1009
|
+
for (const kf of keyframesData.keyframes) {
|
|
1010
|
+
for (const k of Object.keys(kf.properties)) kfProps[k] = true;
|
|
1011
|
+
}
|
|
1012
|
+
group = classifyTweenPropertyGroup(kfProps);
|
|
1013
|
+
}
|
|
1014
|
+
if (group) anim.propertyGroup = group;
|
|
1015
|
+
if (call.global) anim.global = true;
|
|
1016
|
+
if (Object.keys(extras).length > 0) anim.extras = extras;
|
|
1017
|
+
if (keyframesData) anim.keyframes = keyframesData;
|
|
1018
|
+
if (motionPathResult) anim.arcPath = motionPathResult.arcPath;
|
|
1019
|
+
if (hasUnresolvedKeyframes) anim.hasUnresolvedKeyframes = true;
|
|
1020
|
+
if (call.selector === "__unresolved__") anim.hasUnresolvedSelector = true;
|
|
1021
|
+
return anim;
|
|
1022
|
+
}
|
|
1023
|
+
var GSAP_DEFAULT_DURATION = 0.5;
|
|
1024
|
+
function resolvePositionString(pos, cursor, prevStart) {
|
|
1025
|
+
const trimmed = pos.trim();
|
|
1026
|
+
if (trimmed === "") return cursor;
|
|
1027
|
+
if (trimmed.startsWith("+=")) {
|
|
1028
|
+
const n2 = Number.parseFloat(trimmed.slice(2));
|
|
1029
|
+
return Number.isFinite(n2) ? cursor + n2 : null;
|
|
1030
|
+
}
|
|
1031
|
+
if (trimmed.startsWith("-=")) {
|
|
1032
|
+
const n2 = Number.parseFloat(trimmed.slice(2));
|
|
1033
|
+
return Number.isFinite(n2) ? cursor - n2 : null;
|
|
1034
|
+
}
|
|
1035
|
+
if (trimmed === "<") return prevStart;
|
|
1036
|
+
if (trimmed === ">") return cursor;
|
|
1037
|
+
if (trimmed.startsWith("<")) {
|
|
1038
|
+
const n2 = Number.parseFloat(trimmed.slice(1));
|
|
1039
|
+
return Number.isFinite(n2) ? prevStart + n2 : null;
|
|
1040
|
+
}
|
|
1041
|
+
if (trimmed.startsWith(">")) {
|
|
1042
|
+
const n2 = Number.parseFloat(trimmed.slice(1));
|
|
1043
|
+
return Number.isFinite(n2) ? cursor + n2 : null;
|
|
1044
|
+
}
|
|
1045
|
+
const n = Number.parseFloat(trimmed);
|
|
1046
|
+
return Number.isFinite(n) ? n : null;
|
|
1047
|
+
}
|
|
1048
|
+
function applyTimelineDefaults(anims, defaults) {
|
|
1049
|
+
if (!defaults) return;
|
|
1050
|
+
for (const anim of anims) {
|
|
1051
|
+
if (anim.method === "set") continue;
|
|
1052
|
+
if (anim.duration === void 0 && defaults.duration !== void 0) {
|
|
1053
|
+
anim.duration = defaults.duration;
|
|
1054
|
+
}
|
|
1055
|
+
if (anim.ease === void 0 && defaults.ease !== void 0) {
|
|
1056
|
+
anim.ease = defaults.ease;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function resolveTimelinePositions(anims) {
|
|
1061
|
+
let cursor = 0;
|
|
1062
|
+
let prevStart = 0;
|
|
1063
|
+
for (const anim of anims) {
|
|
1064
|
+
if (anim.method === "set" && anim.global) {
|
|
1065
|
+
anim.resolvedStart = 0;
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
const duration = anim.method === "set" ? 0 : anim.duration ?? GSAP_DEFAULT_DURATION;
|
|
1069
|
+
let start;
|
|
1070
|
+
if (anim.implicitPosition) {
|
|
1071
|
+
start = cursor;
|
|
1072
|
+
} else if (typeof anim.position === "number") {
|
|
1073
|
+
start = anim.position;
|
|
1074
|
+
} else if (typeof anim.position === "string") {
|
|
1075
|
+
start = resolvePositionString(anim.position, cursor, prevStart);
|
|
1076
|
+
} else {
|
|
1077
|
+
start = cursor;
|
|
1078
|
+
}
|
|
1079
|
+
if (start != null) {
|
|
1080
|
+
anim.resolvedStart = Math.max(0, start);
|
|
1081
|
+
prevStart = anim.resolvedStart;
|
|
1082
|
+
cursor = Math.max(cursor, anim.resolvedStart + duration);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
function sortBySourcePosition(calls) {
|
|
1087
|
+
calls.sort((a, b) => {
|
|
1088
|
+
const aLoc = a.node.callee?.property?.loc?.start;
|
|
1089
|
+
const bLoc = b.node.callee?.property?.loc?.start;
|
|
1090
|
+
if (!aLoc || !bLoc) return 0;
|
|
1091
|
+
return aLoc.line - bLoc.line || aLoc.column - bLoc.column;
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
function assignStableIds(anims) {
|
|
1095
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1096
|
+
return anims.map((anim) => {
|
|
1097
|
+
const posKey = typeof anim.position === "number" ? String(Math.round(anim.position * 1e3)) : String(anim.position);
|
|
1098
|
+
const groupSuffix = anim.propertyGroup ? `-${anim.propertyGroup}` : "";
|
|
1099
|
+
const base = `${anim.targetSelector}-${anim.method}-${posKey}${groupSuffix}`;
|
|
1100
|
+
const count = (counts.get(base) ?? 0) + 1;
|
|
1101
|
+
counts.set(base, count);
|
|
1102
|
+
const id = count === 1 ? base : `${base}-${count}`;
|
|
1103
|
+
return { ...anim, id };
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
function parseGsapAst(script) {
|
|
1107
|
+
const ast = parseScript(script);
|
|
1108
|
+
const scope = collectScopeBindings(ast);
|
|
1109
|
+
const targetBindings = collectTargetBindings(ast, scope);
|
|
1110
|
+
const detection = findTimelineVar(ast, scope);
|
|
1111
|
+
const timelineVar = detection.timelineVar ?? "tl";
|
|
1112
|
+
const calls = findAllTweenCalls(ast, timelineVar, scope, targetBindings);
|
|
1113
|
+
sortBySourcePosition(calls);
|
|
1114
|
+
const rawAnims = calls.map((call) => tweenCallToAnimation(call, scope));
|
|
1115
|
+
applyTimelineDefaults(rawAnims, detection.defaults);
|
|
1116
|
+
resolveTimelinePositions(rawAnims);
|
|
1117
|
+
const animations = assignStableIds(rawAnims);
|
|
1118
|
+
const located = animations.map((animation, i) => ({
|
|
1119
|
+
id: animation.id,
|
|
1120
|
+
call: calls[i],
|
|
1121
|
+
animation
|
|
1122
|
+
}));
|
|
1123
|
+
return { ast, scope, timelineVar, detection, located };
|
|
1124
|
+
}
|
|
1125
|
+
function parseGsapScript(script) {
|
|
1126
|
+
try {
|
|
1127
|
+
const { detection, timelineVar, located } = parseGsapAst(script);
|
|
1128
|
+
const animations = located.map((l) => l.animation);
|
|
1129
|
+
const timelineMatch = script.match(
|
|
1130
|
+
new RegExp(
|
|
1131
|
+
`^[\\s\\S]*?(?:const|let|var)\\s+${timelineVar}\\s*=\\s*gsap\\.timeline\\s*\\([^)]*\\)\\s*;?`
|
|
1132
|
+
)
|
|
1133
|
+
);
|
|
1134
|
+
const preamble = timelineMatch?.[0] ?? `const ${timelineVar} = gsap.timeline({ paused: true });`;
|
|
1135
|
+
const lastCallIdx = script.lastIndexOf(`${timelineVar}.`);
|
|
1136
|
+
let postamble = "";
|
|
1137
|
+
if (lastCallIdx !== -1) {
|
|
1138
|
+
const afterLast = script.slice(lastCallIdx);
|
|
1139
|
+
const endOfCall = afterLast.indexOf(";");
|
|
1140
|
+
if (endOfCall !== -1) {
|
|
1141
|
+
postamble = script.slice(lastCallIdx + endOfCall + 1).trim();
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
const result = { animations, timelineVar, preamble, postamble };
|
|
1145
|
+
if (detection.timelineCount > 1) result.multipleTimelines = true;
|
|
1146
|
+
if (detection.timelineCount > 0 && detection.timelineVar === null)
|
|
1147
|
+
result.unsupportedTimelinePattern = true;
|
|
1148
|
+
return result;
|
|
1149
|
+
} catch {
|
|
1150
|
+
return { animations: [], timelineVar: "tl", preamble: "", postamble: "" };
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
function parseExpr(code) {
|
|
1154
|
+
return parseScript(`__hf__ = ${code};`).program.body[0].expression.right;
|
|
1155
|
+
}
|
|
1156
|
+
function propKeyName(prop) {
|
|
1157
|
+
return prop?.key?.name ?? prop?.key?.value;
|
|
1158
|
+
}
|
|
1159
|
+
function isObjectProperty(prop) {
|
|
1160
|
+
return prop?.type === "ObjectProperty" || prop?.type === "Property";
|
|
1161
|
+
}
|
|
1162
|
+
function isEditablePropertyKey(key) {
|
|
1163
|
+
return !BUILTIN_VAR_KEYS.has(key) && !DROPPED_VAR_KEYS.has(key) && !EXTRAS_KEYS.has(key);
|
|
1164
|
+
}
|
|
1165
|
+
function makeObjectProperty(key, value) {
|
|
1166
|
+
const obj = parseExpr(`{ ${safeJsKey(key)}: ${serializeValue(value)} }`);
|
|
1167
|
+
return obj.properties[0];
|
|
1168
|
+
}
|
|
1169
|
+
function setVarsKey(varsArg, key, value) {
|
|
1170
|
+
if (varsArg?.type !== "ObjectExpression") return;
|
|
1171
|
+
const existing = varsArg.properties.find(
|
|
1172
|
+
(p) => isObjectProperty(p) && propKeyName(p) === key
|
|
1173
|
+
);
|
|
1174
|
+
if (existing) {
|
|
1175
|
+
existing.value = parseExpr(serializeValue(value));
|
|
1176
|
+
} else {
|
|
1177
|
+
varsArg.properties.push(makeObjectProperty(key, value));
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
function filterEditableKeys(varsArg, shouldKeep) {
|
|
1181
|
+
if (varsArg?.type !== "ObjectExpression") return;
|
|
1182
|
+
varsArg.properties = varsArg.properties.filter((p) => {
|
|
1183
|
+
if (!isObjectProperty(p)) return true;
|
|
1184
|
+
const key = propKeyName(p);
|
|
1185
|
+
if (typeof key !== "string") return true;
|
|
1186
|
+
if (!isEditablePropertyKey(key)) return true;
|
|
1187
|
+
return shouldKeep(key);
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
function reconcileEditableProperties(varsArg, newProps) {
|
|
1191
|
+
filterEditableKeys(varsArg, (key) => key in newProps);
|
|
1192
|
+
for (const [key, value] of Object.entries(newProps)) {
|
|
1193
|
+
setVarsKey(varsArg, key, value);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
function applyEaseUpdate(varsArg, ease) {
|
|
1197
|
+
const kfNode = findKeyframesObjectNode(varsArg);
|
|
1198
|
+
if (kfNode) {
|
|
1199
|
+
setVarsKey(kfNode, "easeEach", ease);
|
|
1200
|
+
removeVarsKey(varsArg, "ease");
|
|
1201
|
+
} else {
|
|
1202
|
+
setVarsKey(varsArg, "ease", ease);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
function stripKeyframeEases(varsArg) {
|
|
1206
|
+
const kfNode = findKeyframesObjectNode(varsArg);
|
|
1207
|
+
const props = kfNode?.properties;
|
|
1208
|
+
if (!Array.isArray(props)) return;
|
|
1209
|
+
for (const entry of props) {
|
|
1210
|
+
if (isObjectProperty(entry)) removeVarsKey(entry.value, "ease");
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
function applyUpdatesToCall(call, updates) {
|
|
1214
|
+
if (updates.properties) reconcileEditableProperties(call.varsArg, updates.properties);
|
|
1215
|
+
if (updates.fromProperties && call.method === "fromTo" && call.fromArg) {
|
|
1216
|
+
reconcileEditableProperties(call.fromArg, updates.fromProperties);
|
|
1217
|
+
}
|
|
1218
|
+
if (updates.duration !== void 0) setVarsKey(call.varsArg, "duration", updates.duration);
|
|
1219
|
+
if (updates.easeEach !== void 0) applyEaseUpdate(call.varsArg, updates.easeEach);
|
|
1220
|
+
else if (updates.ease !== void 0) applyEaseUpdate(call.varsArg, updates.ease);
|
|
1221
|
+
if (updates.resetKeyframeEases) stripKeyframeEases(call.varsArg);
|
|
1222
|
+
if (updates.position !== void 0) {
|
|
1223
|
+
const posIdx = call.method === "fromTo" ? 3 : 2;
|
|
1224
|
+
call.node.arguments[posIdx] = parseExpr(serializeValue(updates.position));
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
function findStatementPath(path) {
|
|
1228
|
+
let p = path;
|
|
1229
|
+
while (p) {
|
|
1230
|
+
if (p.node?.type === "ExpressionStatement") return p;
|
|
1231
|
+
p = p.parentPath;
|
|
1232
|
+
}
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
function insertAfterAnchor(parsed, newStatement) {
|
|
1236
|
+
const lastCall = parsed.located[parsed.located.length - 1]?.call;
|
|
1237
|
+
const anchorPath = lastCall ? findStatementPath(lastCall.path) : findTimelineDeclarationPath(parsed.ast, parsed.timelineVar);
|
|
1238
|
+
if (anchorPath) {
|
|
1239
|
+
anchorPath.insertAfter(newStatement);
|
|
1240
|
+
} else {
|
|
1241
|
+
parsed.ast.program.body.push(newStatement);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
function buildTweenStatementCode(timelineVar, anim) {
|
|
1245
|
+
const selector = JSON.stringify(anim.targetSelector);
|
|
1246
|
+
const props = { ...anim.properties };
|
|
1247
|
+
if (anim.method !== "set" && anim.duration !== void 0) props.duration = anim.duration;
|
|
1248
|
+
if (anim.ease) props.ease = anim.ease;
|
|
1249
|
+
const entries = Object.entries(props).map(([k, v]) => `${safeJsKey(k)}: ${serializeValue(v)}`);
|
|
1250
|
+
if (anim.method === "set" && !anim.global) entries.push("immediateRender: true");
|
|
1251
|
+
if (anim.extras) {
|
|
1252
|
+
for (const [k, v] of Object.entries(anim.extras)) {
|
|
1253
|
+
entries.push(`${safeJsKey(k)}: ${serializeValue(v)}`);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
const objCode = `{ ${entries.join(", ")} }`;
|
|
1257
|
+
const posCode = serializeValue(
|
|
1258
|
+
typeof anim.position === "number" ? anim.position : anim.position ?? 0
|
|
1259
|
+
);
|
|
1260
|
+
if (anim.method === "fromTo") {
|
|
1261
|
+
const fromEntries = Object.entries(anim.fromProperties ?? {}).map(
|
|
1262
|
+
([k, v]) => `${safeJsKey(k)}: ${serializeValue(v)}`
|
|
1263
|
+
);
|
|
1264
|
+
const fromCode = `{ ${fromEntries.join(", ")} }`;
|
|
1265
|
+
return `${timelineVar}.fromTo(${selector}, ${fromCode}, ${objCode}, ${posCode});`;
|
|
1266
|
+
}
|
|
1267
|
+
if (anim.method === "set" && anim.global) {
|
|
1268
|
+
return `gsap.set(${selector}, ${objCode});`;
|
|
1269
|
+
}
|
|
1270
|
+
return `${timelineVar}.${anim.method}(${selector}, ${objCode}, ${posCode});`;
|
|
1271
|
+
}
|
|
1272
|
+
function updateAnimationInScript(script, animationId, updates) {
|
|
1273
|
+
let parsed;
|
|
1274
|
+
try {
|
|
1275
|
+
parsed = parseGsapAst(script);
|
|
1276
|
+
} catch (e) {
|
|
1277
|
+
console.warn("[gsap-parser] updateAnimationInScript parse failed:", e);
|
|
1278
|
+
return script;
|
|
1279
|
+
}
|
|
1280
|
+
const target = parsed.located.find((l) => l.id === animationId);
|
|
1281
|
+
if (!target) return script;
|
|
1282
|
+
applyUpdatesToCall(target.call, updates);
|
|
1283
|
+
return recast.print(parsed.ast).code;
|
|
1284
|
+
}
|
|
1285
|
+
function shiftPositionsInScript(script, targetSelector, delta) {
|
|
1286
|
+
let parsed;
|
|
1287
|
+
try {
|
|
1288
|
+
parsed = parseGsapAst(script);
|
|
1289
|
+
} catch (e) {
|
|
1290
|
+
console.warn("[gsap-parser] shiftPositionsInScript parse failed:", e);
|
|
1291
|
+
return script;
|
|
1292
|
+
}
|
|
1293
|
+
let changed = false;
|
|
1294
|
+
for (const entry of parsed.located) {
|
|
1295
|
+
if (entry.animation.targetSelector !== targetSelector) continue;
|
|
1296
|
+
if (typeof entry.animation.position !== "number") continue;
|
|
1297
|
+
const newPos = Math.max(0, Math.round((entry.animation.position + delta) * 1e3) / 1e3);
|
|
1298
|
+
applyUpdatesToCall(entry.call, { position: newPos });
|
|
1299
|
+
changed = true;
|
|
1300
|
+
}
|
|
1301
|
+
return changed ? recast.print(parsed.ast).code : script;
|
|
1302
|
+
}
|
|
1303
|
+
function scalePositionsInScript(script, targetSelector, oldStart, oldDuration, newStart, newDuration) {
|
|
1304
|
+
if (oldDuration <= 0 || newDuration <= 0) return script;
|
|
1305
|
+
const ratio = newDuration / oldDuration;
|
|
1306
|
+
let parsed;
|
|
1307
|
+
try {
|
|
1308
|
+
parsed = parseGsapAst(script);
|
|
1309
|
+
} catch (e) {
|
|
1310
|
+
console.warn("[gsap-parser] scalePositionsInScript parse failed:", e);
|
|
1311
|
+
return script;
|
|
1312
|
+
}
|
|
1313
|
+
let changed = false;
|
|
1314
|
+
for (const entry of parsed.located) {
|
|
1315
|
+
if (entry.animation.targetSelector !== targetSelector) continue;
|
|
1316
|
+
if (typeof entry.animation.position !== "number") continue;
|
|
1317
|
+
const newPos = Math.max(
|
|
1318
|
+
0,
|
|
1319
|
+
Math.round((newStart + (entry.animation.position - oldStart) * ratio) * 1e3) / 1e3
|
|
1320
|
+
);
|
|
1321
|
+
const updates = { position: newPos };
|
|
1322
|
+
if (typeof entry.animation.duration === "number" && entry.animation.duration > 0) {
|
|
1323
|
+
updates.duration = Math.max(
|
|
1324
|
+
1e-3,
|
|
1325
|
+
Math.round(entry.animation.duration * ratio * 1e3) / 1e3
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
applyUpdatesToCall(entry.call, updates);
|
|
1329
|
+
changed = true;
|
|
1330
|
+
}
|
|
1331
|
+
return changed ? recast.print(parsed.ast).code : script;
|
|
1332
|
+
}
|
|
1333
|
+
function updateAnimationSelector(script, animationId, newSelector) {
|
|
1334
|
+
let parsed;
|
|
1335
|
+
try {
|
|
1336
|
+
parsed = parseGsapAst(script);
|
|
1337
|
+
} catch {
|
|
1338
|
+
return script;
|
|
1339
|
+
}
|
|
1340
|
+
const target = parsed.located.find((l) => l.id === animationId);
|
|
1341
|
+
if (!target) return script;
|
|
1342
|
+
const selectorArg = target.call.path.node.arguments?.[0];
|
|
1343
|
+
if (selectorArg?.type === "StringLiteral") {
|
|
1344
|
+
selectorArg.value = newSelector;
|
|
1345
|
+
} else if (selectorArg?.type === "Identifier") {
|
|
1346
|
+
target.call.path.node.arguments[0] = { type: "StringLiteral", value: newSelector };
|
|
1347
|
+
}
|
|
1348
|
+
return recast.print(parsed.ast).code;
|
|
1349
|
+
}
|
|
1350
|
+
function addAnimationToScript(script, animation) {
|
|
1351
|
+
let parsed;
|
|
1352
|
+
try {
|
|
1353
|
+
parsed = parseGsapAst(script);
|
|
1354
|
+
} catch (e) {
|
|
1355
|
+
console.warn("[gsap-parser] addAnimationToScript parse failed:", e);
|
|
1356
|
+
return { script, id: "" };
|
|
1357
|
+
}
|
|
1358
|
+
if (parsed.located.length === 0 && parsed.detection.timelineVar === null) {
|
|
1359
|
+
return { script, id: "" };
|
|
1360
|
+
}
|
|
1361
|
+
const id = `anim-${Date.now()}`;
|
|
1362
|
+
const statementCode = buildTweenStatementCode(parsed.timelineVar, animation);
|
|
1363
|
+
const newStatement = parseScript(statementCode).program.body[0];
|
|
1364
|
+
insertAfterAnchor(parsed, newStatement);
|
|
1365
|
+
return { script: recast.print(parsed.ast).code, id };
|
|
1366
|
+
}
|
|
1367
|
+
function addAnimationWithKeyframesToScript(script, targetSelector, position, duration, keyframes, ease, easeEach) {
|
|
1368
|
+
let parsed;
|
|
1369
|
+
try {
|
|
1370
|
+
parsed = parseGsapAst(script);
|
|
1371
|
+
} catch (e) {
|
|
1372
|
+
console.warn("[gsap-parser] addAnimationWithKeyframesToScript parse failed:", e);
|
|
1373
|
+
return { script, id: "" };
|
|
1374
|
+
}
|
|
1375
|
+
if (parsed.located.length === 0 && parsed.detection.timelineVar === null) {
|
|
1376
|
+
return { script, id: "" };
|
|
1377
|
+
}
|
|
1378
|
+
const selector = JSON.stringify(targetSelector);
|
|
1379
|
+
const kfCode = buildKeyframeObjectCode(keyframes, easeEach ? { easeEach } : void 0);
|
|
1380
|
+
const varEntries = [`keyframes: ${kfCode}`, `duration: ${serializeValue(duration)}`];
|
|
1381
|
+
if (ease) varEntries.push(`ease: ${JSON.stringify(ease)}`);
|
|
1382
|
+
const posCode = serializeValue(position);
|
|
1383
|
+
const stmtCode = `${parsed.timelineVar}.to(${selector}, { ${varEntries.join(", ")} }, ${posCode});`;
|
|
1384
|
+
const newStatement = parseScript(stmtCode).program.body[0];
|
|
1385
|
+
insertAfterAnchor(parsed, newStatement);
|
|
1386
|
+
const result = recast.print(parsed.ast).code;
|
|
1387
|
+
const reParsed = parseGsapAst(result);
|
|
1388
|
+
const newId = reParsed.located[reParsed.located.length - 1]?.id ?? "";
|
|
1389
|
+
return { script: result, id: newId };
|
|
1390
|
+
}
|
|
1391
|
+
function findTimelineDeclarationPath(ast, timelineVar) {
|
|
1392
|
+
let found = null;
|
|
1393
|
+
recast.types.visit(ast, {
|
|
1394
|
+
visitVariableDeclaration(path) {
|
|
1395
|
+
if (found) return false;
|
|
1396
|
+
for (const decl of path.node.declarations ?? []) {
|
|
1397
|
+
if (decl.id?.name === timelineVar && isGsapTimelineCall(decl.init)) {
|
|
1398
|
+
found = path;
|
|
1399
|
+
return false;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
this.traverse(path);
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
return found;
|
|
1406
|
+
}
|
|
1407
|
+
function findChainParentCall(stmtNode, targetNode) {
|
|
1408
|
+
let found = null;
|
|
1409
|
+
recast.types.visit(stmtNode, {
|
|
1410
|
+
visitCallExpression(p) {
|
|
1411
|
+
if (found) return false;
|
|
1412
|
+
if (p.node.callee?.type === "MemberExpression" && p.node.callee.object === targetNode) {
|
|
1413
|
+
found = p.node;
|
|
1414
|
+
return false;
|
|
1415
|
+
}
|
|
1416
|
+
this.traverse(p);
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
return found;
|
|
1420
|
+
}
|
|
1421
|
+
function removeAnimationFromScript(script, animationId) {
|
|
1422
|
+
let parsed;
|
|
1423
|
+
try {
|
|
1424
|
+
parsed = parseGsapAst(script);
|
|
1425
|
+
} catch (e) {
|
|
1426
|
+
console.warn("[gsap-parser] removeAnimationFromScript parse failed:", e);
|
|
1427
|
+
return script;
|
|
1428
|
+
}
|
|
1429
|
+
let target = parsed.located.find((l) => l.id === animationId);
|
|
1430
|
+
if (!target) {
|
|
1431
|
+
const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
|
|
1432
|
+
target = parsed.located.find((l) => l.id === convertedId);
|
|
1433
|
+
}
|
|
1434
|
+
if (!target) return script;
|
|
1435
|
+
const node = target.call.node;
|
|
1436
|
+
const stmtPath = findStatementPath(target.call.path);
|
|
1437
|
+
if (!stmtPath) return script;
|
|
1438
|
+
const parentCall = findChainParentCall(stmtPath.node, node);
|
|
1439
|
+
if (parentCall) {
|
|
1440
|
+
parentCall.callee.object = node.callee.object;
|
|
1441
|
+
} else if (node.callee?.object?.type === "CallExpression") {
|
|
1442
|
+
stmtPath.node.expression = node.callee.object;
|
|
1443
|
+
} else {
|
|
1444
|
+
stmtPath.prune();
|
|
1445
|
+
}
|
|
1446
|
+
return recast.print(parsed.ast).code;
|
|
1447
|
+
}
|
|
1448
|
+
function insertInheritedStateSet(script, selector, position, properties) {
|
|
1449
|
+
let parsed;
|
|
1450
|
+
try {
|
|
1451
|
+
parsed = parseGsapAst(script);
|
|
1452
|
+
} catch {
|
|
1453
|
+
return script;
|
|
1454
|
+
}
|
|
1455
|
+
const tlVar = parsed.timelineVar;
|
|
1456
|
+
const props = Object.entries(properties).map(([k, v]) => `${k}: ${typeof v === "string" ? JSON.stringify(v) : v}`).join(", ");
|
|
1457
|
+
const code = `${tlVar}.set(${JSON.stringify(selector)}, { ${props} }, ${position});`;
|
|
1458
|
+
const newStatement = parseScript(code).program.body[0];
|
|
1459
|
+
const anchor = findTimelineDeclarationPath(parsed.ast, tlVar);
|
|
1460
|
+
if (anchor) {
|
|
1461
|
+
anchor.insertAfter(newStatement);
|
|
1462
|
+
} else if (parsed.located.length > 0) {
|
|
1463
|
+
const firstTween = parsed.located[0].call;
|
|
1464
|
+
const stmtPath = findStatementPath(firstTween.path);
|
|
1465
|
+
if (stmtPath) stmtPath.insertBefore(newStatement);
|
|
1466
|
+
else parsed.ast.program.body.unshift(newStatement);
|
|
1467
|
+
} else {
|
|
1468
|
+
parsed.ast.program.body.push(newStatement);
|
|
1469
|
+
}
|
|
1470
|
+
return recast.print(parsed.ast).code;
|
|
1471
|
+
}
|
|
1472
|
+
var STUDIO_HOLD_MARKER = "hf-hold";
|
|
1473
|
+
function isStudioHoldSet(anim) {
|
|
1474
|
+
return anim.method === "set" && anim.properties?.data === STUDIO_HOLD_MARKER;
|
|
1475
|
+
}
|
|
1476
|
+
function syncPositionHoldsBeforeKeyframes(script) {
|
|
1477
|
+
let parsed;
|
|
1478
|
+
try {
|
|
1479
|
+
parsed = parseGsapScript(script);
|
|
1480
|
+
} catch {
|
|
1481
|
+
return script;
|
|
1482
|
+
}
|
|
1483
|
+
let result = script;
|
|
1484
|
+
const staleHoldIds = parsed.animations.filter(isStudioHoldSet).map((a) => a.id);
|
|
1485
|
+
for (const id of staleHoldIds) result = removeAnimationFromScript(result, id);
|
|
1486
|
+
let reparsed;
|
|
1487
|
+
try {
|
|
1488
|
+
reparsed = parseGsapScript(result);
|
|
1489
|
+
} catch {
|
|
1490
|
+
return result;
|
|
1491
|
+
}
|
|
1492
|
+
for (const anim of reparsed.animations) {
|
|
1493
|
+
if (!anim.keyframes) continue;
|
|
1494
|
+
const start = anim.resolvedStart ?? (typeof anim.position === "number" ? anim.position : 0);
|
|
1495
|
+
if (!(start > 1e-3)) continue;
|
|
1496
|
+
const firstKf = [...anim.keyframes.keyframes].sort((a, b) => a.percentage - b.percentage)[0];
|
|
1497
|
+
if (!firstKf) continue;
|
|
1498
|
+
const posProps = {};
|
|
1499
|
+
for (const [k, v] of Object.entries(firstKf.properties)) {
|
|
1500
|
+
if (classifyPropertyGroup(k) === "position" && typeof v === "number") posProps[k] = v;
|
|
1501
|
+
}
|
|
1502
|
+
if (Object.keys(posProps).length === 0) continue;
|
|
1503
|
+
result = insertInheritedStateSet(result, anim.targetSelector, 0, {
|
|
1504
|
+
...posProps,
|
|
1505
|
+
data: STUDIO_HOLD_MARKER
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
return result;
|
|
1509
|
+
}
|
|
1510
|
+
function splitAnimationsInScript(script, opts) {
|
|
1511
|
+
const parsed = parseGsapScript(script);
|
|
1512
|
+
const originalSelector = `#${opts.originalId}`;
|
|
1513
|
+
const newSelector = `#${opts.newId}`;
|
|
1514
|
+
const skippedSelectors = [];
|
|
1515
|
+
for (const a of parsed.animations) {
|
|
1516
|
+
if (a.targetSelector !== originalSelector && a.targetSelector.includes(opts.originalId)) {
|
|
1517
|
+
skippedSelectors.push(a.targetSelector);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
const matching = parsed.animations.filter((a) => a.targetSelector === originalSelector);
|
|
1521
|
+
if (matching.length === 0) return { script, skippedSelectors };
|
|
1522
|
+
let result = script;
|
|
1523
|
+
const newElementStart = opts.splitTime;
|
|
1524
|
+
const inheritedProps = {};
|
|
1525
|
+
for (let i = matching.length - 1; i >= 0; i--) {
|
|
1526
|
+
const anim = matching[i];
|
|
1527
|
+
const pos = typeof anim.position === "number" ? anim.position : 0;
|
|
1528
|
+
const dur = anim.duration ?? 0;
|
|
1529
|
+
const animEnd = pos + dur;
|
|
1530
|
+
if (anim.keyframes) {
|
|
1531
|
+
if (pos >= opts.splitTime) {
|
|
1532
|
+
result = updateAnimationSelector(result, anim.id, newSelector);
|
|
1533
|
+
} else if (animEnd > opts.splitTime) {
|
|
1534
|
+
skippedSelectors.push(`${originalSelector} (keyframes spanning split)`);
|
|
1535
|
+
const kfs = anim.keyframes.keyframes;
|
|
1536
|
+
for (const kf of kfs) {
|
|
1537
|
+
const kfTime = pos + kf.percentage / 100 * dur;
|
|
1538
|
+
if (kfTime <= opts.splitTime) {
|
|
1539
|
+
for (const [k, v] of Object.entries(kf.properties)) {
|
|
1540
|
+
inheritedProps[k] = v;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
} else {
|
|
1545
|
+
const kfs = anim.keyframes.keyframes;
|
|
1546
|
+
if (kfs.length > 0) {
|
|
1547
|
+
for (const [k, v] of Object.entries(kfs[kfs.length - 1].properties)) {
|
|
1548
|
+
inheritedProps[k] = v;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
if (animEnd <= opts.splitTime) {
|
|
1555
|
+
if (anim.method === "from") {
|
|
1556
|
+
for (const k of Object.keys(anim.properties)) delete inheritedProps[k];
|
|
1557
|
+
} else {
|
|
1558
|
+
for (const [k, v] of Object.entries(anim.properties)) {
|
|
1559
|
+
inheritedProps[k] = v;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
continue;
|
|
1563
|
+
}
|
|
1564
|
+
if (pos >= opts.splitTime) {
|
|
1565
|
+
result = updateAnimationSelector(result, anim.id, newSelector);
|
|
1566
|
+
continue;
|
|
1567
|
+
}
|
|
1568
|
+
const progress = dur > 0 ? (opts.splitTime - pos) / dur : 0;
|
|
1569
|
+
const fromSource = anim.fromProperties ?? inheritedProps;
|
|
1570
|
+
const midProps = {};
|
|
1571
|
+
for (const [k, v] of Object.entries(anim.properties)) {
|
|
1572
|
+
if (typeof v !== "number") {
|
|
1573
|
+
midProps[k] = v;
|
|
1574
|
+
continue;
|
|
1575
|
+
}
|
|
1576
|
+
const fromVal = typeof fromSource[k] === "number" ? fromSource[k] : 0;
|
|
1577
|
+
midProps[k] = fromVal + (v - fromVal) * progress;
|
|
1578
|
+
}
|
|
1579
|
+
const firstHalfDuration = opts.splitTime - pos;
|
|
1580
|
+
result = updateAnimationInScript(result, anim.id, {
|
|
1581
|
+
duration: firstHalfDuration,
|
|
1582
|
+
properties: midProps
|
|
1583
|
+
});
|
|
1584
|
+
const secondHalfDuration = animEnd - opts.splitTime;
|
|
1585
|
+
const addResult = addAnimationToScript(result, {
|
|
1586
|
+
targetSelector: newSelector,
|
|
1587
|
+
method: "fromTo",
|
|
1588
|
+
position: newElementStart,
|
|
1589
|
+
duration: secondHalfDuration,
|
|
1590
|
+
properties: { ...anim.properties },
|
|
1591
|
+
fromProperties: { ...midProps },
|
|
1592
|
+
ease: anim.ease,
|
|
1593
|
+
extras: anim.extras
|
|
1594
|
+
});
|
|
1595
|
+
result = addResult.script;
|
|
1596
|
+
for (const [k, v] of Object.entries(midProps)) {
|
|
1597
|
+
inheritedProps[k] = v;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
if (Object.keys(inheritedProps).length > 0) {
|
|
1601
|
+
result = insertInheritedStateSet(result, newSelector, newElementStart, inheritedProps);
|
|
1602
|
+
}
|
|
1603
|
+
return { script: result, skippedSelectors };
|
|
1604
|
+
}
|
|
1605
|
+
function sortedKeyframes(kfs) {
|
|
1606
|
+
return kfs.slice().sort((a, b) => a.percentage - b.percentage);
|
|
1607
|
+
}
|
|
1608
|
+
function keyframePropsToCode(kf) {
|
|
1609
|
+
return Object.entries(kf.properties).map(([k, v]) => `${safeJsKey(k)}: ${serializeValue(v)}`);
|
|
1610
|
+
}
|
|
1611
|
+
function buildKeyframeObjectCode(keyframes, options) {
|
|
1612
|
+
const entries = keyframes.map((kf) => {
|
|
1613
|
+
const props = keyframePropsToCode(kf);
|
|
1614
|
+
if (kf.ease) props.push(`ease: ${JSON.stringify(kf.ease)}`);
|
|
1615
|
+
if (kf.auto) props.push(`_auto: 1`);
|
|
1616
|
+
return `${JSON.stringify(`${kf.percentage}%`)}: { ${props.join(", ")} }`;
|
|
1617
|
+
});
|
|
1618
|
+
if (options?.easeEach) entries.push(`easeEach: ${JSON.stringify(options.easeEach)}`);
|
|
1619
|
+
return `{ ${entries.join(", ")} }`;
|
|
1620
|
+
}
|
|
1621
|
+
function removeVarsKey(varsArg, key) {
|
|
1622
|
+
if (varsArg?.type !== "ObjectExpression") return;
|
|
1623
|
+
varsArg.properties = varsArg.properties.filter(
|
|
1624
|
+
(p) => !(isObjectProperty(p) && propKeyName(p) === key)
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
function percentageFromKey(key) {
|
|
1628
|
+
const m = PERCENTAGE_KEY_RE.exec(key);
|
|
1629
|
+
return m ? Number.parseFloat(m[1]) : Number.NaN;
|
|
1630
|
+
}
|
|
1631
|
+
var PCT_TOLERANCE = 2;
|
|
1632
|
+
function findKeyframePropByPct(kfNode, percentage) {
|
|
1633
|
+
const props = kfNode.properties;
|
|
1634
|
+
for (let i = 0; i < props.length; i++) {
|
|
1635
|
+
if (!isObjectProperty(props[i])) continue;
|
|
1636
|
+
const key = propKeyName(props[i]);
|
|
1637
|
+
if (typeof key !== "string") continue;
|
|
1638
|
+
const parsed = percentageFromKey(key);
|
|
1639
|
+
if (Number.isNaN(parsed)) continue;
|
|
1640
|
+
if (Math.abs(parsed - percentage) <= PCT_TOLERANCE) return { idx: i, prop: props[i] };
|
|
1641
|
+
}
|
|
1642
|
+
return null;
|
|
1643
|
+
}
|
|
1644
|
+
function buildKeyframeValueNode(properties, ease) {
|
|
1645
|
+
const entries = Object.entries(properties).map(([k, v]) => `${safeJsKey(k)}: ${serializeValue(v)}`);
|
|
1646
|
+
if (ease) entries.push(`ease: ${JSON.stringify(ease)}`);
|
|
1647
|
+
return parseExpr(`{ ${entries.join(", ")} }`);
|
|
1648
|
+
}
|
|
1649
|
+
function locateAnimation(script, animationId) {
|
|
1650
|
+
let parsed;
|
|
1651
|
+
try {
|
|
1652
|
+
parsed = parseGsapAst(script);
|
|
1653
|
+
} catch {
|
|
1654
|
+
return null;
|
|
1655
|
+
}
|
|
1656
|
+
const target = parsed.located.find((l) => l.id === animationId);
|
|
1657
|
+
return target ? { parsed, target } : null;
|
|
1658
|
+
}
|
|
1659
|
+
var ANIM_ID_RE = /^(.*)-(fromTo|from|to|set)-(\d+)-([a-z]+)$/;
|
|
1660
|
+
function locateAnimationWithFallback(script, animationId) {
|
|
1661
|
+
const loc = locateAnimation(script, animationId);
|
|
1662
|
+
if (loc) return loc;
|
|
1663
|
+
const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
|
|
1664
|
+
if (convertedId !== animationId) {
|
|
1665
|
+
const converted = locateAnimation(script, convertedId);
|
|
1666
|
+
if (converted) return converted;
|
|
1667
|
+
}
|
|
1668
|
+
const want = ANIM_ID_RE.exec(animationId);
|
|
1669
|
+
if (!want) return null;
|
|
1670
|
+
const [, sel, method, wantPosStr, group] = want;
|
|
1671
|
+
const wantPos = Number(wantPosStr);
|
|
1672
|
+
let parsed;
|
|
1673
|
+
try {
|
|
1674
|
+
parsed = parseGsapAst(script);
|
|
1675
|
+
} catch {
|
|
1676
|
+
return null;
|
|
1677
|
+
}
|
|
1678
|
+
let best = null;
|
|
1679
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
1680
|
+
for (const l of parsed.located) {
|
|
1681
|
+
const m = ANIM_ID_RE.exec(l.id);
|
|
1682
|
+
if (!m || m[1] !== sel || m[2] !== method || m[4] !== group) continue;
|
|
1683
|
+
const dist = Math.abs(Number(m[3]) - wantPos);
|
|
1684
|
+
if (dist < bestDist) {
|
|
1685
|
+
best = l;
|
|
1686
|
+
bestDist = dist;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
return best ? { parsed, target: best } : null;
|
|
1690
|
+
}
|
|
1691
|
+
function findKeyframesObjectNode(varsArg) {
|
|
1692
|
+
const node = findPropertyNode(varsArg, "keyframes");
|
|
1693
|
+
return node?.type === "ObjectExpression" ? node : null;
|
|
1694
|
+
}
|
|
1695
|
+
function convertArrayKeyframesToObjectNode(varsArg) {
|
|
1696
|
+
if (varsArg?.type !== "ObjectExpression") return null;
|
|
1697
|
+
const prop = (varsArg.properties ?? []).find(
|
|
1698
|
+
(p) => isObjectProperty(p) && propKeyName(p) === "keyframes"
|
|
1699
|
+
);
|
|
1700
|
+
if (!prop || prop.value?.type !== "ArrayExpression") return null;
|
|
1701
|
+
const els = (prop.value.elements ?? []).filter(
|
|
1702
|
+
(e) => !!e && e.type === "ObjectExpression"
|
|
1703
|
+
);
|
|
1704
|
+
const n = els.length;
|
|
1705
|
+
if (n === 0) return null;
|
|
1706
|
+
const entries = els.map((el, i) => {
|
|
1707
|
+
const pct = n > 1 ? Math.round(i / (n - 1) * 1e3) / 10 : 0;
|
|
1708
|
+
return `${JSON.stringify(`${pct}%`)}: ${recast.print(el).code}`;
|
|
1709
|
+
});
|
|
1710
|
+
prop.value = parseExpr(`{ ${entries.join(", ")} }`);
|
|
1711
|
+
return prop.value;
|
|
1712
|
+
}
|
|
1713
|
+
function filterPercentageProps(kfNode) {
|
|
1714
|
+
return kfNode.properties.filter((p) => {
|
|
1715
|
+
if (!isObjectProperty(p)) return false;
|
|
1716
|
+
const key = propKeyName(p);
|
|
1717
|
+
return typeof key === "string" && PERCENTAGE_KEY_RE.test(key);
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
function collapseKeyframesToFlat(varsArg, record) {
|
|
1721
|
+
for (const [k, v] of Object.entries(record)) {
|
|
1722
|
+
if (k === "ease") continue;
|
|
1723
|
+
if (typeof v === "number" || typeof v === "string") setVarsKey(varsArg, k, v);
|
|
1724
|
+
}
|
|
1725
|
+
removeVarsKey(varsArg, "keyframes");
|
|
1726
|
+
removeVarsKey(varsArg, "easeEach");
|
|
1727
|
+
}
|
|
1728
|
+
function locateKeyframeCtx(script, animationId, percentage) {
|
|
1729
|
+
const loc = locateAnimationWithFallback(script, animationId);
|
|
1730
|
+
if (!loc) return null;
|
|
1731
|
+
const kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
|
|
1732
|
+
if (!kfNode) return null;
|
|
1733
|
+
return { loc, kfNode, pctKey: `${percentage}%` };
|
|
1734
|
+
}
|
|
1735
|
+
function addKeyframeToScript(script, animationId, percentage, properties, ease, backfillDefaults) {
|
|
1736
|
+
let loc = locateAnimationWithFallback(script, animationId);
|
|
1737
|
+
if (!loc) return script;
|
|
1738
|
+
let kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
|
|
1739
|
+
if (!kfNode) kfNode = convertArrayKeyframesToObjectNode(loc.target.call.varsArg);
|
|
1740
|
+
if (!kfNode) {
|
|
1741
|
+
script = convertToKeyframesInScript(script, animationId);
|
|
1742
|
+
loc = locateAnimationWithFallback(script, animationId);
|
|
1743
|
+
if (!loc) return script;
|
|
1744
|
+
kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
|
|
1745
|
+
if (!kfNode) return script;
|
|
1746
|
+
}
|
|
1747
|
+
const pctKey = `${percentage}%`;
|
|
1748
|
+
const newValueNode = buildKeyframeValueNode(properties, ease);
|
|
1749
|
+
const existing = findKeyframePropByPct(kfNode, percentage);
|
|
1750
|
+
if (existing) {
|
|
1751
|
+
if (existing.prop.value?.type === "ObjectExpression") {
|
|
1752
|
+
const existingRecord = objectExpressionToRecord(existing.prop.value, loc.parsed.scope);
|
|
1753
|
+
const merged = { ...existingRecord };
|
|
1754
|
+
for (const [k, v] of Object.entries(properties)) merged[k] = v;
|
|
1755
|
+
existing.prop.value = buildKeyframeValueNode(
|
|
1756
|
+
merged,
|
|
1757
|
+
ease ?? (typeof existingRecord.ease === "string" ? existingRecord.ease : void 0)
|
|
1758
|
+
);
|
|
1759
|
+
} else {
|
|
1760
|
+
existing.prop.value = newValueNode;
|
|
1761
|
+
}
|
|
1762
|
+
} else {
|
|
1763
|
+
const newProp = parseExpr(`{ ${JSON.stringify(pctKey)}: {} }`).properties[0];
|
|
1764
|
+
newProp.value = newValueNode;
|
|
1765
|
+
let insertIdx = kfNode.properties.length;
|
|
1766
|
+
for (let i = 0; i < kfNode.properties.length; i++) {
|
|
1767
|
+
const key = isObjectProperty(kfNode.properties[i]) ? propKeyName(kfNode.properties[i]) : void 0;
|
|
1768
|
+
if (typeof key === "string" && percentageFromKey(key) > percentage) {
|
|
1769
|
+
insertIdx = i;
|
|
1770
|
+
break;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
kfNode.properties.splice(insertIdx, 0, newProp);
|
|
1774
|
+
}
|
|
1775
|
+
if (percentage > 0 && percentage < 100) {
|
|
1776
|
+
const pctProps = filterPercentageProps(kfNode);
|
|
1777
|
+
const allPcts = pctProps.map((p) => percentageFromKey(propKeyName(p) ?? "")).filter((n) => !Number.isNaN(n) && n !== percentage).sort((a, b) => a - b);
|
|
1778
|
+
const leftNeighbor = allPcts.filter((p) => p < percentage).pop();
|
|
1779
|
+
const rightNeighbor = allPcts.find((p) => p > percentage);
|
|
1780
|
+
for (const endPct of [0, 100]) {
|
|
1781
|
+
const isNeighbor = endPct === 0 ? leftNeighbor === 0 : rightNeighbor === 100;
|
|
1782
|
+
if (!isNeighbor) continue;
|
|
1783
|
+
const endProp = pctProps.find(
|
|
1784
|
+
(p) => percentageFromKey(propKeyName(p) ?? "") === endPct
|
|
1785
|
+
);
|
|
1786
|
+
if (!endProp?.value || endProp.value.type !== "ObjectExpression") continue;
|
|
1787
|
+
const hasAuto = endProp.value.properties.some(
|
|
1788
|
+
(p) => isObjectProperty(p) && propKeyName(p) === "_auto"
|
|
1789
|
+
);
|
|
1790
|
+
if (!hasAuto) continue;
|
|
1791
|
+
const updatedProps = { ...properties, _auto: 1 };
|
|
1792
|
+
endProp.value = buildKeyframeValueNode(updatedProps, void 0);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
if (backfillDefaults) {
|
|
1796
|
+
const newPropKeys = Object.keys(properties);
|
|
1797
|
+
const pctProps = filterPercentageProps(kfNode);
|
|
1798
|
+
for (const prop of pctProps) {
|
|
1799
|
+
const key = propKeyName(prop);
|
|
1800
|
+
if (key === pctKey) continue;
|
|
1801
|
+
const valObj = prop.value;
|
|
1802
|
+
if (!valObj || valObj.type !== "ObjectExpression") continue;
|
|
1803
|
+
const existingKeys = new Set(
|
|
1804
|
+
valObj.properties.filter((p) => isObjectProperty(p)).map((p) => propKeyName(p))
|
|
1805
|
+
);
|
|
1806
|
+
for (const pk of newPropKeys) {
|
|
1807
|
+
if (existingKeys.has(pk)) continue;
|
|
1808
|
+
const defaultVal = backfillDefaults[pk];
|
|
1809
|
+
if (defaultVal == null) continue;
|
|
1810
|
+
const fillProp = parseExpr(`{ ${safeJsKey(pk)}: ${serializeValue(defaultVal)} }`).properties[0];
|
|
1811
|
+
valObj.properties.push(fillProp);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
return recast.print(loc.parsed.ast).code;
|
|
1816
|
+
}
|
|
1817
|
+
function removeKeyframeFromScript(script, animationId, percentage) {
|
|
1818
|
+
const arrLoc = locateAnimationWithFallback(script, animationId);
|
|
1819
|
+
const arrVal = arrLoc && findPropertyNode(arrLoc.target.call.varsArg, "keyframes");
|
|
1820
|
+
if (arrLoc && arrVal?.type === "ArrayExpression") {
|
|
1821
|
+
const elements = (arrVal.elements ?? []).filter(
|
|
1822
|
+
(e) => !!e && e.type === "ObjectExpression"
|
|
1823
|
+
);
|
|
1824
|
+
const n = elements.length;
|
|
1825
|
+
if (n === 0) return script;
|
|
1826
|
+
let matchIdx = -1;
|
|
1827
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
1828
|
+
for (let i = 0; i < n; i++) {
|
|
1829
|
+
const pct = n > 1 ? i / (n - 1) * 100 : 0;
|
|
1830
|
+
const dist = Math.abs(pct - percentage);
|
|
1831
|
+
if (dist <= PCT_TOLERANCE && dist < bestDist) {
|
|
1832
|
+
matchIdx = i;
|
|
1833
|
+
bestDist = dist;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
if (matchIdx === -1) return script;
|
|
1837
|
+
const remaining = elements.filter((_, i) => i !== matchIdx);
|
|
1838
|
+
if (remaining.length < 2) {
|
|
1839
|
+
const sole = remaining[0];
|
|
1840
|
+
const record = sole ? objectExpressionToRecord(sole, arrLoc.parsed.scope) : {};
|
|
1841
|
+
collapseKeyframesToFlat(arrLoc.target.call.varsArg, record);
|
|
1842
|
+
} else {
|
|
1843
|
+
const realIdx = arrVal.elements.indexOf(elements[matchIdx]);
|
|
1844
|
+
arrVal.elements.splice(realIdx, 1);
|
|
1845
|
+
}
|
|
1846
|
+
return recast.print(arrLoc.parsed.ast).code;
|
|
1847
|
+
}
|
|
1848
|
+
const ctx = locateKeyframeCtx(script, animationId, percentage);
|
|
1849
|
+
if (!ctx) return script;
|
|
1850
|
+
const { loc, kfNode } = ctx;
|
|
1851
|
+
const match = findKeyframePropByPct(kfNode, percentage);
|
|
1852
|
+
if (!match) return script;
|
|
1853
|
+
const removeIdx = match.idx;
|
|
1854
|
+
kfNode.properties.splice(removeIdx, 1);
|
|
1855
|
+
const remainingKfs = filterPercentageProps(kfNode);
|
|
1856
|
+
if (remainingKfs.length < 2) {
|
|
1857
|
+
const record = remainingKfs.length === 1 ? objectExpressionToRecord(remainingKfs[0].value, loc.parsed.scope) : {};
|
|
1858
|
+
collapseKeyframesToFlat(loc.target.call.varsArg, record);
|
|
1859
|
+
}
|
|
1860
|
+
return recast.print(loc.parsed.ast).code;
|
|
1861
|
+
}
|
|
1862
|
+
function updateKeyframeInScript(script, animationId, percentage, properties, ease) {
|
|
1863
|
+
const arrLoc = locateAnimationWithFallback(script, animationId);
|
|
1864
|
+
const arrVal = arrLoc && findPropertyNode(arrLoc.target.call.varsArg, "keyframes");
|
|
1865
|
+
if (arrLoc && arrVal?.type === "ArrayExpression") {
|
|
1866
|
+
const elements = (arrVal.elements ?? []).filter(
|
|
1867
|
+
(e) => !!e && e.type === "ObjectExpression"
|
|
1868
|
+
);
|
|
1869
|
+
const n = elements.length;
|
|
1870
|
+
if (n === 0) return script;
|
|
1871
|
+
let matchIdx = -1;
|
|
1872
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
1873
|
+
for (let i = 0; i < n; i++) {
|
|
1874
|
+
const pct = n > 1 ? i / (n - 1) * 100 : 0;
|
|
1875
|
+
const dist = Math.abs(pct - percentage);
|
|
1876
|
+
if (dist <= PCT_TOLERANCE && dist < bestDist) {
|
|
1877
|
+
matchIdx = i;
|
|
1878
|
+
bestDist = dist;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
if (matchIdx === -1) return script;
|
|
1882
|
+
const realIdx = arrVal.elements.indexOf(elements[matchIdx]);
|
|
1883
|
+
arrVal.elements[realIdx] = buildKeyframeValueNode(properties, ease);
|
|
1884
|
+
return recast.print(arrLoc.parsed.ast).code;
|
|
1885
|
+
}
|
|
1886
|
+
const ctx = locateKeyframeCtx(script, animationId, percentage);
|
|
1887
|
+
if (!ctx) return script;
|
|
1888
|
+
const { loc, kfNode } = ctx;
|
|
1889
|
+
const match = findKeyframePropByPct(kfNode, percentage);
|
|
1890
|
+
if (!match) return script;
|
|
1891
|
+
if (Object.keys(properties).length === 0 && ease) {
|
|
1892
|
+
const existing = match.prop.value;
|
|
1893
|
+
if (existing?.type === "ObjectExpression") {
|
|
1894
|
+
const props = existing.properties ?? [];
|
|
1895
|
+
const easeIdx = props.findIndex(
|
|
1896
|
+
(p) => isObjectProperty(p) && propKeyName(p) === "ease"
|
|
1897
|
+
);
|
|
1898
|
+
const easeNode = parseExpr(`({ ease: ${JSON.stringify(ease)} })`).properties[0];
|
|
1899
|
+
if (easeIdx >= 0) {
|
|
1900
|
+
props[easeIdx] = easeNode;
|
|
1901
|
+
} else {
|
|
1902
|
+
props.push(easeNode);
|
|
1903
|
+
}
|
|
1904
|
+
return recast.print(loc.parsed.ast).code;
|
|
1905
|
+
}
|
|
1906
|
+
return script;
|
|
1907
|
+
}
|
|
1908
|
+
match.prop.value = buildKeyframeValueNode(properties, ease);
|
|
1909
|
+
return recast.print(loc.parsed.ast).code;
|
|
1910
|
+
}
|
|
1911
|
+
function stripEditableAndEase(varsArg) {
|
|
1912
|
+
if (varsArg?.type !== "ObjectExpression") return;
|
|
1913
|
+
varsArg.properties = varsArg.properties.filter((p) => {
|
|
1914
|
+
if (!isObjectProperty(p)) return true;
|
|
1915
|
+
const key = propKeyName(p);
|
|
1916
|
+
return key !== "ease" && key !== "keyframes";
|
|
1917
|
+
});
|
|
1918
|
+
filterEditableKeys(varsArg, () => false);
|
|
1919
|
+
}
|
|
1920
|
+
function insertKeyframesProp(varsArg, fromProps, toProps, easeEach) {
|
|
1921
|
+
const fromEntries = Object.entries(fromProps).map(([k, v]) => `${safeJsKey(k)}: ${serializeValue(v)}`);
|
|
1922
|
+
const toEntries = Object.entries(toProps).map(([k, v]) => `${safeJsKey(k)}: ${serializeValue(v)}`);
|
|
1923
|
+
const easeEntry = easeEach ? `, easeEach: ${JSON.stringify(easeEach)}` : "";
|
|
1924
|
+
const kfCode = `{ "0%": { ${fromEntries.join(", ")} }, "100%": { ${toEntries.join(", ")} }${easeEntry} }`;
|
|
1925
|
+
const kfProp = parseExpr(`{ keyframes: {} }`).properties[0];
|
|
1926
|
+
kfProp.value = parseExpr(kfCode);
|
|
1927
|
+
if (varsArg?.type === "ObjectExpression") varsArg.properties.unshift(kfProp);
|
|
1928
|
+
}
|
|
1929
|
+
function convertToKeyframesInScript(script, animationId, resolvedFromValues, setDuration = 1) {
|
|
1930
|
+
let loc = locateAnimationWithFallback(script, animationId);
|
|
1931
|
+
if (!loc) return script;
|
|
1932
|
+
const anim = loc.target.animation;
|
|
1933
|
+
if (anim.keyframes) return script;
|
|
1934
|
+
const { fromProps, toProps } = resolveConversionProps(anim, resolvedFromValues);
|
|
1935
|
+
const varsArg = loc.target.call.varsArg;
|
|
1936
|
+
const originalEase = anim.ease;
|
|
1937
|
+
stripEditableAndEase(varsArg);
|
|
1938
|
+
insertKeyframesProp(varsArg, fromProps, toProps, originalEase || void 0);
|
|
1939
|
+
if (originalEase) {
|
|
1940
|
+
setVarsKey(varsArg, "ease", "none");
|
|
1941
|
+
}
|
|
1942
|
+
if (anim.method === "from" || anim.method === "fromTo") {
|
|
1943
|
+
loc.target.call.node.callee.property.name = "to";
|
|
1944
|
+
if (anim.method === "fromTo") loc.target.call.node.arguments.splice(1, 1);
|
|
1945
|
+
}
|
|
1946
|
+
if (anim.method === "set") {
|
|
1947
|
+
const calleeObj = loc.target.call.node.callee.object;
|
|
1948
|
+
if (anim.global && calleeObj?.type === "Identifier") {
|
|
1949
|
+
calleeObj.name = loc.parsed.timelineVar;
|
|
1950
|
+
if (loc.target.call.node.arguments.length < 3) {
|
|
1951
|
+
loc.target.call.node.arguments.push(parseExpr("0"));
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
loc.target.call.node.callee.property.name = "to";
|
|
1955
|
+
removeVarsKey(varsArg, "immediateRender");
|
|
1956
|
+
setVarsKey(varsArg, "duration", Math.max(1e-3, setDuration));
|
|
1957
|
+
}
|
|
1958
|
+
return recast.print(loc.parsed.ast).code;
|
|
1959
|
+
}
|
|
1960
|
+
function removeAllKeyframesFromScript(script, animationId) {
|
|
1961
|
+
let loc = locateAnimationWithFallback(script, animationId);
|
|
1962
|
+
if (!loc) return script;
|
|
1963
|
+
const kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
|
|
1964
|
+
if (!kfNode) return script;
|
|
1965
|
+
const kfEntries = filterPercentageProps(kfNode).map((p) => ({ pct: percentageFromKey(propKeyName(p)), prop: p })).filter((e) => !Number.isNaN(e.pct)).sort((a, b) => a.pct - b.pct);
|
|
1966
|
+
if (kfEntries.length === 0) return script;
|
|
1967
|
+
const method = loc.target.call.method;
|
|
1968
|
+
const collapseEntry = method === "from" ? kfEntries[0] : kfEntries[kfEntries.length - 1];
|
|
1969
|
+
const record = objectExpressionToRecord(collapseEntry.prop.value, loc.parsed.scope);
|
|
1970
|
+
collapseKeyframesToFlat(loc.target.call.varsArg, record);
|
|
1971
|
+
return recast.print(loc.parsed.ast).code;
|
|
1972
|
+
}
|
|
1973
|
+
function materializeKeyframesInScript(script, animationId, keyframes, easeEach, resolvedSelector) {
|
|
1974
|
+
let loc = locateAnimationWithFallback(script, animationId);
|
|
1975
|
+
if (!loc) return script;
|
|
1976
|
+
const varsArg = loc.target.call.varsArg;
|
|
1977
|
+
if (resolvedSelector && loc.target.call.node.arguments[0]) {
|
|
1978
|
+
loc.target.call.node.arguments[0] = parseExpr(JSON.stringify(resolvedSelector));
|
|
1979
|
+
}
|
|
1980
|
+
const kfObjCode = buildKeyframeObjectCode(sortedKeyframes(keyframes), { easeEach });
|
|
1981
|
+
const kfParent = varsArg.properties.find(
|
|
1982
|
+
(p) => isObjectProperty(p) && propKeyName(p) === "keyframes"
|
|
1983
|
+
);
|
|
1984
|
+
if (kfParent) {
|
|
1985
|
+
kfParent.value = parseExpr(kfObjCode);
|
|
1986
|
+
} else {
|
|
1987
|
+
const kfProp = parseExpr(`{ keyframes: ${kfObjCode} }`).properties[0];
|
|
1988
|
+
varsArg.properties.unshift(kfProp);
|
|
1989
|
+
}
|
|
1990
|
+
removeVarsKey(varsArg, "easeEach");
|
|
1991
|
+
return recast.print(loc.parsed.ast).code;
|
|
1992
|
+
}
|
|
1993
|
+
function numericXY(props) {
|
|
1994
|
+
const x = props.x;
|
|
1995
|
+
const y = props.y;
|
|
1996
|
+
return typeof x === "number" && typeof y === "number" ? { x, y } : null;
|
|
1997
|
+
}
|
|
1998
|
+
function extractArcWaypoints(anim) {
|
|
1999
|
+
const kfs = anim.keyframes?.keyframes ?? [];
|
|
2000
|
+
const waypoints = kfs.map((kf) => numericXY(kf.properties)).filter((p) => p !== null);
|
|
2001
|
+
if (waypoints.length >= 2) return waypoints;
|
|
2002
|
+
const px = anim.properties.x;
|
|
2003
|
+
const py = anim.properties.y;
|
|
2004
|
+
if (typeof px !== "number" && typeof py !== "number") return waypoints;
|
|
2005
|
+
return [
|
|
2006
|
+
{ x: 0, y: 0 },
|
|
2007
|
+
{ x: typeof px === "number" ? px : 0, y: typeof py === "number" ? py : 0 }
|
|
2008
|
+
];
|
|
2009
|
+
}
|
|
2010
|
+
function buildMotionPathObjectCode(config) {
|
|
2011
|
+
const { waypoints, segments, autoRotate } = config;
|
|
2012
|
+
const hasExplicitControlPoints = segments.some((s) => s.cp1 && s.cp2);
|
|
2013
|
+
const curvinessVaries = segments.some(
|
|
2014
|
+
(s) => (s.curviness ?? 1) !== (segments[0]?.curviness ?? 1)
|
|
2015
|
+
);
|
|
2016
|
+
let pathEntries;
|
|
2017
|
+
if ((hasExplicitControlPoints || curvinessVaries) && waypoints.length >= 2) {
|
|
2018
|
+
pathEntries = [`{x: ${waypoints[0].x}, y: ${waypoints[0].y}}`];
|
|
2019
|
+
for (let i = 0; i < segments.length; i++) {
|
|
2020
|
+
const seg = segments[i];
|
|
2021
|
+
const nextWp = waypoints[i + 1];
|
|
2022
|
+
if (seg.cp1 && seg.cp2) {
|
|
2023
|
+
pathEntries.push(`{x: ${seg.cp1.x}, y: ${seg.cp1.y}}`);
|
|
2024
|
+
pathEntries.push(`{x: ${seg.cp2.x}, y: ${seg.cp2.y}}`);
|
|
2025
|
+
} else {
|
|
2026
|
+
const wp = waypoints[i];
|
|
2027
|
+
const dx = nextWp.x - wp.x;
|
|
2028
|
+
const dy = nextWp.y - wp.y;
|
|
2029
|
+
const c = seg.curviness ?? 1;
|
|
2030
|
+
pathEntries.push(
|
|
2031
|
+
`{x: ${wp.x + dx * 0.33}, y: ${wp.y + dy * 0.33 - c * Math.abs(dx) * 0.25}}`
|
|
2032
|
+
);
|
|
2033
|
+
pathEntries.push(
|
|
2034
|
+
`{x: ${wp.x + dx * 0.66}, y: ${wp.y + dy * 0.66 - c * Math.abs(dx) * 0.25}}`
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
pathEntries.push(`{x: ${nextWp.x}, y: ${nextWp.y}}`);
|
|
2038
|
+
}
|
|
2039
|
+
const pathStr = pathEntries.join(", ");
|
|
2040
|
+
const parts2 = [`path: [${pathStr}]`, `type: "cubic"`];
|
|
2041
|
+
if (autoRotate === true) parts2.push("autoRotate: true");
|
|
2042
|
+
else if (typeof autoRotate === "number") parts2.push(`autoRotate: ${autoRotate}`);
|
|
2043
|
+
return `{ ${parts2.join(", ")} }`;
|
|
2044
|
+
}
|
|
2045
|
+
pathEntries = waypoints.map((wp) => `{x: ${wp.x}, y: ${wp.y}}`);
|
|
2046
|
+
const curviness = segments[0]?.curviness ?? 1;
|
|
2047
|
+
const parts = [`path: [${pathEntries.join(", ")}]`];
|
|
2048
|
+
if (curviness !== 1) parts.push(`curviness: ${curviness}`);
|
|
2049
|
+
if (autoRotate === true) parts.push("autoRotate: true");
|
|
2050
|
+
else if (typeof autoRotate === "number") parts.push(`autoRotate: ${autoRotate}`);
|
|
2051
|
+
return `{ ${parts.join(", ")} }`;
|
|
2052
|
+
}
|
|
2053
|
+
function setArcPathInScript(script, animationId, config) {
|
|
2054
|
+
const loc = locateAnimation(script, animationId);
|
|
2055
|
+
if (!loc) return script;
|
|
2056
|
+
const varsArg = loc.target.call.varsArg;
|
|
2057
|
+
const anim = loc.target.animation;
|
|
2058
|
+
if (!config.enabled) {
|
|
2059
|
+
const motionPathProp = varsArg.properties.find(
|
|
2060
|
+
(p) => isObjectProperty(p) && propKeyName(p) === "motionPath"
|
|
2061
|
+
);
|
|
2062
|
+
if (motionPathProp) {
|
|
2063
|
+
const mpVal = motionPathProp.value;
|
|
2064
|
+
let pathArr;
|
|
2065
|
+
if (mpVal?.type === "ObjectExpression") {
|
|
2066
|
+
const pathProp = mpVal.properties.find(
|
|
2067
|
+
(p) => isObjectProperty(p) && propKeyName(p) === "path"
|
|
2068
|
+
);
|
|
2069
|
+
if (pathProp?.value?.type === "ArrayExpression") pathArr = pathProp.value.elements;
|
|
2070
|
+
}
|
|
2071
|
+
if (pathArr && pathArr.length > 0) {
|
|
2072
|
+
const last = pathArr[pathArr.length - 1];
|
|
2073
|
+
if (last?.type === "ObjectExpression") {
|
|
2074
|
+
for (const p of last.properties) {
|
|
2075
|
+
const k = propKeyName(p);
|
|
2076
|
+
if (k === "x" || k === "y") {
|
|
2077
|
+
const v = p.value?.value;
|
|
2078
|
+
if (typeof v === "number") setVarsKey(varsArg, k, v);
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
removeVarsKey(varsArg, "motionPath");
|
|
2085
|
+
return recast.print(loc.parsed.ast).code;
|
|
2086
|
+
}
|
|
2087
|
+
const waypoints = extractArcWaypoints(anim);
|
|
2088
|
+
if (waypoints.length < 2) return script;
|
|
2089
|
+
const segments = config.segments.length === waypoints.length - 1 ? config.segments : Array.from({ length: waypoints.length - 1 }, () => ({ curviness: 1 }));
|
|
2090
|
+
const motionPathCode = buildMotionPathObjectCode({
|
|
2091
|
+
waypoints,
|
|
2092
|
+
segments,
|
|
2093
|
+
autoRotate: config.autoRotate
|
|
2094
|
+
});
|
|
2095
|
+
const motionPathNode = parseExpr(motionPathCode);
|
|
2096
|
+
const existingProp = varsArg.properties.find(
|
|
2097
|
+
(p) => isObjectProperty(p) && propKeyName(p) === "motionPath"
|
|
2098
|
+
);
|
|
2099
|
+
if (existingProp) {
|
|
2100
|
+
existingProp.value = motionPathNode;
|
|
2101
|
+
} else {
|
|
2102
|
+
const prop = parseExpr(`{ motionPath: ${motionPathCode} }`).properties[0];
|
|
2103
|
+
varsArg.properties.push(prop);
|
|
2104
|
+
}
|
|
2105
|
+
const kfNode = findKeyframesObjectNode(varsArg);
|
|
2106
|
+
if (kfNode) {
|
|
2107
|
+
for (const pctProp of filterPercentageProps(kfNode)) {
|
|
2108
|
+
if (pctProp.value?.type === "ObjectExpression") {
|
|
2109
|
+
pctProp.value.properties = pctProp.value.properties.filter((p) => {
|
|
2110
|
+
const k = propKeyName(p);
|
|
2111
|
+
return k !== "x" && k !== "y";
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
removeVarsKey(varsArg, "x");
|
|
2117
|
+
removeVarsKey(varsArg, "y");
|
|
2118
|
+
return recast.print(loc.parsed.ast).code;
|
|
2119
|
+
}
|
|
2120
|
+
function updateArcSegmentInScript(script, animationId, segmentIndex, update) {
|
|
2121
|
+
const loc = locateAnimation(script, animationId);
|
|
2122
|
+
if (!loc) return script;
|
|
2123
|
+
const anim = loc.target.animation;
|
|
2124
|
+
if (!anim.arcPath?.enabled) return script;
|
|
2125
|
+
const segments = [...anim.arcPath.segments];
|
|
2126
|
+
if (segmentIndex < 0 || segmentIndex >= segments.length) return script;
|
|
2127
|
+
segments[segmentIndex] = { ...segments[segmentIndex], ...update };
|
|
2128
|
+
const waypoints = extractArcWaypoints(anim);
|
|
2129
|
+
if (waypoints.length < 2) return script;
|
|
2130
|
+
const motionPathCode = buildMotionPathObjectCode({
|
|
2131
|
+
waypoints,
|
|
2132
|
+
segments,
|
|
2133
|
+
autoRotate: anim.arcPath.autoRotate
|
|
2134
|
+
});
|
|
2135
|
+
const varsArg = loc.target.call.varsArg;
|
|
2136
|
+
const existingProp = varsArg.properties.find(
|
|
2137
|
+
(p) => isObjectProperty(p) && propKeyName(p) === "motionPath"
|
|
2138
|
+
);
|
|
2139
|
+
if (existingProp) {
|
|
2140
|
+
existingProp.value = parseExpr(motionPathCode);
|
|
2141
|
+
}
|
|
2142
|
+
return recast.print(loc.parsed.ast).code;
|
|
2143
|
+
}
|
|
2144
|
+
function updateMotionPathPointInScript(script, animationId, pointIndex, point) {
|
|
2145
|
+
const loc = locateAnimation(script, animationId);
|
|
2146
|
+
if (!loc) return script;
|
|
2147
|
+
const anim = loc.target.animation;
|
|
2148
|
+
if (!anim.arcPath?.enabled) return script;
|
|
2149
|
+
const waypoints = extractArcWaypoints(anim);
|
|
2150
|
+
if (pointIndex < 0 || pointIndex >= waypoints.length || waypoints.length < 2) return script;
|
|
2151
|
+
const nextWaypoints = waypoints.map(
|
|
2152
|
+
(wp, i) => i === pointIndex ? { x: point.x, y: point.y } : wp
|
|
2153
|
+
);
|
|
2154
|
+
const motionPathCode = buildMotionPathObjectCode({
|
|
2155
|
+
waypoints: nextWaypoints,
|
|
2156
|
+
segments: anim.arcPath.segments,
|
|
2157
|
+
autoRotate: anim.arcPath.autoRotate
|
|
2158
|
+
});
|
|
2159
|
+
const varsArg = loc.target.call.varsArg;
|
|
2160
|
+
const existingProp = varsArg.properties.find(
|
|
2161
|
+
(p) => isObjectProperty(p) && propKeyName(p) === "motionPath"
|
|
2162
|
+
);
|
|
2163
|
+
if (existingProp) {
|
|
2164
|
+
existingProp.value = parseExpr(motionPathCode);
|
|
2165
|
+
}
|
|
2166
|
+
return recast.print(loc.parsed.ast).code;
|
|
2167
|
+
}
|
|
2168
|
+
function hasCubicSegments(segments) {
|
|
2169
|
+
return segments.some((s) => s.cp1 != null || s.cp2 != null);
|
|
2170
|
+
}
|
|
2171
|
+
function writeMotionPathValue(loc, waypoints, segments, autoRotate) {
|
|
2172
|
+
const motionPathCode = buildMotionPathObjectCode({ waypoints, segments, autoRotate });
|
|
2173
|
+
const varsArg = loc.target.call.varsArg;
|
|
2174
|
+
const existingProp = varsArg.properties.find(
|
|
2175
|
+
(p) => isObjectProperty(p) && propKeyName(p) === "motionPath"
|
|
2176
|
+
);
|
|
2177
|
+
if (existingProp) existingProp.value = parseExpr(motionPathCode);
|
|
2178
|
+
return recast.print(loc.parsed.ast).code;
|
|
2179
|
+
}
|
|
2180
|
+
function addMotionPathPointInScript(script, animationId, index, point) {
|
|
2181
|
+
const loc = locateAnimation(script, animationId);
|
|
2182
|
+
if (!loc) return script;
|
|
2183
|
+
const anim = loc.target.animation;
|
|
2184
|
+
if (!anim.arcPath?.enabled || hasCubicSegments(anim.arcPath.segments)) return script;
|
|
2185
|
+
const waypoints = extractArcWaypoints(anim);
|
|
2186
|
+
if (index < 1 || index > waypoints.length - 1) return script;
|
|
2187
|
+
const segments = [...anim.arcPath.segments];
|
|
2188
|
+
waypoints.splice(index, 0, { x: point.x, y: point.y });
|
|
2189
|
+
const splitCurviness = segments[index - 1]?.curviness ?? 1;
|
|
2190
|
+
segments.splice(index - 1, 0, { curviness: splitCurviness });
|
|
2191
|
+
return writeMotionPathValue(loc, waypoints, segments, anim.arcPath.autoRotate);
|
|
2192
|
+
}
|
|
2193
|
+
function removeMotionPathPointInScript(script, animationId, index) {
|
|
2194
|
+
const loc = locateAnimation(script, animationId);
|
|
2195
|
+
if (!loc) return script;
|
|
2196
|
+
const anim = loc.target.animation;
|
|
2197
|
+
if (!anim.arcPath?.enabled || hasCubicSegments(anim.arcPath.segments)) return script;
|
|
2198
|
+
const waypoints = extractArcWaypoints(anim);
|
|
2199
|
+
if (waypoints.length <= 2 || index < 0 || index >= waypoints.length) return script;
|
|
2200
|
+
const segments = [...anim.arcPath.segments];
|
|
2201
|
+
waypoints.splice(index, 1);
|
|
2202
|
+
segments.splice(Math.min(index, segments.length - 1), 1);
|
|
2203
|
+
return writeMotionPathValue(loc, waypoints, segments, anim.arcPath.autoRotate);
|
|
2204
|
+
}
|
|
2205
|
+
function addMotionPathToScript(script, targetSelector, position, duration, point, ease = "power1.inOut") {
|
|
2206
|
+
let parsed;
|
|
2207
|
+
try {
|
|
2208
|
+
parsed = parseGsapAst(script);
|
|
2209
|
+
} catch (e) {
|
|
2210
|
+
console.warn("[gsap-parser] addMotionPathToScript parse failed:", e);
|
|
2211
|
+
return { script, id: null };
|
|
2212
|
+
}
|
|
2213
|
+
if (parsed.located.length === 0 && parsed.detection.timelineVar === null) {
|
|
2214
|
+
return { script, id: null };
|
|
2215
|
+
}
|
|
2216
|
+
const motionPathCode = buildMotionPathObjectCode({
|
|
2217
|
+
waypoints: [
|
|
2218
|
+
{ x: 0, y: 0 },
|
|
2219
|
+
{ x: point.x, y: point.y }
|
|
2220
|
+
],
|
|
2221
|
+
segments: [{ curviness: 1 }],
|
|
2222
|
+
autoRotate: false
|
|
2223
|
+
});
|
|
2224
|
+
const selector = JSON.stringify(targetSelector);
|
|
2225
|
+
const varEntries = [
|
|
2226
|
+
`motionPath: ${motionPathCode}`,
|
|
2227
|
+
`duration: ${serializeValue(duration)}`,
|
|
2228
|
+
`ease: ${JSON.stringify(ease)}`
|
|
2229
|
+
];
|
|
2230
|
+
const stmtCode = `${parsed.timelineVar}.to(${selector}, { ${varEntries.join(", ")} }, ${serializeValue(position)});`;
|
|
2231
|
+
const newStatement = parseScript(stmtCode).program.body[0];
|
|
2232
|
+
insertAfterAnchor(parsed, newStatement);
|
|
2233
|
+
const result = recast.print(parsed.ast).code;
|
|
2234
|
+
const reParsed = parseGsapAst(result);
|
|
2235
|
+
const newId = reParsed.located[reParsed.located.length - 1]?.id ?? null;
|
|
2236
|
+
return { script: result, id: newId };
|
|
2237
|
+
}
|
|
2238
|
+
function removeArcPathFromScript(script, animationId) {
|
|
2239
|
+
return setArcPathInScript(script, animationId, {
|
|
2240
|
+
enabled: false,
|
|
2241
|
+
autoRotate: false,
|
|
2242
|
+
segments: []
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
function splitIntoPropertyGroups(script, animationId) {
|
|
2246
|
+
let loc = locateAnimationWithFallback(script, animationId);
|
|
2247
|
+
if (!loc) return { script, ids: [animationId] };
|
|
2248
|
+
const anim = loc.target.animation;
|
|
2249
|
+
const allPropKeys = /* @__PURE__ */ new Set();
|
|
2250
|
+
if (anim.keyframes) {
|
|
2251
|
+
for (const kf of anim.keyframes.keyframes) {
|
|
2252
|
+
for (const k of Object.keys(kf.properties)) allPropKeys.add(k);
|
|
2253
|
+
}
|
|
2254
|
+
} else {
|
|
2255
|
+
for (const k of Object.keys(anim.properties)) allPropKeys.add(k);
|
|
2256
|
+
}
|
|
2257
|
+
const groupProps = /* @__PURE__ */ new Map();
|
|
2258
|
+
for (const key of allPropKeys) {
|
|
2259
|
+
if (key === "transformOrigin") continue;
|
|
2260
|
+
const group = classifyPropertyGroup(key);
|
|
2261
|
+
let arr = groupProps.get(group);
|
|
2262
|
+
if (!arr) {
|
|
2263
|
+
arr = [];
|
|
2264
|
+
groupProps.set(group, arr);
|
|
2265
|
+
}
|
|
2266
|
+
arr.push(key);
|
|
2267
|
+
}
|
|
2268
|
+
if (groupProps.size <= 1) return { script, ids: [anim.id] };
|
|
2269
|
+
if (allPropKeys.has("transformOrigin")) {
|
|
2270
|
+
let largestGroup;
|
|
2271
|
+
let largestCount = 0;
|
|
2272
|
+
for (const [group, props] of groupProps) {
|
|
2273
|
+
if (props.length > largestCount) {
|
|
2274
|
+
largestCount = props.length;
|
|
2275
|
+
largestGroup = group;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
if (largestGroup) {
|
|
2279
|
+
groupProps.get(largestGroup).push("transformOrigin");
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
let result = script;
|
|
2283
|
+
result = removeAnimationFromScript(result, anim.id);
|
|
2284
|
+
for (const [, props] of groupProps) {
|
|
2285
|
+
const propSet = new Set(props);
|
|
2286
|
+
if (anim.keyframes) {
|
|
2287
|
+
const groupKeyframes = [];
|
|
2288
|
+
for (const kf of anim.keyframes.keyframes) {
|
|
2289
|
+
const filtered = {};
|
|
2290
|
+
for (const [k, v] of Object.entries(kf.properties)) {
|
|
2291
|
+
if (propSet.has(k)) filtered[k] = v;
|
|
2292
|
+
}
|
|
2293
|
+
if (Object.keys(filtered).length === 0) continue;
|
|
2294
|
+
groupKeyframes.push({
|
|
2295
|
+
percentage: kf.percentage,
|
|
2296
|
+
properties: filtered,
|
|
2297
|
+
...kf.ease ? { ease: kf.ease } : {}
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
if (groupKeyframes.length === 0) continue;
|
|
2301
|
+
const addResult = addAnimationWithKeyframesToScript(
|
|
2302
|
+
result,
|
|
2303
|
+
anim.targetSelector,
|
|
2304
|
+
typeof anim.position === "number" ? anim.position : 0,
|
|
2305
|
+
anim.duration ?? 0.5,
|
|
2306
|
+
groupKeyframes,
|
|
2307
|
+
anim.keyframes.easeEach ?? anim.ease
|
|
2308
|
+
);
|
|
2309
|
+
result = addResult.script;
|
|
2310
|
+
} else {
|
|
2311
|
+
const groupProperties = {};
|
|
2312
|
+
for (const [k, v] of Object.entries(anim.properties)) {
|
|
2313
|
+
if (propSet.has(k)) groupProperties[k] = v;
|
|
2314
|
+
}
|
|
2315
|
+
if (Object.keys(groupProperties).length === 0) continue;
|
|
2316
|
+
let fromProperties;
|
|
2317
|
+
if (anim.method === "fromTo" && anim.fromProperties) {
|
|
2318
|
+
fromProperties = {};
|
|
2319
|
+
for (const [k, v] of Object.entries(anim.fromProperties)) {
|
|
2320
|
+
if (propSet.has(k)) fromProperties[k] = v;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
const addResult = addAnimationToScript(result, {
|
|
2324
|
+
targetSelector: anim.targetSelector,
|
|
2325
|
+
method: anim.method,
|
|
2326
|
+
position: anim.position,
|
|
2327
|
+
duration: anim.duration,
|
|
2328
|
+
ease: anim.ease,
|
|
2329
|
+
properties: groupProperties,
|
|
2330
|
+
fromProperties,
|
|
2331
|
+
extras: anim.extras
|
|
2332
|
+
});
|
|
2333
|
+
result = addResult.script;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
const reParsed = parseGsapAst(result);
|
|
2337
|
+
const newIds = reParsed.located.filter((l) => l.animation.targetSelector === anim.targetSelector).map((l) => l.id);
|
|
2338
|
+
return { script: result, ids: newIds };
|
|
2339
|
+
}
|
|
2340
|
+
function unrollDynamicAnimations(script, animationId, elements) {
|
|
2341
|
+
const loc = locateAnimation(script, animationId);
|
|
2342
|
+
if (!loc) return script;
|
|
2343
|
+
const varsArg = loc.target.call.varsArg;
|
|
2344
|
+
const durationVal = extractLiteralValue(findPropertyNode(varsArg, "duration"), loc.parsed.scope);
|
|
2345
|
+
const easeVal = extractLiteralValue(findPropertyNode(varsArg, "ease"), loc.parsed.scope);
|
|
2346
|
+
const duration = typeof durationVal === "number" ? durationVal : 8;
|
|
2347
|
+
const ease = typeof easeVal === "string" ? easeVal : "none";
|
|
2348
|
+
const posArg = loc.target.call.positionArg;
|
|
2349
|
+
const position = posArg ? extractLiteralValue(posArg, loc.parsed.scope) : 0;
|
|
2350
|
+
const posCode = typeof position === "number" ? String(position) : typeof position === "string" ? JSON.stringify(position) : "0";
|
|
2351
|
+
let loopNode = null;
|
|
2352
|
+
let current = loc.target.call.path;
|
|
2353
|
+
while (current) {
|
|
2354
|
+
const node = current.node ?? current.value;
|
|
2355
|
+
if (node?.type === "ForStatement" || node?.type === "ForInStatement" || node?.type === "ForOfStatement" || node?.type === "WhileStatement") {
|
|
2356
|
+
loopNode = node;
|
|
2357
|
+
break;
|
|
2358
|
+
}
|
|
2359
|
+
if (node?.type === "ExpressionStatement" && node.expression?.type === "CallExpression" && node.expression.callee?.property?.name === "forEach") {
|
|
2360
|
+
loopNode = node;
|
|
2361
|
+
break;
|
|
2362
|
+
}
|
|
2363
|
+
current = current.parent ?? current.parentPath;
|
|
2364
|
+
}
|
|
2365
|
+
const calls = [];
|
|
2366
|
+
for (const el of elements) {
|
|
2367
|
+
const kfCode = buildKeyframeObjectCode(sortedKeyframes(el.keyframes), {
|
|
2368
|
+
easeEach: el.easeEach
|
|
2369
|
+
});
|
|
2370
|
+
calls.push(
|
|
2371
|
+
`${loc.parsed.timelineVar}.to(${JSON.stringify(el.selector)}, { keyframes: ${kfCode}, duration: ${duration}, ease: ${JSON.stringify(ease)} }, ${posCode});`
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
const replacement = calls.join("\n ");
|
|
2375
|
+
if (loopNode) {
|
|
2376
|
+
const start = loopNode.start ?? loopNode.range?.[0];
|
|
2377
|
+
const end = loopNode.end ?? loopNode.range?.[1];
|
|
2378
|
+
if (typeof start === "number" && typeof end === "number") {
|
|
2379
|
+
return script.slice(0, start) + replacement + script.slice(end);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
const stmtNode = loc.target.call.path?.parent?.node ?? loc.target.call.path?.parentPath?.node;
|
|
2383
|
+
if (stmtNode?.type === "ExpressionStatement") {
|
|
2384
|
+
const start = stmtNode.start ?? stmtNode.range?.[0];
|
|
2385
|
+
const end = stmtNode.end ?? stmtNode.range?.[1];
|
|
2386
|
+
if (typeof start === "number" && typeof end === "number") {
|
|
2387
|
+
return script.slice(0, start) + replacement + script.slice(end);
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
return script;
|
|
2391
|
+
}
|
|
2392
|
+
export {
|
|
2393
|
+
PROPERTY_GROUPS,
|
|
2394
|
+
SPRING_PRESETS,
|
|
2395
|
+
SUPPORTED_EASES,
|
|
2396
|
+
SUPPORTED_PROPS,
|
|
2397
|
+
addAnimationToScript,
|
|
2398
|
+
addAnimationWithKeyframesToScript,
|
|
2399
|
+
addKeyframeToScript,
|
|
2400
|
+
addMotionPathPointInScript,
|
|
2401
|
+
addMotionPathToScript,
|
|
2402
|
+
classifyPropertyGroup,
|
|
2403
|
+
classifyTweenPropertyGroup,
|
|
2404
|
+
convertToKeyframesInScript,
|
|
2405
|
+
generateSpringEaseData,
|
|
2406
|
+
getAnimationsForElementId,
|
|
2407
|
+
gsapAnimationsToKeyframes,
|
|
2408
|
+
isStudioHoldSet,
|
|
2409
|
+
keyframesToGsapAnimations,
|
|
2410
|
+
materializeKeyframesInScript,
|
|
2411
|
+
parseGsapScript,
|
|
2412
|
+
removeAllKeyframesFromScript,
|
|
2413
|
+
removeAnimationFromScript,
|
|
2414
|
+
removeArcPathFromScript,
|
|
2415
|
+
removeKeyframeFromScript,
|
|
2416
|
+
removeMotionPathPointInScript,
|
|
2417
|
+
scalePositionsInScript,
|
|
2418
|
+
serializeGsapAnimations,
|
|
2419
|
+
setArcPathInScript,
|
|
2420
|
+
shiftPositionsInScript,
|
|
2421
|
+
splitAnimationsInScript,
|
|
2422
|
+
splitIntoPropertyGroups,
|
|
2423
|
+
syncPositionHoldsBeforeKeyframes,
|
|
2424
|
+
unrollDynamicAnimations,
|
|
2425
|
+
updateAnimationInScript,
|
|
2426
|
+
updateArcSegmentInScript,
|
|
2427
|
+
updateKeyframeInScript,
|
|
2428
|
+
updateMotionPathPointInScript,
|
|
2429
|
+
validateCompositionGsap
|
|
2430
|
+
};
|
|
2431
|
+
//# sourceMappingURL=gsapParser.js.map
|