@hyperframes/core 0.6.97 → 0.6.99

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.
Files changed (89) hide show
  1. package/dist/beats/beatDetection.d.ts +22 -0
  2. package/dist/beats/beatDetection.d.ts.map +1 -0
  3. package/dist/beats/beatDetection.js +246 -0
  4. package/dist/beats/beatDetection.js.map +1 -0
  5. package/dist/beats/beatFile.d.ts +18 -0
  6. package/dist/beats/beatFile.d.ts.map +1 -0
  7. package/dist/beats/beatFile.js +112 -0
  8. package/dist/beats/beatFile.js.map +1 -0
  9. package/dist/beats/index.d.ts +3 -0
  10. package/dist/beats/index.d.ts.map +1 -0
  11. package/dist/beats/index.js +3 -0
  12. package/dist/beats/index.js.map +1 -0
  13. package/dist/compiler/htmlBundler.d.ts +0 -9
  14. package/dist/compiler/htmlBundler.d.ts.map +1 -1
  15. package/dist/compiler/htmlBundler.js +70 -92
  16. package/dist/compiler/htmlBundler.js.map +1 -1
  17. package/dist/compiler/rewriteSubCompPaths.d.ts.map +1 -1
  18. package/dist/compiler/rewriteSubCompPaths.js +3 -11
  19. package/dist/compiler/rewriteSubCompPaths.js.map +1 -1
  20. package/dist/core.types.d.ts +0 -5
  21. package/dist/core.types.d.ts.map +1 -1
  22. package/dist/core.types.js +0 -15
  23. package/dist/core.types.js.map +1 -1
  24. package/dist/fonts/systemFontLocator.d.ts +1 -0
  25. package/dist/fonts/systemFontLocator.d.ts.map +1 -1
  26. package/dist/fonts/systemFontLocator.js +8 -2
  27. package/dist/fonts/systemFontLocator.js.map +1 -1
  28. package/dist/generated/runtime-inline.js +1 -1
  29. package/dist/generated/runtime-inline.js.map +1 -1
  30. package/dist/generators/hyperframes.d.ts.map +1 -1
  31. package/dist/generators/hyperframes.js +21 -18
  32. package/dist/generators/hyperframes.js.map +1 -1
  33. package/dist/hyperframe.manifest.json +1 -1
  34. package/dist/hyperframe.runtime.iife.js +22 -22
  35. package/dist/hyperframe.runtime.mjs +22 -22
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/lint/hyperframeLinter.d.ts +0 -11
  41. package/dist/lint/hyperframeLinter.d.ts.map +1 -1
  42. package/dist/lint/hyperframeLinter.js +0 -73
  43. package/dist/lint/hyperframeLinter.js.map +1 -1
  44. package/dist/lint/rules/adapters.d.ts.map +1 -1
  45. package/dist/lint/rules/adapters.js +9 -15
  46. package/dist/lint/rules/adapters.js.map +1 -1
  47. package/dist/lint/rules/composition.d.ts.map +1 -1
  48. package/dist/lint/rules/composition.js +2 -3
  49. package/dist/lint/rules/composition.js.map +1 -1
  50. package/dist/lint/rules/core.d.ts.map +1 -1
  51. package/dist/lint/rules/core.js +2 -3
  52. package/dist/lint/rules/core.js.map +1 -1
  53. package/dist/lint/rules/gsap.d.ts.map +1 -1
  54. package/dist/lint/rules/gsap.js +13 -67
  55. package/dist/lint/rules/gsap.js.map +1 -1
  56. package/dist/lint/utils.d.ts +5 -0
  57. package/dist/lint/utils.d.ts.map +1 -1
  58. package/dist/lint/utils.js +64 -0
  59. package/dist/lint/utils.js.map +1 -1
  60. package/dist/parsers/gsapParser.d.ts +2 -0
  61. package/dist/parsers/gsapParser.d.ts.map +1 -1
  62. package/dist/parsers/gsapParser.js +143 -140
  63. package/dist/parsers/gsapParser.js.map +1 -1
  64. package/dist/parsers/gsapParserAcorn.d.ts +32 -0
  65. package/dist/parsers/gsapParserAcorn.d.ts.map +1 -0
  66. package/dist/parsers/gsapParserAcorn.js +980 -0
  67. package/dist/parsers/gsapParserAcorn.js.map +1 -0
  68. package/dist/parsers/gsapSerialize.d.ts +2 -0
  69. package/dist/parsers/gsapSerialize.d.ts.map +1 -1
  70. package/dist/parsers/gsapSerialize.js +11 -8
  71. package/dist/parsers/gsapSerialize.js.map +1 -1
  72. package/dist/parsers/gsapWriterAcorn.d.ts +13 -0
  73. package/dist/parsers/gsapWriterAcorn.d.ts.map +1 -0
  74. package/dist/parsers/gsapWriterAcorn.js +384 -0
  75. package/dist/parsers/gsapWriterAcorn.js.map +1 -0
  76. package/dist/parsers/htmlParser.d.ts.map +1 -1
  77. package/dist/parsers/htmlParser.js +13 -18
  78. package/dist/parsers/htmlParser.js.map +1 -1
  79. package/dist/studio-api/helpers/sourceMutation.d.ts +1 -0
  80. package/dist/studio-api/helpers/sourceMutation.d.ts.map +1 -1
  81. package/dist/studio-api/helpers/sourceMutation.js +2 -2
  82. package/dist/studio-api/helpers/sourceMutation.js.map +1 -1
  83. package/dist/studio-api/routes/files.d.ts.map +1 -1
  84. package/dist/studio-api/routes/files.js +73 -56
  85. package/dist/studio-api/routes/files.js.map +1 -1
  86. package/dist/studio-api/routes/fonts.d.ts.map +1 -1
  87. package/dist/studio-api/routes/fonts.js +2 -47
  88. package/dist/studio-api/routes/fonts.js.map +1 -1
  89. package/package.json +13 -1
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import * as recast from "recast";
12
12
  import { parse as babelParse } from "@babel/parser";
