@hyperframes/parsers 0.8.133 → 0.8.135

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.
@@ -22,11 +22,17 @@ type PositionWrite = Pick<GsapAnimation, "propertyGroup" | "properties" | "fromP
22
22
  declare function isXYPositionWrite(a: PositionWrite): boolean;
23
23
  declare function classifyPropertyGroup(prop: string): PropertyGroupName;
24
24
  declare function classifyTweenPropertyGroup(properties: Record<string, unknown>): PropertyGroupName | undefined;
25
+ interface HoldScope {
26
+ touched: (animation: GsapAnimation) => boolean;
27
+ held: (selector: string) => ReadonlyMap<string, unknown>;
28
+ }
29
+ /** The tweens an edit touched (not in `previous`; none without it), and what the script's holds already pin. */
30
+ declare function holdScope(before: readonly GsapAnimation[], previous: readonly GsapAnimation[] | null): HoldScope;
25
31
  /**
26
- * What a Studio hold pins from t=0 before a later keyframed tween: its 0% keyframe's position props,
27
- * minus those an earlier timeline tween on the target writes (a global `gsap.set` is a base value).
32
+ * What a Studio hold pins from t=0: a keyframed tween's 0% position props before a later start, and the position and
33
+ * size of a lone key (GSAP renders none) only where this edit made it or a hold already pins it, minus earlier tweens.
28
34
  */
29
- declare function positionHoldForAnimation(animation: GsapAnimation, animations: readonly GsapAnimation[]): Record<string, number> | null;
35
+ declare function keyframeHoldForAnimation(animation: GsapAnimation, animations: readonly GsapAnimation[], scope: HoldScope): Record<string, number> | null;
30
36
  declare const SUPPORTED_EASES: string[];
31
37
 
32
38
  /**
@@ -216,4 +222,4 @@ declare function gsapAnimationsToKeyframes(animations: GsapAnimation[], elementS
216
222
  skipBaseSet?: boolean;
217
223
  }): Keyframe[];
218
224
 
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 };
225
+ export { type ArcPathConfig as A, BUILTIN_VAR_KEYS as B, keyframeHoldForAnimation as C, DROPPED_VAR_KEYS as D, EXTRAS_KEYS as E, type GsapAnimation as G, type HoldScope as H, type KeyframeEditability as K, type MotionPathShape as M, type ParsedGsap as P, SUPPORTED_EASES as S, type ArcPathSegment as a, type GsapKeyframeFormat as b, type GsapKeyframesData as c, type GsapMethod as d, type GsapPercentageKeyframe as e, PROPERTY_GROUPS as f, type PropertyGroupName as g, SUPPORTED_PROPS as h, classifyPropertyGroup as i, classifyTweenPropertyGroup as j, getAnimationsForElementId as k, gsapAnimationsToKeyframes as l, keyframesToGsapAnimations as m, type GsapProvenance as n, type GsapProvenanceKind as o, buildArcPath as p, editabilityForProvenance 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, holdScope as x, isTweenConfigKey as y, isXYPositionWrite as z };
@@ -1,4 +1,4 @@
1
- import { a as GsapAnimation, A as ArcPathConfig, r as SplitAnimationsOptions, t as SplitAnimationsResult, b as ArcPathSegment } from './gsapSerialize-CfSXM_Ez.js';
1
+ import { G as GsapAnimation, A as ArcPathConfig, r as SplitAnimationsOptions, t as SplitAnimationsResult, a as ArcPathSegment } from './gsapSerialize-WJrRO8uT.js';
2
2
  import './types-Ejqndkej.js';
3
3
 
4
4
  type TweenTarget = Pick<GsapAnimation, "targetSelector" | "hasPartialSelector">;
@@ -179,7 +179,10 @@ declare function addMotionPathToScript(script: string, targetSelector: string, p
179
179
  };
180
180
  declare function removeArcPathFromScript(script: string, animationId: string): string;
181
181
  /** Acorn-native, byte-preserving hold synchronization used after mutations. */
