@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.
@@ -1,2 +1,2 @@
1
- export { B as BUILTIN_VAR_KEYS, D as DROPPED_VAR_KEYS, E as EXTRAS_KEYS, u as GSAP_DEFAULT_DURATION, j as PROPERTY_GROUPS, k as PropertyGroupName, S as SUPPORTED_EASES, l as SUPPORTED_PROPS, m as classifyPropertyGroup, n as classifyTweenPropertyGroup, x as isTweenConfigKey, y as isXYPositionWrite, z as positionHoldForAnimation } from './gsapSerialize-CfSXM_Ez.js';
1
+ export { B as BUILTIN_VAR_KEYS, D as DROPPED_VAR_KEYS, E as EXTRAS_KEYS, u as GSAP_DEFAULT_DURATION, H as HoldScope, f as PROPERTY_GROUPS, g as PropertyGroupName, S as SUPPORTED_EASES, h as SUPPORTED_PROPS, i as classifyPropertyGroup, j as classifyTweenPropertyGroup, x as holdScope, y as isTweenConfigKey, z as isXYPositionWrite, C as keyframeHoldForAnimation } from './gsapSerialize-WJrRO8uT.js';
2
2
  import './types-Ejqndkej.js';
@@ -94,23 +94,43 @@ function knownStart(animation) {
94
94
  if (animation.resolvedStart !== void 0) return animation.resolvedStart;
95
95
  return typeof animation.position === "number" ? animation.position : void 0;
96
96
  }
