framer-motion 8.4.3 → 8.4.4-alpha.0

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
  }
@@ -2072,7 +2080,7 @@ class MotionValue {
2072
2080
  * This will be replaced by the build step with the latest version number.
2073
2081
  * When MotionValues are provided to motion components, warn if versions are mixed.
2074
2082
  */
2075
- this.version = "8.4.2";
2083
+ this.version = "8.4.4-alpha.0";
2076
2084
  /**
2077
2085
  * Duration, in milliseconds, since last updating frame.
2078
2086
  *
@@ -2311,11 +2319,11 @@ class MotionValue {
2311
2319
  *
2312
2320
  * @internal
2313
2321
  */
2314
- start(animation) {
2322
+ start(startAnimation) {
2315
2323
  this.stop();
2316
2324
  return new Promise((resolve) => {
2317
2325
  this.hasAnimated = true;
2318
- this.stopAnimation = animation(resolve);
2326
+ this.animation = startAnimation(resolve) || null;
2319
2327
  if (this.events.animationStart) {
2320
2328
  this.events.animationStart.notify();
2321
2329
  }
@@ -2332,8 +2340,8 @@ class MotionValue {
2332
2340
  * @public
2333
2341
  */
2334
2342
  stop() {
2335
- if (this.stopAnimation) {
2336
- this.stopAnimation();
2343
+ if (this.animation) {
2344
+ this.animation.stop();
2337
2345
  if (this.events.animationCancel) {
2338
2346
  this.events.animationCancel.notify();
2339
2347
  }
@@ -2346,10 +2354,10 @@ class MotionValue {
2346
2354
  * @public
2347
2355
  */
2348
2356
  isAnimating() {
2349
- return !!this.stopAnimation;
2357
+ return !!this.animation;
2350
2358
  }
2351
2359
  clearAnimation() {
2352
- this.stopAnimation = null;
2360
+ this.animation = null;
2353
2361
  }
2354
2362
  /**
2355
2363
  * Destroy and clean up subscribers to this `MotionValue`.
@@ -2768,11 +2776,28 @@ function isWillChangeMotionValue(value) {
2768
2776
 
2769
2777
  const appearStoreId = (id, value) => `${id}: ${value}`;
2770
2778
 
2771
- function handoffOptimizedAppearAnimation(id, name) {
2779
+ function handoffOptimizedAppearAnimation(id, name, value) {
2772
2780
  const { MotionAppearAnimations } = window;
2773
2781
  const animationId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
2774
2782
  const animation = MotionAppearAnimations && MotionAppearAnimations.get(animationId);
2775
2783
  if (animation) {
2784
+ const sampledTime = performance.now();
2785
+ /**
2786
+ * Resync handoff animation with optimised animation.
2787
+ *
2788
+ * This step would be unnecessary if we triggered animateChanges() in useEffect,
2789
+ * but due to potential hydration errors we currently fire them in useLayoutEffect.
2790
+ *
2791
+ * By the time we're safely ready to cancel the optimised WAAPI animation,
2792
+ * the main thread might have been blocked and desynced the two animations.
2793
+ *
2794
+ * Here, we resync the two animations before the optimised WAAPI animation is cancelled.
2795
+ */
2796
+ sync.update(() => {
2797
+ if (value.animation) {
2798
+ value.animation.currentTime = performance.now() - sampledTime;
2799
+ }
2800
+ });
2776
2801
  /**
2777
2802
  * We allow the animation to persist until the next frame:
2778
2803
  * 1. So it continues to play until Framer Motion is ready to render
@@ -2781,12 +2806,12 @@ function handoffOptimizedAppearAnimation(id, name) {
2781
2806
  * it synchronously would prevent subsequent transforms from handing off.
2782
2807
  */
2783
2808
  sync.render(() => {
2809
+ MotionAppearAnimations.delete(animationId);
2784
2810
  /**
2785
2811
  * Animation.cancel() throws so it needs to be wrapped in a try/catch
2786
2812
  */
2787
2813
  try {
2788
2814
  animation.cancel();
2789
- MotionAppearAnimations.delete(animationId);
2790
2815
  }
2791
2816
  catch (e) { }
2792
2817
  });
@@ -3572,6 +3597,25 @@ function animate$1({ duration, driver = framesync, elapsed = 0, repeat: repeatMa
3572
3597
  onStop && onStop();
3573
3598
  driverControls && driverControls.stop();
3574
3599
  },
3600
+ /**
3601
+ * Set the current time of the animation. This is purposefully
3602
+ * mirroring the WAAPI animation API to make them interchanagable.
3603
+ * Going forward this file should be ported more towards
3604
+ * https://github.com/motiondivision/motionone/blob/main/packages/animation/src/Animation.ts
3605
+ * Which behaviourally adheres to WAAPI as far as possible.
3606
+ *
3607
+ * WARNING: This is not safe to use for most animations. We currently
3608
+ * only use it for handoff from WAAPI within Framer.
3609
+ *
3610
+ * This animation function consumes time every frame rather than being sampled for time.
3611
+ * So the sample() method performs some headless frames to ensure
3612
+ * repeats are handled correctly. Ideally in the future we will replace
3613
+ * that method with this, once repeat calculations are pure.
3614
+ */
3615
+ set currentTime(t) {
3616
+ elapsed = initialElapsed;
3617
+ update(t);
3618
+ },
3575
3619
  /**
3576
3620
  * animate() can't yet be sampled for time, instead it
3577
3621
  * consumes time. So to sample it we have to run a low
@@ -3728,21 +3772,29 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
3728
3772
  /**
3729
3773
  * Animation interrupt callback.
3730
3774
  */
3731
- return () => {
3732
- /**
3733
- * WAAPI doesn't natively have any interruption capabilities.
3734
- *
3735
- * Rather than read commited styles back out of the DOM, we can
3736
- * create a renderless JS animation and sample it twice to calculate
3737
- * its current value, "previous" value, and therefore allow
3738
- * Motion to calculate velocity for any subsequent animation.
3739
- */
3740
- const { currentTime } = animation;
3741
- if (currentTime) {
3742
- const sampleAnimation = animate$1({ ...options, autoplay: false });
3743
- value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta).value, sampleAnimation.sample(currentTime).value, sampleDelta);
3744
- }
3745
- sync.update(() => animation.cancel());
3775
+ return {
3776
+ get currentTime() {
3777
+ return animation.currentTime || 0;
3778
+ },
3779
+ set currentTime(t) {
3780
+ animation.currentTime = t;
3781
+ },
3782
+ stop: () => {
3783
+ /**
3784
+ * WAAPI doesn't natively have any interruption capabilities.
3785
+ *
3786
+ * Rather than read commited styles back out of the DOM, we can
3787
+ * create a renderless JS animation and sample it twice to calculate
3788
+ * its current value, "previous" value, and therefore allow
3789
+ * Motion to calculate velocity for any subsequent animation.
3790
+ */
3791
+ const { currentTime } = animation;
3792
+ if (currentTime) {
3793
+ const sampleAnimation = animate$1({ ...options, autoplay: false });
3794
+ value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta).value, sampleAnimation.sample(currentTime).value, sampleDelta);
3795
+ }
3796
+ sync.update(() => animation.cancel());
3797
+ },
3746
3798
  };
3747
3799
  }
3748
3800
 
@@ -3766,9 +3818,8 @@ function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
3766
3818
  const setValue = () => {
3767
3819
  onUpdate && onUpdate(keyframes[keyframes.length - 1]);
3768
3820
  onComplete && onComplete();
3769
- return () => { };
3770
3821
  };
3771
- return elapsed ? delay(setValue, -elapsed) : setValue();
3822
+ return elapsed ? { stop: delay(setValue, -elapsed) } : setValue();
3772
3823
  }
3773
3824
 
3774
3825
  function inertia({ keyframes, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
@@ -4045,8 +4096,7 @@ const createMotionValueAnimation = (valueName, value, target, transition = {}) =
4045
4096
  * If this is an inertia animation, we currently don't support pre-generating
4046
4097
  * keyframes for this as such it must always run on the main thread.
4047
4098
  */
4048
- const animation = inertia(options);
4049
- return () => animation.stop();
4099
+ return inertia(options);
4050
4100
  }
4051
4101
  /**
4052
4102
  * If there's no transition defined for this value, we can generate
@@ -4084,8 +4134,7 @@ const createMotionValueAnimation = (valueName, value, target, transition = {}) =
4084
4134
  /**
4085
4135
  * If we didn't create an accelerated animation, create a JS animation
4086
4136
  */
4087
- const animation = animate$1(options);
4088
- return () => animation.stop();
4137
+ return animate$1(options);
4089
4138
  };
4090
4139
  };
4091
4140
 
@@ -4174,7 +4223,7 @@ function animateTarget(visualElement, definition, { delay = 0, transitionOverrid
4174
4223
  if (!value.hasAnimated) {
4175
4224
  const appearId = visualElement.getProps()[optimizedAppearDataAttribute];
4176
4225
  if (appearId) {
4177
- valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key);
4226
+ valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key, value);
4178
4227
  }
4179
4228
  }
4180
4229
  let animation = value.start(createMotionValueAnimation(key, value, valueTarget, visualElement.shouldReduceMotion && transformProps.has(key)
@@ -5924,7 +5973,7 @@ function updateMotionValuesFromProps(element, next, prev) {
5924
5973
  * and warn against mismatches.
5925
5974
  */
5926
5975
  if (process.env.NODE_ENV === "development") {
5927
- warnOnce(nextValue.version === "8.4.2", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.2 may not work as expected.`);
5976
+ warnOnce(nextValue.version === "8.4.4-alpha.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.4-alpha.0 may not work as expected.`);
5928
5977
  }
5929
5978
  }
5930
5979
  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.2", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.2 may not work as expected.`);
25
+ warnOnce(nextValue.version === "8.4.4-alpha.0", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.4-alpha.0 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.2";
28
+ this.version = "8.4.4-alpha.0";
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`.