motion 12.35.2 → 12.37.0

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.
package/README.md CHANGED
@@ -39,8 +39,7 @@ npm install motion-v
39
39
 
40
40
  Motion is available for [React](https://motion.dev/docs/react), [JavaScript](https://motion.dev/docs/quick-start) and [Vue](https://motion.dev/docs/vue).
41
41
 
42
- <details>
43
- <summary>React ⬇</summary>
42
+ ### React
44
43
 
45
44
  ```jsx
46
45
  import { motion } from "motion/react"
@@ -52,10 +51,9 @@ function Component() {
52
51
 
53
52
  Get started with [Motion for React](https://motion.dev/docs/react).
54
53
 
55
- </details>
54
+ **Note:** Framer Motion is now Motion. Import from `motion/react` instead of `framer-motion`.
56
55
 
57
- <details>
58
- <summary>JavaScript ⬇</summary>
56
+ ### JS
59
57
 
60
58
  ```javascript
61
59
  import { animate } from "motion"
@@ -65,10 +63,7 @@ animate("#box", { x: 100 })
65
63
 
66
64
  Get started with [JavaScript](https://motion.dev/docs/quick-start).
67
65
 
68
- </details>
69
-
70
- <details>
71
- <summary>Vue ⬇</summary>
66
+ ### Vue
72
67
 
73
68
  ```html
74
69
  <script>
@@ -80,19 +75,21 @@ Get started with [JavaScript](https://motion.dev/docs/quick-start).
80
75
 
81
76
  Get started with [Motion for Vue](https://motion.dev/docs/vue).
82
77
 
83
- </details>
78
+ ## 🎓 Examples & tutorials
84
79
 
85
- ## 🎓 Examples
80
+ Browse 330+ [official examples](https://motion.dev/examples), with copy-paste code that'll level-up your animations whether you're a beginner or an expert.
86
81
 
87
- Browse 100+ free and 180+ premium [Motion Examples](https://motion.dev/examples), with copy-paste code that'll level-up your animations whether you're a beginner or an expert.
82
+ Over 100 examples come with a full step-by-step [tutorial](https://motion.dev/tutorials).
88
83
 
89
84
  ## ⚡️ Motion+
90
85
 
91
86
  A one-time payment, lifetime-updates membership:
92
87
 
93
- - **180+ premium examples**
88
+ - **330+ examples**
89
+ - **100+ tutorials**
94
90
  - **Premium APIs** like [Cursor](https://motion.dev/docs/cursor) and [Ticker](https://motion.dev/docs/react-ticker)
95
- - **Visual editing** for VS Code (alpha)
91
+ - **Transition editor** for Cursor and VS Code
92
+ - **AI skills**
96
93
  - **Private Discord**
97
94
  - **Early access content**
98
95
 
@@ -132,7 +129,7 @@ Motion drives the animations on the Cursor homepage, and is working with Cursor
132
129
 
133
130
  ### Gold
134
131
 
135
- <a href="https://lottiefiles.com"><img alt="LottieFiles" src="https://github.com/user-attachments/assets/4e99d8c7-4cba-43ee-93c5-93861ae708ec" width="200px" height="120px"></a> <a href="https://mintlify.com"><img alt="Mintlify" src="https://github.com/user-attachments/assets/2740db2f-1877-49ae-ae80-ba5d19ef1587" width="200px" height="120px"></a>
132
+ <a href="https://mintlify.com"><img alt="Mintlify" src="https://github.com/user-attachments/assets/2740db2f-1877-49ae-ae80-ba5d19ef1587" width="200px" height="120px"></a>
136
133
 
137
134
  ### Silver
138
135
 
@@ -245,7 +245,11 @@
245
245
  const backIn = /*@__PURE__*/ reverseEasing(backOut);
246
246
  const backInOut = /*@__PURE__*/ mirrorEasing(backIn);
247
247
 
248
- const anticipate = (p) => (p *= 2) < 1 ? 0.5 * backIn(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
248
+ const anticipate = (p) => p >= 1
249
+ ? 1
250
+ : (p *= 2) < 1
251
+ ? 0.5 * backIn(p)
252
+ : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
249
253
 
250
254
  const circIn = (p) => 1 - Math.sin(Math.acos(p));
251
255
  const circOut = reverseEasing(circIn);
@@ -364,8 +368,7 @@
364
368
  const queue = addToCurrentFrame ? thisFrame : nextFrame;
365
369
  if (keepAlive)
366
370
  toKeepAlive.add(callback);
367
- if (!queue.has(callback))
368
- queue.add(callback);
371
+ queue.add(callback);
369
372
  return callback;
370
373
  },
371
374
  /**
@@ -390,7 +393,10 @@
390
393
  return;
391
394
  }
392
395
  isProcessing = true;
393
- [thisFrame, nextFrame] = [nextFrame, thisFrame];
396
+ // Swap this frame and the next to avoid GC
397
+ const prevFrame = thisFrame;
398
+ thisFrame = nextFrame;
399
+ nextFrame = prevFrame;
394
400
  // Execute this frame
395
401
  thisFrame.forEach(triggerCallback);
396
402
  /**
@@ -429,11 +435,12 @@
429
435
  }, {});
430
436
  const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender, } = steps;
431
437
  const processBatch = () => {
432
- const timestamp = MotionGlobalConfig.useManualTiming
438
+ const useManualTiming = MotionGlobalConfig.useManualTiming;
439
+ const timestamp = useManualTiming
433
440
  ? state.timestamp
434
441
  : performance.now();
435
442
  runNextFrame = false;
436
- if (!MotionGlobalConfig.useManualTiming) {
443
+ if (!useManualTiming) {
437
444
  state.delta = useDefaultElapsed
438
445
  ? 1000 / 60
439
446
  : Math.max(Math.min(timestamp - state.timestamp, maxElapsed$1), 1);
@@ -1625,9 +1632,9 @@
1625
1632
  };
1626
1633
  }
1627
1634
 
1628
- const isNotNull$1 = (value) => value !== null;
1629
- function getFinalKeyframe$1(keyframes, { repeat, repeatType = "loop" }, finalKeyframe, speed = 1) {
1630
- const resolvedKeyframes = keyframes.filter(isNotNull$1);
1635
+ const isNotNull = (value) => value !== null;
1636
+ function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe, speed = 1) {
1637
+ const resolvedKeyframes = keyframes.filter(isNotNull);
1631
1638
  const useFirstKeyframe = speed < 0 || (repeat && repeatType !== "loop" && repeat % 2 === 1);
1632
1639
  const index = useFirstKeyframe ? 0 : resolvedKeyframes.length - 1;
1633
1640
  return !index || finalKeyframe === undefined
@@ -1692,6 +1699,14 @@
1692
1699
  * Playback speed as a factor. 0 would be stopped, -1 reverse and 2 double speed.
1693
1700
  */
1694
1701
  this.playbackSpeed = 1;
1702
+ /**
1703
+ * Reusable state object for the delay phase to avoid
1704
+ * allocating a new object every frame.
1705
+ */
1706
+ this.delayState = {
1707
+ done: false,
1708
+ value: undefined,
1709
+ };
1695
1710
  /**
1696
1711
  * This method is bound to the instance to fix a pattern where
1697
1712
  * animation.stop is returned as a reference from a useEffect.
@@ -1853,9 +1868,14 @@
1853
1868
  * This prevents delay: x, duration: 0 animations from finishing
1854
1869
  * instantly.
1855
1870
  */
1856
- const state = isInDelayPhase
1857
- ? { done: false, value: keyframes[0] }
1858
- : frameGenerator.next(elapsed);
1871
+ let state;
1872
+ if (isInDelayPhase) {
1873
+ this.delayState.value = keyframes[0];
1874
+ state = this.delayState;
1875
+ }
1876
+ else {
1877
+ state = frameGenerator.next(elapsed);
1878
+ }
1859
1879
  if (mixKeyframes && !isInDelayPhase) {
1860
1880
  state.value = mixKeyframes(state.value);
1861
1881
  }
@@ -1870,7 +1890,7 @@
1870
1890
  (this.state === "finished" || (this.state === "running" && done));
1871
1891
  // TODO: The exception for inertia could be cleaner here
1872
1892
  if (isAnimationFinished && type !== inertia) {
1873
- state.value = getFinalKeyframe$1(keyframes, this.options, finalKeyframe, this.speed);
1893
+ state.value = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
1874
1894
  }
1875
1895
  if (onUpdate) {
1876
1896
  onUpdate(state.value);
@@ -2165,8 +2185,18 @@
2165
2185
  }
2166
2186
  const positionalValues = {
2167
2187
  // Dimensions
2168
- width: ({ x }, { paddingLeft = "0", paddingRight = "0" }) => x.max - x.min - parseFloat(paddingLeft) - parseFloat(paddingRight),
2169
- height: ({ y }, { paddingTop = "0", paddingBottom = "0" }) => y.max - y.min - parseFloat(paddingTop) - parseFloat(paddingBottom),
2188
+ width: ({ x }, { paddingLeft = "0", paddingRight = "0", boxSizing }) => {
2189
+ const width = x.max - x.min;
2190
+ return boxSizing === "border-box"
2191
+ ? width
2192
+ : width - parseFloat(paddingLeft) - parseFloat(paddingRight);
2193
+ },
2194
+ height: ({ y }, { paddingTop = "0", paddingBottom = "0", boxSizing }) => {
2195
+ const height = y.max - y.min;
2196
+ return boxSizing === "border-box"
2197
+ ? height
2198
+ : height - parseFloat(paddingTop) - parseFloat(paddingBottom);
2199
+ },
2170
2200
  top: (_bbox, { top }) => parseFloat(top),
2171
2201
  left: (_bbox, { left }) => parseFloat(left),
2172
2202
  bottom: ({ y }, { top }) => parseFloat(top) + (y.max - y.min),
@@ -2468,7 +2498,7 @@
2468
2498
  this.animation.onfinish = () => {
2469
2499
  this.finishedTime = this.time;
2470
2500
  if (!pseudoElement) {
2471
- const keyframe = getFinalKeyframe$1(keyframes, this.options, finalKeyframe, this.speed);
2501
+ const keyframe = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
2472
2502
  if (this.updateMotionValue) {
2473
2503
  this.updateMotionValue(keyframe);
2474
2504
  }
@@ -2774,17 +2804,42 @@
2774
2804
  /**
2775
2805
  * A list of values that can be hardware-accelerated.
2776
2806
  */
2777
- const acceleratedValues$1 = new Set([
2807
+ const acceleratedValues = new Set([
2778
2808
  "opacity",
2779
2809
  "clipPath",
2780
2810
  "filter",
2781
2811
  "transform",
2782
- // TODO: Could be re-enabled now we have support for linear() easing
2812
+ // TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved
2813
+ // or until we implement support for linear() easing.
2783
2814
  // "background-color"
2784
2815
  ]);
2816
+
2817
+ const browserColorFunctions = /^(?:oklch|oklab|lab|lch|color|color-mix|light-dark)\(/;
2818
+ function hasBrowserOnlyColors(keyframes) {
2819
+ for (let i = 0; i < keyframes.length; i++) {
2820
+ if (typeof keyframes[i] === "string" &&
2821
+ browserColorFunctions.test(keyframes[i])) {
2822
+ return true;
2823
+ }
2824
+ }
2825
+ return false;
2826
+ }
2827
+
2828
+ const colorProperties = new Set([
2829
+ "color",
2830
+ "backgroundColor",
2831
+ "outlineColor",
2832
+ "fill",
2833
+ "stroke",
2834
+ "borderColor",
2835
+ "borderTopColor",
2836
+ "borderRightColor",
2837
+ "borderBottomColor",
2838
+ "borderLeftColor",
2839
+ ]);
2785
2840
  const supportsWaapi = /*@__PURE__*/ memo(() => Object.hasOwnProperty.call(Element.prototype, "animate"));
2786
2841
  function supportsBrowserAnimation(options) {
2787
- const { motionValue, name, repeatDelay, repeatType, damping, type } = options;
2842
+ const { motionValue, name, repeatDelay, repeatType, damping, type, keyframes, } = options;
2788
2843
  const subject = motionValue?.owner?.current;
2789
2844
  /**
2790
2845
  * We use this check instead of isHTMLElement() because we explicitly
@@ -2798,7 +2853,13 @@
2798
2853
  const { onUpdate, transformTemplate } = motionValue.owner.getProps();
2799
2854
  return (supportsWaapi() &&
2800
2855
  name &&
2801
- acceleratedValues$1.has(name) &&
2856
+ /**
2857
+ * Force WAAPI for color properties with browser-only color formats
2858
+ * (oklch, oklab, lab, lch, etc.) that the JS animation path can't parse.
2859
+ */
2860
+ (acceleratedValues.has(name) ||
2861
+ (colorProperties.has(name) &&
2862
+ hasBrowserOnlyColors(keyframes))) &&
2802
2863
  (name !== "transform" || !transformTemplate) &&
2803
2864
  /**
2804
2865
  * If we're outputting values to onUpdate then we can't use WAAPI as there's
@@ -2858,9 +2919,11 @@
2858
2919
  * If we can't animate this value with the resolved keyframes
2859
2920
  * then we should complete it immediately.
2860
2921
  */
2922
+ let canAnimateValue = true;
2861
2923
  if (!canAnimate(keyframes, name, type, velocity)) {
2924
+ canAnimateValue = false;
2862
2925
  if (MotionGlobalConfig.instantAnimations || !delay) {
2863
- onUpdate?.(getFinalKeyframe$1(keyframes, options, finalKeyframe));
2926
+ onUpdate?.(getFinalKeyframe(keyframes, options, finalKeyframe));
2864
2927
  }
2865
2928
  keyframes[0] = keyframes[keyframes.length - 1];
2866
2929
  makeAnimationInstant(options);
@@ -2895,15 +2958,29 @@
2895
2958
  * Animate via WAAPI if possible. If this is a handoff animation, the optimised animation will be running via
2896
2959
  * WAAPI. Therefore, this animation must be JS to ensure it runs "under" the
2897
2960
  * optimised animation.
2961
+ *
2962
+ * Also skip WAAPI when keyframes aren't animatable, as the resolved
2963
+ * values may not be valid CSS and would trigger browser warnings.
2898
2964
  */
2899
- const useWaapi = !isHandoff && supportsBrowserAnimation(resolvedOptions);
2965
+ const useWaapi = canAnimateValue &&
2966
+ !isHandoff &&
2967
+ supportsBrowserAnimation(resolvedOptions);
2900
2968
  const element = resolvedOptions.motionValue?.owner?.current;
2901
- const animation = useWaapi
2902
- ? new NativeAnimationExtended({
2903
- ...resolvedOptions,
2904
- element,
2905
- })
2906
- : new JSAnimation(resolvedOptions);
2969
+ let animation;
2970
+ if (useWaapi) {
2971
+ try {
2972
+ animation = new NativeAnimationExtended({
2973
+ ...resolvedOptions,
2974
+ element,
2975
+ });
2976
+ }
2977
+ catch {
2978
+ animation = new JSAnimation(resolvedOptions);
2979
+ }
2980
+ }
2981
+ else {
2982
+ animation = new JSAnimation(resolvedOptions);
2983
+ }
2907
2984
  animation.finished.then(() => {
2908
2985
  this.notifyFinished();
2909
2986
  }).catch(noop$1);
@@ -3081,8 +3158,11 @@
3081
3158
  const animationMaps = new WeakMap();
3082
3159
  const animationMapKey = (name, pseudoElement = "") => `${name}:${pseudoElement}`;
3083
3160
  function getAnimationMap(element) {
3084
- const map = animationMaps.get(element) || new Map();
3085
- animationMaps.set(element, map);
3161
+ let map = animationMaps.get(element);
3162
+ if (!map) {
3163
+ map = new Map();
3164
+ animationMaps.set(element, map);
3165
+ }
3086
3166
  return map;
3087
3167
  }
3088
3168
 
@@ -3174,17 +3254,6 @@
3174
3254
  return ease;
3175
3255
  };
3176
3256
 
3177
- const isNotNull = (value) => value !== null;
3178
- function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
3179
- const resolvedKeyframes = keyframes.filter(isNotNull);
3180
- const index = repeat && repeatType !== "loop" && repeat % 2 === 1
3181
- ? 0
3182
- : resolvedKeyframes.length - 1;
3183
- return !index || finalKeyframe === undefined
3184
- ? resolvedKeyframes[index]
3185
- : finalKeyframe;
3186
- }
3187
-
3188
3257
  /**
3189
3258
  * If `transition` has `inherit: true`, shallow-merge it with
3190
3259
  * `parentTransition` (child keys win) and strip the `inherit` key.
@@ -3208,13 +3277,29 @@
3208
3277
  return valueTransition;
3209
3278
  }
3210
3279
 
3280
+ const orchestrationKeys = new Set([
3281
+ "when",
3282
+ "delay",
3283
+ "delayChildren",
3284
+ "staggerChildren",
3285
+ "staggerDirection",
3286
+ "repeat",
3287
+ "repeatType",
3288
+ "repeatDelay",
3289
+ "from",
3290
+ "elapsed",
3291
+ ]);
3211
3292
  /**
3212
3293
  * Decide whether a transition is defined on a given Transition.
3213
3294
  * This filters out orchestration options and returns true
3214
3295
  * if any options are left.
3215
3296
  */
3216
- function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) {
3217
- return !!Object.keys(transition).length;
3297
+ function isTransitionDefined(transition) {
3298
+ for (const key in transition) {
3299
+ if (!orchestrationKeys.has(key))
3300
+ return true;
3301
+ }
3302
+ return false;
3218
3303
  }
3219
3304
 
3220
3305
  const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => {
@@ -4346,19 +4431,6 @@
4346
4431
  return true;
4347
4432
  });
4348
4433
 
4349
- /**
4350
- * A list of values that can be hardware-accelerated.
4351
- */
4352
- const acceleratedValues = new Set([
4353
- "opacity",
4354
- "clipPath",
4355
- "filter",
4356
- "transform",
4357
- // TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved
4358
- // or until we implement support for linear() easing.
4359
- // "background-color"
4360
- ]);
4361
-
4362
4434
  function resolveElements(elementOrSelector, scope, selectorCache) {
4363
4435
  if (elementOrSelector == null) {
4364
4436
  return [];
@@ -4504,7 +4576,9 @@
4504
4576
  * that works across iframes
4505
4577
  */
4506
4578
  function isHTMLElement(element) {
4507
- return isObject(element) && "offsetHeight" in element;
4579
+ return (isObject(element) &&
4580
+ "offsetHeight" in element &&
4581
+ !("ownerSVGElement" in element));
4508
4582
  }
4509
4583
 
4510
4584
  const translateAlias$1 = {
@@ -5242,6 +5316,7 @@
5242
5316
  activeAnimation.stop();
5243
5317
  activeAnimation = null;
5244
5318
  }
5319
+ value.animation = undefined;
5245
5320
  };
5246
5321
  const startAnimation = () => {
5247
5322
  const currentValue = asNumber$1(value.get());
@@ -5273,8 +5348,10 @@
5273
5348
  // multiple calls within the same frame (e.g. rapid mouse events)
5274
5349
  const scheduleAnimation = () => {
5275
5350
  startAnimation();
5351
+ value.animation = activeAnimation ?? undefined;
5276
5352
  value["events"].animationStart?.notify();
5277
5353
  activeAnimation?.then(() => {
5354
+ value.animation = undefined;
5278
5355
  value["events"].animationComplete?.notify();
5279
5356
  });
5280
5357
  };
@@ -5284,7 +5361,16 @@
5284
5361
  frame.postRender(scheduleAnimation);
5285
5362
  }, stopAnimation);
5286
5363
  if (isMotionValue(source)) {
5287
- const removeSourceOnChange = source.on("change", (v) => value.set(parseValue(v, unit)));
5364
+ let skipNextAnimation = options.skipInitialAnimation === true;
5365
+ const removeSourceOnChange = source.on("change", (v) => {
5366
+ if (skipNextAnimation) {
5367
+ skipNextAnimation = false;
5368
+ value.jump(parseValue(v, unit), false);
5369
+ }
5370
+ else {
5371
+ value.set(parseValue(v, unit));
5372
+ }
5373
+ });
5288
5374
  const removeValueOnDestroy = value.on("destroy", removeSourceOnChange);
5289
5375
  return () => {
5290
5376
  removeSourceOnChange();
@@ -6564,10 +6650,8 @@
6564
6650
  node.options.layoutScroll &&
6565
6651
  node.scroll &&
6566
6652
  node !== node.root) {
6567
- transformBox(box, {
6568
- x: -node.scroll.offset.x,
6569
- y: -node.scroll.offset.y,
6570
- });
6653
+ translateAxis(box.x, -node.scroll.offset.x);
6654
+ translateAxis(box.y, -node.scroll.offset.y);
6571
6655
  }
6572
6656
  if (delta) {
6573
6657
  // Incoporate each ancestor's scale into a cumulative treeScale for this component
@@ -6594,8 +6678,8 @@
6594
6678
  }
6595
6679
  }
6596
6680
  function translateAxis(axis, distance) {
6597
- axis.min = axis.min + distance;
6598
- axis.max = axis.max + distance;
6681
+ axis.min += distance;
6682
+ axis.max += distance;
6599
6683
  }
6600
6684
  /**
6601
6685
  * Apply a transform to an axis from the latest resolved motion values.
@@ -7762,8 +7846,13 @@
7762
7846
  return transform || "none";
7763
7847
  }
7764
7848
 
7765
- const borders = ["TopLeft", "TopRight", "BottomLeft", "BottomRight"];
7766
- const numBorders = borders.length;
7849
+ const borderLabels = [
7850
+ "borderTopLeftRadius",
7851
+ "borderTopRightRadius",
7852
+ "borderBottomLeftRadius",
7853
+ "borderBottomRightRadius",
7854
+ ];
7855
+ const numBorders = borderLabels.length;
7767
7856
  const asNumber = (value) => typeof value === "string" ? parseFloat(value) : value;
7768
7857
  const isPx = (value) => typeof value === "number" || px.test(value);
7769
7858
  function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
@@ -7778,7 +7867,7 @@
7778
7867
  * Mix border radius
7779
7868
  */
7780
7869
  for (let i = 0; i < numBorders; i++) {
7781
- const borderLabel = `border${borders[i]}Radius`;
7870
+ const borderLabel = borderLabels[i];
7782
7871
  let followRadius = getRadius(follow, borderLabel);
7783
7872
  let leadRadius = getRadius(lead, borderLabel);
7784
7873
  if (followRadius === undefined && leadRadius === undefined)
@@ -8423,6 +8512,11 @@
8423
8512
  }
8424
8513
  else {
8425
8514
  this.isUpdating = false;
8515
+ /**
8516
+ * Ensure animation-blocked nodes (e.g. during drag)
8517
+ * get measured even when memoized (willUpdate skipped).
8518
+ */
8519
+ this.nodes.forEach(ensureDraggedNodesSnapshotted);
8426
8520
  /**
8427
8521
  * Write
8428
8522
  */
@@ -8521,7 +8615,8 @@
8521
8615
  const prevLayout = this.layout;
8522
8616
  this.layout = this.measure(false);
8523
8617
  this.layoutVersion++;
8524
- this.layoutCorrected = createBox();
8618
+ if (!this.layoutCorrected)
8619
+ this.layoutCorrected = createBox();
8525
8620
  this.isLayoutDirty = false;
8526
8621
  this.projectionDelta = undefined;
8527
8622
  this.notifyListeners("measure", this.layout.layoutBox);
@@ -8632,8 +8727,8 @@
8632
8727
  }
8633
8728
  return boxWithoutScroll;
8634
8729
  }
8635
- applyTransform(box, transformOnly = false) {
8636
- const withTransforms = createBox();
8730
+ applyTransform(box, transformOnly = false, output) {
8731
+ const withTransforms = output || createBox();
8637
8732
  copyBoxInto(withTransforms, box);
8638
8733
  for (let i = 0; i < this.path.length; i++) {
8639
8734
  const node = this.path[i];
@@ -8641,10 +8736,8 @@
8641
8736
  node.options.layoutScroll &&
8642
8737
  node.scroll &&
8643
8738
  node !== node.root) {
8644
- transformBox(withTransforms, {
8645
- x: -node.scroll.offset.x,
8646
- y: -node.scroll.offset.y,
8647
- });
8739
+ translateAxis(withTransforms.x, -node.scroll.offset.x);
8740
+ translateAxis(withTransforms.y, -node.scroll.offset.y);
8648
8741
  }
8649
8742
  if (!hasTransform(node.latestValues))
8650
8743
  continue;
@@ -8787,8 +8880,7 @@
8787
8880
  }
8788
8881
  else if (this.targetDelta) {
8789
8882
  if (Boolean(this.resumingFrom)) {
8790
- // TODO: This is creating a new object every frame
8791
- this.target = this.applyTransform(this.layout.layoutBox);
8883
+ this.applyTransform(this.layout.layoutBox, false, this.target);
8792
8884
  }
8793
8885
  else {
8794
8886
  copyBoxInto(this.target, this.layout.layoutBox);
@@ -9398,6 +9490,12 @@
9398
9490
  axisSnapshot.max = axisSnapshot.min + length;
9399
9491
  });
9400
9492
  }
9493
+ else if (animationType === "x" || animationType === "y") {
9494
+ const snapAxis = animationType === "x" ? "y" : "x";
9495
+ copyAxisInto(isShared
9496
+ ? snapshot.measuredBox[snapAxis]
9497
+ : snapshot.layoutBox[snapAxis], layout[snapAxis]);
9498
+ }
9401
9499
  else if (shouldAnimatePositionOnly(animationType, snapshot.layoutBox, layout)) {
9402
9500
  eachAxis((axis) => {
9403
9501
  const axisSnapshot = isShared
@@ -9513,6 +9611,18 @@
9513
9611
  function clearIsLayoutDirty(node) {
9514
9612
  node.isLayoutDirty = false;
9515
9613
  }
9614
+ /**
9615
+ * When a node is animation-blocked (e.g. during drag) and its component
9616
+ * didn't re-render (memoized), willUpdate() is never called so there's
9617
+ * no snapshot. Use the previous layout as a snapshot and mark dirty so
9618
+ * resetTransform/updateLayout/notifyLayoutUpdate process it normally.
9619
+ */
9620
+ function ensureDraggedNodesSnapshotted(node) {
9621
+ if (node.isAnimationBlocked && node.layout && !node.isLayoutDirty) {
9622
+ node.snapshot = node.layout;
9623
+ node.isLayoutDirty = true;
9624
+ }
9625
+ }
9516
9626
  function resetTransformStyle(node) {
9517
9627
  const { visualElement } = node.options;
9518
9628
  if (visualElement && visualElement.getProps().onBeforeLayoutMeasure) {
@@ -10951,16 +11061,48 @@
10951
11061
  [ScrollOffset.Any, "cover"],
10952
11062
  [ScrollOffset.All, "contain"],
10953
11063
  ];
10954
- function matchesPreset(offset, preset) {
11064
+ const stringToProgress = {
11065
+ start: 0,
11066
+ end: 1,
11067
+ };
11068
+ function parseStringOffset(s) {
11069
+ const parts = s.trim().split(/\s+/);
11070
+ if (parts.length !== 2)
11071
+ return undefined;
11072
+ const a = stringToProgress[parts[0]];
11073
+ const b = stringToProgress[parts[1]];
11074
+ if (a === undefined || b === undefined)
11075
+ return undefined;
11076
+ return [a, b];
11077
+ }
11078
+ function normaliseOffset(offset) {
10955
11079
  if (offset.length !== 2)
11080
+ return undefined;
11081
+ const result = [];
11082
+ for (const item of offset) {
11083
+ if (Array.isArray(item)) {
11084
+ result.push(item);
11085
+ }
11086
+ else if (typeof item === "string") {
11087
+ const parsed = parseStringOffset(item);
11088
+ if (!parsed)
11089
+ return undefined;
11090
+ result.push(parsed);
11091
+ }
11092
+ else {
11093
+ return undefined;
11094
+ }
11095
+ }
11096
+ return result;
11097
+ }
11098
+ function matchesPreset(offset, preset) {
11099
+ const normalised = normaliseOffset(offset);
11100
+ if (!normalised)
10956
11101
  return false;
10957
11102
  for (let i = 0; i < 2; i++) {
10958
- const o = offset[i];
11103
+ const o = normalised[i];
10959
11104
  const p = preset[i];
10960
- if (!Array.isArray(o) ||
10961
- o.length !== 2 ||
10962
- o[0] !== p[0] ||
10963
- o[1] !== p[1])
11105
+ if (o[0] !== p[0] || o[1] !== p[1])
10964
11106
  return false;
10965
11107
  }
10966
11108
  return true;