framer-motion 8.0.3 → 8.0.4

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
@@ -90,7 +90,6 @@ function useVisualElement(Component, visualState, props, createVisualElement) {
90
90
  visualElement.animationState.animateChanges();
91
91
  }
92
92
  });
93
- useIsomorphicLayoutEffect(() => () => visualElement && visualElement.notify("Unmount"), []);
94
93
  return visualElement;
95
94
  }
96
95
 
@@ -2062,7 +2061,7 @@ class MotionValue {
2062
2061
  * This will be replaced by the build step with the latest version number.
2063
2062
  * When MotionValues are provided to motion components, warn if versions are mixed.
2064
2063
  */
2065
- this.version = "8.0.3";
2064
+ this.version = "8.0.4";
2066
2065
  /**
2067
2066
  * Duration, in milliseconds, since last updating frame.
2068
2067
  *
@@ -2176,14 +2175,6 @@ class MotionValue {
2176
2175
  * }
2177
2176
  * ```
2178
2177
  *
2179
- * @privateRemarks
2180
- *
2181
- * We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
2182
- *
2183
- * ```jsx
2184
- * useOnChange(x, () => {})
2185
- * ```
2186
- *
2187
2178
  * @param subscriber - A function that receives the latest value.
2188
2179
  * @returns A function that, when called, will cancel this subscription.
2189
2180
  *
@@ -2196,7 +2187,22 @@ class MotionValue {
2196
2187
  if (!this.events[eventName]) {
2197
2188
  this.events[eventName] = new SubscriptionManager();
2198
2189
  }
2199
- return this.events[eventName].add(callback);
2190
+ const unsubscribe = this.events[eventName].add(callback);
2191
+ if (eventName === "change") {
2192
+ return () => {
2193
+ unsubscribe();
2194
+ /**
2195
+ * If we have no more change listeners by the start
2196
+ * of the next frame, stop active animations.
2197
+ */
2198
+ sync.read(() => {
2199
+ if (!this.events.change.getSize()) {
2200
+ this.stop();
2201
+ }
2202
+ });
2203
+ };
2204
+ }
2205
+ return unsubscribe;
2200
2206
  }
