@hyperframes/parsers 0.8.133 → 0.8.134

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 GsapMethod as G, type HoldScope as H, 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, 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 { a as GsapAnimation, A as ArcPathConfig, r as SplitAnimationsOptions, t as SplitAnimationsResult, b as ArcPathSegment } from './gsapSerialize-_tuK-qvB.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
@@ -2906,15 +2926,20 @@ function removeStudioHoldSets(script, parsed) {
2906
2926
  for (const hold of staleHolds) removeCallFromMagicString(ms, hold.call, script);
2907
2927
  return ms.toString();
2908
2928
  }
2909
- function syncPositionHoldsBeforeKeyframes(script) {
2929
+ function syncPositionHoldsBeforeKeyframes(script, previous) {
2910
2930
  const parsed = parseGsapScriptAcornForWrite(script);
2911
2931
  if (!parsed) return script;
2932
+ const tweensOf = (text) => parseGsapScriptAcornForWrite(text)?.located.map((entry) => entry.animation) ?? null;
2933
+ const scope = holdScope(
2934
+ parsed.located.map((entry) => entry.animation),
2935
+ previous === void 0 ? null : tweensOf(previous)
2936
+ );
2912
2937
  let result = removeStudioHoldSets(script, parsed);
2913
2938
  const current = parseGsapScriptAcornForWrite(result);
2914
2939
  if (!current) return result;
2915
2940
  const animations = current.located.map((entry) => entry.animation);
2916
2941
  for (const animation of animations) {
2917
- const position = positionHoldForAnimation(animation, animations);
2942
+ const position = keyframeHoldForAnimation(animation, animations, scope);
2918
2943
  if (!position) continue;
2919
2944
  result = insertInheritedStateSetInScript(result, animation.targetSelector, 0, {
2920
2945
  ...position,
@@ -2923,6 +2948,62 @@ function syncPositionHoldsBeforeKeyframes(script) {
2923
2948
  }
2924
2949
  return result;
2925
2950
  }
2951
+ var roundPct = (pct) => Math.round(pct * 1e3) / 1e3;
2952
+ var LINEAR_RUN = /* @__PURE__ */ new Set(["none", "linear"]);
2953
+ var PLACED_BY_TIMELINE = /\.(?:add|addLabel|addPause|call|repeat|yoyo|duration|timeScale)\s*\(|\b(?:repeat|yoyo)\s*:/;
2954
+ function linearPercentageKeys(animation) {
2955
+ const data = animation.keyframes;
2956
+ if (data?.format !== "percentage" || data.fromMotionPath || animation.arcPath) return null;
2957
+ if (animation.durationUnresolved || !LINEAR_RUN.has(animation.ease || "none")) return null;
2958
+ return data.keyframes;
2959
+ }
2960
+ var loops = (animation) => Boolean(animation.extras?.repeat || animation.extras?.yoyo);
2961
+ function trimmableKeyframes(animation) {
2962
+ const keyframes = loops(animation) ? null : linearPercentageKeys(animation);
2963
+ const { duration, position } = animation;
2964
+ if (!keyframes || typeof position !== "number" || typeof duration !== "number") return null;
2965
+ return duration > 0 ? { keyframes, duration, position } : null;
2966
+ }
2967
+ function trailingSpanTrim(animation) {
2968
+ const tween = trimmableKeyframes(animation);
2969
+ if (!tween) return null;
2970
+ const last = Math.max(...tween.keyframes.map((keyframe) => keyframe.percentage));
2971
+ if (tween.keyframes.length < 2 || !(last > 0) || last >= 99.999) return null;
2972
+ return {
2973
+ position: tween.position,
2974
+ duration: Math.round(tween.duration * last * 10) / 1e3,
2975
+ pctRemap: tween.keyframes.map(({ percentage: from }) => ({
2976
+ from,
2977
+ to: roundPct(from / last * 100)
2978
+ }))
2979
+ };
2980
+ }
2981
+ function trimTrailingKeyframeSpans(previous, script) {
2982
+ if (script === previous) return script;
2983
+ const located = parseGsapScriptAcornForWrite(script)?.located ?? [];
2984
+ const placedAfter = ({ animation: a }) => !a.global && (a.implicitPosition || typeof a.position !== "number");
2985
+ if (PLACED_BY_TIMELINE.test(script) || located.some(placedAfter)) return script;
2986
+ const trims = located.flatMap((entry) => {
2987
+ const trim = trailingSpanTrim(entry.animation);
2988
+ return trim ? [{ entry, trim }] : [];
2989
+ });
2990
+ if (!trims.length) return script;
2991
+ const source = (text, { call }) => `${call.method}${text.slice(call.node.arguments[0]?.start, call.node.end)}`;
2992
+ const previousTweens = parseGsapScriptAcornForWrite(previous)?.located ?? [];
2993
+ const untouched = new Set(previousTweens.map((entry) => source(previous, entry)));
2994
+ let result = script;
2995
+ for (const { entry, trim } of trims) {
2996
+ if (untouched.has(source(script, entry))) continue;
2997
+ result = resizeKeyframedTweenInScript(
2998
+ result,
2999
+ entry.id,
3000
+ trim.position,
3001
+ trim.duration,
3002
+ trim.pctRemap
3003
+ );
3004
+ }
3005
+ return result;
3006
+ }
2926
3007
  function computeForwardBaselines(matching, splitTime) {
2927
3008
  const before = [];
2928
3009
  const acc = {};
@@ -3244,6 +3325,7 @@ export {
3244
3325
  splitAnimationsInScript,
3245
3326
  splitIntoPropertyGroupsFromScript,
3246
3327
  syncPositionHoldsBeforeKeyframes,
3328
+ trimTrailingKeyframeSpans,
3247
3329
  unrollDynamicAnimations,
3248
3330
  updateAnimationInScript,
3249
3331
  updateArcSegmentInScript,