182
- declare function syncPositionHoldsBeforeKeyframes(script: string): string;
182
+ declare function syncPositionHoldsBeforeKeyframes(script: string, previous?: string): string;
183
+ /** GSAP 3.15 stretches a keyframe tween that stops short of 100% on its first render, unlike once played:
184
+ * each tween this mutation wrote or edited ends on its last key instead. Tweens it left alone keep their tail. */
185
+ declare function trimTrailingKeyframeSpans(previous: string, script: string): string;
183
186
  declare function splitAnimationsInScript(script: string, opts: SplitAnimationsOptions): SplitAnimationsResult;
184
187
  type UnrollElement = {
185
188
  selector: string;
@@ -197,4 +200,4 @@ type UnrollElement = {
197
200
  */
198
201
  declare function unrollDynamicAnimations(script: string, animationId: string, elements: UnrollElement[]): string;
199
202
 
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 };
203
+ 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, trimTrailingKeyframeSpans, unrollDynamicAnimations, updateAnimationInScript, updateArcSegmentInScript, updateKeyframeInScript, updateMotionPathPointInScript };
@@ -57,23 +57,43 @@ function knownStart(animation) {
57
57
  if (animation.resolvedStart !== void 0) return animation.resolvedStart;
58
58
  return typeof animation.position === "number" ? animation.position : void 0;
59
59
  }
60
- function positionHoldForAnimation(animation, animations) {
60
+ var tweenSignature = (a) => JSON.stringify([a.targetSelector, a.method, a.position, a.duration, a.properties, a.keyframes]);
61
+ function holdScope(before, previous) {
62
+ const kept = new Set(previous?.map(tweenSignature));
63
+ const held = /* @__PURE__ */ new Map();
64
+ for (const a of before.filter((b) => b.method === "set" && b.properties?.data === "hf-hold")) {
65
+ const props = held.get(a.targetSelector) ?? /* @__PURE__ */ new Map();
66
+ for (const [property, value] of Object.entries(a.properties)) props.set(property, value);
67
+ held.set(a.targetSelector, props);
68
+ }
69
+ return {
70
+ touched: (a) => previous !== null && !kept.has(tweenSignature(a)),
71
+ held: (selector) => held.get(selector) ?? /* @__PURE__ */ new Map()
72
+ };
73
+ }
74
+ function keyframeHoldForAnimation(animation, animations, scope) {
61
75
  if (!animation.keyframes) return null;
62
- const start = knownStart(animation) ?? 0;
63
- if (!(start > 1e-3)) return null;
76
+ const start = knownStart(animation);
77
+ const lone = animation.keyframes.keyframes.length === 1;
78
+ if (start === void 0 || !(start > 1e-3) && !lone) return null;
64
79
  const atStart = animation.keyframes.keyframes.find((keyframe) => keyframe.percentage === 0);
65
80
  if (!atStart) return null;
66
81
  const earlier = animations.filter((other) => {
67
82
  const otherStart = knownStart(other);
68
83
  return other !== animation && !other.global && other.targetSelector === animation.targetSelector && otherStart !== void 0 && otherStart < start - 1e-3;
69
84
  });
70
- const position = {};
85
+ const touched = scope.touched(animation);
86
+ const pinned = scope.held(animation.targetSelector);
87
+ const kept = (property, value) => touched ? pinned.has(property) : pinned.get(property) === value;
88
+ const pins = (group, property, value) => group === "position" && start > 1e-3 || (group === "position" || group === "size") && (lone && touched || kept(property, value));
89
+ const hold = {};
71
90
  for (const [property, value] of Object.entries(atStart.properties)) {
72
- if (classifyPropertyGroup(property) !== "position" || typeof value !== "number") continue;
91
+ if (!pins(classifyPropertyGroup(property), property, value) || typeof value !== "number")
92
+ continue;
73
93
  if (earlier.some((other) => writesProperty(other, property))) continue;
74
- position[property] = value;
94
+ hold[property] = value;
75
95
  }
76
- return Object.keys(position).length > 0 ? position : null;
96
+ return Object.keys(hold).length > 0 ? hold : null;
77
97
  }
78
98
 
79
99
  // src/gsapSerialize.ts
@@ -118,6 +138,10 @@ function buildArcPath(coords, curviness, autoRotate, isCubic) {
118
138
  }
119
139
  return { arcPath: { enabled: true, autoRotate, segments }, waypoints };
120
140
  }
