framer-motion 7.2.1 → 7.3.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
@@ -2235,7 +2235,7 @@ class MotionValue {
2235
2235
  * This will be replaced by the build step with the latest version number.
2236
2236
  * When MotionValues are provided to motion components, warn if versions are mixed.
2237
2237
  */
2238
- this.version = "7.2.1";
2238
+ this.version = "7.3.0";
2239
2239
  /**
2240
2240
  * Duration, in milliseconds, since last updating frame.
2241
2241
  *
@@ -4211,7 +4211,7 @@ function updateMotionValuesFromProps(element, next, prev) {
4211
4211
  * and warn against mismatches.
4212
4212
  */
4213
4213
  if (process.env.NODE_ENV === "development") {
4214
- warnOnce(nextValue.version === "7.2.1", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.2.1 may not work as expected.`);
4214
+ warnOnce(nextValue.version === "7.3.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.3.0 may not work as expected.`);
4215
4215
  }
4216
4216
  }
4217
4217
  else if (isMotionValue(prevValue)) {
@@ -5563,6 +5563,12 @@ function boxEquals(a, b) {
5563
5563
  a.y.min === b.y.min &&
5564
5564
  a.y.max === b.y.max);
5565
5565
  }
5566
+ function aspectRatio(box) {
5567
+ return calcLength(box.x) / calcLength(box.y);
5568
+ }
5569
+ function isCloseTo(a, b, max = 0.01) {
5570
+ return popmotion.distance(a, b) <= max;
5571
+ }
5566
5572
 
