framer-motion 7.6.7 → 7.6.9

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
@@ -254,11 +254,11 @@ const globalProjectionState = {
254
254
  hasEverUpdated: false,
255
255
  };
256
256
 
257
- let id$1 = 1;
257
+ let id$2 = 1;
258
258
  function useProjectionId() {
259
259
  return useConstant(() => {
260
260
  if (globalProjectionState.hasEverUpdated) {
261
- return id$1++;
261
+ return id$2++;
262
262
  }
263
263
  });
264
264
  }
@@ -2258,7 +2258,7 @@ class MotionValue {
2258
2258
  * This will be replaced by the build step with the latest version number.
2259
2259
  * When MotionValues are provided to motion components, warn if versions are mixed.
2260
2260
  */
2261
- this.version = "7.6.7";
2261
+ this.version = "7.6.9";
2262
2262
  /**
2263
2263
  * Duration, in milliseconds, since last updating frame.
2264
2264
  *
@@ -3615,7 +3615,10 @@ function applyTreeDeltas(box, treeScale, treePath, isSharedTransition = false) {
3615
3615
  node.options.layoutScroll &&
3616
3616
  node.scroll &&
3617
3617
  node !== node.root) {
3618
- transformBox(box, { x: -node.scroll.x, y: -node.scroll.y });
3618
+ transformBox(box, {
3619
+ x: -node.scroll.offset.x,
3620
+ y: -node.scroll.offset.y,
3621
+ });
3619
3622
  }
3620
3623
  if (delta) {
3621
3624
  // Incoporate each ancestor's scale into a culmulative treeScale for this component
@@ -3664,8 +3667,8 @@ function measurePageBox(element, rootProjectionNode, transformPagePoint) {
3664
3667
  const viewportBox = measureViewportBox(element, transformPagePoint);
3665
3668
  const { scroll } = rootProjectionNode;
3666
3669
  if (scroll) {
3667
- translateAxis(viewportBox.x, scroll.x);
3668
- translateAxis(viewportBox.y, scroll.y);
3670
+ translateAxis(viewportBox.x, scroll.offset.x);
3671
+ translateAxis(viewportBox.y, scroll.offset.y);
3669
3672
  }
3670
3673
  return viewportBox;
3671
3674
  }
@@ -4521,7 +4524,7 @@ function updateMotionValuesFromProps(element, next, prev) {
4521
4524
  * and warn against mismatches.
4522
4525
  */
4523
4526
  if (process.env.NODE_ENV === "development") {
4524
- warnOnce(nextValue.version === "7.6.7", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.6.7 may not work as expected.`);
4527
+ warnOnce(nextValue.version === "7.6.9", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.6.9 may not work as expected.`);
4525
4528
  }
4526
4529
  }