13
+ import { serializeValue as valueToCode, safeJsKey as safeKey, } from "./gsapSerialize";
13
14
  export { serializeGsapAnimations, getAnimationsForElementId, validateCompositionGsap, keyframesToGsapAnimations, gsapAnimationsToKeyframes, SUPPORTED_PROPS, SUPPORTED_EASES, } from "./gsapSerialize";
14
15
  export { PROPERTY_GROUPS, classifyPropertyGroup, classifyTweenPropertyGroup, } from "./gsapConstants";
15
16
  import { classifyPropertyGroup, classifyTweenPropertyGroup } from "./gsapConstants";
@@ -166,15 +167,17 @@ function collectTargetBindings(ast, scope) {
166
167
  visitVariableDeclarator(path) {
167
168
  const name = path.node.id?.name;
168
169
  const selector = selectorFromQueryCall(path.node.init, scope);
169
- if (name && selector !== null)
170
- addBinding(bindings, enclosingScopeNode(path), name, selector);
170
+ const scopeNode = enclosingScopeNode(path);
171
+ if (name && selector !== null && scopeNode)
172
+ addBinding(bindings, scopeNode, name, selector);
171
173
  this.traverse(path);
172
174
  },
173
175
  visitAssignmentExpression(path) {
174
176
  const left = path.node.left;
175
177
  const selector = selectorFromQueryCall(path.node.right, scope);
176
- if (left?.type === "Identifier" && selector !== null) {
177
- addBinding(bindings, enclosingScopeNode(path), left.name, selector);
178
+ const scopeNode = enclosingScopeNode(path);
179
+ if (left?.type === "Identifier" && selector !== null && scopeNode) {
180
+ addBinding(bindings, scopeNode, left.name, selector);
178
181
  }
179
182
  this.traverse(path);
180
183
  },
@@ -782,7 +785,7 @@ function tweenCallToAnimation(call, scope) {
782
785
  }
783
786
  }
784
787
  const hasPositionArg = !!call.positionArg;
785
- const posVal = hasPositionArg ? extractLiteralValue(call.positionArg, scope) : 0;
788
+ const posVal = call.positionArg ? extractLiteralValue(call.positionArg, scope) : 0;
786
789
  const position = typeof posVal === "number" ? posVal : typeof posVal === "string" ? posVal : 0;
787
790
  let duration = typeof vars.duration === "number" ? vars.duration : undefined;
788
791
  const ease = typeof vars.ease === "string" ? vars.ease : undefined;
@@ -982,17 +985,6 @@ export function parseGsapScript(script) {
982
985
  // which preserves every untouched statement. This is what lets us edit tweens
983
986
  // in real compositions (variable targets, interleaved `gsap.set`, IIFE wrapper)
984
987
  // without regenerating — and discarding — the surrounding code.
985
- /** Render a model value to the JS source it should emit as. Mirrors gsapSerialize. */
986
- function valueToCode(value) {
987
- if (typeof value === "string" && value.startsWith("__raw:"))
988
- return value.slice(6);
989
- if (typeof value === "string")
990
- return JSON.stringify(value);
991
- return String(value);
992
- }
993
- function safeKey(key) {
994
- return /^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key);
995
- }
996
988
  /**
997
989
  * Parse a value/expression snippet into a standalone AST expression node.
998
990
  * Uses an assignment (`__hf__ = <code>`) rather than wrapping in parens so an
@@ -1058,24 +1050,26 @@ function reconcileEditableProperties(varsArg, newProps) {
1058
1050
  setVarsKey(varsArg, key, value);
1059
1051
  }
1060
1052
  }
1053
+ function applyEaseUpdate(varsArg, ease) {
1054
+ const kfNode = findKeyframesObjectNode(varsArg);
1055
+ if (kfNode) {
1056
+ setVarsKey(kfNode, "easeEach", ease);
1057
+ removeVarsKey(varsArg, "ease");
1058
+ }
1059
+ else {
1060
+ setVarsKey(varsArg, "ease", ease);
1061
+ }
1062
+ }
1061
1063
  function applyUpdatesToCall(call, updates) {
1062
1064
  if (updates.properties)
1063
1065
  reconcileEditableProperties(call.varsArg, updates.properties);
1064
- if (updates.fromProperties && call.method === "fromTo") {
1066
+ if (updates.fromProperties && call.method === "fromTo" && call.fromArg) {
1065
1067
  reconcileEditableProperties(call.fromArg, updates.fromProperties);
1066
1068
  }
1067
1069
  if (updates.duration !== undefined)
1068
1070
  setVarsKey(call.varsArg, "duration", updates.duration);
1069
- if (updates.ease !== undefined) {
1070
- const kfNode = findKeyframesObjectNode(call.varsArg);
1071
- if (kfNode) {
1072
- setVarsKey(kfNode, "easeEach", updates.ease);
1073
- removeVarsKey(call.varsArg, "ease");
1074
- }
1075
- else {
1076
- setVarsKey(call.varsArg, "ease", updates.ease);
1077
- }
1078
- }
1071
+ if (updates.ease !== undefined)
1072
+ applyEaseUpdate(call.varsArg, updates.ease);
1079
1073
  if (updates.position !== undefined) {
1080
1074
  const posIdx = call.method === "fromTo" ? 3 : 2;
1081
1075
  call.node.arguments[posIdx] = parseExpr(valueToCode(updates.position));
@@ -1091,6 +1085,18 @@ function findStatementPath(path) {
1091
1085
  }
1092
1086
  return null;
1093
1087
  }
1088
+ function insertAfterAnchor(parsed, newStatement) {
1089
+ const lastCall = parsed.located[parsed.located.length - 1]?.call;
1090
+ const anchorPath = lastCall
1091
+ ? findStatementPath(lastCall.path)
1092
+ : findTimelineDeclarationPath(parsed.ast, parsed.timelineVar);
1093
+ if (anchorPath) {
1094
+ anchorPath.insertAfter(newStatement);
1095
+ }
1096
+ else {
1097
+ parsed.ast.program.body.push(newStatement);
1098
+ }
1099
+ }
1094
1100
  /** Build the source for a single `tl.method(selector, vars, position)` call. */
1095
1101
  function buildTweenStatementCode(timelineVar, anim) {
1096
1102
  const selector = JSON.stringify(anim.targetSelector);
@@ -1130,6 +1136,55 @@ export function updateAnimationInScript(script, animationId, updates) {
1130
1136
  applyUpdatesToCall(target.call, updates);
1131
1137
  return recast.print(parsed.ast).code;
1132
1138
  }
1139
+ export function shiftPositionsInScript(script, targetSelector, delta) {
1140
+ let parsed;
1141
+ try {
1142
+ parsed = parseGsapAst(script);
1143
+ }
1144
+ catch (e) {
1145
+ console.warn("[gsap-parser] shiftPositionsInScript parse failed:", e);
1146
+ return script;
1147
+ }
1148
+ let changed = false;
1149
+ for (const entry of parsed.located) {
1150
+ if (entry.animation.targetSelector !== targetSelector)
1151
+ continue;
1152
+ if (typeof entry.animation.position !== "number")
1153
+ continue;
1154
+ const newPos = Math.max(0, Math.round((entry.animation.position + delta) * 1000) / 1000);
1155
+ applyUpdatesToCall(entry.call, { position: newPos });
1156
+ changed = true;
1157
+ }
1158
+ return changed ? recast.print(parsed.ast).code : script;
1159
+ }
1160
+ export function scalePositionsInScript(script, targetSelector, oldStart, oldDuration, newStart, newDuration) {
1161
+ if (oldDuration <= 0 || newDuration <= 0)
1162
+ return script;
1163
+ const ratio = newDuration / oldDuration;
1164
+ let parsed;
1165
+ try {
1166
+ parsed = parseGsapAst(script);
1167
+ }
1168
+ catch (e) {
1169
+ console.warn("[gsap-parser] scalePositionsInScript parse failed:", e);
1170
+ return script;
1171
+ }
1172
+ let changed = false;
1173
+ for (const entry of parsed.located) {
1174
+ if (entry.animation.targetSelector !== targetSelector)
1175
+ continue;
1176
+ if (typeof entry.animation.position !== "number")
1177
+ continue;
1178
+ const newPos = Math.max(0, Math.round((newStart + (entry.animation.position - oldStart) * ratio) * 1000) / 1000);
1179
+ const updates = { position: newPos };
1180
+ if (typeof entry.animation.duration === "number" && entry.animation.duration > 0) {
1181
+ updates.duration = Math.max(0.001, Math.round(entry.animation.duration * ratio * 1000) / 1000);
1182
+ }
1183
+ applyUpdatesToCall(entry.call, updates);
1184
+ changed = true;
1185
+ }
1186
+ return changed ? recast.print(parsed.ast).code : script;
1187
+ }
1133
1188
  function updateAnimationSelector(script, animationId, newSelector) {
1134
1189
  let parsed;
1135
1190
  try {
@@ -1166,16 +1221,7 @@ export function addAnimationToScript(script, animation) {
1166
1221
  const id = `anim-${Date.now()}`;
1167
1222
  const statementCode = buildTweenStatementCode(parsed.timelineVar, animation);
1168
1223
  const newStatement = parseScript(statementCode).program.body[0];
1169
- const lastCall = parsed.located[parsed.located.length - 1]?.call;
1170
- const anchorPath = lastCall
1171
- ? findStatementPath(lastCall.path)
1172
- : findTimelineDeclarationPath(parsed.ast, parsed.timelineVar);
1173
- if (anchorPath) {
1174
- anchorPath.insertAfter(newStatement);
1175
- }
1176
- else {
1177
- parsed.ast.program.body.push(newStatement);
1178
- }
1224
+ insertAfterAnchor(parsed, newStatement);
1179
1225
  return { script: recast.print(parsed.ast).code, id };
1180
1226
  }
1181
1227
  export function addAnimationWithKeyframesToScript(script, targetSelector, position, duration, keyframes, ease) {
@@ -1191,31 +1237,14 @@ export function addAnimationWithKeyframesToScript(script, targetSelector, positi
1191
1237
  return { script, id: "" };
1192
1238
  }
1193
1239
  const selector = JSON.stringify(targetSelector);
1194
- const kfEntries = keyframes.map((kf) => {
1195
- const propEntries = Object.entries(kf.properties).map(([k, v]) => `${safeKey(k)}: ${valueToCode(v)}`);
1196
- if (kf.ease)
1197
- propEntries.push(`ease: ${JSON.stringify(kf.ease)}`);
1198
- if (kf.auto)
1199
- propEntries.push(`_auto: 1`);
1200
- return `${JSON.stringify(`${kf.percentage}%`)}: { ${propEntries.join(", ")} }`;
1201
- });
1202
- const kfCode = `{ ${kfEntries.join(", ")} }`;
1240
+ const kfCode = buildKeyframeObjectCode(keyframes);
1203
1241
  const varEntries = [`keyframes: ${kfCode}`, `duration: ${valueToCode(duration)}`];
1204
1242
  if (ease)
1205
1243
  varEntries.push(`ease: ${JSON.stringify(ease)}`);
1206
1244
  const posCode = valueToCode(position);
1207
1245
  const stmtCode = `${parsed.timelineVar}.to(${selector}, { ${varEntries.join(", ")} }, ${posCode});`;
1208
1246
  const newStatement = parseScript(stmtCode).program.body[0];
1209
- const lastCall = parsed.located[parsed.located.length - 1]?.call;
1210
- const anchorPath = lastCall
1211
- ? findStatementPath(lastCall.path)
1212
- : findTimelineDeclarationPath(parsed.ast, parsed.timelineVar);
1213
- if (anchorPath) {
1214
- anchorPath.insertAfter(newStatement);
1215
- }
1216
- else {
1217
- parsed.ast.program.body.push(newStatement);
1218
- }
1247
+ insertAfterAnchor(parsed, newStatement);
1219
1248
  const result = recast.print(parsed.ast).code;
1220
1249
  const reParsed = parseGsapAst(result);
1221
1250
  const newId = reParsed.located[reParsed.located.length - 1]?.id ?? "";
@@ -1434,6 +1463,19 @@ function sortedKeyframes(kfs) {
1434
1463
  function keyframePropsToCode(kf) {
1435
1464
  return Object.entries(kf.properties).map(([k, v]) => `${safeKey(k)}: ${valueToCode(v)}`);
1436
1465
  }
1466
+ function buildKeyframeObjectCode(keyframes, options) {
1467
+ const entries = keyframes.map((kf) => {
1468
+ const props = keyframePropsToCode(kf);
1469
+ if (kf.ease)
1470
+ props.push(`ease: ${JSON.stringify(kf.ease)}`);
1471
+ if (kf.auto)
1472
+ props.push(`_auto: 1`);
1473
+ return `${JSON.stringify(`${kf.percentage}%`)}: { ${props.join(", ")} }`;
1474
+ });
1475
+ if (options?.easeEach)
1476
+ entries.push(`easeEach: ${JSON.stringify(options.easeEach)}`);
1477
+ return `{ ${entries.join(", ")} }`;
1478
+ }
1437
1479
  /** Remove a named property from an ObjectExpression's properties array. */
1438
1480
  function removeVarsKey(varsArg, key) {
1439
1481
  if (varsArg?.type !== "ObjectExpression")
@@ -1481,6 +1523,15 @@ function locateAnimation(script, animationId) {
1481
1523
  const target = parsed.located.find((l) => l.id === animationId);
1482
1524
  return target ? { parsed, target } : null;
1483
1525
  }
1526
+ function locateAnimationWithFallback(script, animationId) {
1527
+ const loc = locateAnimation(script, animationId);
1528
+ if (loc)
1529
+ return loc;
1530
+ const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
1531
+ if (convertedId === animationId)
1532
+ return null;
1533
+ return locateAnimation(script, convertedId);
1534
+ }
1484
1535
  /** Find the keyframes ObjectExpression node on a tween's varsArg, or null. */
1485
1536
  function findKeyframesObjectNode(varsArg) {
1486
1537
  const node = findPropertyNode(varsArg, "keyframes");
@@ -1516,11 +1567,7 @@ function collapseKeyframesToFlat(varsArg, record) {
1516
1567
  * updateKeyframeInScript.
1517
1568
  */
1518
1569
  function locateKeyframeCtx(script, animationId, percentage) {
1519
- let loc = locateAnimation(script, animationId);
1520
- if (!loc) {
1521
- const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
1522
- loc = locateAnimation(script, convertedId);
1523
- }
1570
+ const loc = locateAnimationWithFallback(script, animationId);
1524
1571
  if (!loc)
1525
1572
  return null;
1526
1573
  const kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
@@ -1533,21 +1580,13 @@ function locateKeyframeCtx(script, animationId, percentage) {
1533
1580
  * object. If the percentage already exists, its value is replaced.
1534
1581
  */
1535
1582
  export function addKeyframeToScript(script, animationId, percentage, properties, ease, backfillDefaults) {
1536
- let loc = locateAnimation(script, animationId);
1537
- if (!loc) {
1538
- const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
1539
- loc = locateAnimation(script, convertedId);
1540
- }
1583
+ let loc = locateAnimationWithFallback(script, animationId);
1541
1584
  if (!loc)
1542
1585
  return script;
1543
1586
  let kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
1544
1587
  if (!kfNode) {
1545
1588
  script = convertToKeyframesInScript(script, animationId);
1546
- loc = locateAnimation(script, animationId);
1547
- if (!loc) {
1548
- const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
1549
- loc = locateAnimation(script, convertedId);
1550
- }
1589
+ loc = locateAnimationWithFallback(script, animationId);
1551
1590
  if (!loc)
1552
1591
  return script;
1553
1592
  kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
@@ -1626,7 +1665,9 @@ export function addKeyframeToScript(script, animationId, percentage, properties,
1626
1665
  const valObj = prop.value;
1627
1666
  if (!valObj || valObj.type !== "ObjectExpression")
1628
1667
  continue;
1629
- const existingKeys = new Set(valObj.properties.filter((p) => isObjectProperty(p)).map((p) => propKeyName(p)));
1668
+ const existingKeys = new Set(valObj.properties
1669
+ .filter((p) => isObjectProperty(p))
1670
+ .map((p) => propKeyName(p)));
1630
1671
  for (const pk of newPropKeys) {
1631
1672
  if (existingKeys.has(pk))
1632
1673
  continue;
@@ -1761,11 +1802,7 @@ function insertKeyframesProp(varsArg, fromProps, toProps, easeEach) {
1761
1802
  * the "to" state for `from()` tweens (the values the DOM would resolve to).
1762
1803
  */
1763
1804
  export function convertToKeyframesInScript(script, animationId, resolvedFromValues) {
1764
- let loc = locateAnimation(script, animationId);
1765
- if (!loc) {
1766
- const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
1767
- loc = locateAnimation(script, convertedId);
1768
- }
1805
+ let loc = locateAnimationWithFallback(script, animationId);
1769
1806
  if (!loc)
1770
1807
  return script;
1771
1808
  const anim = loc.target.animation;
@@ -1792,11 +1829,7 @@ export function convertToKeyframesInScript(script, animationId, resolvedFromValu
1792
1829
  * last keyframe's properties.
1793
1830
  */
1794
1831
  export function removeAllKeyframesFromScript(script, animationId) {
1795
- let loc = locateAnimation(script, animationId);
1796
- if (!loc) {
1797
- const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
1798
- loc = locateAnimation(script, convertedId);
1799
- }
1832
+ let loc = locateAnimationWithFallback(script, animationId);
1800
1833
  if (!loc)
1801
1834
  return script;
1802
1835
  const kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
@@ -1821,11 +1854,7 @@ export function removeAllKeyframesFromScript(script, animationId) {
1821
1854
  * Called when the user first edits a dynamically-generated keyframe in the studio.
1822
1855
  */
1823
1856
  export function materializeKeyframesInScript(script, animationId, keyframes, easeEach, resolvedSelector) {
1824
- let loc = locateAnimation(script, animationId);
1825
- if (!loc) {
1826
- const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
1827
- loc = locateAnimation(script, convertedId);
1828
- }
1857
+ let loc = locateAnimationWithFallback(script, animationId);
1829
1858
  if (!loc)
1830
1859
  return script;
1831
1860
  const varsArg = loc.target.call.varsArg;
@@ -1833,17 +1862,7 @@ export function materializeKeyframesInScript(script, animationId, keyframes, eas
1833
1862
  if (resolvedSelector && loc.target.call.node.arguments[0]) {
1834
1863
  loc.target.call.node.arguments[0] = parseExpr(JSON.stringify(resolvedSelector));
1835
1864
  }
1836
- const entries = [];
1837
- for (const kf of sortedKeyframes(keyframes)) {
1838
- const propEntries = keyframePropsToCode(kf);
1839
- if (kf.ease)
1840
- propEntries.push(`ease: ${JSON.stringify(kf.ease)}`);
1841
- entries.push(`${JSON.stringify(kf.percentage + "%")}: { ${propEntries.join(", ")} }`);
1842
- }
1843
- if (easeEach) {
1844
- entries.push(`easeEach: ${JSON.stringify(easeEach)}`);
1845
- }
1846
- const kfObjCode = `{ ${entries.join(", ")} }`;
1865
+ const kfObjCode = buildKeyframeObjectCode(sortedKeyframes(keyframes), { easeEach });
1847
1866
  const kfParent = varsArg.properties.find((p) => isObjectProperty(p) && propKeyName(p) === "keyframes");
1848
1867
  if (kfParent) {
1849
1868
  kfParent.value = parseExpr(kfObjCode);
@@ -1856,6 +1875,25 @@ export function materializeKeyframesInScript(script, animationId, keyframes, eas
1856
1875
  return recast.print(loc.parsed.ast).code;
1857
1876
  }
1858
1877
  // ── Arc Path (motionPath) AST Mutations ──────────────────────────────────
1878
+ function numericXY(props) {
1879
+ const x = props.x;
1880
+ const y = props.y;
1881
+ return typeof x === "number" && typeof y === "number" ? { x, y } : null;
1882
+ }
1883
+ function extractArcWaypoints(anim) {
1884
+ const kfs = anim.keyframes?.keyframes ?? [];
1885
+ const waypoints = kfs.map((kf) => numericXY(kf.properties)).filter((p) => p !== null);
1886
+ if (waypoints.length >= 2)
1887
+ return waypoints;
1888
+ const px = anim.properties.x;
1889
+ const py = anim.properties.y;
1890
+ if (typeof px !== "number" && typeof py !== "number")
1891
+ return waypoints;
1892
+ return [
1893
+ { x: 0, y: 0 },
1894
+ { x: typeof px === "number" ? px : 0, y: typeof py === "number" ? py : 0 },
1895
+ ];
1896
+ }
1859
1897
  function buildMotionPathObjectCode(config) {
1860
1898
  const { waypoints, segments, autoRotate } = config;
1861
1899
  const hasExplicitControlPoints = segments.some((s) => s.cp1 && s.cp2);
@@ -1935,25 +1973,7 @@ export function setArcPathInScript(script, animationId, config) {
1935
1973
  removeVarsKey(varsArg, "motionPath");
1936
1974
  return recast.print(loc.parsed.ast).code;
1937
1975
  }
1938
- // Extract x/y waypoints from keyframes or flat tween properties
1939
- const kfs = anim.keyframes?.keyframes ?? [];
1940
- const waypoints = [];
1941
- for (const kf of kfs) {
1942
- const x = typeof kf.properties.x === "number" ? kf.properties.x : undefined;
1943
- const y = typeof kf.properties.y === "number" ? kf.properties.y : undefined;
1944
- if (x !== undefined && y !== undefined)
1945
- waypoints.push({ x, y });
1946
- }
1947
- // For flat tweens with x/y in properties, synthesize start → end waypoints
1948
- if (waypoints.length < 2) {
1949
- const px = anim.properties.x;
1950
- const py = anim.properties.y;
1951
- if (typeof px === "number" || typeof py === "number") {
1952
- waypoints.length = 0;
1953
- waypoints.push({ x: 0, y: 0 });
1954
- waypoints.push({ x: typeof px === "number" ? px : 0, y: typeof py === "number" ? py : 0 });
1955
- }
1956
- }
1976
+ const waypoints = extractArcWaypoints(anim);
1957
1977
  if (waypoints.length < 2)
1958
1978
  return script;
1959
1979
  // Build segments — use provided segments or create defaults
@@ -2003,15 +2023,7 @@ export function updateArcSegmentInScript(script, animationId, segmentIndex, upda
2003
2023
  if (segmentIndex < 0 || segmentIndex >= segments.length)
2004
2024
  return script;
2005
2025
  segments[segmentIndex] = { ...segments[segmentIndex], ...update };
2006
- // Rebuild the full motionPath with updated segments
2007
- const kfs = anim.keyframes?.keyframes ?? [];
2008
- const waypoints = [];
2009
- for (const kf of kfs) {
2010
- const x = typeof kf.properties.x === "number" ? kf.properties.x : undefined;
2011
- const y = typeof kf.properties.y === "number" ? kf.properties.y : undefined;
2012
- if (x !== undefined && y !== undefined)
2013
- waypoints.push({ x, y });
2014
- }
2026
+ const waypoints = extractArcWaypoints(anim);
2015
2027
  if (waypoints.length < 2)
2016
2028
  return script;
2017
2029
  const motionPathCode = buildMotionPathObjectCode({
@@ -2043,11 +2055,7 @@ export function removeArcPathFromScript(script, animationId) {
2043
2055
  */
2044
2056
  // fallow-ignore-next-line complexity
2045
2057
  export function splitIntoPropertyGroups(script, animationId) {
2046
- let loc = locateAnimation(script, animationId);
2047
- if (!loc) {
2048
- const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
2049
- loc = locateAnimation(script, convertedId);
2050
- }
2058
+ let loc = locateAnimationWithFallback(script, animationId);
2051
2059
  if (!loc)
2052
2060
  return { script, ids: [animationId] };
2053
2061
  const anim = loc.target.animation;
@@ -2208,15 +2216,10 @@ export function unrollDynamicAnimations(script, animationId, elements) {
2208
2216
  // Build replacement code: individual tl.to() calls for each element
2209
2217
  const calls = [];
2210
2218
  for (const el of elements) {
2211
- const kfEntries = [];
2212
- for (const kf of sortedKeyframes(el.keyframes)) {
2213
- const propEntries = keyframePropsToCode(kf);
2214
- kfEntries.push(`${JSON.stringify(kf.percentage + "%")}: { ${propEntries.join(", ")} }`);
2215
- }
2216
- if (el.easeEach) {
2217
- kfEntries.push(`easeEach: ${JSON.stringify(el.easeEach)}`);
2218
- }
2219
- calls.push(`${loc.parsed.timelineVar}.to(${JSON.stringify(el.selector)}, { keyframes: { ${kfEntries.join(", ")} }, duration: ${duration}, ease: ${JSON.stringify(ease)} }, ${posCode});`);
2219
+ const kfCode = buildKeyframeObjectCode(sortedKeyframes(el.keyframes), {
2220
+ easeEach: el.easeEach,
2221
+ });
2222
+ calls.push(`${loc.parsed.timelineVar}.to(${JSON.stringify(el.selector)}, { keyframes: ${kfCode}, duration: ${duration}, ease: ${JSON.stringify(ease)} }, ${posCode});`);
2220
2223
  }
2221
2224
  const replacement = calls.join("\n ");
2222
2225
  if (loopNode) {