5567
5573
  class NodeStack {
5568
5574
  constructor() {
@@ -6528,9 +6534,27 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6528
6534
  this.completeAnimation();
6529
6535
  }
6530
6536
  applyTransformsToTarget() {
6531
- const { targetWithTransforms, target, layout, latestValues } = this.getLead();
6537
+ const lead = this.getLead();
6538
+ let { targetWithTransforms, target, layout, latestValues } = lead;
6532
6539
  if (!targetWithTransforms || !target || !layout)
6533
6540
  return;
6541
+ /**
6542
+ * If we're only animating position, and this element isn't the lead element,
6543
+ * then instead of projecting into the lead box we instead want to calculate
6544
+ * a new target that aligns the two boxes but maintains the layout shape.
6545
+ */
6546
+ if (this !== lead &&
6547
+ this.layout &&
6548
+ layout &&
6549
+ shouldAnimatePositionOnly(this.options.animationType, this.layout.actual, layout.actual)) {
6550
+ target = this.target || createBox();
6551
+ const xLength = calcLength(this.layout.actual.x);
6552
+ target.x.min = lead.target.x.min;
6553
+ target.x.max = target.x.min + xLength;
6554
+ const yLength = calcLength(this.layout.actual.y);
6555
+ target.y.min = lead.target.y.min;
6556
+ target.y.max = target.y.min + yLength;
6557
+ }
6534
6558
  copyBoxInto(targetWithTransforms, target);
6535
6559
  /**
6536
6560
  * Apply the latest user-set transforms to the targetBox to produce the targetBoxFinal.
@@ -6762,9 +6786,10 @@ function notifyLayoutUpdate(node) {
6762
6786
  snapshot &&
6763
6787
  node.hasListeners("didUpdate")) {
6764
6788
  const { actual: layout, measured: measuredLayout } = node.layout;
6789
+ const { animationType } = node.options;
6765
6790
  // TODO Maybe we want to also resize the layout snapshot so we don't trigger
6766
6791
  // animations for instance if layout="size" and an element has only changed position
6767
- if (node.options.animationType === "size") {
6792
+ if (animationType === "size") {
6768
6793
  eachAxis((axis) => {
6769
6794
  const axisSnapshot = snapshot.isShared
6770
6795
  ? snapshot.measured[axis]
@@ -6774,7 +6799,7 @@ function notifyLayoutUpdate(node) {
6774
6799
  axisSnapshot.max = axisSnapshot.min + length;
6775
6800
  });
6776
6801
  }
6777
- else if (node.options.animationType === "position") {
6802
+ else if (shouldAnimatePositionOnly(animationType, snapshot.layout, layout)) {
6778
6803
  eachAxis((axis) => {
6779
6804
  const axisSnapshot = snapshot.isShared
6780
6805
  ? snapshot.measured[axis]
@@ -6907,6 +6932,11 @@ function roundBox(box) {
6907
6932
  roundAxis(box.x);
6908
6933
  roundAxis(box.y);
6909
6934
  }
6935
+ function shouldAnimatePositionOnly(animationType, snapshot, layout) {
6936
+ return (animationType === "position" ||
6937
+ (animationType === "preserve-aspect" &&
6938
+ !isCloseTo(aspectRatio(snapshot), aspectRatio(layout))));
6939
+ }
6910
6940
 
6911
6941
  const DocumentProjectionNode = createProjectionNode({
6912
6942
  attachResizeListener: (ref, notify) => addDomEvent(ref, "resize", notify),
@@ -1,3 +1,6 @@
1
+ import { distance } from 'popmotion';
2
+ import { calcLength } from './delta-calc.mjs';
3
+
1
4
  function isAxisDeltaZero(delta) {
2
5
  return delta.translate === 0 && delta.scale === 1;
3
6
  }
@@ -10,5 +13,11 @@ function boxEquals(a, b) {
10
13
  a.y.min === b.y.min &&
11
14
  a.y.max === b.y.max);
12
15
  }
16
+ function aspectRatio(box) {
17
+ return calcLength(box.x) / calcLength(box.y);
18
+ }
19
+ function isCloseTo(a, b, max = 0.01) {
20
+ return distance(a, b) <= max;
21
+ }
13
22
 
14
- export { boxEquals, isDeltaZero };
23
+ export { aspectRatio, boxEquals, isCloseTo, isDeltaZero };
@@ -9,7 +9,7 @@ import { calcRelativePosition, calcRelativeBox, calcBoxDelta, calcLength } from
9
9
  import { removeBoxTransforms } from '../geometry/delta-remove.mjs';
10
10
  import { createBox, createDelta } from '../geometry/models.mjs';
11
11
  import { getValueTransition } from '../../animation/utils/transitions.mjs';
12
- import { boxEquals, isDeltaZero } from '../geometry/utils.mjs';
12
+ import { boxEquals, isDeltaZero, isCloseTo, aspectRatio } from '../geometry/utils.mjs';
13
13
  import { NodeStack } from '../shared/stack.mjs';
14
14
  import { scaleCorrectors } from '../styles/scale-correction.mjs';
15
15
  import { buildProjectionTransform } from '../styles/transform.mjs';
@@ -818,9 +818,27 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
818
818
  this.completeAnimation();
819
819
  }
820
820
  applyTransformsToTarget() {
821
- const { targetWithTransforms, target, layout, latestValues } = this.getLead();
821
+ const lead = this.getLead();
822
+ let { targetWithTransforms, target, layout, latestValues } = lead;
822
823
  if (!targetWithTransforms || !target || !layout)
823
824
  return;
825
+ /**
826
+ * If we're only animating position, and this element isn't the lead element,
827
+ * then instead of projecting into the lead box we instead want to calculate
828
+ * a new target that aligns the two boxes but maintains the layout shape.
829
+ */
830
+ if (this !== lead &&
831
+ this.layout &&
832
+ layout &&
833
+ shouldAnimatePositionOnly(this.options.animationType, this.layout.actual, layout.actual)) {
834
+ target = this.target || createBox();
835
+ const xLength = calcLength(this.layout.actual.x);
836
+ target.x.min = lead.target.x.min;
837
+ target.x.max = target.x.min + xLength;
838
+ const yLength = calcLength(this.layout.actual.y);
839
+ target.y.min = lead.target.y.min;
840
+ target.y.max = target.y.min + yLength;
841
+ }
824
842
  copyBoxInto(targetWithTransforms, target);
825
843
  /**
826
844
  * Apply the latest user-set transforms to the targetBox to produce the targetBoxFinal.
@@ -1052,9 +1070,10 @@ function notifyLayoutUpdate(node) {
1052
1070
  snapshot &&
1053
1071
  node.hasListeners("didUpdate")) {
1054
1072
  const { actual: layout, measured: measuredLayout } = node.layout;
1073
+ const { animationType } = node.options;
1055
1074
  // TODO Maybe we want to also resize the layout snapshot so we don't trigger
1056
1075
  // animations for instance if layout="size" and an element has only changed position
1057
- if (node.options.animationType === "size") {
1076
+ if (animationType === "size") {
1058
1077
  eachAxis((axis) => {
1059
1078
  const axisSnapshot = snapshot.isShared
1060
1079
  ? snapshot.measured[axis]
@@ -1064,7 +1083,7 @@ function notifyLayoutUpdate(node) {
1064
1083
  axisSnapshot.max = axisSnapshot.min + length;
1065
1084
  });
1066
1085
  }
1067
- else if (node.options.animationType === "position") {
1086
+ else if (shouldAnimatePositionOnly(animationType, snapshot.layout, layout)) {
1068
1087
  eachAxis((axis) => {
1069
1088
  const axisSnapshot = snapshot.isShared
1070
1089
  ? snapshot.measured[axis]
@@ -1197,5 +1216,10 @@ function roundBox(box) {
1197
1216
  roundAxis(box.x);
1198
1217
  roundAxis(box.y);
1199
1218
  }
1219
+ function shouldAnimatePositionOnly(animationType, snapshot, layout) {
1220
+ return (animationType === "position" ||
1221
+ (animationType === "preserve-aspect" &&
1222
+ !isCloseTo(aspectRatio(snapshot), aspectRatio(layout))));
1223
+ }
1200
1224
 
1201
1225
  export { createProjectionNode, mixAxis, mixAxisDelta, mixBox };
@@ -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 === "7.2.1", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.2.1 may not work as expected.`);
25
+ warnOnce(nextValue.version === "7.3.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.3.0 may not work as expected.`);
26
26
  }
27
27
  }
28
28
  else if (isMotionValue(prevValue)) {
@@ -24,7 +24,7 @@ class MotionValue {
24
24
  * This will be replaced by the build step with the latest version number.
25
25
  * When MotionValues are provided to motion components, warn if versions are mixed.
26
26
  */
27
- this.version = "7.2.1";
27
+ this.version = "7.3.0";
28
28
  /**
29
29
  * Duration, in milliseconds, since last updating frame.
30
30
  *
@@ -3403,7 +3403,7 @@
3403
3403
  * This will be replaced by the build step with the latest version number.
3404
3404
  * When MotionValues are provided to motion components, warn if versions are mixed.
3405
3405
  */
3406
- this.version = "7.2.1";
3406
+ this.version = "7.3.0";
3407
3407
  /**
3408
3408
  * Duration, in milliseconds, since last updating frame.
3409
3409
  *
@@ -5379,7 +5379,7 @@
5379
5379
  * and warn against mismatches.
5380
5380
  */
5381
5381
  {
5382
- warnOnce(nextValue.version === "7.2.1", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.2.1 may not work as expected.`);
5382
+ warnOnce(nextValue.version === "7.3.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.3.0 may not work as expected.`);
5383
5383
  }
5384
5384
  }
5385
5385
  else if (isMotionValue(prevValue)) {
@@ -6731,6 +6731,12 @@
6731
6731
  a.y.min === b.y.min &&
6732
6732
  a.y.max === b.y.max);
6733
6733
  }
