framer-motion 8.4.4 → 8.4.5

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
@@ -81,11 +81,19 @@ function useVisualElement(Component, visualState, props, createVisualElement) {
81
81
  visualElement && visualElement.render();
82
82
  });
83
83
  /**
84
- * If we have optimised appear animations to handoff from, trigger animateChanges
85
- * from a synchronous useLayoutEffect to ensure there's no flash of incorrectly
86
- * styled component in the event of a hydration error.
84
+ * Ideally this function would always run in a useEffect.
85
+ *
86
+ * However, if we have optimised appear animations to handoff from,
87
+ * it needs to happen synchronously to ensure there's no flash of
88
+ * incorrect styles in the event of a hydration error.
89
+ *
90
+ * So if we detect a situtation where optimised appear animations
91
+ * are running, we use useLayoutEffect to trigger animations.
87
92
  */
88
- useIsomorphicLayoutEffect(() => {
93
+ const useAnimateChangesEffect = window.MotionAppearAnimations
94
+ ? useIsomorphicLayoutEffect
95
+ : React.useEffect;
96
+ useAnimateChangesEffect(() => {
89
97
  if (visualElement && visualElement.animationState) {
90
98
  visualElement.animationState.animateChanges();
91
99
  }
@@ -2081,7 +2089,7 @@ class MotionValue {
2081
2089
  * This will be replaced by the build step with the latest version number.
2082
2090
  * When MotionValues are provided to motion components, warn if versions are mixed.
2083
2091
  */
2084
- this.version = "8.4.4";
2092
+ this.version = "8.4.5";
2085
2093
  /**
2086
2094
  * Duration, in milliseconds, since last updating frame.
2087
2095
  *
@@ -2320,11 +2328,11 @@ class MotionValue {
2320
2328
  *
2321
2329
  * @internal
2322
2330
  */
2323
- start(animation) {
2331
+ start(startAnimation) {
2324
2332
  this.stop();
2325
2333
  return new Promise((resolve) => {
2326
2334
  this.hasAnimated = true;
2327
- this.stopAnimation = animation(resolve);
2335
+ this.animation = startAnimation(resolve) || null;
2328
2336
  if (this.events.animationStart) {
2329
2337
  this.events.animationStart.notify();
2330
2338
  }
@@ -2341,8 +2349,8 @@ class MotionValue {
2341
2349
  * @public
2342
2350
  */
2343
2351
  stop() {
2344
- if (this.stopAnimation) {
2345
- this.stopAnimation();
2352
+ if (this.animation) {
2353
+ this.animation.stop();
2346
2354
  if (this.events.animationCancel) {
2347
2355
  this.events.animationCancel.notify();
2348
2356
  }
@@ -2355,10 +2363,10 @@ class MotionValue {
2355
2363
  * @public
2356
2364
  */
2357
2365
  isAnimating() {
2358
- return !!this.stopAnimation;
2366
+ return !!this.animation;
2359
2367
  }
2360
2368
  clearAnimation() {
2361
- this.stopAnimation = null;
2369
+ this.animation = null;
2362
2370
  }
2363
2371
  /**
2364
2372
  * Destroy and clean up subscribers to this `MotionValue`.
@@ -2777,11 +2785,28 @@ function isWillChangeMotionValue(value) {
2777
2785
 
2778
2786
  const appearStoreId = (id, value) => `${id}: ${value}`;
2779
2787
 
2780
- function handoffOptimizedAppearAnimation(id, name) {
2788
+ function handoffOptimizedAppearAnimation(id, name, value) {
2781
2789
  const { MotionAppearAnimations } = window;
2782
2790
  const animationId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
2783
2791
  const animation = MotionAppearAnimations && MotionAppearAnimations.get(animationId);
2784
2792
  if (animation) {
2793
+ const sampledTime = performance.now();
2794
+ /**
2795
+ * Resync handoff animation with optimised animation.
2796
+ *
2797
+ * This step would be unnecessary if we triggered animateChanges() in useEffect,
2798
+ * but due to potential hydration errors we currently fire them in useLayoutEffect.
2799
+ *
2800
+ * By the time we're safely ready to cancel the optimised WAAPI animation,
2801
+ * the main thread might have been blocked and desynced the two animations.
2802
+ *
2803
+ * Here, we resync the two animations before the optimised WAAPI animation is cancelled.
2804
+ */
2805
+ sync.update(() => {
2806
+ if (value.animation) {
2807
+ value.animation.currentTime = performance.now() - sampledTime;
2808
+ }
2809
+ });
2785
2810
  /**
2786
2811
  * We allow the animation to persist until the next frame:
2787
2812
  * 1. So it continues to play until Framer Motion is ready to render
@@ -2790,12 +2815,12 @@ function handoffOptimizedAppearAnimation(id, name) {
2790
2815
  * it synchronously would prevent subsequent transforms from handing off.
2791
2816
  */
2792
2817
  sync.render(() => {
2818
+ MotionAppearAnimations.delete(animationId);
2793
2819
  /**
2794
2820
  * Animation.cancel() throws so it needs to be wrapped in a try/catch
2795
2821
  */
2796
2822
  try {
2797
2823
  animation.cancel();
2798
- MotionAppearAnimations.delete(animationId);
2799
2824
  }
2800
2825
  catch (e) { }
2801
2826
  });
@@ -3581,6 +3606,25 @@ function animate$1({ duration, driver = framesync, elapsed = 0, repeat: repeatMa
3581
3606
  onStop && onStop();
3582
3607
  driverControls && driverControls.stop();
3583
3608
  },
3609
+ /**
3610
+ * Set the current time of the animation. This is purposefully
3611
+ * mirroring the WAAPI animation API to make them interchanagable.
3612
+ * Going forward this file should be ported more towards
3613
+ * https://github.com/motiondivision/motionone/blob/main/packages/animation/src/Animation.ts
3614
+ * Which behaviourally adheres to WAAPI as far as possible.
3615
+ *
3616
+ * WARNING: This is not safe to use for most animations. We currently
3617
+ * only use it for handoff from WAAPI within Framer.
3618
+ *
3619
+ * This animation function consumes time every frame rather than being sampled for time.
3620
+ * So the sample() method performs some headless frames to ensure
3621
+ * repeats are handled correctly. Ideally in the future we will replace
3622
+ * that method with this, once repeat calculations are pure.
3623
+ */
3624
+ set currentTime(t) {
3625
+ elapsed = initialElapsed;
3626
+ update(t);
3627
+ },
3584
3628
  /**
3585
3629
  * animate() can't yet be sampled for time, instead it
3586
3630
  * consumes time. So to sample it we have to run a low
@@ -3737,21 +3781,29 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
3737
3781
  /**
3738
3782
  * Animation interrupt callback.
3739
3783
  */
3740
- return () => {
3741
- /**
3742
- * WAAPI doesn't natively have any interruption capabilities.
3743
- *
3744
- * Rather than read commited styles back out of the DOM, we can
3745
- * create a renderless JS animation and sample it twice to calculate
3746
- * its current value, "previous" value, and therefore allow
3747
- * Motion to calculate velocity for any subsequent animation.
3748
- */
3749
- const { currentTime } = animation;
3750
- if (currentTime) {
3751
- const sampleAnimation = animate$1({ ...options, autoplay: false });
3752
- value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta).value, sampleAnimation.sample(currentTime).value, sampleDelta);
3753
- }
3754
- sync.update(() => animation.cancel());
3784
+ return {
3785
+ get currentTime() {
3786
+ return animation.currentTime || 0;
3787
+ },
3788
+ set currentTime(t) {
3789
+ animation.currentTime = t;
3790
+ },
3791
+ stop: () => {
3792
+ /**
3793
+ * WAAPI doesn't natively have any interruption capabilities.
3794
+ *
3795
+ * Rather than read commited styles back out of the DOM, we can
3796
+ * create a renderless JS animation and sample it twice to calculate
3797
+ * its current value, "previous" value, and therefore allow
3798
+ * Motion to calculate velocity for any subsequent animation.
3799
+ */
3800
+ const { currentTime } = animation;
3801
+ if (currentTime) {
3802
+ const sampleAnimation = animate$1({ ...options, autoplay: false });
3803
+ value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta).value, sampleAnimation.sample(currentTime).value, sampleDelta);
3804
+ }
3805
+ sync.update(() => animation.cancel());
3806
+ },
3755
3807
  };
3756
3808
  }
3757
3809
 
@@ -3775,9 +3827,8 @@ function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
3775
3827
  const setValue = () => {
3776
3828
  onUpdate && onUpdate(keyframes[keyframes.length - 1]);
3777
3829
  onComplete && onComplete();
3778
- return () => { };
3779
3830
  };
3780
- return elapsed ? delay(setValue, -elapsed) : setValue();
3831
+ return elapsed ? { stop: delay(setValue, -elapsed) } : setValue();
3781
3832
  }
3782
3833
 
3783
3834
  function inertia({ keyframes, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
@@ -4054,8 +4105,7 @@ const createMotionValueAnimation = (valueName, value, target, transition = {}) =
4054
4105
  * If this is an inertia animation, we currently don't support pre-generating
4055
4106
  * keyframes for this as such it must always run on the main thread.
4056
4107
  */
4057
- const animation = inertia(options);
4058
- return () => animation.stop();
4108
+ return inertia(options);
4059
4109
  }
4060
4110
  /**
4061
4111
  * If there's no transition defined for this value, we can generate
@@ -4093,8 +4143,7 @@ const createMotionValueAnimation = (valueName, value, target, transition = {}) =
4093
4143
  /**
4094
4144
  * If we didn't create an accelerated animation, create a JS animation
4095
4145
  */
4096
- const animation = animate$1(options);
4097
- return () => animation.stop();
4146
+ return animate$1(options);
4098
4147
  };
4099
4148
  };
4100
4149
 
@@ -4183,7 +4232,7 @@ function animateTarget(visualElement, definition, { delay = 0, transitionOverrid
4183
4232
  if (!value.hasAnimated) {
4184
4233
  const appearId = visualElement.getProps()[optimizedAppearDataAttribute];
4185
4234
  if (appearId) {
4186
- valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key);
4235
+ valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key, value);
4187
4236
  }
4188
4237
  }
4189
4238
  let animation = value.start(createMotionValueAnimation(key, value, valueTarget, visualElement.shouldReduceMotion && transformProps.has(key)
@@ -5933,7 +5982,7 @@ function updateMotionValuesFromProps(element, next, prev) {
5933
5982
  * and warn against mismatches.
5934
5983
  */
5935
5984
  if (process.env.NODE_ENV === "development") {
5936
- warnOnce(nextValue.version === "8.4.4", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.4 may not work as expected.`);
5985
+ warnOnce(nextValue.version === "8.4.5", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.5 may not work as expected.`);
5937
5986
  }
5938
5987
  }
5939
5988
  else if (isMotionValue(prevValue)) {
@@ -4,9 +4,8 @@ function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
4
4
  const setValue = () => {
5
5
  onUpdate && onUpdate(keyframes[keyframes.length - 1]);
6
6
  onComplete && onComplete();
7
- return () => { };
8
7
  };
9
- return elapsed ? delay(setValue, -elapsed) : setValue();
8
+ return elapsed ? { stop: delay(setValue, -elapsed) } : setValue();
10
9
  }
11
10
 
12
11
  export { createInstantAnimation };
@@ -65,8 +65,7 @@ const createMotionValueAnimation = (valueName, value, target, transition = {}) =
65
65
  * If this is an inertia animation, we currently don't support pre-generating
66
66
  * keyframes for this as such it must always run on the main thread.
67
67
  */
68
- const animation = inertia(options);
69
- return () => animation.stop();
68
+ return inertia(options);
70
69
  }
71
70
  /**
72
71
  * If there's no transition defined for this value, we can generate
@@ -104,8 +103,7 @@ const createMotionValueAnimation = (valueName, value, target, transition = {}) =
104
103
  /**
105
104
  * If we didn't create an accelerated animation, create a JS animation
106
105
  */
107
- const animation = animate(options);
108
- return () => animation.stop();
106
+ return animate(options);
109
107
  };
110
108
  };
111
109
 
@@ -105,6 +105,25 @@ function animate({ duration, driver = framesync, elapsed = 0, repeat: repeatMax
105
105
  onStop && onStop();
106
106
  driverControls && driverControls.stop();
107
107
  },
108
+ /**
109
+ * Set the current time of the animation. This is purposefully
110
+ * mirroring the WAAPI animation API to make them interchanagable.
111
+ * Going forward this file should be ported more towards
112
+ * https://github.com/motiondivision/motionone/blob/main/packages/animation/src/Animation.ts
113
+ * Which behaviourally adheres to WAAPI as far as possible.
114
+ *
115
+ * WARNING: This is not safe to use for most animations. We currently
116
+ * only use it for handoff from WAAPI within Framer.
117
+ *
118
+ * This animation function consumes time every frame rather than being sampled for time.
119
+ * So the sample() method performs some headless frames to ensure
120
+ * repeats are handled correctly. Ideally in the future we will replace
121
+ * that method with this, once repeat calculations are pure.
122
+ */
123
+ set currentTime(t) {
124
+ elapsed = initialElapsed;
125
+ update(t);
126
+ },
108
127
  /**
109
128
  * animate() can't yet be sampled for time, instead it
110
129
  * consumes time. So to sample it we have to run a low
@@ -2,11 +2,28 @@ import { sync } from '../../frameloop/index.mjs';
2
2
  import { transformProps } from '../../render/html/utils/transform.mjs';
3
3
  import { appearStoreId } from './store-id.mjs';
4
4
 
5
- function handoffOptimizedAppearAnimation(id, name) {
5
+ function handoffOptimizedAppearAnimation(id, name, value) {
6
6
  const { MotionAppearAnimations } = window;
7
7
  const animationId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
8
8
  const animation = MotionAppearAnimations && MotionAppearAnimations.get(animationId);
9
9
  if (animation) {
10
+ const sampledTime = performance.now();
11
+ /**
12
+ * Resync handoff animation with optimised animation.
13
+ *
14
+ * This step would be unnecessary if we triggered animateChanges() in useEffect,
15
+ * but due to potential hydration errors we currently fire them in useLayoutEffect.
16
+ *
17
+ * By the time we're safely ready to cancel the optimised WAAPI animation,
18
+ * the main thread might have been blocked and desynced the two animations.
19
+ *
20
+ * Here, we resync the two animations before the optimised WAAPI animation is cancelled.
21
+ */
22
+ sync.update(() => {
23
+ if (value.animation) {
24
+ value.animation.currentTime = performance.now() - sampledTime;
25
+ }
26
+ });
10
27
  /**
11
28
  * We allow the animation to persist until the next frame:
12
29
  * 1. So it continues to play until Framer Motion is ready to render
@@ -15,12 +32,12 @@ function handoffOptimizedAppearAnimation(id, name) {
15
32
  * it synchronously would prevent subsequent transforms from handing off.
16
33
  */
17
34
  sync.render(() => {
35
+ MotionAppearAnimations.delete(animationId);
18
36
  /**
19
37
  * Animation.cancel() throws so it needs to be wrapped in a try/catch
20
38
  */
21
39
  try {
22
40
  animation.cancel();
23
- MotionAppearAnimations.delete(animationId);
24
41
  }
25
42
  catch (e) { }
26
43
  });
@@ -80,21 +80,29 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
80
80
  /**
81
81
  * Animation interrupt callback.
82
82
  */
83
- return () => {
84
- /**
85
- * WAAPI doesn't natively have any interruption capabilities.
86
- *
87
- * Rather than read commited styles back out of the DOM, we can
88
- * create a renderless JS animation and sample it twice to calculate
89
- * its current value, "previous" value, and therefore allow
90
- * Motion to calculate velocity for any subsequent animation.
91
- */
92
- const { currentTime } = animation;
93
- if (currentTime) {
94
- const sampleAnimation = animate({ ...options, autoplay: false });
95
- value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta).value, sampleAnimation.sample(currentTime).value, sampleDelta);
96
- }
97
- sync.update(() => animation.cancel());
83
+ return {
84
+ get currentTime() {
85
+ return animation.currentTime || 0;
86
+ },
87
+ set currentTime(t) {
88
+ animation.currentTime = t;
89
+ },
90
+ stop: () => {
91
+ /**
92
+ * WAAPI doesn't natively have any interruption capabilities.
93
+ *
94
+ * Rather than read commited styles back out of the DOM, we can
95
+ * create a renderless JS animation and sample it twice to calculate
96
+ * its current value, "previous" value, and therefore allow
97
+ * Motion to calculate velocity for any subsequent animation.
98
+ */
99
+ const { currentTime } = animation;
100
+ if (currentTime) {
101
+ const sampleAnimation = animate({ ...options, autoplay: false });
102
+ value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta).value, sampleAnimation.sample(currentTime).value, sampleDelta);
103
+ }
104
+ sync.update(() => animation.cancel());
105
+ },
98
106
  };
99
107
  }
100
108
 
@@ -1,4 +1,4 @@
1
- import { useContext, useRef } from 'react';
1
+ import { useContext, useRef, useEffect } from 'react';
2
2
  import { PresenceContext } from '../../context/PresenceContext.mjs';
3
3
  import { useVisualElementContext } from '../../context/MotionContext/index.mjs';
4
4
  import { useIsomorphicLayoutEffect } from '../../utils/use-isomorphic-effect.mjs';
@@ -32,11 +32,19 @@ function useVisualElement(Component, visualState, props, createVisualElement) {
32
32
  visualElement && visualElement.render();
33
33
  });
34
34
  /**
35
- * If we have optimised appear animations to handoff from, trigger animateChanges
36
- * from a synchronous useLayoutEffect to ensure there's no flash of incorrectly
37
- * styled component in the event of a hydration error.
35
+ * Ideally this function would always run in a useEffect.
36
+ *
37
+ * However, if we have optimised appear animations to handoff from,
38
+ * it needs to happen synchronously to ensure there's no flash of
39
+ * incorrect styles in the event of a hydration error.
40
+ *
41
+ * So if we detect a situtation where optimised appear animations
42
+ * are running, we use useLayoutEffect to trigger animations.
38
43
  */
39
- useIsomorphicLayoutEffect(() => {
44
+ const useAnimateChangesEffect = window.MotionAppearAnimations
45
+ ? useIsomorphicLayoutEffect
46
+ : useEffect;
47
+ useAnimateChangesEffect(() => {
40
48
  if (visualElement && visualElement.animationState) {
41
49
  visualElement.animationState.animateChanges();
42
50
  }
@@ -91,7 +91,7 @@ function animateTarget(visualElement, definition, { delay = 0, transitionOverrid
91
91
  if (!value.hasAnimated) {
92
92
  const appearId = visualElement.getProps()[optimizedAppearDataAttribute];
93
93
  if (appearId) {
94
- valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key);
94
+ valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key, value);
95
95
  }
96
96
  }
97
97
  let animation = value.start(createMotionValueAnimation(key, value, valueTarget, visualElement.shouldReduceMotion && transformProps.has(key)
@@ -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 === "8.4.4", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.4 may not work as expected.`);
25
+ warnOnce(nextValue.version === "8.4.5", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.5 may not work as expected.`);
26
26
  }
27
27
  }
28
28
  else if (isMotionValue(prevValue)) {
@@ -25,7 +25,7 @@ class MotionValue {
25
25
  * This will be replaced by the build step with the latest version number.
26
26
  * When MotionValues are provided to motion components, warn if versions are mixed.
27
27
  */
28
- this.version = "8.4.4";
28
+ this.version = "8.4.5";
29
29
  /**
30
30
  * Duration, in milliseconds, since last updating frame.
31
31
  *
@@ -264,11 +264,11 @@ class MotionValue {
264
264
  *
265
265
  * @internal
266
266
  */
267
- start(animation) {
267
+ start(startAnimation) {
268
268
  this.stop();
269
269
  return new Promise((resolve) => {
270
270
  this.hasAnimated = true;
271
- this.stopAnimation = animation(resolve);
271
+ this.animation = startAnimation(resolve) || null;
272
272
  if (this.events.animationStart) {
273
273
  this.events.animationStart.notify();
274
274
  }
@@ -285,8 +285,8 @@ class MotionValue {
285
285
  * @public
286
286
  */
287
287
  stop() {
288
- if (this.stopAnimation) {
289
- this.stopAnimation();
288
+ if (this.animation) {
289
+ this.animation.stop();
290
290
  if (this.events.animationCancel) {
291
291
  this.events.animationCancel.notify();
292
292
  }
@@ -299,10 +299,10 @@ class MotionValue {
299
299
  * @public
300
300
  */
301
301
  isAnimating() {
302
- return !!this.stopAnimation;
302
+ return !!this.animation;
303
303
  }
304
304
  clearAnimation() {
305
- this.stopAnimation = null;
305
+ this.animation = null;
306
306
  }
307
307
  /**
308
308
  * Destroy and clean up subscribers to this `MotionValue`.