97
- function positionHoldForAnimation(animation, animations) {
97
+ var tweenSignature = (a) => JSON.stringify([a.targetSelector, a.method, a.position, a.duration, a.properties, a.keyframes]);
98
+ function holdScope(before, previous) {
99
+ const kept = new Set(previous?.map(tweenSignature));
100
+ const held = /* @__PURE__ */ new Map();
101
+ for (const a of before.filter((b) => b.method === "set" && b.properties?.data === "hf-hold")) {
102
+ const props = held.get(a.targetSelector) ?? /* @__PURE__ */ new Map();
103
+ for (const [property, value] of Object.entries(a.properties)) props.set(property, value);
104
+ held.set(a.targetSelector, props);
105
+ }
106
+ return {
107
+ touched: (a) => previous !== null && !kept.has(tweenSignature(a)),
108
+ held: (selector) => held.get(selector) ?? /* @__PURE__ */ new Map()
109
+ };
110
+ }
111
+ function keyframeHoldForAnimation(animation, animations, scope) {
98
112
  if (!animation.keyframes) return null;
99
- const start = knownStart(animation) ?? 0;
100
- if (!(start > 1e-3)) return null;
113
+ const start = knownStart(animation);
114
+ const lone = animation.keyframes.keyframes.length === 1;
115
+ if (start === void 0 || !(start > 1e-3) && !lone) return null;
101
116
  const atStart = animation.keyframes.keyframes.find((keyframe) => keyframe.percentage === 0);
102
117
  if (!atStart) return null;
103
118
  const earlier = animations.filter((other) => {
104
119
  const otherStart = knownStart(other);
105
120
  return other !== animation && !other.global && other.targetSelector === animation.targetSelector && otherStart !== void 0 && otherStart < start - 1e-3;
106
121
  });
107
- const position = {};
122
+ const touched = scope.touched(animation);
123
+ const pinned = scope.held(animation.targetSelector);
124
+ const kept = (property, value) => touched ? pinned.has(property) : pinned.get(property) === value;
125
+ const pins = (group, property, value) => group === "position" && start > 1e-3 || (group === "position" || group === "size") && (lone && touched || kept(property, value));
126
+ const hold = {};
108
127
  for (const [property, value] of Object.entries(atStart.properties)) {
109
- if (classifyPropertyGroup(property) !== "position" || typeof value !== "number") continue;
128
+ if (!pins(classifyPropertyGroup(property), property, value) || typeof value !== "number")
129
+ continue;
110
130
  if (earlier.some((other) => writesProperty(other, property))) continue;
111
- position[property] = value;
131
+ hold[property] = value;
112
132
  }
113
- return Object.keys(position).length > 0 ? position : null;
133
+ return Object.keys(hold).length > 0 ? hold : null;
114
134
  }
115
135
  var SUPPORTED_EASES = [
116
136
  "none",
@@ -159,8 +179,9 @@ export {
159
179
  SUPPORTED_PROPS,
160
180
  classifyPropertyGroup,
161
181
  classifyTweenPropertyGroup,
182
+ holdScope,
162
183
  isTweenConfigKey,
163
184
  isXYPositionWrite,
164
- positionHoldForAnimation
185
+ keyframeHoldForAnimation
165
186
  };
166
187
  //# sourceMappingURL=gsapConstants.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/gsapConstants.ts"],"sourcesContent":["/**\n * GSAP property and ease constants.\n *\n * Extracted into a standalone module so browser code can import them\n * without pulling in gsapParser (which depends on recast / @babel/parser).\n */\n\nimport type { GsapAnimation } from \"./gsapSerialize.js\";\n\nexport const GSAP_DEFAULT_DURATION = 0.5;\n\nexport const SUPPORTED_PROPS = [\n // 2D Transforms\n \"x\",\n \"y\",\n \"scale\",\n \"scaleX\",\n \"scaleY\",\n \"rotation\",\n \"skewX\",\n \"skewY\",\n // 3D Transforms\n \"z\",\n \"rotationX\",\n \"rotationY\",\n \"rotationZ\",\n \"perspective\",\n \"transformPerspective\",\n \"transformOrigin\",\n // Visibility\n \"opacity\",\n \"visibility\",\n \"autoAlpha\",\n // Dimensions\n \"width\",\n \"height\",\n // Colors\n \"color\",\n \"backgroundColor\",\n \"borderColor\",\n // Box model\n \"borderRadius\",\n // Typography\n \"fontSize\",\n \"letterSpacing\",\n // Filter & Clipping\n \"filter\",\n \"clipPath\",\n // DOM content (number counters, text roll-ups)\n \"innerText\",\n];\n\n/** Keys stored on dedicated GsapAnimation fields (not in properties/extras). */\nexport const BUILTIN_VAR_KEYS: ReadonlySet<string> = new Set([\"duration\", \"ease\", \"delay\"]);\nexport const DROPPED_VAR_KEYS: ReadonlySet<string> = new Set([\n \"onComplete\",\n \"onStart\",\n \"onUpdate\",\n \"onRepeat\",\n]);\n/** Keys that go in `extras`: non-editable GSAP config that must survive round-trips. */\nexport const EXTRAS_KEYS: ReadonlySet<string> = new Set([\n \"stagger\",\n \"yoyo\",\n \"repeat\",\n \"repeatDelay\",\n \"snap\",\n \"overwrite\",\n \"immediateRender\",\n]);\n\nexport function isTweenConfigKey(key: string): boolean {\n return BUILTIN_VAR_KEYS.has(key) || DROPPED_VAR_KEYS.has(key) || EXTRAS_KEYS.has(key);\n}\n\n// ── Property Groups ─────────────────────────────────────────────────────────\n// Each group maps to an independent GSAP tween so editing one property\n// (e.g. drag → x/y) never contaminates another (e.g. scale, rotation).\n\nexport type PropertyGroupName = \"position\" | \"scale\" | \"size\" | \"rotation\" | \"visual\" | \"other\";\n\nexport const PROPERTY_GROUPS: Record<PropertyGroupName, ReadonlySet<string>> = {\n position: new Set([\"x\", \"y\", \"xPercent\", \"yPercent\"]),\n scale: new Set([\"scale\", \"scaleX\", \"scaleY\"]),\n size: new Set([\"width\", \"height\"]),\n rotation: new Set([\"rotation\", \"skewX\", \"skewY\"]),\n visual: new Set([\"opacity\", \"autoAlpha\"]),\n other: new Set<string>(),\n};\n\nconst PROP_TO_GROUP = new Map<string, PropertyGroupName>();\nfor (const [group, props] of Object.entries(PROPERTY_GROUPS) as [\n PropertyGroupName,\n ReadonlySet<string>,\n][]) {\n for (const p of props) PROP_TO_GROUP.set(p, group);\n}\n\ntype PositionWrite = Pick<\n GsapAnimation,\n \"propertyGroup\" | \"properties\" | \"fromProperties\" | \"keyframes\"\n>;\n\nfunction writesProperty(animation: PositionWrite, property: string): boolean {\n return (\n property in animation.properties ||\n (!!animation.fromProperties && property in animation.fromProperties) ||\n !!animation.keyframes?.keyframes.some((k) => property in k.properties)\n );\n}\n\n/** A position write that sets x or y. An xPercent/yPercent centring set never duplicates one. */\nexport function isXYPositionWrite(a: PositionWrite): boolean {\n return a.propertyGroup === \"position\" && (writesProperty(a, \"x\") || writesProperty(a, \"y\"));\n}\n\nexport function classifyPropertyGroup(prop: string): PropertyGroupName {\n return PROP_TO_GROUP.get(prop) ?? \"other\";\n}\n\nexport function classifyTweenPropertyGroup(\n properties: Record<string, unknown>,\n): PropertyGroupName | undefined {\n const groups = new Set<PropertyGroupName>();\n for (const key of Object.keys(properties)) {\n // transformOrigin is a modifier; `_auto` is Studio's internal endpoint marker;\n // `data` is GSAP-reserved (carries the Studio hold-set tag). None is an animated\n // property, so none should affect the group.\n if (key === \"transformOrigin\" || key === \"_auto\" || key === \"data\") continue;\n const g = classifyPropertyGroup(key);\n groups.add(g);\n }\n if (groups.size === 1) return groups.values().next().value;\n return undefined;\n}\n\nfunction knownStart(animation: GsapAnimation): number | undefined {\n if (animation.resolvedStart !== undefined) return animation.resolvedStart;\n return typeof animation.position === \"number\" ? animation.position : undefined;\n}\n\n/**\n * What a Studio hold pins from t=0 before a later keyframed tween: its 0% keyframe's position props,\n * minus those an earlier timeline tween on the target writes (a global `gsap.set` is a base value).\n */\nexport function positionHoldForAnimation(\n animation: GsapAnimation,\n animations: readonly GsapAnimation[],\n): Record<string, number> | null {\n if (!animation.keyframes) return null;\n const start = knownStart(animation) ?? 0;\n if (!(start > 0.001)) return null;\n const atStart = animation.keyframes.keyframes.find((keyframe) => keyframe.percentage === 0);\n if (!atStart) return null;\n // A tween whose start the parser could not resolve (a label, say) is not known to come first.\n const earlier = animations.filter((other) => {\n const otherStart = knownStart(other);\n return (\n other !== animation &&\n !other.global &&\n other.targetSelector === animation.targetSelector &&\n otherStart !== undefined &&\n otherStart < start - 0.001\n );\n });\n const position: Record<string, number> = {};\n for (const [property, value] of Object.entries(atStart.properties)) {\n if (classifyPropertyGroup(property) !== \"position\" || typeof value !== \"number\") continue;\n if (earlier.some((other) => writesProperty(other, property))) continue;\n position[property] = value;\n }\n return Object.keys(position).length > 0 ? position : null;\n}\n\nexport const SUPPORTED_EASES = [\n \"none\",\n \"power1.in\",\n \"power1.out\",\n \"power1.inOut\",\n \"power2.in\",\n \"power2.out\",\n \"power2.inOut\",\n \"power3.in\",\n \"power3.out\",\n \"power3.inOut\",\n \"power4.in\",\n \"power4.out\",\n \"power4.inOut\",\n \"back.in\",\n \"back.out\",\n \"back.inOut\",\n \"elastic.in\",\n \"elastic.out\",\n \"elastic.inOut\",\n \"bounce.in\",\n \"bounce.out\",\n \"bounce.inOut\",\n \"circ.inOut\",\n \"expo.in\",\n \"expo.out\",\n \"expo.inOut\",\n \"elastic.out(1,0.3)\",\n \"elastic.inOut(1,0.3)\",\n \"spring-gentle\",\n \"spring-bouncy\",\n \"spring-stiff\",\n \"spring-wobbly\",\n \"spring-heavy\",\n \"hold\",\n \"steps(1)\",\n];\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/gsapConstants.ts"],"sourcesContent":["/**\n * GSAP property and ease constants.\n *\n * Extracted into a standalone module so browser code can import them\n * without pulling in gsapParser (which depends on recast / @babel/parser).\n */\n\nimport type { GsapAnimation } from \"./gsapSerialize.js\";\n\nexport const GSAP_DEFAULT_DURATION = 0.5;\n\nexport const SUPPORTED_PROPS = [\n // 2D Transforms\n \"x\",\n \"y\",\n \"scale\",\n \"scaleX\",\n \"scaleY\",\n \"rotation\",\n \"skewX\",\n \"skewY\",\n // 3D Transforms\n \"z\",\n \"rotationX\",\n \"rotationY\",\n \"rotationZ\",\n \"perspective\",\n \"transformPerspective\",\n \"transformOrigin\",\n // Visibility\n \"opacity\",\n \"visibility\",\n \"autoAlpha\",\n // Dimensions\n \"width\",\n \"height\",\n // Colors\n \"color\",\n \"backgroundColor\",\n \"borderColor\",\n // Box model\n \"borderRadius\",\n // Typography\n \"fontSize\",\n \"letterSpacing\",\n // Filter & Clipping\n \"filter\",\n \"clipPath\",\n // DOM content (number counters, text roll-ups)\n \"innerText\",\n];\n\n/** Keys stored on dedicated GsapAnimation fields (not in properties/extras). */\nexport const BUILTIN_VAR_KEYS: ReadonlySet<string> = new Set([\"duration\", \"ease\", \"delay\"]);\nexport const DROPPED_VAR_KEYS: ReadonlySet<string> = new Set([\n \"onComplete\",\n \"onStart\",\n \"onUpdate\",\n \"onRepeat\",\n]);\n/** Keys that go in `extras`: non-editable GSAP config that must survive round-trips. */\nexport const EXTRAS_KEYS: ReadonlySet<string> = new Set([\n \"stagger\",\n \"yoyo\",\n \"repeat\",\n \"repeatDelay\",\n \"snap\",\n \"overwrite\",\n \"immediateRender\",\n]);\n\nexport function isTweenConfigKey(key: string): boolean {\n return BUILTIN_VAR_KEYS.has(key) || DROPPED_VAR_KEYS.has(key) || EXTRAS_KEYS.has(key);\n}\n\n// ── Property Groups ─────────────────────────────────────────────────────────\n// Each group maps to an independent GSAP tween so editing one property\n// (e.g. drag → x/y) never contaminates another (e.g. scale, rotation).\n\nexport type PropertyGroupName = \"position\" | \"scale\" | \"size\" | \"rotation\" | \"visual\" | \"other\";\n\nexport const PROPERTY_GROUPS: Record<PropertyGroupName, ReadonlySet<string>> = {\n position: new Set([\"x\", \"y\", \"xPercent\", \"yPercent\"]),\n scale: new Set([\"scale\", \"scaleX\", \"scaleY\"]),\n size: new Set([\"width\", \"height\"]),\n rotation: new Set([\"rotation\", \"skewX\", \"skewY\"]),\n visual: new Set([\"opacity\", \"autoAlpha\"]),\n other: new Set<string>(),\n};\n\nconst PROP_TO_GROUP = new Map<string, PropertyGroupName>();\nfor (const [group, props] of Object.entries(PROPERTY_GROUPS) as [\n PropertyGroupName,\n ReadonlySet<string>,\n][]) {\n for (const p of props) PROP_TO_GROUP.set(p, group);\n}\n\ntype PositionWrite = Pick<\n GsapAnimation,\n \"propertyGroup\" | \"properties\" | \"fromProperties\" | \"keyframes\"\n>;\n\nfunction writesProperty(animation: PositionWrite, property: string): boolean {\n return (\n property in animation.properties ||\n (!!animation.fromProperties && property in animation.fromProperties) ||\n !!animation.keyframes?.keyframes.some((k) => property in k.properties)\n );\n}\n\n/** A position write that sets x or y. An xPercent/yPercent centring set never duplicates one. */\nexport function isXYPositionWrite(a: PositionWrite): boolean {\n return a.propertyGroup === \"position\" && (writesProperty(a, \"x\") || writesProperty(a, \"y\"));\n}\n\nexport function classifyPropertyGroup(prop: string): PropertyGroupName {\n return PROP_TO_GROUP.get(prop) ?? \"other\";\n}\n\nexport function classifyTweenPropertyGroup(\n properties: Record<string, unknown>,\n): PropertyGroupName | undefined {\n const groups = new Set<PropertyGroupName>();\n for (const key of Object.keys(properties)) {\n // transformOrigin is a modifier; `_auto` is Studio's internal endpoint marker;\n // `data` is GSAP-reserved (carries the Studio hold-set tag). None is an animated\n // property, so none should affect the group.\n if (key === \"transformOrigin\" || key === \"_auto\" || key === \"data\") continue;\n const g = classifyPropertyGroup(key);\n groups.add(g);\n }\n if (groups.size === 1) return groups.values().next().value;\n return undefined;\n}\n\nfunction knownStart(animation: GsapAnimation): number | undefined {\n if (animation.resolvedStart !== undefined) return animation.resolvedStart;\n return typeof animation.position === \"number\" ? animation.position : undefined;\n}\n\nexport interface HoldScope {\n touched: (animation: GsapAnimation) => boolean;\n held: (selector: string) => ReadonlyMap<string, unknown>;\n}\n\nconst tweenSignature = (a: GsapAnimation) =>\n JSON.stringify([a.targetSelector, a.method, a.position, a.duration, a.properties, a.keyframes]);\n\n/** The tweens an edit touched (not in `previous`; none without it), and what the script's holds already pin. */\nexport function holdScope(\n before: readonly GsapAnimation[],\n previous: readonly GsapAnimation[] | null,\n): HoldScope {\n const kept = new Set(previous?.map(tweenSignature));\n const held = new Map<string, Map<string, unknown>>();\n for (const a of before.filter((b) => b.method === \"set\" && b.properties?.data === \"hf-hold\")) {\n const props = held.get(a.targetSelector) ?? new Map<string, unknown>();\n for (const [property, value] of Object.entries(a.properties)) props.set(property, value);\n held.set(a.targetSelector, props);\n }\n return {\n touched: (a) => previous !== null && !kept.has(tweenSignature(a)),\n held: (selector) => held.get(selector) ?? new Map(),\n };\n}\n\n/**\n * What a Studio hold pins from t=0: a keyframed tween's 0% position props before a later start, and the position and\n * size of a lone key (GSAP renders none) only where this edit made it or a hold already pins it, minus earlier tweens.\n */\nexport function keyframeHoldForAnimation(\n animation: GsapAnimation,\n animations: readonly GsapAnimation[],\n scope: HoldScope,\n): Record<string, number> | null {\n if (!animation.keyframes) return null;\n const start = knownStart(animation);\n const lone = animation.keyframes.keyframes.length === 1;\n if (start === undefined || (!(start > 0.001) && !lone)) return null;\n const atStart = animation.keyframes.keyframes.find((keyframe) => keyframe.percentage === 0);\n if (!atStart) return null;\n // A tween whose start the parser could not resolve (a label, say) is not known to come first.\n const earlier = animations.filter((other) => {\n const otherStart = knownStart(other);\n return (\n other !== animation &&\n !other.global &&\n other.targetSelector === animation.targetSelector &&\n otherStart !== undefined &&\n otherStart < start - 0.001\n );\n });\n const touched = scope.touched(animation);\n const pinned = scope.held(animation.targetSelector);\n // An untouched tween keeps only the hold it made: one with its own first value.\n const kept = (property: string, value: unknown) =>\n touched ? pinned.has(property) : pinned.get(property) === value;\n const pins = (group: PropertyGroupName, property: string, value: unknown) =>\n (group === \"position\" && start > 0.001) ||\n ((group === \"position\" || group === \"size\") && ((lone && touched) || kept(property, value)));\n const hold: Record<string, number> = {};\n for (const [property, value] of Object.entries(atStart.properties)) {\n if (!pins(classifyPropertyGroup(property), property, value) || typeof value !== \"number\")\n continue;\n if (earlier.some((other) => writesProperty(other, property))) continue;\n hold[property] = value;\n }\n return Object.keys(hold).length > 0 ? hold : null;\n}\n\nexport const SUPPORTED_EASES = [\n \"none\",\n \"power1.in\",\n \"power1.out\",\n \"power1.inOut\",\n \"power2.in\",\n \"power2.out\",\n \"power2.inOut\",\n \"power3.in\",\n \"power3.out\",\n \"power3.inOut\",\n \"power4.in\",\n \"power4.out\",\n \"power4.inOut\",\n \"back.in\",\n \"back.out\",\n \"back.inOut\",\n \"elastic.in\",\n \"elastic.out\",\n \"elastic.inOut\",\n \"bounce.in\",\n \"bounce.out\",\n \"bounce.inOut\",\n \"circ.inOut\",\n \"expo.in\",\n \"expo.out\",\n \"expo.inOut\",\n \"elastic.out(1,0.3)\",\n \"elastic.inOut(1,0.3)\",\n \"spring-gentle\",\n \"spring-bouncy\",\n \"spring-stiff\",\n \"spring-wobbly\",\n \"spring-heavy\",\n \"hold\",\n \"steps(1)\",\n];\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
- import { a as GsapAnimation, P as ParsedGsap, A as ArcPathConfig, b as ArcPathSegment } from './gsapSerialize-CfSXM_Ez.js';
2
- export { g as GsapKeyframeFormat, h as GsapKeyframesData, G as GsapMethod, i as GsapPercentageKeyframe, j as PROPERTY_GROUPS, k as PropertyGroupName, S as SUPPORTED_EASES, l as SUPPORTED_PROPS, m as classifyPropertyGroup, n as classifyTweenPropertyGroup, o as getAnimationsForElementId, p as gsapAnimationsToKeyframes, q as keyframesToGsapAnimations, s as serializeGsapAnimations, v as validateCompositionGsap } from './gsapSerialize-CfSXM_Ez.js';
1
+ import { G as GsapAnimation, P as ParsedGsap, A as ArcPathConfig, a as ArcPathSegment } from './gsapSerialize-WJrRO8uT.js';
2
+ export { b as GsapKeyframeFormat, c as GsapKeyframesData, d as GsapMethod, e as GsapPercentageKeyframe, f as PROPERTY_GROUPS, g as PropertyGroupName, S as SUPPORTED_EASES, h as SUPPORTED_PROPS, i as classifyPropertyGroup, j as classifyTweenPropertyGroup, k as getAnimationsForElementId, l as gsapAnimationsToKeyframes, m as keyframesToGsapAnimations, s as serializeGsapAnimations, v as validateCompositionGsap } from './gsapSerialize-WJrRO8uT.js';
3
3
  export { SPRING_PRESETS, SpringPreset, generateSpringEaseData } from './springEase.js';
4
4
  import './types-Ejqndkej.js';
5
5
 
@@ -36,19 +36,7 @@ declare function dedupePositionWritesInScript(script: string, selector: string,
36
36
  /** True for a `tl.set(...)` this module emitted to hold a keyframe before its tween.
37
37
  * The Studio filters these out so they never appear as user keyframes/diamonds. */
38
38
  declare function isStudioHoldSet(anim: GsapAnimation): boolean;
39
- /**
40
- * Keep a `tl.set(selector, {x,y}, 0)` "hold" in front of every position-keyframed
41
- * tween that starts after t=0, so the element holds its first keyframe's position
42
- * BEFORE the tween plays instead of snapping to its CSS base (the universal NLE
43
- * "hold before first keyframe" behavior). The set is tagged with `data: "hf-hold"`
44
- * so this pass owns it: every call wipes the prior holds and recomputes from the
45
- * current keyframes, keeping them in sync as keyframes are added/moved/deleted.
46
- *
47
- * Idempotent. Only position props (x/y/xPercent/yPercent) are held — opacity/scale
48
- * keep their authored pre-tween behavior. A tween already starting at 0 needs no
49
- * hold (no gap before it).
50
- */
51
- declare function syncPositionHoldsBeforeKeyframes(script: string): string;
39
+ declare function syncPositionHoldsBeforeKeyframes(script: string, previous?: string): string;
52
40
  interface SplitAnimationsOptions {
53
41
  originalId: string;
54
42
  newId: string;
@@ -98,23 +98,43 @@ function knownStart(animation) {
98
98
  if (animation.resolvedStart !== void 0) return animation.resolvedStart;
99
99
  return typeof animation.position === "number" ? animation.position : void 0;
100
100
  }
101
- function positionHoldForAnimation(animation, animations) {
101
+ var tweenSignature = (a) => JSON.stringify([a.targetSelector, a.method, a.position, a.duration, a.properties, a.keyframes]);
102
+ function holdScope(before, previous) {
103
+ const kept = new Set(previous?.map(tweenSignature));
104
+ const held = /* @__PURE__ */ new Map();
105
+ for (const a of before.filter((b) => b.method === "set" && b.properties?.data === "hf-hold")) {
106
+ const props = held.get(a.targetSelector) ?? /* @__PURE__ */ new Map();
107
+ for (const [property, value] of Object.entries(a.properties)) props.set(property, value);
108
+ held.set(a.targetSelector, props);
109
+ }
110
+ return {
111
+ touched: (a) => previous !== null && !kept.has(tweenSignature(a)),
112
+ held: (selector) => held.get(selector) ?? /* @__PURE__ */ new Map()
113
+ };
114
+ }
115
+ function keyframeHoldForAnimation(animation, animations, scope) {
102
116
  if (!animation.keyframes) return null;
103
- const start = knownStart(animation) ?? 0;
104
- if (!(start > 1e-3)) return null;
117
+ const start = knownStart(animation);
118
+ const lone = animation.keyframes.keyframes.length === 1;
119
+ if (start === void 0 || !(start > 1e-3) && !lone) return null;
105
120
  const atStart = animation.keyframes.keyframes.find((keyframe) => keyframe.percentage === 0);
106
121
  if (!atStart) return null;
107
122
  const earlier = animations.filter((other) => {
108
123
  const otherStart = knownStart(other);
109
124
  return other !== animation && !other.global && other.targetSelector === animation.targetSelector && otherStart !== void 0 && otherStart < start - 1e-3;
110
125
  });
111
- const position = {};
126
+ const touched = scope.touched(animation);
127
+ const pinned = scope.held(animation.targetSelector);
128
+ const kept = (property, value) => touched ? pinned.has(property) : pinned.get(property) === value;
129
+ const pins = (group, property, value) => group === "position" && start > 1e-3 || (group === "position" || group === "size") && (lone && touched || kept(property, value));
130
+ const hold = {};
112
131
  for (const [property, value] of Object.entries(atStart.properties)) {
113
- if (classifyPropertyGroup(property) !== "position" || typeof value !== "number") continue;
132
+ if (!pins(classifyPropertyGroup(property), property, value) || typeof value !== "number")
133
+ continue;
114
134
  if (earlier.some((other) => writesProperty(other, property))) continue;
115
- position[property] = value;
135
+ hold[property] = value;
116
136
  }
117
- return Object.keys(position).length > 0 ? position : null;
137
+ return Object.keys(hold).length > 0 ? hold : null;
118
138
  }
119
139
  var SUPPORTED_EASES = [
120
140
  "none",
@@ -246,6 +266,10 @@ function serializeValue(value) {
246
266
  if (typeof value === "string") return JSON.stringify(value);
247
267
  return String(value);
248
268
  }
269
+ function plainPercentKey(percentage) {
270
+ const text = Math.abs(percentage) < 1e-6 ? percentage.toFixed(20).replace(/\.?0+$/, "") : String(percentage);
271
+ return `${text}%`;
272
+ }
249
273
  function safeJsKey(key) {
250
274
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
251
275
  }
@@ -1210,9 +1234,6 @@ function tweenCallToAnimation(call, scope) {
1210
1234
  properties[key] = val;
1211
1235
  }
1212
1236
  }
1213
- if (keyframesData && typeof vars.easeEach === "string") {
1214
- keyframesData.easeEach = vars.easeEach;
1215
- }
1216
1237
  if (motionPathResult) {
1217
1238
  const { waypoints } = motionPathResult;
1218
1239
  if (!keyframesData) {
@@ -1768,7 +1789,14 @@ var STUDIO_HOLD_MARKER = "hf-hold";
1768
1789
  function isStudioHoldSet(anim) {
1769
1790
  return anim.method === "set" && anim.properties?.data === STUDIO_HOLD_MARKER;
1770
1791
  }
1771
- function syncPositionHoldsBeforeKeyframes(script) {
1792
+ function animationsOf(script) {
1793
+ try {
1794
+ return parseGsapScript(script).animations;
1795
+ } catch {
1796
+ return null;
1797
+ }
1798
+ }
1799
+ function syncPositionHoldsBeforeKeyframes(script, previous) {
1772
1800
  let parsed;
1773
1801
  try {
1774
1802
  parsed = parseGsapScript(script);
@@ -1784,8 +1812,12 @@ function syncPositionHoldsBeforeKeyframes(script) {
1784
1812
  } catch {
1785
1813
  return result;
1786
1814
  }
1815
+ const scope = holdScope(
1816
+ parsed.animations,
1817
+ previous === void 0 ? null : animationsOf(previous)
1818
+ );
1787
1819
  for (const anim of reparsed.animations) {
1788
- const posProps = positionHoldForAnimation(anim, reparsed.animations);
1820
+ const posProps = keyframeHoldForAnimation(anim, reparsed.animations, scope);
1789
1821
  if (!posProps) continue;
1790
1822
  result = insertInheritedStateSet(result, anim.targetSelector, 0, {
1791
1823
  ...posProps,
@@ -1903,7 +1935,7 @@ function buildKeyframeObjectCode(keyframes, options) {
1903
1935
  const props = keyframePropsToCode(kf);
1904
1936
  if (kf.ease) props.push(`ease: ${JSON.stringify(kf.ease)}`);
1905
1937
  if (kf.auto) props.push(`_auto: 1`);
1906
- return `${JSON.stringify(`${kf.percentage}%`)}: { ${props.join(", ")} }`;
1938
+ return `${JSON.stringify(plainPercentKey(kf.percentage))}: { ${props.join(", ")} }`;
1907
1939
  });
1908
1940
  if (options?.easeEach) entries.push(`easeEach: ${JSON.stringify(options.easeEach)}`);
1909
1941
  return `{ ${entries.join(", ")} }`;
@@ -2019,7 +2051,7 @@ function convertArrayKeyframesToObjectNode(varsArg, scope) {
2019
2051
  el.properties = (el.properties ?? []).filter(
2020
2052
  (property) => !isObjectProperty(property) || propKeyName(property) !== "duration"
2021
2053
  );
2022
- return `${JSON.stringify(`${timing.percentages[i]}%`)}: ${recast.print(el).code}`;
2054
+ return `${JSON.stringify(plainPercentKey(timing.percentages[i]))}: ${recast.print(el).code}`;
2023
2055
  });
2024
2056
  prop.value = parseExpr(`{ ${entries.join(", ")}, easeEach: "none" }`);
2025
2057
  return prop.value;
@@ -2044,7 +2076,7 @@ function locateKeyframeCtx(script, animationId, percentage) {
2044
2076
  if (!loc) return null;
2045
2077
  const kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
2046
2078
  if (!kfNode) return null;
2047
- return { loc, kfNode, pctKey: `${percentage}%` };
2079
+ return { loc, kfNode, pctKey: plainPercentKey(percentage) };
2048
2080
  }
2049
2081
  function addKeyframeToScript(script, animationId, percentage, properties, ease, backfillDefaults) {
2050
2082
  let loc = locateAnimationWithFallback(script, animationId);
@@ -2060,7 +2092,7 @@ function addKeyframeToScript(script, animationId, percentage, properties, ease,
2060
2092
  kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
2061
2093
  if (!kfNode) return script;
2062
2094
  }
2063
- const pctKey = `${percentage}%`;
2095
+ const pctKey = plainPercentKey(percentage);
2064
2096
  const newValueNode = buildKeyframeValueNode(properties, ease);
2065
2097
  const existing = findKeyframePropByPct(kfNode, percentage);
2066
2098
  if (existing) {
@@ -2188,11 +2220,16 @@ function moveKeyframeInScript(script, animationId, fromPercentage, toPercentage)
2188
2220
  }
2189
2221
  entries.push({ pct: toPercentage, value: movedValue });
2190
2222
  entries.sort((a, b) => a.pct - b.pct);
2191
- kfNode.properties = entries.map((e) => {
2192
- const p = parseExpr(`{ ${JSON.stringify(`${e.pct}%`)}: {} }`).properties[0];
2193
- p.value = e.value;
2194
- return p;
2195
- });
2223
+ const pctProps = new Set(filterPercentageProps(kfNode));
2224
+ const kept = (kfNode.properties ?? []).filter((p) => !pctProps.has(p));
2225
+ kfNode.properties = [
2226
+ ...entries.map((e) => {
2227
+ const p = parseExpr(`{ ${JSON.stringify(plainPercentKey(e.pct))}: {} }`).properties[0];
2228
+ p.value = e.value;
2229
+ return p;
2230
+ }),
2231
+ ...kept
2232
+ ];
2196
2233
  return recast.print(loc.parsed.ast).code;
2197
2234
  }
2198
2235
  function resizeKeyframedTweenInScript(script, animationId, newPosition, newDuration, pctRemap) {
@@ -2205,7 +2242,7 @@ function resizeKeyframedTweenInScript(script, animationId, newPosition, newDurat
2205
2242
  const match = findKeyframePropByPct(kfNode, from);
2206
2243
  if (!match || seen.has(match.prop)) continue;
2207
2244
  seen.add(match.prop);
2208
- match.prop.key = parseExpr(`{ ${JSON.stringify(`${to}%`)}: 0 }`).properties[0].key;
2245
+ match.prop.key = parseExpr(`{ ${JSON.stringify(plainPercentKey(to))}: 0 }`).properties[0].key;
2209
2246
  }
2210
2247
  applyUpdatesToCall(loc.target.call, {
2211
2248
  position: newPosition,
@@ -2715,7 +2752,8 @@ function splitIntoPropertyGroups(script, animationId) {
2715
2752
  typeof anim.position === "number" ? anim.position : 0,
2716
2753
  anim.duration ?? 0.5,
2717
2754
  groupKeyframes,
2718
- anim.keyframes.easeEach ?? anim.ease
2755
+ anim.keyframes.ease ?? anim.ease,
2756
+ anim.keyframes.easeEach
2719
2757
  );
2720
2758
  result = addResult.script;
2721
2759
  } else {