@hyperframes/parsers 0.8.127 → 0.8.128

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.
@@ -7,6 +7,7 @@ import { K as Keyframe, V as ValidationResult } from './types-Ejqndkej.js';
7
7
  * without pulling in gsapParser (which depends on recast / @babel/parser).
8
8
  */
9
9
 
10
+ declare const GSAP_DEFAULT_DURATION = 0.5;
10
11
  declare const SUPPORTED_PROPS: string[];
11
12
  /** Keys stored on dedicated GsapAnimation fields (not in properties/extras). */
12
13
  declare const BUILTIN_VAR_KEYS: ReadonlySet<string>;
@@ -22,7 +23,7 @@ declare function isXYPositionWrite(a: PositionWrite): boolean;
22
23
  declare function classifyPropertyGroup(prop: string): PropertyGroupName;
23
24
  declare function classifyTweenPropertyGroup(properties: Record<string, unknown>): PropertyGroupName | undefined;
24
25
  /**
25
- * What a Studio hold pins from t=0 before a later keyframed tween: its first keyframe's position props,
26
+ * What a Studio hold pins from t=0 before a later keyframed tween: its 0% keyframe's position props,
26
27
  * minus those an earlier timeline tween on the target writes (a global `gsap.set` is a base value).
27
28
  */
28
29
  declare function positionHoldForAnimation(animation: GsapAnimation, animations: readonly GsapAnimation[]): Record<string, number> | null;
@@ -129,6 +130,7 @@ interface GsapKeyframesData<K extends GsapPercentageKeyframe = GsapPercentageKey
129
130
  keyframes: K[];
130
131
  ease?: string;
131
132
  easeEach?: string;
133
+ fromMotionPath?: true;
132
134
  }
133
135
  interface ArcPathSegment {
134
136
  curviness: number;
@@ -214,4 +216,4 @@ declare function gsapAnimationsToKeyframes(animations: GsapAnimation[], elementS
214
216
  skipBaseSet?: boolean;
215
217
  }): Keyframe[];
216
218
 
217
- export { type ArcPathConfig as A, BUILTIN_VAR_KEYS as B, DROPPED_VAR_KEYS as D, EXTRAS_KEYS as E, type GsapAnimation as G, type KeyframeEditability as K, type MotionPathShape as M, PROPERTY_GROUPS as P, SUPPORTED_EASES as S, type ArcPathSegment as a, type GsapKeyframesData as b, type GsapMethod as c, type GsapPercentageKeyframe as d, type GsapProvenance as e, type GsapProvenanceKind as f, type ParsedGsap as g, type PropertyGroupName as h, SUPPORTED_PROPS as i, type SourcedGsapPercentageKeyframe as j, type SplitAnimationsOptions as k, type SplitAnimationsResult as l, classifyPropertyGroup as m, classifyTweenPropertyGroup as n, editabilityForProvenance as o, getAnimationsForElementId as p, gsapAnimationsToKeyframes as q, keyframesToGsapAnimations as r, serializeGsapAnimations as s, buildArcPath as t, type GsapKeyframeFormat as u, validateCompositionGsap as v, isTweenConfigKey as w, isXYPositionWrite as x, positionHoldForAnimation as y };
219
+ export { type ArcPathConfig as A, BUILTIN_VAR_KEYS as B, DROPPED_VAR_KEYS as D, EXTRAS_KEYS as E, type GsapMethod as G, type KeyframeEditability as K, type MotionPathShape as M, type ParsedGsap as P, SUPPORTED_EASES as S, type GsapAnimation as a, type ArcPathSegment as b, type GsapProvenance as c, type GsapProvenanceKind as d, buildArcPath as e, editabilityForProvenance as f, type GsapKeyframeFormat as g, type GsapKeyframesData as h, type GsapPercentageKeyframe as i, PROPERTY_GROUPS as j, type PropertyGroupName as k, SUPPORTED_PROPS as l, classifyPropertyGroup as m, classifyTweenPropertyGroup as n, getAnimationsForElementId as o, gsapAnimationsToKeyframes as p, keyframesToGsapAnimations as q, type SplitAnimationsOptions as r, serializeGsapAnimations as s, type SplitAnimationsResult as t, GSAP_DEFAULT_DURATION as u, validateCompositionGsap as v, type SourcedGsapPercentageKeyframe as w, isTweenConfigKey as x, isXYPositionWrite as y, positionHoldForAnimation as z };
@@ -1,9 +1,9 @@
1
- import { G as GsapAnimation, A as ArcPathConfig, k as SplitAnimationsOptions, l as SplitAnimationsResult, a as ArcPathSegment } from './gsapSerialize-CFUDcLqX.js';
1
+ import { a as GsapAnimation, A as ArcPathConfig, r as SplitAnimationsOptions, t as SplitAnimationsResult, b as ArcPathSegment } from './gsapSerialize-CfSXM_Ez.js';
2
2
  import './types-Ejqndkej.js';
