framer-motion 8.4.4 → 8.4.6

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.
@@ -79,11 +79,19 @@
79
79
  visualElement && visualElement.render();
80
80
  });
81
81
  /**
82
- * If we have optimised appear animations to handoff from, trigger animateChanges
83
- * from a synchronous useLayoutEffect to ensure there's no flash of incorrectly
84
- * styled component in the event of a hydration error.
82
+ * Ideally this function would always run in a useEffect.
83
+ *
84
+ * However, if we have optimised appear animations to handoff from,
85
+ * it needs to happen synchronously to ensure there's no flash of
86
+ * incorrect styles in the event of a hydration error.
87
+ *
88
+ * So if we detect a situtation where optimised appear animations
89
+ * are running, we use useLayoutEffect to trigger animations.
85
90
  */
86
- useIsomorphicLayoutEffect(() => {
91
+ const useAnimateChangesEffect = window.MotionAppearAnimations
92
+ ? useIsomorphicLayoutEffect
93
+ : React.useEffect;
94
+ useAnimateChangesEffect(() => {
87
95
  if (visualElement && visualElement.animationState) {
88
96
  visualElement.animationState.animateChanges();
89
97
  }
@@ -1463,14 +1471,14 @@
1463
1471
  return false;
1464
1472
  }
1465
1473
 
1466
- function createHoverEvent(visualElement, isActive, callback) {
1474
+ function createHoverEvent(visualElement, isActive, applyVariants, callback) {
1467
1475
  return (event, info) => {
1468
1476
  if (event.type === "touch" || isDragActive())
1469
1477
  return;
1470
1478
  /**
1471
1479
  * Ensure we trigger animations before firing event callback
1472
1480
  */
1473
- if (visualElement.animationState) {
1481
+ if (applyVariants && visualElement.animationState) {
1474
1482
  visualElement.animationState.setActive(exports.AnimationType.Hover, isActive);
1475
1483
  }
1476
1484
  callback && callback(event, info);
@@ -1479,12 +1487,12 @@
1479
1487
  function useHoverGesture({ onHoverStart, onHoverEnd, whileHover, visualElement, }) {
1480
1488
  usePointerEvent(visualElement, "pointerenter", React.useMemo(() => {
1481
1489
  return onHoverStart || whileHover
1482
- ? createHoverEvent(visualElement, true, onHoverStart)
1490
+ ? createHoverEvent(visualElement, true, Boolean(whileHover), onHoverStart)
1483
1491
  : undefined;
1484
1492
  }, [onHoverStart, Boolean(whileHover), visualElement]), { passive: !onHoverStart });
1485
1493
  usePointerEvent(visualElement, "pointerleave", React.useMemo(() => {
1486
1494
  return onHoverEnd || whileHover
1487
- ? createHoverEvent(visualElement, false, onHoverEnd)
1495
+ ? createHoverEvent(visualElement, false, Boolean(whileHover), onHoverEnd)
1488
1496
  : undefined;
1489
1497
  }, [onHoverStart, Boolean(whileHover), visualElement]), { passive: !onHoverEnd });
1490
1498
  }
@@ -1546,8 +1554,10 @@
1546
1554
  function checkPointerEnd() {
1547
1555
  removePointerEndListener();
1548
1556
  isPressing.current = false;
1549
- visualElement.animationState &&
1557
+ const latestProps = visualElement.getProps();
1558
+ if (latestProps.whileTap && visualElement.animationState) {
1550
1559
  visualElement.animationState.setActive(exports.AnimationType.Tap, false);
1560
+ }
1551
1561
  return !isDragActive();
1552
1562
  }
1553
1563
  function onPointerUp(event, info) {
@@ -1569,18 +1579,20 @@
1569
1579
  (_b = (_a = visualElement.getProps()).onTapCancel) === null || _b === void 0 ? void 0 : _b.call(_a, event, info);
1570
1580
  }
1571
1581
  const startPress = React.useCallback((event, info) => {
1572
- var _a, _b;
1582
+ var _a;
1573
1583
  removePointerEndListener();
1574
1584
  if (isPressing.current)
1575
1585
  return;
1576
1586
  isPressing.current = true;
1577
1587
  cancelPointerEndListeners.current = pipe(addPointerEvent(window, "pointerup", onPointerUp, eventOptions), addPointerEvent(window, "pointercancel", onPointerCancel, eventOptions));
1588
+ const latestProps = visualElement.getProps();
1578
1589
  /**
1579
1590
  * Ensure we trigger animations before firing event callback
1580
1591
  */
1581
- visualElement.animationState &&
1592
+ if (latestProps.whileTap && visualElement.animationState) {
1582
1593
  visualElement.animationState.setActive(exports.AnimationType.Tap, true);
1583
- (_b = (_a = visualElement.getProps()).onTapStart) === null || _b === void 0 ? void 0 : _b.call(_a, event, info);
1594
+ }
1595
+ (_a = latestProps.onTapStart) === null || _a === void 0 ? void 0 : _a.call(latestProps, event, info);
1584
1596
  }, [Boolean(onTapStart), visualElement]);
1585
1597
  usePointerEvent(visualElement, "pointerdown", hasPressListeners ? startPress : undefined, eventOptions);
1586
1598
  useUnmountEffect(removePointerEndListener);
@@ -2079,7 +2091,7 @@
2079
2091
  * This will be replaced by the build step with the latest version number.
2080
2092
  * When MotionValues are provided to motion components, warn if versions are mixed.
2081
2093
  */
2082
- this.version = "8.4.4";
2094
+ this.version = "8.4.6";
2083
2095
  /**
2084
2096
  * Duration, in milliseconds, since last updating frame.
2085
2097
  *
@@ -2318,11 +2330,11 @@
2318
2330
  *
2319
2331
  * @internal
2320
2332
  */
2321
- start(animation) {
2333
+ start(startAnimation) {
2322
2334
  this.stop();
2323
2335
  return new Promise((resolve) => {
2324
2336
  this.hasAnimated = true;
2325
- this.stopAnimation = animation(resolve);
2337
+ this.animation = startAnimation(resolve) || null;
2326
2338
  if (this.events.animationStart) {
2327
2339
  this.events.animationStart.notify();
2328
2340
  }
@@ -2339,8 +2351,8 @@
2339
2351
  * @public
2340
2352
  */
2341
2353
  stop() {
2342
- if (this.stopAnimation) {
2343
- this.stopAnimation();
2354
+ if (this.animation) {
2355
+ this.animation.stop();
2344
2356
  if (this.events.animationCancel) {
2345
2357
  this.events.animationCancel.notify();
2346
2358
  }
@@ -2353,10 +2365,10 @@
2353
2365
  * @public
2354
2366
  */
2355
2367
  isAnimating() {
2356
- return !!this.stopAnimation;
2368
+ return !!this.animation;
2357
2369
  }
2358
2370
  clearAnimation() {
2359
- this.stopAnimation = null;
2371
+ this.animation = null;
2360
2372
  }
2361
2373
  /**
2362
2374
  * Destroy and clean up subscribers to this `MotionValue`.
@@ -2775,11 +2787,28 @@
2775
2787
 
2776
2788
  const appearStoreId = (id, value) => `${id}: ${value}`;
2777
2789
 
2778
- function handoffOptimizedAppearAnimation(id, name) {
2790
+ function handoffOptimizedAppearAnimation(id, name, value) {
2779
2791
  const { MotionAppearAnimations } = window;
2780
2792
  const animationId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
2781
2793
  const animation = MotionAppearAnimations && MotionAppearAnimations.get(animationId);
2782
2794
  if (animation) {
2795
+ const sampledTime = performance.now();
2796
+ /**
2797
+ * Resync handoff animation with optimised animation.
2798
+ *
2799
+ * This step would be unnecessary if we triggered animateChanges() in useEffect,
2800
+ * but due to potential hydration errors we currently fire them in useLayoutEffect.
2801
+ *
2802
+ * By the time we're safely ready to cancel the optimised WAAPI animation,
2803
+ * the main thread might have been blocked and desynced the two animations.
2804
+ *
2805
+ * Here, we resync the two animations before the optimised WAAPI animation is cancelled.
2806
+ */
2807
+ sync.update(() => {
2808
+ if (value.animation) {
2809
+ value.animation.currentTime = performance.now() - sampledTime;
2810
+ }
2811
+ });
2783
2812
  /**
2784
2813
  * We allow the animation to persist until the next frame:
2785
2814
  * 1. So it continues to play until Framer Motion is ready to render
@@ -2788,12 +2817,12 @@
2788
2817
  * it synchronously would prevent subsequent transforms from handing off.
2789
2818
  */
2790
2819
  sync.render(() => {
2820
+ MotionAppearAnimations.delete(animationId);
2791
2821
  /**
2792
2822
  * Animation.cancel() throws so it needs to be wrapped in a try/catch
2793
2823
  */
2794
2824
  try {
2795
2825
  animation.cancel();
2796
- MotionAppearAnimations.delete(animationId);
2797
2826
  }
2798
2827
  catch (e) { }
2799
2828
  });
@@ -3594,6 +3623,25 @@
3594
3623
  onStop && onStop();
3595
3624
  driverControls && driverControls.stop();
3596
3625
  },
3626
+ /**
3627
+ * Set the current time of the animation. This is purposefully
3628
+ * mirroring the WAAPI animation API to make them interchanagable.
3629
+ * Going forward this file should be ported more towards
3630
+ * https://github.com/motiondivision/motionone/blob/main/packages/animation/src/Animation.ts
3631
+ * Which behaviourally adheres to WAAPI as far as possible.
3632
+ *
3633
+ * WARNING: This is not safe to use for most animations. We currently
3634
+ * only use it for handoff from WAAPI within Framer.
3635
+ *
3636
+ * This animation function consumes time every frame rather than being sampled for time.
3637
+ * So the sample() method performs some headless frames to ensure
3638
+ * repeats are handled correctly. Ideally in the future we will replace
3639
+ * that method with this, once repeat calculations are pure.
3640
+ */
3641
+ set currentTime(t) {
3642
+ elapsed = initialElapsed;
3643
+ update(t);
3644
+ },
3597
3645
  /**
3598
3646
  * animate() can't yet be sampled for time, instead it
3599
3647
  * consumes time. So to sample it we have to run a low
@@ -3750,21 +3798,29 @@
3750
3798
  /**
3751
3799
  * Animation interrupt callback.
3752
3800
  */
3753
- return () => {
3754
- /**
3755
- * WAAPI doesn't natively have any interruption capabilities.
3756
- *
3757
- * Rather than read commited styles back out of the DOM, we can
3758
- * create a renderless JS animation and sample it twice to calculate
3759
- * its current value, "previous" value, and therefore allow
3760
- * Motion to calculate velocity for any subsequent animation.
3761
- */
3762
- const { currentTime } = animation;
3763
- if (currentTime) {
3764
- const sampleAnimation = animate$1({ ...options, autoplay: false });
3765
- value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta).value, sampleAnimation.sample(currentTime).value, sampleDelta);
3766
- }
3767
- sync.update(() => animation.cancel());
3801
+ return {
3802
+ get currentTime() {
3803
+ return animation.currentTime || 0;
3804
+ },
3805
+ set currentTime(t) {
3806
+ animation.currentTime = t;
3807
+ },
3808
+ stop: () => {
3809
+ /**
3810
+ * WAAPI doesn't natively have any interruption capabilities.
3811
+ *
3812
+ * Rather than read commited styles back out of the DOM, we can
3813
+ * create a renderless JS animation and sample it twice to calculate
3814
+ * its current value, "previous" value, and therefore allow
3815
+ * Motion to calculate velocity for any subsequent animation.
3816
+ */
3817
+ const { currentTime } = animation;
3818
+ if (currentTime) {
3819
+ const sampleAnimation = animate$1({ ...options, autoplay: false });
3820
+ value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta).value, sampleAnimation.sample(currentTime).value, sampleDelta);
3821
+ }
3822
+ sync.update(() => animation.cancel());
3823
+ },
3768
3824
  };
3769
3825
  }
3770
3826
 
@@ -3788,9 +3844,8 @@
3788
3844
  const setValue = () => {
3789
3845
  onUpdate && onUpdate(keyframes[keyframes.length - 1]);
3790
3846
  onComplete && onComplete();
3791
- return () => { };
3792
3847
  };
3793
- return elapsed ? delay(setValue, -elapsed) : setValue();
3848
+ return elapsed ? { stop: delay(setValue, -elapsed) } : setValue();
3794
3849
  }
3795
3850
 
3796
3851
  function inertia({ keyframes, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
@@ -4067,8 +4122,7 @@
4067
4122
  * If this is an inertia animation, we currently don't support pre-generating
4068
4123
  * keyframes for this as such it must always run on the main thread.
4069
4124
  */
4070
- const animation = inertia(options);
4071
- return () => animation.stop();
4125
+ return inertia(options);
4072
4126
  }
4073
4127
  /**
4074
4128
  * If there's no transition defined for this value, we can generate
@@ -4106,8 +4160,7 @@
4106
4160
  /**
4107
4161
  * If we didn't create an accelerated animation, create a JS animation
4108
4162
  */
4109
- const animation = animate$1(options);
4110
- return () => animation.stop();
4163
+ return animate$1(options);
4111
4164
  };
4112
4165
  };
4113
4166
 
@@ -4196,7 +4249,7 @@
4196
4249
  if (!value.hasAnimated) {
4197
4250
  const appearId = visualElement.getProps()[optimizedAppearDataAttribute];
4198
4251
  if (appearId) {
4199
- valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key);
4252
+ valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key, value);
4200
4253
  }
4201
4254
  }
4202
4255
  let animation = value.start(createMotionValueAnimation(key, value, valueTarget, visualElement.shouldReduceMotion && transformProps.has(key)
@@ -5946,7 +5999,7 @@
5946
5999
  * and warn against mismatches.
5947
6000
  */
5948
6001
  {
5949
- warnOnce(nextValue.version === "8.4.4", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.4 may not work as expected.`);
6002
+ warnOnce(nextValue.version === "8.4.6", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.6 may not work as expected.`);
5950
6003
  }
5951
6004
  }
5952
6005
  else if (isMotionValue(prevValue)) {
@@ -5972,7 +6025,7 @@
5972
6025
  }
5973
6026
  else {
5974
6027
  const latestValue = element.getStaticValue(key);
5975
- element.addValue(key, motionValue(latestValue !== undefined ? latestValue : nextValue));
6028
+ element.addValue(key, motionValue(latestValue !== undefined ? latestValue : nextValue, { owner: element }));
5976
6029
  }
5977
6030
  }
5978
6031
  }