motion 12.36.0 → 12.38.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
 
@@ -1632,9 +1632,9 @@
1632
1632
  };
1633
1633
  }
1634
1634
 
1635
- const isNotNull$1 = (value) => value !== null;
1636
- function getFinalKeyframe$1(keyframes, { repeat, repeatType = "loop" }, finalKeyframe, speed = 1) {
1637
- 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);
1638
1638
  const useFirstKeyframe = speed < 0 || (repeat && repeatType !== "loop" && repeat % 2 === 1);
1639
1639
  const index = useFirstKeyframe ? 0 : resolvedKeyframes.length - 1;
1640
1640
  return !index || finalKeyframe === undefined
@@ -1699,6 +1699,14 @@
1699
1699
  * Playback speed as a factor. 0 would be stopped, -1 reverse and 2 double speed.
1700
1700
  */
1701
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
+ };
1702
1710
  /**
1703
1711
  * This method is bound to the instance to fix a pattern where
1704
1712
  * animation.stop is returned as a reference from a useEffect.
@@ -1860,9 +1868,14 @@
1860
1868
  * This prevents delay: x, duration: 0 animations from finishing
1861
1869
  * instantly.
1862
1870
  */
1863
- const state = isInDelayPhase
1864
- ? { done: false, value: keyframes[0] }
1865
- : 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
+ }
1866
1879
  if (mixKeyframes && !isInDelayPhase) {
1867
1880
  state.value = mixKeyframes(state.value);
1868
1881
  }
@@ -1877,7 +1890,7 @@
1877
1890
  (this.state === "finished" || (this.state === "running" && done));
1878
1891
  // TODO: The exception for inertia could be cleaner here
1879
1892
  if (isAnimationFinished && type !== inertia) {
1880
- state.value = getFinalKeyframe$1(keyframes, this.options, finalKeyframe, this.speed);
1893
+ state.value = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
1881
1894
  }