3
3
 
4
4
  type TweenTarget = Pick<GsapAnimation, "targetSelector" | "hasPartialSelector">;
5
5
  type TweenTime = Pick<GsapAnimation, "position" | "implicitPosition">;
6
- declare function clipTweenMatcher(clipSelectors: string | readonly string[], root?: ParentNode): (tween: TweenTarget) => boolean;
6
+ declare function clipTweenMatcher(clipSelectors: string | readonly string[], root?: ParentNode, queries?: Map<string, Element[]>): (tween: TweenTarget) => boolean;
7
7
  /** Where a GSAP script's clips live: linkedom keeps a `<template>`'s children under it, out of document queries. */
8
8
  declare function clipQueryRoot(script: Element): ParentNode;
9
9
  /** A written position; an implicit one follows the tween before it and must stay unwritten. */
@@ -21,6 +21,26 @@ declare function updateAnimationInScript(script: string, animationId: string, up
21
21
  * (used by timeline clip-move to keep GSAP positions in sync with the clip start).
22
22
  */
23
23
  declare function shiftPositionsInScript(script: string, targetSelector: string, delta: number, root?: ParentNode): string;
24
+ type ClipTweenRetime = {
25
+ kind: "shift";
26
+ targetSelector: string;
27
+ delta: number;
28
+ } | {
29
+ kind: "scale";
30
+ targetSelector: string;
31
+ oldStart: number;
32
+ oldDuration: number;
33
+ newStart: number;
34
+ newDuration: number;
35
+ };
36
+ /**
37
+ * Applies `retimes` in order with one parse: the bytes equal running shift/scalePositionsInScript once
38
+ * per retime. `changed[i]` says whether retime i moved a value.
39
+ */
40
+ declare function retimeClipTweensInScript(script: string, retimes: readonly ClipTweenRetime[], root?: ParentNode): {
41
+ script: string;
42
+ changed: boolean[];
43
+ };
24
44
  /** Copies each tween on `fromSelector` for `toSelector`, `delta` seconds later, in its own argument text. Exact or
25
45
  * absent: only a tween at a number, written straight in the timeline's own block, is copied. */
26
46
  declare function copyAnimationsInScript(script: string, fromSelector: string, toSelector: string, delta: number): string;
@@ -177,4 +197,4 @@ type UnrollElement = {
177
197
  */
178
198
  declare function unrollDynamicAnimations(script: string, animationId: string, elements: UnrollElement[]): string;
179
199
 
180
- export { type UnrollElement, addAnimationToScript, addAnimationWithKeyframesToScript, addKeyframeToScript, addLabelToScript, addMotionPathPointInScript, addMotionPathToScript, clipQueryRoot, clipTweenMatcher, convertToKeyframesFromScript, copyAnimationsInScript, dedupePositionWritesInScript, hasExplicitTime, materializeKeyframesFromScript, moveKeyframeInScript, removeAllKeyframesFromScript, removeAnimationFromScript, removeArcPathFromScript, removeKeyframeFromScript, removeLabelFromScript, removeMotionPathPointInScript, removePropertyFromAnimation, replaceTweenWithKeyframesInScript, resizeKeyframedTweenInScript, scalePositionsInScript, setArcPathInScript, shiftPositionsInScript, splitAnimationsInScript, splitIntoPropertyGroupsFromScript, syncPositionHoldsBeforeKeyframes, unrollDynamicAnimations, updateAnimationInScript, updateArcSegmentInScript, updateKeyframeInScript, updateMotionPathPointInScript };
200
+ export { type ClipTweenRetime, type UnrollElement, addAnimationToScript, addAnimationWithKeyframesToScript, addKeyframeToScript, addLabelToScript, addMotionPathPointInScript, addMotionPathToScript, clipQueryRoot, clipTweenMatcher, convertToKeyframesFromScript, copyAnimationsInScript, dedupePositionWritesInScript, hasExplicitTime, materializeKeyframesFromScript, moveKeyframeInScript, removeAllKeyframesFromScript, removeAnimationFromScript, removeArcPathFromScript, removeKeyframeFromScript, removeLabelFromScript, removeMotionPathPointInScript, removePropertyFromAnimation, replaceTweenWithKeyframesInScript, resizeKeyframedTweenInScript, retimeClipTweensInScript, scalePositionsInScript, setArcPathInScript, shiftPositionsInScript, splitAnimationsInScript, splitIntoPropertyGroupsFromScript, syncPositionHoldsBeforeKeyframes, unrollDynamicAnimations, updateAnimationInScript, updateArcSegmentInScript, updateKeyframeInScript, updateMotionPathPointInScript };
@@ -2,6 +2,7 @@
2
2
  import MagicString from "magic-string";
3
3
 
4
4
  // src/gsapConstants.ts
5
+ var GSAP_DEFAULT_DURATION = 0.5;
5
6
  var BUILTIN_VAR_KEYS = /* @__PURE__ */ new Set(["duration", "ease", "delay"]);
6
7
  var DROPPED_VAR_KEYS = /* @__PURE__ */ new Set([
7
8
  "onComplete",
@@ -60,16 +61,14 @@ function positionHoldForAnimation(animation, animations) {
60
61
  if (!animation.keyframes) return null;
61
62
  const start = knownStart(animation) ?? 0;
62
63
  if (!(start > 1e-3)) return null;
63
- const first = [...animation.keyframes.keyframes].sort(
64
- (left, right) => left.percentage - right.percentage
65
- )[0];
66
- if (!first) return null;
64
+ const atStart = animation.keyframes.keyframes.find((keyframe) => keyframe.percentage === 0);
65
+ if (!atStart) return null;
67
66
  const earlier = animations.filter((other) => {
68
67
  const otherStart = knownStart(other);
69
68
  return other !== animation && !other.global && other.targetSelector === animation.targetSelector && otherStart !== void 0 && otherStart < start - 1e-3;
70
69
  });
71
70
  const position = {};
72
- for (const [property, value] of Object.entries(first.properties)) {
71
+ for (const [property, value] of Object.entries(atStart.properties)) {
73
72
  if (classifyPropertyGroup(property) !== "position" || typeof value !== "number") continue;
74
73
  if (earlier.some((other) => writesProperty(other, property))) continue;
75
74
  position[property] = value;
@@ -78,6 +77,9 @@ function positionHoldForAnimation(animation, animations) {
78
77
  }
79
78
 
80
79
  // src/gsapSerialize.ts
80
+ function authorsKeyframes(anim) {
81
+ return anim.keyframes !== void 0 || anim.hasUnresolvedKeyframes === true;
82
+ }
81
83
  function mergePercentageKeyframes(keyframes) {
82
84
  const byPercentage = /* @__PURE__ */ new Map();
83
85
  for (const keyframe of keyframes) {
@@ -229,34 +231,27 @@ function readProvenance(node) {
229
231
  var roundPercentage = (percentage) => Math.round(percentage * 10) / 10;
230
232
  var OBJECT_ARRAY_PERCENTAGE_TOLERANCE = 2;
231
233
  function getObjectArrayKeyframeTiming(durations) {
232
- const hasAuthoredDuration = durations.some((duration) => duration !== void 0);
233
- if (hasAuthoredDuration) {
234
- if (!durations.every(
235
- (duration) => typeof duration === "number" && Number.isFinite(duration) && duration > 0
236
- )) {
237
- return null;
238
- }
239
- const totalDuration = durations.reduce((sum, duration) => sum + duration, 0);
240
- let cumulative = 0;
241
- return {
242
- percentages: durations.map((duration) => {
243
- cumulative += duration;
244
- return roundPercentage(cumulative / totalDuration * 100);
245
- }),
246
- totalDuration
247
- };
234
+ const steps = durations.map((duration) => duration ?? GSAP_DEFAULT_DURATION);
235
+ if (!steps.every(
236
+ (duration) => typeof duration === "number" && Number.isFinite(duration) && duration > 0
237
+ )) {
238
+ return null;
248
239
  }
249
- const lastIndex = durations.length - 1;
240
+ const totalDuration = steps.reduce((sum, duration) => sum + duration, 0);
241
+ let cumulative = 0;
250
242
  return {
251
- percentages: durations.map(
252
- (_, index) => lastIndex > 0 ? roundPercentage(index / lastIndex * 100) : 0
253
- )
243
+ percentages: steps.map((duration) => {
244
+ cumulative += duration;
245
+ return roundPercentage(cumulative / totalDuration * 100);
246
+ }),
247
+ totalDuration
254
248
  };
255
249
  }
256
250
  function getCompatibleObjectArrayKeyframeTiming(durations, outerDuration) {
257
251
  const timing = getObjectArrayKeyframeTiming(durations);
258
252
  if (!timing) return null;
259
- if (timing.totalDuration === void 0 || outerDuration === void 0) return timing;
253
+ const stepsAuthorDuration = durations.some((duration) => duration !== void 0);
254
+ if (!stepsAuthorDuration || outerDuration === void 0) return timing;
260
255
  if (typeof outerDuration === "number" && Math.abs(outerDuration - timing.totalDuration) <= Number.EPSILON) {
261
256
  return timing;
262
257
  }
@@ -852,6 +847,7 @@ function parsePercentageKeyframes(node, scope, source) {
852
847
  if (typeof key !== "string") continue;
853
848
  const pctMatch = PERCENTAGE_KEY_RE.exec(key);
854
849
  if (pctMatch) {
850
+ if (!isPlainStep(prop.value)) return void 0;
855
851
  const percentage = Number.parseFloat(pctMatch[1] ?? "0");
856
852
  const record = objectExpressionToRecord(prop.value, scope, source);
857
853
  const properties = {};
@@ -870,6 +866,8 @@ function parsePercentageKeyframes(node, scope, source) {
870
866
  ease = tryResolveStringProp(prop.value, scope, source) ?? ease;
871
867
  } else if (key === "easeEach") {
872
868
  easeEach = tryResolveStringProp(prop.value, scope, source) ?? easeEach;
869
+ } else {
870
+ return void 0;
873
871
  }
874
872
  }
875
873
  keyframes.sort((a, b) => a.percentage - b.percentage);
@@ -893,12 +891,19 @@ function computeKeyframesTotalDuration(varsNode, scope, source) {
893
891
  }
894
892
  return getObjectArrayKeyframeTiming(durations)?.totalDuration;
895
893
  }
896
- var isFullyReadableStep = (el) => el?.type === "ObjectExpression" && !el.properties.some((p) => p.type === "SpreadElement");
894
+ function staticKeyName(prop) {
895
+ if (prop.type !== "ObjectProperty" && prop.type !== "Property") return void 0;
896
+ if (prop.method || prop.kind && prop.kind !== "init") return void 0;
897
+ const key = prop.computed ? prop.key?.type === "Literal" && prop.key.value : prop.key?.name ?? prop.key?.value;
898
+ return typeof key === "string" ? key : void 0;
899
+ }
900
+ var isPlainObject = (node) => node?.type === "ObjectExpression" && (node.properties ?? []).every((p) => staticKeyName(p) !== void 0);
901
+ var isPlainStep = (node) => node?.type === "Literal" || isPlainObject(node);
897
902
  function parseObjectArrayKeyframes(node, scope, source) {
898
903
  const elements = node.elements ?? [];
899
904
  const raw = [];
900
905
  for (const el of elements) {
901
- if (!isFullyReadableStep(el)) return void 0;
906
+ if (!isPlainObject(el)) return void 0;
902
907
  const record = objectExpressionToRecord(el, scope, source);
903
908
  const properties = {};
904
909
  let duration;
@@ -937,9 +942,8 @@ function parseSimpleArrayKeyframes(node, scope) {
937
942
  const values = [];
938
943
  for (const el of prop.value.elements ?? []) {
939
944
  const val = resolveNode(el, scope);
940
- if (typeof val === "number" || typeof val === "string") {
941
- values.push(val);
942
- }
945
+ if (typeof val !== "number" && typeof val !== "string") return void 0;
946
+ values.push(val);
943
947
  }
944
948
  if (values.length > 0) arrayProps.set(key, values);
945
949
  } else if (key === "ease") {
@@ -972,6 +976,7 @@ function parseKeyframesNode(node, scope, source) {
972
976
  }
973
977
  if (node.type !== "ObjectExpression") return void 0;
974
978
  const props = node.properties ?? [];
979
+ if (!props.every((p) => staticKeyName(p) !== void 0)) return void 0;
975
980
  let hasPercentageKey = false;
976
981
  let hasArrayValue = false;
977
982
  for (const prop of props) {
@@ -1072,7 +1077,7 @@ function tweenCallToAnimation(call, scope, source, identifierBindings) {
1072
1077
  percentage: waypoints.length > 1 ? Math.round(i / (waypoints.length - 1) * 100) : 0,
1073
1078
  properties: { x: wp.x, y: wp.y }
1074
1079
  }));
1075
- keyframesData = { format: "percentage", keyframes: kf };
1080
+ keyframesData = { format: "percentage", keyframes: kf, fromMotionPath: true };
1076
1081
  } else {
1077
1082
  const kfs = keyframesData.keyframes;
1078
1083
  if (kfs.length === waypoints.length) {
@@ -1102,10 +1107,10 @@ function tweenCallToAnimation(call, scope, source, identifierBindings) {
1102
1107
  const position = typeof posVal === "number" ? posVal : typeof posVal === "string" ? posVal : hasPositionArg ? `__raw:${source.slice(call.positionArg.start, call.positionArg.end)}` : 0;
1103
1108
  let duration = typeof vars.duration === "number" ? vars.duration : void 0;
1104
1109
  const ease = typeof vars.ease === "string" ? vars.ease : void 0;
1105
- if (duration === void 0 && keyframesData) {
1110
+ const durationUnresolved = call.method !== "set" && duration === void 0 && hasUnknownDuration(call.varsArg, scope);
1111
+ if (duration === void 0 && keyframesData && !durationUnresolved) {
1106
1112
  duration = computeKeyframesTotalDuration(call.varsArg, scope, source);
1107
1113
  }
1108
- const durationUnresolved = call.method !== "set" && duration === void 0 && hasUnknownDuration(call.varsArg, scope);
1109
1114
  let selector = call.selector;
1110
1115
  let targetIdentity;
1111
1116
  if (selector === "__unresolved__") {
@@ -1159,7 +1164,6 @@ function tweenCallToAnimation(call, scope, source, identifierBindings) {
1159
1164
  if (provenance) anim.provenance = provenance;
1160
1165
  return anim;
1161
1166
  }
1162
- var GSAP_DEFAULT_DURATION = 0.5;
1163
1167
  function resolvePositionString(pos, cursor, prevStart) {
1164
1168
  const trimmed = pos.trim();
1165
1169
  if (trimmed === "") return cursor;
@@ -1192,7 +1196,7 @@ function applyTimelineDefaults(anims, defaults) {
1192
1196
  if (defaults.duration !== void 0) anim.duration = defaults.duration;
1193
1197
  else if (defaults.durationUnresolved) anim.durationUnresolved = true;
1194
1198
  }
1195
- if (anim.ease === void 0 && defaults.ease !== void 0 && !anim.keyframes) {
1199
+ if (anim.ease === void 0 && defaults.ease !== void 0 && (!anim.keyframes || anim.keyframes.fromMotionPath)) {
1196
1200
  anim.ease = defaults.ease;
1197
1201
  }
1198
1202
  }
@@ -1361,14 +1365,25 @@ function parseGsapScriptAcornForWrite(script) {
1361
1365
  import * as acornWalk2 from "acorn-walk";
1362
1366
 
1363
1367
  // src/clipTweens.ts
1364
- function clipTweenMatcher(clipSelectors, root) {
1368
+ function clipTweenMatcher(clipSelectors, root, queries = /* @__PURE__ */ new Map()) {
1365
1369
  const own = typeof clipSelectors === "string" ? [clipSelectors] : clipSelectors;
1366
- const clips = root ? own.flatMap((selector) => queryAll(root, selector)) : [];
1370
+ if (!root)
1371
+ return ({ targetSelector, hasPartialSelector }) => !hasPartialSelector && own.includes(targetSelector);
1372
+ const queryAll = (selector) => {
1373
+ if (ID_SELECTOR.test(selector)) {
1374
+ if (!queries.has(ALL_IDS)) indexIds(root, queries);
1375
+ return queries.get(selector) ?? [];
1376
+ }
1377
+ let found = queries.get(selector);
1378
+ if (!found) queries.set(selector, found = queryRoot(root, selector));
1379
+ return found;
1380
+ };
1381
+ const clips = own.flatMap((selector) => queryAll(selector));
1367
1382
  return ({ targetSelector, hasPartialSelector }) => {
1368
1383
  if (hasPartialSelector) return false;
1369
1384
  if (own.includes(targetSelector)) return true;
1370
- if (!root || clips.length === 0) return false;
1371
- const targets = queryAll(root, targetSelector);
1385
+ if (clips.length === 0) return false;
1386
+ const targets = queryAll(targetSelector);
1372
1387
  return targets.length > 0 && targets.every((target) => {
1373
1388
  const owner = target.closest("[data-start]");
1374
1389
  return clips.includes(target) || owner !== null && clips.includes(owner);
@@ -1381,7 +1396,20 @@ function clipQueryRoot(script) {
1381
1396
  function hasExplicitTime(animation) {
1382
1397
  return typeof animation.position === "number" && !animation.implicitPosition;
1383
1398
  }
1384
- function queryAll(root, selector) {
1399
+ var ID_SELECTOR = /^#-?[A-Za-z_][\w-]*$/;
1400
+ var ALL_IDS = "[id]";
1401
+ function indexIds(root, queries) {
1402
+ const all = queryRoot(root, ALL_IDS);
1403
+ queries.set(ALL_IDS, all);
1404
+ for (const el of all) {
1405
+ const key = `#${el.id}`;
1406
+ if (!ID_SELECTOR.test(key)) continue;
1407
+ const list = queries.get(key);
1408
+ if (list) list.push(el);
1409
+ else queries.set(key, [el]);
1410
+ }
1411
+ }
1412
+ function queryRoot(root, selector) {
1385
1413
  try {
1386
1414
  return Array.from(root.querySelectorAll(selector));
1387
1415
  } catch {
@@ -1557,7 +1585,7 @@ function updateAnimationInScript(script, animationId, updates) {
1557
1585
  const parsed = parseGsapScriptAcornForWrite(script);
1558
1586
  if (!parsed) return script;
1559
1587
  const target = parsed.located.find((l) => l.id === animationId);
1560
- if (!target) return script;
1588
+ if (!target || updates.properties && target.animation.hasUnresolvedKeyframes) return script;
1561
1589
  const ms = new MagicString(script);
1562
1590
  const { call } = target;
1563
1591
  if (updates.properties) {
@@ -1621,17 +1649,54 @@ function overwritePosition(ms, call, position) {
1621
1649
  }
1622
1650
  }
1623
1651
  function shiftPositionsInScript(script, targetSelector, delta, root) {
1624
- const parsed = parseGsapScriptAcornForWrite(script);
1625
- if (!parsed) return script;
1626
- const carries = clipTweenMatcher(targetSelector, root);
1652
+ return retimeClipTweensInScript(script, [{ kind: "shift", targetSelector, delta }], root).script;
1653
+ }
1654
+ var isLiveRetime = (r) => r.kind === "shift" || r.oldDuration > 0 && r.newDuration > 0;
1655
+ function applyRetime(animation, retime) {
1656
+ if (retime.kind === "shift") {
1657
+ if (!hasExplicitTime(animation)) return { position: false, duration: false };
1658
+ animation.position = shiftedPosition(animation.position, retime.delta);
1659
+ return { position: true, duration: false };
1660
+ }
1661
+ const touched = { position: false, duration: false };
1662
+ if (!isLiveRetime(retime) || typeof animation.position !== "number") return touched;
1663
+ const ratio = retime.newDuration / retime.oldDuration;
1664
+ if (hasExplicitTime(animation)) {
1665
+ const scaled = retime.newStart + (animation.position - retime.oldStart) * ratio;
1666
+ animation.position = Math.max(0, Math.round(scaled * 1e3) / 1e3);
1667
+ touched.position = true;
1668
+ }
1669
+ if (typeof animation.duration === "number" && animation.duration > 0) {
1670
+ animation.duration = Math.max(1e-3, Math.round(animation.duration * ratio * 1e3) / 1e3);
1671
+ touched.duration = true;
1672
+ }
1673
+ return touched;
1674
+ }
1675
+ function retimeClipTweensInScript(script, retimes, root) {
1676
+ const changed = retimes.map(() => false);
1677
+ const parsed = retimes.some(isLiveRetime) ? parseGsapScriptAcornForWrite(script) : null;
1678
+ if (!parsed) return { script, changed };
1679
+ const queries = /* @__PURE__ */ new Map();
1680
+ const matchers = retimes.map((r) => clipTweenMatcher(r.targetSelector, root, queries));
1627
1681
  const ms = new MagicString(script);
1628
- let changed = false;
1682
+ let wrote = false;
1629
1683
  for (const entry of parsed.located) {
1630
- if (!carries(entry.animation) || !hasExplicitTime(entry.animation)) continue;
1631
- overwritePosition(ms, entry.call, shiftedPosition(entry.animation.position, delta));
1632
- changed = true;
1684
+ const animation = { ...entry.animation };
1685
+ let position = false;
1686
+ let duration = false;
1687
+ retimes.forEach((retime, i) => {
1688
+ if (!matchers[i](animation)) return;
1689
+ const before = [animation.position, animation.duration];
1690
+ const touched = applyRetime(animation, retime);
1691
+ position ||= touched.position;
1692
+ duration ||= touched.duration;
1693
+ if (before[0] !== animation.position || before[1] !== animation.duration) changed[i] = true;
1694
+ });
1695
+ if (position) overwritePosition(ms, entry.call, animation.position);
1696
+ if (duration) upsertProp(ms, entry.call.varsArg, "duration", animation.duration);
1697
+ wrote ||= position || duration;
1633
1698
  }
1634
- return changed ? ms.toString() : script;
1699
+ return { script: wrote ? ms.toString() : script, changed };
1635
1700
  }
1636
1701
  function copyAnimationsInScript(script, fromSelector, toSelector, delta) {
1637
1702
  const parsed = parseGsapScriptAcornForWrite(script);
@@ -1703,29 +1768,11 @@ function isLoopOrForEach(node) {
1703
1768
  return isLoopNode(node) || isForEachStatement(node) || node?.type === "DoWhileStatement";
1704
1769
  }
1705
1770
  function scalePositionsInScript(script, targetSelector, oldStart, oldDuration, newStart, newDuration, root) {
1706
- if (oldDuration <= 0 || newDuration <= 0) return script;
1707
- const ratio = newDuration / oldDuration;
1708
- const parsed = parseGsapScriptAcornForWrite(script);
1709
- if (!parsed) return script;
1710
- const carries = clipTweenMatcher(targetSelector, root);
1711
- const ms = new MagicString(script);
1712
- let changed = false;
1713
- for (const entry of parsed.located) {
1714
- if (!carries(entry.animation) || typeof entry.animation.position !== "number") continue;
1715
- if (hasExplicitTime(entry.animation)) {
1716
- const newPos = Math.max(
1717
- 0,
1718
- Math.round((newStart + (entry.animation.position - oldStart) * ratio) * 1e3) / 1e3
1719
- );
1720
- overwritePosition(ms, entry.call, newPos);
1721
- }
1722
- if (typeof entry.animation.duration === "number" && entry.animation.duration > 0) {
1723
- const newDur = Math.max(1e-3, Math.round(entry.animation.duration * ratio * 1e3) / 1e3);
1724
- upsertProp(ms, entry.call.varsArg, "duration", newDur);
1725
- }
1726
- changed = true;
1727
- }
1728
- return changed ? ms.toString() : script;
1771
+ return retimeClipTweensInScript(
1772
+ script,
1773
+ [{ kind: "scale", targetSelector, oldStart, oldDuration, newStart, newDuration }],
1774
+ root
1775
+ ).script;
1729
1776
  }
1730
1777
  function addAnimationToScript(script, animation) {
1731
1778
  const parsed = parseGsapScriptAcornForWrite(script);
@@ -1999,7 +2046,7 @@ function updateKeyframeInScript(script, animationId, percentage, properties, eas
1999
2046
  const parsed = parseGsapScriptAcornForWrite(script);
2000
2047
  if (!parsed) return script;
2001
2048
  const target = parsed.located.find((l) => l.id === animationId);
2002
- if (!target) return script;
2049
+ if (!target || target.animation.hasUnresolvedKeyframes) return script;
2003
2050
  const kfPropNode = findPropertyNode2(target.call.varsArg, "keyframes");
2004
2051
  if (!kfPropNode) {
2005
2052
  return updateMotionPathKeyframe(script, target, percentage, properties, ease) ?? script;
@@ -2067,7 +2114,7 @@ function locateWithKeyframes(script, animationId) {
2067
2114
  if (!parsed) return null;
2068
2115
  const convertedId = animationId.replace(/-from-|-fromTo-/, "-to-");
2069
2116
  const target = parsed.located.find((l) => l.id === animationId) ?? parsed.located.find((l) => l.id === convertedId);
2070
- if (!target) return null;
2117
+ if (!target || target.animation.hasUnresolvedKeyframes) return null;
2071
2118
  const kfPropNode = findPropertyNode2(target.call.varsArg, "keyframes");
2072
2119
  if (!kfPropNode || kfPropNode.value?.type !== "ObjectExpression") return null;
2073
2120
  return { script, parsed, target, kfNode: kfPropNode.value };
@@ -2091,8 +2138,12 @@ function convertArrayKeyframesToObject(script, target) {
2091
2138
  return `${JSON.stringify(`${timing.percentages[i]}%`)}: ${recordToCode(record)}`;
2092
2139
  });
2093
2140
  const ms = new MagicString(script);
2094
- ms.overwrite(kfPropNode.value.start, kfPropNode.value.end, `{ ${entries.join(", ")} }`);
2095
- if (timing.totalDuration !== void 0 && findPropertyNode2(target.call.varsArg, "duration") === void 0) {
2141
+ ms.overwrite(
2142
+ kfPropNode.value.start,
2143
+ kfPropNode.value.end,
2144
+ `{ ${entries.join(", ")}, easeEach: "none" }`
2145
+ );
2146
+ if (findPropertyNode2(target.call.varsArg, "duration") === void 0) {
2096
2147
  upsertProp(ms, target.call.varsArg, "duration", timing.totalDuration);
2097
2148
  }
2098
2149
  return ms.toString();
@@ -2102,7 +2153,7 @@ function ensureKeyframesNode(script, animationId) {
2102
2153
  if (direct) return direct;
2103
2154
  const parsed = parseGsapScriptAcornForWrite(script);
2104
2155
  const target = parsed?.located.find((l) => l.id === animationId);
2105
- if (!target) return null;
2156
+ if (!target || target.animation.hasUnresolvedKeyframes) return null;
2106
2157
  const kfProp = findPropertyNode2(target.call.varsArg, "keyframes");
2107
2158
  if (kfProp?.value?.type === "ArrayExpression") {
2108
2159
  const normalized = convertArrayKeyframesToObject(script, target);
@@ -2204,7 +2255,7 @@ function removeKeyframeFromScript(script, animationId, percentage) {
2204
2255
  const parsed = parseGsapScriptAcornForWrite(script);
2205
2256
  if (!parsed) return script;
2206
2257
  const target = parsed.located.find((l) => l.id === animationId);
2207
- if (!target) return script;
2258
+ if (!target || target.animation.hasUnresolvedKeyframes) return script;
2208
2259
  const kfPropNode = findPropertyNode2(target.call.varsArg, "keyframes");
2209
2260
  if (!kfPropNode) return script;
2210
2261
  if (kfPropNode.value?.type === "ArrayExpression") {
@@ -2343,7 +2394,7 @@ function convertToKeyframesFromScript(script, animationId, resolvedFromValues, s
2343
2394
  const target = parsed.located.find((l) => l.id === animationId);
2344
2395
  if (!target) return script;
2345
2396
  const { animation, call } = target;
2346
- if (animation.keyframes) return script;
2397
+ if (authorsKeyframes(animation)) return script;
2347
2398
  const isSet = call.method === "set";
2348
2399
  const { fromProps, toProps } = resolveConversionProps(animation, resolvedFromValues);
2349
2400
  const ms = new MagicString(script);
@@ -2559,6 +2610,7 @@ function splitIntoPropertyGroupsFromScript(script, animationId) {
2559
2610
  const target = parsed.located.find((l) => l.id === animationId);
2560
2611
  if (!target) return { script, ids: [animationId] };
2561
2612
  const { animation } = target;
2613
+ if (animation.hasUnresolvedKeyframes) return { script, ids: [animationId] };
2562
2614
  const allPropKeys = collectPropertyKeys(animation);
2563
2615
  const groupProps = partitionPropertyGroups(allPropKeys);
2564
2616
  if (groupProps.size <= 1) return { script, ids: [animationId] };
@@ -2879,8 +2931,8 @@ function computeForwardBaselines(matching, splitTime) {
2879
2931
  const pos = typeof anim.position === "number" ? anim.position : 0;
2880
2932
  const dur = anim.duration ?? 0;
2881
2933
  const animEnd = pos + dur;
2882
- if (anim.keyframes) {
2883
- const kfs = anim.keyframes.keyframes;
2934
+ if (authorsKeyframes(anim)) {
2935
+ const kfs = anim.keyframes?.keyframes ?? [];
2884
2936
  if (pos >= splitTime) {
2885
2937
  } else if (animEnd > splitTime) {
2886
2938
  for (const kf of kfs) {
@@ -2941,17 +2993,22 @@ function buildSpanningSplit(result, anim, pos, dur, fromSource, ctx) {
2941
2993
  extras: anim.extras
2942
2994
  }).script;
2943
2995
  }
2996
+ function applyKeyframedTweenSplit(result, anim, pos, animEnd, ctx, skippedSelectors) {
2997
+ if (pos >= ctx.splitTime)
2998
+ return updateAnimationSelectorInScript(result, anim.id, ctx.newSelector);
2999
+ if (animEnd > ctx.splitTime) {
3000
+ skippedSelectors.push(`${ctx.originalSelector} (keyframes spanning split)`);
3001
+ } else if (anim.hasUnresolvedKeyframes) {
3002
+ skippedSelectors.push(`${ctx.originalSelector} (unreadable keyframes before split)`);
3003
+ }
3004
+ return result;
3005
+ }
2944
3006
  function applyTweenSplit(result, anim, baselineBefore, ctx, skippedSelectors) {
2945
3007
  const pos = typeof anim.position === "number" ? anim.position : 0;
2946
3008
  const dur = anim.duration ?? 0;
2947
3009
  const animEnd = pos + dur;
2948
- if (anim.keyframes) {
2949
- if (pos >= ctx.splitTime)
2950
- return updateAnimationSelectorInScript(result, anim.id, ctx.newSelector);
2951
- if (animEnd > ctx.splitTime) {
2952
- skippedSelectors.push(`${ctx.originalSelector} (keyframes spanning split)`);
2953
- }
2954
- return result;
3010
+ if (authorsKeyframes(anim)) {
3011
+ return applyKeyframedTweenSplit(result, anim, pos, animEnd, ctx, skippedSelectors);
2955
3012
  }
2956
3013
  if (animEnd <= ctx.splitTime) return result;
2957
3014
  if (pos >= ctx.splitTime)
@@ -3180,6 +3237,7 @@ export {
3180
3237
  removePropertyFromAnimation,
3181
3238
  replaceTweenWithKeyframesInScript,
3182
3239
  resizeKeyframedTweenInScript,
3240
+ retimeClipTweensInScript,
3183
3241
  scalePositionsInScript,
3184
3242
  setArcPathInScript,
3185
3243
  shiftPositionsInScript,