@hyperframes/parsers 0.7.71 → 0.7.73

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.
@@ -91,18 +91,39 @@ var SUPPORTED_EASES = [
91
91
  "bounce.in",
92
92
  "bounce.out",
93
93
  "bounce.inOut",
94
+ "circ.inOut",
94
95
  "expo.in",
95
96
  "expo.out",
96
97
  "expo.inOut",
98
+ "elastic.out(1,0.3)",
99
+ "elastic.inOut(1,0.3)",
97
100
  "spring-gentle",
98
101
  "spring-bouncy",
99
102
  "spring-stiff",
100
103
  "spring-wobbly",
101
104
  "spring-heavy",
105
+ "hold",
102
106
  "steps(1)"
103
107
  ];
104
108
 
105
109
  // src/gsapSerialize.ts
110
+ function mergePercentageKeyframes(keyframes) {
111
+ const byPercentage = /* @__PURE__ */ new Map();
112
+ for (const keyframe of keyframes) {
113
+ const existing = byPercentage.get(keyframe.percentage);
114
+ if (!existing) {
115
+ byPercentage.set(keyframe.percentage, {
116
+ ...keyframe,
117
+ properties: { ...keyframe.properties }
118
+ });
119
+ continue;
120
+ }
121
+ existing.properties = { ...existing.properties, ...keyframe.properties };
122
+ if (keyframe.ease !== void 0) existing.ease = keyframe.ease;
123
+ if (keyframe.auto !== void 0) existing.auto = keyframe.auto;
124
+ }
125
+ return [...byPercentage.values()].sort((a, b) => a.percentage - b.percentage);
126
+ }
106
127
  function serializeGsapAnimations(animations, timelineVar = "tl", options) {
107
128
  const sorted = [...animations].sort((a, b) => {
108
129
  const aNum = a.resolvedStart ?? (typeof a.position === "number" ? a.position : Number.MAX_SAFE_INTEGER);
@@ -330,6 +351,61 @@ function resolveConversionProps(anim, resolvedFromValues) {
330
351
  return { fromProps: { ...anim.fromProperties ?? {} }, toProps };
331
352
  }
332
353
 
354
+ // src/gsapObjectArrayTiming.ts
355
+ var roundPercentage = (percentage) => Math.round(percentage * 10) / 10;
356
+ var OBJECT_ARRAY_PERCENTAGE_TOLERANCE = 2;
357
+ function getObjectArrayKeyframeTiming(durations) {
358
+ const hasAuthoredDuration = durations.some((duration) => duration !== void 0);
359
+ if (hasAuthoredDuration) {
360
+ if (!durations.every(
361
+ (duration) => typeof duration === "number" && Number.isFinite(duration) && duration > 0
362
+ )) {
363
+ return null;
364
+ }
365
+ const totalDuration = durations.reduce((sum, duration) => sum + duration, 0);
366
+ let cumulative = 0;
367
+ return {
368
+ percentages: durations.map((duration) => {
369
+ cumulative += duration;
370
+ return roundPercentage(cumulative / totalDuration * 100);
371
+ }),
372
+ totalDuration
373
+ };
374
+ }
375
+ const lastIndex = durations.length - 1;
376
+ return {
377
+ percentages: durations.map(
378
+ (_, index) => lastIndex > 0 ? roundPercentage(index / lastIndex * 100) : 0
379
+ )
380
+ };
381
+ }
382
+ function getCompatibleObjectArrayKeyframeTiming(durations, outerDuration) {
383
+ const timing = getObjectArrayKeyframeTiming(durations);
384
+ if (!timing) return null;
385
+ if (timing.totalDuration === void 0 || outerDuration === void 0) return timing;
386
+ if (typeof outerDuration === "number" && Math.abs(outerDuration - timing.totalDuration) <= Number.EPSILON) {
387
+ return timing;
388
+ }
389
+ return null;
390
+ }
391
+ function findObjectArrayKeyframeIndex(durations, percentage, options) {
392
+ if (!Number.isFinite(percentage) || percentage < 0 || percentage > 100) return null;
393
+ const timing = getObjectArrayKeyframeTiming(durations);
394
+ if (!timing) return null;
395
+ const { percentages } = timing;
396
+ let match = null;
397
+ let bestDistance = Number.POSITIVE_INFINITY;
398
+ for (let index = 0; index < percentages.length; index++) {
399
+ const distance = Math.abs(percentages[index] - percentage);
400
+ if (distance < bestDistance) {
401
+ match = index;
402
+ bestDistance = distance;
403
+ }
404
+ }
405
+ const tolerance = options?.tolerance ?? OBJECT_ARRAY_PERCENTAGE_TOLERANCE;
406
+ return bestDistance <= tolerance || options?.fallbackToNearest ? match : null;
407
+ }
408
+
333
409
  // src/springEase.ts
334
410
  var SPRING_PRESETS = [
335
411
  { name: "spring-gentle", label: "Gentle", mass: 1, stiffness: 100, damping: 15 },
@@ -776,6 +852,8 @@ function parsePercentageKeyframes(node, scope) {
776
852
  for (const [k, v] of Object.entries(record)) {
777
853
  if (k === "ease" && typeof v === "string") {
778
854
  kfEase = v;
855
+ } else if (k === "duration") {
856
+ continue;
779
857
  } else if (typeof v === "number" || typeof v === "string") {
780
858
  properties[k] = v;
781
859
  }
@@ -800,13 +878,13 @@ function computeKeyframesTotalDuration(varsNode, scope) {
800
878
  (p) => (p.key?.name ?? p.key?.value) === "keyframes"
801
879
  )?.value;
802
880
  if (!kfNode || kfNode.type !== "ArrayExpression") return void 0;
803
- let total = 0;
881
+ const durations = [];
804
882
  for (const el of kfNode.elements ?? []) {
805
883
  if (!el || el.type !== "ObjectExpression") continue;
806
884
  const r = objectExpressionToRecord(el, scope);
807
- if (typeof r.duration === "number") total += r.duration;
885
+ durations.push(r.duration);
808
886
  }
809
- return total > 0 ? total : void 0;
887
+ return getObjectArrayKeyframeTiming(durations)?.totalDuration;
810
888
  }
811
889
  function parseObjectArrayKeyframes(node, scope) {
812
890
  const elements = node.elements ?? [];
@@ -820,7 +898,7 @@ function parseObjectArrayKeyframes(node, scope) {
820
898
  let duration;
821
899
  let ease;
822
900
  for (const [k, v] of Object.entries(record)) {
823
- if (k === "duration" && typeof v === "number") {
901
+ if (k === "duration") {
824
902
  duration = v;
825
903
  } else if (k === "ease" && typeof v === "string") {
826
904
  ease = v;
@@ -830,30 +908,16 @@ function parseObjectArrayKeyframes(node, scope) {
830
908
  }
831
909
  raw.push({ properties, duration, ease });
832
910
  }
833
- const totalDuration = raw.reduce((sum, r) => sum + (r.duration ?? 0), 0);
834
- const keyframes = [];
835
- if (totalDuration > 0) {
836
- let cumulative = 0;
837
- for (const entry of raw) {
838
- cumulative += entry.duration ?? 0;
839
- const percentage = Math.round(cumulative / totalDuration * 100);
840
- keyframes.push({
841
- percentage,
842
- properties: entry.properties,
843
- ...entry.ease ? { ease: entry.ease } : {}
844
- });
845
- }
846
- } else {
847
- for (let i = 0; i < raw.length; i++) {
848
- const entry = raw[i];
849
- const percentage = raw.length > 1 ? Math.round(i / (raw.length - 1) * 100) : 0;
850
- keyframes.push({
851
- percentage,
852
- properties: entry.properties,
853
- ...entry.ease ? { ease: entry.ease } : {}
854
- });
855
- }
856
- }
911
+ const timing = getObjectArrayKeyframeTiming(raw.map((entry) => entry.duration));
912
+ if (!timing) return void 0;
913
+ const { percentages } = timing;
914
+ const keyframes = raw.map(
915
+ (entry, index) => ({
916
+ percentage: percentages[index],
917
+ properties: entry.properties,
918
+ ...entry.ease ? { ease: entry.ease } : {}
919
+ })
920
+ );
857
921
  return { format: "object-array", keyframes };
858
922
  }
859
923
  function parseSimpleArrayKeyframes(node, scope) {
@@ -1680,7 +1744,7 @@ function keyframePropsToCode(kf) {
1680
1744
  return Object.entries(kf.properties).map(([k, v]) => `${safeJsKey(k)}: ${serializeValue(v)}`);
1681
1745
  }
1682
1746
  function buildKeyframeObjectCode(keyframes, options) {
1683
- const entries = keyframes.map((kf) => {
1747
+ const entries = mergePercentageKeyframes(keyframes).map((kf) => {
1684
1748
  const props = keyframePropsToCode(kf);
1685
1749
  if (kf.ease) props.push(`ease: ${JSON.stringify(kf.ease)}`);
1686
1750
  if (kf.auto) props.push(`_auto: 1`);
@@ -1719,6 +1783,17 @@ function buildKeyframeValueNode(properties, ease) {
1719
1783
  if (effectiveEase) entries.push(`ease: ${JSON.stringify(effectiveEase)}`);
1720
1784
  return parseExpr(`{ ${entries.join(", ")} }`);
1721
1785
  }
1786
+ function setObjectExpressionEase(node, ease) {
1787
+ if (node?.type !== "ObjectExpression") return false;
1788
+ const props = node.properties ?? [];
1789
+ const easeIdx = props.findIndex(
1790
+ (property) => isObjectProperty(property) && propKeyName(property) === "ease"
1791
+ );
1792
+ const easeNode = parseExpr(`({ ease: ${JSON.stringify(ease)} })`).properties[0];
1793
+ if (easeIdx >= 0) props[easeIdx] = easeNode;
1794
+ else props.push(easeNode);
1795
+ return true;
1796
+ }
1722
1797
  function locateAnimation(script, animationId) {
1723
1798
  let parsed;
1724
1799
  try {
@@ -1765,7 +1840,7 @@ function findKeyframesObjectNode(varsArg) {
1765
1840
  const node = findPropertyNode(varsArg, "keyframes");
1766
1841
  return node?.type === "ObjectExpression" ? node : null;
1767
1842
  }
1768
- function convertArrayKeyframesToObjectNode(varsArg) {
1843
+ function convertArrayKeyframesToObjectNode(varsArg, scope) {
1769
1844
  if (varsArg?.type !== "ObjectExpression") return null;
1770
1845
  const prop = (varsArg.properties ?? []).find(
1771
1846
  (p) => isObjectProperty(p) && propKeyName(p) === "keyframes"
@@ -1774,11 +1849,22 @@ function convertArrayKeyframesToObjectNode(varsArg) {
1774
1849
  const els = (prop.value.elements ?? []).filter(
1775
1850
  (e) => !!e && e.type === "ObjectExpression"
1776
1851
  );
1777
- const n = els.length;
1778
- if (n === 0) return null;
1852
+ if (els.length === 0) return null;
1853
+ const records = els.map((element) => objectExpressionToRecord(element, scope));
1854
+ const outerDuration = objectExpressionToRecord(varsArg, scope).duration;
1855
+ const timing = getCompatibleObjectArrayKeyframeTiming(
1856
+ records.map((record) => record.duration),
1857
+ outerDuration
1858
+ );
1859
+ if (!timing) return null;
1860
+ if (timing.totalDuration !== void 0 && findPropertyNode(varsArg, "duration") === void 0) {
1861
+ setVarsKey(varsArg, "duration", timing.totalDuration);
1862
+ }
1779
1863
  const entries = els.map((el, i) => {
1780
- const pct = n > 1 ? Math.round(i / (n - 1) * 1e3) / 10 : 0;
1781
- return `${JSON.stringify(`${pct}%`)}: ${recast.print(el).code}`;
1864
+ el.properties = (el.properties ?? []).filter(
1865
+ (property) => !isObjectProperty(property) || propKeyName(property) !== "duration"
1866
+ );
1867
+ return `${JSON.stringify(`${timing.percentages[i]}%`)}: ${recast.print(el).code}`;
1782
1868
  });
1783
1869
  prop.value = parseExpr(`{ ${entries.join(", ")} }`);
1784
1870
  return prop.value;
@@ -1809,7 +1895,9 @@ function addKeyframeToScript(script, animationId, percentage, properties, ease,
1809
1895
  let loc = locateAnimationWithFallback(script, animationId);
1810
1896
  if (!loc) return script;
1811
1897
  let kfNode = findKeyframesObjectNode(loc.target.call.varsArg);
1812
- if (!kfNode) kfNode = convertArrayKeyframesToObjectNode(loc.target.call.varsArg);
1898
+ if (!kfNode) {
1899
+ kfNode = convertArrayKeyframesToObjectNode(loc.target.call.varsArg, loc.parsed.scope);
1900
+ }
1813
1901
  if (!kfNode) {
1814
1902
  script = convertToKeyframesInScript(script, animationId);
1815
1903
  loc = locateAnimationWithFallback(script, animationId);
@@ -1894,19 +1982,12 @@ function removeKeyframeFromScript(script, animationId, percentage) {
1894
1982
  const elements = (arrVal.elements ?? []).filter(
1895
1983
  (e) => !!e && e.type === "ObjectExpression"
1896
1984
  );
1897
- const n = elements.length;
1898
- if (n === 0) return script;
1899
- let matchIdx = -1;
1900
- let bestDist = Number.POSITIVE_INFINITY;
1901
- for (let i = 0; i < n; i++) {
1902
- const pct = n > 1 ? i / (n - 1) * 100 : 0;
1903
- const dist = Math.abs(pct - percentage);
1904
- if (dist <= PCT_TOLERANCE && dist < bestDist) {
1905
- matchIdx = i;
1906
- bestDist = dist;
1907
- }
1908
- }
1909
- if (matchIdx === -1) return script;
1985
+ if (elements.length === 0) return script;
1986
+ const durations = elements.map(
1987
+ (element) => objectExpressionToRecord(element, arrLoc.parsed.scope).duration
1988
+ );
1989
+ const matchIdx = findObjectArrayKeyframeIndex(durations, percentage);
1990
+ if (matchIdx === null) return script;
1910
1991
  const remaining = elements.filter((_, i) => i !== matchIdx);
1911
1992
  if (remaining.length < 2) {
1912
1993
  const sole = remaining[0];
@@ -1935,18 +2016,17 @@ function removeKeyframeFromScript(script, animationId, percentage) {
1935
2016
  function moveKeyframeInScript(script, animationId, fromPercentage, toPercentage) {
1936
2017
  const loc = locateAnimationWithFallback(script, animationId);
1937
2018
  if (!loc) return script;
1938
- const kfNode = findKeyframesObjectNode(loc.target.call.varsArg) ?? convertArrayKeyframesToObjectNode(loc.target.call.varsArg);
2019
+ const kfNode = findKeyframesObjectNode(loc.target.call.varsArg) ?? convertArrayKeyframesToObjectNode(loc.target.call.varsArg, loc.parsed.scope);
1939
2020
  if (!kfNode) return script;
1940
2021
  const match = findKeyframePropByPct(kfNode, fromPercentage);
1941
2022
  if (!match) return script;
1942
2023
  if (Math.abs(fromPercentage - toPercentage) < MOVE_NOOP_EPSILON_PCT) return script;
1943
2024
  const dest = findKeyframePropByPct(kfNode, toPercentage);
1944
- const collision = dest && dest.prop !== match.prop ? dest : null;
2025
+ if (dest && dest.prop !== match.prop) return script;
1945
2026
  const movedValue = match.prop.value;
1946
2027
  const entries = [];
1947
2028
  for (const prop of filterPercentageProps(kfNode)) {
1948
2029
  if (prop === match.prop) continue;
1949
- if (collision && prop === collision.prop) continue;
1950
2030
  const pct = percentageFromKey(propKeyName(prop) ?? "");
1951
2031
  if (Number.isNaN(pct)) continue;
1952
2032
  entries.push({ pct, value: prop.value });
@@ -1963,7 +2043,7 @@ function moveKeyframeInScript(script, animationId, fromPercentage, toPercentage)
1963
2043
  function resizeKeyframedTweenInScript(script, animationId, newPosition, newDuration, pctRemap) {
1964
2044
  const loc = locateAnimationWithFallback(script, animationId);
1965
2045
  if (!loc) return script;
1966
- const kfNode = findKeyframesObjectNode(loc.target.call.varsArg) ?? convertArrayKeyframesToObjectNode(loc.target.call.varsArg);
2046
+ const kfNode = findKeyframesObjectNode(loc.target.call.varsArg) ?? convertArrayKeyframesToObjectNode(loc.target.call.varsArg, loc.parsed.scope);
1967
2047
  if (!kfNode) return script;
1968
2048
  const seen = /* @__PURE__ */ new Set();
1969
2049
  for (const { from, to } of pctRemap) {
@@ -1972,7 +2052,12 @@ function resizeKeyframedTweenInScript(script, animationId, newPosition, newDurat
1972
2052
  seen.add(match.prop);
1973
2053
  match.prop.key = parseExpr(`{ ${JSON.stringify(`${to}%`)}: 0 }`).properties[0].key;
1974
2054
  }
1975
- applyUpdatesToCall(loc.target.call, { position: newPosition, duration: newDuration });
2055
+ applyUpdatesToCall(loc.target.call, {
2056
+ position: newPosition,
2057
+ // Resizing is an explicit duration-authoring gesture. Promote GSAP's
2058
+ // implicit default so the dragged window is the window that plays.
2059
+ duration: newDuration
2060
+ });
1976
2061
  return recast.print(loc.parsed.ast).code;
1977
2062
  }
1978
2063
  function updateKeyframeInScript(script, animationId, percentage, properties, ease) {
@@ -1982,41 +2067,63 @@ function updateKeyframeInScript(script, animationId, percentage, properties, eas
1982
2067
  const elements = (arrVal.elements ?? []).filter(
1983
2068
  (e) => !!e && e.type === "ObjectExpression"
1984
2069
  );
1985
- const n = elements.length;
1986
- if (n === 0) return script;
1987
- let matchIdx = -1;
1988
- let bestDist = Number.POSITIVE_INFINITY;
1989
- for (let i = 0; i < n; i++) {
1990
- const pct = n > 1 ? i / (n - 1) * 100 : 0;
1991
- const dist = Math.abs(pct - percentage);
1992
- if (dist <= PCT_TOLERANCE && dist < bestDist) {
1993
- matchIdx = i;
1994
- bestDist = dist;
1995
- }
1996
- }
1997
- if (matchIdx === -1) return script;
1998
- const realIdx = arrVal.elements.indexOf(elements[matchIdx]);
1999
- arrVal.elements[realIdx] = buildKeyframeValueNode(properties, ease);
2070
+ if (elements.length === 0) return script;
2071
+ const records = elements.map(
2072
+ (element) => objectExpressionToRecord(element, arrLoc.parsed.scope)
2073
+ );
2074
+ const matchIdx = findObjectArrayKeyframeIndex(
2075
+ records.map((record) => record.duration),
2076
+ percentage,
2077
+ { fallbackToNearest: true }
2078
+ );
2079
+ if (matchIdx === null) return script;
2080
+ const matchEl = elements[matchIdx];
2081
+ if (!matchEl) return script;
2082
+ const realIdx = arrVal.elements.indexOf(matchEl);
2083
+ if (Object.keys(properties).length === 0 && ease && setObjectExpressionEase(matchEl, ease)) {
2084
+ return recast.print(arrLoc.parsed.ast).code;
2085
+ }
2086
+ const merged = {};
2087
+ for (const [key, value] of Object.entries(records[matchIdx] ?? {})) {
2088
+ if (typeof value === "number" || typeof value === "string") merged[key] = value;
2089
+ }
2090
+ Object.assign(merged, properties);
2091
+ arrVal.elements[realIdx] = buildKeyframeValueNode(merged, ease);
2000
2092
  return recast.print(arrLoc.parsed.ast).code;
2001
2093
  }
2094
+ if (arrLoc && !arrVal && arrLoc.target.animation.arcPath?.enabled) {
2095
+ const propertyKeys = Object.keys(properties);
2096
+ if (propertyKeys.some((key) => key !== "x" && key !== "y")) return script;
2097
+ let next = script;
2098
+ if (propertyKeys.length > 0) {
2099
+ const waypoints = extractArcWaypoints(arrLoc.target.animation);
2100
+ if (waypoints.length < 2) return script;
2101
+ const pointIndex = Math.max(
2102
+ 0,
2103
+ Math.min(waypoints.length - 1, Math.round(percentage / 100 * (waypoints.length - 1)))
2104
+ );
2105
+ const current = waypoints[pointIndex];
2106
+ if (!current) return script;
2107
+ const x = properties.x ?? current.x;
2108
+ const y = properties.y ?? current.y;
2109
+ if (typeof x !== "number" || typeof y !== "number") return script;
2110
+ next = updateMotionPathPointInScript(next, animationId, pointIndex, { x, y });
2111
+ }
2112
+ if (ease !== void 0) {
2113
+ const updated = locateAnimationWithFallback(next, animationId);
2114
+ if (!updated) return script;
2115
+ applyUpdatesToCall(updated.target.call, { ease });
2116
+ next = recast.print(updated.parsed.ast).code;
2117
+ }
2118
+ return next;
2119
+ }
2002
2120
  const ctx = locateKeyframeCtx(script, animationId, percentage);
2003
2121
  if (!ctx) return script;
2004
2122
  const { loc, kfNode } = ctx;
2005
2123
  const match = findKeyframePropByPct(kfNode, percentage);
2006
2124
  if (!match) return script;
2007
2125
  if (Object.keys(properties).length === 0 && ease) {
2008
- const existing2 = match.prop.value;
2009
- if (existing2?.type === "ObjectExpression") {
2010
- const props = existing2.properties ?? [];
2011
- const easeIdx = props.findIndex(
2012
- (p) => isObjectProperty(p) && propKeyName(p) === "ease"
2013
- );
2014
- const easeNode = parseExpr(`({ ease: ${JSON.stringify(ease)} })`).properties[0];
2015
- if (easeIdx >= 0) {
2016
- props[easeIdx] = easeNode;
2017
- } else {
2018
- props.push(easeNode);
2019
- }
2126
+ if (setObjectExpressionEase(match.prop.value, ease)) {
2020
2127
  return recast.print(loc.parsed.ast).code;
2021
2128
  }
2022
2129
  return script;
@@ -2089,13 +2196,21 @@ function convertToKeyframesInScript(script, animationId, resolvedFromValues, set
2089
2196
  function removeAllKeyframesFromScript(script, animationId) {
2090
2197
  let loc = locateAnimationWithFallback(script, animationId);
2091
2198
  if (!loc) return script;
2092
- const kfNode = findKeyframesObjectNode(loc.target.call.varsArg) ?? convertArrayKeyframesToObjectNode(loc.target.call.varsArg);
2093
- if (!kfNode) return script;
2094
- const kfEntries = filterPercentageProps(kfNode).map((p) => ({ pct: percentageFromKey(propKeyName(p)), prop: p })).filter((e) => !Number.isNaN(e.pct)).sort((a, b) => a.pct - b.pct);
2095
- if (kfEntries.length === 0) return script;
2199
+ const kfNode = findKeyframesObjectNode(loc.target.call.varsArg) ?? convertArrayKeyframesToObjectNode(loc.target.call.varsArg, loc.parsed.scope);
2096
2200
  const method = loc.target.call.method;
2097
- const collapseEntry = method === "from" ? kfEntries[0] : kfEntries[kfEntries.length - 1];
2098
- const record = objectExpressionToRecord(collapseEntry.prop.value, loc.parsed.scope);
2201
+ let record;
2202
+ if (kfNode) {
2203
+ const kfEntries = filterPercentageProps(kfNode).map((p) => ({ pct: percentageFromKey(propKeyName(p)), prop: p })).filter((e) => !Number.isNaN(e.pct)).sort((a, b) => a.pct - b.pct);
2204
+ if (kfEntries.length === 0) return script;
2205
+ const collapseEntry = method === "from" ? kfEntries[0] : kfEntries[kfEntries.length - 1];
2206
+ record = objectExpressionToRecord(collapseEntry.prop.value, loc.parsed.scope);
2207
+ } else {
2208
+ const synthetic = loc.target.animation.arcPath?.enabled ? loc.target.animation.keyframes?.keyframes : void 0;
2209
+ if (!synthetic?.length) return script;
2210
+ const sorted = [...synthetic].sort((a, b) => a.percentage - b.percentage);
2211
+ record = (method === "from" ? sorted[0] : sorted[sorted.length - 1]).properties;
2212
+ removeVarsKey(loc.target.call.varsArg, "motionPath");
2213
+ }
2099
2214
  collapseKeyframesToFlat(loc.target.call.varsArg, record);
2100
2215
  removeVarsKey(loc.target.call.varsArg, "ease");
2101
2216
  setVarsKey(loc.target.call.varsArg, "duration", 0);