1882
1895
  if (onUpdate) {
1883
1896
  onUpdate(state.value);
@@ -2485,7 +2498,7 @@
2485
2498
  this.animation.onfinish = () => {
2486
2499
  this.finishedTime = this.time;
2487
2500
  if (!pseudoElement) {
2488
- const keyframe = getFinalKeyframe$1(keyframes, this.options, finalKeyframe, this.speed);
2501
+ const keyframe = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
2489
2502
  if (this.updateMotionValue) {
2490
2503
  this.updateMotionValue(keyframe);
2491
2504
  }
@@ -2791,17 +2804,42 @@
2791
2804
  /**
2792
2805
  * A list of values that can be hardware-accelerated.
2793
2806
  */
2794
- const acceleratedValues$1 = new Set([
2807
+ const acceleratedValues = new Set([
2795
2808
  "opacity",
2796
2809
  "clipPath",
2797
2810
  "filter",
2798
2811
  "transform",
2799
- // 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.
2800
2814
  // "background-color"
2801
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
+ ]);
2802
2840
  const supportsWaapi = /*@__PURE__*/ memo(() => Object.hasOwnProperty.call(Element.prototype, "animate"));
2803
2841
  function supportsBrowserAnimation(options) {
2804
- const { motionValue, name, repeatDelay, repeatType, damping, type } = options;
2842
+ const { motionValue, name, repeatDelay, repeatType, damping, type, keyframes, } = options;
2805
2843
  const subject = motionValue?.owner?.current;
2806
2844
  /**
2807
2845
  * We use this check instead of isHTMLElement() because we explicitly
@@ -2815,7 +2853,13 @@
2815
2853
  const { onUpdate, transformTemplate } = motionValue.owner.getProps();
2816
2854
  return (supportsWaapi() &&
2817
2855
  name &&
2818
- 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))) &&
2819
2863
  (name !== "transform" || !transformTemplate) &&
2820
2864
  /**
2821
2865
  * If we're outputting values to onUpdate then we can't use WAAPI as there's
@@ -2879,7 +2923,7 @@
2879
2923
  if (!canAnimate(keyframes, name, type, velocity)) {
2880
2924
  canAnimateValue = false;
2881
2925
  if (MotionGlobalConfig.instantAnimations || !delay) {
2882
- onUpdate?.(getFinalKeyframe$1(keyframes, options, finalKeyframe));
2926
+ onUpdate?.(getFinalKeyframe(keyframes, options, finalKeyframe));
2883
2927
  }
2884
2928
  keyframes[0] = keyframes[keyframes.length - 1];
2885
2929
  makeAnimationInstant(options);
@@ -2922,12 +2966,21 @@
2922
2966
  !isHandoff &&
2923
2967
  supportsBrowserAnimation(resolvedOptions);
2924
2968
  const element = resolvedOptions.motionValue?.owner?.current;
2925
- const animation = useWaapi
2926
- ? new NativeAnimationExtended({
2927
- ...resolvedOptions,
2928
- element,
2929
- })
2930
- : 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
+ }
2931
2984
  animation.finished.then(() => {
2932
2985
  this.notifyFinished();
2933
2986
  }).catch(noop$1);
@@ -3105,8 +3158,11 @@
3105
3158
  const animationMaps = new WeakMap();
3106
3159
  const animationMapKey = (name, pseudoElement = "") => `${name}:${pseudoElement}`;
3107
3160
  function getAnimationMap(element) {
3108
- const map = animationMaps.get(element) || new Map();
3109
- animationMaps.set(element, map);
3161
+ let map = animationMaps.get(element);
3162
+ if (!map) {
3163
+ map = new Map();
3164
+ animationMaps.set(element, map);
3165
+ }
3110
3166
  return map;
3111
3167
  }
3112
3168
 
@@ -3198,17 +3254,6 @@
3198
3254
  return ease;
3199
3255
  };
3200
3256
 
3201
- const isNotNull = (value) => value !== null;
3202
- function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
3203
- const resolvedKeyframes = keyframes.filter(isNotNull);
3204
- const index = repeat && repeatType !== "loop" && repeat % 2 === 1
3205
- ? 0
3206
- : resolvedKeyframes.length - 1;
3207
- return !index || finalKeyframe === undefined
3208
- ? resolvedKeyframes[index]
3209
- : finalKeyframe;
3210
- }
3211
-
3212
3257
  /**
3213
3258
  * If `transition` has `inherit: true`, shallow-merge it with
3214
3259
  * `parentTransition` (child keys win) and strip the `inherit` key.
@@ -3232,13 +3277,29 @@
3232
3277
  return valueTransition;
3233
3278
  }
3234
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
+ ]);
3235
3292
  /**
3236
3293
  * Decide whether a transition is defined on a given Transition.
3237
3294
  * This filters out orchestration options and returns true
3238
3295
  * if any options are left.
3239
3296
  */
3240
- function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) {
3241
- 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;
3242
3303
  }
3243
3304
 
3244
3305
  const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => {
@@ -3802,13 +3863,16 @@
3802
3863
  };
3803
3864
  /**
3804
3865
  * If the value is already at the defined target, skip the animation.
3866
+ * We still re-assert the value via frame.update to take precedence
3867
+ * over any stale transitionEnd callbacks from previous animations.
3805
3868
  */
3806
3869
  const currentValue = value.get();
3807
3870
  if (currentValue !== undefined &&
3808
- !value.isAnimating &&
3871
+ !value.isAnimating() &&
3809
3872
  !Array.isArray(valueTarget) &&
3810
3873
  valueTarget === currentValue &&
3811
3874
  !valueTransition.velocity) {
3875
+ frame.update(() => value.set(valueTarget));
3812
3876
  continue;
3813
3877
  }
3814
3878
  /**
@@ -4370,19 +4434,6 @@
4370
4434
  return true;
4371
4435
  });
4372
4436
 
4373
- /**
4374
- * A list of values that can be hardware-accelerated.
4375
- */
4376
- const acceleratedValues = new Set([
4377
- "opacity",
4378
- "clipPath",
4379
- "filter",
4380
- "transform",
4381
- // TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved
4382
- // or until we implement support for linear() easing.
4383
- // "background-color"
4384
- ]);
4385
-
4386
4437
  function resolveElements(elementOrSelector, scope, selectorCache) {
4387
4438
  if (elementOrSelector == null) {
4388
4439
  return [];
@@ -5268,6 +5319,7 @@
5268
5319
  activeAnimation.stop();
5269
5320
  activeAnimation = null;
5270
5321
  }
5322
+ value.animation = undefined;
5271
5323
  };
5272
5324
  const startAnimation = () => {
5273
5325
  const currentValue = asNumber$1(value.get());
@@ -5299,8 +5351,10 @@
5299
5351
  // multiple calls within the same frame (e.g. rapid mouse events)
5300
5352
  const scheduleAnimation = () => {
5301
5353
  startAnimation();
5354
+ value.animation = activeAnimation ?? undefined;
5302
5355
  value["events"].animationStart?.notify();
5303
5356
  activeAnimation?.then(() => {
5357
+ value.animation = undefined;
5304
5358
  value["events"].animationComplete?.notify();
5305
5359
  });
5306
5360
  };
@@ -6599,10 +6653,8 @@
6599
6653
  node.options.layoutScroll &&
6600
6654
  node.scroll &&
6601
6655
  node !== node.root) {
6602
- transformBox(box, {
6603
- x: -node.scroll.offset.x,
6604
- y: -node.scroll.offset.y,
6605
- });
6656
+ translateAxis(box.x, -node.scroll.offset.x);
6657
+ translateAxis(box.y, -node.scroll.offset.y);
6606
6658
  }
6607
6659
  if (delta) {
6608
6660
  // Incoporate each ancestor's scale into a cumulative treeScale for this component
@@ -6629,8 +6681,8 @@
6629
6681
  }
6630
6682
  }
6631
6683
  function translateAxis(axis, distance) {
6632
- axis.min = axis.min + distance;
6633
- axis.max = axis.max + distance;
6684
+ axis.min += distance;
6685
+ axis.max += distance;
6634
6686
  }
6635
6687
  /**
6636
6688
  * Apply a transform to an axis from the latest resolved motion values.
@@ -7651,21 +7703,27 @@
7651
7703
  calcAxisDelta(delta.x, source.x, target.x, origin ? origin.originX : undefined);
7652
7704
  calcAxisDelta(delta.y, source.y, target.y, origin ? origin.originY : undefined);
7653
7705
  }
7654
- function calcRelativeAxis(target, relative, parent) {
7655
- target.min = parent.min + relative.min;
7706
+ function calcRelativeAxis(target, relative, parent, anchor = 0) {
7707
+ const anchorPoint = anchor
7708
+ ? mixNumber$1(parent.min, parent.max, anchor)
7709
+ : parent.min;
7710
+ target.min = anchorPoint + relative.min;
7656
7711
  target.max = target.min + calcLength(relative);
7657
7712
  }
7658
- function calcRelativeBox(target, relative, parent) {
7659
- calcRelativeAxis(target.x, relative.x, parent.x);
7660
- calcRelativeAxis(target.y, relative.y, parent.y);
7713
+ function calcRelativeBox(target, relative, parent, anchor) {
7714
+ calcRelativeAxis(target.x, relative.x, parent.x, anchor?.x);
7715
+ calcRelativeAxis(target.y, relative.y, parent.y, anchor?.y);
7661
7716
  }
7662
- function calcRelativeAxisPosition(target, layout, parent) {
7663
- target.min = layout.min - parent.min;
7717
+ function calcRelativeAxisPosition(target, layout, parent, anchor = 0) {
7718
+ const anchorPoint = anchor
7719
+ ? mixNumber$1(parent.min, parent.max, anchor)
7720
+ : parent.min;
7721
+ target.min = layout.min - anchorPoint;
7664
7722
  target.max = target.min + calcLength(layout);
7665
7723
  }
7666
- function calcRelativePosition(target, layout, parent) {
7667
- calcRelativeAxisPosition(target.x, layout.x, parent.x);
7668
- calcRelativeAxisPosition(target.y, layout.y, parent.y);
7724
+ function calcRelativePosition(target, layout, parent, anchor) {
7725
+ calcRelativeAxisPosition(target.x, layout.x, parent.x, anchor?.x);
7726
+ calcRelativeAxisPosition(target.y, layout.y, parent.y, anchor?.y);
7669
7727
  }
7670
7728
 
7671
7729
  /**
@@ -7797,8 +7855,13 @@
7797
7855
  return transform || "none";
7798
7856
  }
7799
7857
 
7800
- const borders = ["TopLeft", "TopRight", "BottomLeft", "BottomRight"];
7801
- const numBorders = borders.length;
7858
+ const borderLabels = [
7859
+ "borderTopLeftRadius",
7860
+ "borderTopRightRadius",
7861
+ "borderBottomLeftRadius",
7862
+ "borderBottomRightRadius",
7863
+ ];
7864
+ const numBorders = borderLabels.length;
7802
7865
  const asNumber = (value) => typeof value === "string" ? parseFloat(value) : value;
7803
7866
  const isPx = (value) => typeof value === "number" || px.test(value);
7804
7867
  function mixValues(target, follow, lead, progress, shouldCrossfadeOpacity, isOnlyMember) {
@@ -7813,7 +7876,7 @@
7813
7876
  * Mix border radius
7814
7877
  */
7815
7878
  for (let i = 0; i < numBorders; i++) {
7816
- const borderLabel = `border${borders[i]}Radius`;
7879
+ const borderLabel = borderLabels[i];
7817
7880
  let followRadius = getRadius(follow, borderLabel);
7818
7881
  let leadRadius = getRadius(lead, borderLabel);
7819
7882
  if (followRadius === undefined && leadRadius === undefined)
@@ -8440,8 +8503,18 @@
8440
8503
  // but should still clean up the measurements so that the next
8441
8504
  // snapshot could be taken correctly.
8442
8505
  if (updateWasBlocked) {
8506
+ const wasBlockedByResize = this.updateBlockedByResize;
8443
8507
  this.unblockUpdate();
8508
+ this.updateBlockedByResize = false;
8444
8509
  this.clearAllSnapshots();
8510
+ /**
8511
+ * When blocked by resize, still measure layouts so
8512
+ * callbacks like onLayoutMeasure fire (e.g. Reorder).
8513
+ * Skip notifyLayoutUpdate to prevent animations.
8514
+ */
8515
+ if (wasBlockedByResize) {
8516
+ this.nodes.forEach(forceLayoutMeasure);
8517
+ }
8445
8518
  this.nodes.forEach(clearMeasurements);
8446
8519
  return;
8447
8520
  }
@@ -8458,6 +8531,11 @@
8458
8531
  }
8459
8532
  else {
8460
8533
  this.isUpdating = false;
8534
+ /**
8535
+ * Ensure animation-blocked nodes (e.g. during drag)
8536
+ * get measured even when memoized (willUpdate skipped).
8537
+ */
8538
+ this.nodes.forEach(ensureDraggedNodesSnapshotted);
8461
8539
  /**
8462
8540
  * Write
8463
8541
  */
@@ -8556,7 +8634,8 @@
8556
8634
  const prevLayout = this.layout;
8557
8635
  this.layout = this.measure(false);
8558
8636
  this.layoutVersion++;
8559
- this.layoutCorrected = createBox();
8637
+ if (!this.layoutCorrected)
8638
+ this.layoutCorrected = createBox();
8560
8639
  this.isLayoutDirty = false;
8561
8640
  this.projectionDelta = undefined;
8562
8641
  this.notifyListeners("measure", this.layout.layoutBox);
@@ -8667,8 +8746,8 @@
8667
8746
  }
8668
8747
  return boxWithoutScroll;
8669
8748
  }
8670
- applyTransform(box, transformOnly = false) {
8671
- const withTransforms = createBox();
8749
+ applyTransform(box, transformOnly = false, output) {
8750
+ const withTransforms = output || createBox();
8672
8751
  copyBoxInto(withTransforms, box);
8673
8752
  for (let i = 0; i < this.path.length; i++) {
8674
8753
  const node = this.path[i];
@@ -8676,10 +8755,8 @@
8676
8755
  node.options.layoutScroll &&
8677
8756
  node.scroll &&
8678
8757
  node !== node.root) {
8679
- transformBox(withTransforms, {
8680
- x: -node.scroll.offset.x,
8681
- y: -node.scroll.offset.y,
8682
- });
8758
+ translateAxis(withTransforms.x, -node.scroll.offset.x);
8759
+ translateAxis(withTransforms.y, -node.scroll.offset.y);
8683
8760
  }
8684
8761
  if (!hasTransform(node.latestValues))
8685
8762
  continue;
@@ -8787,7 +8864,9 @@
8787
8864
  * even if no animation has started.
8788
8865
  */
8789
8866
  if (!this.targetDelta && !this.relativeTarget) {
8790
- if (relativeParent && relativeParent.layout) {
8867
+ if (this.options.layoutAnchor !== false &&
8868
+ relativeParent &&
8869
+ relativeParent.layout) {
8791
8870
  this.createRelativeTarget(relativeParent, this.layout.layoutBox, relativeParent.layout.layoutBox);
8792
8871
  }
8793
8872
  else {
@@ -8815,15 +8894,14 @@
8815
8894
  this.relativeParent &&
8816
8895
  this.relativeParent.target) {
8817
8896
  this.forceRelativeParentToResolveTarget();
8818
- calcRelativeBox(this.target, this.relativeTarget, this.relativeParent.target);
8897
+ calcRelativeBox(this.target, this.relativeTarget, this.relativeParent.target, this.options.layoutAnchor || undefined);
8819
8898
  /**
8820
8899
  * If we've only got a targetDelta, resolve it into a target
8821
8900
  */
8822
8901
  }
8823
8902
  else if (this.targetDelta) {
8824
8903
  if (Boolean(this.resumingFrom)) {
8825
- // TODO: This is creating a new object every frame
8826
- this.target = this.applyTransform(this.layout.layoutBox);
8904
+ this.applyTransform(this.layout.layoutBox, false, this.target);
8827
8905
  }
8828
8906
  else {
8829
8907
  copyBoxInto(this.target, this.layout.layoutBox);
@@ -8841,7 +8919,8 @@
8841
8919
  */
8842
8920
  if (this.attemptToResolveRelativeTarget) {
8843
8921
  this.attemptToResolveRelativeTarget = false;
8844
- if (relativeParent &&
8922
+ if (this.options.layoutAnchor !== false &&
8923
+ relativeParent &&
8845
8924
  Boolean(relativeParent.resumingFrom) ===
8846
8925
  Boolean(this.resumingFrom) &&
8847
8926
  !relativeParent.options.layoutScroll &&
@@ -8885,7 +8964,7 @@
8885
8964
  this.forceRelativeParentToResolveTarget();
8886
8965
  this.relativeTarget = createBox();
8887
8966
  this.relativeTargetOrigin = createBox();
8888
- calcRelativePosition(this.relativeTargetOrigin, layout, parentLayout);
8967
+ calcRelativePosition(this.relativeTargetOrigin, layout, parentLayout, this.options.layoutAnchor || undefined);
8889
8968
  copyBoxInto(this.relativeTarget, this.relativeTargetOrigin);
8890
8969
  }
8891
8970
  removeRelativeTarget() {
@@ -9057,7 +9136,7 @@
9057
9136
  this.layout &&
9058
9137
  this.relativeParent &&
9059
9138
  this.relativeParent.layout) {
9060
- calcRelativePosition(relativeLayout, this.layout.layoutBox, this.relativeParent.layout.layoutBox);
9139
+ calcRelativePosition(relativeLayout, this.layout.layoutBox, this.relativeParent.layout.layoutBox, this.options.layoutAnchor || undefined);
9061
9140
  mixBox(this.relativeTarget, this.relativeTargetOrigin, relativeLayout, progress);
9062
9141
  /**
9063
9142
  * If this is an unchanged relative target we can consider the
@@ -9476,10 +9555,11 @@
9476
9555
  if (relativeParent && !relativeParent.resumeFrom) {
9477
9556
  const { snapshot: parentSnapshot, layout: parentLayout } = relativeParent;
9478
9557
  if (parentSnapshot && parentLayout) {
9558
+ const anchor = node.options.layoutAnchor || undefined;
9479
9559
  const relativeSnapshot = createBox();
9480
- calcRelativePosition(relativeSnapshot, snapshot.layoutBox, parentSnapshot.layoutBox);
9560
+ calcRelativePosition(relativeSnapshot, snapshot.layoutBox, parentSnapshot.layoutBox, anchor);
9481
9561
  const relativeLayout = createBox();
9482
- calcRelativePosition(relativeLayout, layout, parentLayout.layoutBox);
9562
+ calcRelativePosition(relativeLayout, layout, parentLayout.layoutBox, anchor);
9483
9563
  if (!boxEqualsRounded(relativeSnapshot, relativeLayout)) {
9484
9564
  hasRelativeLayoutChanged = true;
9485
9565
  }
@@ -9551,9 +9631,25 @@
9551
9631
  function clearMeasurements(node) {
9552
9632
  node.clearMeasurements();
9553
9633
  }
9634
+ function forceLayoutMeasure(node) {
9635
+ node.isLayoutDirty = true;
9636
+ node.updateLayout();
9637
+ }
9554
9638
  function clearIsLayoutDirty(node) {
9555
9639
  node.isLayoutDirty = false;
9556
9640
  }
9641
+ /**
9642
+ * When a node is animation-blocked (e.g. during drag) and its component
9643
+ * didn't re-render (memoized), willUpdate() is never called so there's
9644
+ * no snapshot. Use the previous layout as a snapshot and mark dirty so
9645
+ * resetTransform/updateLayout/notifyLayoutUpdate process it normally.
9646
+ */
9647
+ function ensureDraggedNodesSnapshotted(node) {
9648
+ if (node.isAnimationBlocked && node.layout && !node.isLayoutDirty) {
9649
+ node.snapshot = node.layout;
9650
+ node.isLayoutDirty = true;
9651
+ }
9652
+ }
9557
9653
  function resetTransformStyle(node) {
9558
9654
  const { visualElement } = node.options;
9559
9655
  if (visualElement && visualElement.getProps().onBeforeLayoutMeasure) {
@@ -10992,16 +11088,48 @@
10992
11088
  [ScrollOffset.Any, "cover"],
10993
11089
  [ScrollOffset.All, "contain"],
10994
11090
  ];
10995
- function matchesPreset(offset, preset) {
11091
+ const stringToProgress = {
11092
+ start: 0,
11093
+ end: 1,
11094
+ };
11095
+ function parseStringOffset(s) {
11096
+ const parts = s.trim().split(/\s+/);
11097
+ if (parts.length !== 2)
11098
+ return undefined;
11099
+ const a = stringToProgress[parts[0]];
11100
+ const b = stringToProgress[parts[1]];
11101
+ if (a === undefined || b === undefined)
11102
+ return undefined;
11103
+ return [a, b];
11104
+ }
11105
+ function normaliseOffset(offset) {
10996
11106
  if (offset.length !== 2)
11107
+ return undefined;
11108
+ const result = [];
11109
+ for (const item of offset) {
11110
+ if (Array.isArray(item)) {
11111
+ result.push(item);
11112
+ }
11113
+ else if (typeof item === "string") {
11114
+ const parsed = parseStringOffset(item);
11115
+ if (!parsed)
11116
+ return undefined;
11117
+ result.push(parsed);
11118
+ }
11119
+ else {
11120
+ return undefined;
11121
+ }
11122
+ }
11123
+ return result;
11124
+ }
11125
+ function matchesPreset(offset, preset) {
11126
+ const normalised = normaliseOffset(offset);
11127
+ if (!normalised)
10997
11128
  return false;
10998
11129
  for (let i = 0; i < 2; i++) {
10999
- const o = offset[i];
11130
+ const o = normalised[i];
11000
11131
  const p = preset[i];
11001
- if (!Array.isArray(o) ||
11002
- o.length !== 2 ||
11003
- o[0] !== p[0] ||
11004
- o[1] !== p[1])
11132
+ if (o[0] !== p[0] || o[1] !== p[1])
11005
11133
  return false;
11006
11134
  }
11007
11135
  return true;