framer-motion 7.6.6 → 7.6.8

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.6";
2261
+ this.version = "7.6.8";
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.6", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.6.6 may not work as expected.`);
4527
+ warnOnce(nextValue.version === "7.6.8", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.6.8 may not work as expected.`);
4525
4528
  }
4526
4529
  }
4527
4530
  else if (isMotionValue(prevValue)) {
@@ -4675,17 +4678,6 @@ class VisualElement {
4675
4678
  }
4676
4679
  }
4677
4680
  }
4678
- /**
4679
- * Update external values with initial values
4680
- */
4681
- if (props.values) {
4682
- for (const key in props.values) {
4683
- const value = props.values[key];
4684
- if (latestValues[key] !== undefined && isMotionValue(value)) {
4685
- value.set(latestValues[key]);
4686
- }
4687
- }
4688
- }
4689
4681
  }
4690
4682
  /**
4691
4683
  * This method takes React props and returns found MotionValues. For example, HTML
@@ -5610,7 +5602,6 @@ class NodeStack {
5610
5602
  node.snapshot = prevLead.snapshot;
5611
5603
  node.snapshot.latestValues =
5612
5604
  prevLead.animationValues || prevLead.latestValues;
5613
- node.snapshot.isShared = true;
5614
5605
  }
5615
5606
  if ((_a = node.root) === null || _a === void 0 ? void 0 : _a.isUpdating) {
5616
5607
  node.isLayoutDirty = true;
@@ -5656,8 +5647,8 @@ class NodeStack {
5656
5647
  }
5657
5648
  }
5658
5649
 
5659
- const identityProjection = "translate3d(0px, 0px, 0) scale(1, 1) scale(1, 1)";
5660
5650
  function buildProjectionTransform(delta, treeScale, latestTransform) {
5651
+ let transform = "";
5661
5652
  /**
5662
5653
  * The translations we use to calculate are always relative to the viewport coordinate space.
5663
5654
  * But when we apply scales, we also scale the coordinate space of an element and its children.
@@ -5666,12 +5657,16 @@ function buildProjectionTransform(delta, treeScale, latestTransform) {
5666
5657
  */
5667
5658
  const xTranslate = delta.x.translate / treeScale.x;
5668
5659
  const yTranslate = delta.y.translate / treeScale.y;
5669
- let transform = `translate3d(${xTranslate}px, ${yTranslate}px, 0) `;
5660
+ if (xTranslate || yTranslate) {
5661
+ transform = `translate3d(${xTranslate}px, ${yTranslate}px, 0) `;
5662
+ }
5670
5663
  /**
5671
5664
  * Apply scale correction for the tree transform.
5672
5665
  * This will apply scale to the screen-orientated axes.
5673
5666
  */
5674
- transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
5667
+ if (treeScale.x !== 1 || treeScale.y !== 1) {
5668
+ transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
5669
+ }
5675
5670
  if (latestTransform) {
5676
5671
  const { rotate, rotateX, rotateY } = latestTransform;
5677
5672
  if (rotate)
@@ -5687,8 +5682,10 @@ function buildProjectionTransform(delta, treeScale, latestTransform) {
5687
5682
  */
5688
5683
  const elementScaleX = delta.x.scale * treeScale.x;
5689
5684
  const elementScaleY = delta.y.scale * treeScale.y;
5690
- transform += `scale(${elementScaleX}, ${elementScaleY})`;
5691
- return transform === identityProjection ? "none" : transform;
5685
+ if (elementScaleX !== 1 || elementScaleY !== 1) {
5686
+ transform += `scale(${elementScaleX}, ${elementScaleY})`;
5687
+ }
5688
+ return transform || "none";
5692
5689
  }
5693
5690
 
5694
5691
  const compareByDepth = (a, b) => a.depth - b.depth;
@@ -5719,9 +5716,18 @@ const transformAxes = ["", "X", "Y", "Z"];
5719
5716
  * which has a noticeable difference in spring animations
5720
5717
  */
5721
5718
  const animationTarget = 1000;
5719
+ let id$1 = 0;
5722
5720
  function createProjectionNode({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
5723
5721
  return class ProjectionNode {
5724
5722
  constructor(elementId, latestValues = {}, parent = defaultParent === null || defaultParent === void 0 ? void 0 : defaultParent()) {
5723
+ /**
5724
+ * A unique ID generated for every projection node.
5725
+ */
5726
+ this.id = id$1++;
5727
+ /**
5728
+ * An id that represents a unique session instigated by startUpdate.
5729
+ */
5730
+ this.animationId = 0;
5725
5731
  /**
5726
5732
  * A Set containing all this component's children. This is used to iterate
5727
5733
  * through the children.
@@ -5830,8 +5836,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
5830
5836
  hasListeners(name) {
5831
5837
  return this.eventHandlers.has(name);
5832
5838
  }
5833
- registerPotentialNode(id, node) {
5834
- this.potentialNodes.set(id, node);
5839
+ registerPotentialNode(elementId, node) {
5840
+ this.potentialNodes.set(elementId, node);
5835
5841
  }
5836
5842
  /**
5837
5843
  * Lifecycles
@@ -5964,6 +5970,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
5964
5970
  return;
5965
5971
  this.isUpdating = true;
5966
5972
  (_a = this.nodes) === null || _a === void 0 ? void 0 : _a.forEach(resetRotation);
5973
+ this.animationId++;
5967
5974
  }
5968
5975
  willUpdate(shouldNotifyListeners = true) {
5969
5976
  var _a, _b, _c;
@@ -5978,11 +5985,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
5978
5985
  for (let i = 0; i < this.path.length; i++) {
5979
5986
  const node = this.path[i];
5980
5987
  node.shouldResetTransform = true;
5981
- /**
5982
- * TODO: Check we haven't updated the scroll
5983
- * since the last didUpdate
5984
- */
5985
- node.updateScroll();
5988
+ node.updateScroll("snapshot");
5986
5989
  }
5987
5990
  const { layoutId, layout } = this.options;
5988
5991
  if (layoutId === undefined && !layout)
@@ -6098,10 +6101,20 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6098
6101
  this.notifyListeners("measure", this.layout.layoutBox);
6099
6102
  (_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);
6100
6103
  }
6101
- updateScroll() {
6102
- if (this.options.layoutScroll && this.instance) {
6103
- this.isScrollRoot = checkIsScrollRoot(this.instance);
6104
- this.scroll = measureScroll(this.instance);
6104
+ updateScroll(phase = "measure") {
6105
+ let needsMeasurement = Boolean(this.options.layoutScroll && this.instance);
6106
+ if (this.scroll &&
6107
+ this.scroll.animationId === this.root.animationId &&
6108
+ this.scroll.phase === phase) {
6109
+ needsMeasurement = false;
6110
+ }
6111
+ if (needsMeasurement) {
6112
+ this.scroll = {
6113
+ animationId: this.root.animationId,
6114
+ phase,
6115
+ isRoot: checkIsScrollRoot(this.instance),
6116
+ offset: measureScroll(this.instance),
6117
+ };
6105
6118
  }
6106
6119
  }
6107
6120
  resetTransform() {
@@ -6123,6 +6136,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6123
6136
  }
6124
6137
  }
6125
6138
  measure(removeTransform = true) {
6139
+ var _a;
6126
6140
  const pageBox = this.measurePageBox();
6127
6141
  let layoutBox = this.removeElementScroll(pageBox);
6128
6142
  /**
@@ -6134,10 +6148,17 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6134
6148
  layoutBox = this.removeTransform(layoutBox);
6135
6149
  }
6136
6150
  roundBox(layoutBox);
6151
+ const positionStyle = (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.readValue("position");
6152
+ const position = positionStyle === "fixed" || positionStyle === "sticky"
6153
+ ? positionStyle
6154
+ : "static";
6137
6155
  return {
6156
+ animationId: this.root.animationId,
6138
6157
  measuredBox: pageBox,
6139
6158
  layoutBox,
6140
6159
  latestValues: {},
6160
+ source: this.id,
6161
+ position,
6141
6162
  };
6142
6163
  }
6143
6164
  measurePageBox() {
@@ -6148,8 +6169,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6148
6169
  // Remove viewport scroll to give page-relative coordinates
6149
6170
  const { scroll } = this.root;
6150
6171
  if (scroll) {
6151
- translateAxis(box.x, scroll.x);
6152
- translateAxis(box.y, scroll.y);
6172
+ translateAxis(box.x, scroll.offset.x);
6173
+ translateAxis(box.y, scroll.offset.y);
6153
6174
  }
6154
6175
  return box;
6155
6176
  }
@@ -6162,13 +6183,13 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6162
6183
  */
6163
6184
  for (let i = 0; i < this.path.length; i++) {
6164
6185
  const node = this.path[i];
6165
- const { scroll, options, isScrollRoot } = node;
6186
+ const { scroll, options } = node;
6166
6187
  if (node !== this.root && scroll && options.layoutScroll) {
6167
6188
  /**
6168
6189
  * If this is a new scroll root, we want to remove all previous scrolls
6169
6190
  * from the viewport box.
6170
6191
  */
6171
- if (isScrollRoot) {
6192
+ if (scroll.isRoot) {
6172
6193
  copyBoxInto(boxWithoutScroll, box);
6173
6194
  const { scroll: rootScroll } = this.root;
6174
6195
  /**
@@ -6176,12 +6197,12 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6176
6197
  * to the measured bounding box.
6177
6198
  */
6178
6199
  if (rootScroll) {
6179
- translateAxis(boxWithoutScroll.x, -rootScroll.x);
6180
- translateAxis(boxWithoutScroll.y, -rootScroll.y);
6200
+ translateAxis(boxWithoutScroll.x, -rootScroll.offset.x);
6201
+ translateAxis(boxWithoutScroll.y, -rootScroll.offset.y);
6181
6202
  }
6182
6203
  }
6183
- translateAxis(boxWithoutScroll.x, scroll.x);
6184
- translateAxis(boxWithoutScroll.y, scroll.y);
6204
+ translateAxis(boxWithoutScroll.x, scroll.offset.x);
6205
+ translateAxis(boxWithoutScroll.y, scroll.offset.y);
6185
6206
  }
6186
6207
  }
6187
6208
  return boxWithoutScroll;
@@ -6196,8 +6217,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6196
6217
  node.scroll &&
6197
6218
  node !== node.root) {
6198
6219
  transformBox(withTransforms, {
6199
- x: -node.scroll.x,
6200
- y: -node.scroll.y,
6220
+ x: -node.scroll.offset.x,
6221
+ y: -node.scroll.offset.y,
6201
6222
  });
6202
6223
  }
6203
6224
  if (!hasTransform(node.latestValues))
@@ -6431,7 +6452,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6431
6452
  }
6432
6453
  }
6433
6454
  setAnimationOrigin(delta, hasOnlyRelativeTargetChanged = false) {
6434
- var _a;
6455
+ var _a, _b;
6435
6456
  const snapshot = this.snapshot;
6436
6457
  const snapshotLatestValues = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.latestValues) || {};
6437
6458
  const mixedValues = { ...this.latestValues };
@@ -6439,8 +6460,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6439
6460
  this.relativeTarget = this.relativeTargetOrigin = undefined;
6440
6461
  this.attemptToResolveRelativeTarget = !hasOnlyRelativeTargetChanged;
6441
6462
  const relativeLayout = createBox();
6442
- const isSharedLayoutAnimation = snapshot === null || snapshot === void 0 ? void 0 : snapshot.isShared;
6443
- const isOnlyMember = (((_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.members.length) || 0) <= 1;
6463
+ const isSharedLayoutAnimation = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.source) !== ((_a = this.layout) === null || _a === void 0 ? void 0 : _a.source);
6464
+ const isOnlyMember = (((_b = this.getStack()) === null || _b === void 0 ? void 0 : _b.members.length) || 0) <= 1;
6444
6465
  const shouldCrossfadeOpacity = Boolean(isSharedLayoutAnimation &&
6445
6466
  !isOnlyMember &&
6446
6467
  this.options.crossfade === true &&
@@ -6621,25 +6642,30 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
6621
6642
  return;
6622
6643
  // If there's no detected rotation values, we can early return without a forced render.
6623
6644
  let hasRotate = false;
6624
- // Keep a record of all the values we've reset
6625
- const resetValues = {};
6626
- // Check the rotate value of all axes and reset to 0
6627
- for (let i = 0; i < transformAxes.length; i++) {
6628
- const axis = transformAxes[i];
6629
- const key = "rotate" + axis;
6630
- // If this rotation doesn't exist as a motion value, then we don't
6631
- // need to reset it
6632
- if (!visualElement.getStaticValue(key)) {
6633
- continue;
6634
- }
6645
+ /**
6646
+ * An unrolled check for rotation values. Most elements don't have any rotation and
6647
+ * skipping the nested loop and new object creation is 50% faster.
6648
+ */
6649
+ const { latestValues } = visualElement;
6650
+ if (latestValues.rotate ||
6651
+ latestValues.rotateX ||
6652
+ latestValues.rotateY ||
6653
+ latestValues.rotateZ) {
6635
6654
  hasRotate = true;
6636
- // Record the rotation and then temporarily set it to 0
6637
- resetValues[key] = visualElement.getStaticValue(key);
6638
- visualElement.setStaticValue(key, 0);
6639
6655
  }
6640
6656
  // If there's no rotation values, we don't need to do any more.
6641
6657
  if (!hasRotate)
6642
6658
  return;
6659
+ const resetValues = {};
6660
+ // Check the rotate value of all axes and reset to 0
6661
+ for (let i = 0; i < transformAxes.length; i++) {
6662
+ const key = "rotate" + transformAxes[i];
6663
+ // Record the rotation and then temporarily set it to 0
6664
+ if (latestValues[key]) {
6665
+ resetValues[key] = latestValues[key];
6666
+ visualElement.setStaticValue(key, 0);
6667
+ }
6668
+ }
6643
6669
  // Force a render of this element to apply the transform with all rotations
6644
6670
  // set to 0.
6645
6671
  visualElement === null || visualElement === void 0 ? void 0 : visualElement.render();
@@ -6781,11 +6807,12 @@ function notifyLayoutUpdate(node) {
6781
6807
  node.hasListeners("didUpdate")) {
6782
6808
  const { layoutBox: layout, measuredBox: measuredLayout } = node.layout;
6783
6809
  const { animationType } = node.options;
6810
+ const isShared = snapshot.source !== node.layout.source;
6784
6811
  // TODO Maybe we want to also resize the layout snapshot so we don't trigger
6785
6812
  // animations for instance if layout="size" and an element has only changed position
6786
6813
  if (animationType === "size") {
6787
6814
  eachAxis((axis) => {
6788
- const axisSnapshot = snapshot.isShared
6815
+ const axisSnapshot = isShared
6789
6816
  ? snapshot.measuredBox[axis]
6790
6817
  : snapshot.layoutBox[axis];
6791
6818
  const length = calcLength(axisSnapshot);
@@ -6795,7 +6822,7 @@ function notifyLayoutUpdate(node) {
6795
6822
  }
6796
6823
  else if (shouldAnimatePositionOnly(animationType, snapshot.layoutBox, layout)) {
6797
6824
  eachAxis((axis) => {
6798
- const axisSnapshot = snapshot.isShared
6825
+ const axisSnapshot = isShared
6799
6826
  ? snapshot.measuredBox[axis]
6800
6827
  : snapshot.layoutBox[axis];
6801
6828
  const length = calcLength(layout[axis]);
@@ -6805,7 +6832,7 @@ function notifyLayoutUpdate(node) {
6805
6832
  const layoutDelta = createDelta();
6806
6833
  calcBoxDelta(layoutDelta, layout, snapshot.layoutBox);
6807
6834
  const visualDelta = createDelta();
6808
- if (snapshot.isShared) {
6835
+ if (isShared) {
6809
6836
  calcBoxDelta(visualDelta, node.applyTransform(measuredLayout, true), snapshot.measuredBox);
6810
6837
  }
6811
6838
  else {
@@ -6901,7 +6928,7 @@ const defaultLayoutTransition = {
6901
6928
  duration: 0.45,
6902
6929
  ease: [0.4, 0, 0.1, 1],
6903
6930
  };
6904
- function mountNodeEarly(node, id) {
6931
+ function mountNodeEarly(node, elementId) {
6905
6932
  /**
6906
6933
  * Rather than searching the DOM from document we can search the
6907
6934
  * path for the deepest mounted ancestor and search from there
@@ -6914,7 +6941,7 @@ function mountNodeEarly(node, id) {
6914
6941
  }
6915
6942
  }
6916
6943
  const searchElement = searchNode && searchNode !== node.root ? searchNode.instance : document;
6917
- const element = searchElement.querySelector(`[data-projection-id="${id}"]`);
6944
+ const element = searchElement.querySelector(`[data-projection-id="${elementId}"]`);
6918
6945
  if (element)
6919
6946
  node.mount(element, true);
6920
6947
  }
@@ -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
@@ -26,9 +26,18 @@ const transformAxes = ["", "X", "Y", "Z"];
26
26
  * which has a noticeable difference in spring animations
27
27
  */
28
28
  const animationTarget = 1000;
29
+ let id = 0;
29
30
  function createProjectionNode({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
30
31
  return class ProjectionNode {
31
32
  constructor(elementId, latestValues = {}, parent = defaultParent === null || defaultParent === void 0 ? void 0 : defaultParent()) {
33
+ /**
34
+ * A unique ID generated for every projection node.
35
+ */
36
+ this.id = id++;
37
+ /**
38
+ * An id that represents a unique session instigated by startUpdate.
39
+ */
40
+ this.animationId = 0;
32
41
  /**
33
42
  * A Set containing all this component's children. This is used to iterate
34
43
  * through the children.
@@ -137,8 +146,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
137
146
  hasListeners(name) {
138
147
  return this.eventHandlers.has(name);
139
148
  }
140
- registerPotentialNode(id, node) {
141
- this.potentialNodes.set(id, node);
149
+ registerPotentialNode(elementId, node) {
150
+ this.potentialNodes.set(elementId, node);
142
151
  }
143
152
  /**
144
153
  * Lifecycles
@@ -271,6 +280,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
271
280
  return;
272
281
  this.isUpdating = true;
273
282
  (_a = this.nodes) === null || _a === void 0 ? void 0 : _a.forEach(resetRotation);
283
+ this.animationId++;
274
284
  }
275
285
  willUpdate(shouldNotifyListeners = true) {
276
286
  var _a, _b, _c;
@@ -285,11 +295,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
285
295
  for (let i = 0; i < this.path.length; i++) {
286
296
  const node = this.path[i];
287
297
  node.shouldResetTransform = true;
288
- /**
289
- * TODO: Check we haven't updated the scroll
290
- * since the last didUpdate
291
- */
292
- node.updateScroll();
298
+ node.updateScroll("snapshot");
293
299
  }
294
300
  const { layoutId, layout } = this.options;
295
301
  if (layoutId === undefined && !layout)
@@ -405,10 +411,20 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
405
411
  this.notifyListeners("measure", this.layout.layoutBox);
406
412
  (_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);
407
413
  }
408
- updateScroll() {
409
- if (this.options.layoutScroll && this.instance) {
410
- this.isScrollRoot = checkIsScrollRoot(this.instance);
411
- this.scroll = measureScroll(this.instance);
414
+ updateScroll(phase = "measure") {
415
+ let needsMeasurement = Boolean(this.options.layoutScroll && this.instance);
416
+ if (this.scroll &&
417
+ this.scroll.animationId === this.root.animationId &&
418
+ this.scroll.phase === phase) {
419
+ needsMeasurement = false;
420
+ }
421
+ if (needsMeasurement) {
422
+ this.scroll = {
423
+ animationId: this.root.animationId,
424
+ phase,
425
+ isRoot: checkIsScrollRoot(this.instance),
426
+ offset: measureScroll(this.instance),
427
+ };
412
428
  }
413
429
  }
414
430
  resetTransform() {
@@ -430,6 +446,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
430
446
  }
431
447
  }
432
448
  measure(removeTransform = true) {
449
+ var _a;
433
450
  const pageBox = this.measurePageBox();
434
451
  let layoutBox = this.removeElementScroll(pageBox);
435
452
  /**
@@ -441,10 +458,17 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
441
458
  layoutBox = this.removeTransform(layoutBox);
442
459
  }
443
460
  roundBox(layoutBox);
461
+ const positionStyle = (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.readValue("position");
462
+ const position = positionStyle === "fixed" || positionStyle === "sticky"
463
+ ? positionStyle
464
+ : "static";
444
465
  return {
466
+ animationId: this.root.animationId,
445
467
  measuredBox: pageBox,
446
468
  layoutBox,
447
469
  latestValues: {},
470
+ source: this.id,
471
+ position,
448
472
  };
449
473
  }
450
474
  measurePageBox() {
@@ -455,8 +479,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
455
479
  // Remove viewport scroll to give page-relative coordinates
456
480
  const { scroll } = this.root;
457
481
  if (scroll) {
458
- translateAxis(box.x, scroll.x);
459
- translateAxis(box.y, scroll.y);
482
+ translateAxis(box.x, scroll.offset.x);
483
+ translateAxis(box.y, scroll.offset.y);
460
484
  }
461
485
  return box;
462
486
  }
@@ -469,13 +493,13 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
469
493
  */
470
494
  for (let i = 0; i < this.path.length; i++) {
471
495
  const node = this.path[i];
472
- const { scroll, options, isScrollRoot } = node;
496
+ const { scroll, options } = node;
473
497
  if (node !== this.root && scroll && options.layoutScroll) {
474
498
  /**
475
499
  * If this is a new scroll root, we want to remove all previous scrolls
476
500
  * from the viewport box.
477
501
  */
478
- if (isScrollRoot) {
502
+ if (scroll.isRoot) {
479
503
  copyBoxInto(boxWithoutScroll, box);
480
504
  const { scroll: rootScroll } = this.root;
481
505
  /**
@@ -483,12 +507,12 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
483
507
  * to the measured bounding box.
484
508
  */
485
509
  if (rootScroll) {
486
- translateAxis(boxWithoutScroll.x, -rootScroll.x);
487
- translateAxis(boxWithoutScroll.y, -rootScroll.y);
510
+ translateAxis(boxWithoutScroll.x, -rootScroll.offset.x);
511
+ translateAxis(boxWithoutScroll.y, -rootScroll.offset.y);
488
512
  }
489
513
  }
490
- translateAxis(boxWithoutScroll.x, scroll.x);
491
- translateAxis(boxWithoutScroll.y, scroll.y);
514
+ translateAxis(boxWithoutScroll.x, scroll.offset.x);
515
+ translateAxis(boxWithoutScroll.y, scroll.offset.y);
492
516
  }
493
517
  }
494
518
  return boxWithoutScroll;
@@ -503,8 +527,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
503
527
  node.scroll &&
504
528
  node !== node.root) {
505
529
  transformBox(withTransforms, {
506
- x: -node.scroll.x,
507
- y: -node.scroll.y,
530
+ x: -node.scroll.offset.x,
531
+ y: -node.scroll.offset.y,
508
532
  });
509
533
  }
510
534
  if (!hasTransform(node.latestValues))
@@ -738,7 +762,7 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
738
762
  }
739
763
  }
740
764
  setAnimationOrigin(delta, hasOnlyRelativeTargetChanged = false) {
741
- var _a;
765
+ var _a, _b;
742
766
  const snapshot = this.snapshot;
743
767
  const snapshotLatestValues = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.latestValues) || {};
744
768
  const mixedValues = { ...this.latestValues };
@@ -746,8 +770,8 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
746
770
  this.relativeTarget = this.relativeTargetOrigin = undefined;
747
771
  this.attemptToResolveRelativeTarget = !hasOnlyRelativeTargetChanged;
748
772
  const relativeLayout = createBox();
749
- const isSharedLayoutAnimation = snapshot === null || snapshot === void 0 ? void 0 : snapshot.isShared;
750
- const isOnlyMember = (((_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.members.length) || 0) <= 1;
773
+ const isSharedLayoutAnimation = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.source) !== ((_a = this.layout) === null || _a === void 0 ? void 0 : _a.source);
774
+ const isOnlyMember = (((_b = this.getStack()) === null || _b === void 0 ? void 0 : _b.members.length) || 0) <= 1;
751
775
  const shouldCrossfadeOpacity = Boolean(isSharedLayoutAnimation &&
752
776
  !isOnlyMember &&
753
777
  this.options.crossfade === true &&
@@ -928,25 +952,30 @@ function createProjectionNode({ attachResizeListener, defaultParent, measureScro
928
952
  return;
929
953
  // If there's no detected rotation values, we can early return without a forced render.
930
954
  let hasRotate = false;
931
- // Keep a record of all the values we've reset
932
- const resetValues = {};
933
- // Check the rotate value of all axes and reset to 0
934
- for (let i = 0; i < transformAxes.length; i++) {
935
- const axis = transformAxes[i];
936
- const key = "rotate" + axis;
937
- // If this rotation doesn't exist as a motion value, then we don't
938
- // need to reset it
939
- if (!visualElement.getStaticValue(key)) {
940
- continue;
941
- }
955
+ /**
956
+ * An unrolled check for rotation values. Most elements don't have any rotation and
957
+ * skipping the nested loop and new object creation is 50% faster.
958
+ */
959
+ const { latestValues } = visualElement;
960
+ if (latestValues.rotate ||
961
+ latestValues.rotateX ||
962
+ latestValues.rotateY ||
963
+ latestValues.rotateZ) {
942
964
  hasRotate = true;
943
- // Record the rotation and then temporarily set it to 0
944
- resetValues[key] = visualElement.getStaticValue(key);
945
- visualElement.setStaticValue(key, 0);
946
965
  }
947
966
  // If there's no rotation values, we don't need to do any more.
948
967
  if (!hasRotate)
949
968
  return;
969
+ const resetValues = {};
970
+ // Check the rotate value of all axes and reset to 0
971
+ for (let i = 0; i < transformAxes.length; i++) {
972
+ const key = "rotate" + transformAxes[i];
973
+ // Record the rotation and then temporarily set it to 0
974
+ if (latestValues[key]) {
975
+ resetValues[key] = latestValues[key];
976
+ visualElement.setStaticValue(key, 0);
977
+ }
978
+ }
950
979
  // Force a render of this element to apply the transform with all rotations
951
980
  // set to 0.
952
981
  visualElement === null || visualElement === void 0 ? void 0 : visualElement.render();
@@ -1088,11 +1117,12 @@ function notifyLayoutUpdate(node) {
1088
1117
  node.hasListeners("didUpdate")) {
1089
1118
  const { layoutBox: layout, measuredBox: measuredLayout } = node.layout;
1090
1119
  const { animationType } = node.options;
1120
+ const isShared = snapshot.source !== node.layout.source;
1091
1121
  // TODO Maybe we want to also resize the layout snapshot so we don't trigger
1092
1122
  // animations for instance if layout="size" and an element has only changed position
1093
1123
  if (animationType === "size") {
1094
1124
  eachAxis((axis) => {
1095
- const axisSnapshot = snapshot.isShared
1125
+ const axisSnapshot = isShared
1096
1126
  ? snapshot.measuredBox[axis]
1097
1127
  : snapshot.layoutBox[axis];
1098
1128
  const length = calcLength(axisSnapshot);
@@ -1102,7 +1132,7 @@ function notifyLayoutUpdate(node) {
1102
1132
  }
1103
1133
  else if (shouldAnimatePositionOnly(animationType, snapshot.layoutBox, layout)) {
1104
1134
  eachAxis((axis) => {
1105
- const axisSnapshot = snapshot.isShared
1135
+ const axisSnapshot = isShared
1106
1136
  ? snapshot.measuredBox[axis]
1107
1137
  : snapshot.layoutBox[axis];
1108
1138
  const length = calcLength(layout[axis]);
@@ -1112,7 +1142,7 @@ function notifyLayoutUpdate(node) {
1112
1142
  const layoutDelta = createDelta();
1113
1143
  calcBoxDelta(layoutDelta, layout, snapshot.layoutBox);
1114
1144
  const visualDelta = createDelta();
1115
- if (snapshot.isShared) {
1145
+ if (isShared) {
1116
1146
  calcBoxDelta(visualDelta, node.applyTransform(measuredLayout, true), snapshot.measuredBox);
1117
1147
  }
1118
1148
  else {
@@ -1208,7 +1238,7 @@ const defaultLayoutTransition = {
1208
1238
  duration: 0.45,
1209
1239
  ease: [0.4, 0, 0.1, 1],
1210
1240
  };
1211
- function mountNodeEarly(node, id) {
1241
+ function mountNodeEarly(node, elementId) {
1212
1242
  /**
1213
1243
  * Rather than searching the DOM from document we can search the
1214
1244
  * path for the deepest mounted ancestor and search from there
@@ -1221,7 +1251,7 @@ function mountNodeEarly(node, id) {
1221
1251
  }
1222
1252
  }
1223
1253
  const searchElement = searchNode && searchNode !== node.root ? searchNode.instance : document;
1224
- const element = searchElement.querySelector(`[data-projection-id="${id}"]`);
1254
+ const element = searchElement.querySelector(`[data-projection-id="${elementId}"]`);
1225
1255
  if (element)
1226
1256
  node.mount(element, true);
1227
1257
  }