framer-motion 12.7.3 → 12.7.5-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/README.md +1 -1
- package/dist/cjs/client.js +1 -1
- package/dist/cjs/{create-DwAwaNot.js → create-C7kXmWbI.js} +99 -2828
- package/dist/cjs/dom-mini.js +82 -66
- package/dist/cjs/dom.js +264 -3000
- package/dist/cjs/index.js +163 -218
- package/dist/cjs/m.js +13 -170
- package/dist/cjs/mini.js +77 -9
- package/dist/dom-mini.js +1 -1
- package/dist/dom.d.ts +5 -94
- package/dist/dom.js +1 -1
- package/dist/es/animation/animate/sequence.mjs +1 -1
- package/dist/es/animation/animators/waapi/animate-elements.mjs +78 -10
- package/dist/es/animation/interfaces/motion-value.mjs +11 -30
- package/dist/es/animation/interfaces/visual-element-target.mjs +1 -2
- package/dist/es/animation/optimized-appear/store-id.mjs +1 -1
- package/dist/es/animation/sequence/create.mjs +2 -5
- package/dist/es/animation/sequence/utils/edit.mjs +2 -3
- package/dist/es/animation/utils/default-transitions.mjs +1 -1
- package/dist/es/animation/utils/stagger.mjs +1 -1
- package/dist/es/components/AnimatePresence/PresenceChild.mjs +26 -23
- package/dist/es/components/Reorder/utils/check-reorder.mjs +1 -1
- package/dist/es/dom.mjs +2 -18
- package/dist/es/gestures/drag/VisualElementDragControls.mjs +1 -3
- package/dist/es/gestures/drag/utils/constraints.mjs +2 -3
- package/dist/es/gestures/focus.mjs +1 -1
- package/dist/es/gestures/pan/PanSession.mjs +1 -2
- package/dist/es/index.mjs +3 -24
- package/dist/es/motion/utils/is-forced-motion-value.mjs +1 -1
- package/dist/es/projection/animation/mix-values.mjs +2 -4
- package/dist/es/projection/geometry/delta-apply.mjs +1 -1
- package/dist/es/projection/geometry/delta-calc.mjs +1 -1
- package/dist/es/projection/geometry/delta-remove.mjs +1 -2
- package/dist/es/projection/node/create-projection-node.mjs +3 -7
- package/dist/es/projection/styles/scale-border-radius.mjs +1 -1
- package/dist/es/projection/styles/scale-box-shadow.mjs +1 -2
- package/dist/es/projection/styles/scale-correction.mjs +1 -1
- package/dist/es/projection.mjs +1 -3
- package/dist/es/render/VisualElement.mjs +2 -9
- package/dist/es/render/dom/DOMVisualElement.mjs +1 -1
- package/dist/es/render/dom/scroll/attach-animation.mjs +17 -0
- package/dist/es/render/dom/scroll/attach-function.mjs +23 -0
- package/dist/es/render/dom/scroll/index.mjs +6 -82
- package/dist/es/render/dom/scroll/offsets/index.mjs +2 -3
- package/dist/es/render/dom/scroll/utils/get-timeline.mjs +29 -0
- package/dist/es/render/html/HTMLVisualElement.mjs +1 -3
- package/dist/es/render/html/utils/build-styles.mjs +1 -4
- package/dist/es/render/html/utils/build-transform.mjs +1 -3
- package/dist/es/render/svg/SVGVisualElement.mjs +1 -3
- package/dist/es/render/svg/config-motion.mjs +1 -2
- package/dist/es/render/svg/utils/path.mjs +1 -1
- package/dist/es/render/svg/utils/scrape-motion-values.mjs +1 -1
- package/dist/es/render/svg/utils/transform-origin.mjs +1 -1
- package/dist/es/render/utils/motion-values.mjs +1 -1
- package/dist/es/utils/delay.mjs +1 -1
- package/dist/es/utils/transform.mjs +1 -1
- package/dist/es/utils/use-cycle.mjs +1 -1
- package/dist/es/utils/use-instant-transition.mjs +4 -4
- package/dist/es/value/use-spring.mjs +2 -3
- package/dist/es/value/use-will-change/get-will-change-name.mjs +1 -2
- package/dist/framer-motion.dev.js +3881 -3419
- package/dist/framer-motion.js +1 -1
- package/dist/m.d.ts +3 -50
- package/dist/mini.js +1 -1
- package/dist/size-rollup-animate.js +1 -1
- package/dist/size-rollup-dom-animation-assets.js +1 -1
- package/dist/size-rollup-dom-animation.js +1 -1
- package/dist/size-rollup-dom-max-assets.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-rollup-scroll.js +1 -1
- package/dist/size-rollup-waapi-animate.js +1 -1
- package/dist/types/client.d.ts +4 -3
- package/dist/types/index.d.ts +56 -351
- package/dist/{types.d-B50aGbjN.d.ts → types.d-B1Voffvi.d.ts} +3 -138
- package/package.json +9 -9
- package/dist/es/animation/animators/AcceleratedAnimation.mjs +0 -319
- package/dist/es/animation/animators/BaseAnimation.mjs +0 -120
- package/dist/es/animation/animators/MainThreadAnimation.mjs +0 -394
- package/dist/es/animation/animators/drivers/driver-frameloop.mjs +0 -16
- package/dist/es/animation/animators/utils/accelerated-values.mjs +0 -14
- package/dist/es/animation/animators/utils/can-animate.mjs +0 -42
- package/dist/es/animation/animators/waapi/utils/supports-waapi.mjs +0 -5
- package/dist/es/animation/generators/inertia.mjs +0 -87
- package/dist/es/animation/generators/keyframes.mjs +0 -51
- package/dist/es/animation/generators/spring/defaults.mjs +0 -27
- package/dist/es/animation/generators/spring/find.mjs +0 -85
- package/dist/es/animation/generators/spring/index.mjs +0 -174
- package/dist/es/animation/generators/utils/velocity.mjs +0 -9
- package/dist/es/animation/utils/is-animatable.mjs +0 -30
- package/dist/es/animation/utils/is-none.mjs +0 -15
- package/dist/es/easing/anticipate.mjs +0 -5
- package/dist/es/easing/back.mjs +0 -9
- package/dist/es/easing/circ.mjs +0 -8
- package/dist/es/easing/cubic-bezier.mjs +0 -51
- package/dist/es/easing/ease.mjs +0 -7
- package/dist/es/easing/modifiers/mirror.mjs +0 -5
- package/dist/es/easing/modifiers/reverse.mjs +0 -5
- package/dist/es/easing/steps.mjs +0 -15
- package/dist/es/easing/utils/get-easing-for-segment.mjs +0 -8
- package/dist/es/easing/utils/is-easing-array.mjs +0 -5
- package/dist/es/easing/utils/map.mjs +0 -37
- package/dist/es/render/dom/DOMKeyframesResolver.mjs +0 -130
- package/dist/es/render/dom/scroll/observe.mjs +0 -18
- package/dist/es/render/dom/utils/css-variables-conversion.mjs +0 -42
- package/dist/es/render/dom/utils/is-css-variable.mjs +0 -15
- package/dist/es/render/dom/utils/unit-conversion.mjs +0 -36
- package/dist/es/render/dom/value-types/animatable-none.mjs +0 -15
- package/dist/es/render/dom/value-types/defaults.mjs +0 -30
- package/dist/es/render/dom/value-types/dimensions.mjs +0 -15
- package/dist/es/render/dom/value-types/find.mjs +0 -15
- package/dist/es/render/dom/value-types/get-as-type.mjs +0 -10
- package/dist/es/render/dom/value-types/number-browser.mjs +0 -41
- package/dist/es/render/dom/value-types/number.mjs +0 -18
- package/dist/es/render/dom/value-types/test.mjs +0 -6
- package/dist/es/render/dom/value-types/transform.mjs +0 -31
- package/dist/es/render/dom/value-types/type-auto.mjs +0 -9
- package/dist/es/render/dom/value-types/type-int.mjs +0 -8
- package/dist/es/render/html/utils/keys-position.mjs +0 -13
- package/dist/es/render/html/utils/keys-transform.mjs +0 -28
- package/dist/es/render/html/utils/make-none-animatable.mjs +0 -30
- package/dist/es/render/html/utils/parse-transform.mjs +0 -83
- package/dist/es/render/utils/KeyframesResolver.mjs +0 -163
- package/dist/es/utils/clamp.mjs +0 -9
- package/dist/es/utils/hsla-to-rgba.mjs +0 -42
- package/dist/es/utils/interpolate.mjs +0 -76
- package/dist/es/utils/is-numerical-string.mjs +0 -6
- package/dist/es/utils/is-zero-value-string.mjs +0 -6
- package/dist/es/utils/mix/color.mjs +0 -47
- package/dist/es/utils/mix/complex.mjs +0 -93
- package/dist/es/utils/mix/immediate.mjs +0 -5
- package/dist/es/utils/mix/index.mjs +0 -14
- package/dist/es/utils/mix/number.mjs +0 -26
- package/dist/es/utils/mix/visibility.mjs +0 -16
- package/dist/es/utils/offsets/default.mjs +0 -9
- package/dist/es/utils/offsets/fill.mjs +0 -12
- package/dist/es/utils/offsets/time.mjs +0 -5
- package/dist/es/utils/pipe.mjs +0 -11
- package/dist/es/utils/use-instant-transition-state.mjs +0 -5
- package/dist/es/utils/wrap.mjs +0 -6
- package/dist/es/value/types/color/hex.mjs +0 -40
- package/dist/es/value/types/color/hsla.mjs +0 -22
- package/dist/es/value/types/color/index.mjs +0 -27
- package/dist/es/value/types/color/rgba.mjs +0 -25
- package/dist/es/value/types/color/utils.mjs +0 -29
- package/dist/es/value/types/complex/filter.mjs +0 -30
- package/dist/es/value/types/complex/index.mjs +0 -91
- package/dist/es/value/types/numbers/index.mjs +0 -17
- package/dist/es/value/types/numbers/units.mjs +0 -17
- package/dist/es/value/types/utils/color-regex.mjs +0 -3
- package/dist/es/value/types/utils/float-regex.mjs +0 -3
- package/dist/es/value/types/utils/is-nullish.mjs +0 -5
- package/dist/es/value/types/utils/sanitize.mjs +0 -5
- package/dist/es/value/types/utils/single-color-regex.mjs +0 -3
package/dist/cjs/dom.js
CHANGED
|
@@ -5,344 +5,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var motionDom = require('motion-dom');
|
|
6
6
|
var motionUtils = require('motion-utils');
|
|
7
7
|
|
|
8
|
-
const clamp = (min, max, v) => {
|
|
9
|
-
if (v > max)
|
|
10
|
-
return max;
|
|
11
|
-
if (v < min)
|
|
12
|
-
return min;
|
|
13
|
-
return v;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const velocitySampleDuration = 5; // ms
|
|
17
|
-
function calcGeneratorVelocity(resolveValue, t, current) {
|
|
18
|
-
const prevT = Math.max(t - velocitySampleDuration, 0);
|
|
19
|
-
return motionUtils.velocityPerSecond(current - resolveValue(prevT), t - prevT);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const springDefaults = {
|
|
23
|
-
// Default spring physics
|
|
24
|
-
stiffness: 100,
|
|
25
|
-
damping: 10,
|
|
26
|
-
mass: 1.0,
|
|
27
|
-
velocity: 0.0,
|
|
28
|
-
// Default duration/bounce-based options
|
|
29
|
-
duration: 800, // in ms
|
|
30
|
-
bounce: 0.3,
|
|
31
|
-
visualDuration: 0.3, // in seconds
|
|
32
|
-
// Rest thresholds
|
|
33
|
-
restSpeed: {
|
|
34
|
-
granular: 0.01,
|
|
35
|
-
default: 2,
|
|
36
|
-
},
|
|
37
|
-
restDelta: {
|
|
38
|
-
granular: 0.005,
|
|
39
|
-
default: 0.5,
|
|
40
|
-
},
|
|
41
|
-
// Limits
|
|
42
|
-
minDuration: 0.01, // in seconds
|
|
43
|
-
maxDuration: 10.0, // in seconds
|
|
44
|
-
minDamping: 0.05,
|
|
45
|
-
maxDamping: 1,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const safeMin = 0.001;
|
|
49
|
-
function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass, }) {
|
|
50
|
-
let envelope;
|
|
51
|
-
let derivative;
|
|
52
|
-
motionUtils.warning(duration <= motionUtils.secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less");
|
|
53
|
-
let dampingRatio = 1 - bounce;
|
|
54
|
-
/**
|
|
55
|
-
* Restrict dampingRatio and duration to within acceptable ranges.
|
|
56
|
-
*/
|
|
57
|
-
dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio);
|
|
58
|
-
duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, motionUtils.millisecondsToSeconds(duration));
|
|
59
|
-
if (dampingRatio < 1) {
|
|
60
|
-
/**
|
|
61
|
-
* Underdamped spring
|
|
62
|
-
*/
|
|
63
|
-
envelope = (undampedFreq) => {
|
|
64
|
-
const exponentialDecay = undampedFreq * dampingRatio;
|
|
65
|
-
const delta = exponentialDecay * duration;
|
|
66
|
-
const a = exponentialDecay - velocity;
|
|
67
|
-
const b = calcAngularFreq(undampedFreq, dampingRatio);
|
|
68
|
-
const c = Math.exp(-delta);
|
|
69
|
-
return safeMin - (a / b) * c;
|
|
70
|
-
};
|
|
71
|
-
derivative = (undampedFreq) => {
|
|
72
|
-
const exponentialDecay = undampedFreq * dampingRatio;
|
|
73
|
-
const delta = exponentialDecay * duration;
|
|
74
|
-
const d = delta * velocity + velocity;
|
|
75
|
-
const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
|
|
76
|
-
const f = Math.exp(-delta);
|
|
77
|
-
const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
|
|
78
|
-
const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
|
|
79
|
-
return (factor * ((d - e) * f)) / g;
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
/**
|
|
84
|
-
* Critically-damped spring
|
|
85
|
-
*/
|
|
86
|
-
envelope = (undampedFreq) => {
|
|
87
|
-
const a = Math.exp(-undampedFreq * duration);
|
|
88
|
-
const b = (undampedFreq - velocity) * duration + 1;
|
|
89
|
-
return -safeMin + a * b;
|
|
90
|
-
};
|
|
91
|
-
derivative = (undampedFreq) => {
|
|
92
|
-
const a = Math.exp(-undampedFreq * duration);
|
|
93
|
-
const b = (velocity - undampedFreq) * (duration * duration);
|
|
94
|
-
return a * b;
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
const initialGuess = 5 / duration;
|
|
98
|
-
const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
|
|
99
|
-
duration = motionUtils.secondsToMilliseconds(duration);
|
|
100
|
-
if (isNaN(undampedFreq)) {
|
|
101
|
-
return {
|
|
102
|
-
stiffness: springDefaults.stiffness,
|
|
103
|
-
damping: springDefaults.damping,
|
|
104
|
-
duration,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
const stiffness = Math.pow(undampedFreq, 2) * mass;
|
|
109
|
-
return {
|
|
110
|
-
stiffness,
|
|
111
|
-
damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
|
|
112
|
-
duration,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const rootIterations = 12;
|
|
117
|
-
function approximateRoot(envelope, derivative, initialGuess) {
|
|
118
|
-
let result = initialGuess;
|
|
119
|
-
for (let i = 1; i < rootIterations; i++) {
|
|
120
|
-
result = result - envelope(result) / derivative(result);
|
|
121
|
-
}
|
|
122
|
-
return result;
|
|
123
|
-
}
|
|
124
|
-
function calcAngularFreq(undampedFreq, dampingRatio) {
|
|
125
|
-
return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const durationKeys = ["duration", "bounce"];
|
|
129
|
-
const physicsKeys = ["stiffness", "damping", "mass"];
|
|
130
|
-
function isSpringType(options, keys) {
|
|
131
|
-
return keys.some((key) => options[key] !== undefined);
|
|
132
|
-
}
|
|
133
|
-
function getSpringOptions(options) {
|
|
134
|
-
let springOptions = {
|
|
135
|
-
velocity: springDefaults.velocity,
|
|
136
|
-
stiffness: springDefaults.stiffness,
|
|
137
|
-
damping: springDefaults.damping,
|
|
138
|
-
mass: springDefaults.mass,
|
|
139
|
-
isResolvedFromDuration: false,
|
|
140
|
-
...options,
|
|
141
|
-
};
|
|
142
|
-
// stiffness/damping/mass overrides duration/bounce
|
|
143
|
-
if (!isSpringType(options, physicsKeys) &&
|
|
144
|
-
isSpringType(options, durationKeys)) {
|
|
145
|
-
if (options.visualDuration) {
|
|
146
|
-
const visualDuration = options.visualDuration;
|
|
147
|
-
const root = (2 * Math.PI) / (visualDuration * 1.2);
|
|
148
|
-
const stiffness = root * root;
|
|
149
|
-
const damping = 2 *
|
|
150
|
-
clamp(0.05, 1, 1 - (options.bounce || 0)) *
|
|
151
|
-
Math.sqrt(stiffness);
|
|
152
|
-
springOptions = {
|
|
153
|
-
...springOptions,
|
|
154
|
-
mass: springDefaults.mass,
|
|
155
|
-
stiffness,
|
|
156
|
-
damping,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
const derived = findSpring(options);
|
|
161
|
-
springOptions = {
|
|
162
|
-
...springOptions,
|
|
163
|
-
...derived,
|
|
164
|
-
mass: springDefaults.mass,
|
|
165
|
-
};
|
|
166
|
-
springOptions.isResolvedFromDuration = true;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return springOptions;
|
|
170
|
-
}
|
|
171
|
-
function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) {
|
|
172
|
-
const options = typeof optionsOrVisualDuration !== "object"
|
|
173
|
-
? {
|
|
174
|
-
visualDuration: optionsOrVisualDuration,
|
|
175
|
-
keyframes: [0, 1],
|
|
176
|
-
bounce,
|
|
177
|
-
}
|
|
178
|
-
: optionsOrVisualDuration;
|
|
179
|
-
let { restSpeed, restDelta } = options;
|
|
180
|
-
const origin = options.keyframes[0];
|
|
181
|
-
const target = options.keyframes[options.keyframes.length - 1];
|
|
182
|
-
/**
|
|
183
|
-
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
184
|
-
* to reduce GC during animation.
|
|
185
|
-
*/
|
|
186
|
-
const state = { done: false, value: origin };
|
|
187
|
-
const { stiffness, damping, mass, duration, velocity, isResolvedFromDuration, } = getSpringOptions({
|
|
188
|
-
...options,
|
|
189
|
-
velocity: -motionUtils.millisecondsToSeconds(options.velocity || 0),
|
|
190
|
-
});
|
|
191
|
-
const initialVelocity = velocity || 0.0;
|
|
192
|
-
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
193
|
-
const initialDelta = target - origin;
|
|
194
|
-
const undampedAngularFreq = motionUtils.millisecondsToSeconds(Math.sqrt(stiffness / mass));
|
|
195
|
-
/**
|
|
196
|
-
* If we're working on a granular scale, use smaller defaults for determining
|
|
197
|
-
* when the spring is finished.
|
|
198
|
-
*
|
|
199
|
-
* These defaults have been selected emprically based on what strikes a good
|
|
200
|
-
* ratio between feeling good and finishing as soon as changes are imperceptible.
|
|
201
|
-
*/
|
|
202
|
-
const isGranularScale = Math.abs(initialDelta) < 5;
|
|
203
|
-
restSpeed || (restSpeed = isGranularScale
|
|
204
|
-
? springDefaults.restSpeed.granular
|
|
205
|
-
: springDefaults.restSpeed.default);
|
|
206
|
-
restDelta || (restDelta = isGranularScale
|
|
207
|
-
? springDefaults.restDelta.granular
|
|
208
|
-
: springDefaults.restDelta.default);
|
|
209
|
-
let resolveSpring;
|
|
210
|
-
if (dampingRatio < 1) {
|
|
211
|
-
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
212
|
-
// Underdamped spring
|
|
213
|
-
resolveSpring = (t) => {
|
|
214
|
-
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
215
|
-
return (target -
|
|
216
|
-
envelope *
|
|
217
|
-
(((initialVelocity +
|
|
218
|
-
dampingRatio * undampedAngularFreq * initialDelta) /
|
|
219
|
-
angularFreq) *
|
|
220
|
-
Math.sin(angularFreq * t) +
|
|
221
|
-
initialDelta * Math.cos(angularFreq * t)));
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
else if (dampingRatio === 1) {
|
|
225
|
-
// Critically damped spring
|
|
226
|
-
resolveSpring = (t) => target -
|
|
227
|
-
Math.exp(-undampedAngularFreq * t) *
|
|
228
|
-
(initialDelta +
|
|
229
|
-
(initialVelocity + undampedAngularFreq * initialDelta) * t);
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
// Overdamped spring
|
|
233
|
-
const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
|
|
234
|
-
resolveSpring = (t) => {
|
|
235
|
-
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
236
|
-
// When performing sinh or cosh values can hit Infinity so we cap them here
|
|
237
|
-
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
238
|
-
return (target -
|
|
239
|
-
(envelope *
|
|
240
|
-
((initialVelocity +
|
|
241
|
-
dampingRatio * undampedAngularFreq * initialDelta) *
|
|
242
|
-
Math.sinh(freqForT) +
|
|
243
|
-
dampedAngularFreq *
|
|
244
|
-
initialDelta *
|
|
245
|
-
Math.cosh(freqForT))) /
|
|
246
|
-
dampedAngularFreq);
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
const generator = {
|
|
250
|
-
calculatedDuration: isResolvedFromDuration ? duration || null : null,
|
|
251
|
-
next: (t) => {
|
|
252
|
-
const current = resolveSpring(t);
|
|
253
|
-
if (!isResolvedFromDuration) {
|
|
254
|
-
let currentVelocity = 0.0;
|
|
255
|
-
/**
|
|
256
|
-
* We only need to calculate velocity for under-damped springs
|
|
257
|
-
* as over- and critically-damped springs can't overshoot, so
|
|
258
|
-
* checking only for displacement is enough.
|
|
259
|
-
*/
|
|
260
|
-
if (dampingRatio < 1) {
|
|
261
|
-
currentVelocity =
|
|
262
|
-
t === 0
|
|
263
|
-
? motionUtils.secondsToMilliseconds(initialVelocity)
|
|
264
|
-
: calcGeneratorVelocity(resolveSpring, t, current);
|
|
265
|
-
}
|
|
266
|
-
const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
|
|
267
|
-
const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
|
|
268
|
-
state.done =
|
|
269
|
-
isBelowVelocityThreshold && isBelowDisplacementThreshold;
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
state.done = t >= duration;
|
|
273
|
-
}
|
|
274
|
-
state.value = state.done ? target : current;
|
|
275
|
-
return state;
|
|
276
|
-
},
|
|
277
|
-
toString: () => {
|
|
278
|
-
const calculatedDuration = Math.min(motionDom.calcGeneratorDuration(generator), motionDom.maxGeneratorDuration);
|
|
279
|
-
const easing = motionDom.generateLinearEasing((progress) => generator.next(calculatedDuration * progress).value, calculatedDuration, 30);
|
|
280
|
-
return calculatedDuration + "ms " + easing;
|
|
281
|
-
},
|
|
282
|
-
toTransition: () => { },
|
|
283
|
-
};
|
|
284
|
-
return generator;
|
|
285
|
-
}
|
|
286
|
-
spring.applyToOptions = (options) => {
|
|
287
|
-
const generatorOptions = motionDom.createGeneratorEasing(options, 100, spring);
|
|
288
|
-
options.ease = motionDom.supportsLinearEasing() ? generatorOptions.ease : "easeOut";
|
|
289
|
-
options.duration = motionUtils.secondsToMilliseconds(generatorOptions.duration);
|
|
290
|
-
options.type = "keyframes";
|
|
291
|
-
return options;
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
const wrap = (min, max, v) => {
|
|
295
|
-
const rangeSize = max - min;
|
|
296
|
-
return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
const isEasingArray = (ease) => {
|
|
300
|
-
return Array.isArray(ease) && typeof ease[0] !== "number";
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
function getEasingForSegment(easing, i) {
|
|
304
|
-
return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/*
|
|
308
|
-
Value in range from progress
|
|
309
|
-
|
|
310
|
-
Given a lower limit and an upper limit, we return the value within
|
|
311
|
-
that range as expressed by progress (usually a number from 0 to 1)
|
|
312
|
-
|
|
313
|
-
So progress = 0.5 would change
|
|
314
|
-
|
|
315
|
-
from -------- to
|
|
316
|
-
|
|
317
|
-
to
|
|
318
|
-
|
|
319
|
-
from ---- to
|
|
320
|
-
|
|
321
|
-
E.g. from = 10, to = 20, progress = 0.5 => 15
|
|
322
|
-
|
|
323
|
-
@param [number]: Lower limit of range
|
|
324
|
-
@param [number]: Upper limit of range
|
|
325
|
-
@param [number]: The progress between lower and upper limits expressed 0-1
|
|
326
|
-
@return [number]: Value as calculated from progress within range (not limited within range)
|
|
327
|
-
*/
|
|
328
|
-
const mixNumber$1 = (from, to, progress) => {
|
|
329
|
-
return from + (to - from) * progress;
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
function fillOffset(offset, remaining) {
|
|
333
|
-
const min = offset[offset.length - 1];
|
|
334
|
-
for (let i = 1; i <= remaining; i++) {
|
|
335
|
-
const offsetProgress = motionUtils.progress(0, remaining, i);
|
|
336
|
-
offset.push(mixNumber$1(min, 1, offsetProgress));
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function defaultOffset$1(arr) {
|
|
341
|
-
const offset = [0];
|
|
342
|
-
fillOffset(offset, arr.length - 1);
|
|
343
|
-
return offset;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
8
|
const isMotionValue = (value) => Boolean(value && value.getVelocity);
|
|
347
9
|
|
|
348
10
|
function isDOMKeyframes(keyframes) {
|
|
@@ -407,8 +69,8 @@ function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
|
|
|
407
69
|
for (let i = 0; i < keyframes.length; i++) {
|
|
408
70
|
sequence.push({
|
|
409
71
|
value: keyframes[i],
|
|
410
|
-
at: mixNumber
|
|
411
|
-
easing: getEasingForSegment(easing, i),
|
|
72
|
+
at: motionDom.mixNumber(startTime, endTime, offset[i]),
|
|
73
|
+
easing: motionUtils.getEasingForSegment(easing, i),
|
|
412
74
|
});
|
|
413
75
|
}
|
|
414
76
|
}
|
|
@@ -482,7 +144,7 @@ function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...seq
|
|
|
482
144
|
let maxDuration = 0;
|
|
483
145
|
const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
|
|
484
146
|
const valueKeyframesAsList = keyframesAsList(valueKeyframes);
|
|
485
|
-
const { delay = 0, times = defaultOffset
|
|
147
|
+
const { delay = 0, times = motionDom.defaultOffset(valueKeyframesAsList), type = "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
|
|
486
148
|
let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
|
|
487
149
|
/**
|
|
488
150
|
* Resolve stagger() if defined.
|
|
@@ -530,7 +192,7 @@ function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...seq
|
|
|
530
192
|
* Fill out if offset if fewer offsets than keyframes
|
|
531
193
|
*/
|
|
532
194
|
const remainder = times.length - valueKeyframesAsList.length;
|
|
533
|
-
remainder > 0 && fillOffset(times, remainder);
|
|
195
|
+
remainder > 0 && motionDom.fillOffset(times, remainder);
|
|
534
196
|
/**
|
|
535
197
|
* If only one value has been set, ie [1], push a null to the start of
|
|
536
198
|
* the keyframe array. This will let us mark a keyframe at this point
|
|
@@ -554,7 +216,7 @@ function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...seq
|
|
|
554
216
|
times.push(originalTimes[keyframeIndex] + (repeatIndex + 1));
|
|
555
217
|
ease.push(keyframeIndex === 0
|
|
556
218
|
? "linear"
|
|
557
|
-
: getEasingForSegment(originalEase, keyframeIndex - 1));
|
|
219
|
+
: motionUtils.getEasingForSegment(originalEase, keyframeIndex - 1));
|
|
558
220
|
}
|
|
559
221
|
}
|
|
560
222
|
normalizeTimes(times, repeat);
|
|
@@ -675,2446 +337,124 @@ function getValueTransition(transition, key) {
|
|
|
675
337
|
: { ...transition };
|
|
676
338
|
}
|
|
677
339
|
const isNumber = (keyframe) => typeof keyframe === "number";
|
|
678
|
-
const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
|
|
679
|
-
|
|
680
|
-
const visualElementStore = new WeakMap();
|
|
681
|
-
|
|
682
|
-
/**
|
|
683
|
-
* Generate a list of every possible transform key.
|
|
684
|
-
*/
|
|
685
|
-
const transformPropOrder = [
|
|
686
|
-
"transformPerspective",
|
|
687
|
-
"x",
|
|
688
|
-
"y",
|
|
689
|
-
"z",
|
|
690
|
-
"translateX",
|
|
691
|
-
"translateY",
|
|
692
|
-
"translateZ",
|
|
693
|
-
"scale",
|
|
694
|
-
"scaleX",
|
|
695
|
-
"scaleY",
|
|
696
|
-
"rotate",
|
|
697
|
-
"rotateX",
|
|
698
|
-
"rotateY",
|
|
699
|
-
"rotateZ",
|
|
700
|
-
"skew",
|
|
701
|
-
"skewX",
|
|
702
|
-
"skewY",
|
|
703
|
-
];
|
|
704
|
-
/**
|
|
705
|
-
* A quick lookup for transform props.
|
|
706
|
-
*/
|
|
707
|
-
const transformProps = new Set(transformPropOrder);
|
|
708
|
-
|
|
709
|
-
const positionalKeys = new Set([
|
|
710
|
-
"width",
|
|
711
|
-
"height",
|
|
712
|
-
"top",
|
|
713
|
-
"left",
|
|
714
|
-
"right",
|
|
715
|
-
"bottom",
|
|
716
|
-
...transformPropOrder,
|
|
717
|
-
]);
|
|
718
|
-
|
|
719
|
-
const isKeyframesTarget = (v) => {
|
|
720
|
-
return Array.isArray(v);
|
|
721
|
-
};
|
|
722
|
-
|
|
723
|
-
const resolveFinalValueInKeyframes = (v) => {
|
|
724
|
-
// TODO maybe throw if v.length - 1 is placeholder token?
|
|
725
|
-
return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
|
|
726
|
-
};
|
|
727
|
-
|
|
728
|
-
function getValueState(visualElement) {
|
|
729
|
-
const state = [{}, {}];
|
|
730
|
-
visualElement?.values.forEach((value, key) => {
|
|
731
|
-
state[0][key] = value.get();
|
|
732
|
-
state[1][key] = value.getVelocity();
|
|
733
|
-
});
|
|
734
|
-
return state;
|
|
735
|
-
}
|
|
736
|
-
function resolveVariantFromProps(props, definition, custom, visualElement) {
|
|
737
|
-
/**
|
|
738
|
-
* If the variant definition is a function, resolve.
|
|
739
|
-
*/
|
|
740
|
-
if (typeof definition === "function") {
|
|
741
|
-
const [current, velocity] = getValueState(visualElement);
|
|
742
|
-
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
743
|
-
}
|
|
744
|
-
/**
|
|
745
|
-
* If the variant definition is a variant label, or
|
|
746
|
-
* the function returned a variant label, resolve.
|
|
747
|
-
*/
|
|
748
|
-
if (typeof definition === "string") {
|
|
749
|
-
definition = props.variants && props.variants[definition];
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* At this point we've resolved both functions and variant labels,
|
|
753
|
-
* but the resolved variant label might itself have been a function.
|
|
754
|
-
* If so, resolve. This can only have returned a valid target object.
|
|
755
|
-
*/
|
|
756
|
-
if (typeof definition === "function") {
|
|
757
|
-
const [current, velocity] = getValueState(visualElement);
|
|
758
|
-
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
759
|
-
}
|
|
760
|
-
return definition;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
function resolveVariant(visualElement, definition, custom) {
|
|
764
|
-
const props = visualElement.getProps();
|
|
765
|
-
return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
/**
|
|
769
|
-
* Set VisualElement's MotionValue, creating a new MotionValue for it if
|
|
770
|
-
* it doesn't exist.
|
|
771
|
-
*/
|
|
772
|
-
function setMotionValue(visualElement, key, value) {
|
|
773
|
-
if (visualElement.hasValue(key)) {
|
|
774
|
-
visualElement.getValue(key).set(value);
|
|
775
|
-
}
|
|
776
|
-
else {
|
|
777
|
-
visualElement.addValue(key, motionDom.motionValue(value));
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
function setTarget(visualElement, definition) {
|
|
781
|
-
const resolved = resolveVariant(visualElement, definition);
|
|
782
|
-
let { transitionEnd = {}, transition = {}, ...target } = resolved || {};
|
|
783
|
-
target = { ...target, ...transitionEnd };
|
|
784
|
-
for (const key in target) {
|
|
785
|
-
const value = resolveFinalValueInKeyframes(target[key]);
|
|
786
|
-
setMotionValue(visualElement, key, value);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
function isWillChangeMotionValue(value) {
|
|
791
|
-
return Boolean(isMotionValue(value) && value.add);
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
function addValueToWillChange(visualElement, key) {
|
|
795
|
-
const willChange = visualElement.getValue("willChange");
|
|
796
|
-
/**
|
|
797
|
-
* It could be that a user has set willChange to a regular MotionValue,
|
|
798
|
-
* in which case we can't add the value to it.
|
|
799
|
-
*/
|
|
800
|
-
if (isWillChangeMotionValue(willChange)) {
|
|
801
|
-
return willChange.add(key);
|
|
802
|
-
}
|
|
803
|
-
else if (!willChange && motionUtils.MotionGlobalConfig.WillChange) {
|
|
804
|
-
const newWillChange = new motionUtils.MotionGlobalConfig.WillChange("auto");
|
|
805
|
-
visualElement.addValue("willChange", newWillChange);
|
|
806
|
-
newWillChange.add(key);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
/**
|
|
811
|
-
* Convert camelCase to dash-case properties.
|
|
812
|
-
*/
|
|
813
|
-
const camelToDash = (str) => str.replace(/([a-z])([A-Z])/gu, "$1-$2").toLowerCase();
|
|
814
|
-
|
|
815
|
-
const optimizedAppearDataId = "framerAppearId";
|
|
816
|
-
const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
|
|
817
|
-
|
|
818
|
-
function getOptimisedAppearId(visualElement) {
|
|
819
|
-
return visualElement.props[optimizedAppearDataAttribute];
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
/*
|
|
823
|
-
Bezier function generator
|
|
824
|
-
This has been modified from Gaëtan Renaudeau's BezierEasing
|
|
825
|
-
https://github.com/gre/bezier-easing/blob/master/src/index.js
|
|
826
|
-
https://github.com/gre/bezier-easing/blob/master/LICENSE
|
|
827
|
-
|
|
828
|
-
I've removed the newtonRaphsonIterate algo because in benchmarking it
|
|
829
|
-
wasn't noticiably faster than binarySubdivision, indeed removing it
|
|
830
|
-
usually improved times, depending on the curve.
|
|
831
|
-
I also removed the lookup table, as for the added bundle size and loop we're
|
|
832
|
-
only cutting ~4 or so subdivision iterations. I bumped the max iterations up
|
|
833
|
-
to 12 to compensate and this still tended to be faster for no perceivable
|
|
834
|
-
loss in accuracy.
|
|
835
|
-
Usage
|
|
836
|
-
const easeOut = cubicBezier(.17,.67,.83,.67);
|
|
837
|
-
const x = easeOut(0.5); // returns 0.627...
|
|
838
|
-
*/
|
|
839
|
-
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
|
|
840
|
-
const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
|
|
841
|
-
t;
|
|
842
|
-
const subdivisionPrecision = 0.0000001;
|
|
843
|
-
const subdivisionMaxIterations = 12;
|
|
844
|
-
function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
|
|
845
|
-
let currentX;
|
|
846
|
-
let currentT;
|
|
847
|
-
let i = 0;
|
|
848
|
-
do {
|
|
849
|
-
currentT = lowerBound + (upperBound - lowerBound) / 2.0;
|
|
850
|
-
currentX = calcBezier(currentT, mX1, mX2) - x;
|
|
851
|
-
if (currentX > 0.0) {
|
|
852
|
-
upperBound = currentT;
|
|
853
|
-
}
|
|
854
|
-
else {
|
|
855
|
-
lowerBound = currentT;
|
|
856
|
-
}
|
|
857
|
-
} while (Math.abs(currentX) > subdivisionPrecision &&
|
|
858
|
-
++i < subdivisionMaxIterations);
|
|
859
|
-
return currentT;
|
|
860
|
-
}
|
|
861
|
-
function cubicBezier(mX1, mY1, mX2, mY2) {
|
|
862
|
-
// If this is a linear gradient, return linear easing
|
|
863
|
-
if (mX1 === mY1 && mX2 === mY2)
|
|
864
|
-
return motionUtils.noop;
|
|
865
|
-
const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
|
|
866
|
-
// If animation is at start/end, return t without easing
|
|
867
|
-
return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
// Accepts an easing function and returns a new one that outputs mirrored values for
|
|
871
|
-
// the second half of the animation. Turns easeIn into easeInOut.
|
|
872
|
-
const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
|
|
873
|
-
|
|
874
|
-
// Accepts an easing function and returns a new one that outputs reversed values.
|
|
875
|
-
// Turns easeIn into easeOut.
|
|
876
|
-
const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
|
|
877
|
-
|
|
878
|
-
const backOut = /*@__PURE__*/ cubicBezier(0.33, 1.53, 0.69, 0.99);
|
|
879
|
-
const backIn = /*@__PURE__*/ reverseEasing(backOut);
|
|
880
|
-
const backInOut = /*@__PURE__*/ mirrorEasing(backIn);
|
|
881
|
-
|
|
882
|
-
const anticipate = (p) => (p *= 2) < 1 ? 0.5 * backIn(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
|
|
883
|
-
|
|
884
|
-
const circIn = (p) => 1 - Math.sin(Math.acos(p));
|
|
885
|
-
const circOut = reverseEasing(circIn);
|
|
886
|
-
const circInOut = mirrorEasing(circIn);
|
|
887
|
-
|
|
888
|
-
/**
|
|
889
|
-
* Check if the value is a zero value string like "0px" or "0%"
|
|
890
|
-
*/
|
|
891
|
-
const isZeroValueString = (v) => /^0[^.\s]+$/u.test(v);
|
|
892
|
-
|
|
893
|
-
function isNone(value) {
|
|
894
|
-
if (typeof value === "number") {
|
|
895
|
-
return value === 0;
|
|
896
|
-
}
|
|
897
|
-
else if (value !== null) {
|
|
898
|
-
return value === "none" || value === "0" || isZeroValueString(value);
|
|
899
|
-
}
|
|
900
|
-
else {
|
|
901
|
-
return true;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
const number = {
|
|
906
|
-
test: (v) => typeof v === "number",
|
|
907
|
-
parse: parseFloat,
|
|
908
|
-
transform: (v) => v,
|
|
909
|
-
};
|
|
910
|
-
const alpha = {
|
|
911
|
-
...number,
|
|
912
|
-
transform: (v) => clamp(0, 1, v),
|
|
913
|
-
};
|
|
914
|
-
const scale = {
|
|
915
|
-
...number,
|
|
916
|
-
default: 1,
|
|
917
|
-
};
|
|
918
|
-
|
|
919
|
-
// If this number is a decimal, make it just five decimal places
|
|
920
|
-
// to avoid exponents
|
|
921
|
-
const sanitize = (v) => Math.round(v * 100000) / 100000;
|
|
922
|
-
|
|
923
|
-
const floatRegex = /-?(?:\d+(?:\.\d+)?|\.\d+)/gu;
|
|
924
|
-
|
|
925
|
-
function isNullish(v) {
|
|
926
|
-
return v == null;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
const singleColorRegex = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))$/iu;
|
|
930
|
-
|
|
931
|
-
/**
|
|
932
|
-
* Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
|
|
933
|
-
* but false if a number or multiple colors
|
|
934
|
-
*/
|
|
935
|
-
const isColorString = (type, testProp) => (v) => {
|
|
936
|
-
return Boolean((typeof v === "string" &&
|
|
937
|
-
singleColorRegex.test(v) &&
|
|
938
|
-
v.startsWith(type)) ||
|
|
939
|
-
(testProp &&
|
|
940
|
-
!isNullish(v) &&
|
|
941
|
-
Object.prototype.hasOwnProperty.call(v, testProp)));
|
|
942
|
-
};
|
|
943
|
-
const splitColor = (aName, bName, cName) => (v) => {
|
|
944
|
-
if (typeof v !== "string")
|
|
945
|
-
return v;
|
|
946
|
-
const [a, b, c, alpha] = v.match(floatRegex);
|
|
947
|
-
return {
|
|
948
|
-
[aName]: parseFloat(a),
|
|
949
|
-
[bName]: parseFloat(b),
|
|
950
|
-
[cName]: parseFloat(c),
|
|
951
|
-
alpha: alpha !== undefined ? parseFloat(alpha) : 1,
|
|
952
|
-
};
|
|
953
|
-
};
|
|
954
|
-
|
|
955
|
-
const clampRgbUnit = (v) => clamp(0, 255, v);
|
|
956
|
-
const rgbUnit = {
|
|
957
|
-
...number,
|
|
958
|
-
transform: (v) => Math.round(clampRgbUnit(v)),
|
|
959
|
-
};
|
|
960
|
-
const rgba = {
|
|
961
|
-
test: /*@__PURE__*/ isColorString("rgb", "red"),
|
|
962
|
-
parse: /*@__PURE__*/ splitColor("red", "green", "blue"),
|
|
963
|
-
transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" +
|
|
964
|
-
rgbUnit.transform(red) +
|
|
965
|
-
", " +
|
|
966
|
-
rgbUnit.transform(green) +
|
|
967
|
-
", " +
|
|
968
|
-
rgbUnit.transform(blue) +
|
|
969
|
-
", " +
|
|
970
|
-
sanitize(alpha.transform(alpha$1)) +
|
|
971
|
-
")",
|
|
972
|
-
};
|
|
973
|
-
|
|
974
|
-
function parseHex(v) {
|
|
975
|
-
let r = "";
|
|
976
|
-
let g = "";
|
|
977
|
-
let b = "";
|
|
978
|
-
let a = "";
|
|
979
|
-
// If we have 6 characters, ie #FF0000
|
|
980
|
-
if (v.length > 5) {
|
|
981
|
-
r = v.substring(1, 3);
|
|
982
|
-
g = v.substring(3, 5);
|
|
983
|
-
b = v.substring(5, 7);
|
|
984
|
-
a = v.substring(7, 9);
|
|
985
|
-
// Or we have 3 characters, ie #F00
|
|
986
|
-
}
|
|
987
|
-
else {
|
|
988
|
-
r = v.substring(1, 2);
|
|
989
|
-
g = v.substring(2, 3);
|
|
990
|
-
b = v.substring(3, 4);
|
|
991
|
-
a = v.substring(4, 5);
|
|
992
|
-
r += r;
|
|
993
|
-
g += g;
|
|
994
|
-
b += b;
|
|
995
|
-
a += a;
|
|
996
|
-
}
|
|
997
|
-
return {
|
|
998
|
-
red: parseInt(r, 16),
|
|
999
|
-
green: parseInt(g, 16),
|
|
1000
|
-
blue: parseInt(b, 16),
|
|
1001
|
-
alpha: a ? parseInt(a, 16) / 255 : 1,
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
const hex = {
|
|
1005
|
-
test: /*@__PURE__*/ isColorString("#"),
|
|
1006
|
-
parse: parseHex,
|
|
1007
|
-
transform: rgba.transform,
|
|
1008
|
-
};
|
|
1009
|
-
|
|
1010
|
-
const createUnitType = (unit) => ({
|
|
1011
|
-
test: (v) => typeof v === "string" && v.endsWith(unit) && v.split(" ").length === 1,
|
|
1012
|
-
parse: parseFloat,
|
|
1013
|
-
transform: (v) => `${v}${unit}`,
|
|
1014
|
-
});
|
|
1015
|
-
const degrees = /*@__PURE__*/ createUnitType("deg");
|
|
1016
|
-
const percent = /*@__PURE__*/ createUnitType("%");
|
|
1017
|
-
const px = /*@__PURE__*/ createUnitType("px");
|
|
1018
|
-
const vh = /*@__PURE__*/ createUnitType("vh");
|
|
1019
|
-
const vw = /*@__PURE__*/ createUnitType("vw");
|
|
1020
|
-
const progressPercentage = {
|
|
1021
|
-
...percent,
|
|
1022
|
-
parse: (v) => percent.parse(v) / 100,
|
|
1023
|
-
transform: (v) => percent.transform(v * 100),
|
|
1024
|
-
};
|
|
1025
|
-
|
|
1026
|
-
const hsla = {
|
|
1027
|
-
test: /*@__PURE__*/ isColorString("hsl", "hue"),
|
|
1028
|
-
parse: /*@__PURE__*/ splitColor("hue", "saturation", "lightness"),
|
|
1029
|
-
transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => {
|
|
1030
|
-
return ("hsla(" +
|
|
1031
|
-
Math.round(hue) +
|
|
1032
|
-
", " +
|
|
1033
|
-
percent.transform(sanitize(saturation)) +
|
|
1034
|
-
", " +
|
|
1035
|
-
percent.transform(sanitize(lightness)) +
|
|
1036
|
-
", " +
|
|
1037
|
-
sanitize(alpha.transform(alpha$1)) +
|
|
1038
|
-
")");
|
|
1039
|
-
},
|
|
1040
|
-
};
|
|
1041
|
-
|
|
1042
|
-
const color = {
|
|
1043
|
-
test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v),
|
|
1044
|
-
parse: (v) => {
|
|
1045
|
-
if (rgba.test(v)) {
|
|
1046
|
-
return rgba.parse(v);
|
|
1047
|
-
}
|
|
1048
|
-
else if (hsla.test(v)) {
|
|
1049
|
-
return hsla.parse(v);
|
|
1050
|
-
}
|
|
1051
|
-
else {
|
|
1052
|
-
return hex.parse(v);
|
|
1053
|
-
}
|
|
1054
|
-
},
|
|
1055
|
-
transform: (v) => {
|
|
1056
|
-
return typeof v === "string"
|
|
1057
|
-
? v
|
|
1058
|
-
: v.hasOwnProperty("red")
|
|
1059
|
-
? rgba.transform(v)
|
|
1060
|
-
: hsla.transform(v);
|
|
1061
|
-
},
|
|
1062
|
-
};
|
|
1063
|
-
|
|
1064
|
-
const colorRegex = /(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))/giu;
|
|
1065
|
-
|
|
1066
|
-
function test(v) {
|
|
1067
|
-
return (isNaN(v) &&
|
|
1068
|
-
typeof v === "string" &&
|
|
1069
|
-
(v.match(floatRegex)?.length || 0) +
|
|
1070
|
-
(v.match(colorRegex)?.length || 0) >
|
|
1071
|
-
0);
|
|
1072
|
-
}
|
|
1073
|
-
const NUMBER_TOKEN = "number";
|
|
1074
|
-
const COLOR_TOKEN = "color";
|
|
1075
|
-
const VAR_TOKEN = "var";
|
|
1076
|
-
const VAR_FUNCTION_TOKEN = "var(";
|
|
1077
|
-
const SPLIT_TOKEN = "${}";
|
|
1078
|
-
// this regex consists of the `singleCssVariableRegex|rgbHSLValueRegex|digitRegex`
|
|
1079
|
-
const complexRegex = /var\s*\(\s*--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)|#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\)|-?(?:\d+(?:\.\d+)?|\.\d+)/giu;
|
|
1080
|
-
function analyseComplexValue(value) {
|
|
1081
|
-
const originalValue = value.toString();
|
|
1082
|
-
const values = [];
|
|
1083
|
-
const indexes = {
|
|
1084
|
-
color: [],
|
|
1085
|
-
number: [],
|
|
1086
|
-
var: [],
|
|
1087
|
-
};
|
|
1088
|
-
const types = [];
|
|
1089
|
-
let i = 0;
|
|
1090
|
-
const tokenised = originalValue.replace(complexRegex, (parsedValue) => {
|
|
1091
|
-
if (color.test(parsedValue)) {
|
|
1092
|
-
indexes.color.push(i);
|
|
1093
|
-
types.push(COLOR_TOKEN);
|
|
1094
|
-
values.push(color.parse(parsedValue));
|
|
1095
|
-
}
|
|
1096
|
-
else if (parsedValue.startsWith(VAR_FUNCTION_TOKEN)) {
|
|
1097
|
-
indexes.var.push(i);
|
|
1098
|
-
types.push(VAR_TOKEN);
|
|
1099
|
-
values.push(parsedValue);
|
|
1100
|
-
}
|
|
1101
|
-
else {
|
|
1102
|
-
indexes.number.push(i);
|
|
1103
|
-
types.push(NUMBER_TOKEN);
|
|
1104
|
-
values.push(parseFloat(parsedValue));
|
|
1105
|
-
}
|
|
1106
|
-
++i;
|
|
1107
|
-
return SPLIT_TOKEN;
|
|
1108
|
-
});
|
|
1109
|
-
const split = tokenised.split(SPLIT_TOKEN);
|
|
1110
|
-
return { values, split, indexes, types };
|
|
1111
|
-
}
|
|
1112
|
-
function parseComplexValue(v) {
|
|
1113
|
-
return analyseComplexValue(v).values;
|
|
1114
|
-
}
|
|
1115
|
-
function createTransformer(source) {
|
|
1116
|
-
const { split, types } = analyseComplexValue(source);
|
|
1117
|
-
const numSections = split.length;
|
|
1118
|
-
return (v) => {
|
|
1119
|
-
let output = "";
|
|
1120
|
-
for (let i = 0; i < numSections; i++) {
|
|
1121
|
-
output += split[i];
|
|
1122
|
-
if (v[i] !== undefined) {
|
|
1123
|
-
const type = types[i];
|
|
1124
|
-
if (type === NUMBER_TOKEN) {
|
|
1125
|
-
output += sanitize(v[i]);
|
|
1126
|
-
}
|
|
1127
|
-
else if (type === COLOR_TOKEN) {
|
|
1128
|
-
output += color.transform(v[i]);
|
|
1129
|
-
}
|
|
1130
|
-
else {
|
|
1131
|
-
output += v[i];
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
return output;
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
|
-
const convertNumbersToZero = (v) => typeof v === "number" ? 0 : v;
|
|
1139
|
-
function getAnimatableNone$1(v) {
|
|
1140
|
-
const parsed = parseComplexValue(v);
|
|
1141
|
-
const transformer = createTransformer(v);
|
|
1142
|
-
return transformer(parsed.map(convertNumbersToZero));
|
|
1143
|
-
}
|
|
1144
|
-
const complex = {
|
|
1145
|
-
test,
|
|
1146
|
-
parse: parseComplexValue,
|
|
1147
|
-
createTransformer,
|
|
1148
|
-
getAnimatableNone: getAnimatableNone$1,
|
|
1149
|
-
};
|
|
1150
|
-
|
|
1151
|
-
/**
|
|
1152
|
-
* Properties that should default to 1 or 100%
|
|
1153
|
-
*/
|
|
1154
|
-
const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
|
|
1155
|
-
function applyDefaultFilter(v) {
|
|
1156
|
-
const [name, value] = v.slice(0, -1).split("(");
|
|
1157
|
-
if (name === "drop-shadow")
|
|
1158
|
-
return v;
|
|
1159
|
-
const [number] = value.match(floatRegex) || [];
|
|
1160
|
-
if (!number)
|
|
1161
|
-
return v;
|
|
1162
|
-
const unit = value.replace(number, "");
|
|
1163
|
-
let defaultValue = maxDefaults.has(name) ? 1 : 0;
|
|
1164
|
-
if (number !== value)
|
|
1165
|
-
defaultValue *= 100;
|
|
1166
|
-
return name + "(" + defaultValue + unit + ")";
|
|
1167
|
-
}
|
|
1168
|
-
const functionRegex = /\b([a-z-]*)\(.*?\)/gu;
|
|
1169
|
-
const filter = {
|
|
1170
|
-
...complex,
|
|
1171
|
-
getAnimatableNone: (v) => {
|
|
1172
|
-
const functions = v.match(functionRegex);
|
|
1173
|
-
return functions ? functions.map(applyDefaultFilter).join(" ") : v;
|
|
1174
|
-
},
|
|
1175
|
-
};
|
|
1176
|
-
|
|
1177
|
-
const browserNumberValueTypes = {
|
|
1178
|
-
// Border props
|
|
1179
|
-
borderWidth: px,
|
|
1180
|
-
borderTopWidth: px,
|
|
1181
|
-
borderRightWidth: px,
|
|
1182
|
-
borderBottomWidth: px,
|
|
1183
|
-
borderLeftWidth: px,
|
|
1184
|
-
borderRadius: px,
|
|
1185
|
-
radius: px,
|
|
1186
|
-
borderTopLeftRadius: px,
|
|
1187
|
-
borderTopRightRadius: px,
|
|
1188
|
-
borderBottomRightRadius: px,
|
|
1189
|
-
borderBottomLeftRadius: px,
|
|
1190
|
-
// Positioning props
|
|
1191
|
-
width: px,
|
|
1192
|
-
maxWidth: px,
|
|
1193
|
-
height: px,
|
|
1194
|
-
maxHeight: px,
|
|
1195
|
-
top: px,
|
|
1196
|
-
right: px,
|
|
1197
|
-
bottom: px,
|
|
1198
|
-
left: px,
|
|
1199
|
-
// Spacing props
|
|
1200
|
-
padding: px,
|
|
1201
|
-
paddingTop: px,
|
|
1202
|
-
paddingRight: px,
|
|
1203
|
-
paddingBottom: px,
|
|
1204
|
-
paddingLeft: px,
|
|
1205
|
-
margin: px,
|
|
1206
|
-
marginTop: px,
|
|
1207
|
-
marginRight: px,
|
|
1208
|
-
marginBottom: px,
|
|
1209
|
-
marginLeft: px,
|
|
1210
|
-
// Misc
|
|
1211
|
-
backgroundPositionX: px,
|
|
1212
|
-
backgroundPositionY: px,
|
|
1213
|
-
};
|
|
1214
|
-
|
|
1215
|
-
const transformValueTypes = {
|
|
1216
|
-
rotate: degrees,
|
|
1217
|
-
rotateX: degrees,
|
|
1218
|
-
rotateY: degrees,
|
|
1219
|
-
rotateZ: degrees,
|
|
1220
|
-
scale,
|
|
1221
|
-
scaleX: scale,
|
|
1222
|
-
scaleY: scale,
|
|
1223
|
-
scaleZ: scale,
|
|
1224
|
-
skew: degrees,
|
|
1225
|
-
skewX: degrees,
|
|
1226
|
-
skewY: degrees,
|
|
1227
|
-
distance: px,
|
|
1228
|
-
translateX: px,
|
|
1229
|
-
translateY: px,
|
|
1230
|
-
translateZ: px,
|
|
1231
|
-
x: px,
|
|
1232
|
-
y: px,
|
|
1233
|
-
z: px,
|
|
1234
|
-
perspective: px,
|
|
1235
|
-
transformPerspective: px,
|
|
1236
|
-
opacity: alpha,
|
|
1237
|
-
originX: progressPercentage,
|
|
1238
|
-
originY: progressPercentage,
|
|
1239
|
-
originZ: px,
|
|
1240
|
-
};
|
|
1241
|
-
|
|
1242
|
-
const int = {
|
|
1243
|
-
...number,
|
|
1244
|
-
transform: Math.round,
|
|
1245
|
-
};
|
|
1246
|
-
|
|
1247
|
-
const numberValueTypes = {
|
|
1248
|
-
...browserNumberValueTypes,
|
|
1249
|
-
...transformValueTypes,
|
|
1250
|
-
zIndex: int,
|
|
1251
|
-
size: px,
|
|
1252
|
-
// SVG
|
|
1253
|
-
fillOpacity: alpha,
|
|
1254
|
-
strokeOpacity: alpha,
|
|
1255
|
-
numOctaves: int,
|
|
1256
|
-
};
|
|
1257
|
-
|
|
1258
|
-
/**
|
|
1259
|
-
* A map of default value types for common values
|
|
1260
|
-
*/
|
|
1261
|
-
const defaultValueTypes = {
|
|
1262
|
-
...numberValueTypes,
|
|
1263
|
-
// Color props
|
|
1264
|
-
color,
|
|
1265
|
-
backgroundColor: color,
|
|
1266
|
-
outlineColor: color,
|
|
1267
|
-
fill: color,
|
|
1268
|
-
stroke: color,
|
|
1269
|
-
// Border props
|
|
1270
|
-
borderColor: color,
|
|
1271
|
-
borderTopColor: color,
|
|
1272
|
-
borderRightColor: color,
|
|
1273
|
-
borderBottomColor: color,
|
|
1274
|
-
borderLeftColor: color,
|
|
1275
|
-
filter,
|
|
1276
|
-
WebkitFilter: filter,
|
|
1277
|
-
};
|
|
1278
|
-
/**
|
|
1279
|
-
* Gets the default ValueType for the provided value key
|
|
1280
|
-
*/
|
|
1281
|
-
const getDefaultValueType = (key) => defaultValueTypes[key];
|
|
1282
|
-
|
|
1283
|
-
function getAnimatableNone(key, value) {
|
|
1284
|
-
let defaultValueType = getDefaultValueType(key);
|
|
1285
|
-
if (defaultValueType !== filter)
|
|
1286
|
-
defaultValueType = complex;
|
|
1287
|
-
// If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
|
|
1288
|
-
return defaultValueType.getAnimatableNone
|
|
1289
|
-
? defaultValueType.getAnimatableNone(value)
|
|
1290
|
-
: undefined;
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
/**
|
|
1294
|
-
* If we encounter keyframes like "none" or "0" and we also have keyframes like
|
|
1295
|
-
* "#fff" or "200px 200px" we want to find a keyframe to serve as a template for
|
|
1296
|
-
* the "none" keyframes. In this case "#fff" or "200px 200px" - then these get turned into
|
|
1297
|
-
* zero equivalents, i.e. "#fff0" or "0px 0px".
|
|
1298
|
-
*/
|
|
1299
|
-
const invalidTemplates = new Set(["auto", "none", "0"]);
|
|
1300
|
-
function makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name) {
|
|
1301
|
-
let i = 0;
|
|
1302
|
-
let animatableTemplate = undefined;
|
|
1303
|
-
while (i < unresolvedKeyframes.length && !animatableTemplate) {
|
|
1304
|
-
const keyframe = unresolvedKeyframes[i];
|
|
1305
|
-
if (typeof keyframe === "string" &&
|
|
1306
|
-
!invalidTemplates.has(keyframe) &&
|
|
1307
|
-
analyseComplexValue(keyframe).values.length) {
|
|
1308
|
-
animatableTemplate = unresolvedKeyframes[i];
|
|
1309
|
-
}
|
|
1310
|
-
i++;
|
|
1311
|
-
}
|
|
1312
|
-
if (animatableTemplate && name) {
|
|
1313
|
-
for (const noneIndex of noneKeyframeIndexes) {
|
|
1314
|
-
unresolvedKeyframes[noneIndex] = getAnimatableNone(name, animatableTemplate);
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
const radToDeg = (rad) => (rad * 180) / Math.PI;
|
|
1320
|
-
const rotate = (v) => {
|
|
1321
|
-
const angle = radToDeg(Math.atan2(v[1], v[0]));
|
|
1322
|
-
return rebaseAngle(angle);
|
|
1323
|
-
};
|
|
1324
|
-
const matrix2dParsers = {
|
|
1325
|
-
x: 4,
|
|
1326
|
-
y: 5,
|
|
1327
|
-
translateX: 4,
|
|
1328
|
-
translateY: 5,
|
|
1329
|
-
scaleX: 0,
|
|
1330
|
-
scaleY: 3,
|
|
1331
|
-
scale: (v) => (Math.abs(v[0]) + Math.abs(v[3])) / 2,
|
|
1332
|
-
rotate,
|
|
1333
|
-
rotateZ: rotate,
|
|
1334
|
-
skewX: (v) => radToDeg(Math.atan(v[1])),
|
|
1335
|
-
skewY: (v) => radToDeg(Math.atan(v[2])),
|
|
1336
|
-
skew: (v) => (Math.abs(v[1]) + Math.abs(v[2])) / 2,
|
|
1337
|
-
};
|
|
1338
|
-
const rebaseAngle = (angle) => {
|
|
1339
|
-
angle = angle % 360;
|
|
1340
|
-
if (angle < 0)
|
|
1341
|
-
angle += 360;
|
|
1342
|
-
return angle;
|
|
1343
|
-
};
|
|
1344
|
-
const rotateZ = rotate;
|
|
1345
|
-
const scaleX = (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
|
1346
|
-
const scaleY = (v) => Math.sqrt(v[4] * v[4] + v[5] * v[5]);
|
|
1347
|
-
const matrix3dParsers = {
|
|
1348
|
-
x: 12,
|
|
1349
|
-
y: 13,
|
|
1350
|
-
z: 14,
|
|
1351
|
-
translateX: 12,
|
|
1352
|
-
translateY: 13,
|
|
1353
|
-
translateZ: 14,
|
|
1354
|
-
scaleX,
|
|
1355
|
-
scaleY,
|
|
1356
|
-
scale: (v) => (scaleX(v) + scaleY(v)) / 2,
|
|
1357
|
-
rotateX: (v) => rebaseAngle(radToDeg(Math.atan2(v[6], v[5]))),
|
|
1358
|
-
rotateY: (v) => rebaseAngle(radToDeg(Math.atan2(-v[2], v[0]))),
|
|
1359
|
-
rotateZ,
|
|
1360
|
-
rotate: rotateZ,
|
|
1361
|
-
skewX: (v) => radToDeg(Math.atan(v[4])),
|
|
1362
|
-
skewY: (v) => radToDeg(Math.atan(v[1])),
|
|
1363
|
-
skew: (v) => (Math.abs(v[1]) + Math.abs(v[4])) / 2,
|
|
1364
|
-
};
|
|
1365
|
-
function defaultTransformValue(name) {
|
|
1366
|
-
return name.includes("scale") ? 1 : 0;
|
|
1367
|
-
}
|
|
1368
|
-
function parseValueFromTransform(transform, name) {
|
|
1369
|
-
if (!transform || transform === "none") {
|
|
1370
|
-
return defaultTransformValue(name);
|
|
1371
|
-
}
|
|
1372
|
-
const matrix3dMatch = transform.match(/^matrix3d\(([-\d.e\s,]+)\)$/u);
|
|
1373
|
-
let parsers;
|
|
1374
|
-
let match;
|
|
1375
|
-
if (matrix3dMatch) {
|
|
1376
|
-
parsers = matrix3dParsers;
|
|
1377
|
-
match = matrix3dMatch;
|
|
1378
|
-
}
|
|
1379
|
-
else {
|
|
1380
|
-
const matrix2dMatch = transform.match(/^matrix\(([-\d.e\s,]+)\)$/u);
|
|
1381
|
-
parsers = matrix2dParsers;
|
|
1382
|
-
match = matrix2dMatch;
|
|
1383
|
-
}
|
|
1384
|
-
if (!match) {
|
|
1385
|
-
return defaultTransformValue(name);
|
|
1386
|
-
}
|
|
1387
|
-
const valueParser = parsers[name];
|
|
1388
|
-
const values = match[1].split(",").map(convertTransformToNumber);
|
|
1389
|
-
return typeof valueParser === "function"
|
|
1390
|
-
? valueParser(values)
|
|
1391
|
-
: values[valueParser];
|
|
1392
|
-
}
|
|
1393
|
-
const readTransformValue = (instance, name) => {
|
|
1394
|
-
const { transform = "none" } = getComputedStyle(instance);
|
|
1395
|
-
return parseValueFromTransform(transform, name);
|
|
1396
|
-
};
|
|
1397
|
-
function convertTransformToNumber(value) {
|
|
1398
|
-
return parseFloat(value.trim());
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
const isNumOrPxType = (v) => v === number || v === px;
|
|
1402
|
-
const transformKeys = new Set(["x", "y", "z"]);
|
|
1403
|
-
const nonTranslationalTransformKeys = transformPropOrder.filter((key) => !transformKeys.has(key));
|
|
1404
|
-
function removeNonTranslationalTransform(visualElement) {
|
|
1405
|
-
const removedTransforms = [];
|
|
1406
|
-
nonTranslationalTransformKeys.forEach((key) => {
|
|
1407
|
-
const value = visualElement.getValue(key);
|
|
1408
|
-
if (value !== undefined) {
|
|
1409
|
-
removedTransforms.push([key, value.get()]);
|
|
1410
|
-
value.set(key.startsWith("scale") ? 1 : 0);
|
|
1411
|
-
}
|
|
1412
|
-
});
|
|
1413
|
-
return removedTransforms;
|
|
1414
|
-
}
|
|
1415
|
-
const positionalValues = {
|
|
1416
|
-
// Dimensions
|
|
1417
|
-
width: ({ x }, { paddingLeft = "0", paddingRight = "0" }) => x.max - x.min - parseFloat(paddingLeft) - parseFloat(paddingRight),
|
|
1418
|
-
height: ({ y }, { paddingTop = "0", paddingBottom = "0" }) => y.max - y.min - parseFloat(paddingTop) - parseFloat(paddingBottom),
|
|
1419
|
-
top: (_bbox, { top }) => parseFloat(top),
|
|
1420
|
-
left: (_bbox, { left }) => parseFloat(left),
|
|
1421
|
-
bottom: ({ y }, { top }) => parseFloat(top) + (y.max - y.min),
|
|
1422
|
-
right: ({ x }, { left }) => parseFloat(left) + (x.max - x.min),
|
|
1423
|
-
// Transform
|
|
1424
|
-
x: (_bbox, { transform }) => parseValueFromTransform(transform, "x"),
|
|
1425
|
-
y: (_bbox, { transform }) => parseValueFromTransform(transform, "y"),
|
|
1426
|
-
};
|
|
1427
|
-
// Alias translate longform names
|
|
1428
|
-
positionalValues.translateX = positionalValues.x;
|
|
1429
|
-
positionalValues.translateY = positionalValues.y;
|
|
1430
|
-
|
|
1431
|
-
const toResolve = new Set();
|
|
1432
|
-
let isScheduled = false;
|
|
1433
|
-
let anyNeedsMeasurement = false;
|
|
1434
|
-
function measureAllKeyframes() {
|
|
1435
|
-
if (anyNeedsMeasurement) {
|
|
1436
|
-
const resolversToMeasure = Array.from(toResolve).filter((resolver) => resolver.needsMeasurement);
|
|
1437
|
-
const elementsToMeasure = new Set(resolversToMeasure.map((resolver) => resolver.element));
|
|
1438
|
-
const transformsToRestore = new Map();
|
|
1439
|
-
/**
|
|
1440
|
-
* Write pass
|
|
1441
|
-
* If we're measuring elements we want to remove bounding box-changing transforms.
|
|
1442
|
-
*/
|
|
1443
|
-
elementsToMeasure.forEach((element) => {
|
|
1444
|
-
const removedTransforms = removeNonTranslationalTransform(element);
|
|
1445
|
-
if (!removedTransforms.length)
|
|
1446
|
-
return;
|
|
1447
|
-
transformsToRestore.set(element, removedTransforms);
|
|
1448
|
-
element.render();
|
|
1449
|
-
});
|
|
1450
|
-
// Read
|
|
1451
|
-
resolversToMeasure.forEach((resolver) => resolver.measureInitialState());
|
|
1452
|
-
// Write
|
|
1453
|
-
elementsToMeasure.forEach((element) => {
|
|
1454
|
-
element.render();
|
|
1455
|
-
const restore = transformsToRestore.get(element);
|
|
1456
|
-
if (restore) {
|
|
1457
|
-
restore.forEach(([key, value]) => {
|
|
1458
|
-
element.getValue(key)?.set(value);
|
|
1459
|
-
});
|
|
1460
|
-
}
|
|
1461
|
-
});
|
|
1462
|
-
// Read
|
|
1463
|
-
resolversToMeasure.forEach((resolver) => resolver.measureEndState());
|
|
1464
|
-
// Write
|
|
1465
|
-
resolversToMeasure.forEach((resolver) => {
|
|
1466
|
-
if (resolver.suspendedScrollY !== undefined) {
|
|
1467
|
-
window.scrollTo(0, resolver.suspendedScrollY);
|
|
1468
|
-
}
|
|
1469
|
-
});
|
|
1470
|
-
}
|
|
1471
|
-
anyNeedsMeasurement = false;
|
|
1472
|
-
isScheduled = false;
|
|
1473
|
-
toResolve.forEach((resolver) => resolver.complete());
|
|
1474
|
-
toResolve.clear();
|
|
1475
|
-
}
|
|
1476
|
-
function readAllKeyframes() {
|
|
1477
|
-
toResolve.forEach((resolver) => {
|
|
1478
|
-
resolver.readKeyframes();
|
|
1479
|
-
if (resolver.needsMeasurement) {
|
|
1480
|
-
anyNeedsMeasurement = true;
|
|
1481
|
-
}
|
|
1482
|
-
});
|
|
1483
|
-
}
|
|
1484
|
-
function flushKeyframeResolvers() {
|
|
1485
|
-
readAllKeyframes();
|
|
1486
|
-
measureAllKeyframes();
|
|
1487
|
-
}
|
|
1488
|
-
class KeyframeResolver {
|
|
1489
|
-
constructor(unresolvedKeyframes, onComplete, name, motionValue, element, isAsync = false) {
|
|
1490
|
-
/**
|
|
1491
|
-
* Track whether this resolver has completed. Once complete, it never
|
|
1492
|
-
* needs to attempt keyframe resolution again.
|
|
1493
|
-
*/
|
|
1494
|
-
this.isComplete = false;
|
|
1495
|
-
/**
|
|
1496
|
-
* Track whether this resolver is async. If it is, it'll be added to the
|
|
1497
|
-
* resolver queue and flushed in the next frame. Resolvers that aren't going
|
|
1498
|
-
* to trigger read/write thrashing don't need to be async.
|
|
1499
|
-
*/
|
|
1500
|
-
this.isAsync = false;
|
|
1501
|
-
/**
|
|
1502
|
-
* Track whether this resolver needs to perform a measurement
|
|
1503
|
-
* to resolve its keyframes.
|
|
1504
|
-
*/
|
|
1505
|
-
this.needsMeasurement = false;
|
|
1506
|
-
/**
|
|
1507
|
-
* Track whether this resolver is currently scheduled to resolve
|
|
1508
|
-
* to allow it to be cancelled and resumed externally.
|
|
1509
|
-
*/
|
|
1510
|
-
this.isScheduled = false;
|
|
1511
|
-
this.unresolvedKeyframes = [...unresolvedKeyframes];
|
|
1512
|
-
this.onComplete = onComplete;
|
|
1513
|
-
this.name = name;
|
|
1514
|
-
this.motionValue = motionValue;
|
|
1515
|
-
this.element = element;
|
|
1516
|
-
this.isAsync = isAsync;
|
|
1517
|
-
}
|
|
1518
|
-
scheduleResolve() {
|
|
1519
|
-
this.isScheduled = true;
|
|
1520
|
-
if (this.isAsync) {
|
|
1521
|
-
toResolve.add(this);
|
|
1522
|
-
if (!isScheduled) {
|
|
1523
|
-
isScheduled = true;
|
|
1524
|
-
motionDom.frame.read(readAllKeyframes);
|
|
1525
|
-
motionDom.frame.resolveKeyframes(measureAllKeyframes);
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
else {
|
|
1529
|
-
this.readKeyframes();
|
|
1530
|
-
this.complete();
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
readKeyframes() {
|
|
1534
|
-
const { unresolvedKeyframes, name, element, motionValue } = this;
|
|
1535
|
-
/**
|
|
1536
|
-
* If a keyframe is null, we hydrate it either by reading it from
|
|
1537
|
-
* the instance, or propagating from previous keyframes.
|
|
1538
|
-
*/
|
|
1539
|
-
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
|
1540
|
-
if (unresolvedKeyframes[i] === null) {
|
|
1541
|
-
/**
|
|
1542
|
-
* If the first keyframe is null, we need to find its value by sampling the element
|
|
1543
|
-
*/
|
|
1544
|
-
if (i === 0) {
|
|
1545
|
-
const currentValue = motionValue?.get();
|
|
1546
|
-
const finalKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
|
|
1547
|
-
if (currentValue !== undefined) {
|
|
1548
|
-
unresolvedKeyframes[0] = currentValue;
|
|
1549
|
-
}
|
|
1550
|
-
else if (element && name) {
|
|
1551
|
-
const valueAsRead = element.readValue(name, finalKeyframe);
|
|
1552
|
-
if (valueAsRead !== undefined && valueAsRead !== null) {
|
|
1553
|
-
unresolvedKeyframes[0] = valueAsRead;
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
if (unresolvedKeyframes[0] === undefined) {
|
|
1557
|
-
unresolvedKeyframes[0] = finalKeyframe;
|
|
1558
|
-
}
|
|
1559
|
-
if (motionValue && currentValue === undefined) {
|
|
1560
|
-
motionValue.set(unresolvedKeyframes[0]);
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
else {
|
|
1564
|
-
unresolvedKeyframes[i] = unresolvedKeyframes[i - 1];
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
setFinalKeyframe() { }
|
|
1570
|
-
measureInitialState() { }
|
|
1571
|
-
renderEndStyles() { }
|
|
1572
|
-
measureEndState() { }
|
|
1573
|
-
complete() {
|
|
1574
|
-
this.isComplete = true;
|
|
1575
|
-
this.onComplete(this.unresolvedKeyframes, this.finalKeyframe);
|
|
1576
|
-
toResolve.delete(this);
|
|
1577
|
-
}
|
|
1578
|
-
cancel() {
|
|
1579
|
-
if (!this.isComplete) {
|
|
1580
|
-
this.isScheduled = false;
|
|
1581
|
-
toResolve.delete(this);
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
resume() {
|
|
1585
|
-
if (!this.isComplete)
|
|
1586
|
-
this.scheduleResolve();
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
|
|
1590
|
-
/**
|
|
1591
|
-
* Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
|
|
1592
|
-
*/
|
|
1593
|
-
const isNumericalString = (v) => /^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(v);
|
|
1594
|
-
|
|
1595
|
-
const checkStringStartsWith = (token) => (key) => typeof key === "string" && key.startsWith(token);
|
|
1596
|
-
const isCSSVariableName =
|
|
1597
|
-
/*@__PURE__*/ checkStringStartsWith("--");
|
|
1598
|
-
const startsAsVariableToken =
|
|
1599
|
-
/*@__PURE__*/ checkStringStartsWith("var(--");
|
|
1600
|
-
const isCSSVariableToken = (value) => {
|
|
1601
|
-
const startsWithToken = startsAsVariableToken(value);
|
|
1602
|
-
if (!startsWithToken)
|
|
1603
|
-
return false;
|
|
1604
|
-
// Ensure any comments are stripped from the value as this can harm performance of the regex.
|
|
1605
|
-
return singleCssVariableRegex.test(value.split("/*")[0].trim());
|
|
1606
|
-
};
|
|
1607
|
-
const singleCssVariableRegex = /var\(--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)$/iu;
|
|
1608
|
-
|
|
1609
|
-
/**
|
|
1610
|
-
* Parse Framer's special CSS variable format into a CSS token and a fallback.
|
|
1611
|
-
*
|
|
1612
|
-
* ```
|
|
1613
|
-
* `var(--foo, #fff)` => [`--foo`, '#fff']
|
|
1614
|
-
* ```
|
|
1615
|
-
*
|
|
1616
|
-
* @param current
|
|
1617
|
-
*/
|
|
1618
|
-
const splitCSSVariableRegex =
|
|
1619
|
-
// eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words
|
|
1620
|
-
/^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u;
|
|
1621
|
-
function parseCSSVariable(current) {
|
|
1622
|
-
const match = splitCSSVariableRegex.exec(current);
|
|
1623
|
-
if (!match)
|
|
1624
|
-
return [,];
|
|
1625
|
-
const [, token1, token2, fallback] = match;
|
|
1626
|
-
return [`--${token1 ?? token2}`, fallback];
|
|
1627
|
-
}
|
|
1628
|
-
const maxDepth = 4;
|
|
1629
|
-
function getVariableValue(current, element, depth = 1) {
|
|
1630
|
-
motionUtils.invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`);
|
|
1631
|
-
const [token, fallback] = parseCSSVariable(current);
|
|
1632
|
-
// No CSS variable detected
|
|
1633
|
-
if (!token)
|
|
1634
|
-
return;
|
|
1635
|
-
// Attempt to read this CSS variable off the element
|
|
1636
|
-
const resolved = window.getComputedStyle(element).getPropertyValue(token);
|
|
1637
|
-
if (resolved) {
|
|
1638
|
-
const trimmed = resolved.trim();
|
|
1639
|
-
return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed;
|
|
1640
|
-
}
|
|
1641
|
-
return isCSSVariableToken(fallback)
|
|
1642
|
-
? getVariableValue(fallback, element, depth + 1)
|
|
1643
|
-
: fallback;
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
/**
|
|
1647
|
-
* Tests a provided value against a ValueType
|
|
1648
|
-
*/
|
|
1649
|
-
const testValueType = (v) => (type) => type.test(v);
|
|
1650
|
-
|
|
1651
|
-
/**
|
|
1652
|
-
* ValueType for "auto"
|
|
1653
|
-
*/
|
|
1654
|
-
const auto = {
|
|
1655
|
-
test: (v) => v === "auto",
|
|
1656
|
-
parse: (v) => v,
|
|
1657
|
-
};
|
|
1658
|
-
|
|
1659
|
-
/**
|
|
1660
|
-
* A list of value types commonly used for dimensions
|
|
1661
|
-
*/
|
|
1662
|
-
const dimensionValueTypes = [number, px, percent, degrees, vw, vh, auto];
|
|
1663
|
-
/**
|
|
1664
|
-
* Tests a dimensional value against the list of dimension ValueTypes
|
|
1665
|
-
*/
|
|
1666
|
-
const findDimensionValueType = (v) => dimensionValueTypes.find(testValueType(v));
|
|
1667
|
-
|
|
1668
|
-
class DOMKeyframesResolver extends KeyframeResolver {
|
|
1669
|
-
constructor(unresolvedKeyframes, onComplete, name, motionValue, element) {
|
|
1670
|
-
super(unresolvedKeyframes, onComplete, name, motionValue, element, true);
|
|
1671
|
-
}
|
|
1672
|
-
readKeyframes() {
|
|
1673
|
-
const { unresolvedKeyframes, element, name } = this;
|
|
1674
|
-
if (!element || !element.current)
|
|
1675
|
-
return;
|
|
1676
|
-
super.readKeyframes();
|
|
1677
|
-
/**
|
|
1678
|
-
* If any keyframe is a CSS variable, we need to find its value by sampling the element
|
|
1679
|
-
*/
|
|
1680
|
-
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
|
1681
|
-
let keyframe = unresolvedKeyframes[i];
|
|
1682
|
-
if (typeof keyframe === "string") {
|
|
1683
|
-
keyframe = keyframe.trim();
|
|
1684
|
-
if (isCSSVariableToken(keyframe)) {
|
|
1685
|
-
const resolved = getVariableValue(keyframe, element.current);
|
|
1686
|
-
if (resolved !== undefined) {
|
|
1687
|
-
unresolvedKeyframes[i] = resolved;
|
|
1688
|
-
}
|
|
1689
|
-
if (i === unresolvedKeyframes.length - 1) {
|
|
1690
|
-
this.finalKeyframe = keyframe;
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
/**
|
|
1696
|
-
* Resolve "none" values. We do this potentially twice - once before and once after measuring keyframes.
|
|
1697
|
-
* This could be seen as inefficient but it's a trade-off to avoid measurements in more situations, which
|
|
1698
|
-
* have a far bigger performance impact.
|
|
1699
|
-
*/
|
|
1700
|
-
this.resolveNoneKeyframes();
|
|
1701
|
-
/**
|
|
1702
|
-
* Check to see if unit type has changed. If so schedule jobs that will
|
|
1703
|
-
* temporarily set styles to the destination keyframes.
|
|
1704
|
-
* Skip if we have more than two keyframes or this isn't a positional value.
|
|
1705
|
-
* TODO: We can throw if there are multiple keyframes and the value type changes.
|
|
1706
|
-
*/
|
|
1707
|
-
if (!positionalKeys.has(name) || unresolvedKeyframes.length !== 2) {
|
|
1708
|
-
return;
|
|
1709
|
-
}
|
|
1710
|
-
const [origin, target] = unresolvedKeyframes;
|
|
1711
|
-
const originType = findDimensionValueType(origin);
|
|
1712
|
-
const targetType = findDimensionValueType(target);
|
|
1713
|
-
/**
|
|
1714
|
-
* Either we don't recognise these value types or we can animate between them.
|
|
1715
|
-
*/
|
|
1716
|
-
if (originType === targetType)
|
|
1717
|
-
return;
|
|
1718
|
-
/**
|
|
1719
|
-
* If both values are numbers or pixels, we can animate between them by
|
|
1720
|
-
* converting them to numbers.
|
|
1721
|
-
*/
|
|
1722
|
-
if (isNumOrPxType(originType) && isNumOrPxType(targetType)) {
|
|
1723
|
-
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
|
1724
|
-
const value = unresolvedKeyframes[i];
|
|
1725
|
-
if (typeof value === "string") {
|
|
1726
|
-
unresolvedKeyframes[i] = parseFloat(value);
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
else {
|
|
1731
|
-
/**
|
|
1732
|
-
* Else, the only way to resolve this is by measuring the element.
|
|
1733
|
-
*/
|
|
1734
|
-
this.needsMeasurement = true;
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
resolveNoneKeyframes() {
|
|
1738
|
-
const { unresolvedKeyframes, name } = this;
|
|
1739
|
-
const noneKeyframeIndexes = [];
|
|
1740
|
-
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
|
1741
|
-
if (isNone(unresolvedKeyframes[i])) {
|
|
1742
|
-
noneKeyframeIndexes.push(i);
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
if (noneKeyframeIndexes.length) {
|
|
1746
|
-
makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name);
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
measureInitialState() {
|
|
1750
|
-
const { element, unresolvedKeyframes, name } = this;
|
|
1751
|
-
if (!element || !element.current)
|
|
1752
|
-
return;
|
|
1753
|
-
if (name === "height") {
|
|
1754
|
-
this.suspendedScrollY = window.pageYOffset;
|
|
1755
|
-
}
|
|
1756
|
-
this.measuredOrigin = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
|
|
1757
|
-
unresolvedKeyframes[0] = this.measuredOrigin;
|
|
1758
|
-
// Set final key frame to measure after next render
|
|
1759
|
-
const measureKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
|
|
1760
|
-
if (measureKeyframe !== undefined) {
|
|
1761
|
-
element.getValue(name, measureKeyframe).jump(measureKeyframe, false);
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
measureEndState() {
|
|
1765
|
-
const { element, name, unresolvedKeyframes } = this;
|
|
1766
|
-
if (!element || !element.current)
|
|
1767
|
-
return;
|
|
1768
|
-
const value = element.getValue(name);
|
|
1769
|
-
value && value.jump(this.measuredOrigin, false);
|
|
1770
|
-
const finalKeyframeIndex = unresolvedKeyframes.length - 1;
|
|
1771
|
-
const finalKeyframe = unresolvedKeyframes[finalKeyframeIndex];
|
|
1772
|
-
unresolvedKeyframes[finalKeyframeIndex] = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
|
|
1773
|
-
if (finalKeyframe !== null && this.finalKeyframe === undefined) {
|
|
1774
|
-
this.finalKeyframe = finalKeyframe;
|
|
1775
|
-
}
|
|
1776
|
-
// If we removed transform values, reapply them before the next render
|
|
1777
|
-
if (this.removedTransforms?.length) {
|
|
1778
|
-
this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => {
|
|
1779
|
-
element
|
|
1780
|
-
.getValue(unsetTransformName)
|
|
1781
|
-
.set(unsetTransformValue);
|
|
1782
|
-
});
|
|
1783
|
-
}
|
|
1784
|
-
this.resolveNoneKeyframes();
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
/**
|
|
1789
|
-
* Check if a value is animatable. Examples:
|
|
1790
|
-
*
|
|
1791
|
-
* ✅: 100, "100px", "#fff"
|
|
1792
|
-
* ❌: "block", "url(2.jpg)"
|
|
1793
|
-
* @param value
|
|
1794
|
-
*
|
|
1795
|
-
* @internal
|
|
1796
|
-
*/
|
|
1797
|
-
const isAnimatable = (value, name) => {
|
|
1798
|
-
// If the list of keys tat might be non-animatable grows, replace with Set
|
|
1799
|
-
if (name === "zIndex")
|
|
1800
|
-
return false;
|
|
1801
|
-
// If it's a number or a keyframes array, we can animate it. We might at some point
|
|
1802
|
-
// need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
|
|
1803
|
-
// but for now lets leave it like this for performance reasons
|
|
1804
|
-
if (typeof value === "number" || Array.isArray(value))
|
|
1805
|
-
return true;
|
|
1806
|
-
if (typeof value === "string" && // It's animatable if we have a string
|
|
1807
|
-
(complex.test(value) || value === "0") && // And it contains numbers and/or colors
|
|
1808
|
-
!value.startsWith("url(") // Unless it starts with "url("
|
|
1809
|
-
) {
|
|
1810
|
-
return true;
|
|
1811
|
-
}
|
|
1812
|
-
return false;
|
|
1813
|
-
};
|
|
1814
|
-
|
|
1815
|
-
function hasKeyframesChanged(keyframes) {
|
|
1816
|
-
const current = keyframes[0];
|
|
1817
|
-
if (keyframes.length === 1)
|
|
1818
|
-
return true;
|
|
1819
|
-
for (let i = 0; i < keyframes.length; i++) {
|
|
1820
|
-
if (keyframes[i] !== current)
|
|
1821
|
-
return true;
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
function canAnimate(keyframes, name, type, velocity) {
|
|
1825
|
-
/**
|
|
1826
|
-
* Check if we're able to animate between the start and end keyframes,
|
|
1827
|
-
* and throw a warning if we're attempting to animate between one that's
|
|
1828
|
-
* animatable and another that isn't.
|
|
1829
|
-
*/
|
|
1830
|
-
const originKeyframe = keyframes[0];
|
|
1831
|
-
if (originKeyframe === null)
|
|
1832
|
-
return false;
|
|
1833
|
-
/**
|
|
1834
|
-
* These aren't traditionally animatable but we do support them.
|
|
1835
|
-
* In future we could look into making this more generic or replacing
|
|
1836
|
-
* this function with mix() === mixImmediate
|
|
1837
|
-
*/
|
|
1838
|
-
if (name === "display" || name === "visibility")
|
|
1839
|
-
return true;
|
|
1840
|
-
const targetKeyframe = keyframes[keyframes.length - 1];
|
|
1841
|
-
const isOriginAnimatable = isAnimatable(originKeyframe, name);
|
|
1842
|
-
const isTargetAnimatable = isAnimatable(targetKeyframe, name);
|
|
1843
|
-
motionUtils.warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${name} from "${originKeyframe}" to "${targetKeyframe}". ${originKeyframe} is not an animatable value - to enable this animation set ${originKeyframe} to a value animatable to ${targetKeyframe} via the \`style\` property.`);
|
|
1844
|
-
// Always skip if any of these are true
|
|
1845
|
-
if (!isOriginAnimatable || !isTargetAnimatable) {
|
|
1846
|
-
return false;
|
|
1847
|
-
}
|
|
1848
|
-
return (hasKeyframesChanged(keyframes) ||
|
|
1849
|
-
((type === "spring" || motionDom.isGenerator(type)) && velocity));
|
|
1850
|
-
}
|
|
1851
|
-
|
|
1852
|
-
const isNotNull = (value) => value !== null;
|
|
1853
|
-
function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
|
|
1854
|
-
const resolvedKeyframes = keyframes.filter(isNotNull);
|
|
1855
|
-
const index = repeat && repeatType !== "loop" && repeat % 2 === 1
|
|
1856
|
-
? 0
|
|
1857
|
-
: resolvedKeyframes.length - 1;
|
|
1858
|
-
return !index || finalKeyframe === undefined
|
|
1859
|
-
? resolvedKeyframes[index]
|
|
1860
|
-
: finalKeyframe;
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
/**
|
|
1864
|
-
* Maximum time allowed between an animation being created and it being
|
|
1865
|
-
* resolved for us to use the latter as the start time.
|
|
1866
|
-
*
|
|
1867
|
-
* This is to ensure that while we prefer to "start" an animation as soon
|
|
1868
|
-
* as it's triggered, we also want to avoid a visual jump if there's a big delay
|
|
1869
|
-
* between these two moments.
|
|
1870
|
-
*/
|
|
1871
|
-
const MAX_RESOLVE_DELAY = 40;
|
|
1872
|
-
class BaseAnimation {
|
|
1873
|
-
constructor({ autoplay = true, delay = 0, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", ...options }) {
|
|
1874
|
-
// Track whether the animation has been stopped. Stopped animations won't restart.
|
|
1875
|
-
this.isStopped = false;
|
|
1876
|
-
this.hasAttemptedResolve = false;
|
|
1877
|
-
this.createdAt = motionDom.time.now();
|
|
1878
|
-
this.options = {
|
|
1879
|
-
autoplay,
|
|
1880
|
-
delay,
|
|
1881
|
-
type,
|
|
1882
|
-
repeat,
|
|
1883
|
-
repeatDelay,
|
|
1884
|
-
repeatType,
|
|
1885
|
-
...options,
|
|
1886
|
-
};
|
|
1887
|
-
this.updateFinishedPromise();
|
|
1888
|
-
}
|
|
1889
|
-
/**
|
|
1890
|
-
* This method uses the createdAt and resolvedAt to calculate the
|
|
1891
|
-
* animation startTime. *Ideally*, we would use the createdAt time as t=0
|
|
1892
|
-
* as the following frame would then be the first frame of the animation in
|
|
1893
|
-
* progress, which would feel snappier.
|
|
1894
|
-
*
|
|
1895
|
-
* However, if there's a delay (main thread work) between the creation of
|
|
1896
|
-
* the animation and the first commited frame, we prefer to use resolvedAt
|
|
1897
|
-
* to avoid a sudden jump into the animation.
|
|
1898
|
-
*/
|
|
1899
|
-
calcStartTime() {
|
|
1900
|
-
if (!this.resolvedAt)
|
|
1901
|
-
return this.createdAt;
|
|
1902
|
-
return this.resolvedAt - this.createdAt > MAX_RESOLVE_DELAY
|
|
1903
|
-
? this.resolvedAt
|
|
1904
|
-
: this.createdAt;
|
|
1905
|
-
}
|
|
1906
|
-
/**
|
|
1907
|
-
* A getter for resolved data. If keyframes are not yet resolved, accessing
|
|
1908
|
-
* this.resolved will synchronously flush all pending keyframe resolvers.
|
|
1909
|
-
* This is a deoptimisation, but at its worst still batches read/writes.
|
|
1910
|
-
*/
|
|
1911
|
-
get resolved() {
|
|
1912
|
-
if (!this._resolved && !this.hasAttemptedResolve) {
|
|
1913
|
-
flushKeyframeResolvers();
|
|
1914
|
-
}
|
|
1915
|
-
return this._resolved;
|
|
1916
|
-
}
|
|
1917
|
-
/**
|
|
1918
|
-
* A method to be called when the keyframes resolver completes. This method
|
|
1919
|
-
* will check if its possible to run the animation and, if not, skip it.
|
|
1920
|
-
* Otherwise, it will call initPlayback on the implementing class.
|
|
1921
|
-
*/
|
|
1922
|
-
onKeyframesResolved(keyframes, finalKeyframe) {
|
|
1923
|
-
this.resolvedAt = motionDom.time.now();
|
|
1924
|
-
this.hasAttemptedResolve = true;
|
|
1925
|
-
const { name, type, velocity, delay, onComplete, onUpdate, isGenerator, } = this.options;
|
|
1926
|
-
/**
|
|
1927
|
-
* If we can't animate this value with the resolved keyframes
|
|
1928
|
-
* then we should complete it immediately.
|
|
1929
|
-
*/
|
|
1930
|
-
if (!isGenerator && !canAnimate(keyframes, name, type, velocity)) {
|
|
1931
|
-
// Finish immediately
|
|
1932
|
-
if (!delay) {
|
|
1933
|
-
onUpdate &&
|
|
1934
|
-
onUpdate(getFinalKeyframe(keyframes, this.options, finalKeyframe));
|
|
1935
|
-
onComplete && onComplete();
|
|
1936
|
-
this.resolveFinishedPromise();
|
|
1937
|
-
return;
|
|
1938
|
-
}
|
|
1939
|
-
// Finish after a delay
|
|
1940
|
-
else {
|
|
1941
|
-
this.options.duration = 0;
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
const resolvedAnimation = this.initPlayback(keyframes, finalKeyframe);
|
|
1945
|
-
if (resolvedAnimation === false)
|
|
1946
|
-
return;
|
|
1947
|
-
this._resolved = {
|
|
1948
|
-
keyframes,
|
|
1949
|
-
finalKeyframe,
|
|
1950
|
-
...resolvedAnimation,
|
|
1951
|
-
};
|
|
1952
|
-
this.onPostResolved();
|
|
1953
|
-
}
|
|
1954
|
-
onPostResolved() { }
|
|
1955
|
-
/**
|
|
1956
|
-
* Allows the returned animation to be awaited or promise-chained. Currently
|
|
1957
|
-
* resolves when the animation finishes at all but in a future update could/should
|
|
1958
|
-
* reject if its cancels.
|
|
1959
|
-
*/
|
|
1960
|
-
then(resolve, reject) {
|
|
1961
|
-
return this.currentFinishedPromise.then(resolve, reject);
|
|
1962
|
-
}
|
|
1963
|
-
flatten() {
|
|
1964
|
-
if (!this.options.allowFlatten)
|
|
1965
|
-
return;
|
|
1966
|
-
this.options.type = "keyframes";
|
|
1967
|
-
this.options.ease = "linear";
|
|
1968
|
-
}
|
|
1969
|
-
updateFinishedPromise() {
|
|
1970
|
-
this.currentFinishedPromise = new Promise((resolve) => {
|
|
1971
|
-
this.resolveFinishedPromise = resolve;
|
|
1972
|
-
});
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
// Adapted from https://gist.github.com/mjackson/5311256
|
|
1977
|
-
function hueToRgb(p, q, t) {
|
|
1978
|
-
if (t < 0)
|
|
1979
|
-
t += 1;
|
|
1980
|
-
if (t > 1)
|
|
1981
|
-
t -= 1;
|
|
1982
|
-
if (t < 1 / 6)
|
|
1983
|
-
return p + (q - p) * 6 * t;
|
|
1984
|
-
if (t < 1 / 2)
|
|
1985
|
-
return q;
|
|
1986
|
-
if (t < 2 / 3)
|
|
1987
|
-
return p + (q - p) * (2 / 3 - t) * 6;
|
|
1988
|
-
return p;
|
|
1989
|
-
}
|
|
1990
|
-
function hslaToRgba({ hue, saturation, lightness, alpha }) {
|
|
1991
|
-
hue /= 360;
|
|
1992
|
-
saturation /= 100;
|
|
1993
|
-
lightness /= 100;
|
|
1994
|
-
let red = 0;
|
|
1995
|
-
let green = 0;
|
|
1996
|
-
let blue = 0;
|
|
1997
|
-
if (!saturation) {
|
|
1998
|
-
red = green = blue = lightness;
|
|
1999
|
-
}
|
|
2000
|
-
else {
|
|
2001
|
-
const q = lightness < 0.5
|
|
2002
|
-
? lightness * (1 + saturation)
|
|
2003
|
-
: lightness + saturation - lightness * saturation;
|
|
2004
|
-
const p = 2 * lightness - q;
|
|
2005
|
-
red = hueToRgb(p, q, hue + 1 / 3);
|
|
2006
|
-
green = hueToRgb(p, q, hue);
|
|
2007
|
-
blue = hueToRgb(p, q, hue - 1 / 3);
|
|
2008
|
-
}
|
|
2009
|
-
return {
|
|
2010
|
-
red: Math.round(red * 255),
|
|
2011
|
-
green: Math.round(green * 255),
|
|
2012
|
-
blue: Math.round(blue * 255),
|
|
2013
|
-
alpha,
|
|
2014
|
-
};
|
|
2015
|
-
}
|
|
2016
|
-
|
|
2017
|
-
function mixImmediate(a, b) {
|
|
2018
|
-
return (p) => (p > 0 ? b : a);
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2021
|
-
// Linear color space blending
|
|
2022
|
-
// Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
|
|
2023
|
-
// Demonstrated http://codepen.io/osublake/pen/xGVVaN
|
|
2024
|
-
const mixLinearColor = (from, to, v) => {
|
|
2025
|
-
const fromExpo = from * from;
|
|
2026
|
-
const expo = v * (to * to - fromExpo) + fromExpo;
|
|
2027
|
-
return expo < 0 ? 0 : Math.sqrt(expo);
|
|
2028
|
-
};
|
|
2029
|
-
const colorTypes = [hex, rgba, hsla];
|
|
2030
|
-
const getColorType = (v) => colorTypes.find((type) => type.test(v));
|
|
2031
|
-
function asRGBA(color) {
|
|
2032
|
-
const type = getColorType(color);
|
|
2033
|
-
motionUtils.warning(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
|
|
2034
|
-
if (!Boolean(type))
|
|
2035
|
-
return false;
|
|
2036
|
-
let model = type.parse(color);
|
|
2037
|
-
if (type === hsla) {
|
|
2038
|
-
// TODO Remove this cast - needed since Motion's stricter typing
|
|
2039
|
-
model = hslaToRgba(model);
|
|
2040
|
-
}
|
|
2041
|
-
return model;
|
|
2042
|
-
}
|
|
2043
|
-
const mixColor = (from, to) => {
|
|
2044
|
-
const fromRGBA = asRGBA(from);
|
|
2045
|
-
const toRGBA = asRGBA(to);
|
|
2046
|
-
if (!fromRGBA || !toRGBA) {
|
|
2047
|
-
return mixImmediate(from, to);
|
|
2048
|
-
}
|
|
2049
|
-
const blended = { ...fromRGBA };
|
|
2050
|
-
return (v) => {
|
|
2051
|
-
blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
|
|
2052
|
-
blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
|
|
2053
|
-
blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
|
|
2054
|
-
blended.alpha = mixNumber$1(fromRGBA.alpha, toRGBA.alpha, v);
|
|
2055
|
-
return rgba.transform(blended);
|
|
2056
|
-
};
|
|
2057
|
-
};
|
|
2058
|
-
|
|
2059
|
-
/**
|
|
2060
|
-
* Pipe
|
|
2061
|
-
* Compose other transformers to run linearily
|
|
2062
|
-
* pipe(min(20), max(40))
|
|
2063
|
-
* @param {...functions} transformers
|
|
2064
|
-
* @return {function}
|
|
2065
|
-
*/
|
|
2066
|
-
const combineFunctions = (a, b) => (v) => b(a(v));
|
|
2067
|
-
const pipe = (...transformers) => transformers.reduce(combineFunctions);
|
|
2068
|
-
|
|
2069
|
-
const invisibleValues = new Set(["none", "hidden"]);
|
|
2070
|
-
/**
|
|
2071
|
-
* Returns a function that, when provided a progress value between 0 and 1,
|
|
2072
|
-
* will return the "none" or "hidden" string only when the progress is that of
|
|
2073
|
-
* the origin or target.
|
|
2074
|
-
*/
|
|
2075
|
-
function mixVisibility(origin, target) {
|
|
2076
|
-
if (invisibleValues.has(origin)) {
|
|
2077
|
-
return (p) => (p <= 0 ? origin : target);
|
|
2078
|
-
}
|
|
2079
|
-
else {
|
|
2080
|
-
return (p) => (p >= 1 ? target : origin);
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
|
|
2084
|
-
function mixNumber(a, b) {
|
|
2085
|
-
return (p) => mixNumber$1(a, b, p);
|
|
2086
|
-
}
|
|
2087
|
-
function getMixer$1(a) {
|
|
2088
|
-
if (typeof a === "number") {
|
|
2089
|
-
return mixNumber;
|
|
2090
|
-
}
|
|
2091
|
-
else if (typeof a === "string") {
|
|
2092
|
-
return isCSSVariableToken(a)
|
|
2093
|
-
? mixImmediate
|
|
2094
|
-
: color.test(a)
|
|
2095
|
-
? mixColor
|
|
2096
|
-
: mixComplex;
|
|
2097
|
-
}
|
|
2098
|
-
else if (Array.isArray(a)) {
|
|
2099
|
-
return mixArray;
|
|
2100
|
-
}
|
|
2101
|
-
else if (typeof a === "object") {
|
|
2102
|
-
return color.test(a) ? mixColor : mixObject;
|
|
2103
|
-
}
|
|
2104
|
-
return mixImmediate;
|
|
2105
|
-
}
|
|
2106
|
-
function mixArray(a, b) {
|
|
2107
|
-
const output = [...a];
|
|
2108
|
-
const numValues = output.length;
|
|
2109
|
-
const blendValue = a.map((v, i) => getMixer$1(v)(v, b[i]));
|
|
2110
|
-
return (p) => {
|
|
2111
|
-
for (let i = 0; i < numValues; i++) {
|
|
2112
|
-
output[i] = blendValue[i](p);
|
|
2113
|
-
}
|
|
2114
|
-
return output;
|
|
2115
|
-
};
|
|
2116
|
-
}
|
|
2117
|
-
function mixObject(a, b) {
|
|
2118
|
-
const output = { ...a, ...b };
|
|
2119
|
-
const blendValue = {};
|
|
2120
|
-
for (const key in output) {
|
|
2121
|
-
if (a[key] !== undefined && b[key] !== undefined) {
|
|
2122
|
-
blendValue[key] = getMixer$1(a[key])(a[key], b[key]);
|
|
2123
|
-
}
|
|
2124
|
-
}
|
|
2125
|
-
return (v) => {
|
|
2126
|
-
for (const key in blendValue) {
|
|
2127
|
-
output[key] = blendValue[key](v);
|
|
2128
|
-
}
|
|
2129
|
-
return output;
|
|
2130
|
-
};
|
|
2131
|
-
}
|
|
2132
|
-
function matchOrder(origin, target) {
|
|
2133
|
-
const orderedOrigin = [];
|
|
2134
|
-
const pointers = { color: 0, var: 0, number: 0 };
|
|
2135
|
-
for (let i = 0; i < target.values.length; i++) {
|
|
2136
|
-
const type = target.types[i];
|
|
2137
|
-
const originIndex = origin.indexes[type][pointers[type]];
|
|
2138
|
-
const originValue = origin.values[originIndex] ?? 0;
|
|
2139
|
-
orderedOrigin[i] = originValue;
|
|
2140
|
-
pointers[type]++;
|
|
2141
|
-
}
|
|
2142
|
-
return orderedOrigin;
|
|
2143
|
-
}
|
|
2144
|
-
const mixComplex = (origin, target) => {
|
|
2145
|
-
const template = complex.createTransformer(target);
|
|
2146
|
-
const originStats = analyseComplexValue(origin);
|
|
2147
|
-
const targetStats = analyseComplexValue(target);
|
|
2148
|
-
const canInterpolate = originStats.indexes.var.length === targetStats.indexes.var.length &&
|
|
2149
|
-
originStats.indexes.color.length === targetStats.indexes.color.length &&
|
|
2150
|
-
originStats.indexes.number.length >= targetStats.indexes.number.length;
|
|
2151
|
-
if (canInterpolate) {
|
|
2152
|
-
if ((invisibleValues.has(origin) &&
|
|
2153
|
-
!targetStats.values.length) ||
|
|
2154
|
-
(invisibleValues.has(target) &&
|
|
2155
|
-
!originStats.values.length)) {
|
|
2156
|
-
return mixVisibility(origin, target);
|
|
2157
|
-
}
|
|
2158
|
-
return pipe(mixArray(matchOrder(originStats, targetStats), targetStats.values), template);
|
|
2159
|
-
}
|
|
2160
|
-
else {
|
|
2161
|
-
motionUtils.warning(true, `Complex values '${origin}' and '${target}' too different to mix. Ensure all colors are of the same type, and that each contains the same quantity of number and color values. Falling back to instant transition.`);
|
|
2162
|
-
return mixImmediate(origin, target);
|
|
2163
|
-
}
|
|
2164
|
-
};
|
|
2165
|
-
|
|
2166
|
-
function mix(from, to, p) {
|
|
2167
|
-
if (typeof from === "number" &&
|
|
2168
|
-
typeof to === "number" &&
|
|
2169
|
-
typeof p === "number") {
|
|
2170
|
-
return mixNumber$1(from, to, p);
|
|
2171
|
-
}
|
|
2172
|
-
const mixer = getMixer$1(from);
|
|
2173
|
-
return mixer(from, to);
|
|
2174
|
-
}
|
|
2175
|
-
|
|
2176
|
-
function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
|
|
2177
|
-
const origin = keyframes[0];
|
|
2178
|
-
const state = {
|
|
2179
|
-
done: false,
|
|
2180
|
-
value: origin,
|
|
2181
|
-
};
|
|
2182
|
-
const isOutOfBounds = (v) => (min !== undefined && v < min) || (max !== undefined && v > max);
|
|
2183
|
-
const nearestBoundary = (v) => {
|
|
2184
|
-
if (min === undefined)
|
|
2185
|
-
return max;
|
|
2186
|
-
if (max === undefined)
|
|
2187
|
-
return min;
|
|
2188
|
-
return Math.abs(min - v) < Math.abs(max - v) ? min : max;
|
|
2189
|
-
};
|
|
2190
|
-
let amplitude = power * velocity;
|
|
2191
|
-
const ideal = origin + amplitude;
|
|
2192
|
-
const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
|
|
2193
|
-
/**
|
|
2194
|
-
* If the target has changed we need to re-calculate the amplitude, otherwise
|
|
2195
|
-
* the animation will start from the wrong position.
|
|
2196
|
-
*/
|
|
2197
|
-
if (target !== ideal)
|
|
2198
|
-
amplitude = target - origin;
|
|
2199
|
-
const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant);
|
|
2200
|
-
const calcLatest = (t) => target + calcDelta(t);
|
|
2201
|
-
const applyFriction = (t) => {
|
|
2202
|
-
const delta = calcDelta(t);
|
|
2203
|
-
const latest = calcLatest(t);
|
|
2204
|
-
state.done = Math.abs(delta) <= restDelta;
|
|
2205
|
-
state.value = state.done ? target : latest;
|
|
2206
|
-
};
|
|
2207
|
-
/**
|
|
2208
|
-
* Ideally this would resolve for t in a stateless way, we could
|
|
2209
|
-
* do that by always precalculating the animation but as we know
|
|
2210
|
-
* this will be done anyway we can assume that spring will
|
|
2211
|
-
* be discovered during that.
|
|
2212
|
-
*/
|
|
2213
|
-
let timeReachedBoundary;
|
|
2214
|
-
let spring$1;
|
|
2215
|
-
const checkCatchBoundary = (t) => {
|
|
2216
|
-
if (!isOutOfBounds(state.value))
|
|
2217
|
-
return;
|
|
2218
|
-
timeReachedBoundary = t;
|
|
2219
|
-
spring$1 = spring({
|
|
2220
|
-
keyframes: [state.value, nearestBoundary(state.value)],
|
|
2221
|
-
velocity: calcGeneratorVelocity(calcLatest, t, state.value), // TODO: This should be passing * 1000
|
|
2222
|
-
damping: bounceDamping,
|
|
2223
|
-
stiffness: bounceStiffness,
|
|
2224
|
-
restDelta,
|
|
2225
|
-
restSpeed,
|
|
2226
|
-
});
|
|
2227
|
-
};
|
|
2228
|
-
checkCatchBoundary(0);
|
|
2229
|
-
return {
|
|
2230
|
-
calculatedDuration: null,
|
|
2231
|
-
next: (t) => {
|
|
2232
|
-
/**
|
|
2233
|
-
* We need to resolve the friction to figure out if we need a
|
|
2234
|
-
* spring but we don't want to do this twice per frame. So here
|
|
2235
|
-
* we flag if we updated for this frame and later if we did
|
|
2236
|
-
* we can skip doing it again.
|
|
2237
|
-
*/
|
|
2238
|
-
let hasUpdatedFrame = false;
|
|
2239
|
-
if (!spring$1 && timeReachedBoundary === undefined) {
|
|
2240
|
-
hasUpdatedFrame = true;
|
|
2241
|
-
applyFriction(t);
|
|
2242
|
-
checkCatchBoundary(t);
|
|
2243
|
-
}
|
|
2244
|
-
/**
|
|
2245
|
-
* If we have a spring and the provided t is beyond the moment the friction
|
|
2246
|
-
* animation crossed the min/max boundary, use the spring.
|
|
2247
|
-
*/
|
|
2248
|
-
if (timeReachedBoundary !== undefined && t >= timeReachedBoundary) {
|
|
2249
|
-
return spring$1.next(t - timeReachedBoundary);
|
|
2250
|
-
}
|
|
2251
|
-
else {
|
|
2252
|
-
!hasUpdatedFrame && applyFriction(t);
|
|
2253
|
-
return state;
|
|
2254
|
-
}
|
|
2255
|
-
},
|
|
2256
|
-
};
|
|
2257
|
-
}
|
|
2258
|
-
|
|
2259
|
-
const easeIn = /*@__PURE__*/ cubicBezier(0.42, 0, 1, 1);
|
|
2260
|
-
const easeOut = /*@__PURE__*/ cubicBezier(0, 0, 0.58, 1);
|
|
2261
|
-
const easeInOut = /*@__PURE__*/ cubicBezier(0.42, 0, 0.58, 1);
|
|
2262
|
-
|
|
2263
|
-
const easingLookup = {
|
|
2264
|
-
linear: motionUtils.noop,
|
|
2265
|
-
easeIn,
|
|
2266
|
-
easeInOut,
|
|
2267
|
-
easeOut,
|
|
2268
|
-
circIn,
|
|
2269
|
-
circInOut,
|
|
2270
|
-
circOut,
|
|
2271
|
-
backIn,
|
|
2272
|
-
backInOut,
|
|
2273
|
-
backOut,
|
|
2274
|
-
anticipate,
|
|
2275
|
-
};
|
|
2276
|
-
const easingDefinitionToFunction = (definition) => {
|
|
2277
|
-
if (motionDom.isBezierDefinition(definition)) {
|
|
2278
|
-
// If cubic bezier definition, create bezier curve
|
|
2279
|
-
motionUtils.invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
|
|
2280
|
-
const [x1, y1, x2, y2] = definition;
|
|
2281
|
-
return cubicBezier(x1, y1, x2, y2);
|
|
2282
|
-
}
|
|
2283
|
-
else if (typeof definition === "string") {
|
|
2284
|
-
// Else lookup from table
|
|
2285
|
-
motionUtils.invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
|
|
2286
|
-
return easingLookup[definition];
|
|
2287
|
-
}
|
|
2288
|
-
return definition;
|
|
2289
|
-
};
|
|
2290
|
-
|
|
2291
|
-
function createMixers(output, ease, customMixer) {
|
|
2292
|
-
const mixers = [];
|
|
2293
|
-
const mixerFactory = customMixer || mix;
|
|
2294
|
-
const numMixers = output.length - 1;
|
|
2295
|
-
for (let i = 0; i < numMixers; i++) {
|
|
2296
|
-
let mixer = mixerFactory(output[i], output[i + 1]);
|
|
2297
|
-
if (ease) {
|
|
2298
|
-
const easingFunction = Array.isArray(ease) ? ease[i] || motionUtils.noop : ease;
|
|
2299
|
-
mixer = pipe(easingFunction, mixer);
|
|
2300
|
-
}
|
|
2301
|
-
mixers.push(mixer);
|
|
2302
|
-
}
|
|
2303
|
-
return mixers;
|
|
2304
|
-
}
|
|
2305
|
-
/**
|
|
2306
|
-
* Create a function that maps from a numerical input array to a generic output array.
|
|
2307
|
-
*
|
|
2308
|
-
* Accepts:
|
|
2309
|
-
* - Numbers
|
|
2310
|
-
* - Colors (hex, hsl, hsla, rgb, rgba)
|
|
2311
|
-
* - Complex (combinations of one or more numbers or strings)
|
|
2312
|
-
*
|
|
2313
|
-
* ```jsx
|
|
2314
|
-
* const mixColor = interpolate([0, 1], ['#fff', '#000'])
|
|
2315
|
-
*
|
|
2316
|
-
* mixColor(0.5) // 'rgba(128, 128, 128, 1)'
|
|
2317
|
-
* ```
|
|
2318
|
-
*
|
|
2319
|
-
* TODO Revist this approach once we've moved to data models for values,
|
|
2320
|
-
* probably not needed to pregenerate mixer functions.
|
|
2321
|
-
*
|
|
2322
|
-
* @public
|
|
2323
|
-
*/
|
|
2324
|
-
function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
|
|
2325
|
-
const inputLength = input.length;
|
|
2326
|
-
motionUtils.invariant(inputLength === output.length, "Both input and output ranges must be the same length");
|
|
2327
|
-
/**
|
|
2328
|
-
* If we're only provided a single input, we can just make a function
|
|
2329
|
-
* that returns the output.
|
|
2330
|
-
*/
|
|
2331
|
-
if (inputLength === 1)
|
|
2332
|
-
return () => output[0];
|
|
2333
|
-
if (inputLength === 2 && output[0] === output[1])
|
|
2334
|
-
return () => output[1];
|
|
2335
|
-
const isZeroDeltaRange = input[0] === input[1];
|
|
2336
|
-
// If input runs highest -> lowest, reverse both arrays
|
|
2337
|
-
if (input[0] > input[inputLength - 1]) {
|
|
2338
|
-
input = [...input].reverse();
|
|
2339
|
-
output = [...output].reverse();
|
|
2340
|
-
}
|
|
2341
|
-
const mixers = createMixers(output, ease, mixer);
|
|
2342
|
-
const numMixers = mixers.length;
|
|
2343
|
-
const interpolator = (v) => {
|
|
2344
|
-
if (isZeroDeltaRange && v < input[0])
|
|
2345
|
-
return output[0];
|
|
2346
|
-
let i = 0;
|
|
2347
|
-
if (numMixers > 1) {
|
|
2348
|
-
for (; i < input.length - 2; i++) {
|
|
2349
|
-
if (v < input[i + 1])
|
|
2350
|
-
break;
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
const progressInRange = motionUtils.progress(input[i], input[i + 1], v);
|
|
2354
|
-
return mixers[i](progressInRange);
|
|
2355
|
-
};
|
|
2356
|
-
return isClamp
|
|
2357
|
-
? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
|
|
2358
|
-
: interpolator;
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
function convertOffsetToTimes(offset, duration) {
|
|
2362
|
-
return offset.map((o) => o * duration);
|
|
2363
|
-
}
|
|
2364
|
-
|
|
2365
|
-
function defaultEasing(values, easing) {
|
|
2366
|
-
return values.map(() => easing || easeInOut).splice(0, values.length - 1);
|
|
2367
|
-
}
|
|
2368
|
-
function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "easeInOut", }) {
|
|
2369
|
-
/**
|
|
2370
|
-
* Easing functions can be externally defined as strings. Here we convert them
|
|
2371
|
-
* into actual functions.
|
|
2372
|
-
*/
|
|
2373
|
-
const easingFunctions = isEasingArray(ease)
|
|
2374
|
-
? ease.map(easingDefinitionToFunction)
|
|
2375
|
-
: easingDefinitionToFunction(ease);
|
|
2376
|
-
/**
|
|
2377
|
-
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
2378
|
-
* to reduce GC during animation.
|
|
2379
|
-
*/
|
|
2380
|
-
const state = {
|
|
2381
|
-
done: false,
|
|
2382
|
-
value: keyframeValues[0],
|
|
2383
|
-
};
|
|
2384
|
-
/**
|
|
2385
|
-
* Create a times array based on the provided 0-1 offsets
|
|
2386
|
-
*/
|
|
2387
|
-
const absoluteTimes = convertOffsetToTimes(
|
|
2388
|
-
// Only use the provided offsets if they're the correct length
|
|
2389
|
-
// TODO Maybe we should warn here if there's a length mismatch
|
|
2390
|
-
times && times.length === keyframeValues.length
|
|
2391
|
-
? times
|
|
2392
|
-
: defaultOffset$1(keyframeValues), duration);
|
|
2393
|
-
const mapTimeToKeyframe = interpolate(absoluteTimes, keyframeValues, {
|
|
2394
|
-
ease: Array.isArray(easingFunctions)
|
|
2395
|
-
? easingFunctions
|
|
2396
|
-
: defaultEasing(keyframeValues, easingFunctions),
|
|
2397
|
-
});
|
|
2398
|
-
return {
|
|
2399
|
-
calculatedDuration: duration,
|
|
2400
|
-
next: (t) => {
|
|
2401
|
-
state.value = mapTimeToKeyframe(t);
|
|
2402
|
-
state.done = t >= duration;
|
|
2403
|
-
return state;
|
|
2404
|
-
},
|
|
2405
|
-
};
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
|
-
const frameloopDriver = (update) => {
|
|
2409
|
-
const passTimestamp = ({ timestamp }) => update(timestamp);
|
|
2410
|
-
return {
|
|
2411
|
-
start: () => motionDom.frame.update(passTimestamp, true),
|
|
2412
|
-
stop: () => motionDom.cancelFrame(passTimestamp),
|
|
2413
|
-
/**
|
|
2414
|
-
* If we're processing this frame we can use the
|
|
2415
|
-
* framelocked timestamp to keep things in sync.
|
|
2416
|
-
*/
|
|
2417
|
-
now: () => (motionDom.frameData.isProcessing ? motionDom.frameData.timestamp : motionDom.time.now()),
|
|
2418
|
-
};
|
|
2419
|
-
};
|
|
2420
|
-
|
|
2421
|
-
const generators = {
|
|
2422
|
-
decay: inertia,
|
|
2423
|
-
inertia,
|
|
2424
|
-
tween: keyframes,
|
|
2425
|
-
keyframes: keyframes,
|
|
2426
|
-
spring,
|
|
2427
|
-
};
|
|
2428
|
-
const percentToProgress = (percent) => percent / 100;
|
|
2429
|
-
/**
|
|
2430
|
-
* Animation that runs on the main thread. Designed to be WAAPI-spec in the subset of
|
|
2431
|
-
* features we expose publically. Mostly the compatibility is to ensure visual identity
|
|
2432
|
-
* between both WAAPI and main thread animations.
|
|
2433
|
-
*/
|
|
2434
|
-
class MainThreadAnimation extends BaseAnimation {
|
|
2435
|
-
constructor(options) {
|
|
2436
|
-
super(options);
|
|
2437
|
-
/**
|
|
2438
|
-
* The time at which the animation was paused.
|
|
2439
|
-
*/
|
|
2440
|
-
this.holdTime = null;
|
|
2441
|
-
/**
|
|
2442
|
-
* The time at which the animation was cancelled.
|
|
2443
|
-
*/
|
|
2444
|
-
this.cancelTime = null;
|
|
2445
|
-
/**
|
|
2446
|
-
* The current time of the animation.
|
|
2447
|
-
*/
|
|
2448
|
-
this.currentTime = 0;
|
|
2449
|
-
/**
|
|
2450
|
-
* Playback speed as a factor. 0 would be stopped, -1 reverse and 2 double speed.
|
|
2451
|
-
*/
|
|
2452
|
-
this.playbackSpeed = 1;
|
|
2453
|
-
/**
|
|
2454
|
-
* The state of the animation to apply when the animation is resolved. This
|
|
2455
|
-
* allows calls to the public API to control the animation before it is resolved,
|
|
2456
|
-
* without us having to resolve it first.
|
|
2457
|
-
*/
|
|
2458
|
-
this.pendingPlayState = "running";
|
|
2459
|
-
/**
|
|
2460
|
-
* The time at which the animation was started.
|
|
2461
|
-
*/
|
|
2462
|
-
this.startTime = null;
|
|
2463
|
-
this.state = "idle";
|
|
2464
|
-
/**
|
|
2465
|
-
* This method is bound to the instance to fix a pattern where
|
|
2466
|
-
* animation.stop is returned as a reference from a useEffect.
|
|
2467
|
-
*/
|
|
2468
|
-
this.stop = () => {
|
|
2469
|
-
this.resolver.cancel();
|
|
2470
|
-
this.isStopped = true;
|
|
2471
|
-
if (this.state === "idle")
|
|
2472
|
-
return;
|
|
2473
|
-
this.teardown();
|
|
2474
|
-
const { onStop } = this.options;
|
|
2475
|
-
onStop && onStop();
|
|
2476
|
-
};
|
|
2477
|
-
const { name, motionValue, element, keyframes } = this.options;
|
|
2478
|
-
const KeyframeResolver$1 = element?.KeyframeResolver || KeyframeResolver;
|
|
2479
|
-
const onResolved = (resolvedKeyframes, finalKeyframe) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe);
|
|
2480
|
-
this.resolver = new KeyframeResolver$1(keyframes, onResolved, name, motionValue, element);
|
|
2481
|
-
this.resolver.scheduleResolve();
|
|
2482
|
-
}
|
|
2483
|
-
flatten() {
|
|
2484
|
-
super.flatten();
|
|
2485
|
-
// If we've already resolved the animation, re-initialise it
|
|
2486
|
-
if (this._resolved) {
|
|
2487
|
-
Object.assign(this._resolved, this.initPlayback(this._resolved.keyframes));
|
|
2488
|
-
}
|
|
2489
|
-
}
|
|
2490
|
-
initPlayback(keyframes$1) {
|
|
2491
|
-
const { type = "keyframes", repeat = 0, repeatDelay = 0, repeatType, velocity = 0, } = this.options;
|
|
2492
|
-
const generatorFactory = motionDom.isGenerator(type)
|
|
2493
|
-
? type
|
|
2494
|
-
: generators[type] || keyframes;
|
|
2495
|
-
/**
|
|
2496
|
-
* If our generator doesn't support mixing numbers, we need to replace keyframes with
|
|
2497
|
-
* [0, 100] and then make a function that maps that to the actual keyframes.
|
|
2498
|
-
*
|
|
2499
|
-
* 100 is chosen instead of 1 as it works nicer with spring animations.
|
|
2500
|
-
*/
|
|
2501
|
-
let mapPercentToKeyframes;
|
|
2502
|
-
let mirroredGenerator;
|
|
2503
|
-
if (process.env.NODE_ENV !== "production" &&
|
|
2504
|
-
generatorFactory !== keyframes) {
|
|
2505
|
-
motionUtils.invariant(keyframes$1.length <= 2, `Only two keyframes currently supported with spring and inertia animations. Trying to animate ${keyframes$1}`);
|
|
2506
|
-
}
|
|
2507
|
-
if (generatorFactory !== keyframes &&
|
|
2508
|
-
typeof keyframes$1[0] !== "number") {
|
|
2509
|
-
mapPercentToKeyframes = pipe(percentToProgress, mix(keyframes$1[0], keyframes$1[1]));
|
|
2510
|
-
keyframes$1 = [0, 100];
|
|
2511
|
-
}
|
|
2512
|
-
const generator = generatorFactory({ ...this.options, keyframes: keyframes$1 });
|
|
2513
|
-
/**
|
|
2514
|
-
* If we have a mirror repeat type we need to create a second generator that outputs the
|
|
2515
|
-
* mirrored (not reversed) animation and later ping pong between the two generators.
|
|
2516
|
-
*/
|
|
2517
|
-
if (repeatType === "mirror") {
|
|
2518
|
-
mirroredGenerator = generatorFactory({
|
|
2519
|
-
...this.options,
|
|
2520
|
-
keyframes: [...keyframes$1].reverse(),
|
|
2521
|
-
velocity: -velocity,
|
|
2522
|
-
});
|
|
2523
|
-
}
|
|
2524
|
-
/**
|
|
2525
|
-
* If duration is undefined and we have repeat options,
|
|
2526
|
-
* we need to calculate a duration from the generator.
|
|
2527
|
-
*
|
|
2528
|
-
* We set it to the generator itself to cache the duration.
|
|
2529
|
-
* Any timeline resolver will need to have already precalculated
|
|
2530
|
-
* the duration by this step.
|
|
2531
|
-
*/
|
|
2532
|
-
if (generator.calculatedDuration === null) {
|
|
2533
|
-
generator.calculatedDuration = motionDom.calcGeneratorDuration(generator);
|
|
2534
|
-
}
|
|
2535
|
-
const { calculatedDuration } = generator;
|
|
2536
|
-
const resolvedDuration = calculatedDuration + repeatDelay;
|
|
2537
|
-
const totalDuration = resolvedDuration * (repeat + 1) - repeatDelay;
|
|
2538
|
-
return {
|
|
2539
|
-
generator,
|
|
2540
|
-
mirroredGenerator,
|
|
2541
|
-
mapPercentToKeyframes,
|
|
2542
|
-
calculatedDuration,
|
|
2543
|
-
resolvedDuration,
|
|
2544
|
-
totalDuration,
|
|
2545
|
-
};
|
|
2546
|
-
}
|
|
2547
|
-
onPostResolved() {
|
|
2548
|
-
const { autoplay = true } = this.options;
|
|
2549
|
-
motionDom.activeAnimations.mainThread++;
|
|
2550
|
-
this.play();
|
|
2551
|
-
if (this.pendingPlayState === "paused" || !autoplay) {
|
|
2552
|
-
this.pause();
|
|
2553
|
-
}
|
|
2554
|
-
else {
|
|
2555
|
-
this.state = this.pendingPlayState;
|
|
2556
|
-
}
|
|
2557
|
-
}
|
|
2558
|
-
tick(timestamp, sample = false) {
|
|
2559
|
-
const { resolved } = this;
|
|
2560
|
-
// If the animations has failed to resolve, return the final keyframe.
|
|
2561
|
-
if (!resolved) {
|
|
2562
|
-
const { keyframes } = this.options;
|
|
2563
|
-
return { done: true, value: keyframes[keyframes.length - 1] };
|
|
2564
|
-
}
|
|
2565
|
-
const { finalKeyframe, generator, mirroredGenerator, mapPercentToKeyframes, keyframes, calculatedDuration, totalDuration, resolvedDuration, } = resolved;
|
|
2566
|
-
if (this.startTime === null)
|
|
2567
|
-
return generator.next(0);
|
|
2568
|
-
const { delay, repeat, repeatType, repeatDelay, onUpdate } = this.options;
|
|
2569
|
-
/**
|
|
2570
|
-
* requestAnimationFrame timestamps can come through as lower than
|
|
2571
|
-
* the startTime as set by performance.now(). Here we prevent this,
|
|
2572
|
-
* though in the future it could be possible to make setting startTime
|
|
2573
|
-
* a pending operation that gets resolved here.
|
|
2574
|
-
*/
|
|
2575
|
-
if (this.speed > 0) {
|
|
2576
|
-
this.startTime = Math.min(this.startTime, timestamp);
|
|
2577
|
-
}
|
|
2578
|
-
else if (this.speed < 0) {
|
|
2579
|
-
this.startTime = Math.min(timestamp - totalDuration / this.speed, this.startTime);
|
|
2580
|
-
}
|
|
2581
|
-
// Update currentTime
|
|
2582
|
-
if (sample) {
|
|
2583
|
-
this.currentTime = timestamp;
|
|
2584
|
-
}
|
|
2585
|
-
else if (this.holdTime !== null) {
|
|
2586
|
-
this.currentTime = this.holdTime;
|
|
2587
|
-
}
|
|
2588
|
-
else {
|
|
2589
|
-
// Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
|
|
2590
|
-
// 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
|
|
2591
|
-
// example.
|
|
2592
|
-
this.currentTime =
|
|
2593
|
-
Math.round(timestamp - this.startTime) * this.speed;
|
|
2594
|
-
}
|
|
2595
|
-
// Rebase on delay
|
|
2596
|
-
const timeWithoutDelay = this.currentTime - delay * (this.speed >= 0 ? 1 : -1);
|
|
2597
|
-
const isInDelayPhase = this.speed >= 0
|
|
2598
|
-
? timeWithoutDelay < 0
|
|
2599
|
-
: timeWithoutDelay > totalDuration;
|
|
2600
|
-
this.currentTime = Math.max(timeWithoutDelay, 0);
|
|
2601
|
-
// If this animation has finished, set the current time to the total duration.
|
|
2602
|
-
if (this.state === "finished" && this.holdTime === null) {
|
|
2603
|
-
this.currentTime = totalDuration;
|
|
2604
|
-
}
|
|
2605
|
-
let elapsed = this.currentTime;
|
|
2606
|
-
let frameGenerator = generator;
|
|
2607
|
-
if (repeat) {
|
|
2608
|
-
/**
|
|
2609
|
-
* Get the current progress (0-1) of the animation. If t is >
|
|
2610
|
-
* than duration we'll get values like 2.5 (midway through the
|
|
2611
|
-
* third iteration)
|
|
2612
|
-
*/
|
|
2613
|
-
const progress = Math.min(this.currentTime, totalDuration) / resolvedDuration;
|
|
2614
|
-
/**
|
|
2615
|
-
* Get the current iteration (0 indexed). For instance the floor of
|
|
2616
|
-
* 2.5 is 2.
|
|
2617
|
-
*/
|
|
2618
|
-
let currentIteration = Math.floor(progress);
|
|
2619
|
-
/**
|
|
2620
|
-
* Get the current progress of the iteration by taking the remainder
|
|
2621
|
-
* so 2.5 is 0.5 through iteration 2
|
|
2622
|
-
*/
|
|
2623
|
-
let iterationProgress = progress % 1.0;
|
|
2624
|
-
/**
|
|
2625
|
-
* If iteration progress is 1 we count that as the end
|
|
2626
|
-
* of the previous iteration.
|
|
2627
|
-
*/
|
|
2628
|
-
if (!iterationProgress && progress >= 1) {
|
|
2629
|
-
iterationProgress = 1;
|
|
2630
|
-
}
|
|
2631
|
-
iterationProgress === 1 && currentIteration--;
|
|
2632
|
-
currentIteration = Math.min(currentIteration, repeat + 1);
|
|
2633
|
-
/**
|
|
2634
|
-
* Reverse progress if we're not running in "normal" direction
|
|
2635
|
-
*/
|
|
2636
|
-
const isOddIteration = Boolean(currentIteration % 2);
|
|
2637
|
-
if (isOddIteration) {
|
|
2638
|
-
if (repeatType === "reverse") {
|
|
2639
|
-
iterationProgress = 1 - iterationProgress;
|
|
2640
|
-
if (repeatDelay) {
|
|
2641
|
-
iterationProgress -= repeatDelay / resolvedDuration;
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
|
-
else if (repeatType === "mirror") {
|
|
2645
|
-
frameGenerator = mirroredGenerator;
|
|
2646
|
-
}
|
|
2647
|
-
}
|
|
2648
|
-
elapsed = clamp(0, 1, iterationProgress) * resolvedDuration;
|
|
2649
|
-
}
|
|
2650
|
-
/**
|
|
2651
|
-
* If we're in negative time, set state as the initial keyframe.
|
|
2652
|
-
* This prevents delay: x, duration: 0 animations from finishing
|
|
2653
|
-
* instantly.
|
|
2654
|
-
*/
|
|
2655
|
-
const state = isInDelayPhase
|
|
2656
|
-
? { done: false, value: keyframes[0] }
|
|
2657
|
-
: frameGenerator.next(elapsed);
|
|
2658
|
-
if (mapPercentToKeyframes) {
|
|
2659
|
-
state.value = mapPercentToKeyframes(state.value);
|
|
2660
|
-
}
|
|
2661
|
-
let { done } = state;
|
|
2662
|
-
if (!isInDelayPhase && calculatedDuration !== null) {
|
|
2663
|
-
done =
|
|
2664
|
-
this.speed >= 0
|
|
2665
|
-
? this.currentTime >= totalDuration
|
|
2666
|
-
: this.currentTime <= 0;
|
|
2667
|
-
}
|
|
2668
|
-
const isAnimationFinished = this.holdTime === null &&
|
|
2669
|
-
(this.state === "finished" || (this.state === "running" && done));
|
|
2670
|
-
if (isAnimationFinished && finalKeyframe !== undefined) {
|
|
2671
|
-
state.value = getFinalKeyframe(keyframes, this.options, finalKeyframe);
|
|
2672
|
-
}
|
|
2673
|
-
if (onUpdate) {
|
|
2674
|
-
onUpdate(state.value);
|
|
2675
|
-
}
|
|
2676
|
-
if (isAnimationFinished) {
|
|
2677
|
-
this.finish();
|
|
2678
|
-
}
|
|
2679
|
-
return state;
|
|
2680
|
-
}
|
|
2681
|
-
get duration() {
|
|
2682
|
-
const { resolved } = this;
|
|
2683
|
-
return resolved ? motionUtils.millisecondsToSeconds(resolved.calculatedDuration) : 0;
|
|
2684
|
-
}
|
|
2685
|
-
get time() {
|
|
2686
|
-
return motionUtils.millisecondsToSeconds(this.currentTime);
|
|
2687
|
-
}
|
|
2688
|
-
set time(newTime) {
|
|
2689
|
-
newTime = motionUtils.secondsToMilliseconds(newTime);
|
|
2690
|
-
this.currentTime = newTime;
|
|
2691
|
-
if (this.holdTime !== null || this.speed === 0) {
|
|
2692
|
-
this.holdTime = newTime;
|
|
2693
|
-
}
|
|
2694
|
-
else if (this.driver) {
|
|
2695
|
-
this.startTime = this.driver.now() - newTime / this.speed;
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
get speed() {
|
|
2699
|
-
return this.playbackSpeed;
|
|
2700
|
-
}
|
|
2701
|
-
set speed(newSpeed) {
|
|
2702
|
-
const hasChanged = this.playbackSpeed !== newSpeed;
|
|
2703
|
-
this.playbackSpeed = newSpeed;
|
|
2704
|
-
if (hasChanged) {
|
|
2705
|
-
this.time = motionUtils.millisecondsToSeconds(this.currentTime);
|
|
2706
|
-
}
|
|
2707
|
-
}
|
|
2708
|
-
play() {
|
|
2709
|
-
if (!this.resolver.isScheduled) {
|
|
2710
|
-
this.resolver.resume();
|
|
2711
|
-
}
|
|
2712
|
-
if (!this._resolved) {
|
|
2713
|
-
this.pendingPlayState = "running";
|
|
2714
|
-
return;
|
|
2715
|
-
}
|
|
2716
|
-
if (this.isStopped)
|
|
2717
|
-
return;
|
|
2718
|
-
const { driver = frameloopDriver, onPlay, startTime } = this.options;
|
|
2719
|
-
if (!this.driver) {
|
|
2720
|
-
this.driver = driver((timestamp) => this.tick(timestamp));
|
|
2721
|
-
}
|
|
2722
|
-
onPlay && onPlay();
|
|
2723
|
-
const now = this.driver.now();
|
|
2724
|
-
if (this.holdTime !== null) {
|
|
2725
|
-
this.startTime = now - this.holdTime;
|
|
2726
|
-
}
|
|
2727
|
-
else if (!this.startTime) {
|
|
2728
|
-
this.startTime = startTime ?? this.calcStartTime();
|
|
2729
|
-
}
|
|
2730
|
-
else if (this.state === "finished") {
|
|
2731
|
-
this.startTime = now;
|
|
2732
|
-
}
|
|
2733
|
-
if (this.state === "finished") {
|
|
2734
|
-
this.updateFinishedPromise();
|
|
2735
|
-
}
|
|
2736
|
-
this.cancelTime = this.startTime;
|
|
2737
|
-
this.holdTime = null;
|
|
2738
|
-
/**
|
|
2739
|
-
* Set playState to running only after we've used it in
|
|
2740
|
-
* the previous logic.
|
|
2741
|
-
*/
|
|
2742
|
-
this.state = "running";
|
|
2743
|
-
this.driver.start();
|
|
2744
|
-
}
|
|
2745
|
-
pause() {
|
|
2746
|
-
if (!this._resolved) {
|
|
2747
|
-
this.pendingPlayState = "paused";
|
|
2748
|
-
return;
|
|
2749
|
-
}
|
|
2750
|
-
this.state = "paused";
|
|
2751
|
-
this.holdTime = this.currentTime ?? 0;
|
|
2752
|
-
}
|
|
2753
|
-
complete() {
|
|
2754
|
-
if (this.state !== "running") {
|
|
2755
|
-
this.play();
|
|
2756
|
-
}
|
|
2757
|
-
this.pendingPlayState = this.state = "finished";
|
|
2758
|
-
this.holdTime = null;
|
|
2759
|
-
}
|
|
2760
|
-
finish() {
|
|
2761
|
-
this.teardown();
|
|
2762
|
-
this.state = "finished";
|
|
2763
|
-
const { onComplete } = this.options;
|
|
2764
|
-
onComplete && onComplete();
|
|
2765
|
-
}
|
|
2766
|
-
cancel() {
|
|
2767
|
-
if (this.cancelTime !== null) {
|
|
2768
|
-
this.tick(this.cancelTime);
|
|
2769
|
-
}
|
|
2770
|
-
this.teardown();
|
|
2771
|
-
this.updateFinishedPromise();
|
|
2772
|
-
}
|
|
2773
|
-
teardown() {
|
|
2774
|
-
this.state = "idle";
|
|
2775
|
-
this.stopDriver();
|
|
2776
|
-
this.resolveFinishedPromise();
|
|
2777
|
-
this.updateFinishedPromise();
|
|
2778
|
-
this.startTime = this.cancelTime = null;
|
|
2779
|
-
this.resolver.cancel();
|
|
2780
|
-
motionDom.activeAnimations.mainThread--;
|
|
2781
|
-
}
|
|
2782
|
-
stopDriver() {
|
|
2783
|
-
if (!this.driver)
|
|
2784
|
-
return;
|
|
2785
|
-
this.driver.stop();
|
|
2786
|
-
this.driver = undefined;
|
|
2787
|
-
}
|
|
2788
|
-
sample(time) {
|
|
2789
|
-
this.startTime = 0;
|
|
2790
|
-
return this.tick(time, true);
|
|
2791
|
-
}
|
|
2792
|
-
get finished() {
|
|
2793
|
-
return this.currentFinishedPromise;
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
340
|
+
const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
|
|
2796
341
|
|
|
2797
|
-
|
|
2798
|
-
* A list of values that can be hardware-accelerated.
|
|
2799
|
-
*/
|
|
2800
|
-
const acceleratedValues = new Set([
|
|
2801
|
-
"opacity",
|
|
2802
|
-
"clipPath",
|
|
2803
|
-
"filter",
|
|
2804
|
-
"transform",
|
|
2805
|
-
// TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved
|
|
2806
|
-
// or until we implement support for linear() easing.
|
|
2807
|
-
// "background-color"
|
|
2808
|
-
]);
|
|
342
|
+
const visualElementStore = new WeakMap();
|
|
2809
343
|
|
|
2810
|
-
const
|
|
344
|
+
const isKeyframesTarget = (v) => {
|
|
345
|
+
return Array.isArray(v);
|
|
346
|
+
};
|
|
2811
347
|
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
* Check if an animation can run natively via WAAPI or requires pregenerated keyframes.
|
|
2825
|
-
* WAAPI doesn't support spring or function easings so we run these as JS animation before
|
|
2826
|
-
* handing off.
|
|
2827
|
-
*/
|
|
2828
|
-
function requiresPregeneratedKeyframes(options) {
|
|
2829
|
-
return (motionDom.isGenerator(options.type) ||
|
|
2830
|
-
options.type === "spring" ||
|
|
2831
|
-
!motionDom.isWaapiSupportedEasing(options.ease));
|
|
348
|
+
const resolveFinalValueInKeyframes = (v) => {
|
|
349
|
+
// TODO maybe throw if v.length - 1 is placeholder token?
|
|
350
|
+
return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
function getValueState(visualElement) {
|
|
354
|
+
const state = [{}, {}];
|
|
355
|
+
visualElement?.values.forEach((value, key) => {
|
|
356
|
+
state[0][key] = value.get();
|
|
357
|
+
state[1][key] = value.getVelocity();
|
|
358
|
+
});
|
|
359
|
+
return state;
|
|
2832
360
|
}
|
|
2833
|
-
function
|
|
361
|
+
function resolveVariantFromProps(props, definition, custom, visualElement) {
|
|
2834
362
|
/**
|
|
2835
|
-
*
|
|
2836
|
-
* We sample this at regular intervals to generate keyframes that we then
|
|
2837
|
-
* linearly interpolate between.
|
|
363
|
+
* If the variant definition is a function, resolve.
|
|
2838
364
|
*/
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
delay: 0,
|
|
2844
|
-
isGenerator: true,
|
|
2845
|
-
});
|
|
2846
|
-
let state = { done: false, value: keyframes[0] };
|
|
2847
|
-
const pregeneratedKeyframes = [];
|
|
365
|
+
if (typeof definition === "function") {
|
|
366
|
+
const [current, velocity] = getValueState(visualElement);
|
|
367
|
+
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
368
|
+
}
|
|
2848
369
|
/**
|
|
2849
|
-
*
|
|
2850
|
-
*
|
|
370
|
+
* If the variant definition is a variant label, or
|
|
371
|
+
* the function returned a variant label, resolve.
|
|
2851
372
|
*/
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
state = sampleAnimation.sample(t);
|
|
2855
|
-
pregeneratedKeyframes.push(state.value);
|
|
2856
|
-
t += sampleDelta;
|
|
373
|
+
if (typeof definition === "string") {
|
|
374
|
+
definition = props.variants && props.variants[definition];
|
|
2857
375
|
}
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
376
|
+
/**
|
|
377
|
+
* At this point we've resolved both functions and variant labels,
|
|
378
|
+
* but the resolved variant label might itself have been a function.
|
|
379
|
+
* If so, resolve. This can only have returned a valid target object.
|
|
380
|
+
*/
|
|
381
|
+
if (typeof definition === "function") {
|
|
382
|
+
const [current, velocity] = getValueState(visualElement);
|
|
383
|
+
definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
|
|
384
|
+
}
|
|
385
|
+
return definition;
|
|
2864
386
|
}
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
};
|
|
2870
|
-
function isUnsupportedEase(key) {
|
|
2871
|
-
return key in unsupportedEasingFunctions;
|
|
387
|
+
|
|
388
|
+
function resolveVariant(visualElement, definition, custom) {
|
|
389
|
+
const props = visualElement.getProps();
|
|
390
|
+
return resolveVariantFromProps(props, definition, custom !== undefined ? custom : props.custom, visualElement);
|
|
2872
391
|
}
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
let { duration = 300, times, ease, type, motionValue, name, startTime, } = this.options;
|
|
2882
|
-
/**
|
|
2883
|
-
* If element has since been unmounted, return false to indicate
|
|
2884
|
-
* the animation failed to initialised.
|
|
2885
|
-
*/
|
|
2886
|
-
if (!motionValue.owner || !motionValue.owner.current) {
|
|
2887
|
-
return false;
|
|
2888
|
-
}
|
|
2889
|
-
/**
|
|
2890
|
-
* If the user has provided an easing function name that isn't supported
|
|
2891
|
-
* by WAAPI (like "anticipate"), we need to provide the corressponding
|
|
2892
|
-
* function. This will later get converted to a linear() easing function.
|
|
2893
|
-
*/
|
|
2894
|
-
if (typeof ease === "string" &&
|
|
2895
|
-
motionDom.supportsLinearEasing() &&
|
|
2896
|
-
isUnsupportedEase(ease)) {
|
|
2897
|
-
ease = unsupportedEasingFunctions[ease];
|
|
2898
|
-
}
|
|
2899
|
-
/**
|
|
2900
|
-
* If this animation needs pre-generated keyframes then generate.
|
|
2901
|
-
*/
|
|
2902
|
-
if (requiresPregeneratedKeyframes(this.options)) {
|
|
2903
|
-
const { onComplete, onUpdate, motionValue, element, ...options } = this.options;
|
|
2904
|
-
const pregeneratedAnimation = pregenerateKeyframes(keyframes, options);
|
|
2905
|
-
keyframes = pregeneratedAnimation.keyframes;
|
|
2906
|
-
// If this is a very short animation, ensure we have
|
|
2907
|
-
// at least two keyframes to animate between as older browsers
|
|
2908
|
-
// can't animate between a single keyframe.
|
|
2909
|
-
if (keyframes.length === 1) {
|
|
2910
|
-
keyframes[1] = keyframes[0];
|
|
2911
|
-
}
|
|
2912
|
-
duration = pregeneratedAnimation.duration;
|
|
2913
|
-
times = pregeneratedAnimation.times;
|
|
2914
|
-
ease = pregeneratedAnimation.ease;
|
|
2915
|
-
type = "keyframes";
|
|
2916
|
-
}
|
|
2917
|
-
const animation = motionDom.startWaapiAnimation(motionValue.owner.current, name, keyframes, { ...this.options, duration, times, ease });
|
|
2918
|
-
// Override the browser calculated startTime with one synchronised to other JS
|
|
2919
|
-
// and WAAPI animations starting this event loop.
|
|
2920
|
-
animation.startTime = startTime ?? this.calcStartTime();
|
|
2921
|
-
if (this.pendingTimeline) {
|
|
2922
|
-
motionDom.attachTimeline(animation, this.pendingTimeline);
|
|
2923
|
-
this.pendingTimeline = undefined;
|
|
2924
|
-
}
|
|
2925
|
-
else {
|
|
2926
|
-
/**
|
|
2927
|
-
* Prefer the `onfinish` prop as it's more widely supported than
|
|
2928
|
-
* the `finished` promise.
|
|
2929
|
-
*
|
|
2930
|
-
* Here, we synchronously set the provided MotionValue to the end
|
|
2931
|
-
* keyframe. If we didn't, when the WAAPI animation is finished it would
|
|
2932
|
-
* be removed from the element which would then revert to its old styles.
|
|
2933
|
-
*/
|
|
2934
|
-
animation.onfinish = () => {
|
|
2935
|
-
const { onComplete } = this.options;
|
|
2936
|
-
motionValue.set(getFinalKeyframe(keyframes, this.options, finalKeyframe));
|
|
2937
|
-
onComplete && onComplete();
|
|
2938
|
-
this.cancel();
|
|
2939
|
-
this.resolveFinishedPromise();
|
|
2940
|
-
};
|
|
2941
|
-
}
|
|
2942
|
-
return {
|
|
2943
|
-
animation,
|
|
2944
|
-
duration,
|
|
2945
|
-
times,
|
|
2946
|
-
type,
|
|
2947
|
-
ease,
|
|
2948
|
-
keyframes: keyframes,
|
|
2949
|
-
};
|
|
2950
|
-
}
|
|
2951
|
-
get duration() {
|
|
2952
|
-
const { resolved } = this;
|
|
2953
|
-
if (!resolved)
|
|
2954
|
-
return 0;
|
|
2955
|
-
const { duration } = resolved;
|
|
2956
|
-
return motionUtils.millisecondsToSeconds(duration);
|
|
2957
|
-
}
|
|
2958
|
-
get time() {
|
|
2959
|
-
const { resolved } = this;
|
|
2960
|
-
if (!resolved)
|
|
2961
|
-
return 0;
|
|
2962
|
-
const { animation } = resolved;
|
|
2963
|
-
return motionUtils.millisecondsToSeconds(animation.currentTime || 0);
|
|
2964
|
-
}
|
|
2965
|
-
set time(newTime) {
|
|
2966
|
-
const { resolved } = this;
|
|
2967
|
-
if (!resolved)
|
|
2968
|
-
return;
|
|
2969
|
-
const { animation } = resolved;
|
|
2970
|
-
animation.currentTime = motionUtils.secondsToMilliseconds(newTime);
|
|
2971
|
-
}
|
|
2972
|
-
get speed() {
|
|
2973
|
-
const { resolved } = this;
|
|
2974
|
-
if (!resolved)
|
|
2975
|
-
return 1;
|
|
2976
|
-
const { animation } = resolved;
|
|
2977
|
-
return animation.playbackRate;
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Set VisualElement's MotionValue, creating a new MotionValue for it if
|
|
395
|
+
* it doesn't exist.
|
|
396
|
+
*/
|
|
397
|
+
function setMotionValue(visualElement, key, value) {
|
|
398
|
+
if (visualElement.hasValue(key)) {
|
|
399
|
+
visualElement.getValue(key).set(value);
|
|
2978
400
|
}
|
|
2979
|
-
|
|
2980
|
-
|
|
401
|
+
else {
|
|
402
|
+
visualElement.addValue(key, motionDom.motionValue(value));
|
|
2981
403
|
}
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
const { resolved } = this;
|
|
2991
|
-
if (!resolved)
|
|
2992
|
-
return "idle";
|
|
2993
|
-
const { animation } = resolved;
|
|
2994
|
-
return animation.playState;
|
|
2995
|
-
}
|
|
2996
|
-
get startTime() {
|
|
2997
|
-
const { resolved } = this;
|
|
2998
|
-
if (!resolved)
|
|
2999
|
-
return null;
|
|
3000
|
-
const { animation } = resolved;
|
|
3001
|
-
// Coerce to number as TypeScript incorrectly types this
|
|
3002
|
-
// as CSSNumberish
|
|
3003
|
-
return animation.startTime;
|
|
404
|
+
}
|
|
405
|
+
function setTarget(visualElement, definition) {
|
|
406
|
+
const resolved = resolveVariant(visualElement, definition);
|
|
407
|
+
let { transitionEnd = {}, transition = {}, ...target } = resolved || {};
|
|
408
|
+
target = { ...target, ...transitionEnd };
|
|
409
|
+
for (const key in target) {
|
|
410
|
+
const value = resolveFinalValueInKeyframes(target[key]);
|
|
411
|
+
setMotionValue(visualElement, key, value);
|
|
3004
412
|
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function isWillChangeMotionValue(value) {
|
|
416
|
+
return Boolean(isMotionValue(value) && value.add);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function addValueToWillChange(visualElement, key) {
|
|
420
|
+
const willChange = visualElement.getValue("willChange");
|
|
3005
421
|
/**
|
|
3006
|
-
*
|
|
3007
|
-
*
|
|
422
|
+
* It could be that a user has set willChange to a regular MotionValue,
|
|
423
|
+
* in which case we can't add the value to it.
|
|
3008
424
|
*/
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
this.pendingTimeline = timeline;
|
|
3012
|
-
}
|
|
3013
|
-
else {
|
|
3014
|
-
const { resolved } = this;
|
|
3015
|
-
if (!resolved)
|
|
3016
|
-
return motionUtils.noop;
|
|
3017
|
-
const { animation } = resolved;
|
|
3018
|
-
motionDom.attachTimeline(animation, timeline);
|
|
3019
|
-
}
|
|
3020
|
-
return motionUtils.noop;
|
|
3021
|
-
}
|
|
3022
|
-
play() {
|
|
3023
|
-
if (this.isStopped)
|
|
3024
|
-
return;
|
|
3025
|
-
const { resolved } = this;
|
|
3026
|
-
if (!resolved)
|
|
3027
|
-
return;
|
|
3028
|
-
const { animation } = resolved;
|
|
3029
|
-
if (animation.playState === "finished") {
|
|
3030
|
-
this.updateFinishedPromise();
|
|
3031
|
-
}
|
|
3032
|
-
animation.play();
|
|
3033
|
-
}
|
|
3034
|
-
pause() {
|
|
3035
|
-
const { resolved } = this;
|
|
3036
|
-
if (!resolved)
|
|
3037
|
-
return;
|
|
3038
|
-
const { animation } = resolved;
|
|
3039
|
-
animation.pause();
|
|
3040
|
-
}
|
|
3041
|
-
stop() {
|
|
3042
|
-
this.resolver.cancel();
|
|
3043
|
-
this.isStopped = true;
|
|
3044
|
-
if (this.state === "idle")
|
|
3045
|
-
return;
|
|
3046
|
-
this.resolveFinishedPromise();
|
|
3047
|
-
this.updateFinishedPromise();
|
|
3048
|
-
const { resolved } = this;
|
|
3049
|
-
if (!resolved)
|
|
3050
|
-
return;
|
|
3051
|
-
const { animation, keyframes, duration, type, ease, times } = resolved;
|
|
3052
|
-
if (animation.playState === "idle" ||
|
|
3053
|
-
animation.playState === "finished") {
|
|
3054
|
-
return;
|
|
3055
|
-
}
|
|
3056
|
-
/**
|
|
3057
|
-
* WAAPI doesn't natively have any interruption capabilities.
|
|
3058
|
-
*
|
|
3059
|
-
* Rather than read commited styles back out of the DOM, we can
|
|
3060
|
-
* create a renderless JS animation and sample it twice to calculate
|
|
3061
|
-
* its current value, "previous" value, and therefore allow
|
|
3062
|
-
* Motion to calculate velocity for any subsequent animation.
|
|
3063
|
-
*/
|
|
3064
|
-
if (this.time) {
|
|
3065
|
-
const { motionValue, onUpdate, onComplete, element, ...options } = this.options;
|
|
3066
|
-
const sampleAnimation = new MainThreadAnimation({
|
|
3067
|
-
...options,
|
|
3068
|
-
keyframes,
|
|
3069
|
-
duration,
|
|
3070
|
-
type,
|
|
3071
|
-
ease,
|
|
3072
|
-
times,
|
|
3073
|
-
isGenerator: true,
|
|
3074
|
-
});
|
|
3075
|
-
const sampleTime = motionUtils.secondsToMilliseconds(this.time);
|
|
3076
|
-
motionValue.setWithVelocity(sampleAnimation.sample(sampleTime - sampleDelta).value, sampleAnimation.sample(sampleTime).value, sampleDelta);
|
|
3077
|
-
}
|
|
3078
|
-
const { onStop } = this.options;
|
|
3079
|
-
onStop && onStop();
|
|
3080
|
-
this.cancel();
|
|
3081
|
-
}
|
|
3082
|
-
complete() {
|
|
3083
|
-
const { resolved } = this;
|
|
3084
|
-
if (!resolved)
|
|
3085
|
-
return;
|
|
3086
|
-
resolved.animation.finish();
|
|
425
|
+
if (isWillChangeMotionValue(willChange)) {
|
|
426
|
+
return willChange.add(key);
|
|
3087
427
|
}
|
|
3088
|
-
|
|
3089
|
-
const
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
resolved.animation.cancel();
|
|
3093
|
-
}
|
|
3094
|
-
static supports(options) {
|
|
3095
|
-
const { motionValue, name, repeatDelay, repeatType, damping, type } = options;
|
|
3096
|
-
if (!motionValue ||
|
|
3097
|
-
!motionValue.owner ||
|
|
3098
|
-
!(motionValue.owner.current instanceof HTMLElement)) {
|
|
3099
|
-
return false;
|
|
3100
|
-
}
|
|
3101
|
-
const { onUpdate, transformTemplate } = motionValue.owner.getProps();
|
|
3102
|
-
return (supportsWaapi() &&
|
|
3103
|
-
name &&
|
|
3104
|
-
acceleratedValues.has(name) &&
|
|
3105
|
-
(name !== "transform" || !transformTemplate) &&
|
|
3106
|
-
/**
|
|
3107
|
-
* If we're outputting values to onUpdate then we can't use WAAPI as there's
|
|
3108
|
-
* no way to read the value from WAAPI every frame.
|
|
3109
|
-
*/
|
|
3110
|
-
!onUpdate &&
|
|
3111
|
-
!repeatDelay &&
|
|
3112
|
-
repeatType !== "mirror" &&
|
|
3113
|
-
damping !== 0 &&
|
|
3114
|
-
type !== "inertia");
|
|
428
|
+
else if (!willChange && motionUtils.MotionGlobalConfig.WillChange) {
|
|
429
|
+
const newWillChange = new motionUtils.MotionGlobalConfig.WillChange("auto");
|
|
430
|
+
visualElement.addValue("willChange", newWillChange);
|
|
431
|
+
newWillChange.add(key);
|
|
3115
432
|
}
|
|
3116
433
|
}
|
|
3117
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Convert camelCase to dash-case properties.
|
|
437
|
+
*/
|
|
438
|
+
const camelToDash = (str) => str.replace(/([a-z])([A-Z])/gu, "$1-$2").toLowerCase();
|
|
439
|
+
|
|
440
|
+
const optimizedAppearDataId = "framerAppearId";
|
|
441
|
+
const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
|
|
442
|
+
|
|
443
|
+
function getOptimisedAppearId(visualElement) {
|
|
444
|
+
return visualElement.props[optimizedAppearDataAttribute];
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const isNotNull = (value) => value !== null;
|
|
448
|
+
function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
|
|
449
|
+
const resolvedKeyframes = keyframes.filter(isNotNull);
|
|
450
|
+
const index = repeat && repeatType !== "loop" && repeat % 2 === 1
|
|
451
|
+
? 0
|
|
452
|
+
: resolvedKeyframes.length - 1;
|
|
453
|
+
return !index || finalKeyframe === undefined
|
|
454
|
+
? resolvedKeyframes[index]
|
|
455
|
+
: finalKeyframe;
|
|
456
|
+
}
|
|
457
|
+
|
|
3118
458
|
const underDampedSpring = {
|
|
3119
459
|
type: "spring",
|
|
3120
460
|
stiffness: 500,
|
|
@@ -3144,7 +484,7 @@ const getDefaultTransition = (valueKey, { keyframes }) => {
|
|
|
3144
484
|
if (keyframes.length > 2) {
|
|
3145
485
|
return keyframesTransition;
|
|
3146
486
|
}
|
|
3147
|
-
else if (transformProps.has(valueKey)) {
|
|
487
|
+
else if (motionDom.transformProps.has(valueKey)) {
|
|
3148
488
|
return valueKey.startsWith("scale")
|
|
3149
489
|
? criticallyDampedSpring(keyframes[1])
|
|
3150
490
|
: underDampedSpring;
|
|
@@ -3175,7 +515,7 @@ const animateMotionValue = (name, value, target, transition = {}, element, isHan
|
|
|
3175
515
|
*/
|
|
3176
516
|
let { elapsed = 0 } = transition;
|
|
3177
517
|
elapsed = elapsed - motionUtils.secondsToMilliseconds(delay);
|
|
3178
|
-
|
|
518
|
+
const options = {
|
|
3179
519
|
keyframes: Array.isArray(target) ? target : [null, target],
|
|
3180
520
|
ease: "easeOut",
|
|
3181
521
|
velocity: value.getVelocity(),
|
|
@@ -3198,22 +538,18 @@ const animateMotionValue = (name, value, target, transition = {}, element, isHan
|
|
|
3198
538
|
* unique transition settings for this value.
|
|
3199
539
|
*/
|
|
3200
540
|
if (!isTransitionDefined(valueTransition)) {
|
|
3201
|
-
options
|
|
3202
|
-
...options,
|
|
3203
|
-
...getDefaultTransition(name, options),
|
|
3204
|
-
};
|
|
541
|
+
Object.assign(options, getDefaultTransition(name, options));
|
|
3205
542
|
}
|
|
3206
543
|
/**
|
|
3207
544
|
* Both WAAPI and our internal animation functions use durations
|
|
3208
545
|
* as defined by milliseconds, while our external API defines them
|
|
3209
546
|
* as seconds.
|
|
3210
547
|
*/
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
}
|
|
548
|
+
options.duration && (options.duration = motionUtils.secondsToMilliseconds(options.duration));
|
|
549
|
+
options.repeatDelay && (options.repeatDelay = motionUtils.secondsToMilliseconds(options.repeatDelay));
|
|
550
|
+
/**
|
|
551
|
+
* Support deprecated way to set initial value. Prefer keyframe syntax.
|
|
552
|
+
*/
|
|
3217
553
|
if (options.from !== undefined) {
|
|
3218
554
|
options.keyframes[0] = options.from;
|
|
3219
555
|
}
|
|
@@ -3225,7 +561,8 @@ const animateMotionValue = (name, value, target, transition = {}, element, isHan
|
|
|
3225
561
|
shouldSkip = true;
|
|
3226
562
|
}
|
|
3227
563
|
}
|
|
3228
|
-
if (motionUtils.MotionGlobalConfig.
|
|
564
|
+
if (motionUtils.MotionGlobalConfig.instantAnimations ||
|
|
565
|
+
motionUtils.MotionGlobalConfig.skipAnimations) {
|
|
3229
566
|
shouldSkip = true;
|
|
3230
567
|
options.duration = 0;
|
|
3231
568
|
options.delay = 0;
|
|
@@ -3247,22 +584,10 @@ const animateMotionValue = (name, value, target, transition = {}, element, isHan
|
|
|
3247
584
|
options.onUpdate(finalKeyframe);
|
|
3248
585
|
options.onComplete();
|
|
3249
586
|
});
|
|
3250
|
-
|
|
3251
|
-
// than returning undefined
|
|
3252
|
-
return new motionDom.GroupAnimationWithThen([]);
|
|
587
|
+
return;
|
|
3253
588
|
}
|
|
3254
589
|
}
|
|
3255
|
-
|
|
3256
|
-
* Animate via WAAPI if possible. If this is a handoff animation, the optimised animation will be running via
|
|
3257
|
-
* WAAPI. Therefore, this animation must be JS to ensure it runs "under" the
|
|
3258
|
-
* optimised animation.
|
|
3259
|
-
*/
|
|
3260
|
-
if (!isHandoff && AcceleratedAnimation.supports(options)) {
|
|
3261
|
-
return new AcceleratedAnimation(options);
|
|
3262
|
-
}
|
|
3263
|
-
else {
|
|
3264
|
-
return new MainThreadAnimation(options);
|
|
3265
|
-
}
|
|
590
|
+
return new motionDom.AsyncMotionValueAnimation(options);
|
|
3266
591
|
};
|
|
3267
592
|
|
|
3268
593
|
/**
|
|
@@ -3312,7 +637,7 @@ function animateTarget(visualElement, targetAndTransition, { delay = 0, transiti
|
|
|
3312
637
|
}
|
|
3313
638
|
}
|
|
3314
639
|
addValueToWillChange(visualElement, key);
|
|
3315
|
-
value.start(animateMotionValue(key, value, valueTarget, visualElement.shouldReduceMotion && positionalKeys.has(key)
|
|
640
|
+
value.start(animateMotionValue(key, value, valueTarget, visualElement.shouldReduceMotion && motionDom.positionalKeys.has(key)
|
|
3316
641
|
? { type: false }
|
|
3317
642
|
: valueTransition, visualElement, isHandoff));
|
|
3318
643
|
const animation = value.animation;
|
|
@@ -3388,15 +713,6 @@ function initPrefersReducedMotion() {
|
|
|
3388
713
|
}
|
|
3389
714
|
}
|
|
3390
715
|
|
|
3391
|
-
/**
|
|
3392
|
-
* A list of all ValueTypes
|
|
3393
|
-
*/
|
|
3394
|
-
const valueTypes = [...dimensionValueTypes, color, complex];
|
|
3395
|
-
/**
|
|
3396
|
-
* Tests a value against the list of ValueTypes
|
|
3397
|
-
*/
|
|
3398
|
-
const findValueType = (v) => valueTypes.find(testValueType(v));
|
|
3399
|
-
|
|
3400
716
|
function isAnimationControls(v) {
|
|
3401
717
|
return (v !== null &&
|
|
3402
718
|
typeof v === "object" &&
|
|
@@ -3444,7 +760,7 @@ function updateMotionValuesFromProps(element, next, prev) {
|
|
|
3444
760
|
* and warn against mismatches.
|
|
3445
761
|
*/
|
|
3446
762
|
if (process.env.NODE_ENV === "development") {
|
|
3447
|
-
motionUtils.warnOnce(nextValue.version === "12.7.
|
|
763
|
+
motionUtils.warnOnce(nextValue.version === "12.7.5-alpha.0", `Attempting to mix Motion versions ${nextValue.version} with 12.7.5-alpha.0 may not work as expected.`);
|
|
3448
764
|
}
|
|
3449
765
|
}
|
|
3450
766
|
else if (isMotionValue(prevValue)) {
|
|
@@ -3536,7 +852,7 @@ class VisualElement {
|
|
|
3536
852
|
* value might be provided externally by the component via props.
|
|
3537
853
|
*/
|
|
3538
854
|
this.values = new Map();
|
|
3539
|
-
this.KeyframeResolver = KeyframeResolver;
|
|
855
|
+
this.KeyframeResolver = motionDom.KeyframeResolver;
|
|
3540
856
|
/**
|
|
3541
857
|
* Cleanup functions for active features (hover/tap/exit etc)
|
|
3542
858
|
*/
|
|
@@ -3664,7 +980,7 @@ class VisualElement {
|
|
|
3664
980
|
if (this.valueSubscriptions.has(key)) {
|
|
3665
981
|
this.valueSubscriptions.get(key)();
|
|
3666
982
|
}
|
|
3667
|
-
const valueIsTransform = transformProps.has(key);
|
|
983
|
+
const valueIsTransform = motionDom.transformProps.has(key);
|
|
3668
984
|
if (valueIsTransform && this.onBindTransform) {
|
|
3669
985
|
this.onBindTransform();
|
|
3670
986
|
}
|
|
@@ -3874,12 +1190,12 @@ class VisualElement {
|
|
|
3874
1190
|
this.readValueFromInstance(this.current, key, this.options);
|
|
3875
1191
|
if (value !== undefined && value !== null) {
|
|
3876
1192
|
if (typeof value === "string" &&
|
|
3877
|
-
(isNumericalString(value) || isZeroValueString(value))) {
|
|
1193
|
+
(motionUtils.isNumericalString(value) || motionUtils.isZeroValueString(value))) {
|
|
3878
1194
|
// If this is a number read as a string, ie "0" or "200", convert it to a number
|
|
3879
1195
|
value = parseFloat(value);
|
|
3880
1196
|
}
|
|
3881
|
-
else if (!findValueType(value) && complex.test(target)) {
|
|
3882
|
-
value = getAnimatableNone(key, target);
|
|
1197
|
+
else if (!motionDom.findValueType(value) && motionDom.complex.test(target)) {
|
|
1198
|
+
value = motionDom.getAnimatableNone(key, target);
|
|
3883
1199
|
}
|
|
3884
1200
|
this.setBaseTarget(key, isMotionValue(value) ? value.get() : value);
|
|
3885
1201
|
}
|
|
@@ -3943,7 +1259,7 @@ class VisualElement {
|
|
|
3943
1259
|
class DOMVisualElement extends VisualElement {
|
|
3944
1260
|
constructor() {
|
|
3945
1261
|
super(...arguments);
|
|
3946
|
-
this.KeyframeResolver = DOMKeyframesResolver;
|
|
1262
|
+
this.KeyframeResolver = motionDom.DOMKeyframesResolver;
|
|
3947
1263
|
}
|
|
3948
1264
|
sortInstanceNodePosition(a, b) {
|
|
3949
1265
|
/**
|
|
@@ -3978,22 +1294,13 @@ class DOMVisualElement extends VisualElement {
|
|
|
3978
1294
|
}
|
|
3979
1295
|
}
|
|
3980
1296
|
|
|
3981
|
-
/**
|
|
3982
|
-
* Provided a value and a ValueType, returns the value as that value type.
|
|
3983
|
-
*/
|
|
3984
|
-
const getValueAsType = (value, type) => {
|
|
3985
|
-
return type && typeof value === "number"
|
|
3986
|
-
? type.transform(value)
|
|
3987
|
-
: value;
|
|
3988
|
-
};
|
|
3989
|
-
|
|
3990
1297
|
const translateAlias = {
|
|
3991
1298
|
x: "translateX",
|
|
3992
1299
|
y: "translateY",
|
|
3993
1300
|
z: "translateZ",
|
|
3994
1301
|
transformPerspective: "perspective",
|
|
3995
1302
|
};
|
|
3996
|
-
const numTransforms = transformPropOrder.length;
|
|
1303
|
+
const numTransforms = motionDom.transformPropOrder.length;
|
|
3997
1304
|
/**
|
|
3998
1305
|
* Build a CSS transform style from individual x/y/scale etc properties.
|
|
3999
1306
|
*
|
|
@@ -4009,7 +1316,7 @@ function buildTransform(latestValues, transform, transformTemplate) {
|
|
|
4009
1316
|
* are present to the transform string.
|
|
4010
1317
|
*/
|
|
4011
1318
|
for (let i = 0; i < numTransforms; i++) {
|
|
4012
|
-
const key = transformPropOrder[i];
|
|
1319
|
+
const key = motionDom.transformPropOrder[i];
|
|
4013
1320
|
const value = latestValues[key];
|
|
4014
1321
|
if (value === undefined)
|
|
4015
1322
|
continue;
|
|
@@ -4021,7 +1328,7 @@ function buildTransform(latestValues, transform, transformTemplate) {
|
|
|
4021
1328
|
valueIsDefault = parseFloat(value) === 0;
|
|
4022
1329
|
}
|
|
4023
1330
|
if (!valueIsDefault || transformTemplate) {
|
|
4024
|
-
const valueAsType = getValueAsType(value, numberValueTypes[key]);
|
|
1331
|
+
const valueAsType = motionDom.getValueAsType(value, motionDom.numberValueTypes[key]);
|
|
4025
1332
|
if (!valueIsDefault) {
|
|
4026
1333
|
transformIsDefault = false;
|
|
4027
1334
|
const transformName = translateAlias[key] || key;
|
|
@@ -4057,18 +1364,18 @@ function buildHTMLStyles(state, latestValues, transformTemplate) {
|
|
|
4057
1364
|
*/
|
|
4058
1365
|
for (const key in latestValues) {
|
|
4059
1366
|
const value = latestValues[key];
|
|
4060
|
-
if (transformProps.has(key)) {
|
|
1367
|
+
if (motionDom.transformProps.has(key)) {
|
|
4061
1368
|
// If this is a transform, flag to enable further transform processing
|
|
4062
1369
|
hasTransform = true;
|
|
4063
1370
|
continue;
|
|
4064
1371
|
}
|
|
4065
|
-
else if (isCSSVariableName(key)) {
|
|
1372
|
+
else if (motionDom.isCSSVariableName(key)) {
|
|
4066
1373
|
vars[key] = value;
|
|
4067
1374
|
continue;
|
|
4068
1375
|
}
|
|
4069
1376
|
else {
|
|
4070
1377
|
// Convert the value to its default value type, ie 0 -> "0px"
|
|
4071
|
-
const valueAsType = getValueAsType(value, numberValueTypes[key]);
|
|
1378
|
+
const valueAsType = motionDom.getValueAsType(value, motionDom.numberValueTypes[key]);
|
|
4072
1379
|
if (key.startsWith("origin")) {
|
|
4073
1380
|
// If this is a transform origin, flag and enable further transform-origin processing
|
|
4074
1381
|
hasTransformOrigin = true;
|
|
@@ -4124,17 +1431,17 @@ function buildSVGPath(attrs, length, spacing = 1, offset = 0, useDashCase = true
|
|
|
4124
1431
|
// when defining props on a React component.
|
|
4125
1432
|
const keys = useDashCase ? dashKeys : camelKeys;
|
|
4126
1433
|
// Build the dash offset
|
|
4127
|
-
attrs[keys.offset] = px.transform(-offset);
|
|
1434
|
+
attrs[keys.offset] = motionDom.px.transform(-offset);
|
|
4128
1435
|
// Build the dash array
|
|
4129
|
-
const pathLength = px.transform(length);
|
|
4130
|
-
const pathSpacing = px.transform(spacing);
|
|
1436
|
+
const pathLength = motionDom.px.transform(length);
|
|
1437
|
+
const pathSpacing = motionDom.px.transform(spacing);
|
|
4131
1438
|
attrs[keys.array] = `${pathLength} ${pathSpacing}`;
|
|
4132
1439
|
}
|
|
4133
1440
|
|
|
4134
1441
|
function calcOrigin(origin, offset, size) {
|
|
4135
1442
|
return typeof origin === "string"
|
|
4136
1443
|
? origin
|
|
4137
|
-
: px.transform(offset + size * origin);
|
|
1444
|
+
: motionDom.px.transform(offset + size * origin);
|
|
4138
1445
|
}
|
|
4139
1446
|
/**
|
|
4140
1447
|
* The SVG transform origin defaults are different to CSS and is less intuitive,
|
|
@@ -4260,7 +1567,7 @@ function renderSVG(element, renderState, _styleProp, projection) {
|
|
|
4260
1567
|
const scaleCorrectors = {};
|
|
4261
1568
|
|
|
4262
1569
|
function isForcedMotionValue(key, { layout, layoutId }) {
|
|
4263
|
-
return (transformProps.has(key) ||
|
|
1570
|
+
return (motionDom.transformProps.has(key) ||
|
|
4264
1571
|
key.startsWith("origin") ||
|
|
4265
1572
|
((layout || layoutId !== undefined) &&
|
|
4266
1573
|
(!!scaleCorrectors[key] || key === "opacity")));
|
|
@@ -4286,7 +1593,7 @@ function scrapeMotionValuesFromProps(props, prevProps, visualElement) {
|
|
|
4286
1593
|
for (const key in props) {
|
|
4287
1594
|
if (isMotionValue(props[key]) ||
|
|
4288
1595
|
isMotionValue(prevProps[key])) {
|
|
4289
|
-
const targetKey = transformPropOrder.indexOf(key) !== -1
|
|
1596
|
+
const targetKey = motionDom.transformPropOrder.indexOf(key) !== -1
|
|
4290
1597
|
? "attr" + key.charAt(0).toUpperCase() + key.substring(1)
|
|
4291
1598
|
: key;
|
|
4292
1599
|
newValues[targetKey] = props[key];
|
|
@@ -4311,8 +1618,8 @@ class SVGVisualElement extends DOMVisualElement {
|
|
|
4311
1618
|
return props[key];
|
|
4312
1619
|
}
|
|
4313
1620
|
readValueFromInstance(instance, key) {
|
|
4314
|
-
if (transformProps.has(key)) {
|
|
4315
|
-
const defaultType = getDefaultValueType(key);
|
|
1621
|
+
if (motionDom.transformProps.has(key)) {
|
|
1622
|
+
const defaultType = motionDom.getDefaultValueType(key);
|
|
4316
1623
|
return defaultType ? defaultType.default || 0 : 0;
|
|
4317
1624
|
}
|
|
4318
1625
|
key = !camelCaseAttributes.has(key) ? camelToDash(key) : key;
|
|
@@ -4381,12 +1688,12 @@ class HTMLVisualElement extends DOMVisualElement {
|
|
|
4381
1688
|
this.renderInstance = renderHTML;
|
|
4382
1689
|
}
|
|
4383
1690
|
readValueFromInstance(instance, key) {
|
|
4384
|
-
if (transformProps.has(key)) {
|
|
4385
|
-
return readTransformValue(instance, key);
|
|
1691
|
+
if (motionDom.transformProps.has(key)) {
|
|
1692
|
+
return motionDom.readTransformValue(instance, key);
|
|
4386
1693
|
}
|
|
4387
1694
|
else {
|
|
4388
1695
|
const computedStyle = getComputedStyle$1(instance);
|
|
4389
|
-
const value = (isCSSVariableName(key)
|
|
1696
|
+
const value = (motionDom.isCSSVariableName(key)
|
|
4390
1697
|
? computedStyle.getPropertyValue(key)
|
|
4391
1698
|
: computedStyle[key]) || 0;
|
|
4392
1699
|
return typeof value === "string" ? value.trim() : value;
|
|
@@ -4527,7 +1834,7 @@ function animateSubject(subject, keyframes, options, scope) {
|
|
|
4527
1834
|
|
|
4528
1835
|
function animateSequence(sequence, options, scope) {
|
|
4529
1836
|
const animations = [];
|
|
4530
|
-
const animationDefinitions = createAnimationsFromSequence(sequence, options, scope, { spring });
|
|
1837
|
+
const animationDefinitions = createAnimationsFromSequence(sequence, options, scope, { spring: motionDom.spring });
|
|
4531
1838
|
animationDefinitions.forEach(({ keyframes, transition }, subject) => {
|
|
4532
1839
|
animations.push(...animateSubject(subject, keyframes, transition));
|
|
4533
1840
|
});
|
|
@@ -4567,7 +1874,27 @@ function animateElements(elementOrSelector, keyframes, options, scope) {
|
|
|
4567
1874
|
const elements = motionDom.resolveElements(elementOrSelector, scope);
|
|
4568
1875
|
const numElements = elements.length;
|
|
4569
1876
|
motionUtils.invariant(Boolean(numElements), "No valid element provided.");
|
|
4570
|
-
|
|
1877
|
+
/**
|
|
1878
|
+
* WAAPI doesn't support interrupting animations.
|
|
1879
|
+
*
|
|
1880
|
+
* Therefore, starting animations requires a three-step process:
|
|
1881
|
+
* 1. Stop existing animations (write styles to DOM)
|
|
1882
|
+
* 2. Resolve keyframes (read styles from DOM)
|
|
1883
|
+
* 3. Create new animations (write styles to DOM)
|
|
1884
|
+
*
|
|
1885
|
+
* The hybrid `animate()` function uses AsyncAnimation to resolve
|
|
1886
|
+
* keyframes before creating new animations, which removes style
|
|
1887
|
+
* thrashing. Here, we have much stricter filesize constraints.
|
|
1888
|
+
* Therefore we do this in a synchronous way that ensures that
|
|
1889
|
+
* at least within `animate()` calls there is no style thrashing.
|
|
1890
|
+
*
|
|
1891
|
+
* In the motion-native-animate-mini-interrupt benchmark this
|
|
1892
|
+
* was 80% faster than a single loop.
|
|
1893
|
+
*/
|
|
1894
|
+
const animationDefinitions = [];
|
|
1895
|
+
/**
|
|
1896
|
+
* Step 1: Build options and stop existing animations (write)
|
|
1897
|
+
*/
|
|
4571
1898
|
for (let i = 0; i < numElements; i++) {
|
|
4572
1899
|
const element = elements[i];
|
|
4573
1900
|
const elementTransition = { ...options };
|
|
@@ -4578,20 +1905,68 @@ function animateElements(elementOrSelector, keyframes, options, scope) {
|
|
|
4578
1905
|
elementTransition.delay = elementTransition.delay(i, numElements);
|
|
4579
1906
|
}
|
|
4580
1907
|
for (const valueName in keyframes) {
|
|
4581
|
-
|
|
1908
|
+
let valueKeyframes = keyframes[valueName];
|
|
1909
|
+
if (!Array.isArray(valueKeyframes)) {
|
|
1910
|
+
valueKeyframes = [valueKeyframes];
|
|
1911
|
+
}
|
|
4582
1912
|
const valueOptions = {
|
|
4583
1913
|
...motionDom.getValueTransition(elementTransition, valueName),
|
|
4584
1914
|
};
|
|
4585
1915
|
valueOptions.duration && (valueOptions.duration = motionUtils.secondsToMilliseconds(valueOptions.duration));
|
|
4586
1916
|
valueOptions.delay && (valueOptions.delay = motionUtils.secondsToMilliseconds(valueOptions.delay));
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
1917
|
+
/**
|
|
1918
|
+
* If there's an existing animation playing on this element then stop it
|
|
1919
|
+
* before creating a new one.
|
|
1920
|
+
*/
|
|
1921
|
+
const map = motionDom.getAnimationMap(element);
|
|
1922
|
+
const key = motionDom.animationMapKey(valueName, valueOptions.pseudoElement || "");
|
|
1923
|
+
const currentAnimation = map.get(key);
|
|
1924
|
+
currentAnimation && currentAnimation.stop();
|
|
1925
|
+
animationDefinitions.push({
|
|
1926
|
+
map,
|
|
1927
|
+
key,
|
|
1928
|
+
unresolvedKeyframes: valueKeyframes,
|
|
1929
|
+
options: {
|
|
1930
|
+
...valueOptions,
|
|
1931
|
+
element,
|
|
1932
|
+
name: valueName,
|
|
1933
|
+
allowFlatten: !elementTransition.type && !elementTransition.ease,
|
|
1934
|
+
},
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Step 2: Resolve keyframes (read)
|
|
1940
|
+
*/
|
|
1941
|
+
for (let i = 0; i < animationDefinitions.length; i++) {
|
|
1942
|
+
const { unresolvedKeyframes, options: animationOptions } = animationDefinitions[i];
|
|
1943
|
+
const { element, name, pseudoElement } = animationOptions;
|
|
1944
|
+
if (!pseudoElement && unresolvedKeyframes[0] === null) {
|
|
1945
|
+
unresolvedKeyframes[0] = motionDom.getComputedStyle(element, name);
|
|
1946
|
+
}
|
|
1947
|
+
motionDom.fillWildcards(unresolvedKeyframes);
|
|
1948
|
+
motionDom.applyPxDefaults(unresolvedKeyframes, name);
|
|
1949
|
+
/**
|
|
1950
|
+
* If we only have one keyframe, explicitly read the initial keyframe
|
|
1951
|
+
* from the computed style. This is to ensure consistency with WAAPI behaviour
|
|
1952
|
+
* for restarting animations, for instance .play() after finish, when it
|
|
1953
|
+
* has one vs two keyframes.
|
|
1954
|
+
*/
|
|
1955
|
+
if (!pseudoElement && unresolvedKeyframes.length < 2) {
|
|
1956
|
+
unresolvedKeyframes.unshift(motionDom.getComputedStyle(element, name));
|
|
4594
1957
|
}
|
|
1958
|
+
animationOptions.keyframes = unresolvedKeyframes;
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Step 3: Create new animations (write)
|
|
1962
|
+
*/
|
|
1963
|
+
const animations = [];
|
|
1964
|
+
for (let i = 0; i < animationDefinitions.length; i++) {
|
|
1965
|
+
const { map, key, options: animationOptions } = animationDefinitions[i];
|
|
1966
|
+
const animation = new motionDom.NativeAnimation(animationOptions);
|
|
1967
|
+
map.set(key, animation);
|
|
1968
|
+
animation.finished.finally(() => map.delete(key));
|
|
1969
|
+
animations.push(animation);
|
|
4595
1970
|
}
|
|
4596
1971
|
return animations;
|
|
4597
1972
|
}
|
|
@@ -4604,21 +1979,6 @@ const createScopedWaapiAnimate = (scope) => {
|
|
|
4604
1979
|
};
|
|
4605
1980
|
const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
|
|
4606
1981
|
|
|
4607
|
-
function observeTimeline(update, timeline) {
|
|
4608
|
-
let prevProgress;
|
|
4609
|
-
const onFrame = () => {
|
|
4610
|
-
const { currentTime } = timeline;
|
|
4611
|
-
const percentage = currentTime === null ? 0 : currentTime.value;
|
|
4612
|
-
const progress = percentage / 100;
|
|
4613
|
-
if (prevProgress !== progress) {
|
|
4614
|
-
update(progress);
|
|
4615
|
-
}
|
|
4616
|
-
prevProgress = progress;
|
|
4617
|
-
};
|
|
4618
|
-
motionDom.frame.update(onFrame, true);
|
|
4619
|
-
return () => motionDom.cancelFrame(onFrame);
|
|
4620
|
-
}
|
|
4621
|
-
|
|
4622
1982
|
const resizeHandlers = new WeakMap();
|
|
4623
1983
|
let observer;
|
|
4624
1984
|
function getElementSize(target, borderBoxSize) {
|
|
@@ -4949,10 +2309,10 @@ function resolveOffsets(container, info, options) {
|
|
|
4949
2309
|
* to map scroll value into a progress.
|
|
4950
2310
|
*/
|
|
4951
2311
|
if (hasChanged) {
|
|
4952
|
-
info[axis].interpolate = interpolate(info[axis].offset, defaultOffset
|
|
2312
|
+
info[axis].interpolate = motionDom.interpolate(info[axis].offset, motionDom.defaultOffset(offsetDefinition), { clamp: false });
|
|
4953
2313
|
info[axis].interpolatorOffsets = [...info[axis].offset];
|
|
4954
2314
|
}
|
|
4955
|
-
info[axis].progress = clamp(0, 1, info[axis].interpolate(info[axis].current));
|
|
2315
|
+
info[axis].progress = motionUtils.clamp(0, 1, info[axis].interpolate(info[axis].current));
|
|
4956
2316
|
}
|
|
4957
2317
|
|
|
4958
2318
|
function measure(container, target = container, info) {
|
|
@@ -5075,19 +2435,16 @@ function scrollInfo(onScroll, { container = document.documentElement, ...options
|
|
|
5075
2435
|
};
|
|
5076
2436
|
}
|
|
5077
2437
|
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
if (source)
|
|
5081
|
-
container = source;
|
|
5082
|
-
// ScrollTimeline records progress as a percentage CSSUnitValue
|
|
2438
|
+
const timelineCache = new Map();
|
|
2439
|
+
function scrollTimelineFallback(options) {
|
|
5083
2440
|
const currentTime = { value: 0 };
|
|
5084
2441
|
const cancel = scrollInfo((info) => {
|
|
5085
|
-
currentTime.value = info[axis].progress * 100;
|
|
5086
|
-
},
|
|
2442
|
+
currentTime.value = info[options.axis].progress * 100;
|
|
2443
|
+
}, options);
|
|
5087
2444
|
return { currentTime, cancel };
|
|
5088
2445
|
}
|
|
5089
|
-
|
|
5090
|
-
|
|
2446
|
+
function getTimeline({ source, container, ...options }) {
|
|
2447
|
+
const { axis } = options;
|
|
5091
2448
|
// Support legacy source argument. Deprecate later.
|
|
5092
2449
|
if (source)
|
|
5093
2450
|
container = source;
|
|
@@ -5098,10 +2455,24 @@ function getTimeline({ source, container = document.documentElement, axis = "y",
|
|
|
5098
2455
|
if (!elementCache[axis]) {
|
|
5099
2456
|
elementCache[axis] = motionDom.supportsScrollTimeline()
|
|
5100
2457
|
? new ScrollTimeline({ source: container, axis })
|
|
5101
|
-
: scrollTimelineFallback({
|
|
2458
|
+
: scrollTimelineFallback({ container, ...options });
|
|
5102
2459
|
}
|
|
5103
2460
|
return elementCache[axis];
|
|
5104
2461
|
}
|
|
2462
|
+
|
|
2463
|
+
function attachToAnimation(animation, options) {
|
|
2464
|
+
const timeline = getTimeline(options);
|
|
2465
|
+
return animation.attachTimeline({
|
|
2466
|
+
timeline,
|
|
2467
|
+
observe: (valueAnimation) => {
|
|
2468
|
+
valueAnimation.pause();
|
|
2469
|
+
return motionDom.observeTimeline((progress) => {
|
|
2470
|
+
valueAnimation.time = valueAnimation.duration * progress;
|
|
2471
|
+
}, timeline);
|
|
2472
|
+
},
|
|
2473
|
+
});
|
|
2474
|
+
}
|
|
2475
|
+
|
|
5105
2476
|
/**
|
|
5106
2477
|
* If the onScroll function has two arguments, it's expecting
|
|
5107
2478
|
* more specific information about the scroll from scrollInfo.
|
|
@@ -5109,51 +2480,22 @@ function getTimeline({ source, container = document.documentElement, axis = "y",
|
|
|
5109
2480
|
function isOnScrollWithInfo(onScroll) {
|
|
5110
2481
|
return onScroll.length === 2;
|
|
5111
2482
|
}
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
* the future we can also offer ViewTimeline support.
|
|
5115
|
-
*/
|
|
5116
|
-
function needsElementTracking(options) {
|
|
5117
|
-
return options && (options.target || options.offset);
|
|
5118
|
-
}
|
|
5119
|
-
function scrollFunction(onScroll, options) {
|
|
5120
|
-
if (isOnScrollWithInfo(onScroll) || needsElementTracking(options)) {
|
|
2483
|
+
function attachToFunction(onScroll, options) {
|
|
2484
|
+
if (isOnScrollWithInfo(onScroll)) {
|
|
5121
2485
|
return scrollInfo((info) => {
|
|
5122
2486
|
onScroll(info[options.axis].progress, info);
|
|
5123
2487
|
}, options);
|
|
5124
2488
|
}
|
|
5125
2489
|
else {
|
|
5126
|
-
return observeTimeline(onScroll, getTimeline(options));
|
|
2490
|
+
return motionDom.observeTimeline(onScroll, getTimeline(options));
|
|
5127
2491
|
}
|
|
5128
2492
|
}
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
animation.pause();
|
|
5133
|
-
return scrollInfo((info) => {
|
|
5134
|
-
animation.time = animation.duration * info[options.axis].progress;
|
|
5135
|
-
}, options);
|
|
5136
|
-
}
|
|
5137
|
-
else {
|
|
5138
|
-
const timeline = getTimeline(options);
|
|
5139
|
-
if (animation.attachTimeline) {
|
|
5140
|
-
return animation.attachTimeline(timeline, (valueAnimation) => {
|
|
5141
|
-
valueAnimation.pause();
|
|
5142
|
-
return observeTimeline((progress) => {
|
|
5143
|
-
valueAnimation.time = valueAnimation.duration * progress;
|
|
5144
|
-
}, timeline);
|
|
5145
|
-
});
|
|
5146
|
-
}
|
|
5147
|
-
else {
|
|
5148
|
-
return motionUtils.noop;
|
|
5149
|
-
}
|
|
5150
|
-
}
|
|
5151
|
-
}
|
|
5152
|
-
function scroll(onScroll, { axis = "y", ...options } = {}) {
|
|
5153
|
-
const optionsWithDefaults = { axis, ...options };
|
|
2493
|
+
|
|
2494
|
+
function scroll(onScroll, { axis = "y", container = document.documentElement, ...options } = {}) {
|
|
2495
|
+
const optionsWithDefaults = { axis, container, ...options };
|
|
5154
2496
|
return typeof onScroll === "function"
|
|
5155
|
-
?
|
|
5156
|
-
:
|
|
2497
|
+
? attachToFunction(onScroll, optionsWithDefaults)
|
|
2498
|
+
: attachToAnimation(onScroll, optionsWithDefaults);
|
|
5157
2499
|
}
|
|
5158
2500
|
|
|
5159
2501
|
const thresholds = {
|
|
@@ -5196,18 +2538,6 @@ function inView(elementOrSelector, onStart, { root, margin: rootMargin, amount =
|
|
|
5196
2538
|
return () => observer.disconnect();
|
|
5197
2539
|
}
|
|
5198
2540
|
|
|
5199
|
-
function steps(numSteps, direction = "end") {
|
|
5200
|
-
return (progress) => {
|
|
5201
|
-
progress =
|
|
5202
|
-
direction === "end"
|
|
5203
|
-
? Math.min(progress, 0.999)
|
|
5204
|
-
: Math.max(progress, 0.001);
|
|
5205
|
-
const expanded = progress * numSteps;
|
|
5206
|
-
const rounded = direction === "end" ? Math.floor(expanded) : Math.ceil(expanded);
|
|
5207
|
-
return clamp(0, 1, rounded / numSteps);
|
|
5208
|
-
};
|
|
5209
|
-
}
|
|
5210
|
-
|
|
5211
2541
|
function getOriginIndex(from, total) {
|
|
5212
2542
|
if (from === "first") {
|
|
5213
2543
|
return 0;
|
|
@@ -5224,7 +2554,7 @@ function stagger(duration = 0.1, { startDelay = 0, from = 0, ease } = {}) {
|
|
|
5224
2554
|
let delay = duration * distance;
|
|
5225
2555
|
if (ease) {
|
|
5226
2556
|
const maxDelay = total * duration;
|
|
5227
|
-
const easingFunction = easingDefinitionToFunction(ease);
|
|
2557
|
+
const easingFunction = motionUtils.easingDefinitionToFunction(ease);
|
|
5228
2558
|
delay = easingFunction(delay / maxDelay) * maxDelay;
|
|
5229
2559
|
}
|
|
5230
2560
|
return startDelay + delay;
|
|
@@ -5243,7 +2573,7 @@ function delay(callback, timeout) {
|
|
|
5243
2573
|
callback(elapsed - timeout);
|
|
5244
2574
|
}
|
|
5245
2575
|
};
|
|
5246
|
-
motionDom.frame.
|
|
2576
|
+
motionDom.frame.setup(checkElapsed, true);
|
|
5247
2577
|
return () => motionDom.cancelFrame(checkElapsed);
|
|
5248
2578
|
}
|
|
5249
2579
|
function delayInSeconds(callback, timeout) {
|
|
@@ -5269,99 +2599,33 @@ function transform(...args) {
|
|
|
5269
2599
|
const inputRange = args[1 + argOffset];
|
|
5270
2600
|
const outputRange = args[2 + argOffset];
|
|
5271
2601
|
const options = args[3 + argOffset];
|
|
5272
|
-
const interpolator = interpolate(inputRange, outputRange, {
|
|
2602
|
+
const interpolator = motionDom.interpolate(inputRange, outputRange, {
|
|
5273
2603
|
mixer: getMixer(outputRange[0]),
|
|
5274
2604
|
...options,
|
|
5275
2605
|
});
|
|
5276
2606
|
return useImmediate ? interpolator(inputValue) : interpolator;
|
|
5277
2607
|
}
|
|
5278
2608
|
|
|
5279
|
-
Object.defineProperty(exports, "MotionValue", {
|
|
5280
|
-
enumerable: true,
|
|
5281
|
-
get: function () { return motionDom.MotionValue; }
|
|
5282
|
-
});
|
|
5283
|
-
Object.defineProperty(exports, "cancelFrame", {
|
|
5284
|
-
enumerable: true,
|
|
5285
|
-
get: function () { return motionDom.cancelFrame; }
|
|
5286
|
-
});
|
|
5287
|
-
Object.defineProperty(exports, "cancelSync", {
|
|
5288
|
-
enumerable: true,
|
|
5289
|
-
get: function () { return motionDom.cancelSync; }
|
|
5290
|
-
});
|
|
5291
|
-
Object.defineProperty(exports, "frame", {
|
|
5292
|
-
enumerable: true,
|
|
5293
|
-
get: function () { return motionDom.frame; }
|
|
5294
|
-
});
|
|
5295
|
-
Object.defineProperty(exports, "frameData", {
|
|
5296
|
-
enumerable: true,
|
|
5297
|
-
get: function () { return motionDom.frameData; }
|
|
5298
|
-
});
|
|
5299
|
-
Object.defineProperty(exports, "hover", {
|
|
5300
|
-
enumerable: true,
|
|
5301
|
-
get: function () { return motionDom.hover; }
|
|
5302
|
-
});
|
|
5303
|
-
Object.defineProperty(exports, "isDragActive", {
|
|
5304
|
-
enumerable: true,
|
|
5305
|
-
get: function () { return motionDom.isDragActive; }
|
|
5306
|
-
});
|
|
5307
|
-
Object.defineProperty(exports, "motionValue", {
|
|
5308
|
-
enumerable: true,
|
|
5309
|
-
get: function () { return motionDom.motionValue; }
|
|
5310
|
-
});
|
|
5311
|
-
Object.defineProperty(exports, "press", {
|
|
5312
|
-
enumerable: true,
|
|
5313
|
-
get: function () { return motionDom.press; }
|
|
5314
|
-
});
|
|
5315
|
-
Object.defineProperty(exports, "sync", {
|
|
5316
|
-
enumerable: true,
|
|
5317
|
-
get: function () { return motionDom.sync; }
|
|
5318
|
-
});
|
|
5319
|
-
Object.defineProperty(exports, "time", {
|
|
5320
|
-
enumerable: true,
|
|
5321
|
-
get: function () { return motionDom.time; }
|
|
5322
|
-
});
|
|
5323
|
-
Object.defineProperty(exports, "invariant", {
|
|
5324
|
-
enumerable: true,
|
|
5325
|
-
get: function () { return motionUtils.invariant; }
|
|
5326
|
-
});
|
|
5327
|
-
Object.defineProperty(exports, "noop", {
|
|
5328
|
-
enumerable: true,
|
|
5329
|
-
get: function () { return motionUtils.noop; }
|
|
5330
|
-
});
|
|
5331
|
-
Object.defineProperty(exports, "progress", {
|
|
5332
|
-
enumerable: true,
|
|
5333
|
-
get: function () { return motionUtils.progress; }
|
|
5334
|
-
});
|
|
5335
2609
|
exports.animate = animate;
|
|
5336
2610
|
exports.animateMini = animateMini;
|
|
5337
|
-
exports.anticipate = anticipate;
|
|
5338
|
-
exports.backIn = backIn;
|
|
5339
|
-
exports.backInOut = backInOut;
|
|
5340
|
-
exports.backOut = backOut;
|
|
5341
|
-
exports.circIn = circIn;
|
|
5342
|
-
exports.circInOut = circInOut;
|
|
5343
|
-
exports.circOut = circOut;
|
|
5344
|
-
exports.clamp = clamp;
|
|
5345
2611
|
exports.createScopedAnimate = createScopedAnimate;
|
|
5346
|
-
exports.cubicBezier = cubicBezier;
|
|
5347
2612
|
exports.delay = delayInSeconds;
|
|
5348
2613
|
exports.distance = distance;
|
|
5349
2614
|
exports.distance2D = distance2D;
|
|
5350
|
-
exports.easeIn = easeIn;
|
|
5351
|
-
exports.easeInOut = easeInOut;
|
|
5352
|
-
exports.easeOut = easeOut;
|
|
5353
2615
|
exports.inView = inView;
|
|
5354
|
-
exports.inertia = inertia;
|
|
5355
|
-
exports.interpolate = interpolate;
|
|
5356
|
-
exports.keyframes = keyframes;
|
|
5357
|
-
exports.mirrorEasing = mirrorEasing;
|
|
5358
|
-
exports.mix = mix;
|
|
5359
|
-
exports.pipe = pipe;
|
|
5360
|
-
exports.reverseEasing = reverseEasing;
|
|
5361
2616
|
exports.scroll = scroll;
|
|
5362
2617
|
exports.scrollInfo = scrollInfo;
|
|
5363
|
-
exports.spring = spring;
|
|
5364
2618
|
exports.stagger = stagger;
|
|
5365
|
-
exports.steps = steps;
|
|
5366
2619
|
exports.transform = transform;
|
|
5367
|
-
|
|
2620
|
+
Object.keys(motionDom).forEach(function (k) {
|
|
2621
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
2622
|
+
enumerable: true,
|
|
2623
|
+
get: function () { return motionDom[k]; }
|
|
2624
|
+
});
|
|
2625
|
+
});
|
|
2626
|
+
Object.keys(motionUtils).forEach(function (k) {
|
|
2627
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
2628
|
+
enumerable: true,
|
|
2629
|
+
get: function () { return motionUtils[k]; }
|
|
2630
|
+
});
|
|
2631
|
+
});
|