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/index.d.ts CHANGED
@@ -1333,15 +1333,27 @@ declare class FlatTree {
1333
1333
  forEach(callback: (child: WithDepth) => void): void;
1334
1334
  }
1335
1335
 
1336
+ declare type Position = "static" | "sticky" | "fixed";
1336
1337
  interface Measurements {
1338
+ animationId: number;
1337
1339
  measuredBox: Box;
1338
1340
  layoutBox: Box;
1339
1341
  latestValues: ResolvedValues;
1340
- isShared?: boolean;
1342
+ source: number;
1343
+ position: Position;
1344
+ }
1345
+ declare type Phase = "snapshot" | "measure";
1346
+ interface ScrollMeasurements {
1347
+ animationId: number;
1348
+ phase: Phase;
1349
+ isRoot: boolean;
1350
+ offset: Point;
1341
1351
  }
1342
1352
  declare type LayoutEvents = "willUpdate" | "didUpdate" | "beforeMeasure" | "measure" | "projectionUpdate" | "animationStart" | "animationComplete";
1343
1353
  interface IProjectionNode<I = unknown> {
1354
+ id: number;
1344
1355
  elementId: number | undefined;
1356
+ animationId: number;
1345
1357
  parent?: IProjectionNode;
1346
1358
  relativeParent?: IProjectionNode;
1347
1359
  root?: IProjectionNode;
@@ -1360,13 +1372,13 @@ interface IProjectionNode<I = unknown> {
1360
1372
  relativeTarget?: Box;
1361
1373
  targetDelta?: Delta;
1362
1374
  targetWithTransforms?: Box;
1363
- scroll?: Point;
1364
- isScrollRoot?: boolean;
1375
+ scroll?: ScrollMeasurements;
1365
1376
  treeScale?: Point;
1366
1377
  projectionDelta?: Delta;
1367
1378
  projectionDeltaWithTransform?: Delta;
1368
1379
  latestValues: ResolvedValues;
1369
1380
  isLayoutDirty: boolean;
1381
+ isProjectionDirty: boolean;
1370
1382
  shouldResetTransform: boolean;
1371
1383
  prevTransformTemplateValue: string | undefined;
1372
1384
  isUpdateBlocked(): boolean;
@@ -1384,7 +1396,7 @@ interface IProjectionNode<I = unknown> {
1384
1396
  updateLayout(): void;
1385
1397
  updateSnapshot(): void;
1386
1398
  clearSnapshot(): void;
1387
- updateScroll(): void;
1399
+ updateScroll(phase?: Phase): void;
1388
1400
  scheduleUpdateProjection(): void;
1389
1401
  scheduleCheckAfterUnmount(): void;
1390
1402
  checkUpdateFailed(): void;
@@ -1242,7 +1242,7 @@
1242
1242
  * This will be replaced by the build step with the latest version number.
1243
1243
  * When MotionValues are provided to motion components, warn if versions are mixed.
1244
1244
  */
1245
- this.version = "7.6.7";
1245
+ this.version = "7.6.9";
1246
1246
  /**
1247
1247
  * Duration, in milliseconds, since last updating frame.
1248
1248
  *
@@ -2194,7 +2194,10 @@
2194
2194
  node.options.layoutScroll &&
2195
2195
  node.scroll &&
2196
2196
  node !== node.root) {
2197
- transformBox(box, { x: -node.scroll.x, y: -node.scroll.y });
2197
+ transformBox(box, {
2198
+ x: -node.scroll.offset.x,
2199
+ y: -node.scroll.offset.y,
2200
+ });
2198
2201
  }
2199
2202
  if (delta) {
2200
2203
  // Incoporate each ancestor's scale into a culmulative treeScale for this component
@@ -2420,7 +2423,6 @@
2420
2423
  node.snapshot = prevLead.snapshot;
2421
2424
  node.snapshot.latestValues =
2422
2425
  prevLead.animationValues || prevLead.latestValues;
2423
- node.snapshot.isShared = true;
2424
2426
  }
2425
2427
  if ((_a = node.root) === null || _a === void 0 ? void 0 : _a.isUpdating) {
2426
2428
  node.isLayoutDirty = true;
@@ -2471,8 +2473,8 @@
2471
2473
  Object.assign(scaleCorrectors, correctors);
2472
2474
  }
2473
2475
 
2474
- const identityProjection = "translate3d(0px, 0px, 0) scale(1, 1) scale(1, 1)";
2475
2476
  function buildProjectionTransform(delta, treeScale, latestTransform) {
2477
+ let transform = "";
2476
2478
  /**
2477
2479
  * The translations we use to calculate are always relative to the viewport coordinate space.
2478
2480
  * But when we apply scales, we also scale the coordinate space of an element and its children.
@@ -2481,12 +2483,16 @@
2481
2483
  */
2482
2484
  const xTranslate = delta.x.translate / treeScale.x;
2483
2485
  const yTranslate = delta.y.translate / treeScale.y;
2484
- let transform = `translate3d(${xTranslate}px, ${yTranslate}px, 0) `;
2486
+ if (xTranslate || yTranslate) {
2487
+ transform = `translate3d(${xTranslate}px, ${yTranslate}px, 0) `;
2488
+ }
2485
2489
  /**
2486
2490
  * Apply scale correction for the tree transform.
2487
2491
  * This will apply scale to the screen-orientated axes.
2488
2492
  */
2489
- transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
2493
+ if (treeScale.x !== 1 || treeScale.y !== 1) {
2494
+ transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
2495
+ }
2490
2496
  if (latestTransform) {
2491
2497
  const { rotate, rotateX, rotateY } = latestTransform;
2492
2498
  if (rotate)
@@ -2502,8 +2508,10 @@
2502
2508
  */
2503
2509
  const elementScaleX = delta.x.scale * treeScale.x;
2504
2510
  const elementScaleY = delta.y.scale * treeScale.y;
2505
- transform += `scale(${elementScaleX}, ${elementScaleY})`;
2506
- return transform === identityProjection ? "none" : transform;
2511
+ if (elementScaleX !== 1 || elementScaleY !== 1) {
2512
+ transform += `scale(${elementScaleX}, ${elementScaleY})`;
2513
+ }
2514
+ return transform || "none";
2507
2515
  }
2508
2516
 
2509
2517
  function eachAxis(callback) {
@@ -2568,9 +2576,18 @@
2568
2576
  * which has a noticeable difference in spring animations
2569
2577
  */
2570
2578
  const animationTarget = 1000;
2579
+ let id = 0;
2571
2580
  function createProjectionNode({ attachResizeListener, defaultParent, measureScroll, checkIsScrollRoot, resetTransform, }) {
2572
2581
  return class ProjectionNode {
2573
2582
  constructor(elementId, latestValues = {}, parent = defaultParent === null || defaultParent === void 0 ? void 0 : defaultParent()) {
2583
+ /**
2584
+ * A unique ID generated for every projection node.
2585
+ */
2586
+ this.id = id++;
2587
+ /**
2588
+ * An id that represents a unique session instigated by startUpdate.
2589
+ */
2590
+ this.animationId = 0;
2574
2591
  /**
2575
2592
  * A Set containing all this component's children. This is used to iterate
2576
2593
  * through the children.
@@ -2597,6 +2614,11 @@
2597
2614
  * and if one node is dirtied, they all are.
2598
2615
  */
2599
2616
  this.isLayoutDirty = false;
2617
+ /**
2618
+ * Flag to true if we think the projection calculations for this or any
2619
+ * child might need recalculating as a result of an updated transform or layout animation.
2620
+ */
2621
+ this.isProjectionDirty = false;
2600
2622
  /**
2601
2623
  * Block layout updates for instant layout transitions throughout the tree.
2602
2624
  */
@@ -2679,8 +2701,8 @@
2679
2701
  hasListeners(name) {
2680
2702
  return this.eventHandlers.has(name);
2681
2703
  }
2682
- registerPotentialNode(id, node) {
2683
- this.potentialNodes.set(id, node);
2704
+ registerPotentialNode(elementId, node) {
2705
+ this.potentialNodes.set(elementId, node);
2684
2706
  }
2685
2707
  /**
2686
2708
  * Lifecycles
@@ -2813,6 +2835,7 @@
2813
2835
  return;
2814
2836
  this.isUpdating = true;
2815
2837
  (_a = this.nodes) === null || _a === void 0 ? void 0 : _a.forEach(resetRotation);
2838
+ this.animationId++;
2816
2839
  }
2817
2840
  willUpdate(shouldNotifyListeners = true) {
2818
2841
  var _a, _b, _c;
@@ -2827,11 +2850,7 @@
2827
2850
  for (let i = 0; i < this.path.length; i++) {
2828
2851
  const node = this.path[i];
2829
2852
  node.shouldResetTransform = true;
2830
- /**
2831
- * TODO: Check we haven't updated the scroll
2832
- * since the last didUpdate
2833
- */
2834
- node.updateScroll();
2853
+ node.updateScroll("snapshot");
2835
2854
  }
2836
2855
  const { layoutId, layout } = this.options;
2837
2856
  if (layoutId === undefined && !layout)
@@ -2947,10 +2966,20 @@
2947
2966
  this.notifyListeners("measure", this.layout.layoutBox);
2948
2967
  (_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);
2949
2968
  }
2950
- updateScroll() {
2951
- if (this.options.layoutScroll && this.instance) {
2952
- this.isScrollRoot = checkIsScrollRoot(this.instance);
2953
- this.scroll = measureScroll(this.instance);
2969
+ updateScroll(phase = "measure") {
2970
+ let needsMeasurement = Boolean(this.options.layoutScroll && this.instance);
2971
+ if (this.scroll &&
2972
+ this.scroll.animationId === this.root.animationId &&
2973
+ this.scroll.phase === phase) {
2974
+ needsMeasurement = false;
2975
+ }
2976
+ if (needsMeasurement) {
2977
+ this.scroll = {
2978
+ animationId: this.root.animationId,
2979
+ phase,
2980
+ isRoot: checkIsScrollRoot(this.instance),
2981
+ offset: measureScroll(this.instance),
2982
+ };
2954
2983
  }
2955
2984
  }
2956
2985
  resetTransform() {
@@ -2972,6 +3001,7 @@
2972
3001
  }
2973
3002
  }
2974
3003
  measure(removeTransform = true) {
3004
+ var _a;
2975
3005
  const pageBox = this.measurePageBox();
2976
3006
  let layoutBox = this.removeElementScroll(pageBox);
2977
3007
  /**
@@ -2983,10 +3013,17 @@
2983
3013
  layoutBox = this.removeTransform(layoutBox);
2984
3014
  }
2985
3015
  roundBox(layoutBox);
3016
+ const positionStyle = (_a = this.options.visualElement) === null || _a === void 0 ? void 0 : _a.readValue("position");
3017
+ const position = positionStyle === "fixed" || positionStyle === "sticky"
3018
+ ? positionStyle
3019
+ : "static";
2986
3020
  return {
3021
+ animationId: this.root.animationId,
2987
3022
  measuredBox: pageBox,
2988
3023
  layoutBox,
2989
3024
  latestValues: {},
3025
+ source: this.id,
3026
+ position,
2990
3027
  };
2991
3028
  }
2992
3029
  measurePageBox() {
@@ -2997,8 +3034,8 @@
2997
3034
  // Remove viewport scroll to give page-relative coordinates
2998
3035
  const { scroll } = this.root;
2999
3036
  if (scroll) {
3000
- translateAxis(box.x, scroll.x);
3001
- translateAxis(box.y, scroll.y);
3037
+ translateAxis(box.x, scroll.offset.x);
3038
+ translateAxis(box.y, scroll.offset.y);
3002
3039
  }
3003
3040
  return box;
3004
3041
  }
@@ -3011,13 +3048,13 @@
3011
3048
  */
3012
3049
  for (let i = 0; i < this.path.length; i++) {
3013
3050
  const node = this.path[i];
3014
- const { scroll, options, isScrollRoot } = node;
3051
+ const { scroll, options } = node;
3015
3052
  if (node !== this.root && scroll && options.layoutScroll) {
3016
3053
  /**
3017
3054
  * If this is a new scroll root, we want to remove all previous scrolls
3018
3055
  * from the viewport box.
3019
3056
  */
3020
- if (isScrollRoot) {
3057
+ if (scroll.isRoot) {
3021
3058
  copyBoxInto(boxWithoutScroll, box);
3022
3059
  const { scroll: rootScroll } = this.root;
3023
3060
  /**
@@ -3025,12 +3062,12 @@
3025
3062
  * to the measured bounding box.
3026
3063
  */
3027
3064
  if (rootScroll) {
3028
- translateAxis(boxWithoutScroll.x, -rootScroll.x);
3029
- translateAxis(boxWithoutScroll.y, -rootScroll.y);
3065
+ translateAxis(boxWithoutScroll.x, -rootScroll.offset.x);
3066
+ translateAxis(boxWithoutScroll.y, -rootScroll.offset.y);
3030
3067
  }
3031
3068
  }
3032
- translateAxis(boxWithoutScroll.x, scroll.x);
3033
- translateAxis(boxWithoutScroll.y, scroll.y);
3069
+ translateAxis(boxWithoutScroll.x, scroll.offset.x);
3070
+ translateAxis(boxWithoutScroll.y, scroll.offset.y);
3034
3071
  }
3035
3072
  }
3036
3073
  return boxWithoutScroll;
@@ -3045,8 +3082,8 @@
3045
3082
  node.scroll &&
3046
3083
  node !== node.root) {
3047
3084
  transformBox(withTransforms, {
3048
- x: -node.scroll.x,
3049
- y: -node.scroll.y,
3085
+ x: -node.scroll.offset.x,
3086
+ y: -node.scroll.offset.y,
3050
3087
  });
3051
3088
  }
3052
3089
  if (!hasTransform(node.latestValues))
@@ -3084,6 +3121,7 @@
3084
3121
  */
3085
3122
  setTargetDelta(delta) {
3086
3123
  this.targetDelta = delta;
3124
+ this.isProjectionDirty = true;
3087
3125
  this.root.scheduleUpdateProjection();
3088
3126
  }
3089
3127
  setOptions(options) {
@@ -3107,6 +3145,14 @@
3107
3145
  */
3108
3146
  resolveTargetDelta() {
3109
3147
  var _a;
3148
+ /**
3149
+ * Propagate isProjectionDirty. Nodes are ordered by depth, so if the parent here
3150
+ * is dirty we can simply pass this forward.
3151
+ */
3152
+ this.isProjectionDirty || (this.isProjectionDirty = this.getLead().isProjectionDirty ||
3153
+ Boolean(this.parent && this.parent.isProjectionDirty));
3154
+ if (!this.isProjectionDirty)
3155
+ return;
3110
3156
  const { layout, layoutId } = this.options;
3111
3157
  /**
3112
3158
  * If we have no layout, we can't perform projection, so early return
@@ -3210,6 +3256,9 @@
3210
3256
  }
3211
3257
  calcProjection() {
3212
3258
  var _a;
3259
+ if (!this.isProjectionDirty)
3260
+ return;
3261
+ this.isProjectionDirty = false;
3213
3262
  const { layout, layoutId } = this.options;
3214
3263
  /**
3215
3264
  * If this section of the tree isn't animating we can
@@ -3280,7 +3329,7 @@
3280
3329
  }
3281
3330
  }
3282
3331
  setAnimationOrigin(delta, hasOnlyRelativeTargetChanged = false) {
3283
- var _a;
3332
+ var _a, _b;
3284
3333
  const snapshot = this.snapshot;
3285
3334
  const snapshotLatestValues = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.latestValues) || {};
3286
3335
  const mixedValues = { ...this.latestValues };
@@ -3288,8 +3337,8 @@
3288
3337
  this.relativeTarget = this.relativeTargetOrigin = undefined;
3289
3338
  this.attemptToResolveRelativeTarget = !hasOnlyRelativeTargetChanged;
3290
3339
  const relativeLayout = createBox();
3291
- const isSharedLayoutAnimation = snapshot === null || snapshot === void 0 ? void 0 : snapshot.isShared;
3292
- const isOnlyMember = (((_a = this.getStack()) === null || _a === void 0 ? void 0 : _a.members.length) || 0) <= 1;
3340
+ const isSharedLayoutAnimation = (snapshot === null || snapshot === void 0 ? void 0 : snapshot.source) !== ((_a = this.layout) === null || _a === void 0 ? void 0 : _a.source);
3341
+ const isOnlyMember = (((_b = this.getStack()) === null || _b === void 0 ? void 0 : _b.members.length) || 0) <= 1;
3293
3342
  const shouldCrossfadeOpacity = Boolean(isSharedLayoutAnimation &&
3294
3343
  !isOnlyMember &&
3295
3344
  this.options.crossfade === true &&
@@ -3470,25 +3519,30 @@
3470
3519
  return;
3471
3520
  // If there's no detected rotation values, we can early return without a forced render.
3472
3521
  let hasRotate = false;
3473
- // Keep a record of all the values we've reset
3474
- const resetValues = {};
3475
- // Check the rotate value of all axes and reset to 0
3476
- for (let i = 0; i < transformAxes.length; i++) {
3477
- const axis = transformAxes[i];
3478
- const key = "rotate" + axis;
3479
- // If this rotation doesn't exist as a motion value, then we don't
3480
- // need to reset it
3481
- if (!visualElement.getStaticValue(key)) {
3482
- continue;
3483
- }
3522
+ /**
3523
+ * An unrolled check for rotation values. Most elements don't have any rotation and
3524
+ * skipping the nested loop and new object creation is 50% faster.
3525
+ */
3526
+ const { latestValues } = visualElement;
3527
+ if (latestValues.rotate ||
3528
+ latestValues.rotateX ||
3529
+ latestValues.rotateY ||
3530
+ latestValues.rotateZ) {
3484
3531
  hasRotate = true;
3485
- // Record the rotation and then temporarily set it to 0
3486
- resetValues[key] = visualElement.getStaticValue(key);
3487
- visualElement.setStaticValue(key, 0);
3488
3532
  }
3489
3533
  // If there's no rotation values, we don't need to do any more.
3490
3534
  if (!hasRotate)
3491
3535
  return;
3536
+ const resetValues = {};
3537
+ // Check the rotate value of all axes and reset to 0
3538
+ for (let i = 0; i < transformAxes.length; i++) {
3539
+ const key = "rotate" + transformAxes[i];
3540
+ // Record the rotation and then temporarily set it to 0
3541
+ if (latestValues[key]) {
3542
+ resetValues[key] = latestValues[key];
3543
+ visualElement.setStaticValue(key, 0);
3544
+ }
3545
+ }
3492
3546
  // Force a render of this element to apply the transform with all rotations
3493
3547
  // set to 0.
3494
3548
  visualElement === null || visualElement === void 0 ? void 0 : visualElement.render();
@@ -3630,11 +3684,12 @@
3630
3684
  node.hasListeners("didUpdate")) {
3631
3685
  const { layoutBox: layout, measuredBox: measuredLayout } = node.layout;
3632
3686
  const { animationType } = node.options;
3687
+ const isShared = snapshot.source !== node.layout.source;
3633
3688
  // TODO Maybe we want to also resize the layout snapshot so we don't trigger
3634
3689
  // animations for instance if layout="size" and an element has only changed position
3635
3690
  if (animationType === "size") {
3636
3691
  eachAxis((axis) => {
3637
- const axisSnapshot = snapshot.isShared
3692
+ const axisSnapshot = isShared
3638
3693
  ? snapshot.measuredBox[axis]
3639
3694
  : snapshot.layoutBox[axis];
3640
3695
  const length = calcLength(axisSnapshot);
@@ -3644,7 +3699,7 @@
3644
3699
  }
3645
3700
  else if (shouldAnimatePositionOnly(animationType, snapshot.layoutBox, layout)) {
3646
3701
  eachAxis((axis) => {
3647
- const axisSnapshot = snapshot.isShared
3702
+ const axisSnapshot = isShared
3648
3703
  ? snapshot.measuredBox[axis]
3649
3704
  : snapshot.layoutBox[axis];
3650
3705
  const length = calcLength(layout[axis]);
@@ -3654,7 +3709,7 @@
3654
3709
  const layoutDelta = createDelta();
3655
3710
  calcBoxDelta(layoutDelta, layout, snapshot.layoutBox);
3656
3711
  const visualDelta = createDelta();
3657
- if (snapshot.isShared) {
3712
+ if (isShared) {
3658
3713
  calcBoxDelta(visualDelta, node.applyTransform(measuredLayout, true), snapshot.measuredBox);
3659
3714
  }
3660
3715
  else {
@@ -3750,7 +3805,7 @@
3750
3805
  duration: 0.45,
3751
3806
  ease: [0.4, 0, 0.1, 1],
3752
3807
  };
3753
- function mountNodeEarly(node, id) {
3808
+ function mountNodeEarly(node, elementId) {
3754
3809
  /**
3755
3810
  * Rather than searching the DOM from document we can search the
3756
3811
  * path for the deepest mounted ancestor and search from there
@@ -3763,7 +3818,7 @@
3763
3818
  }
3764
3819
  }
3765
3820
  const searchElement = searchNode && searchNode !== node.root ? searchNode.instance : document;
3766
- const element = searchElement.querySelector(`[data-projection-id="${id}"]`);
3821
+ const element = searchElement.querySelector(`[data-projection-id="${elementId}"]`);
3767
3822
  if (element)
3768
3823
  node.mount(element, true);
3769
3824
  }
@@ -4742,7 +4797,7 @@
4742
4797
  * and warn against mismatches.
4743
4798
  */
4744
4799
  {
4745
- warnOnce(nextValue.version === "7.6.7", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.6.7 may not work as expected.`);
4800
+ warnOnce(nextValue.version === "7.6.9", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.6.9 may not work as expected.`);
4746
4801
  }
4747
4802
  }
4748
4803
  else if (isMotionValue(prevValue)) {
@@ -4944,10 +4999,14 @@
4944
4999
  this.current = null;
4945
5000
  }
4946
5001
  bindToMotionValue(key, value) {
5002
+ const valueIsTransform = transformProps.has(key);
4947
5003
  const removeOnChange = value.onChange((latestValue) => {
4948
5004
  this.latestValues[key] = latestValue;
4949
5005
  this.props.onUpdate &&
4950
5006
  sync.update(this.notifyUpdate, false, true);
5007
+ if (valueIsTransform && this.projection) {
5008
+ this.projection.isProjectionDirty = true;
5009
+ }
4951
5010
  });
4952
5011
  const removeOnRenderRequest = value.onRenderRequest(this.scheduleRender);
4953
5012
  this.valueSubscriptions.set(key, () => {