@hyperframes/core 0.6.86 → 0.6.87

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.
@@ -1 +1 @@
1
- {"version":3,"file":"gsapConstants.d.ts","sourceRoot":"","sources":["../../src/parsers/gsapConstants.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,eAAe,UAsC3B,CAAC;AAEF,eAAO,MAAM,eAAe,UA+B3B,CAAC"}
1
+ {"version":3,"file":"gsapConstants.d.ts","sourceRoot":"","sources":["../../src/parsers/gsapConstants.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,eAAe,UAsC3B,CAAC;AAEF,eAAO,MAAM,eAAe,UAgC3B,CAAC"}
@@ -74,5 +74,6 @@ export const SUPPORTED_EASES = [
74
74
  "spring-stiff",
75
75
  "spring-wobbly",
76
76
  "spring-heavy",
77
+ "steps(1)",
77
78
  ];
78
79
  //# sourceMappingURL=gsapConstants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"gsapConstants.js","sourceRoot":"","sources":["../../src/parsers/gsapConstants.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,gBAAgB;IAChB,GAAG;IACH,GAAG;IACH,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,OAAO;IACP,OAAO;IACP,gBAAgB;IAChB,GAAG;IACH,WAAW;IACX,WAAW;IACX,WAAW;IACX,aAAa;IACb,iBAAiB;IACjB,aAAa;IACb,SAAS;IACT,YAAY;IACZ,WAAW;IACX,aAAa;IACb,OAAO;IACP,QAAQ;IACR,SAAS;IACT,OAAO;IACP,iBAAiB;IACjB,aAAa;IACb,YAAY;IACZ,cAAc;IACd,aAAa;IACb,UAAU;IACV,eAAe;IACf,oBAAoB;IACpB,QAAQ;IACR,UAAU;IACV,+CAA+C;IAC/C,WAAW;CACZ,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,MAAM;IACN,WAAW;IACX,YAAY;IACZ,cAAc;IACd,WAAW;IACX,YAAY;IACZ,cAAc;IACd,WAAW;IACX,YAAY;IACZ,cAAc;IACd,WAAW;IACX,YAAY;IACZ,cAAc;IACd,SAAS;IACT,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,eAAe;IACf,WAAW;IACX,YAAY;IACZ,cAAc;IACd,SAAS;IACT,UAAU;IACV,YAAY;IACZ,eAAe;IACf,eAAe;IACf,cAAc;IACd,eAAe;IACf,cAAc;CACf,CAAC"}
1
+ {"version":3,"file":"gsapConstants.js","sourceRoot":"","sources":["../../src/parsers/gsapConstants.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,gBAAgB;IAChB,GAAG;IACH,GAAG;IACH,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,OAAO;IACP,OAAO;IACP,gBAAgB;IAChB,GAAG;IACH,WAAW;IACX,WAAW;IACX,WAAW;IACX,aAAa;IACb,iBAAiB;IACjB,aAAa;IACb,SAAS;IACT,YAAY;IACZ,WAAW;IACX,aAAa;IACb,OAAO;IACP,QAAQ;IACR,SAAS;IACT,OAAO;IACP,iBAAiB;IACjB,aAAa;IACb,YAAY;IACZ,cAAc;IACd,aAAa;IACb,UAAU;IACV,eAAe;IACf,oBAAoB;IACpB,QAAQ;IACR,UAAU;IACV,+CAA+C;IAC/C,WAAW;CACZ,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,MAAM;IACN,WAAW;IACX,YAAY;IACZ,cAAc;IACd,WAAW;IACX,YAAY;IACZ,cAAc;IACd,WAAW;IACX,YAAY;IACZ,cAAc;IACd,WAAW;IACX,YAAY;IACZ,cAAc;IACd,SAAS;IACT,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,eAAe;IACf,WAAW;IACX,YAAY;IACZ,cAAc;IACd,SAAS;IACT,UAAU;IACV,YAAY;IACZ,eAAe;IACf,eAAe;IACf,cAAc;IACd,eAAe;IACf,cAAc;IACd,UAAU;CACX,CAAC"}
@@ -1,5 +1,5 @@
1
- import { type GsapAnimation, type ParsedGsap } from "./gsapSerialize";
2
- export type { GsapAnimation, GsapMethod, ParsedGsap, GsapKeyframesData, GsapPercentageKeyframe, GsapKeyframeFormat, } from "./gsapSerialize";
1
+ import { type ArcPathConfig, type ArcPathSegment, type GsapAnimation, type ParsedGsap } from "./gsapSerialize";
2
+ export type { ArcPathConfig, ArcPathSegment, GsapAnimation, GsapMethod, ParsedGsap, GsapKeyframesData, GsapPercentageKeyframe, GsapKeyframeFormat, } from "./gsapSerialize";
3
3
  export { serializeGsapAnimations, getAnimationsForElementId, validateCompositionGsap, keyframesToGsapAnimations, gsapAnimationsToKeyframes, SUPPORTED_PROPS, SUPPORTED_EASES, } from "./gsapSerialize";
4
4
  export { generateSpringEaseData, SPRING_PRESETS } from "./springEase";
5
5
  export type { SpringPreset } from "./springEase";
@@ -9,6 +9,15 @@ export declare function addAnimationToScript(script: string, animation: Omit<Gsa
9
9
  script: string;
10
10
  id: string;
11
11
  };
12
+ export declare function addAnimationWithKeyframesToScript(script: string, targetSelector: string, position: number, duration: number, keyframes: Array<{
13
+ percentage: number;
14
+ properties: Record<string, number | string>;
15
+ ease?: string;
16
+ auto?: boolean;
17
+ }>, ease?: string): {
18
+ script: string;
19
+ id: string;
20
+ };
12
21
  export declare function removeAnimationFromScript(script: string, animationId: string): string;
13
22
  /**
14
23
  * Insert a keyframe at the given percentage in an existing percentage-keyframes
@@ -45,6 +54,9 @@ export declare function materializeKeyframesInScript(script: string, animationId
45
54
  properties: Record<string, number | string>;
46
55
  ease?: string;
47
56
  }>, easeEach?: string, resolvedSelector?: string): string;
57
+ export declare function setArcPathInScript(script: string, animationId: string, config: ArcPathConfig): string;
58
+ export declare function updateArcSegmentInScript(script: string, animationId: string, segmentIndex: number, update: Partial<ArcPathSegment>): string;
59
+ export declare function removeArcPathFromScript(script: string, animationId: string): string;
48
60
  /**
49
61
  * Replace a dynamic loop that generates multiple tween calls with individual
50
62
  * static `tl.to()` calls — one per element. Finds the loop containing the
@@ -1 +1 @@
1
- {"version":3,"file":"gsapParser.d.ts","sourceRoot":"","sources":["../../src/parsers/gsapParser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gsapParser.d.ts","sourceRoot":"","sources":["../../src/parsers/gsapParser.ts"],"names":[],"mappings":"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"}
@@ -587,6 +587,81 @@ function parseSimpleArrayKeyframes(node, scope) {
587
587
  ...(easeEach ? { easeEach } : {}),
588
588
  };
589
589
  }
590
+ function parseMotionPathNode(node, scope) {
591
+ if (!node)
592
+ return undefined;
593
+ let pathNode;
594
+ let autoRotate = false;
595
+ let curviness = 1;
596
+ let isCubic = false;
597
+ if (node.type === "ObjectExpression") {
598
+ for (const prop of node.properties ?? []) {
599
+ if (!isObjectProperty(prop))
600
+ continue;
601
+ const key = propKeyName(prop);
602
+ if (key === "path")
603
+ pathNode = prop.value;
604
+ else if (key === "autoRotate") {
605
+ const val = resolveNode(prop.value, scope);
606
+ autoRotate = typeof val === "number" ? val : val === true;
607
+ }
608
+ else if (key === "curviness") {
609
+ const val = resolveNode(prop.value, scope);
610
+ if (typeof val === "number")
611
+ curviness = val;
612
+ }
613
+ else if (key === "type") {
614
+ const val = resolveNode(prop.value, scope);
615
+ if (val === "cubic")
616
+ isCubic = true;
617
+ }
618
+ }
619
+ }
620
+ else if (node.type === "ArrayExpression") {
621
+ pathNode = node;
622
+ }
623
+ if (!pathNode || pathNode.type !== "ArrayExpression")
624
+ return undefined;
625
+ const elements = pathNode.elements ?? [];
626
+ const coords = [];
627
+ for (const elem of elements) {
628
+ if (!elem || elem.type !== "ObjectExpression")
629
+ continue;
630
+ const rec = objectExpressionToRecord(elem, scope);
631
+ const x = typeof rec.x === "number" ? rec.x : undefined;
632
+ const y = typeof rec.y === "number" ? rec.y : undefined;
633
+ if (x !== undefined && y !== undefined)
634
+ coords.push({ x, y });
635
+ }
636
+ if (coords.length < 2)
637
+ return undefined;
638
+ let waypoints;
639
+ const segments = [];
640
+ if (isCubic && coords.length >= 4) {
641
+ // type: "cubic" — coords are [anchor, cp1, cp2, anchor, cp1, cp2, anchor, ...]
642
+ // Every 3rd coord starting from 0 is an anchor, the two between are control points.
643
+ waypoints = [];
644
+ waypoints.push(coords[0]);
645
+ for (let i = 1; i + 2 < coords.length; i += 3) {
646
+ const cp1 = coords[i];
647
+ const cp2 = coords[i + 1];
648
+ const anchor = coords[i + 2];
649
+ waypoints.push(anchor);
650
+ segments.push({ curviness, cp1, cp2 });
651
+ }
652
+ }
653
+ else {
654
+ // Waypoint array with global curviness
655
+ waypoints = coords;
656
+ for (let i = 0; i < waypoints.length - 1; i++) {
657
+ segments.push({ curviness });
658
+ }
659
+ }
660
+ return {
661
+ arcPath: { enabled: true, autoRotate, segments },
662
+ waypoints,
663
+ };
664
+ }
590
665
  // fallow-ignore-next-line complexity
591
666
  function tweenCallToAnimation(call, scope) {
592
667
  const vars = objectExpressionToRecord(call.varsArg, scope);
@@ -594,6 +669,7 @@ function tweenCallToAnimation(call, scope) {
594
669
  const extras = {};
595
670
  let keyframesData;
596
671
  let hasUnresolvedKeyframes = false;
672
+ let motionPathResult;
597
673
  for (const [key, val] of Object.entries(vars)) {
598
674
  if (BUILTIN_VAR_KEYS.has(key))
599
675
  continue;
@@ -606,6 +682,11 @@ function tweenCallToAnimation(call, scope) {
606
682
  hasUnresolvedKeyframes = true;
607
683
  continue;
608
684
  }
685
+ if (key === "motionPath") {
686
+ const mpNode = findPropertyNode(call.varsArg, "motionPath");
687
+ motionPathResult = parseMotionPathNode(mpNode, scope);
688
+ continue;
689
+ }
609
690
  if (key === "easeEach") {
610
691
  // easeEach is only meaningful alongside keyframes — handled below.
611
692
  continue;
@@ -630,6 +711,30 @@ function tweenCallToAnimation(call, scope) {
630
711
  if (keyframesData && typeof vars.easeEach === "string") {
631
712
  keyframesData.easeEach = vars.easeEach;
632
713
  }
714
+ // When motionPath is present, reconstruct x/y as keyframe waypoints.
715
+ if (motionPathResult) {
716
+ const { waypoints } = motionPathResult;
717
+ if (!keyframesData) {
718
+ // No explicit keyframes — create synthetic percentage keyframes from waypoints.
719
+ const kf = waypoints.map((wp, i) => ({
720
+ percentage: waypoints.length > 1 ? Math.round((i / (waypoints.length - 1)) * 100) : 0,
721
+ properties: { x: wp.x, y: wp.y },
722
+ }));
723
+ keyframesData = { format: "percentage", keyframes: kf };
724
+ }
725
+ else {
726
+ // Merge waypoint positions into existing keyframes at matching percentages.
727
+ // If keyframe count matches waypoint count, assign positionally.
728
+ const kfs = keyframesData.keyframes;
729
+ if (kfs.length === waypoints.length) {
730
+ for (let i = 0; i < kfs.length; i++) {
731
+ kfs[i].properties.x = waypoints[i].x;
732
+ kfs[i].properties.y = waypoints[i].y;
733
+ }
734
+ }
735
+ }
736
+ // arcPath is attached below on the animation result.
737
+ }
633
738
  let fromProperties;
634
739
  if (call.method === "fromTo" && call.fromArg) {
635
740
  fromProperties = {};
@@ -657,6 +762,8 @@ function tweenCallToAnimation(call, scope) {
657
762
  anim.extras = extras;
658
763
  if (keyframesData)
659
764
  anim.keyframes = keyframesData;
765
+ if (motionPathResult)
766
+ anim.arcPath = motionPathResult.arcPath;
660
767
  if (hasUnresolvedKeyframes)
661
768
  anim.hasUnresolvedKeyframes = true;
662
769
  if (call.selector === "__unresolved__")
@@ -809,8 +916,16 @@ function applyUpdatesToCall(call, updates) {
809
916
  }
810
917
  if (updates.duration !== undefined)
811
918
  setVarsKey(call.varsArg, "duration", updates.duration);
812
- if (updates.ease !== undefined)
813
- setVarsKey(call.varsArg, "ease", updates.ease);
919
+ if (updates.ease !== undefined) {
920
+ const kfNode = findKeyframesObjectNode(call.varsArg);
921
+ if (kfNode) {
922
+ setVarsKey(kfNode, "easeEach", updates.ease);
923
+ removeVarsKey(call.varsArg, "ease");
924
+ }
925
+ else {
926
+ setVarsKey(call.varsArg, "ease", updates.ease);
927
+ }
928
+ }
814
929
  if (updates.position !== undefined) {
815
930
  const posIdx = call.method === "fromTo" ? 3 : 2;
816
931
  call.node.arguments[posIdx] = parseExpr(valueToCode(updates.position));
@@ -893,6 +1008,49 @@ export function addAnimationToScript(script, animation) {
893
1008
  }
894
1009
  return { script: recast.print(parsed.ast).code, id };
895
1010
  }
1011
+ export function addAnimationWithKeyframesToScript(script, targetSelector, position, duration, keyframes, ease) {
1012
+ let parsed;
1013
+ try {
1014
+ parsed = parseGsapAst(script);
1015
+ }
1016
+ catch (e) {
1017
+ console.warn("[gsap-parser] addAnimationWithKeyframesToScript parse failed:", e);
1018
+ return { script, id: "" };
1019
+ }
1020
+ if (parsed.located.length === 0 && parsed.detection.timelineVar === null) {
1021
+ return { script, id: "" };
1022
+ }
1023
+ const selector = JSON.stringify(targetSelector);
1024
+ const kfEntries = keyframes.map((kf) => {
1025
+ const propEntries = Object.entries(kf.properties).map(([k, v]) => `${safeKey(k)}: ${valueToCode(v)}`);
1026
+ if (kf.ease)
1027
+ propEntries.push(`ease: ${JSON.stringify(kf.ease)}`);
1028
+ if (kf.auto)
1029
+ propEntries.push(`_auto: 1`);
1030
+ return `${JSON.stringify(`${kf.percentage}%`)}: { ${propEntries.join(", ")} }`;
1031
+ });
1032
+ const kfCode = `{ ${kfEntries.join(", ")} }`;
1033
+ const varEntries = [`keyframes: ${kfCode}`, `duration: ${valueToCode(duration)}`];
1034
+ if (ease)
1035
+ varEntries.push(`ease: ${JSON.stringify(ease)}`);
1036
+ const posCode = valueToCode(position);
1037
+ const stmtCode = `${parsed.timelineVar}.to(${selector}, { ${varEntries.join(", ")} }, ${posCode});`;
1038
+ const newStatement = parseScript(stmtCode).program.body[0];
1039
+ const lastCall = parsed.located[parsed.located.length - 1]?.call;
1040
+ const anchorPath = lastCall
1041
+ ? findStatementPath(lastCall.path)
1042
+ : findTimelineDeclarationPath(parsed.ast, parsed.timelineVar);
1043
+ if (anchorPath) {
1044
+ anchorPath.insertAfter(newStatement);
1045
+ }
1046
+ else {
1047
+ parsed.ast.program.body.push(newStatement);
1048
+ }
1049
+ const result = recast.print(parsed.ast).code;
1050
+ const reParsed = parseGsapAst(result);
1051
+ const newId = reParsed.located[reParsed.located.length - 1]?.id ?? "";
1052
+ return { script: result, id: newId };
1053
+ }
896
1054
  /** Find the statement path of `const <timelineVar> = gsap.timeline(...)`. */
897
1055
  function findTimelineDeclarationPath(ast, timelineVar) {
898
1056
  let found = null;
@@ -1023,12 +1181,27 @@ function collapseKeyframesToFlat(varsArg, record) {
1023
1181
  * object. If the percentage already exists, its value is replaced.
1024
1182
  */
1025
1183
  export function addKeyframeToScript(script, animationId, percentage, properties, ease, backfillDefaults) {
1026
- const loc = locateAnimation(script, animationId);
1184
+ let loc = locateAnimation(script, animationId);
1185
+ if (!loc) {
1186
+ const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
1187
+ loc = locateAnimation(script, convertedId);
1188
+ }
1027
1189
  if (!loc)
1028
1190
  return script;
1029
- const kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
1030
- if (!kfNode)
1031
- return script;
1191
+ let kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
1192
+ if (!kfNode) {
1193
+ script = convertToKeyframesInScript(script, animationId);
1194
+ loc = locateAnimation(script, animationId);
1195
+ if (!loc) {
1196
+ const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
1197
+ loc = locateAnimation(script, convertedId);
1198
+ }
1199
+ if (!loc)
1200
+ return script;
1201
+ kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
1202
+ if (!kfNode)
1203
+ return script;
1204
+ }
1032
1205
  const pctKey = `${percentage}%`;
1033
1206
  const newValueNode = buildKeyframeValueNode(properties, ease);
1034
1207
  // Replace if this percentage already exists
@@ -1053,6 +1226,21 @@ export function addKeyframeToScript(script, animationId, percentage, properties,
1053
1226
  }
1054
1227
  kfNode.properties.splice(insertIdx, 0, newProp);
1055
1228
  }
1229
+ // Auto-update 100%: if the 100% keyframe still has `_auto: 1` (never
1230
+ // explicitly edited by the user), update it to match the new keyframe's
1231
+ // values so the element holds its final position instead of snapping back.
1232
+ // Once the user drags at 100%, `_auto` is gone and we stop touching it.
1233
+ if (percentage < 100 && percentage !== 0) {
1234
+ const pctProps = filterPercentageProps(kfNode);
1235
+ const hundredProp = pctProps.find((p) => percentageFromKey(propKeyName(p) ?? "") === 100);
1236
+ if (hundredProp?.value?.type === "ObjectExpression") {
1237
+ const hasAuto = hundredProp.value.properties.some((p) => isObjectProperty(p) && propKeyName(p) === "_auto");
1238
+ if (hasAuto) {
1239
+ const updatedProps = { ...properties, _auto: 1 };
1240
+ hundredProp.value = buildKeyframeValueNode(updatedProps, undefined);
1241
+ }
1242
+ }
1243
+ }
1056
1244
  // Backfill: when the new keyframe introduces properties absent from other
1057
1245
  // keyframes, add default values so GSAP can interpolate them.
1058
1246
  if (backfillDefaults) {
@@ -1271,6 +1459,184 @@ export function materializeKeyframesInScript(script, animationId, keyframes, eas
1271
1459
  removeVarsKey(varsArg, "easeEach");
1272
1460
  return recast.print(loc.parsed.ast).code;
1273
1461
  }
1462
+ // ── Arc Path (motionPath) AST Mutations ──────────────────────────────────
1463
+ function buildMotionPathObjectCode(config) {
1464
+ const { waypoints, segments, autoRotate } = config;
1465
+ const hasExplicitControlPoints = segments.some((s) => s.cp1 && s.cp2);
1466
+ let pathEntries;
1467
+ if (hasExplicitControlPoints && waypoints.length >= 2) {
1468
+ // type: "cubic" — interleave control points: [anchor, cp1, cp2, anchor, ...]
1469
+ pathEntries = [`{x: ${waypoints[0].x}, y: ${waypoints[0].y}}`];
1470
+ for (let i = 0; i < segments.length; i++) {
1471
+ const seg = segments[i];
1472
+ const nextWp = waypoints[i + 1];
1473
+ if (seg.cp1 && seg.cp2) {
1474
+ pathEntries.push(`{x: ${seg.cp1.x}, y: ${seg.cp1.y}}`);
1475
+ pathEntries.push(`{x: ${seg.cp2.x}, y: ${seg.cp2.y}}`);
1476
+ }
1477
+ else {
1478
+ // Auto-generate simple midpoint control points from curviness
1479
+ const wp = waypoints[i];
1480
+ const dx = nextWp.x - wp.x;
1481
+ const dy = nextWp.y - wp.y;
1482
+ const c = seg.curviness ?? 1;
1483
+ pathEntries.push(`{x: ${wp.x + dx * 0.33}, y: ${wp.y + dy * 0.33 - c * Math.abs(dx) * 0.25}}`);
1484
+ pathEntries.push(`{x: ${wp.x + dx * 0.66}, y: ${wp.y + dy * 0.66 - c * Math.abs(dx) * 0.25}}`);
1485
+ }
1486
+ pathEntries.push(`{x: ${nextWp.x}, y: ${nextWp.y}}`);
1487
+ }
1488
+ const pathStr = pathEntries.join(", ");
1489
+ const parts = [`path: [${pathStr}]`, `type: "cubic"`];
1490
+ if (autoRotate === true)
1491
+ parts.push("autoRotate: true");
1492
+ else if (typeof autoRotate === "number")
1493
+ parts.push(`autoRotate: ${autoRotate}`);
1494
+ return `{ ${parts.join(", ")} }`;
1495
+ }
1496
+ // Simple waypoint array with curviness
1497
+ pathEntries = waypoints.map((wp) => `{x: ${wp.x}, y: ${wp.y}}`);
1498
+ const curviness = segments[0]?.curviness ?? 1;
1499
+ const parts = [`path: [${pathEntries.join(", ")}]`];
1500
+ if (curviness !== 1)
1501
+ parts.push(`curviness: ${curviness}`);
1502
+ if (autoRotate === true)
1503
+ parts.push("autoRotate: true");
1504
+ else if (typeof autoRotate === "number")
1505
+ parts.push(`autoRotate: ${autoRotate}`);
1506
+ return `{ ${parts.join(", ")} }`;
1507
+ }
1508
+ export function setArcPathInScript(script, animationId, config) {
1509
+ const loc = locateAnimation(script, animationId);
1510
+ if (!loc)
1511
+ return script;
1512
+ const varsArg = loc.target.call.varsArg;
1513
+ const anim = loc.target.animation;
1514
+ if (!config.enabled) {
1515
+ // Disable arc: restore x/y from motionPath's last waypoint, then remove motionPath
1516
+ const motionPathProp = varsArg.properties.find((p) => isObjectProperty(p) && propKeyName(p) === "motionPath");
1517
+ if (motionPathProp) {
1518
+ const mpVal = motionPathProp.value;
1519
+ let pathArr;
1520
+ if (mpVal?.type === "ObjectExpression") {
1521
+ const pathProp = mpVal.properties.find((p) => isObjectProperty(p) && propKeyName(p) === "path");
1522
+ if (pathProp?.value?.type === "ArrayExpression")
1523
+ pathArr = pathProp.value.elements;
1524
+ }
1525
+ if (pathArr && pathArr.length > 0) {
1526
+ const last = pathArr[pathArr.length - 1];
1527
+ if (last?.type === "ObjectExpression") {
1528
+ for (const p of last.properties) {
1529
+ const k = propKeyName(p);
1530
+ if (k === "x" || k === "y") {
1531
+ const v = p.value?.value;
1532
+ if (typeof v === "number")
1533
+ setVarsKey(varsArg, k, v);
1534
+ }
1535
+ }
1536
+ }
1537
+ }
1538
+ }
1539
+ removeVarsKey(varsArg, "motionPath");
1540
+ return recast.print(loc.parsed.ast).code;
1541
+ }
1542
+ // Extract x/y waypoints from keyframes or flat tween properties
1543
+ const kfs = anim.keyframes?.keyframes ?? [];
1544
+ const waypoints = [];
1545
+ for (const kf of kfs) {
1546
+ const x = typeof kf.properties.x === "number" ? kf.properties.x : undefined;
1547
+ const y = typeof kf.properties.y === "number" ? kf.properties.y : undefined;
1548
+ if (x !== undefined && y !== undefined)
1549
+ waypoints.push({ x, y });
1550
+ }
1551
+ // For flat tweens with x/y in properties, synthesize start → end waypoints
1552
+ if (waypoints.length < 2) {
1553
+ const px = anim.properties.x;
1554
+ const py = anim.properties.y;
1555
+ if (typeof px === "number" || typeof py === "number") {
1556
+ waypoints.length = 0;
1557
+ waypoints.push({ x: 0, y: 0 });
1558
+ waypoints.push({ x: typeof px === "number" ? px : 0, y: typeof py === "number" ? py : 0 });
1559
+ }
1560
+ }
1561
+ if (waypoints.length < 2)
1562
+ return script;
1563
+ // Build segments — use provided segments or create defaults
1564
+ const segments = config.segments.length === waypoints.length - 1
1565
+ ? config.segments
1566
+ : Array.from({ length: waypoints.length - 1 }, () => ({ curviness: 1 }));
1567
+ const motionPathCode = buildMotionPathObjectCode({
1568
+ waypoints,
1569
+ segments,
1570
+ autoRotate: config.autoRotate,
1571
+ });
1572
+ // Set motionPath on the vars
1573
+ const motionPathNode = parseExpr(motionPathCode);
1574
+ const existingProp = varsArg.properties.find((p) => isObjectProperty(p) && propKeyName(p) === "motionPath");
1575
+ if (existingProp) {
1576
+ existingProp.value = motionPathNode;
1577
+ }
1578
+ else {
1579
+ const prop = parseExpr(`{ motionPath: ${motionPathCode} }`).properties[0];
1580
+ varsArg.properties.push(prop);
1581
+ }
1582
+ // Strip x/y from keyframes (they're now in motionPath)
1583
+ const kfNode = findKeyframesObjectNode(varsArg);
1584
+ if (kfNode) {
1585
+ for (const pctProp of filterPercentageProps(kfNode)) {
1586
+ if (pctProp.value?.type === "ObjectExpression") {
1587
+ pctProp.value.properties = pctProp.value.properties.filter((p) => {
1588
+ const k = propKeyName(p);
1589
+ return k !== "x" && k !== "y";
1590
+ });
1591
+ }
1592
+ }
1593
+ }
1594
+ // Strip flat x/y from vars (they're now in motionPath)
1595
+ removeVarsKey(varsArg, "x");
1596
+ removeVarsKey(varsArg, "y");
1597
+ return recast.print(loc.parsed.ast).code;
1598
+ }
1599
+ export function updateArcSegmentInScript(script, animationId, segmentIndex, update) {
1600
+ const loc = locateAnimation(script, animationId);
1601
+ if (!loc)
1602
+ return script;
1603
+ const anim = loc.target.animation;
1604
+ if (!anim.arcPath?.enabled)
1605
+ return script;
1606
+ const segments = [...anim.arcPath.segments];
1607
+ if (segmentIndex < 0 || segmentIndex >= segments.length)
1608
+ return script;
1609
+ segments[segmentIndex] = { ...segments[segmentIndex], ...update };
1610
+ // Rebuild the full motionPath with updated segments
1611
+ const kfs = anim.keyframes?.keyframes ?? [];
1612
+ const waypoints = [];
1613
+ for (const kf of kfs) {
1614
+ const x = typeof kf.properties.x === "number" ? kf.properties.x : undefined;
1615
+ const y = typeof kf.properties.y === "number" ? kf.properties.y : undefined;
1616
+ if (x !== undefined && y !== undefined)
1617
+ waypoints.push({ x, y });
1618
+ }
1619
+ if (waypoints.length < 2)
1620
+ return script;
1621
+ const motionPathCode = buildMotionPathObjectCode({
1622
+ waypoints,
1623
+ segments,
1624
+ autoRotate: anim.arcPath.autoRotate,
1625
+ });
1626
+ const varsArg = loc.target.call.varsArg;
1627
+ const existingProp = varsArg.properties.find((p) => isObjectProperty(p) && propKeyName(p) === "motionPath");
1628
+ if (existingProp) {
1629
+ existingProp.value = parseExpr(motionPathCode);
1630
+ }
1631
+ return recast.print(loc.parsed.ast).code;
1632
+ }
1633
+ export function removeArcPathFromScript(script, animationId) {
1634
+ return setArcPathInScript(script, animationId, {
1635
+ enabled: false,
1636
+ autoRotate: false,
1637
+ segments: [],
1638
+ });
1639
+ }
1274
1640
  /**
1275
1641
  * Replace a dynamic loop that generates multiple tween calls with individual
1276
1642
  * static `tl.to()` calls — one per element. Finds the loop containing the