2201
2207
  clearListeners() {
2202
2208
  for (const eventManagers in this.events) {
@@ -5865,7 +5871,7 @@ function updateMotionValuesFromProps(element, next, prev) {
5865
5871
  * and warn against mismatches.
5866
5872
  */
5867
5873
  if (process.env.NODE_ENV === "development") {
5868
- warnOnce(nextValue.version === "8.0.3", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.0.3 may not work as expected.`);
5874
+ warnOnce(nextValue.version === "8.0.4", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.0.4 may not work as expected.`);
5869
5875
  }
5870
5876
  }
5871
5877
  else if (isMotionValue(prevValue)) {
@@ -5909,7 +5915,6 @@ const propEventHandlers = [
5909
5915
  "AnimationStart",
5910
5916
  "AnimationComplete",
5911
5917
  "Update",
5912
- "Unmount",
5913
5918
  "BeforeLayoutMeasure",
5914
5919
  "LayoutMeasure",
5915
5920
  "LayoutAnimationStart",
@@ -6255,11 +6260,12 @@ class VisualElement {
6255
6260
  */
6256
6261
  addValue(key, value) {
6257
6262
  // Remove existing value if it exists
6258
- if (this.hasValue(key))
6263
+ if (value !== this.values.get(key)) {
6259
6264
  this.removeValue(key);
6265
+ this.bindToMotionValue(key, value);
6266
+ }
6260
6267
  this.values.set(key, value);
6261
6268
  this.latestValues[key] = value.get();
6262
- this.bindToMotionValue(key, value);
6263
6269
  }
6264
6270
  /**
6265
6271
  * Remove a motion value and unbind any active subscriptions.
@@ -6278,10 +6284,6 @@ class VisualElement {
6278
6284
  hasValue(key) {
6279
6285
  return this.values.has(key);
6280
6286
  }
6281
- /**
6282
- * Get a motion value for this key. If called with a default
6283
- * value, we'll create one if none exists.
6284
- */
6285
6287
  getValue(key, defaultValue) {
6286
6288
  if (this.props.values && this.props.values[key]) {
6287
6289
  return this.props.values[key];
@@ -8981,24 +8983,6 @@ function transform(...args) {
8981
8983
  return useImmediate ? interpolator(inputValue) : interpolator;
8982
8984
  }
8983
8985
 
8984
- function useOnChange(value, callback) {
8985
- useIsomorphicLayoutEffect(() => {
8986
- if (isMotionValue(value)) {
8987
- callback(value.get());
8988
- return value.on("change", callback);
8989
- }
8990
- }, [value, callback]);
8991
- }
8992
- function useMultiOnChange(values, handler, cleanup) {
8993
- useIsomorphicLayoutEffect(() => {
8994
- const subscriptions = values.map((value) => value.on("change", handler));
8995
- return () => {
8996
- subscriptions.forEach((unsubscribe) => unsubscribe());
8997
- cleanup();
8998
- };
8999
- });
9000
- }
9001
-
9002
8986
  function useCombineMotionValues(values, combineValues) {
9003
8987
  /**
9004
8988
  * Initialise the returned motion value. This remains the same between renders.
@@ -9020,7 +9004,14 @@ function useCombineMotionValues(values, combineValues) {
9020
9004
  * Subscribe to all motion values found within the template. Whenever any of them change,
9021
9005
  * schedule an update.
9022
9006
  */
9023
- useMultiOnChange(values, () => sync.update(updateValue, false, true), () => cancelSync.update(updateValue));
9007
+ useIsomorphicLayoutEffect(() => {
9008
+ const scheduleUpdate = () => sync.update(updateValue, false, true);
9009
+ const subscriptions = values.map((v) => v.on("change", scheduleUpdate));
9010
+ return () => {
9011
+ subscriptions.forEach((unsubscribe) => unsubscribe());
9012
+ cancelSync.update(updateValue);
9013
+ };
9014
+ });
9024
9015
  return value;
9025
9016
  }
9026
9017
 
@@ -9180,7 +9171,11 @@ function useSpring(source, config = {}) {
9180
9171
  return value.get();
9181
9172
  });
9182
9173
  }, [JSON.stringify(config)]);
9183
- useOnChange(source, (v) => value.set(parseFloat(v)));
9174
+ useIsomorphicLayoutEffect(() => {
9175
+ if (isMotionValue(source)) {
9176
+ return source.on("change", (v) => value.set(parseFloat(v)));
9177
+ }
9178
+ }, [value]);
9184
9179
  return value;
9185
9180
  }
9186
9181
 
@@ -9317,7 +9312,13 @@ function useWillChange() {
9317
9312
  }
9318
9313
 
9319
9314
  function useMotionValueEvent(value, event, callback) {
9320
- useIsomorphicLayoutEffect(() => value.on(event, callback), [value, event, callback]);
9315
+ /**
9316
+ * useInsertionEffect will create subscriptions before any other
9317
+ * effects will run. Effects run upwards through the tree so it
9318
+ * can be that binding a useLayoutEffect higher up the tree can
9319
+ * miss changes from lower down the tree.
9320
+ */
9321
+ React.useInsertionEffect(() => value.on(event, callback), [value, event, callback]);
9321
9322
  }
9322
9323
 
9323
9324
  /**
@@ -41,7 +41,6 @@ function useVisualElement(Component, visualState, props, createVisualElement) {
41
41
  visualElement.animationState.animateChanges();
42
42
  }
43
43
  });
44
- useIsomorphicLayoutEffect(() => () => visualElement && visualElement.notify("Unmount"), []);
45
44
  return visualElement;
46
45
  }
47
46
 
@@ -24,7 +24,6 @@ const propEventHandlers = [
24
24
  "AnimationStart",
25
25
  "AnimationComplete",
26
26
  "Update",
27
- "Unmount",
28
27
  "BeforeLayoutMeasure",
29
28
  "LayoutMeasure",
30
29
  "LayoutAnimationStart",
@@ -370,11 +369,12 @@ class VisualElement {
370
369
  */
371
370
  addValue(key, value) {
372
371
  // Remove existing value if it exists
373
- if (this.hasValue(key))
372
+ if (value !== this.values.get(key)) {
374
373
  this.removeValue(key);
374
+ this.bindToMotionValue(key, value);
375
+ }
375
376
  this.values.set(key, value);
376
377
  this.latestValues[key] = value.get();
377
- this.bindToMotionValue(key, value);
378
378
  }
379
379
  /**
380
380
  * Remove a motion value and unbind any active subscriptions.
@@ -393,10 +393,6 @@ class VisualElement {
393
393
  hasValue(key) {
394
394
  return this.values.has(key);
395
395
  }
396
- /**
397
- * Get a motion value for this key. If called with a default
398
- * value, we'll create one if none exists.
399
- */
400
396
  getValue(key, defaultValue) {
401
397
  if (this.props.values && this.props.values[key]) {
402
398
  return this.props.values[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.0.3", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.0.3 may not work as expected.`);
25
+ warnOnce(nextValue.version === "8.0.4", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.0.4 may not work as expected.`);
26
26
  }
27
27
  }
28
28
  else if (isMotionValue(prevValue)) {
@@ -1,7 +1,13 @@
1
- import { useIsomorphicLayoutEffect } from './use-isomorphic-effect.mjs';
1
+ import { useInsertionEffect } from 'react';
2
2
 
3
3
  function useMotionValueEvent(value, event, callback) {
4
- useIsomorphicLayoutEffect(() => value.on(event, callback), [value, event, callback]);
4
+ /**
5
+ * useInsertionEffect will create subscriptions before any other
6
+ * effects will run. Effects run upwards through the tree so it
7
+ * can be that binding a useLayoutEffect higher up the tree can
8
+ * miss changes from lower down the tree.
9
+ */
10
+ useInsertionEffect(() => value.on(event, callback), [value, event, callback]);
5
11
  }
6
12
 
7
13
  export { useMotionValueEvent };
@@ -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.0.3";
28
+ this.version = "8.0.4";
29
29
  /**
30
30
  * Duration, in milliseconds, since last updating frame.
31
31
  *
@@ -139,14 +139,6 @@ class MotionValue {
139
139
  * }
140
140
  * ```
141
141
  *
142
- * @privateRemarks
143
- *
144
- * We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
145
- *
146
- * ```jsx
147
- * useOnChange(x, () => {})
148
- * ```
149
- *
150
142
  * @param subscriber - A function that receives the latest value.
151
143
  * @returns A function that, when called, will cancel this subscription.
152
144
  *
@@ -159,7 +151,22 @@ class MotionValue {
159
151
  if (!this.events[eventName]) {
160
152
  this.events[eventName] = new SubscriptionManager();
161
153
  }
162
- return this.events[eventName].add(callback);
154
+ const unsubscribe = this.events[eventName].add(callback);
155
+ if (eventName === "change") {
156
+ return () => {
157
+ unsubscribe();
158
+ /**
159
+ * If we have no more change listeners by the start
160
+ * of the next frame, stop active animations.
161
+ */
162
+ sync.read(() => {
163
+ if (!this.events.change.getSize()) {
164
+ this.stop();
165
+ }
166
+ });
167
+ };
168
+ }
169
+ return unsubscribe;
163
170
  }
164
171
  clearListeners() {
165
172
  for (const eventManagers in this.events) {
@@ -1,6 +1,6 @@
1
1
  import { useMotionValue } from './use-motion-value.mjs';
2
- import { useMultiOnChange } from './use-on-change.mjs';
3
2
  import { cancelSync, sync } from '../frameloop/index.mjs';
3
+ import { useIsomorphicLayoutEffect } from '../utils/use-isomorphic-effect.mjs';
4
4
 
5
5
  function useCombineMotionValues(values, combineValues) {
6
6
  /**
@@ -23,7 +23,14 @@ function useCombineMotionValues(values, combineValues) {
23
23
  * Subscribe to all motion values found within the template. Whenever any of them change,
24
24
  * schedule an update.
25
25
  */
26
- useMultiOnChange(values, () => sync.update(updateValue, false, true), () => cancelSync.update(updateValue));
26
+ useIsomorphicLayoutEffect(() => {
27
+ const scheduleUpdate = () => sync.update(updateValue, false, true);
28
+ const subscriptions = values.map((v) => v.on("change", scheduleUpdate));
29
+ return () => {
30
+ subscriptions.forEach((unsubscribe) => unsubscribe());
31
+ cancelSync.update(updateValue);
32
+ };
33
+ });
27
34
  return value;
28
35
  }
29
36
 
@@ -1,9 +1,9 @@
1
1
  import { useContext, useRef, useMemo } from 'react';
2
2
  import { isMotionValue } from './utils/is-motion-value.mjs';
3
3
  import { useMotionValue } from './use-motion-value.mjs';
4
- import { useOnChange } from './use-on-change.mjs';
5
4
  import { MotionConfigContext } from '../context/MotionConfigContext.mjs';
6
5
  import { animate } from '../animation/legacy-popmotion/index.mjs';
6
+ import { useIsomorphicLayoutEffect } from '../utils/use-isomorphic-effect.mjs';
7
7
 
8
8
  /**
9
9
  * Creates a `MotionValue` that, when `set`, will use a spring animation to animate to its new state.
@@ -49,7 +49,11 @@ function useSpring(source, config = {}) {
49
49
  return value.get();
50
50
  });
51
51
  }, [JSON.stringify(config)]);
52
- useOnChange(source, (v) => value.set(parseFloat(v)));
52
+ useIsomorphicLayoutEffect(() => {
53
+ if (isMotionValue(source)) {
54
+ return source.on("change", (v) => value.set(parseFloat(v)));
55
+ }
56
+ }, [value]);
53
57
  return value;
54
58
  }
55
59
 
@@ -88,7 +88,6 @@
88
88
  visualElement.animationState.animateChanges();
89
89
  }
90
90
  });
91
- useIsomorphicLayoutEffect(() => () => visualElement && visualElement.notify("Unmount"), []);
92
91
  return visualElement;
93
92
  }
94
93
 
@@ -2060,7 +2059,7 @@
2060
2059
  * This will be replaced by the build step with the latest version number.
2061
2060
  * When MotionValues are provided to motion components, warn if versions are mixed.
2062
2061
  */
2063
- this.version = "8.0.3";
2062
+ this.version = "8.0.4";
2064
2063
  /**
2065
2064
  * Duration, in milliseconds, since last updating frame.
2066
2065
  *
@@ -2174,14 +2173,6 @@
2174
2173
  * }
2175
2174
  * ```
2176
2175
  *
2177
- * @privateRemarks
2178
- *
2179
- * We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
2180
- *
2181
- * ```jsx
2182
- * useOnChange(x, () => {})
2183
- * ```
2184
- *
2185
2176
  * @param subscriber - A function that receives the latest value.
2186
2177
  * @returns A function that, when called, will cancel this subscription.
2187
2178
  *
@@ -2194,7 +2185,22 @@
2194
2185
  if (!this.events[eventName]) {
2195
2186
  this.events[eventName] = new SubscriptionManager();
2196
2187
  }
2197
- return this.events[eventName].add(callback);
2188
+ const unsubscribe = this.events[eventName].add(callback);
2189
+ if (eventName === "change") {
2190
+ return () => {
2191
+ unsubscribe();
2192
+ /**
2193
+ * If we have no more change listeners by the start
2194
+ * of the next frame, stop active animations.
2195
+ */
2196
+ sync.read(() => {
2197
+ if (!this.events.change.getSize()) {
2198
+ this.stop();
2199
+ }
2200
+ });
2201
+ };
2202
+ }
2203
+ return unsubscribe;
2198
2204
  }
2199
2205
  clearListeners() {
2200
2206
  for (const eventManagers in this.events) {
@@ -5878,7 +5884,7 @@
5878
5884
  * and warn against mismatches.
5879
5885
  */
5880
5886
  {
5881
- warnOnce(nextValue.version === "8.0.3", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.0.3 may not work as expected.`);
5887
+ warnOnce(nextValue.version === "8.0.4", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.0.4 may not work as expected.`);
5882
5888
  }
5883
5889
  }
5884
5890
  else if (isMotionValue(prevValue)) {
@@ -5922,7 +5928,6 @@
5922
5928
  "AnimationStart",
5923
5929
  "AnimationComplete",
5924
5930
  "Update",
5925
- "Unmount",
5926
5931
  "BeforeLayoutMeasure",
5927
5932
  "LayoutMeasure",
5928
5933
  "LayoutAnimationStart",
@@ -6268,11 +6273,12 @@
6268
6273
  */
6269
6274
  addValue(key, value) {
6270
6275
  // Remove existing value if it exists
6271
- if (this.hasValue(key))
6276
+ if (value !== this.values.get(key)) {
6272
6277
  this.removeValue(key);
6278
+ this.bindToMotionValue(key, value);
6279
+ }
6273
6280
  this.values.set(key, value);
6274
6281
  this.latestValues[key] = value.get();
6275
- this.bindToMotionValue(key, value);
6276
6282
  }
6277
6283
  /**
6278
6284
  * Remove a motion value and unbind any active subscriptions.
@@ -6291,10 +6297,6 @@
6291
6297
  hasValue(key) {
6292
6298
  return this.values.has(key);
6293
6299
  }
6294
- /**
6295
- * Get a motion value for this key. If called with a default
6296
- * value, we'll create one if none exists.
6297
- */
6298
6300
  getValue(key, defaultValue) {
6299
6301
  if (this.props.values && this.props.values[key]) {
6300
6302
  return this.props.values[key];
@@ -8994,24 +8996,6 @@
8994
8996
  return useImmediate ? interpolator(inputValue) : interpolator;
8995
8997
  }
8996
8998
 
8997
- function useOnChange(value, callback) {
8998
- useIsomorphicLayoutEffect(() => {
8999
- if (isMotionValue(value)) {
9000
- callback(value.get());
9001
- return value.on("change", callback);
9002
- }
9003
- }, [value, callback]);
9004
- }
9005
- function useMultiOnChange(values, handler, cleanup) {
9006
- useIsomorphicLayoutEffect(() => {
9007
- const subscriptions = values.map((value) => value.on("change", handler));
9008
- return () => {
9009
- subscriptions.forEach((unsubscribe) => unsubscribe());
9010
- cleanup();
9011
- };
9012
- });
9013
- }
9014
-
9015
8999
  function useCombineMotionValues(values, combineValues) {
9016
9000
  /**
9017
9001
  * Initialise the returned motion value. This remains the same between renders.
@@ -9033,7 +9017,14 @@
9033
9017
  * Subscribe to all motion values found within the template. Whenever any of them change,
9034
9018
  * schedule an update.
9035
9019
  */
9036
- useMultiOnChange(values, () => sync.update(updateValue, false, true), () => cancelSync.update(updateValue));
9020
+ useIsomorphicLayoutEffect(() => {
9021
+ const scheduleUpdate = () => sync.update(updateValue, false, true);
9022
+ const subscriptions = values.map((v) => v.on("change", scheduleUpdate));
9023
+ return () => {
9024
+ subscriptions.forEach((unsubscribe) => unsubscribe());
9025
+ cancelSync.update(updateValue);
9026
+ };
9027
+ });
9037
9028
  return value;
9038
9029
  }
9039
9030
 
@@ -9193,7 +9184,11 @@
9193
9184
  return value.get();
9194
9185
  });
9195
9186
  }, [JSON.stringify(config)]);
9196
- useOnChange(source, (v) => value.set(parseFloat(v)));
9187
+ useIsomorphicLayoutEffect(() => {
9188
+ if (isMotionValue(source)) {
9189
+ return source.on("change", (v) => value.set(parseFloat(v)));
9190
+ }
9191
+ }, [value]);
9197
9192
  return value;
9198
9193
  }
9199
9194
 
@@ -9938,7 +9933,13 @@
9938
9933
  }
9939
9934
 
9940
9935
  function useMotionValueEvent(value, event, callback) {
9941
- useIsomorphicLayoutEffect(() => value.on(event, callback), [value, event, callback]);
9936
+ /**
9937
+ * useInsertionEffect will create subscriptions before any other
9938
+ * effects will run. Effects run upwards through the tree so it
9939
+ * can be that binding a useLayoutEffect higher up the tree can
9940
+ * miss changes from lower down the tree.
9941
+ */
9942
+ React.useInsertionEffect(() => value.on(event, callback), [value, event, callback]);
9942
9943
  }
9943
9944
 
9944
9945
  /**