141
+ function plainPercentKey(percentage) {
142
+ const text = Math.abs(percentage) < 1e-6 ? percentage.toFixed(20).replace(/\.?0+$/, "") : String(percentage);
143
+ return `${text}%`;
144
+ }
121
145
  var CSS_IDENTITY = {
122
146
  opacity: 1,
123
147
  autoAlpha: 1,
@@ -1067,9 +1091,6 @@ function tweenCallToAnimation(call, scope, source, identifierBindings) {
1067
1091
  properties[key] = val;
1068
1092
  }
1069
1093
  }
1070
- if (keyframesData && typeof vars.easeEach === "string") {
1071
- keyframesData.easeEach = vars.easeEach;
1072
- }
1073
1094
  if (motionPathResult) {
1074
1095
  const { waypoints } = motionPathResult;
1075
1096
  if (!keyframesData) {
@@ -2135,7 +2156,7 @@ function convertArrayKeyframesToObject(script, target) {
2135
2156
  if (!timing) return script;
2136
2157
  const entries = els.map((el, i) => {
2137
2158
  const { duration: _duration, ...record } = records[i];
2138
- return `${JSON.stringify(`${timing.percentages[i]}%`)}: ${recordToCode(record)}`;
2159
+ return `${JSON.stringify(plainPercentKey(timing.percentages[i]))}: ${recordToCode(record)}`;
2139
2160
  });
2140
2161
  const ms = new MagicString(script);
2141
2162
  ms.overwrite(
@@ -2197,7 +2218,13 @@ function addKeyframeToScript(script, animationId, percentage, properties, ease,
2197
2218
  ms.overwrite(existing.prop.value.start, existing.prop.value.end, recordToCode(targetRecord));
2198
2219
  }
2199
2220
  } else {
2200
- insertNewKeyframe(ms, kfNode, percentage, `${percentage}%`, recordToCode(targetRecord));
2221
+ insertNewKeyframe(
2222
+ ms,
2223
+ kfNode,
2224
+ percentage,
2225
+ plainPercentKey(percentage),
2226
+ recordToCode(targetRecord)
2227
+ );
2201
2228
  }
2202
2229
  for (const [prop, rec] of [...endpointOverwrites, ...backfillOverwrites]) {
2203
2230
  ms.overwrite(prop.value.start, prop.value.end, recordToCode(rec));
@@ -2296,7 +2323,12 @@ function moveKeyframeInScript(script, animationId, fromPercentage, toPercentage)
2296
2323
  }
2297
2324
  entries.push({ pct: toPercentage, record: valueNodeToRecord(match.prop.value, src) });
2298
2325
  entries.sort((a, b) => a.pct - b.pct);
2299
- const body = entries.map((e) => `${JSON.stringify(`${e.pct}%`)}: ${recordToCode(e.record)}`).join(", ");
2326
+ const pctProps = new Set(percentagePropsOf(kfNode));
2327
+ const kept = (kfNode.properties ?? []).filter((p) => !pctProps.has(p)).map((p) => src.slice(p.start, p.end));
2328
+ const body = [
2329
+ ...entries.map((e) => `${JSON.stringify(plainPercentKey(e.pct))}: ${recordToCode(e.record)}`),
2330
+ ...kept
2331
+ ].join(", ");
2300
2332
  const ms = new MagicString(src);
2301
2333
  ms.overwrite(kfNode.start, kfNode.end, `{ ${body} }`);
2302
2334
  return ms.toString();
@@ -2315,7 +2347,7 @@ function resizeKeyframedTweenInScript(script, animationId, newPosition, newDurat
2315
2347
  }
2316
2348
  const ms = new MagicString(src);
2317
2349
  for (const { keyNode, to } of edits) {
2318
- ms.overwrite(keyNode.start, keyNode.end, JSON.stringify(`${to}%`));
2350
+ ms.overwrite(keyNode.start, keyNode.end, JSON.stringify(plainPercentKey(to)));
2319
2351
  }
2320
2352
  overwritePosition(ms, target.call, newPosition);
2321
2353
  upsertProp(ms, target.call.varsArg, "duration", newDuration);
@@ -2433,7 +2465,7 @@ function buildKeyframeObjectCode(keyframes, easeEach) {
2433
2465
  const props = Object.entries(kf.properties).map(([k, v]) => `${safeKey(k)}: ${valueToCode(v)}`);
2434
2466
  if (kf.ease) props.push(`ease: ${valueToCode(kf.ease)}`);
2435
2467
  if (kf.auto) props.push(`_auto: 1`);
2436
- return `${JSON.stringify(`${kf.percentage}%`)}: { ${props.join(", ")} }`;
2468
+ return `${JSON.stringify(plainPercentKey(kf.percentage))}: { ${props.join(", ")} }`;
2437
2469
  });
2438
2470
  if (easeEach) entries.push(`easeEach: ${valueToCode(easeEach)}`);
2439
2471
  return `{ ${entries.join(", ")} }`;
@@ -2587,7 +2619,8 @@ function addGroupAnimToScript(script, anim, propSet) {
2587
2619
  pos,
2588
2620
  anim.duration ?? 0.5,
2589
2621
  groupKeyframes,
2590
- anim.keyframes.easeEach ?? anim.ease
2622
+ anim.keyframes.ease ?? anim.ease,
2623
+ anim.keyframes.easeEach
2591
2624
  );
2592
2625
  }
2593
2626
  const groupProperties = filterGroupProperties(anim.properties, propSet);
@@ -2906,15 +2939,20 @@ function removeStudioHoldSets(script, parsed) {
2906
2939
  for (const hold of staleHolds) removeCallFromMagicString(ms, hold.call, script);
2907
2940
  return ms.toString();
2908
2941
  }
2909
- function syncPositionHoldsBeforeKeyframes(script) {
2942
+ function syncPositionHoldsBeforeKeyframes(script, previous) {
2910
2943
  const parsed = parseGsapScriptAcornForWrite(script);
2911
2944
  if (!parsed) return script;
2945
+ const tweensOf = (text) => parseGsapScriptAcornForWrite(text)?.located.map((entry) => entry.animation) ?? null;
2946
+ const scope = holdScope(
2947
+ parsed.located.map((entry) => entry.animation),
2948
+ previous === void 0 ? null : tweensOf(previous)
2949
+ );
2912
2950
  let result = removeStudioHoldSets(script, parsed);
2913
2951
  const current = parseGsapScriptAcornForWrite(result);
2914
2952
  if (!current) return result;
2915
2953
  const animations = current.located.map((entry) => entry.animation);
2916
2954
  for (const animation of animations) {
2917
- const position = positionHoldForAnimation(animation, animations);
2955
+ const position = keyframeHoldForAnimation(animation, animations, scope);
2918
2956
  if (!position) continue;
2919
2957
  result = insertInheritedStateSetInScript(result, animation.targetSelector, 0, {
2920
2958
  ...position,
@@ -2923,6 +2961,62 @@ function syncPositionHoldsBeforeKeyframes(script) {
2923
2961
  }
2924
2962
  return result;
2925
2963
  }
2964
+ var roundPct = (pct) => Math.round(pct * 1e3) / 1e3;
2965
+ var LINEAR_RUN = /* @__PURE__ */ new Set(["none", "linear"]);
2966
+ var PLACED_BY_TIMELINE = /\.(?:add|addLabel|addPause|call|repeat|yoyo|duration|timeScale)\s*\(|\b(?:repeat|yoyo)\s*:/;
2967
+ function linearPercentageKeys(animation) {
2968
+ const data = animation.keyframes;
2969
+ if (data?.format !== "percentage" || data.fromMotionPath || animation.arcPath) return null;
2970
+ if (animation.durationUnresolved || !LINEAR_RUN.has(animation.ease || "none")) return null;
2971
+ return data.keyframes;
2972
+ }
2973
+ var loops = (animation) => Boolean(animation.extras?.repeat || animation.extras?.yoyo);
2974
+ function trimmableKeyframes(animation) {
2975
+ const keyframes = loops(animation) ? null : linearPercentageKeys(animation);
2976
+ const { duration, position } = animation;
2977
+ if (!keyframes || typeof position !== "number" || typeof duration !== "number") return null;
2978
+ return duration > 0 ? { keyframes, duration, position } : null;
2979
+ }
2980
+ function trailingSpanTrim(animation) {
2981
+ const tween = trimmableKeyframes(animation);
2982
+ if (!tween) return null;
2983
+ const last = Math.max(...tween.keyframes.map((keyframe) => keyframe.percentage));
2984
+ if (tween.keyframes.length < 2 || !(last > 0) || last >= 99.999) return null;
2985
+ return {
2986
+ position: tween.position,
2987
+ duration: Math.round(tween.duration * last * 10) / 1e3,
2988
+ pctRemap: tween.keyframes.map(({ percentage: from }) => ({
2989
+ from,
2990
+ to: roundPct(from / last * 100)
2991
+ }))
2992
+ };
2993
+ }
2994
+ function trimTrailingKeyframeSpans(previous, script) {
2995
+ if (script === previous) return script;
2996
+ const located = parseGsapScriptAcornForWrite(script)?.located ?? [];
2997
+ const placedAfter = ({ animation: a }) => !a.global && (a.implicitPosition || typeof a.position !== "number");
2998
+ if (PLACED_BY_TIMELINE.test(script) || located.some(placedAfter)) return script;
2999
+ const trims = located.flatMap((entry) => {
3000
+ const trim = trailingSpanTrim(entry.animation);
3001
+ return trim ? [{ entry, trim }] : [];
3002
+ });
3003
+ if (!trims.length) return script;
3004
+ const source = (text, { call }) => `${call.method}${text.slice(call.node.arguments[0]?.start, call.node.end)}`;
3005
+ const previousTweens = parseGsapScriptAcornForWrite(previous)?.located ?? [];
3006
+ const untouched = new Set(previousTweens.map((entry) => source(previous, entry)));
3007
+ let result = script;
3008
+ for (const { entry, trim } of trims) {
3009
+ if (untouched.has(source(script, entry))) continue;
3010
+ result = resizeKeyframedTweenInScript(
3011
+ result,
3012
+ entry.id,
3013
+ trim.position,
3014
+ trim.duration,
3015
+ trim.pctRemap
3016
+ );
3017
+ }
3018
+ return result;
3019
+ }
2926
3020
  function computeForwardBaselines(matching, splitTime) {
2927
3021
  const before = [];
2928
3022
  const acc = {};
@@ -3244,6 +3338,7 @@ export {
3244
3338
  splitAnimationsInScript,
3245
3339
  splitIntoPropertyGroupsFromScript,
3246
3340
  syncPositionHoldsBeforeKeyframes,
3341
+ trimTrailingKeyframeSpans,
3247
3342
  unrollDynamicAnimations,
3248
3343
  updateAnimationInScript,
3249
3344
  updateArcSegmentInScript,