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.
- package/dist/cjs/index.js +98 -45
- package/dist/es/animation/create-instant-animation.mjs +1 -2
- package/dist/es/animation/index.mjs +2 -4
- package/dist/es/animation/legacy-popmotion/index.mjs +19 -0
- package/dist/es/animation/optimized-appear/handoff.mjs +19 -2
- package/dist/es/animation/waapi/create-accelerated-animation.mjs +23 -15
- package/dist/es/gestures/use-hover-gesture.mjs +4 -4
- package/dist/es/gestures/use-tap-gesture.mjs +8 -4
- package/dist/es/motion/utils/use-visual-element.mjs +13 -5
- package/dist/es/render/utils/animation.mjs +1 -1
- package/dist/es/render/utils/motion-values.mjs +2 -2
- package/dist/es/value/index.mjs +7 -7
- package/dist/framer-motion.dev.js +98 -45
- package/dist/framer-motion.js +1 -1
- package/dist/index.d.ts +133 -133
- package/dist/projection.dev.js +54 -30
- package/dist/size-rollup-dom-animation-m.js +1 -1
- package/dist/size-rollup-dom-animation.js +1 -1
- package/dist/size-rollup-dom-max.js +1 -1
- package/dist/size-rollup-m.js +1 -1
- package/dist/size-rollup-motion.js +1 -1
- package/dist/size-webpack-dom-animation.js +1 -1
- package/dist/size-webpack-dom-max.js +1 -1
- package/dist/size-webpack-m.js +1 -1
- package/dist/three-entry.d.ts +62 -62
- package/package.json +9 -9
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
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
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
|
-
|
|
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
|
}
|
|
@@ -1465,14 +1473,14 @@ function isDragActive() {
|
|
|
1465
1473
|
return false;
|
|
1466
1474
|
}
|
|
1467
1475
|
|
|
1468
|
-
function createHoverEvent(visualElement, isActive, callback) {
|
|
1476
|
+
function createHoverEvent(visualElement, isActive, applyVariants, callback) {
|
|
1469
1477
|
return (event, info) => {
|
|
1470
1478
|
if (event.type === "touch" || isDragActive())
|
|
1471
1479
|
return;
|
|
1472
1480
|
/**
|
|
1473
1481
|
* Ensure we trigger animations before firing event callback
|
|
1474
1482
|
*/
|
|
1475
|
-
if (visualElement.animationState) {
|
|
1483
|
+
if (applyVariants && visualElement.animationState) {
|
|
1476
1484
|
visualElement.animationState.setActive(exports.AnimationType.Hover, isActive);
|
|
1477
1485
|
}
|
|
1478
1486
|
callback && callback(event, info);
|
|
@@ -1481,12 +1489,12 @@ function createHoverEvent(visualElement, isActive, callback) {
|
|
|
1481
1489
|
function useHoverGesture({ onHoverStart, onHoverEnd, whileHover, visualElement, }) {
|
|
1482
1490
|
usePointerEvent(visualElement, "pointerenter", React.useMemo(() => {
|
|
1483
1491
|
return onHoverStart || whileHover
|
|
1484
|
-
? createHoverEvent(visualElement, true, onHoverStart)
|
|
1492
|
+
? createHoverEvent(visualElement, true, Boolean(whileHover), onHoverStart)
|
|
1485
1493
|
: undefined;
|
|
1486
1494
|
}, [onHoverStart, Boolean(whileHover), visualElement]), { passive: !onHoverStart });
|
|
1487
1495
|
usePointerEvent(visualElement, "pointerleave", React.useMemo(() => {
|
|
1488
1496
|
return onHoverEnd || whileHover
|
|
1489
|
-
? createHoverEvent(visualElement, false, onHoverEnd)
|
|
1497
|
+
? createHoverEvent(visualElement, false, Boolean(whileHover), onHoverEnd)
|
|
1490
1498
|
: undefined;
|
|
1491
1499
|
}, [onHoverStart, Boolean(whileHover), visualElement]), { passive: !onHoverEnd });
|
|
1492
1500
|
}
|
|
@@ -1548,8 +1556,10 @@ function useTapGesture({ onTap, onTapStart, onTapCancel, whileTap, visualElement
|
|
|
1548
1556
|
function checkPointerEnd() {
|
|
1549
1557
|
removePointerEndListener();
|
|
1550
1558
|
isPressing.current = false;
|
|
1551
|
-
visualElement.
|
|
1559
|
+
const latestProps = visualElement.getProps();
|
|
1560
|
+
if (latestProps.whileTap && visualElement.animationState) {
|
|
1552
1561
|
visualElement.animationState.setActive(exports.AnimationType.Tap, false);
|
|
1562
|
+
}
|
|
1553
1563
|
return !isDragActive();
|
|
1554
1564
|
}
|
|
1555
1565
|
function onPointerUp(event, info) {
|
|
@@ -1571,18 +1581,20 @@ function useTapGesture({ onTap, onTapStart, onTapCancel, whileTap, visualElement
|
|
|
1571
1581
|
(_b = (_a = visualElement.getProps()).onTapCancel) === null || _b === void 0 ? void 0 : _b.call(_a, event, info);
|
|
1572
1582
|
}
|
|
1573
1583
|
const startPress = React.useCallback((event, info) => {
|
|
1574
|
-
var _a
|
|
1584
|
+
var _a;
|
|
1575
1585
|
removePointerEndListener();
|
|
1576
1586
|
if (isPressing.current)
|
|
1577
1587
|
return;
|
|
1578
1588
|
isPressing.current = true;
|
|
1579
1589
|
cancelPointerEndListeners.current = pipe(addPointerEvent(window, "pointerup", onPointerUp, eventOptions), addPointerEvent(window, "pointercancel", onPointerCancel, eventOptions));
|
|
1590
|
+
const latestProps = visualElement.getProps();
|
|
1580
1591
|
/**
|
|
1581
1592
|
* Ensure we trigger animations before firing event callback
|
|
1582
1593
|
*/
|
|
1583
|
-
visualElement.animationState
|
|
1594
|
+
if (latestProps.whileTap && visualElement.animationState) {
|
|
1584
1595
|
visualElement.animationState.setActive(exports.AnimationType.Tap, true);
|
|
1585
|
-
|
|
1596
|
+
}
|
|
1597
|
+
(_a = latestProps.onTapStart) === null || _a === void 0 ? void 0 : _a.call(latestProps, event, info);
|
|
1586
1598
|
}, [Boolean(onTapStart), visualElement]);
|
|
1587
1599
|
usePointerEvent(visualElement, "pointerdown", hasPressListeners ? startPress : undefined, eventOptions);
|
|
1588
1600
|
useUnmountEffect(removePointerEndListener);
|
|
@@ -2081,7 +2093,7 @@ class MotionValue {
|
|
|
2081
2093
|
* This will be replaced by the build step with the latest version number.
|
|
2082
2094
|
* When MotionValues are provided to motion components, warn if versions are mixed.
|
|
2083
2095
|
*/
|
|
2084
|
-
this.version = "8.4.
|
|
2096
|
+
this.version = "8.4.6";
|
|
2085
2097
|
/**
|
|
2086
2098
|
* Duration, in milliseconds, since last updating frame.
|
|
2087
2099
|
*
|
|
@@ -2320,11 +2332,11 @@ class MotionValue {
|
|
|
2320
2332
|
*
|
|
2321
2333
|
* @internal
|
|
2322
2334
|
*/
|
|
2323
|
-
start(
|
|
2335
|
+
start(startAnimation) {
|
|
2324
2336
|
this.stop();
|
|
2325
2337
|
return new Promise((resolve) => {
|
|
2326
2338
|
this.hasAnimated = true;
|
|
2327
|
-
this.
|
|
2339
|
+
this.animation = startAnimation(resolve) || null;
|
|
2328
2340
|
if (this.events.animationStart) {
|
|
2329
2341
|
this.events.animationStart.notify();
|
|
2330
2342
|
}
|
|
@@ -2341,8 +2353,8 @@ class MotionValue {
|
|
|
2341
2353
|
* @public
|
|
2342
2354
|
*/
|
|
2343
2355
|
stop() {
|
|
2344
|
-
if (this.
|
|
2345
|
-
this.
|
|
2356
|
+
if (this.animation) {
|
|
2357
|
+
this.animation.stop();
|
|
2346
2358
|
if (this.events.animationCancel) {
|
|
2347
2359
|
this.events.animationCancel.notify();
|
|
2348
2360
|
}
|
|
@@ -2355,10 +2367,10 @@ class MotionValue {
|
|
|
2355
2367
|
* @public
|
|
2356
2368
|
*/
|
|
2357
2369
|
isAnimating() {
|
|
2358
|
-
return !!this.
|
|
2370
|
+
return !!this.animation;
|
|
2359
2371
|
}
|
|
2360
2372
|
clearAnimation() {
|
|
2361
|
-
this.
|
|
2373
|
+
this.animation = null;
|
|
2362
2374
|
}
|
|
2363
2375
|
/**
|
|
2364
2376
|
* Destroy and clean up subscribers to this `MotionValue`.
|
|
@@ -2777,11 +2789,28 @@ function isWillChangeMotionValue(value) {
|
|
|
2777
2789
|
|
|
2778
2790
|
const appearStoreId = (id, value) => `${id}: ${value}`;
|
|
2779
2791
|
|
|
2780
|
-
function handoffOptimizedAppearAnimation(id, name) {
|
|
2792
|
+
function handoffOptimizedAppearAnimation(id, name, value) {
|
|
2781
2793
|
const { MotionAppearAnimations } = window;
|
|
2782
2794
|
const animationId = appearStoreId(id, transformProps.has(name) ? "transform" : name);
|
|
2783
2795
|
const animation = MotionAppearAnimations && MotionAppearAnimations.get(animationId);
|
|
2784
2796
|
if (animation) {
|
|
2797
|
+
const sampledTime = performance.now();
|
|
2798
|
+
/**
|
|
2799
|
+
* Resync handoff animation with optimised animation.
|
|
2800
|
+
*
|
|
2801
|
+
* This step would be unnecessary if we triggered animateChanges() in useEffect,
|
|
2802
|
+
* but due to potential hydration errors we currently fire them in useLayoutEffect.
|
|
2803
|
+
*
|
|
2804
|
+
* By the time we're safely ready to cancel the optimised WAAPI animation,
|
|
2805
|
+
* the main thread might have been blocked and desynced the two animations.
|
|
2806
|
+
*
|
|
2807
|
+
* Here, we resync the two animations before the optimised WAAPI animation is cancelled.
|
|
2808
|
+
*/
|
|
2809
|
+
sync.update(() => {
|
|
2810
|
+
if (value.animation) {
|
|
2811
|
+
value.animation.currentTime = performance.now() - sampledTime;
|
|
2812
|
+
}
|
|
2813
|
+
});
|
|
2785
2814
|
/**
|
|
2786
2815
|
* We allow the animation to persist until the next frame:
|
|
2787
2816
|
* 1. So it continues to play until Framer Motion is ready to render
|
|
@@ -2790,12 +2819,12 @@ function handoffOptimizedAppearAnimation(id, name) {
|
|
|
2790
2819
|
* it synchronously would prevent subsequent transforms from handing off.
|
|
2791
2820
|
*/
|
|
2792
2821
|
sync.render(() => {
|
|
2822
|
+
MotionAppearAnimations.delete(animationId);
|
|
2793
2823
|
/**
|
|
2794
2824
|
* Animation.cancel() throws so it needs to be wrapped in a try/catch
|
|
2795
2825
|
*/
|
|
2796
2826
|
try {
|
|
2797
2827
|
animation.cancel();
|
|
2798
|
-
MotionAppearAnimations.delete(animationId);
|
|
2799
2828
|
}
|
|
2800
2829
|
catch (e) { }
|
|
2801
2830
|
});
|
|
@@ -3581,6 +3610,25 @@ function animate$1({ duration, driver = framesync, elapsed = 0, repeat: repeatMa
|
|
|
3581
3610
|
onStop && onStop();
|
|
3582
3611
|
driverControls && driverControls.stop();
|
|
3583
3612
|
},
|
|
3613
|
+
/**
|
|
3614
|
+
* Set the current time of the animation. This is purposefully
|
|
3615
|
+
* mirroring the WAAPI animation API to make them interchanagable.
|
|
3616
|
+
* Going forward this file should be ported more towards
|
|
3617
|
+
* https://github.com/motiondivision/motionone/blob/main/packages/animation/src/Animation.ts
|
|
3618
|
+
* Which behaviourally adheres to WAAPI as far as possible.
|
|
3619
|
+
*
|
|
3620
|
+
* WARNING: This is not safe to use for most animations. We currently
|
|
3621
|
+
* only use it for handoff from WAAPI within Framer.
|
|
3622
|
+
*
|
|
3623
|
+
* This animation function consumes time every frame rather than being sampled for time.
|
|
3624
|
+
* So the sample() method performs some headless frames to ensure
|
|
3625
|
+
* repeats are handled correctly. Ideally in the future we will replace
|
|
3626
|
+
* that method with this, once repeat calculations are pure.
|
|
3627
|
+
*/
|
|
3628
|
+
set currentTime(t) {
|
|
3629
|
+
elapsed = initialElapsed;
|
|
3630
|
+
update(t);
|
|
3631
|
+
},
|
|
3584
3632
|
/**
|
|
3585
3633
|
* animate() can't yet be sampled for time, instead it
|
|
3586
3634
|
* consumes time. So to sample it we have to run a low
|
|
@@ -3737,21 +3785,29 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
|
|
|
3737
3785
|
/**
|
|
3738
3786
|
* Animation interrupt callback.
|
|
3739
3787
|
*/
|
|
3740
|
-
return
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3788
|
+
return {
|
|
3789
|
+
get currentTime() {
|
|
3790
|
+
return animation.currentTime || 0;
|
|
3791
|
+
},
|
|
3792
|
+
set currentTime(t) {
|
|
3793
|
+
animation.currentTime = t;
|
|
3794
|
+
},
|
|
3795
|
+
stop: () => {
|
|
3796
|
+
/**
|
|
3797
|
+
* WAAPI doesn't natively have any interruption capabilities.
|
|
3798
|
+
*
|
|
3799
|
+
* Rather than read commited styles back out of the DOM, we can
|
|
3800
|
+
* create a renderless JS animation and sample it twice to calculate
|
|
3801
|
+
* its current value, "previous" value, and therefore allow
|
|
3802
|
+
* Motion to calculate velocity for any subsequent animation.
|
|
3803
|
+
*/
|
|
3804
|
+
const { currentTime } = animation;
|
|
3805
|
+
if (currentTime) {
|
|
3806
|
+
const sampleAnimation = animate$1({ ...options, autoplay: false });
|
|
3807
|
+
value.setWithVelocity(sampleAnimation.sample(currentTime - sampleDelta).value, sampleAnimation.sample(currentTime).value, sampleDelta);
|
|
3808
|
+
}
|
|
3809
|
+
sync.update(() => animation.cancel());
|
|
3810
|
+
},
|
|
3755
3811
|
};
|
|
3756
3812
|
}
|
|
3757
3813
|
|
|
@@ -3775,9 +3831,8 @@ function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
|
|
|
3775
3831
|
const setValue = () => {
|
|
3776
3832
|
onUpdate && onUpdate(keyframes[keyframes.length - 1]);
|
|
3777
3833
|
onComplete && onComplete();
|
|
3778
|
-
return () => { };
|
|
3779
3834
|
};
|
|
3780
|
-
return elapsed ? delay(setValue, -elapsed) : setValue();
|
|
3835
|
+
return elapsed ? { stop: delay(setValue, -elapsed) } : setValue();
|
|
3781
3836
|
}
|
|
3782
3837
|
|
|
3783
3838
|
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 +4109,7 @@ const createMotionValueAnimation = (valueName, value, target, transition = {}) =
|
|
|
4054
4109
|
* If this is an inertia animation, we currently don't support pre-generating
|
|
4055
4110
|
* keyframes for this as such it must always run on the main thread.
|
|
4056
4111
|
*/
|
|
4057
|
-
|
|
4058
|
-
return () => animation.stop();
|
|
4112
|
+
return inertia(options);
|
|
4059
4113
|
}
|
|
4060
4114
|
/**
|
|
4061
4115
|
* If there's no transition defined for this value, we can generate
|
|
@@ -4093,8 +4147,7 @@ const createMotionValueAnimation = (valueName, value, target, transition = {}) =
|
|
|
4093
4147
|
/**
|
|
4094
4148
|
* If we didn't create an accelerated animation, create a JS animation
|
|
4095
4149
|
*/
|
|
4096
|
-
|
|
4097
|
-
return () => animation.stop();
|
|
4150
|
+
return animate$1(options);
|
|
4098
4151
|
};
|
|
4099
4152
|
};
|
|
4100
4153
|
|
|
@@ -4183,7 +4236,7 @@ function animateTarget(visualElement, definition, { delay = 0, transitionOverrid
|
|
|
4183
4236
|
if (!value.hasAnimated) {
|
|
4184
4237
|
const appearId = visualElement.getProps()[optimizedAppearDataAttribute];
|
|
4185
4238
|
if (appearId) {
|
|
4186
|
-
valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key);
|
|
4239
|
+
valueTransition.elapsed = handoffOptimizedAppearAnimation(appearId, key, value);
|
|
4187
4240
|
}
|
|
4188
4241
|
}
|
|
4189
4242
|
let animation = value.start(createMotionValueAnimation(key, value, valueTarget, visualElement.shouldReduceMotion && transformProps.has(key)
|
|
@@ -5933,7 +5986,7 @@ function updateMotionValuesFromProps(element, next, prev) {
|
|
|
5933
5986
|
* and warn against mismatches.
|
|
5934
5987
|
*/
|
|
5935
5988
|
if (process.env.NODE_ENV === "development") {
|
|
5936
|
-
warnOnce(nextValue.version === "8.4.
|
|
5989
|
+
warnOnce(nextValue.version === "8.4.6", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.6 may not work as expected.`);
|
|
5937
5990
|
}
|
|
5938
5991
|
}
|
|
5939
5992
|
else if (isMotionValue(prevValue)) {
|
|
@@ -5959,7 +6012,7 @@ function updateMotionValuesFromProps(element, next, prev) {
|
|
|
5959
6012
|
}
|
|
5960
6013
|
else {
|
|
5961
6014
|
const latestValue = element.getStaticValue(key);
|
|
5962
|
-
element.addValue(key, motionValue(latestValue !== undefined ? latestValue : nextValue));
|
|
6015
|
+
element.addValue(key, motionValue(latestValue !== undefined ? latestValue : nextValue, { owner: element }));
|
|
5963
6016
|
}
|
|
5964
6017
|
}
|
|
5965
6018
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
|
@@ -3,14 +3,14 @@ import { usePointerEvent } from '../events/use-pointer-event.mjs';
|
|
|
3
3
|
import { isDragActive } from './drag/utils/lock.mjs';
|
|
4
4
|
import { useMemo } from 'react';
|
|
5
5
|
|
|
6
|
-
function createHoverEvent(visualElement, isActive, callback) {
|
|
6
|
+
function createHoverEvent(visualElement, isActive, applyVariants, callback) {
|
|
7
7
|
return (event, info) => {
|
|
8
8
|
if (event.type === "touch" || isDragActive())
|
|
9
9
|
return;
|
|
10
10
|
/**
|
|
11
11
|
* Ensure we trigger animations before firing event callback
|
|
12
12
|
*/
|
|
13
|
-
if (visualElement.animationState) {
|
|
13
|
+
if (applyVariants && visualElement.animationState) {
|
|
14
14
|
visualElement.animationState.setActive(AnimationType.Hover, isActive);
|
|
15
15
|
}
|
|
16
16
|
callback && callback(event, info);
|
|
@@ -19,12 +19,12 @@ function createHoverEvent(visualElement, isActive, callback) {
|
|
|
19
19
|
function useHoverGesture({ onHoverStart, onHoverEnd, whileHover, visualElement, }) {
|
|
20
20
|
usePointerEvent(visualElement, "pointerenter", useMemo(() => {
|
|
21
21
|
return onHoverStart || whileHover
|
|
22
|
-
? createHoverEvent(visualElement, true, onHoverStart)
|
|
22
|
+
? createHoverEvent(visualElement, true, Boolean(whileHover), onHoverStart)
|
|
23
23
|
: undefined;
|
|
24
24
|
}, [onHoverStart, Boolean(whileHover), visualElement]), { passive: !onHoverStart });
|
|
25
25
|
usePointerEvent(visualElement, "pointerleave", useMemo(() => {
|
|
26
26
|
return onHoverEnd || whileHover
|
|
27
|
-
? createHoverEvent(visualElement, false, onHoverEnd)
|
|
27
|
+
? createHoverEvent(visualElement, false, Boolean(whileHover), onHoverEnd)
|
|
28
28
|
: undefined;
|
|
29
29
|
}, [onHoverStart, Boolean(whileHover), visualElement]), { passive: !onHoverEnd });
|
|
30
30
|
}
|
|
@@ -30,8 +30,10 @@ function useTapGesture({ onTap, onTapStart, onTapCancel, whileTap, visualElement
|
|
|
30
30
|
function checkPointerEnd() {
|
|
31
31
|
removePointerEndListener();
|
|
32
32
|
isPressing.current = false;
|
|
33
|
-
visualElement.
|
|
33
|
+
const latestProps = visualElement.getProps();
|
|
34
|
+
if (latestProps.whileTap && visualElement.animationState) {
|
|
34
35
|
visualElement.animationState.setActive(AnimationType.Tap, false);
|
|
36
|
+
}
|
|
35
37
|
return !isDragActive();
|
|
36
38
|
}
|
|
37
39
|
function onPointerUp(event, info) {
|
|
@@ -53,18 +55,20 @@ function useTapGesture({ onTap, onTapStart, onTapCancel, whileTap, visualElement
|
|
|
53
55
|
(_b = (_a = visualElement.getProps()).onTapCancel) === null || _b === void 0 ? void 0 : _b.call(_a, event, info);
|
|
54
56
|
}
|
|
55
57
|
const startPress = useCallback((event, info) => {
|
|
56
|
-
var _a
|
|
58
|
+
var _a;
|
|
57
59
|
removePointerEndListener();
|
|
58
60
|
if (isPressing.current)
|
|
59
61
|
return;
|
|
60
62
|
isPressing.current = true;
|
|
61
63
|
cancelPointerEndListeners.current = pipe(addPointerEvent(window, "pointerup", onPointerUp, eventOptions), addPointerEvent(window, "pointercancel", onPointerCancel, eventOptions));
|
|
64
|
+
const latestProps = visualElement.getProps();
|
|
62
65
|
/**
|
|
63
66
|
* Ensure we trigger animations before firing event callback
|
|
64
67
|
*/
|
|
65
|
-
visualElement.animationState
|
|
68
|
+
if (latestProps.whileTap && visualElement.animationState) {
|
|
66
69
|
visualElement.animationState.setActive(AnimationType.Tap, true);
|
|
67
|
-
|
|
70
|
+
}
|
|
71
|
+
(_a = latestProps.onTapStart) === null || _a === void 0 ? void 0 : _a.call(latestProps, event, info);
|
|
68
72
|
}, [Boolean(onTapStart), visualElement]);
|
|
69
73
|
usePointerEvent(visualElement, "pointerdown", hasPressListeners ? startPress : undefined, eventOptions);
|
|
70
74
|
useUnmountEffect(removePointerEndListener);
|
|
@@ -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
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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
|
-
|
|
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.
|
|
25
|
+
warnOnce(nextValue.version === "8.4.6", `Attempting to mix Framer Motion versions ${nextValue.version} with 8.4.6 may not work as expected.`);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
else if (isMotionValue(prevValue)) {
|
|
@@ -48,7 +48,7 @@ function updateMotionValuesFromProps(element, next, prev) {
|
|
|
48
48
|
}
|
|
49
49
|
else {
|
|
50
50
|
const latestValue = element.getStaticValue(key);
|
|
51
|
-
element.addValue(key, motionValue(latestValue !== undefined ? latestValue : nextValue));
|
|
51
|
+
element.addValue(key, motionValue(latestValue !== undefined ? latestValue : nextValue, { owner: element }));
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
}
|
package/dist/es/value/index.mjs
CHANGED
|
@@ -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.
|
|
28
|
+
this.version = "8.4.6";
|
|
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(
|
|
267
|
+
start(startAnimation) {
|
|
268
268
|
this.stop();
|
|
269
269
|
return new Promise((resolve) => {
|
|
270
270
|
this.hasAnimated = true;
|
|
271
|
-
this.
|
|
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.
|
|
289
|
-
this.
|
|
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.
|
|
302
|
+
return !!this.animation;
|
|
303
303
|
}
|
|
304
304
|
clearAnimation() {
|
|
305
|
-
this.
|
|
305
|
+
this.animation = null;
|
|
306
306
|
}
|
|
307
307
|
/**
|
|
308
308
|
* Destroy and clean up subscribers to this `MotionValue`.
|