framer-motion 8.4.7 → 8.5.2-alpha.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/dist/cjs/index.js CHANGED
@@ -850,6 +850,8 @@ const validMotionProps = new Set([
850
850
  "layout",
851
851
  "layoutId",
852
852
  "layoutDependency",
853
+ "layoutScroll",
854
+ "layoutRoot",
853
855
  "onLayoutAnimationStart",
854
856
  "onLayoutAnimationComplete",
855
857
  "onLayoutMeasure",
@@ -877,7 +879,6 @@ const validMotionProps = new Set([
877
879
  "dragTransition",
878
880
  "onHoverStart",
879
881
  "onHoverEnd",
880
- "layoutScroll",
881
882
  ...inViewProps,
882
883
  ...tapProps,
883
884
  ...animationProps,
@@ -2104,7 +2105,7 @@ class MotionValue {
2104
2105
  * This will be replaced by the build step with the latest version number.
2105
2106
  * When MotionValues are provided to motion components, warn if versions are mixed.
2106
2107
  */
2107
- this.version = "8.4.7";
2108
+ this.version = "8.5.2-alpha.0";
2108
2109
  /**
2109
2110
  * Duration, in milliseconds, since last updating frame.
2110
2111
  *
@@ -2803,8 +2804,21 @@ const appearStoreId = (id, value) => `${id}: ${value}`;
2803
2804
  function handoffOptimizedAppearAnimation(id, name, value) {
2804
2805
  const { MotionAppearAnimations } = window;
2805
2806
  const animationId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
2806
- const animation = MotionAppearAnimations && MotionAppearAnimations.get(animationId);
2807
- if (animation) {
2807
+ const { animation, ready } = (MotionAppearAnimations && MotionAppearAnimations.get(animationId)) ||
2808
+ {};
2809
+ if (!animation)
2810
+ return 0;
2811
+ const cancelOptimisedAnimation = () => {
2812
+ MotionAppearAnimations.delete(animationId);
2813
+ /**
2814
+ * Animation.cancel() throws so it needs to be wrapped in a try/catch
2815
+ */
2816
+ try {
2817
+ animation.cancel();
2818
+ }
2819
+ catch (e) { }
2820
+ };
2821
+ if (ready) {
2808
2822
  const sampledTime = performance.now();
2809
2823
  /**
2810
2824
  * Resync handoff animation with optimised animation.
@@ -2829,19 +2843,12 @@ function handoffOptimizedAppearAnimation(id, name, value) {
2829
2843
  * 2. As all independent transforms share a single transform animation, stopping
2830
2844
  * it synchronously would prevent subsequent transforms from handing off.
2831
2845
  */
2832
- sync.render(() => {
2833
- MotionAppearAnimations.delete(animationId);
2834
- /**
2835
- * Animation.cancel() throws so it needs to be wrapped in a try/catch
2836
- */
2837
- try {
2838
- animation.cancel();
2839
- }
2840
- catch (e) { }
2841
- });
2846
+ sync.render(cancelOptimisedAnimation);
2847
+ console.log(animation.currentTime);
2842
2848
  return animation.currentTime || 0;
2843
2849
  }
2844
2850
  else {
2851
+ cancelOptimisedAnimation();
2845
2852
  return 0;
2846
2853
  }
2847
2854
  }
@@ -3383,7 +3390,7 @@ const velocitySampleDuration = 5;
3383
3390
  /**
3384
3391
  * This is based on the spring implementation of Wobble https://github.com/skevy/wobble
3385
3392
  */
3386
- function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
3393
+ function spring({ keyframes, restDelta, restSpeed, ...options }) {
3387
3394
  let origin = keyframes[0];
3388
3395
  let target = keyframes[keyframes.length - 1];
3389
3396
  /**
@@ -3399,12 +3406,15 @@ function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
3399
3406
  const initialDelta = target - origin;
3400
3407
  const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
3401
3408
  /**
3402
- * If we're working within what looks like a 0-1 range, change the default restDelta
3403
- * to 0.01
3409
+ * If we're working on a granular scale, use smaller defaults for determining
3410
+ * when the spring is finished.
3411
+ *
3412
+ * These defaults have been selected emprically based on what strikes a good
3413
+ * ratio between feeling good and finishing as soon as changes are imperceptible.
3404
3414
  */
3405
- if (restDelta === undefined) {
3406
- restDelta = Math.min(Math.abs(target - origin) / 100, 0.4);
3407
- }
3415
+ const isGranularScale = Math.abs(initialDelta) < 5;
3416
+ restSpeed || (restSpeed = isGranularScale ? 0.01 : 2);
3417
+ restDelta || (restDelta = isGranularScale ? 0.005 : 0.5);
3408
3418
  if (dampingRatio < 1) {
3409
3419
  const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
3410
3420
  // Underdamped spring
@@ -5997,7 +6007,7 @@ function updateMotionValuesFromProps(element, next, prev) {
5997
6007
  * and warn against mismatches.
5998
6008
  */
5999
6009
  if (process.env.NODE_ENV === "development") {
6000
- warnOnce(nextValue.version === "8.4.7", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.7 may not work as expected.`);
6010
+ warnOnce(nextValue.version === "8.5.2-alpha.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.5.2-alpha.0 may not work as expected.`);
6001
6011
  }
6002
6012
  }
6003
6013
  else if (isMotionValue(prevValue)) {
@@ -6255,7 +6265,7 @@ class VisualElement {
6255
6265
  }
6256
6266
  if (!this.projection && ProjectionNodeConstructor) {
6257
6267
  this.projection = new ProjectionNodeConstructor(projectionId, this.latestValues, this.parent && this.parent.projection);
6258
- const { layoutId, layout, drag, dragConstraints, layoutScroll } = renderedProps;
6268
+ const { layoutId, layout, drag, dragConstraints, layoutScroll, layoutRoot, } = renderedProps;
6259
6269
  this.projection.setOptions({
6260
6270
  layoutId,
6261
6271
  layout,
@@ -6273,6 +6283,7 @@ class VisualElement {
6273
6283
  animationType: typeof layout === "string" ? layout : "both",
6274
6284
  initialPromotionConfig: initialLayoutGroupConfig,
6275
6285
  layoutScroll,
6286
+ layoutRoot,
6276
6287
  });
6277
6288
  }
6278
6289
  return features;
@@ -7413,7 +7424,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
7413
7424
  * relative to its parent has indeed changed. So here we check for that.
7414
7425
  */
7415
7426
  const hasOnlyRelativeTargetChanged = !hasLayoutChanged && hasRelativeTargetChanged;
7416
- if (((_c = this.resumeFrom) === null || _c === void 0 ? void 0 : _c.instance) ||
7427
+ if (this.options.layoutRoot ||
7428
+ ((_c = this.resumeFrom) === null || _c === void 0 ? void 0 : _c.instance) ||
7417
7429
  hasOnlyRelativeTargetChanged ||
7418
7430
  (hasLayoutChanged &&
7419
7431
  (targetChanged || !this.currentAnimation))) {
@@ -7427,7 +7439,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
7427
7439
  onPlay: onLayoutAnimationStart,
7428
7440
  onComplete: onLayoutAnimationComplete,
7429
7441
  };
7430
- if (visualElement.shouldReduceMotion) {
7442
+ if (visualElement.shouldReduceMotion ||
7443
+ this.options.layoutRoot) {
7431
7444
  animationOptions.delay = 0;
7432
7445
  animationOptions.type = false;
7433
7446
  }
@@ -7483,6 +7496,10 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
7483
7496
  (_a = this.nodes) === null || _a === void 0 ? void 0 : _a.forEach(resetRotation);
7484
7497
  this.animationId++;
7485
7498
  }
7499
+ getTransformTemplate() {
7500
+ var _a;
7501
+ return (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.getProps().transformTemplate;
7502
+ }
7486
7503
  willUpdate(shouldNotifyListeners = true) {
7487
7504
  var _a, _b, _c;
7488
7505
  if (this.root.isUpdateBlocked()) {
@@ -7497,12 +7514,14 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
7497
7514
  const node = this.path[i];
7498
7515
  node.shouldResetTransform = true;
7499
7516
  node.updateScroll("snapshot");
7517
+ if (node.options.layoutRoot) {
7518
+ node.willUpdate(false);
7519
+ }
7500
7520
  }
7501
7521
  const { layoutId, layout } = this.options;
7502
7522
  if (layoutId === undefined && !layout)
7503
7523
  return;
7504
- const transformTemplate = (_c = this.options.visualElement) === null || _c === void 0 ? void 0 : _c.getProps().transformTemplate;
7505
- this.prevTransformTemplateValue = transformTemplate === null || transformTemplate === void 0 ? void 0 : transformTemplate(this.latestValues, "");
7524
+ this.prevTransformTemplateValue = (_c = this.getTransformTemplate()) === null || _c === void 0 ? void 0 : _c(this.latestValues, "");
7506
7525
  this.updateSnapshot();
7507
7526
  shouldNotifyListeners && this.notifyListeners("willUpdate");
7508
7527
  }
@@ -7634,8 +7653,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
7634
7653
  return;
7635
7654
  const isResetRequested = this.isLayoutDirty || this.shouldResetTransform;
7636
7655
  const hasProjection = this.projectionDelta && !isDeltaZero(this.projectionDelta);
7637
- const transformTemplate = (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.getProps().transformTemplate;
7638
- const transformTemplateValue = transformTemplate === null || transformTemplate === void 0 ? void 0 : transformTemplate(this.latestValues, "");
7656
+ const transformTemplateValue = (_a = this.getTransformTemplate()) === null || _a === void 0 ? void 0 : _a(this.latestValues, "");
7639
7657
  const transformTemplateHasChanged = transformTemplateValue !== this.prevTransformTemplateValue;
7640
7658
  if (isResetRequested &&
7641
7659
  (hasProjection ||
@@ -7890,9 +7908,12 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
7890
7908
  getClosestProjectingParent() {
7891
7909
  if (!this.parent ||
7892
7910
  hasScale(this.parent.latestValues) ||
7893
- has2DTranslate(this.parent.latestValues))
7911
+ has2DTranslate(this.parent.latestValues)) {
7894
7912
  return undefined;
7895
- if ((this.parent.relativeTarget || this.parent.targetDelta) &&
7913
+ }
7914
+ if ((this.parent.relativeTarget ||
7915
+ this.parent.targetDelta ||
7916
+ this.parent.options.layoutRoot) &&
7896
7917
  this.parent.layout) {
7897
7918
  return this.parent;
7898
7919
  }
@@ -7987,7 +8008,10 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
7987
8008
  const snapshotLatestValues = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.latestValues) || {};
7988
8009
  const mixedValues = { ...this.latestValues };
7989
8010
  const targetDelta = createDelta();
7990
- this.relativeTarget = this.relativeTargetOrigin = undefined;
8011
+ if (!this.relativeParent ||
8012
+ !this.relativeParent.options.layoutRoot) {
8013
+ this.relativeTarget = this.relativeTargetOrigin = undefined;
8014
+ }
7991
8015
  this.attemptToResolveRelativeTarget = !hasOnlyRelativeTargetChanged;
7992
8016
  const relativeLayout = createBox();
7993
8017
  const isSharedLayoutAnimation = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.source) !== ((_a = this.layout) === null || _a === void 0 ? void 0 : _a.source);
@@ -8018,7 +8042,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
8018
8042
  this.scheduleRender();
8019
8043
  this.animationProgress = progress;
8020
8044
  };
8021
- this.mixTargetDelta(0);
8045
+ this.mixTargetDelta(this.options.layoutRoot ? 1000 : 0);
8022
8046
  }
8023
8047
  startAnimation(options) {
8024
8048
  var _a, _b;
@@ -8208,7 +8232,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
8208
8232
  visualElement.scheduleRender();
8209
8233
  }
8210
8234
  getProjectionStyles(styleProp = {}) {
8211
- var _a, _b, _c;
8235
+ var _a, _b;
8212
8236
  // TODO: Return lifecycle-persistent object
8213
8237
  const styles = {};
8214
8238
  if (!this.instance || this.isSVG)
@@ -8219,7 +8243,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
8219
8243
  else {
8220
8244
  styles.visibility = "";
8221
8245
  }
8222
- const transformTemplate = (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.getProps().transformTemplate;
8246
+ const transformTemplate = this.getTransformTemplate();
8223
8247
  if (this.needsReset) {
8224
8248
  this.needsReset = false;
8225
8249
  styles.opacity = "";
@@ -8264,7 +8288,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
8264
8288
  */
8265
8289
  styles.opacity =
8266
8290
  lead === this
8267
- ? (_c = (_b = valuesToRender.opacity) !== null && _b !== void 0 ? _b : this.latestValues.opacity) !== null && _c !== void 0 ? _c : 1
8291
+ ? (_b = (_a = valuesToRender.opacity) !== null && _a !== void 0 ? _a : this.latestValues.opacity) !== null && _b !== void 0 ? _b : 1
8268
8292
  : this.preserveOpacity
8269
8293
  ? this.latestValues.opacity
8270
8294
  : valuesToRender.opacityExit;
@@ -8394,6 +8418,11 @@ function notifyLayoutUpdate(node) {
8394
8418
  if (!boxEquals(relativeSnapshot, relativeLayout)) {
8395
8419
  hasRelativeTargetChanged = true;
8396
8420
  }
8421
+ if (relativeParent.options.layoutRoot) {
8422
+ node.relativeTarget = relativeLayout;
8423
+ node.relativeTargetOrigin = relativeSnapshot;
8424
+ node.relativeParent = relativeParent;
8425
+ }
8397
8426
  }
8398
8427
  }
8399
8428
  }
@@ -9840,14 +9869,37 @@ function useResetProjection() {
9840
9869
  return reset;
9841
9870
  }
9842
9871
 
9843
- function startOptimizedAppearAnimation(element, name, keyframes, options) {
9872
+ function startOptimizedAppearAnimation(element, name, keyframes, options, onReady) {
9844
9873
  window.MotionAppearAnimations || (window.MotionAppearAnimations = new Map());
9845
9874
  const id = element.dataset[optimizedAppearDataId];
9846
- const animation = animateStyle(element, name, keyframes, options);
9847
- if (id && animation) {
9848
- window.MotionAppearAnimations.set(appearStoreId(id, name), animation);
9875
+ if (!id)
9876
+ return;
9877
+ const storeId = appearStoreId(id, name);
9878
+ /**
9879
+ * Use a dummy animation to detect when Chrome is ready to start
9880
+ * painting the page and hold off from triggering the real animation
9881
+ * until then.
9882
+ */
9883
+ const readyAnimation = animateStyle(element, name, [keyframes[0], keyframes[0]], { duration: 1 });
9884
+ window.MotionAppearAnimations.set(storeId, {
9885
+ animation: readyAnimation,
9886
+ ready: false,
9887
+ });
9888
+ const startAnimation = () => {
9889
+ const animation = animateStyle(element, name, keyframes, options);
9890
+ window.MotionAppearAnimations.set(storeId, { animation, ready: true });
9891
+ if (onReady)
9892
+ onReady(animation);
9893
+ };
9894
+ if (readyAnimation.ready) {
9895
+ readyAnimation.ready.then(() => {
9896
+ readyAnimation.cancel();
9897
+ startAnimation();
9898
+ });
9899
+ }
9900
+ else {
9901
+ startAnimation();
9849
9902
  }
9850
- return animation;
9851
9903
  }
9852
9904
 
9853
9905
  const createObject = () => ({});
@@ -33,7 +33,7 @@ const velocitySampleDuration = 5;
33
33
  /**
34
34
  * This is based on the spring implementation of Wobble https://github.com/skevy/wobble
35
35
  */
36
- function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
36
+ function spring({ keyframes, restDelta, restSpeed, ...options }) {
37
37
  let origin = keyframes[0];
38
38
  let target = keyframes[keyframes.length - 1];
39
39
  /**
@@ -49,12 +49,15 @@ function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
49
49
  const initialDelta = target - origin;
50
50
  const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
51
51
  /**
52
- * If we're working within what looks like a 0-1 range, change the default restDelta
53
- * to 0.01
52
+ * If we're working on a granular scale, use smaller defaults for determining
53
+ * when the spring is finished.
54
+ *
55
+ * These defaults have been selected emprically based on what strikes a good
56
+ * ratio between feeling good and finishing as soon as changes are imperceptible.
54
57
  */
55
- if (restDelta === undefined) {
56
- restDelta = Math.min(Math.abs(target - origin) / 100, 0.4);
57
- }
58
+ const isGranularScale = Math.abs(initialDelta) < 5;
59
+ restSpeed || (restSpeed = isGranularScale ? 0.01 : 2);
60
+ restDelta || (restDelta = isGranularScale ? 0.005 : 0.5);
58
61
  if (dampingRatio < 1) {
59
62
  const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
60
63
  // Underdamped spring
@@ -5,8 +5,21 @@ import { appearStoreId } from './store-id.mjs';
5
5
  function handoffOptimizedAppearAnimation(id, name, value) {
6
6
  const { MotionAppearAnimations } = window;
7
7
  const animationId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
8
- const animation = MotionAppearAnimations && MotionAppearAnimations.get(animationId);
9
- if (animation) {
8
+ const { animation, ready } = (MotionAppearAnimations && MotionAppearAnimations.get(animationId)) ||
9
+ {};
10
+ if (!animation)
11
+ return 0;
12
+ const cancelOptimisedAnimation = () => {
13
+ MotionAppearAnimations.delete(animationId);
14
+ /**
15
+ * Animation.cancel() throws so it needs to be wrapped in a try/catch
16
+ */
17
+ try {
18
+ animation.cancel();
19
+ }
20
+ catch (e) { }
21
+ };
22
+ if (ready) {
10
23
  const sampledTime = performance.now();
11
24
  /**
12
25
  * Resync handoff animation with optimised animation.
@@ -31,19 +44,12 @@ function handoffOptimizedAppearAnimation(id, name, value) {
31
44
  * 2. As all independent transforms share a single transform animation, stopping
32
45
  * it synchronously would prevent subsequent transforms from handing off.
33
46
  */
34
- sync.render(() => {
35
- MotionAppearAnimations.delete(animationId);
36
- /**
37
- * Animation.cancel() throws so it needs to be wrapped in a try/catch
38
- */
39
- try {
40
- animation.cancel();
41
- }
42
- catch (e) { }
43
- });
47
+ sync.render(cancelOptimisedAnimation);
48
+ console.log(animation.currentTime);
44
49
  return animation.currentTime || 0;
45
50
  }
46
51
  else {
52
+ cancelOptimisedAnimation();
47
53
  return 0;
48
54
  }
49
55
  }
@@ -2,14 +2,37 @@ import { appearStoreId } from './store-id.mjs';
2
2
  import { animateStyle } from '../waapi/index.mjs';
3
3
  import { optimizedAppearDataId } from './data-id.mjs';
4
4
 
5
- function startOptimizedAppearAnimation(element, name, keyframes, options) {
5
+ function startOptimizedAppearAnimation(element, name, keyframes, options, onReady) {
6
6
  window.MotionAppearAnimations || (window.MotionAppearAnimations = new Map());
7
7
  const id = element.dataset[optimizedAppearDataId];
8
- const animation = animateStyle(element, name, keyframes, options);
9
- if (id && animation) {
10
- window.MotionAppearAnimations.set(appearStoreId(id, name), animation);
8
+ if (!id)
9
+ return;
10
+ const storeId = appearStoreId(id, name);
11
+ /**
12
+ * Use a dummy animation to detect when Chrome is ready to start
13
+ * painting the page and hold off from triggering the real animation
14
+ * until then.
15
+ */
16
+ const readyAnimation = animateStyle(element, name, [keyframes[0], keyframes[0]], { duration: 1 });
17
+ window.MotionAppearAnimations.set(storeId, {
18
+ animation: readyAnimation,
19
+ ready: false,
20
+ });
21
+ const startAnimation = () => {
22
+ const animation = animateStyle(element, name, keyframes, options);
23
+ window.MotionAppearAnimations.set(storeId, { animation, ready: true });
24
+ if (onReady)
25
+ onReady(animation);
26
+ };
27
+ if (readyAnimation.ready) {
28
+ readyAnimation.ready.then(() => {
29
+ readyAnimation.cancel();
30
+ startAnimation();
31
+ });
32
+ }
33
+ else {
34
+ startAnimation();
11
35
  }
12
- return animation;
13
36
  }
14
37
 
15
38
  export { startOptimizedAppearAnimation };
@@ -35,6 +35,8 @@ const validMotionProps = new Set([
35
35
  "layout",
36
36
  "layoutId",
37
37
  "layoutDependency",
38
+ "layoutScroll",
39
+ "layoutRoot",
38
40
  "onLayoutAnimationStart",
39
41
  "onLayoutAnimationComplete",
40
42
  "onLayoutMeasure",
@@ -62,7 +64,6 @@ const validMotionProps = new Set([
62
64
  "dragTransition",
63
65
  "onHoverStart",
64
66
  "onHoverEnd",
65
- "layoutScroll",
66
67
  ...inViewProps,
67
68
  ...tapProps,
68
69
  ...animationProps,
@@ -224,7 +224,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
224
224
  * relative to its parent has indeed changed. So here we check for that.
225
225
  */
226
226
  const hasOnlyRelativeTargetChanged = !hasLayoutChanged && hasRelativeTargetChanged;
227
- if (((_c = this.resumeFrom) === null || _c === void 0 ? void 0 : _c.instance) ||
227
+ if (this.options.layoutRoot ||
228
+ ((_c = this.resumeFrom) === null || _c === void 0 ? void 0 : _c.instance) ||
228
229
  hasOnlyRelativeTargetChanged ||
229
230
  (hasLayoutChanged &&
230
231
  (targetChanged || !this.currentAnimation))) {
@@ -238,7 +239,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
238
239
  onPlay: onLayoutAnimationStart,
239
240
  onComplete: onLayoutAnimationComplete,
240
241
  };
241
- if (visualElement.shouldReduceMotion) {
242
+ if (visualElement.shouldReduceMotion ||
243
+ this.options.layoutRoot) {
242
244
  animationOptions.delay = 0;
243
245
  animationOptions.type = false;
244
246
  }
@@ -294,6 +296,10 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
294
296
  (_a = this.nodes) === null || _a === void 0 ? void 0 : _a.forEach(resetRotation);
295
297
  this.animationId++;
296
298
  }
299
+ getTransformTemplate() {
300
+ var _a;
301
+ return (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.getProps().transformTemplate;
302
+ }
297
303
  willUpdate(shouldNotifyListeners = true) {
298
304
  var _a, _b, _c;
299
305
  if (this.root.isUpdateBlocked()) {
@@ -308,12 +314,14 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
308
314
  const node = this.path[i];
309
315
  node.shouldResetTransform = true;
310
316
  node.updateScroll("snapshot");
317
+ if (node.options.layoutRoot) {
318
+ node.willUpdate(false);
319
+ }
311
320
  }
312
321
  const { layoutId, layout } = this.options;
313
322
  if (layoutId === undefined && !layout)
314
323
  return;
315
- const transformTemplate = (_c = this.options.visualElement) === null || _c === void 0 ? void 0 : _c.getProps().transformTemplate;
316
- this.prevTransformTemplateValue = transformTemplate === null || transformTemplate === void 0 ? void 0 : transformTemplate(this.latestValues, "");
324
+ this.prevTransformTemplateValue = (_c = this.getTransformTemplate()) === null || _c === void 0 ? void 0 : _c(this.latestValues, "");
317
325
  this.updateSnapshot();
318
326
  shouldNotifyListeners && this.notifyListeners("willUpdate");
319
327
  }
@@ -445,8 +453,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
445
453
  return;
446
454
  const isResetRequested = this.isLayoutDirty || this.shouldResetTransform;
447
455
  const hasProjection = this.projectionDelta && !isDeltaZero(this.projectionDelta);
448
- const transformTemplate = (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.getProps().transformTemplate;
449
- const transformTemplateValue = transformTemplate === null || transformTemplate === void 0 ? void 0 : transformTemplate(this.latestValues, "");
456
+ const transformTemplateValue = (_a = this.getTransformTemplate()) === null || _a === void 0 ? void 0 : _a(this.latestValues, "");
450
457
  const transformTemplateHasChanged = transformTemplateValue !== this.prevTransformTemplateValue;
451
458
  if (isResetRequested &&
452
459
  (hasProjection ||
@@ -701,9 +708,12 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
701
708
  getClosestProjectingParent() {
702
709
  if (!this.parent ||
703
710
  hasScale(this.parent.latestValues) ||
704
- has2DTranslate(this.parent.latestValues))
711
+ has2DTranslate(this.parent.latestValues)) {
705
712
  return undefined;
706
- if ((this.parent.relativeTarget || this.parent.targetDelta) &&
713
+ }
714
+ if ((this.parent.relativeTarget ||
715
+ this.parent.targetDelta ||
716
+ this.parent.options.layoutRoot) &&
707
717
  this.parent.layout) {
708
718
  return this.parent;
709
719
  }
@@ -798,7 +808,10 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
798
808
  const snapshotLatestValues = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.latestValues) || {};
799
809
  const mixedValues = { ...this.latestValues };
800
810
  const targetDelta = createDelta();
801
- this.relativeTarget = this.relativeTargetOrigin = undefined;
811
+ if (!this.relativeParent ||
812
+ !this.relativeParent.options.layoutRoot) {
813
+ this.relativeTarget = this.relativeTargetOrigin = undefined;
814
+ }
802
815
  this.attemptToResolveRelativeTarget = !hasOnlyRelativeTargetChanged;
803
816
  const relativeLayout = createBox();
804
817
  const isSharedLayoutAnimation = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.source) !== ((_a = this.layout) === null || _a === void 0 ? void 0 : _a.source);
@@ -829,7 +842,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
829
842
  this.scheduleRender();
830
843
  this.animationProgress = progress;
831
844
  };
832
- this.mixTargetDelta(0);
845
+ this.mixTargetDelta(this.options.layoutRoot ? 1000 : 0);
833
846
  }
834
847
  startAnimation(options) {
835
848
  var _a, _b;
@@ -1019,7 +1032,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
1019
1032
  visualElement.scheduleRender();
1020
1033
  }
1021
1034
  getProjectionStyles(styleProp = {}) {
1022
- var _a, _b, _c;
1035
+ var _a, _b;
1023
1036
  // TODO: Return lifecycle-persistent object
1024
1037
  const styles = {};
1025
1038
  if (!this.instance || this.isSVG)
@@ -1030,7 +1043,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
1030
1043
  else {
1031
1044
  styles.visibility = "";
1032
1045
  }
1033
- const transformTemplate = (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.getProps().transformTemplate;
1046
+ const transformTemplate = this.getTransformTemplate();
1034
1047
  if (this.needsReset) {
1035
1048
  this.needsReset = false;
1036
1049
  styles.opacity = "";
@@ -1075,7 +1088,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
1075
1088
  */
1076
1089
  styles.opacity =
1077
1090
  lead === this
1078
- ? (_c = (_b = valuesToRender.opacity) !== null && _b !== void 0 ? _b : this.latestValues.opacity) !== null && _c !== void 0 ? _c : 1
1091
+ ? (_b = (_a = valuesToRender.opacity) !== null && _a !== void 0 ? _a : this.latestValues.opacity) !== null && _b !== void 0 ? _b : 1
1079
1092
  : this.preserveOpacity
1080
1093
  ? this.latestValues.opacity
1081
1094
  : valuesToRender.opacityExit;
@@ -1205,6 +1218,11 @@ function notifyLayoutUpdate(node) {
1205
1218
  if (!boxEquals(relativeSnapshot, relativeLayout)) {
1206
1219
  hasRelativeTargetChanged = true;
1207
1220
  }
1221
+ if (relativeParent.options.layoutRoot) {
1222
+ node.relativeTarget = relativeLayout;
1223
+ node.relativeTargetOrigin = relativeSnapshot;
1224
+ node.relativeParent = relativeParent;
1225
+ }
1208
1226
  }
1209
1227
  }
1210
1228
  }
@@ -238,7 +238,7 @@ class VisualElement {
238
238
  }
239
239
  if (!this.projection && ProjectionNodeConstructor) {
240
240
  this.projection = new ProjectionNodeConstructor(projectionId, this.latestValues, this.parent && this.parent.projection);
241
- const { layoutId, layout, drag, dragConstraints, layoutScroll } = renderedProps;
241
+ const { layoutId, layout, drag, dragConstraints, layoutScroll, layoutRoot, } = renderedProps;
242
242
  this.projection.setOptions({
243
243
  layoutId,
244
244
  layout,
@@ -256,6 +256,7 @@ class VisualElement {
256
256
  animationType: typeof layout === "string" ? layout : "both",
257
257
  initialPromotionConfig: initialLayoutGroupConfig,
258
258
  layoutScroll,
259
+ layoutRoot,
259
260
  });
260
261
  }
261
262
  return features;
@@ -22,7 +22,7 @@ function updateMotionValuesFromProps(element, next, prev) {
22
22
  * and warn against mismatches.
23
23
  */
24
24
  if (process.env.NODE_ENV === "development") {
25
- warnOnce(nextValue.version === "8.4.7", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.7 may not work as expected.`);
25
+ warnOnce(nextValue.version === "8.5.2-alpha.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.5.2-alpha.0 may not work as expected.`);
26
26
  }
27
27
  }
28
28
  else if (isMotionValue(prevValue)) {
@@ -25,7 +25,7 @@ class MotionValue {
25
25
  * This will be replaced by the build step with the latest version number.
26
26
  * When MotionValues are provided to motion components, warn if versions are mixed.
27
27
  */
28
- this.version = "8.4.7";
28
+ this.version = "8.5.2-alpha.0";
29
29
  /**
30
30
  * Duration, in milliseconds, since last updating frame.
31
31
  *