6734
+ function aspectRatio(box) {
6735
+ return calcLength(box.x) / calcLength(box.y);
6736
+ }
6737
+ function isCloseTo(a, b, max = 0.01) {
6738
+ return distance(a, b) <= max;
6739
+ }
6734
6740
 
6735
6741
  class NodeStack {
6736
6742
  constructor() {
@@ -7696,9 +7702,27 @@
7696
7702
  this.completeAnimation();
7697
7703
  }
7698
7704
  applyTransformsToTarget() {
7699
- const { targetWithTransforms, target, layout, latestValues } = this.getLead();
7705
+ const lead = this.getLead();
7706
+ let { targetWithTransforms, target, layout, latestValues } = lead;
7700
7707
  if (!targetWithTransforms || !target || !layout)
7701
7708
  return;
7709
+ /**
7710
+ * If we're only animating position, and this element isn't the lead element,
7711
+ * then instead of projecting into the lead box we instead want to calculate
7712
+ * a new target that aligns the two boxes but maintains the layout shape.
7713
+ */
7714
+ if (this !== lead &&
7715
+ this.layout &&
7716
+ layout &&
7717
+ shouldAnimatePositionOnly(this.options.animationType, this.layout.actual, layout.actual)) {
7718
+ target = this.target || createBox();
7719
+ const xLength = calcLength(this.layout.actual.x);
7720
+ target.x.min = lead.target.x.min;
7721
+ target.x.max = target.x.min + xLength;
7722
+ const yLength = calcLength(this.layout.actual.y);
7723
+ target.y.min = lead.target.y.min;
7724
+ target.y.max = target.y.min + yLength;
7725
+ }
7702
7726
  copyBoxInto(targetWithTransforms, target);
7703
7727
  /**
7704
7728
  * Apply the latest user-set transforms to the targetBox to produce the targetBoxFinal.
@@ -7930,9 +7954,10 @@
7930
7954
  snapshot &&
7931
7955
  node.hasListeners("didUpdate")) {
7932
7956
  const { actual: layout, measured: measuredLayout } = node.layout;
7957
+ const { animationType } = node.options;
7933
7958
  // TODO Maybe we want to also resize the layout snapshot so we don't trigger
7934
7959
  // animations for instance if layout="size" and an element has only changed position
7935
- if (node.options.animationType === "size") {
7960
+ if (animationType === "size") {
7936
7961
  eachAxis((axis) => {
7937
7962
  const axisSnapshot = snapshot.isShared
7938
7963
  ? snapshot.measured[axis]
@@ -7942,7 +7967,7 @@
7942
7967
  axisSnapshot.max = axisSnapshot.min + length;
7943
7968
  });
7944
7969
  }
7945
- else if (node.options.animationType === "position") {
7970
+ else if (shouldAnimatePositionOnly(animationType, snapshot.layout, layout)) {
7946
7971
  eachAxis((axis) => {
7947
7972
  const axisSnapshot = snapshot.isShared
7948
7973
  ? snapshot.measured[axis]
@@ -8075,6 +8100,11 @@
8075
8100
  roundAxis(box.x);
8076
8101
  roundAxis(box.y);
8077
8102
  }
8103
+ function shouldAnimatePositionOnly(animationType, snapshot, layout) {
8104
+ return (animationType === "position" ||
8105
+ (animationType === "preserve-aspect" &&
8106
+ !isCloseTo(aspectRatio(snapshot), aspectRatio(layout))));
8107
+ }
8078
8108
 
8079
8109
  const DocumentProjectionNode = createProjectionNode({
8080
8110
  attachResizeListener: (ref, notify) => addDomEvent(ref, "resize", notify),