4527
4530
  else if (isMotionValue(prevValue)) {
@@ -4723,10 +4726,14 @@ class VisualElement {
4723
4726
  this.current = null;
4724
4727
  }
4725
4728
  bindToMotionValue(key, value) {
4729
+ const valueIsTransform = transformProps.has(key);
4726
4730
  const removeOnChange = value.onChange((latestValue) => {
4727
4731
  this.latestValues[key] = latestValue;
4728
4732
  this.props.onUpdate &&
4729
4733
  sync__default["default"].update(this.notifyUpdate, false, true);
4734
+ if (valueIsTransform && this.projection) {
4735
+ this.projection.isProjectionDirty = true;
4736
+ }
4730
4737
  });
4731
4738
  const removeOnRenderRequest = value.onRenderRequest(this.scheduleRender);
4732
4739
  this.valueSubscriptions.set(key, () => {
@@ -5599,7 +5606,6 @@ class NodeStack {
5599
5606
  node.snapshot = prevLead.snapshot;
5600
5607
  node.snapshot.latestValues =
5601
5608
  prevLead.animationValues || prevLead.latestValues;
5602
- node.snapshot.isShared = true;
5603
5609
  }
5604
5610
  if ((_a = node.root) === null || _a === void 0 ? void 0 : _a.isUpdating) {
5605
5611
  node.isLayoutDirty = true;
@@ -5645,8 +5651,8 @@ class NodeStack {
5645
5651
  }
5646
5652
  }
5647
5653
 
5648
- const identityProjection = "translate3d(0px, 0px, 0) scale(1, 1) scale(1, 1)";
5649
5654
  function buildProjectionTransform(delta, treeScale, latestTransform) {
5655
+ let transform = "";
5650
5656
  /**
5651
5657
  * The translations we use to calculate are always relative to the viewport coordinate space.
5652
5658
  * But when we apply scales, we also scale the coordinate space of an element and its children.
@@ -5655,12 +5661,16 @@ function buildProjectionTransform(delta, treeScale, latestTransform) {
5655
5661
  */
5656
5662
  const xTranslate = delta.x.translate / treeScale.x;
5657
5663
  const yTranslate = delta.y.translate / treeScale.y;
5658
- let transform = `translate3d(${xTranslate}px, ${yTranslate}px, 0) `;
5664
+ if (xTranslate || yTranslate) {
5665
+ transform = `translate3d(${xTranslate}px, ${yTranslate}px, 0) `;
5666
+ }
5659
5667
  /**
5660
5668
  * Apply scale correction for the tree transform.
5661
5669
  * This will apply scale to the screen-orientated axes.
5662
5670
  */
5663
- transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
5671
+ if (treeScale.x !== 1 || treeScale.y !== 1) {
5672
+ transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
5673
+ }
5664
5674
  if (latestTransform) {
5665
5675
  const { rotate, rotateX, rotateY } = latestTransform;
5666
5676
  if (rotate)
@@ -5676,8 +5686,10 @@ function buildProjectionTransform(delta, treeScale, latestTransform) {
5676
5686
  */
5677
5687
  const elementScaleX = delta.x.scale * treeScale.x;
5678
5688
  const elementScaleY = delta.y.scale * treeScale.y;
5679
- transform += `scale(${elementScaleX}, ${elementScaleY})`;
5680
- return transform === identityProjection ? "none" : transform;
5689
+ if (elementScaleX !== 1 || elementScaleY !== 1) {
5690
+ transform += `scale(${elementScaleX}, ${elementScaleY})`;
5691
+ }
5692
+ return transform || "none";
5681
5693
  }
5682
5694
 
5683
5695
  const compareByDepth = (a, b) => a.depth - b.depth;
@@ -5708,9 +5720,18 @@ const transformAxes = ["", "X", "Y", "Z"];
5708
5720
  * which has a noticeable difference in spring animations
5709
5721
  */
5710
5722
  const animationTarget = 1000;
5723
+ let id$1 = 0;
5711
5724
  function createProjectionNode({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
5712
5725
  return class ProjectionNode {
5713
5726
  constructor(elementId, latestValues = {}, parent = defaultParent === null || defaultParent === void 0 ? void 0 : defaultParent()) {
5727
+ /**
5728
+ * A unique ID generated for every projection node.
5729
+ */
5730
+ this.id = id$1++;
5731
+ /**
5732
+ * An id that represents a unique session instigated by startUpdate.
5733
+ */
5734
+ this.animationId = 0;
5714
5735
  /**
5715
5736
  * A Set containing all this component's children. This is used to iterate
5716
5737
  * through the children.
@@ -5737,6 +5758,11 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
5737
5758
  * and if one node is dirtied, they all are.
5738
5759
  */
5739
5760
  this.isLayoutDirty = false;
5761
+ /**
5762
+ * Flag to true if we think the projection calculations for this or any
5763
+ * child might need recalculating as a result of an updated transform or layout animation.
5764
+ */
5765
+ this.isProjectionDirty = false;
5740
5766
  /**
5741
5767
  * Block layout updates for instant layout transitions throughout the tree.
5742
5768
  */
@@ -5819,8 +5845,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
5819
5845
  hasListeners(name) {
5820
5846
  return this.eventHandlers.has(name);
5821
5847
  }
5822
- registerPotentialNode(id, node) {
5823
- this.potentialNodes.set(id, node);
5848
+ registerPotentialNode(elementId, node) {
5849
+ this.potentialNodes.set(elementId, node);
5824
5850
  }
5825
5851
  /**
5826
5852
  * Lifecycles
@@ -5953,6 +5979,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
5953
5979
  return;
5954
5980
  this.isUpdating = true;
5955
5981
  (_a = this.nodes) === null || _a === void 0 ? void 0 : _a.forEach(resetRotation);
5982
+ this.animationId++;
5956
5983
  }
5957
5984
  willUpdate(shouldNotifyListeners = true) {
5958
5985
  var _a, _b, _c;
@@ -5967,11 +5994,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
5967
5994
  for (let i = 0; i < this.path.length; i++) {
5968
5995
  const node = this.path[i];
5969
5996
  node.shouldResetTransform = true;
5970
- /**
5971
- * TODO: Check we haven't updated the scroll
5972
- * since the last didUpdate
5973
- */
5974
- node.updateScroll();
5997
+ node.updateScroll("snapshot");
5975
5998
  }
5976
5999
  const { layoutId, layout } = this.options;
5977
6000
  if (layoutId === undefined && !layout)
@@ -6087,10 +6110,20 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6087
6110
  this.notifyListeners("measure", this.layout.layoutBox);
6088
6111
  (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.notify("LayoutMeasure", this.layout.layoutBox, prevLayout === null || prevLayout === void 0 ? void 0 : prevLayout.layoutBox);
6089
6112
  }
6090
- updateScroll() {
6091
- if (this.options.layoutScroll && this.instance) {
6092
- this.isScrollRoot = checkIsScrollRoot(this.instance);
6093
- this.scroll = measureScroll(this.instance);
6113
+ updateScroll(phase = "measure") {
6114
+ let needsMeasurement = Boolean(this.options.layoutScroll && this.instance);
6115
+ if (this.scroll &&
6116
+ this.scroll.animationId === this.root.animationId &&
6117
+ this.scroll.phase === phase) {
6118
+ needsMeasurement = false;
6119
+ }
6120
+ if (needsMeasurement) {
6121
+ this.scroll = {
6122
+ animationId: this.root.animationId,
6123
+ phase,
6124
+ isRoot: checkIsScrollRoot(this.instance),
6125
+ offset: measureScroll(this.instance),
6126
+ };
6094
6127
  }
6095
6128
  }
6096
6129
  resetTransform() {
@@ -6112,6 +6145,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6112
6145
  }
6113
6146
  }
6114
6147
  measure(removeTransform = true) {
6148
+ var _a;
6115
6149
  const pageBox = this.measurePageBox();
6116
6150
  let layoutBox = this.removeElementScroll(pageBox);
6117
6151
  /**
@@ -6123,10 +6157,17 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6123
6157
  layoutBox = this.removeTransform(layoutBox);
6124
6158
  }
6125
6159
  roundBox(layoutBox);
6160
+ const positionStyle = (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.readValue("position");
6161
+ const position = positionStyle === "fixed" || positionStyle === "sticky"
6162
+ ? positionStyle
6163
+ : "static";
6126
6164
  return {
6165
+ animationId: this.root.animationId,
6127
6166
  measuredBox: pageBox,
6128
6167
  layoutBox,
6129
6168
  latestValues: {},
6169
+ source: this.id,
6170
+ position,
6130
6171
  };
6131
6172
  }
6132
6173
  measurePageBox() {
@@ -6137,8 +6178,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6137
6178
  // Remove viewport scroll to give page-relative coordinates
6138
6179
  const { scroll } = this.root;
6139
6180
  if (scroll) {
6140
- translateAxis(box.x, scroll.x);
6141
- translateAxis(box.y, scroll.y);
6181
+ translateAxis(box.x, scroll.offset.x);
6182
+ translateAxis(box.y, scroll.offset.y);
6142
6183
  }
6143
6184
  return box;
6144
6185
  }
@@ -6151,13 +6192,13 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6151
6192
  */
6152
6193
  for (let i = 0; i < this.path.length; i++) {
6153
6194
  const node = this.path[i];
6154
- const { scroll, options, isScrollRoot } = node;
6195
+ const { scroll, options } = node;
6155
6196
  if (node !== this.root && scroll && options.layoutScroll) {
6156
6197
  /**
6157
6198
  * If this is a new scroll root, we want to remove all previous scrolls
6158
6199
  * from the viewport box.
6159
6200
  */
6160
- if (isScrollRoot) {
6201
+ if (scroll.isRoot) {
6161
6202
  copyBoxInto(boxWithoutScroll, box);
6162
6203
  const { scroll: rootScroll } = this.root;
6163
6204
  /**
@@ -6165,12 +6206,12 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6165
6206
  * to the measured bounding box.
6166
6207
  */
6167
6208
  if (rootScroll) {
6168
- translateAxis(boxWithoutScroll.x, -rootScroll.x);
6169
- translateAxis(boxWithoutScroll.y, -rootScroll.y);
6209
+ translateAxis(boxWithoutScroll.x, -rootScroll.offset.x);
6210
+ translateAxis(boxWithoutScroll.y, -rootScroll.offset.y);
6170
6211
  }
6171
6212
  }
6172
- translateAxis(boxWithoutScroll.x, scroll.x);
6173
- translateAxis(boxWithoutScroll.y, scroll.y);
6213
+ translateAxis(boxWithoutScroll.x, scroll.offset.x);
6214
+ translateAxis(boxWithoutScroll.y, scroll.offset.y);
6174
6215
  }
6175
6216
  }
6176
6217
  return boxWithoutScroll;
@@ -6185,8 +6226,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6185
6226
  node.scroll &&
6186
6227
  node !== node.root) {
6187
6228
  transformBox(withTransforms, {
6188
- x: -node.scroll.x,
6189
- y: -node.scroll.y,
6229
+ x: -node.scroll.offset.x,
6230
+ y: -node.scroll.offset.y,
6190
6231
  });
6191
6232
  }
6192
6233
  if (!hasTransform(node.latestValues))
@@ -6224,6 +6265,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6224
6265
  */
6225
6266
  setTargetDelta(delta) {
6226
6267
  this.targetDelta = delta;
6268
+ this.isProjectionDirty = true;
6227
6269
  this.root.scheduleUpdateProjection();
6228
6270
  }
6229
6271
  setOptions(options) {
@@ -6247,6 +6289,14 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6247
6289
  */
6248
6290
  resolveTargetDelta() {
6249
6291
  var _a;
6292
+ /**
6293
+ * Propagate isProjectionDirty. Nodes are ordered by depth, so if the parent here
6294
+ * is dirty we can simply pass this forward.
6295
+ */
6296
+ this.isProjectionDirty || (this.isProjectionDirty = this.getLead().isProjectionDirty ||
6297
+ Boolean(this.parent && this.parent.isProjectionDirty));
6298
+ if (!this.isProjectionDirty)
6299
+ return;
6250
6300
  const { layout, layoutId } = this.options;
6251
6301
  /**
6252
6302
  * If we have no layout, we can't perform projection, so early return
@@ -6350,6 +6400,9 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6350
6400
  }
6351
6401
  calcProjection() {
6352
6402
  var _a;
6403
+ if (!this.isProjectionDirty)
6404
+ return;
6405
+ this.isProjectionDirty = false;
6353
6406
  const { layout, layoutId } = this.options;
6354
6407
  /**
6355
6408
  * If this section of the tree isn't animating we can
@@ -6420,7 +6473,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6420
6473
  }
6421
6474
  }
6422
6475
  setAnimationOrigin(delta, hasOnlyRelativeTargetChanged = false) {
6423
- var _a;
6476
+ var _a, _b;
6424
6477
  const snapshot = this.snapshot;
6425
6478
  const snapshotLatestValues = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.latestValues) || {};
6426
6479
  const mixedValues = { ...this.latestValues };
@@ -6428,8 +6481,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6428
6481
  this.relativeTarget = this.relativeTargetOrigin = undefined;
6429
6482
  this.attemptToResolveRelativeTarget = !hasOnlyRelativeTargetChanged;
6430
6483
  const relativeLayout = createBox();
6431
- const isSharedLayoutAnimation = snapshot === null || snapshot === void 0 ? void 0 : snapshot.isShared;
6432
- const isOnlyMember = (((_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.members.length) || 0) <= 1;
6484
+ const isSharedLayoutAnimation = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.source) !== ((_a = this.layout) === null || _a === void 0 ? void 0 : _a.source);
6485
+ const isOnlyMember = (((_b = this.getStack()) === null || _b === void 0 ? void 0 : _b.members.length) || 0) <= 1;
6433
6486
  const shouldCrossfadeOpacity = Boolean(isSharedLayoutAnimation &&
6434
6487
  !isOnlyMember &&
6435
6488
  this.options.crossfade === true &&
@@ -6610,25 +6663,30 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6610
6663
  return;
6611
6664
  // If there's no detected rotation values, we can early return without a forced render.
6612
6665
  let hasRotate = false;
6613
- // Keep a record of all the values we've reset
6614
- const resetValues = {};
6615
- // Check the rotate value of all axes and reset to 0
6616
- for (let i = 0; i < transformAxes.length; i++) {
6617
- const axis = transformAxes[i];
6618
- const key = "rotate" + axis;
6619
- // If this rotation doesn't exist as a motion value, then we don't
6620
- // need to reset it
6621
- if (!visualElement.getStaticValue(key)) {
6622
- continue;
6623
- }
6666
+ /**
6667
+ * An unrolled check for rotation values. Most elements don't have any rotation and
6668
+ * skipping the nested loop and new object creation is 50% faster.
6669
+ */
6670
+ const { latestValues } = visualElement;
6671
+ if (latestValues.rotate ||
6672
+ latestValues.rotateX ||
6673
+ latestValues.rotateY ||
6674
+ latestValues.rotateZ) {
6624
6675
  hasRotate = true;
6625
- // Record the rotation and then temporarily set it to 0
6626
- resetValues[key] = visualElement.getStaticValue(key);
6627
- visualElement.setStaticValue(key, 0);
6628
6676
  }
6629
6677
  // If there's no rotation values, we don't need to do any more.
6630
6678
  if (!hasRotate)
6631
6679
  return;
6680
+ const resetValues = {};
6681
+ // Check the rotate value of all axes and reset to 0
6682
+ for (let i = 0; i < transformAxes.length; i++) {
6683
+ const key = "rotate" + transformAxes[i];
6684
+ // Record the rotation and then temporarily set it to 0
6685
+ if (latestValues[key]) {
6686
+ resetValues[key] = latestValues[key];
6687
+ visualElement.setStaticValue(key, 0);
6688
+ }
6689
+ }
6632
6690
  // Force a render of this element to apply the transform with all rotations
6633
6691
  // set to 0.
6634
6692
  visualElement === null || visualElement === void 0 ? void 0 : visualElement.render();
@@ -6770,11 +6828,12 @@ function notifyLayoutUpdate(node) {
6770
6828
  node.hasListeners("didUpdate")) {
6771
6829
  const { layoutBox: layout, measuredBox: measuredLayout } = node.layout;
6772
6830
  const { animationType } = node.options;
6831
+ const isShared = snapshot.source !== node.layout.source;
6773
6832
  // TODO Maybe we want to also resize the layout snapshot so we don't trigger
6774
6833
  // animations for instance if layout="size" and an element has only changed position
6775
6834
  if (animationType === "size") {
6776
6835
  eachAxis((axis) => {
6777
- const axisSnapshot = snapshot.isShared
6836
+ const axisSnapshot = isShared
6778
6837
  ? snapshot.measuredBox[axis]
6779
6838
  : snapshot.layoutBox[axis];
6780
6839
  const length = calcLength(axisSnapshot);
@@ -6784,7 +6843,7 @@ function notifyLayoutUpdate(node) {
6784
6843
  }
6785
6844
  else if (shouldAnimatePositionOnly(animationType, snapshot.layoutBox, layout)) {
6786
6845
  eachAxis((axis) => {
6787
- const axisSnapshot = snapshot.isShared
6846
+ const axisSnapshot = isShared
6788
6847
  ? snapshot.measuredBox[axis]
6789
6848
  : snapshot.layoutBox[axis];
6790
6849
  const length = calcLength(layout[axis]);
@@ -6794,7 +6853,7 @@ function notifyLayoutUpdate(node) {
6794
6853
  const layoutDelta = createDelta();
6795
6854
  calcBoxDelta(layoutDelta, layout, snapshot.layoutBox);
6796
6855
  const visualDelta = createDelta();
6797
- if (snapshot.isShared) {
6856
+ if (isShared) {
6798
6857
  calcBoxDelta(visualDelta, node.applyTransform(measuredLayout, true), snapshot.measuredBox);
6799
6858
  }
6800
6859
  else {
@@ -6890,7 +6949,7 @@ const defaultLayoutTransition = {
6890
6949
  duration: 0.45,
6891
6950
  ease: [0.4, 0, 0.1, 1],
6892
6951
  };
6893
- function mountNodeEarly(node, id) {
6952
+ function mountNodeEarly(node, elementId) {
6894
6953
  /**
6895
6954
  * Rather than searching the DOM from document we can search the
6896
6955
  * path for the deepest mounted ancestor and search from there
@@ -6903,7 +6962,7 @@ function mountNodeEarly(node, id) {
6903
6962
  }
6904
6963
  }
6905
6964
  const searchElement = searchNode && searchNode !== node.root ? searchNode.instance : document;
6906
- const element = searchElement.querySelector(`[data-projection-id="${id}"]`);
6965
+ const element = searchElement.querySelector(`[data-projection-id="${elementId}"]`);
6907
6966
  if (element)
6908
6967
  node.mount(element, true);
6909
6968
  }
@@ -56,7 +56,10 @@ function applyTreeDeltas(box, treeScale, treePath, isSharedTransition = false) {
56
56
  node.options.layoutScroll &&
57
57
  node.scroll &&
58
58
  node !== node.root) {
59
- transformBox(box, { x: -node.scroll.x, y: -node.scroll.y });
59
+ transformBox(box, {
60
+ x: -node.scroll.offset.x,
61
+ y: -node.scroll.offset.y,
62
+ });
60
63
  }
61
64
  if (delta) {
62
65
  // Incoporate each ancestor's scale into a culmulative treeScale for this component