framer-motion 7.7.3 → 7.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +1966 -1860
- package/dist/es/animation/animate.mjs +2 -2
- package/dist/es/animation/create-accelerated-animation.mjs +8 -0
- package/dist/es/animation/create-instant-animation.mjs +12 -0
- package/dist/es/animation/{animation-controls.mjs → hooks/animation-controls.mjs} +2 -2
- package/dist/es/animation/{use-animated-state.mjs → hooks/use-animated-state.mjs} +6 -6
- package/dist/es/animation/{use-animation.mjs → hooks/use-animation.mjs} +1 -1
- package/dist/es/animation/index.mjs +121 -0
- package/dist/es/animation/legacy-popmotion/decay.mjs +11 -4
- package/dist/es/animation/legacy-popmotion/index.mjs +23 -15
- package/dist/es/animation/legacy-popmotion/inertia.mjs +14 -8
- package/dist/es/animation/legacy-popmotion/keyframes.mjs +21 -13
- package/dist/es/animation/legacy-popmotion/spring.mjs +33 -39
- package/dist/es/animation/optimized-appear/data-id.mjs +6 -0
- package/dist/es/animation/optimized-appear/handoff.mjs +34 -0
- package/dist/es/animation/optimized-appear/start.mjs +15 -0
- package/dist/es/animation/optimized-appear/store-id.mjs +3 -0
- package/dist/es/animation/utils/default-transitions.mjs +9 -14
- package/dist/es/animation/utils/keyframes.mjs +41 -0
- package/dist/es/animation/utils/transitions.mjs +1 -171
- package/dist/es/animation/waapi/easing.mjs +3 -0
- package/dist/es/animation/waapi/index.mjs +16 -0
- package/dist/es/animation/waapi/supports.mjs +17 -0
- package/dist/es/gestures/drag/VisualElementDragControls.mjs +2 -2
- package/dist/es/index.mjs +6 -3
- package/dist/es/render/utils/animation.mjs +15 -3
- package/dist/es/render/utils/motion-values.mjs +1 -1
- package/dist/es/utils/delay.mjs +3 -0
- package/dist/es/value/index.mjs +2 -2
- package/dist/es/value/use-spring.mjs +1 -2
- package/dist/framer-motion.dev.js +1971 -1865
- package/dist/framer-motion.js +1 -1
- package/dist/index.d.ts +424 -341
- package/dist/projection.dev.js +1655 -1623
- 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-motion.js +1 -1
- package/dist/size-webpack-dom-animation.js +1 -1
- package/dist/size-webpack-dom-max.js +1 -1
- package/dist/three-entry.d.ts +289 -282
- package/package.json +11 -9
package/dist/projection.dev.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
2
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) :
|
|
3
3
|
typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) :
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Projection = {}, global.
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Projection = {}, global.React));
|
|
5
5
|
})(this, (function (exports, react) { 'use strict';
|
|
6
6
|
|
|
7
7
|
/*
|
|
@@ -161,531 +161,110 @@
|
|
|
161
161
|
onNextFrame(processFrame);
|
|
162
162
|
};
|
|
163
163
|
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
var warning = function () { };
|
|
165
|
+
var invariant = function () { };
|
|
166
|
+
{
|
|
167
|
+
warning = function (check, message) {
|
|
168
|
+
if (!check && typeof console !== 'undefined') {
|
|
169
|
+
console.warn(message);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
invariant = function (check, message) {
|
|
173
|
+
if (!check) {
|
|
174
|
+
throw new Error(message);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
167
177
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Converts seconds to milliseconds
|
|
181
|
+
*
|
|
182
|
+
* @param seconds - Time in seconds.
|
|
183
|
+
* @return milliseconds - Converted time in milliseconds.
|
|
184
|
+
*/
|
|
185
|
+
const secondsToMilliseconds = (seconds) => seconds * 1000;
|
|
186
|
+
|
|
187
|
+
const instantAnimationState = {
|
|
188
|
+
current: false,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
*
|
|
193
|
+
*/
|
|
194
|
+
function createAcceleratedAnimation() {
|
|
195
|
+
return () => { };
|
|
172
196
|
}
|
|
173
197
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (!numSubscriptions)
|
|
185
|
-
return;
|
|
186
|
-
if (numSubscriptions === 1) {
|
|
187
|
-
/**
|
|
188
|
-
* If there's only a single handler we can just call it without invoking a loop.
|
|
189
|
-
*/
|
|
190
|
-
this.subscriptions[0](a, b, c);
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
for (let i = 0; i < numSubscriptions; i++) {
|
|
194
|
-
/**
|
|
195
|
-
* Check whether the handler exists before firing as it's possible
|
|
196
|
-
* the subscriptions were modified during this loop running.
|
|
197
|
-
*/
|
|
198
|
-
const handler = this.subscriptions[i];
|
|
199
|
-
handler && handler(a, b, c);
|
|
200
|
-
}
|
|
198
|
+
/**
|
|
199
|
+
* Timeout defined in ms
|
|
200
|
+
*/
|
|
201
|
+
function delay(callback, timeout) {
|
|
202
|
+
const start = performance.now();
|
|
203
|
+
const checkElapsed = ({ timestamp }) => {
|
|
204
|
+
const elapsed = timestamp - start;
|
|
205
|
+
if (elapsed >= timeout) {
|
|
206
|
+
cancelSync.read(checkElapsed);
|
|
207
|
+
callback(elapsed - timeout);
|
|
201
208
|
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
clear() {
|
|
207
|
-
this.subscriptions.length = 0;
|
|
208
|
-
}
|
|
209
|
+
};
|
|
210
|
+
sync.read(checkElapsed, true);
|
|
211
|
+
return () => cancelSync.read(checkElapsed);
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
|
|
212
|
-
|
|
214
|
+
function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
|
|
215
|
+
const setValue = () => {
|
|
216
|
+
onUpdate && onUpdate(keyframes[keyframes.length - 1]);
|
|
217
|
+
onComplete && onComplete();
|
|
218
|
+
return () => { };
|
|
219
|
+
};
|
|
220
|
+
return elapsed ? delay(setValue, -elapsed) : setValue();
|
|
221
|
+
}
|
|
213
222
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
223
|
+
// Accepts an easing function and returns a new one that outputs mirrored values for
|
|
224
|
+
// the second half of the animation. Turns easeIn into easeInOut.
|
|
225
|
+
const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
|
|
226
|
+
|
|
227
|
+
// Accepts an easing function and returns a new one that outputs reversed values.
|
|
228
|
+
// Turns easeIn into easeOut.
|
|
229
|
+
const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
|
|
230
|
+
|
|
231
|
+
const easeIn = (p) => p * p;
|
|
232
|
+
const easeOut = reverseEasing(easeIn);
|
|
233
|
+
const easeInOut = mirrorEasing(easeIn);
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* TODO: When we move from string as a source of truth to data models
|
|
237
|
+
* everything in this folder should probably be referred to as models vs types
|
|
238
|
+
*/
|
|
239
|
+
// If this number is a decimal, make it just five decimal places
|
|
240
|
+
// to avoid exponents
|
|
241
|
+
const sanitize = (v) => Math.round(v * 100000) / 100000;
|
|
242
|
+
const floatRegex = /(-)?([\d]*\.?[\d])+/g;
|
|
243
|
+
const colorRegex = /(#[0-9a-f]{6}|#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))/gi;
|
|
244
|
+
const singleColorRegex = /^(#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))$/i;
|
|
245
|
+
function isString(v) {
|
|
246
|
+
return typeof v === "string";
|
|
219
247
|
}
|
|
220
248
|
|
|
221
|
-
const
|
|
222
|
-
|
|
249
|
+
const clamp = (min, max, v) => Math.min(Math.max(v, min), max);
|
|
250
|
+
|
|
251
|
+
const number = {
|
|
252
|
+
test: (v) => typeof v === "number",
|
|
253
|
+
parse: parseFloat,
|
|
254
|
+
transform: (v) => v,
|
|
255
|
+
};
|
|
256
|
+
const alpha = {
|
|
257
|
+
...number,
|
|
258
|
+
transform: (v) => clamp(0, 1, v),
|
|
223
259
|
};
|
|
260
|
+
const scale = {
|
|
261
|
+
...number,
|
|
262
|
+
default: 1,
|
|
263
|
+
};
|
|
264
|
+
|
|
224
265
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* @public
|
|
228
|
-
*/
|
|
229
|
-
class MotionValue {
|
|
230
|
-
/**
|
|
231
|
-
* @param init - The initiating value
|
|
232
|
-
* @param config - Optional configuration options
|
|
233
|
-
*
|
|
234
|
-
* - `transformer`: A function to transform incoming values with.
|
|
235
|
-
*
|
|
236
|
-
* @internal
|
|
237
|
-
*/
|
|
238
|
-
constructor(init) {
|
|
239
|
-
/**
|
|
240
|
-
* This will be replaced by the build step with the latest version number.
|
|
241
|
-
* When MotionValues are provided to motion components, warn if versions are mixed.
|
|
242
|
-
*/
|
|
243
|
-
this.version = "7.7.3";
|
|
244
|
-
/**
|
|
245
|
-
* Duration, in milliseconds, since last updating frame.
|
|
246
|
-
*
|
|
247
|
-
* @internal
|
|
248
|
-
*/
|
|
249
|
-
this.timeDelta = 0;
|
|
250
|
-
/**
|
|
251
|
-
* Timestamp of the last time this `MotionValue` was updated.
|
|
252
|
-
*
|
|
253
|
-
* @internal
|
|
254
|
-
*/
|
|
255
|
-
this.lastUpdated = 0;
|
|
256
|
-
/**
|
|
257
|
-
* Functions to notify when the `MotionValue` updates.
|
|
258
|
-
*
|
|
259
|
-
* @internal
|
|
260
|
-
*/
|
|
261
|
-
this.updateSubscribers = new SubscriptionManager();
|
|
262
|
-
/**
|
|
263
|
-
* Functions to notify when the velocity updates.
|
|
264
|
-
*
|
|
265
|
-
* @internal
|
|
266
|
-
*/
|
|
267
|
-
this.velocityUpdateSubscribers = new SubscriptionManager();
|
|
268
|
-
/**
|
|
269
|
-
* Functions to notify when the `MotionValue` updates and `render` is set to `true`.
|
|
270
|
-
*
|
|
271
|
-
* @internal
|
|
272
|
-
*/
|
|
273
|
-
this.renderSubscribers = new SubscriptionManager();
|
|
274
|
-
/**
|
|
275
|
-
* Tracks whether this value can output a velocity. Currently this is only true
|
|
276
|
-
* if the value is numerical, but we might be able to widen the scope here and support
|
|
277
|
-
* other value types.
|
|
278
|
-
*
|
|
279
|
-
* @internal
|
|
280
|
-
*/
|
|
281
|
-
this.canTrackVelocity = false;
|
|
282
|
-
this.updateAndNotify = (v, render = true) => {
|
|
283
|
-
this.prev = this.current;
|
|
284
|
-
this.current = v;
|
|
285
|
-
// Update timestamp
|
|
286
|
-
const { delta, timestamp } = frameData;
|
|
287
|
-
if (this.lastUpdated !== timestamp) {
|
|
288
|
-
this.timeDelta = delta;
|
|
289
|
-
this.lastUpdated = timestamp;
|
|
290
|
-
sync.postRender(this.scheduleVelocityCheck);
|
|
291
|
-
}
|
|
292
|
-
// Update update subscribers
|
|
293
|
-
if (this.prev !== this.current) {
|
|
294
|
-
this.updateSubscribers.notify(this.current);
|
|
295
|
-
}
|
|
296
|
-
// Update velocity subscribers
|
|
297
|
-
if (this.velocityUpdateSubscribers.getSize()) {
|
|
298
|
-
this.velocityUpdateSubscribers.notify(this.getVelocity());
|
|
299
|
-
}
|
|
300
|
-
// Update render subscribers
|
|
301
|
-
if (render) {
|
|
302
|
-
this.renderSubscribers.notify(this.current);
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
/**
|
|
306
|
-
* Schedule a velocity check for the next frame.
|
|
307
|
-
*
|
|
308
|
-
* This is an instanced and bound function to prevent generating a new
|
|
309
|
-
* function once per frame.
|
|
310
|
-
*
|
|
311
|
-
* @internal
|
|
312
|
-
*/
|
|
313
|
-
this.scheduleVelocityCheck = () => sync.postRender(this.velocityCheck);
|
|
314
|
-
/**
|
|
315
|
-
* Updates `prev` with `current` if the value hasn't been updated this frame.
|
|
316
|
-
* This ensures velocity calculations return `0`.
|
|
317
|
-
*
|
|
318
|
-
* This is an instanced and bound function to prevent generating a new
|
|
319
|
-
* function once per frame.
|
|
320
|
-
*
|
|
321
|
-
* @internal
|
|
322
|
-
*/
|
|
323
|
-
this.velocityCheck = ({ timestamp }) => {
|
|
324
|
-
if (timestamp !== this.lastUpdated) {
|
|
325
|
-
this.prev = this.current;
|
|
326
|
-
this.velocityUpdateSubscribers.notify(this.getVelocity());
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
this.hasAnimated = false;
|
|
330
|
-
this.prev = this.current = init;
|
|
331
|
-
this.canTrackVelocity = isFloat(this.current);
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* Adds a function that will be notified when the `MotionValue` is updated.
|
|
335
|
-
*
|
|
336
|
-
* It returns a function that, when called, will cancel the subscription.
|
|
337
|
-
*
|
|
338
|
-
* When calling `onChange` inside a React component, it should be wrapped with the
|
|
339
|
-
* `useEffect` hook. As it returns an unsubscribe function, this should be returned
|
|
340
|
-
* from the `useEffect` function to ensure you don't add duplicate subscribers..
|
|
341
|
-
*
|
|
342
|
-
* ```jsx
|
|
343
|
-
* export const MyComponent = () => {
|
|
344
|
-
* const x = useMotionValue(0)
|
|
345
|
-
* const y = useMotionValue(0)
|
|
346
|
-
* const opacity = useMotionValue(1)
|
|
347
|
-
*
|
|
348
|
-
* useEffect(() => {
|
|
349
|
-
* function updateOpacity() {
|
|
350
|
-
* const maxXY = Math.max(x.get(), y.get())
|
|
351
|
-
* const newOpacity = transform(maxXY, [0, 100], [1, 0])
|
|
352
|
-
* opacity.set(newOpacity)
|
|
353
|
-
* }
|
|
354
|
-
*
|
|
355
|
-
* const unsubscribeX = x.onChange(updateOpacity)
|
|
356
|
-
* const unsubscribeY = y.onChange(updateOpacity)
|
|
357
|
-
*
|
|
358
|
-
* return () => {
|
|
359
|
-
* unsubscribeX()
|
|
360
|
-
* unsubscribeY()
|
|
361
|
-
* }
|
|
362
|
-
* }, [])
|
|
363
|
-
*
|
|
364
|
-
* return <motion.div style={{ x }} />
|
|
365
|
-
* }
|
|
366
|
-
* ```
|
|
367
|
-
*
|
|
368
|
-
* @privateRemarks
|
|
369
|
-
*
|
|
370
|
-
* We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
|
|
371
|
-
*
|
|
372
|
-
* ```jsx
|
|
373
|
-
* useOnChange(x, () => {})
|
|
374
|
-
* ```
|
|
375
|
-
*
|
|
376
|
-
* @param subscriber - A function that receives the latest value.
|
|
377
|
-
* @returns A function that, when called, will cancel this subscription.
|
|
378
|
-
*
|
|
379
|
-
* @public
|
|
380
|
-
*/
|
|
381
|
-
onChange(subscription) {
|
|
382
|
-
return this.updateSubscribers.add(subscription);
|
|
383
|
-
}
|
|
384
|
-
clearListeners() {
|
|
385
|
-
this.updateSubscribers.clear();
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Adds a function that will be notified when the `MotionValue` requests a render.
|
|
389
|
-
*
|
|
390
|
-
* @param subscriber - A function that's provided the latest value.
|
|
391
|
-
* @returns A function that, when called, will cancel this subscription.
|
|
392
|
-
*
|
|
393
|
-
* @internal
|
|
394
|
-
*/
|
|
395
|
-
onRenderRequest(subscription) {
|
|
396
|
-
// Render immediately
|
|
397
|
-
subscription(this.get());
|
|
398
|
-
return this.renderSubscribers.add(subscription);
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Attaches a passive effect to the `MotionValue`.
|
|
402
|
-
*
|
|
403
|
-
* @internal
|
|
404
|
-
*/
|
|
405
|
-
attach(passiveEffect) {
|
|
406
|
-
this.passiveEffect = passiveEffect;
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Sets the state of the `MotionValue`.
|
|
410
|
-
*
|
|
411
|
-
* @remarks
|
|
412
|
-
*
|
|
413
|
-
* ```jsx
|
|
414
|
-
* const x = useMotionValue(0)
|
|
415
|
-
* x.set(10)
|
|
416
|
-
* ```
|
|
417
|
-
*
|
|
418
|
-
* @param latest - Latest value to set.
|
|
419
|
-
* @param render - Whether to notify render subscribers. Defaults to `true`
|
|
420
|
-
*
|
|
421
|
-
* @public
|
|
422
|
-
*/
|
|
423
|
-
set(v, render = true) {
|
|
424
|
-
if (!render || !this.passiveEffect) {
|
|
425
|
-
this.updateAndNotify(v, render);
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
this.passiveEffect(v, this.updateAndNotify);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Returns the latest state of `MotionValue`
|
|
433
|
-
*
|
|
434
|
-
* @returns - The latest state of `MotionValue`
|
|
435
|
-
*
|
|
436
|
-
* @public
|
|
437
|
-
*/
|
|
438
|
-
get() {
|
|
439
|
-
return this.current;
|
|
440
|
-
}
|
|
441
|
-
/**
|
|
442
|
-
* @public
|
|
443
|
-
*/
|
|
444
|
-
getPrevious() {
|
|
445
|
-
return this.prev;
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Returns the latest velocity of `MotionValue`
|
|
449
|
-
*
|
|
450
|
-
* @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
|
|
451
|
-
*
|
|
452
|
-
* @public
|
|
453
|
-
*/
|
|
454
|
-
getVelocity() {
|
|
455
|
-
// This could be isFloat(this.prev) && isFloat(this.current), but that would be wasteful
|
|
456
|
-
return this.canTrackVelocity
|
|
457
|
-
? // These casts could be avoided if parseFloat would be typed better
|
|
458
|
-
velocityPerSecond(parseFloat(this.current) -
|
|
459
|
-
parseFloat(this.prev), this.timeDelta)
|
|
460
|
-
: 0;
|
|
461
|
-
}
|
|
462
|
-
/**
|
|
463
|
-
* Registers a new animation to control this `MotionValue`. Only one
|
|
464
|
-
* animation can drive a `MotionValue` at one time.
|
|
465
|
-
*
|
|
466
|
-
* ```jsx
|
|
467
|
-
* value.start()
|
|
468
|
-
* ```
|
|
469
|
-
*
|
|
470
|
-
* @param animation - A function that starts the provided animation
|
|
471
|
-
*
|
|
472
|
-
* @internal
|
|
473
|
-
*/
|
|
474
|
-
start(animation) {
|
|
475
|
-
this.stop();
|
|
476
|
-
return new Promise((resolve) => {
|
|
477
|
-
this.hasAnimated = true;
|
|
478
|
-
this.stopAnimation = animation(resolve);
|
|
479
|
-
}).then(() => this.clearAnimation());
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* Stop the currently active animation.
|
|
483
|
-
*
|
|
484
|
-
* @public
|
|
485
|
-
*/
|
|
486
|
-
stop() {
|
|
487
|
-
if (this.stopAnimation)
|
|
488
|
-
this.stopAnimation();
|
|
489
|
-
this.clearAnimation();
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Returns `true` if this value is currently animating.
|
|
493
|
-
*
|
|
494
|
-
* @public
|
|
495
|
-
*/
|
|
496
|
-
isAnimating() {
|
|
497
|
-
return !!this.stopAnimation;
|
|
498
|
-
}
|
|
499
|
-
clearAnimation() {
|
|
500
|
-
this.stopAnimation = null;
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Destroy and clean up subscribers to this `MotionValue`.
|
|
504
|
-
*
|
|
505
|
-
* The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
|
|
506
|
-
* handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
|
|
507
|
-
* created a `MotionValue` via the `motionValue` function.
|
|
508
|
-
*
|
|
509
|
-
* @public
|
|
510
|
-
*/
|
|
511
|
-
destroy() {
|
|
512
|
-
this.updateSubscribers.clear();
|
|
513
|
-
this.renderSubscribers.clear();
|
|
514
|
-
this.stop();
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
function motionValue(init) {
|
|
518
|
-
return new MotionValue(init);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
const isMotionValue = (value) => !!(value === null || value === void 0 ? void 0 : value.getVelocity);
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Converts seconds to milliseconds
|
|
525
|
-
*
|
|
526
|
-
* @param seconds - Time in seconds.
|
|
527
|
-
* @return milliseconds - Converted time in milliseconds.
|
|
528
|
-
*/
|
|
529
|
-
const secondsToMilliseconds = (seconds) => seconds * 1000;
|
|
530
|
-
|
|
531
|
-
var warning = function () { };
|
|
532
|
-
var invariant = function () { };
|
|
533
|
-
{
|
|
534
|
-
warning = function (check, message) {
|
|
535
|
-
if (!check && typeof console !== 'undefined') {
|
|
536
|
-
console.warn(message);
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
invariant = function (check, message) {
|
|
540
|
-
if (!check) {
|
|
541
|
-
throw new Error(message);
|
|
542
|
-
}
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const noop = (any) => any;
|
|
547
|
-
|
|
548
|
-
/*
|
|
549
|
-
Bezier function generator
|
|
550
|
-
This has been modified from Gaëtan Renaudeau's BezierEasing
|
|
551
|
-
https://github.com/gre/bezier-easing/blob/master/src/index.js
|
|
552
|
-
https://github.com/gre/bezier-easing/blob/master/LICENSE
|
|
553
|
-
|
|
554
|
-
I've removed the newtonRaphsonIterate algo because in benchmarking it
|
|
555
|
-
wasn't noticiably faster than binarySubdivision, indeed removing it
|
|
556
|
-
usually improved times, depending on the curve.
|
|
557
|
-
I also removed the lookup table, as for the added bundle size and loop we're
|
|
558
|
-
only cutting ~4 or so subdivision iterations. I bumped the max iterations up
|
|
559
|
-
to 12 to compensate and this still tended to be faster for no perceivable
|
|
560
|
-
loss in accuracy.
|
|
561
|
-
Usage
|
|
562
|
-
const easeOut = cubicBezier(.17,.67,.83,.67);
|
|
563
|
-
const x = easeOut(0.5); // returns 0.627...
|
|
564
|
-
*/
|
|
565
|
-
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
|
|
566
|
-
const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
|
|
567
|
-
t;
|
|
568
|
-
const subdivisionPrecision = 0.0000001;
|
|
569
|
-
const subdivisionMaxIterations = 12;
|
|
570
|
-
function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
|
|
571
|
-
let currentX;
|
|
572
|
-
let currentT;
|
|
573
|
-
let i = 0;
|
|
574
|
-
do {
|
|
575
|
-
currentT = lowerBound + (upperBound - lowerBound) / 2.0;
|
|
576
|
-
currentX = calcBezier(currentT, mX1, mX2) - x;
|
|
577
|
-
if (currentX > 0.0) {
|
|
578
|
-
upperBound = currentT;
|
|
579
|
-
}
|
|
580
|
-
else {
|
|
581
|
-
lowerBound = currentT;
|
|
582
|
-
}
|
|
583
|
-
} while (Math.abs(currentX) > subdivisionPrecision &&
|
|
584
|
-
++i < subdivisionMaxIterations);
|
|
585
|
-
return currentT;
|
|
586
|
-
}
|
|
587
|
-
function cubicBezier(mX1, mY1, mX2, mY2) {
|
|
588
|
-
// If this is a linear gradient, return linear easing
|
|
589
|
-
if (mX1 === mY1 && mX2 === mY2)
|
|
590
|
-
return noop;
|
|
591
|
-
const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
|
|
592
|
-
// If animation is at start/end, return t without easing
|
|
593
|
-
return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// Accepts an easing function and returns a new one that outputs mirrored values for
|
|
597
|
-
// the second half of the animation. Turns easeIn into easeInOut.
|
|
598
|
-
const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
|
|
599
|
-
|
|
600
|
-
// Accepts an easing function and returns a new one that outputs reversed values.
|
|
601
|
-
// Turns easeIn into easeOut.
|
|
602
|
-
const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
|
|
603
|
-
|
|
604
|
-
const easeIn = (p) => p * p;
|
|
605
|
-
const easeOut = reverseEasing(easeIn);
|
|
606
|
-
const easeInOut = mirrorEasing(easeIn);
|
|
607
|
-
|
|
608
|
-
const circIn = (p) => 1 - Math.sin(Math.acos(p));
|
|
609
|
-
const circOut = reverseEasing(circIn);
|
|
610
|
-
const circInOut = mirrorEasing(circOut);
|
|
611
|
-
|
|
612
|
-
const createBackIn = (power = 1.525) => (p) => p * p * ((power + 1) * p - power);
|
|
613
|
-
const backIn = createBackIn();
|
|
614
|
-
const backOut = reverseEasing(backIn);
|
|
615
|
-
const backInOut = mirrorEasing(backIn);
|
|
616
|
-
|
|
617
|
-
const createAnticipate = (power) => {
|
|
618
|
-
const backEasing = createBackIn(power);
|
|
619
|
-
return (p) => (p *= 2) < 1
|
|
620
|
-
? 0.5 * backEasing(p)
|
|
621
|
-
: 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
|
|
622
|
-
};
|
|
623
|
-
const anticipate = createAnticipate();
|
|
624
|
-
|
|
625
|
-
const easingLookup = {
|
|
626
|
-
linear: noop,
|
|
627
|
-
easeIn,
|
|
628
|
-
easeInOut,
|
|
629
|
-
easeOut,
|
|
630
|
-
circIn,
|
|
631
|
-
circInOut,
|
|
632
|
-
circOut,
|
|
633
|
-
backIn,
|
|
634
|
-
backInOut,
|
|
635
|
-
backOut,
|
|
636
|
-
anticipate,
|
|
637
|
-
};
|
|
638
|
-
const easingDefinitionToFunction = (definition) => {
|
|
639
|
-
if (Array.isArray(definition)) {
|
|
640
|
-
// If cubic bezier definition, create bezier curve
|
|
641
|
-
invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
|
|
642
|
-
const [x1, y1, x2, y2] = definition;
|
|
643
|
-
return cubicBezier(x1, y1, x2, y2);
|
|
644
|
-
}
|
|
645
|
-
else if (typeof definition === "string") {
|
|
646
|
-
// Else lookup from table
|
|
647
|
-
invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
|
|
648
|
-
return easingLookup[definition];
|
|
649
|
-
}
|
|
650
|
-
return definition;
|
|
651
|
-
};
|
|
652
|
-
const isEasingArray = (ease) => {
|
|
653
|
-
return Array.isArray(ease) && typeof ease[0] !== "number";
|
|
654
|
-
};
|
|
655
|
-
|
|
656
|
-
/**
|
|
657
|
-
* TODO: When we move from string as a source of truth to data models
|
|
658
|
-
* everything in this folder should probably be referred to as models vs types
|
|
659
|
-
*/
|
|
660
|
-
// If this number is a decimal, make it just five decimal places
|
|
661
|
-
// to avoid exponents
|
|
662
|
-
const sanitize = (v) => Math.round(v * 100000) / 100000;
|
|
663
|
-
const floatRegex = /(-)?([\d]*\.?[\d])+/g;
|
|
664
|
-
const colorRegex = /(#[0-9a-f]{6}|#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))/gi;
|
|
665
|
-
const singleColorRegex = /^(#[0-9a-f]{3}|#(?:[0-9a-f]{2}){2,4}|(rgb|hsl)a?\((-?[\d\.]+%?[,\s]+){2}(-?[\d\.]+%?)\s*[\,\/]?\s*[\d\.]*%?\))$/i;
|
|
666
|
-
function isString(v) {
|
|
667
|
-
return typeof v === "string";
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
const clamp = (min, max, v) => Math.min(Math.max(v, min), max);
|
|
671
|
-
|
|
672
|
-
const number = {
|
|
673
|
-
test: (v) => typeof v === "number",
|
|
674
|
-
parse: parseFloat,
|
|
675
|
-
transform: (v) => v,
|
|
676
|
-
};
|
|
677
|
-
const alpha = {
|
|
678
|
-
...number,
|
|
679
|
-
transform: (v) => clamp(0, 1, v),
|
|
680
|
-
};
|
|
681
|
-
const scale = {
|
|
682
|
-
...number,
|
|
683
|
-
default: 1,
|
|
684
|
-
};
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
|
|
688
|
-
* but false if a number or multiple colors
|
|
266
|
+
* Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
|
|
267
|
+
* but false if a number or multiple colors
|
|
689
268
|
*/
|
|
690
269
|
const isColorString = (type, testProp) => (v) => {
|
|
691
270
|
return Boolean((isString(v) && singleColorRegex.test(v) && v.startsWith(type)) ||
|
|
@@ -800,1228 +379,1673 @@
|
|
|
800
379
|
return hsla.parse(v);
|
|
801
380
|
}
|
|
802
381
|
else {
|
|
803
|
-
return hex.parse(v);
|
|
382
|
+
return hex.parse(v);
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
transform: (v) => {
|
|
386
|
+
return isString(v)
|
|
387
|
+
? v
|
|
388
|
+
: v.hasOwnProperty("red")
|
|
389
|
+
? rgba.transform(v)
|
|
390
|
+
: hsla.transform(v);
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
/*
|
|
395
|
+
Value in range from progress
|
|
396
|
+
|
|
397
|
+
Given a lower limit and an upper limit, we return the value within
|
|
398
|
+
that range as expressed by progress (usually a number from 0 to 1)
|
|
399
|
+
|
|
400
|
+
So progress = 0.5 would change
|
|
401
|
+
|
|
402
|
+
from -------- to
|
|
403
|
+
|
|
404
|
+
to
|
|
405
|
+
|
|
406
|
+
from ---- to
|
|
407
|
+
|
|
408
|
+
E.g. from = 10, to = 20, progress = 0.5 => 15
|
|
409
|
+
|
|
410
|
+
@param [number]: Lower limit of range
|
|
411
|
+
@param [number]: Upper limit of range
|
|
412
|
+
@param [number]: The progress between lower and upper limits expressed 0-1
|
|
413
|
+
@return [number]: Value as calculated from progress within range (not limited within range)
|
|
414
|
+
*/
|
|
415
|
+
const mix = (from, to, progress) => -progress * from + progress * to + from;
|
|
416
|
+
|
|
417
|
+
// Adapted from https://gist.github.com/mjackson/5311256
|
|
418
|
+
function hueToRgb(p, q, t) {
|
|
419
|
+
if (t < 0)
|
|
420
|
+
t += 1;
|
|
421
|
+
if (t > 1)
|
|
422
|
+
t -= 1;
|
|
423
|
+
if (t < 1 / 6)
|
|
424
|
+
return p + (q - p) * 6 * t;
|
|
425
|
+
if (t < 1 / 2)
|
|
426
|
+
return q;
|
|
427
|
+
if (t < 2 / 3)
|
|
428
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
429
|
+
return p;
|
|
430
|
+
}
|
|
431
|
+
function hslaToRgba({ hue, saturation, lightness, alpha }) {
|
|
432
|
+
hue /= 360;
|
|
433
|
+
saturation /= 100;
|
|
434
|
+
lightness /= 100;
|
|
435
|
+
let red = 0;
|
|
436
|
+
let green = 0;
|
|
437
|
+
let blue = 0;
|
|
438
|
+
if (!saturation) {
|
|
439
|
+
red = green = blue = lightness;
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
const q = lightness < 0.5
|
|
443
|
+
? lightness * (1 + saturation)
|
|
444
|
+
: lightness + saturation - lightness * saturation;
|
|
445
|
+
const p = 2 * lightness - q;
|
|
446
|
+
red = hueToRgb(p, q, hue + 1 / 3);
|
|
447
|
+
green = hueToRgb(p, q, hue);
|
|
448
|
+
blue = hueToRgb(p, q, hue - 1 / 3);
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
red: Math.round(red * 255),
|
|
452
|
+
green: Math.round(green * 255),
|
|
453
|
+
blue: Math.round(blue * 255),
|
|
454
|
+
alpha,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Linear color space blending
|
|
459
|
+
// Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
|
|
460
|
+
// Demonstrated http://codepen.io/osublake/pen/xGVVaN
|
|
461
|
+
const mixLinearColor = (from, to, v) => {
|
|
462
|
+
const fromExpo = from * from;
|
|
463
|
+
return Math.sqrt(Math.max(0, v * (to * to - fromExpo) + fromExpo));
|
|
464
|
+
};
|
|
465
|
+
const colorTypes = [hex, rgba, hsla];
|
|
466
|
+
const getColorType = (v) => colorTypes.find((type) => type.test(v));
|
|
467
|
+
function asRGBA(color) {
|
|
468
|
+
const type = getColorType(color);
|
|
469
|
+
invariant(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
|
|
470
|
+
let model = type.parse(color);
|
|
471
|
+
if (type === hsla) {
|
|
472
|
+
// TODO Remove this cast - needed since Framer Motion's stricter typing
|
|
473
|
+
model = hslaToRgba(model);
|
|
474
|
+
}
|
|
475
|
+
return model;
|
|
476
|
+
}
|
|
477
|
+
const mixColor = (from, to) => {
|
|
478
|
+
const fromRGBA = asRGBA(from);
|
|
479
|
+
const toRGBA = asRGBA(to);
|
|
480
|
+
const blended = { ...fromRGBA };
|
|
481
|
+
return (v) => {
|
|
482
|
+
blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
|
|
483
|
+
blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
|
|
484
|
+
blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
|
|
485
|
+
blended.alpha = mix(fromRGBA.alpha, toRGBA.alpha, v);
|
|
486
|
+
return rgba.transform(blended);
|
|
487
|
+
};
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Pipe
|
|
492
|
+
* Compose other transformers to run linearily
|
|
493
|
+
* pipe(min(20), max(40))
|
|
494
|
+
* @param {...functions} transformers
|
|
495
|
+
* @return {function}
|
|
496
|
+
*/
|
|
497
|
+
const combineFunctions = (a, b) => (v) => b(a(v));
|
|
498
|
+
const pipe = (...transformers) => transformers.reduce(combineFunctions);
|
|
499
|
+
|
|
500
|
+
const colorToken = "${c}";
|
|
501
|
+
const numberToken = "${n}";
|
|
502
|
+
function test(v) {
|
|
503
|
+
var _a, _b;
|
|
504
|
+
return (isNaN(v) &&
|
|
505
|
+
isString(v) &&
|
|
506
|
+
(((_a = v.match(floatRegex)) === null || _a === void 0 ? void 0 : _a.length) || 0) +
|
|
507
|
+
(((_b = v.match(colorRegex)) === null || _b === void 0 ? void 0 : _b.length) || 0) >
|
|
508
|
+
0);
|
|
509
|
+
}
|
|
510
|
+
function analyseComplexValue(v) {
|
|
511
|
+
if (typeof v === "number")
|
|
512
|
+
v = `${v}`;
|
|
513
|
+
const values = [];
|
|
514
|
+
let numColors = 0;
|
|
515
|
+
let numNumbers = 0;
|
|
516
|
+
const colors = v.match(colorRegex);
|
|
517
|
+
if (colors) {
|
|
518
|
+
numColors = colors.length;
|
|
519
|
+
// Strip colors from input so they're not picked up by number regex.
|
|
520
|
+
// There's a better way to combine these regex searches, but its beyond my regex skills
|
|
521
|
+
v = v.replace(colorRegex, colorToken);
|
|
522
|
+
values.push(...colors.map(color.parse));
|
|
523
|
+
}
|
|
524
|
+
const numbers = v.match(floatRegex);
|
|
525
|
+
if (numbers) {
|
|
526
|
+
numNumbers = numbers.length;
|
|
527
|
+
v = v.replace(floatRegex, numberToken);
|
|
528
|
+
values.push(...numbers.map(number.parse));
|
|
529
|
+
}
|
|
530
|
+
return { values, numColors, numNumbers, tokenised: v };
|
|
531
|
+
}
|
|
532
|
+
function parse(v) {
|
|
533
|
+
return analyseComplexValue(v).values;
|
|
534
|
+
}
|
|
535
|
+
function createTransformer(source) {
|
|
536
|
+
const { values, numColors, tokenised } = analyseComplexValue(source);
|
|
537
|
+
const numValues = values.length;
|
|
538
|
+
return (v) => {
|
|
539
|
+
let output = tokenised;
|
|
540
|
+
for (let i = 0; i < numValues; i++) {
|
|
541
|
+
output = output.replace(i < numColors ? colorToken : numberToken, i < numColors
|
|
542
|
+
? color.transform(v[i])
|
|
543
|
+
: sanitize(v[i]));
|
|
544
|
+
}
|
|
545
|
+
return output;
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
const convertNumbersToZero = (v) => typeof v === "number" ? 0 : v;
|
|
549
|
+
function getAnimatableNone$1(v) {
|
|
550
|
+
const parsed = parse(v);
|
|
551
|
+
const transformer = createTransformer(v);
|
|
552
|
+
return transformer(parsed.map(convertNumbersToZero));
|
|
553
|
+
}
|
|
554
|
+
const complex = { test, parse, createTransformer, getAnimatableNone: getAnimatableNone$1 };
|
|
555
|
+
|
|
556
|
+
function getMixer(origin, target) {
|
|
557
|
+
if (typeof origin === "number") {
|
|
558
|
+
return (v) => mix(origin, target, v);
|
|
559
|
+
}
|
|
560
|
+
else if (color.test(origin)) {
|
|
561
|
+
return mixColor(origin, target);
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
return mixComplex(origin, target);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const mixArray = (from, to) => {
|
|
568
|
+
const output = [...from];
|
|
569
|
+
const numValues = output.length;
|
|
570
|
+
const blendValue = from.map((fromThis, i) => getMixer(fromThis, to[i]));
|
|
571
|
+
return (v) => {
|
|
572
|
+
for (let i = 0; i < numValues; i++) {
|
|
573
|
+
output[i] = blendValue[i](v);
|
|
574
|
+
}
|
|
575
|
+
return output;
|
|
576
|
+
};
|
|
577
|
+
};
|
|
578
|
+
const mixObject = (origin, target) => {
|
|
579
|
+
const output = { ...origin, ...target };
|
|
580
|
+
const blendValue = {};
|
|
581
|
+
for (const key in output) {
|
|
582
|
+
if (origin[key] !== undefined && target[key] !== undefined) {
|
|
583
|
+
blendValue[key] = getMixer(origin[key], target[key]);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return (v) => {
|
|
587
|
+
for (const key in blendValue) {
|
|
588
|
+
output[key] = blendValue[key](v);
|
|
589
|
+
}
|
|
590
|
+
return output;
|
|
591
|
+
};
|
|
592
|
+
};
|
|
593
|
+
const mixComplex = (origin, target) => {
|
|
594
|
+
const template = complex.createTransformer(target);
|
|
595
|
+
const originStats = analyseComplexValue(origin);
|
|
596
|
+
const targetStats = analyseComplexValue(target);
|
|
597
|
+
const canInterpolate = originStats.numColors === targetStats.numColors &&
|
|
598
|
+
originStats.numNumbers >= targetStats.numNumbers;
|
|
599
|
+
if (canInterpolate) {
|
|
600
|
+
return pipe(mixArray(originStats.values, targetStats.values), template);
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
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.`);
|
|
604
|
+
return (p) => `${p > 0 ? target : origin}`;
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
/*
|
|
609
|
+
Progress within given range
|
|
610
|
+
|
|
611
|
+
Given a lower limit and an upper limit, we return the progress
|
|
612
|
+
(expressed as a number 0-1) represented by the given value, and
|
|
613
|
+
limit that progress to within 0-1.
|
|
614
|
+
|
|
615
|
+
@param [number]: Lower limit
|
|
616
|
+
@param [number]: Upper limit
|
|
617
|
+
@param [number]: Value to find progress within given range
|
|
618
|
+
@return [number]: Progress of value within range as expressed 0-1
|
|
619
|
+
*/
|
|
620
|
+
const progress = (from, to, value) => {
|
|
621
|
+
const toFromDifference = to - from;
|
|
622
|
+
return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
const mixNumber = (from, to) => (p) => mix(from, to, p);
|
|
626
|
+
function detectMixerFactory(v) {
|
|
627
|
+
if (typeof v === "number") {
|
|
628
|
+
return mixNumber;
|
|
629
|
+
}
|
|
630
|
+
else if (typeof v === "string") {
|
|
631
|
+
if (color.test(v)) {
|
|
632
|
+
return mixColor;
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
return mixComplex;
|
|
804
636
|
}
|
|
805
|
-
},
|
|
806
|
-
transform: (v) => {
|
|
807
|
-
return isString(v)
|
|
808
|
-
? v
|
|
809
|
-
: v.hasOwnProperty("red")
|
|
810
|
-
? rgba.transform(v)
|
|
811
|
-
: hsla.transform(v);
|
|
812
|
-
},
|
|
813
|
-
};
|
|
814
|
-
|
|
815
|
-
const colorToken = "${c}";
|
|
816
|
-
const numberToken = "${n}";
|
|
817
|
-
function test(v) {
|
|
818
|
-
var _a, _b;
|
|
819
|
-
return (isNaN(v) &&
|
|
820
|
-
isString(v) &&
|
|
821
|
-
(((_a = v.match(floatRegex)) === null || _a === void 0 ? void 0 : _a.length) || 0) +
|
|
822
|
-
(((_b = v.match(colorRegex)) === null || _b === void 0 ? void 0 : _b.length) || 0) >
|
|
823
|
-
0);
|
|
824
|
-
}
|
|
825
|
-
function analyseComplexValue(v) {
|
|
826
|
-
if (typeof v === "number")
|
|
827
|
-
v = `${v}`;
|
|
828
|
-
const values = [];
|
|
829
|
-
let numColors = 0;
|
|
830
|
-
let numNumbers = 0;
|
|
831
|
-
const colors = v.match(colorRegex);
|
|
832
|
-
if (colors) {
|
|
833
|
-
numColors = colors.length;
|
|
834
|
-
// Strip colors from input so they're not picked up by number regex.
|
|
835
|
-
// There's a better way to combine these regex searches, but its beyond my regex skills
|
|
836
|
-
v = v.replace(colorRegex, colorToken);
|
|
837
|
-
values.push(...colors.map(color.parse));
|
|
838
637
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
numNumbers = numbers.length;
|
|
842
|
-
v = v.replace(floatRegex, numberToken);
|
|
843
|
-
values.push(...numbers.map(number.parse));
|
|
638
|
+
else if (Array.isArray(v)) {
|
|
639
|
+
return mixArray;
|
|
844
640
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
return
|
|
641
|
+
else if (typeof v === "object") {
|
|
642
|
+
return mixObject;
|
|
643
|
+
}
|
|
644
|
+
return mixNumber;
|
|
849
645
|
}
|
|
850
|
-
function
|
|
851
|
-
const
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
646
|
+
function createMixers(output, ease, customMixer) {
|
|
647
|
+
const mixers = [];
|
|
648
|
+
const mixerFactory = customMixer || detectMixerFactory(output[0]);
|
|
649
|
+
const numMixers = output.length - 1;
|
|
650
|
+
for (let i = 0; i < numMixers; i++) {
|
|
651
|
+
let mixer = mixerFactory(output[i], output[i + 1]);
|
|
652
|
+
if (ease) {
|
|
653
|
+
const easingFunction = Array.isArray(ease) ? ease[i] : ease;
|
|
654
|
+
mixer = pipe(easingFunction, mixer);
|
|
859
655
|
}
|
|
860
|
-
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
const convertNumbersToZero = (v) => typeof v === "number" ? 0 : v;
|
|
864
|
-
function getAnimatableNone$1(v) {
|
|
865
|
-
const parsed = parse(v);
|
|
866
|
-
const transformer = createTransformer(v);
|
|
867
|
-
return transformer(parsed.map(convertNumbersToZero));
|
|
656
|
+
mixers.push(mixer);
|
|
657
|
+
}
|
|
658
|
+
return mixers;
|
|
868
659
|
}
|
|
869
|
-
const complex = { test, parse, createTransformer, getAnimatableNone: getAnimatableNone$1 };
|
|
870
|
-
|
|
871
660
|
/**
|
|
872
|
-
*
|
|
661
|
+
* Create a function that maps from a numerical input array to a generic output array.
|
|
873
662
|
*
|
|
874
|
-
*
|
|
875
|
-
*
|
|
876
|
-
*
|
|
663
|
+
* Accepts:
|
|
664
|
+
* - Numbers
|
|
665
|
+
* - Colors (hex, hsl, hsla, rgb, rgba)
|
|
666
|
+
* - Complex (combinations of one or more numbers or strings)
|
|
877
667
|
*
|
|
878
|
-
*
|
|
668
|
+
* ```jsx
|
|
669
|
+
* const mixColor = interpolate([0, 1], ['#fff', '#000'])
|
|
670
|
+
*
|
|
671
|
+
* mixColor(0.5) // 'rgba(128, 128, 128, 1)'
|
|
672
|
+
* ```
|
|
673
|
+
*
|
|
674
|
+
* TODO Revist this approach once we've moved to data models for values,
|
|
675
|
+
* probably not needed to pregenerate mixer functions.
|
|
676
|
+
*
|
|
677
|
+
* @public
|
|
879
678
|
*/
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
// If
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
return true;
|
|
889
|
-
if (typeof value === "string" && // It's animatable if we have a string
|
|
890
|
-
complex.test(value) && // And it contains numbers and/or colors
|
|
891
|
-
!value.startsWith("url(") // Unless it starts with "url("
|
|
892
|
-
) {
|
|
893
|
-
return true;
|
|
679
|
+
function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
|
|
680
|
+
const inputLength = input.length;
|
|
681
|
+
invariant(inputLength === output.length, "Both input and output ranges must be the same length");
|
|
682
|
+
invariant(!ease || !Array.isArray(ease) || ease.length === inputLength - 1, "Array of easing functions must be of length `input.length - 1`, as it applies to the transitions **between** the defined values.");
|
|
683
|
+
// If input runs highest -> lowest, reverse both arrays
|
|
684
|
+
if (input[0] > input[inputLength - 1]) {
|
|
685
|
+
input = [...input].reverse();
|
|
686
|
+
output = [...output].reverse();
|
|
894
687
|
}
|
|
895
|
-
|
|
896
|
-
|
|
688
|
+
const mixers = createMixers(output, ease, mixer);
|
|
689
|
+
const numMixers = mixers.length;
|
|
690
|
+
const interpolator = (v) => {
|
|
691
|
+
let i = 0;
|
|
692
|
+
if (numMixers > 1) {
|
|
693
|
+
for (; i < input.length - 2; i++) {
|
|
694
|
+
if (v < input[i + 1])
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
const progressInRange = progress(input[i], input[i + 1], v);
|
|
699
|
+
return mixers[i](progressInRange);
|
|
700
|
+
};
|
|
701
|
+
return isClamp
|
|
702
|
+
? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
|
|
703
|
+
: interpolator;
|
|
704
|
+
}
|
|
897
705
|
|
|
898
|
-
const
|
|
899
|
-
|
|
706
|
+
const noop = (any) => any;
|
|
707
|
+
|
|
708
|
+
/*
|
|
709
|
+
Bezier function generator
|
|
710
|
+
This has been modified from Gaëtan Renaudeau's BezierEasing
|
|
711
|
+
https://github.com/gre/bezier-easing/blob/master/src/index.js
|
|
712
|
+
https://github.com/gre/bezier-easing/blob/master/LICENSE
|
|
713
|
+
|
|
714
|
+
I've removed the newtonRaphsonIterate algo because in benchmarking it
|
|
715
|
+
wasn't noticiably faster than binarySubdivision, indeed removing it
|
|
716
|
+
usually improved times, depending on the curve.
|
|
717
|
+
I also removed the lookup table, as for the added bundle size and loop we're
|
|
718
|
+
only cutting ~4 or so subdivision iterations. I bumped the max iterations up
|
|
719
|
+
to 12 to compensate and this still tended to be faster for no perceivable
|
|
720
|
+
loss in accuracy.
|
|
721
|
+
Usage
|
|
722
|
+
const easeOut = cubicBezier(.17,.67,.83,.67);
|
|
723
|
+
const x = easeOut(0.5); // returns 0.627...
|
|
724
|
+
*/
|
|
725
|
+
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
|
|
726
|
+
const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
|
|
727
|
+
t;
|
|
728
|
+
const subdivisionPrecision = 0.0000001;
|
|
729
|
+
const subdivisionMaxIterations = 12;
|
|
730
|
+
function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
|
|
731
|
+
let currentX;
|
|
732
|
+
let currentT;
|
|
733
|
+
let i = 0;
|
|
734
|
+
do {
|
|
735
|
+
currentT = lowerBound + (upperBound - lowerBound) / 2.0;
|
|
736
|
+
currentX = calcBezier(currentT, mX1, mX2) - x;
|
|
737
|
+
if (currentX > 0.0) {
|
|
738
|
+
upperBound = currentT;
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
lowerBound = currentT;
|
|
742
|
+
}
|
|
743
|
+
} while (Math.abs(currentX) > subdivisionPrecision &&
|
|
744
|
+
++i < subdivisionMaxIterations);
|
|
745
|
+
return currentT;
|
|
746
|
+
}
|
|
747
|
+
function cubicBezier(mX1, mY1, mX2, mY2) {
|
|
748
|
+
// If this is a linear gradient, return linear easing
|
|
749
|
+
if (mX1 === mY1 && mX2 === mY2)
|
|
750
|
+
return noop;
|
|
751
|
+
const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
|
|
752
|
+
// If animation is at start/end, return t without easing
|
|
753
|
+
return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const circIn = (p) => 1 - Math.sin(Math.acos(p));
|
|
757
|
+
const circOut = reverseEasing(circIn);
|
|
758
|
+
const circInOut = mirrorEasing(circOut);
|
|
759
|
+
|
|
760
|
+
const createBackIn = (power = 1.525) => (p) => p * p * ((power + 1) * p - power);
|
|
761
|
+
const backIn = createBackIn();
|
|
762
|
+
const backOut = reverseEasing(backIn);
|
|
763
|
+
const backInOut = mirrorEasing(backIn);
|
|
764
|
+
|
|
765
|
+
const createAnticipate = (power) => {
|
|
766
|
+
const backEasing = createBackIn(power);
|
|
767
|
+
return (p) => (p *= 2) < 1
|
|
768
|
+
? 0.5 * backEasing(p)
|
|
769
|
+
: 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
|
|
900
770
|
};
|
|
771
|
+
const anticipate = createAnticipate();
|
|
901
772
|
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
const linearTween = () => ({
|
|
915
|
-
type: "keyframes",
|
|
916
|
-
ease: "linear",
|
|
917
|
-
duration: 0.3,
|
|
918
|
-
});
|
|
919
|
-
const keyframes$1 = (values) => ({
|
|
920
|
-
type: "keyframes",
|
|
921
|
-
duration: 0.8,
|
|
922
|
-
values,
|
|
923
|
-
});
|
|
924
|
-
const defaultTransitions = {
|
|
925
|
-
x: underDampedSpring,
|
|
926
|
-
y: underDampedSpring,
|
|
927
|
-
z: underDampedSpring,
|
|
928
|
-
rotate: underDampedSpring,
|
|
929
|
-
rotateX: underDampedSpring,
|
|
930
|
-
rotateY: underDampedSpring,
|
|
931
|
-
rotateZ: underDampedSpring,
|
|
932
|
-
scaleX: criticallyDampedSpring,
|
|
933
|
-
scaleY: criticallyDampedSpring,
|
|
934
|
-
scale: criticallyDampedSpring,
|
|
935
|
-
opacity: linearTween,
|
|
936
|
-
backgroundColor: linearTween,
|
|
937
|
-
color: linearTween,
|
|
938
|
-
default: criticallyDampedSpring,
|
|
773
|
+
const easingLookup = {
|
|
774
|
+
linear: noop,
|
|
775
|
+
easeIn,
|
|
776
|
+
easeInOut,
|
|
777
|
+
easeOut,
|
|
778
|
+
circIn,
|
|
779
|
+
circInOut,
|
|
780
|
+
circOut,
|
|
781
|
+
backIn,
|
|
782
|
+
backInOut,
|
|
783
|
+
backOut,
|
|
784
|
+
anticipate,
|
|
939
785
|
};
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
786
|
+
const easingDefinitionToFunction = (definition) => {
|
|
787
|
+
if (Array.isArray(definition)) {
|
|
788
|
+
// If cubic bezier definition, create bezier curve
|
|
789
|
+
invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`);
|
|
790
|
+
const [x1, y1, x2, y2] = definition;
|
|
791
|
+
return cubicBezier(x1, y1, x2, y2);
|
|
944
792
|
}
|
|
945
|
-
else {
|
|
946
|
-
|
|
947
|
-
|
|
793
|
+
else if (typeof definition === "string") {
|
|
794
|
+
// Else lookup from table
|
|
795
|
+
invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`);
|
|
796
|
+
return easingLookup[definition];
|
|
948
797
|
}
|
|
949
|
-
return
|
|
950
|
-
};
|
|
951
|
-
|
|
952
|
-
/**
|
|
953
|
-
* Properties that should default to 1 or 100%
|
|
954
|
-
*/
|
|
955
|
-
const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
|
|
956
|
-
function applyDefaultFilter(v) {
|
|
957
|
-
const [name, value] = v.slice(0, -1).split("(");
|
|
958
|
-
if (name === "drop-shadow")
|
|
959
|
-
return v;
|
|
960
|
-
const [number] = value.match(floatRegex) || [];
|
|
961
|
-
if (!number)
|
|
962
|
-
return v;
|
|
963
|
-
const unit = value.replace(number, "");
|
|
964
|
-
let defaultValue = maxDefaults.has(name) ? 1 : 0;
|
|
965
|
-
if (number !== value)
|
|
966
|
-
defaultValue *= 100;
|
|
967
|
-
return name + "(" + defaultValue + unit + ")";
|
|
968
|
-
}
|
|
969
|
-
const functionRegex = /([a-z-]*)\(.*?\)/g;
|
|
970
|
-
const filter = {
|
|
971
|
-
...complex,
|
|
972
|
-
getAnimatableNone: (v) => {
|
|
973
|
-
const functions = v.match(functionRegex);
|
|
974
|
-
return functions ? functions.map(applyDefaultFilter).join(" ") : v;
|
|
975
|
-
},
|
|
976
|
-
};
|
|
977
|
-
|
|
978
|
-
const int = {
|
|
979
|
-
...number,
|
|
980
|
-
transform: Math.round,
|
|
798
|
+
return definition;
|
|
981
799
|
};
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
// Border props
|
|
985
|
-
borderWidth: px,
|
|
986
|
-
borderTopWidth: px,
|
|
987
|
-
borderRightWidth: px,
|
|
988
|
-
borderBottomWidth: px,
|
|
989
|
-
borderLeftWidth: px,
|
|
990
|
-
borderRadius: px,
|
|
991
|
-
radius: px,
|
|
992
|
-
borderTopLeftRadius: px,
|
|
993
|
-
borderTopRightRadius: px,
|
|
994
|
-
borderBottomRightRadius: px,
|
|
995
|
-
borderBottomLeftRadius: px,
|
|
996
|
-
// Positioning props
|
|
997
|
-
width: px,
|
|
998
|
-
maxWidth: px,
|
|
999
|
-
height: px,
|
|
1000
|
-
maxHeight: px,
|
|
1001
|
-
size: px,
|
|
1002
|
-
top: px,
|
|
1003
|
-
right: px,
|
|
1004
|
-
bottom: px,
|
|
1005
|
-
left: px,
|
|
1006
|
-
// Spacing props
|
|
1007
|
-
padding: px,
|
|
1008
|
-
paddingTop: px,
|
|
1009
|
-
paddingRight: px,
|
|
1010
|
-
paddingBottom: px,
|
|
1011
|
-
paddingLeft: px,
|
|
1012
|
-
margin: px,
|
|
1013
|
-
marginTop: px,
|
|
1014
|
-
marginRight: px,
|
|
1015
|
-
marginBottom: px,
|
|
1016
|
-
marginLeft: px,
|
|
1017
|
-
// Transform props
|
|
1018
|
-
rotate: degrees,
|
|
1019
|
-
rotateX: degrees,
|
|
1020
|
-
rotateY: degrees,
|
|
1021
|
-
rotateZ: degrees,
|
|
1022
|
-
scale,
|
|
1023
|
-
scaleX: scale,
|
|
1024
|
-
scaleY: scale,
|
|
1025
|
-
scaleZ: scale,
|
|
1026
|
-
skew: degrees,
|
|
1027
|
-
skewX: degrees,
|
|
1028
|
-
skewY: degrees,
|
|
1029
|
-
distance: px,
|
|
1030
|
-
translateX: px,
|
|
1031
|
-
translateY: px,
|
|
1032
|
-
translateZ: px,
|
|
1033
|
-
x: px,
|
|
1034
|
-
y: px,
|
|
1035
|
-
z: px,
|
|
1036
|
-
perspective: px,
|
|
1037
|
-
transformPerspective: px,
|
|
1038
|
-
opacity: alpha,
|
|
1039
|
-
originX: progressPercentage,
|
|
1040
|
-
originY: progressPercentage,
|
|
1041
|
-
originZ: px,
|
|
1042
|
-
// Misc
|
|
1043
|
-
zIndex: int,
|
|
1044
|
-
// SVG
|
|
1045
|
-
fillOpacity: alpha,
|
|
1046
|
-
strokeOpacity: alpha,
|
|
1047
|
-
numOctaves: int,
|
|
800
|
+
const isEasingArray = (ease) => {
|
|
801
|
+
return Array.isArray(ease) && typeof ease[0] !== "number";
|
|
1048
802
|
};
|
|
1049
803
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
804
|
+
function defaultEasing(values, easing) {
|
|
805
|
+
return values.map(() => easing || easeInOut).splice(0, values.length - 1);
|
|
806
|
+
}
|
|
807
|
+
function defaultOffset(values) {
|
|
808
|
+
const numValues = values.length;
|
|
809
|
+
return values.map((_value, i) => i !== 0 ? i / (numValues - 1) : 0);
|
|
810
|
+
}
|
|
811
|
+
function convertOffsetToTimes(offset, duration) {
|
|
812
|
+
return offset.map((o) => o * duration);
|
|
813
|
+
}
|
|
814
|
+
function keyframes({ keyframes: keyframeValues, ease = easeInOut, times, duration = 300, }) {
|
|
815
|
+
keyframeValues = [...keyframeValues];
|
|
816
|
+
const origin = keyframes[0];
|
|
817
|
+
/**
|
|
818
|
+
* Easing functions can be externally defined as strings. Here we convert them
|
|
819
|
+
* into actual functions.
|
|
820
|
+
*/
|
|
821
|
+
const easingFunctions = isEasingArray(ease)
|
|
822
|
+
? ease.map(easingDefinitionToFunction)
|
|
823
|
+
: easingDefinitionToFunction(ease);
|
|
824
|
+
/**
|
|
825
|
+
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
826
|
+
* to reduce GC during animation.
|
|
827
|
+
*/
|
|
828
|
+
const state = { done: false, value: origin };
|
|
829
|
+
/**
|
|
830
|
+
* Create a times array based on the provided 0-1 offsets
|
|
831
|
+
*/
|
|
832
|
+
const absoluteTimes = convertOffsetToTimes(
|
|
833
|
+
// Only use the provided offsets if they're the correct length
|
|
834
|
+
// TODO Maybe we should warn here if there's a length mismatch
|
|
835
|
+
times && times.length === keyframes.length
|
|
836
|
+
? times
|
|
837
|
+
: defaultOffset(keyframeValues), duration);
|
|
838
|
+
function createInterpolator() {
|
|
839
|
+
return interpolate(absoluteTimes, keyframeValues, {
|
|
840
|
+
ease: Array.isArray(easingFunctions)
|
|
841
|
+
? easingFunctions
|
|
842
|
+
: defaultEasing(keyframeValues, easingFunctions),
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
let interpolator = createInterpolator();
|
|
846
|
+
return {
|
|
847
|
+
next: (t) => {
|
|
848
|
+
state.value = interpolator(t);
|
|
849
|
+
state.done = t >= duration;
|
|
850
|
+
return state;
|
|
851
|
+
},
|
|
852
|
+
flipTarget: () => {
|
|
853
|
+
keyframeValues.reverse();
|
|
854
|
+
interpolator = createInterpolator();
|
|
855
|
+
},
|
|
856
|
+
};
|
|
857
|
+
}
|
|
1074
858
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
859
|
+
const safeMin = 0.001;
|
|
860
|
+
const minDuration = 0.01;
|
|
861
|
+
const maxDuration = 10.0;
|
|
862
|
+
const minDamping = 0.05;
|
|
863
|
+
const maxDamping = 1;
|
|
864
|
+
function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
|
|
865
|
+
let envelope;
|
|
866
|
+
let derivative;
|
|
867
|
+
warning(duration <= maxDuration * 1000, "Spring duration must be 10 seconds or less");
|
|
868
|
+
let dampingRatio = 1 - bounce;
|
|
869
|
+
/**
|
|
870
|
+
* Restrict dampingRatio and duration to within acceptable ranges.
|
|
871
|
+
*/
|
|
872
|
+
dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
|
|
873
|
+
duration = clamp(minDuration, maxDuration, duration / 1000);
|
|
874
|
+
if (dampingRatio < 1) {
|
|
875
|
+
/**
|
|
876
|
+
* Underdamped spring
|
|
877
|
+
*/
|
|
878
|
+
envelope = (undampedFreq) => {
|
|
879
|
+
const exponentialDecay = undampedFreq * dampingRatio;
|
|
880
|
+
const delta = exponentialDecay * duration;
|
|
881
|
+
const a = exponentialDecay - velocity;
|
|
882
|
+
const b = calcAngularFreq(undampedFreq, dampingRatio);
|
|
883
|
+
const c = Math.exp(-delta);
|
|
884
|
+
return safeMin - (a / b) * c;
|
|
885
|
+
};
|
|
886
|
+
derivative = (undampedFreq) => {
|
|
887
|
+
const exponentialDecay = undampedFreq * dampingRatio;
|
|
888
|
+
const delta = exponentialDecay * duration;
|
|
889
|
+
const d = delta * velocity + velocity;
|
|
890
|
+
const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
|
|
891
|
+
const f = Math.exp(-delta);
|
|
892
|
+
const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
|
|
893
|
+
const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
|
|
894
|
+
return (factor * ((d - e) * f)) / g;
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
/**
|
|
899
|
+
* Critically-damped spring
|
|
900
|
+
*/
|
|
901
|
+
envelope = (undampedFreq) => {
|
|
902
|
+
const a = Math.exp(-undampedFreq * duration);
|
|
903
|
+
const b = (undampedFreq - velocity) * duration + 1;
|
|
904
|
+
return -safeMin + a * b;
|
|
905
|
+
};
|
|
906
|
+
derivative = (undampedFreq) => {
|
|
907
|
+
const a = Math.exp(-undampedFreq * duration);
|
|
908
|
+
const b = (velocity - undampedFreq) * (duration * duration);
|
|
909
|
+
return a * b;
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
const initialGuess = 5 / duration;
|
|
913
|
+
const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
|
|
914
|
+
duration = duration * 1000;
|
|
915
|
+
if (isNaN(undampedFreq)) {
|
|
916
|
+
return {
|
|
917
|
+
stiffness: 100,
|
|
918
|
+
damping: 10,
|
|
919
|
+
duration,
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
const stiffness = Math.pow(undampedFreq, 2) * mass;
|
|
924
|
+
return {
|
|
925
|
+
stiffness,
|
|
926
|
+
damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
|
|
927
|
+
duration,
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
const rootIterations = 12;
|
|
932
|
+
function approximateRoot(envelope, derivative, initialGuess) {
|
|
933
|
+
let result = initialGuess;
|
|
934
|
+
for (let i = 1; i < rootIterations; i++) {
|
|
935
|
+
result = result - envelope(result) / derivative(result);
|
|
936
|
+
}
|
|
937
|
+
return result;
|
|
938
|
+
}
|
|
939
|
+
function calcAngularFreq(undampedFreq, dampingRatio) {
|
|
940
|
+
return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
|
|
1082
941
|
}
|
|
1083
942
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
};
|
|
943
|
+
/*
|
|
944
|
+
Convert velocity into velocity per second
|
|
1087
945
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
};
|
|
946
|
+
@param [number]: Unit per frame
|
|
947
|
+
@param [number]: Frame duration in ms
|
|
948
|
+
*/
|
|
949
|
+
function velocityPerSecond(velocity, frameDuration) {
|
|
950
|
+
return frameDuration ? velocity * (1000 / frameDuration) : 0;
|
|
951
|
+
}
|
|
1095
952
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
953
|
+
const durationKeys = ["duration", "bounce"];
|
|
954
|
+
const physicsKeys = ["stiffness", "damping", "mass"];
|
|
955
|
+
function isSpringType(options, keys) {
|
|
956
|
+
return keys.some((key) => options[key] !== undefined);
|
|
957
|
+
}
|
|
958
|
+
function getSpringOptions(options) {
|
|
959
|
+
let springOptions = {
|
|
960
|
+
velocity: 0.0,
|
|
961
|
+
stiffness: 100,
|
|
962
|
+
damping: 10,
|
|
963
|
+
mass: 1.0,
|
|
964
|
+
isResolvedFromDuration: false,
|
|
965
|
+
...options,
|
|
966
|
+
};
|
|
967
|
+
// stiffness/damping/mass overrides duration/bounce
|
|
968
|
+
if (!isSpringType(options, physicsKeys) &&
|
|
969
|
+
isSpringType(options, durationKeys)) {
|
|
970
|
+
const derived = findSpring(options);
|
|
971
|
+
springOptions = {
|
|
972
|
+
...springOptions,
|
|
973
|
+
...derived,
|
|
974
|
+
velocity: 0.0,
|
|
975
|
+
mass: 1.0,
|
|
976
|
+
};
|
|
977
|
+
springOptions.isResolvedFromDuration = true;
|
|
978
|
+
}
|
|
979
|
+
return springOptions;
|
|
980
|
+
}
|
|
981
|
+
const velocitySampleDuration = 5;
|
|
982
|
+
/**
|
|
983
|
+
* This is based on the spring implementation of Wobble https://github.com/skevy/wobble
|
|
984
|
+
*/
|
|
985
|
+
function spring({ keyframes, restSpeed = 2, restDelta = 0.01, ...options }) {
|
|
986
|
+
let origin = keyframes[0];
|
|
987
|
+
let target = keyframes[keyframes.length - 1];
|
|
988
|
+
/**
|
|
989
|
+
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
990
|
+
* to reduce GC during animation.
|
|
991
|
+
*/
|
|
992
|
+
const state = { done: false, value: origin };
|
|
993
|
+
const { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
|
|
994
|
+
let resolveSpring = zero;
|
|
995
|
+
let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
|
|
996
|
+
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
997
|
+
function createSpring() {
|
|
998
|
+
const initialDelta = target - origin;
|
|
999
|
+
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
1000
|
+
/**
|
|
1001
|
+
* If we're working within what looks like a 0-1 range, change the default restDelta
|
|
1002
|
+
* to 0.01
|
|
1003
|
+
*/
|
|
1004
|
+
if (restDelta === undefined) {
|
|
1005
|
+
restDelta = Math.min(Math.abs(target - origin) / 100, 0.4);
|
|
1006
|
+
}
|
|
1007
|
+
if (dampingRatio < 1) {
|
|
1008
|
+
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
1009
|
+
// Underdamped spring
|
|
1010
|
+
resolveSpring = (t) => {
|
|
1011
|
+
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
1012
|
+
return (target -
|
|
1013
|
+
envelope *
|
|
1014
|
+
(((initialVelocity +
|
|
1015
|
+
dampingRatio * undampedAngularFreq * initialDelta) /
|
|
1016
|
+
angularFreq) *
|
|
1017
|
+
Math.sin(angularFreq * t) +
|
|
1018
|
+
initialDelta * Math.cos(angularFreq * t)));
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
else if (dampingRatio === 1) {
|
|
1022
|
+
// Critically damped spring
|
|
1023
|
+
resolveSpring = (t) => target -
|
|
1024
|
+
Math.exp(-undampedAngularFreq * t) *
|
|
1025
|
+
(initialDelta +
|
|
1026
|
+
(initialVelocity + undampedAngularFreq * initialDelta) *
|
|
1027
|
+
t);
|
|
1103
1028
|
}
|
|
1029
|
+
else {
|
|
1030
|
+
// Overdamped spring
|
|
1031
|
+
const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
|
|
1032
|
+
resolveSpring = (t) => {
|
|
1033
|
+
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
1034
|
+
// When performing sinh or cosh values can hit Infinity so we cap them here
|
|
1035
|
+
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
1036
|
+
return (target -
|
|
1037
|
+
(envelope *
|
|
1038
|
+
((initialVelocity +
|
|
1039
|
+
dampingRatio * undampedAngularFreq * initialDelta) *
|
|
1040
|
+
Math.sinh(freqForT) +
|
|
1041
|
+
dampedAngularFreq *
|
|
1042
|
+
initialDelta *
|
|
1043
|
+
Math.cosh(freqForT))) /
|
|
1044
|
+
dampedAngularFreq);
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
createSpring();
|
|
1049
|
+
return {
|
|
1050
|
+
next: (t) => {
|
|
1051
|
+
const current = resolveSpring(t);
|
|
1052
|
+
if (!isResolvedFromDuration) {
|
|
1053
|
+
let currentVelocity = initialVelocity;
|
|
1054
|
+
if (t !== 0) {
|
|
1055
|
+
/**
|
|
1056
|
+
* We only need to calculate velocity for under-damped springs
|
|
1057
|
+
* as over- and critically-damped springs can't overshoot, so
|
|
1058
|
+
* checking only for displacement is enough.
|
|
1059
|
+
*/
|
|
1060
|
+
if (dampingRatio < 1) {
|
|
1061
|
+
const prevT = Math.max(0, t - velocitySampleDuration);
|
|
1062
|
+
currentVelocity = velocityPerSecond(current - resolveSpring(prevT), t - prevT);
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
currentVelocity = 0;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
|
|
1069
|
+
const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
|
|
1070
|
+
state.done =
|
|
1071
|
+
isBelowVelocityThreshold && isBelowDisplacementThreshold;
|
|
1072
|
+
}
|
|
1073
|
+
else {
|
|
1074
|
+
state.done = t >= duration;
|
|
1075
|
+
}
|
|
1076
|
+
state.value = state.done ? target : current;
|
|
1077
|
+
return state;
|
|
1078
|
+
},
|
|
1079
|
+
flipTarget: () => {
|
|
1080
|
+
initialVelocity = -initialVelocity;
|
|
1081
|
+
[origin, target] = [target, origin];
|
|
1082
|
+
createSpring();
|
|
1083
|
+
},
|
|
1104
1084
|
};
|
|
1105
|
-
sync.read(checkElapsed, true);
|
|
1106
|
-
return () => cancelSync.read(checkElapsed);
|
|
1107
1085
|
}
|
|
1086
|
+
spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
|
|
1087
|
+
const zero = (_t) => 0;
|
|
1108
1088
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1089
|
+
function decay({
|
|
1090
|
+
/**
|
|
1091
|
+
* The decay animation dynamically calculates an end of the animation
|
|
1092
|
+
* based on the initial keyframe, so we only need to define a single keyframe
|
|
1093
|
+
* as default.
|
|
1094
|
+
*/
|
|
1095
|
+
keyframes = [0], velocity = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
|
|
1096
|
+
const origin = keyframes[0];
|
|
1097
|
+
/**
|
|
1098
|
+
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
|
|
1099
|
+
* to reduce GC during animation.
|
|
1100
|
+
*/
|
|
1101
|
+
const state = { done: false, value: origin };
|
|
1102
|
+
let amplitude = power * velocity;
|
|
1103
|
+
const ideal = origin + amplitude;
|
|
1104
|
+
const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
|
|
1105
|
+
/**
|
|
1106
|
+
* If the target has changed we need to re-calculate the amplitude, otherwise
|
|
1107
|
+
* the animation will start from the wrong position.
|
|
1108
|
+
*/
|
|
1109
|
+
if (target !== ideal)
|
|
1110
|
+
amplitude = target - origin;
|
|
1111
|
+
return {
|
|
1112
|
+
next: (t) => {
|
|
1113
|
+
const delta = -amplitude * Math.exp(-t / timeConstant);
|
|
1114
|
+
state.done = !(delta > restDelta || delta < -restDelta);
|
|
1115
|
+
state.value = state.done ? target : target + delta;
|
|
1116
|
+
return state;
|
|
1117
|
+
},
|
|
1118
|
+
flipTarget: () => { },
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1131
1121
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
if (t < 1 / 2)
|
|
1141
|
-
return q;
|
|
1142
|
-
if (t < 2 / 3)
|
|
1143
|
-
return p + (q - p) * (2 / 3 - t) * 6;
|
|
1144
|
-
return p;
|
|
1122
|
+
const types = {
|
|
1123
|
+
decay,
|
|
1124
|
+
keyframes: keyframes,
|
|
1125
|
+
tween: keyframes,
|
|
1126
|
+
spring,
|
|
1127
|
+
};
|
|
1128
|
+
function loopElapsed(elapsed, duration, delay = 0) {
|
|
1129
|
+
return elapsed - duration - delay;
|
|
1145
1130
|
}
|
|
1146
|
-
function
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1131
|
+
function reverseElapsed(elapsed, duration = 0, delay = 0, isForwardPlayback = true) {
|
|
1132
|
+
return isForwardPlayback
|
|
1133
|
+
? loopElapsed(duration + -elapsed, duration, delay)
|
|
1134
|
+
: duration - (elapsed - duration) + delay;
|
|
1135
|
+
}
|
|
1136
|
+
function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) {
|
|
1137
|
+
return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay;
|
|
1138
|
+
}
|
|
1139
|
+
const framesync = (update) => {
|
|
1140
|
+
const passTimestamp = ({ delta }) => update(delta);
|
|
1141
|
+
return {
|
|
1142
|
+
start: () => sync.update(passTimestamp, true),
|
|
1143
|
+
stop: () => cancelSync.update(passTimestamp),
|
|
1144
|
+
};
|
|
1145
|
+
};
|
|
1146
|
+
function animate$1({ duration, driver = framesync, elapsed = 0, repeat: repeatMax = 0, repeatType = "loop", repeatDelay = 0, keyframes, onPlay, onStop, onComplete, onRepeat, onUpdate, type = "keyframes", ...options }) {
|
|
1147
|
+
var _a, _b;
|
|
1148
|
+
let driverControls;
|
|
1149
|
+
let repeatCount = 0;
|
|
1150
|
+
let computedDuration = duration;
|
|
1151
|
+
let latest;
|
|
1152
|
+
let isComplete = false;
|
|
1153
|
+
let isForwardPlayback = true;
|
|
1154
|
+
let interpolateFromNumber;
|
|
1155
|
+
const animator = types[keyframes.length > 2 ? "keyframes" : type];
|
|
1156
|
+
const origin = keyframes[0];
|
|
1157
|
+
const target = keyframes[keyframes.length - 1];
|
|
1158
|
+
if ((_b = (_a = animator).needsInterpolation) === null || _b === void 0 ? void 0 : _b.call(_a, origin, target)) {
|
|
1159
|
+
interpolateFromNumber = interpolate([0, 100], [origin, target], {
|
|
1160
|
+
clamp: false,
|
|
1161
|
+
});
|
|
1162
|
+
keyframes = [0, 100];
|
|
1163
|
+
}
|
|
1164
|
+
const animation = animator({
|
|
1165
|
+
...options,
|
|
1166
|
+
duration,
|
|
1167
|
+
keyframes,
|
|
1168
|
+
});
|
|
1169
|
+
function repeat() {
|
|
1170
|
+
repeatCount++;
|
|
1171
|
+
if (repeatType === "reverse") {
|
|
1172
|
+
isForwardPlayback = repeatCount % 2 === 0;
|
|
1173
|
+
elapsed = reverseElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback);
|
|
1174
|
+
}
|
|
1175
|
+
else {
|
|
1176
|
+
elapsed = loopElapsed(elapsed, computedDuration, repeatDelay);
|
|
1177
|
+
if (repeatType === "mirror")
|
|
1178
|
+
animation.flipTarget();
|
|
1179
|
+
}
|
|
1180
|
+
isComplete = false;
|
|
1181
|
+
onRepeat && onRepeat();
|
|
1182
|
+
}
|
|
1183
|
+
function complete() {
|
|
1184
|
+
driverControls.stop();
|
|
1185
|
+
onComplete && onComplete();
|
|
1186
|
+
}
|
|
1187
|
+
function update(delta) {
|
|
1188
|
+
if (!isForwardPlayback)
|
|
1189
|
+
delta = -delta;
|
|
1190
|
+
elapsed += delta;
|
|
1191
|
+
if (!isComplete) {
|
|
1192
|
+
const state = animation.next(Math.max(0, elapsed));
|
|
1193
|
+
latest = state.value;
|
|
1194
|
+
if (interpolateFromNumber)
|
|
1195
|
+
latest = interpolateFromNumber(latest);
|
|
1196
|
+
isComplete = isForwardPlayback ? state.done : elapsed <= 0;
|
|
1197
|
+
}
|
|
1198
|
+
onUpdate && onUpdate(latest);
|
|
1199
|
+
if (isComplete) {
|
|
1200
|
+
if (repeatCount === 0) {
|
|
1201
|
+
computedDuration =
|
|
1202
|
+
computedDuration !== undefined ? computedDuration : elapsed;
|
|
1203
|
+
}
|
|
1204
|
+
if (repeatCount < repeatMax) {
|
|
1205
|
+
hasRepeatDelayElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback) && repeat();
|
|
1206
|
+
}
|
|
1207
|
+
else {
|
|
1208
|
+
complete();
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1155
1211
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
const p = 2 * lightness - q;
|
|
1161
|
-
red = hueToRgb(p, q, hue + 1 / 3);
|
|
1162
|
-
green = hueToRgb(p, q, hue);
|
|
1163
|
-
blue = hueToRgb(p, q, hue - 1 / 3);
|
|
1212
|
+
function play() {
|
|
1213
|
+
onPlay && onPlay();
|
|
1214
|
+
driverControls = driver(update);
|
|
1215
|
+
driverControls.start();
|
|
1164
1216
|
}
|
|
1217
|
+
play();
|
|
1165
1218
|
return {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1219
|
+
stop: () => {
|
|
1220
|
+
onStop && onStop();
|
|
1221
|
+
driverControls.stop();
|
|
1222
|
+
},
|
|
1170
1223
|
};
|
|
1171
1224
|
}
|
|
1172
1225
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
return Math.sqrt(Math.max(0, v * (to * to - fromExpo) + fromExpo));
|
|
1179
|
-
};
|
|
1180
|
-
const colorTypes = [hex, rgba, hsla];
|
|
1181
|
-
const getColorType = (v) => colorTypes.find((type) => type.test(v));
|
|
1182
|
-
function asRGBA(color) {
|
|
1183
|
-
const type = getColorType(color);
|
|
1184
|
-
invariant(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`);
|
|
1185
|
-
let model = type.parse(color);
|
|
1186
|
-
if (type === hsla) {
|
|
1187
|
-
// TODO Remove this cast - needed since Framer Motion's stricter typing
|
|
1188
|
-
model = hslaToRgba(model);
|
|
1189
|
-
}
|
|
1190
|
-
return model;
|
|
1191
|
-
}
|
|
1192
|
-
const mixColor = (from, to) => {
|
|
1193
|
-
const fromRGBA = asRGBA(from);
|
|
1194
|
-
const toRGBA = asRGBA(to);
|
|
1195
|
-
const blended = { ...fromRGBA };
|
|
1196
|
-
return (v) => {
|
|
1197
|
-
blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
|
|
1198
|
-
blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
|
|
1199
|
-
blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
|
|
1200
|
-
blended.alpha = mix(fromRGBA.alpha, toRGBA.alpha, v);
|
|
1201
|
-
return rgba.transform(blended);
|
|
1202
|
-
};
|
|
1203
|
-
};
|
|
1204
|
-
|
|
1205
|
-
/**
|
|
1206
|
-
* Pipe
|
|
1207
|
-
* Compose other transformers to run linearily
|
|
1208
|
-
* pipe(min(20), max(40))
|
|
1209
|
-
* @param {...functions} transformers
|
|
1210
|
-
* @return {function}
|
|
1211
|
-
*/
|
|
1212
|
-
const combineFunctions = (a, b) => (v) => b(a(v));
|
|
1213
|
-
const pipe = (...transformers) => transformers.reduce(combineFunctions);
|
|
1214
|
-
|
|
1215
|
-
function getMixer(origin, target) {
|
|
1216
|
-
if (typeof origin === "number") {
|
|
1217
|
-
return (v) => mix(origin, target, v);
|
|
1226
|
+
function inertia({ keyframes, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
|
|
1227
|
+
const origin = keyframes[0];
|
|
1228
|
+
let currentAnimation;
|
|
1229
|
+
function isOutOfBounds(v) {
|
|
1230
|
+
return (min !== undefined && v < min) || (max !== undefined && v > max);
|
|
1218
1231
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1232
|
+
function findNearestBoundary(v) {
|
|
1233
|
+
if (min === undefined)
|
|
1234
|
+
return max;
|
|
1235
|
+
if (max === undefined)
|
|
1236
|
+
return min;
|
|
1237
|
+
return Math.abs(min - v) < Math.abs(max - v) ? min : max;
|
|
1221
1238
|
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1239
|
+
function startAnimation(options) {
|
|
1240
|
+
currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop();
|
|
1241
|
+
currentAnimation = animate$1({
|
|
1242
|
+
keyframes: [0, 1],
|
|
1243
|
+
velocity: 0,
|
|
1244
|
+
...options,
|
|
1245
|
+
driver,
|
|
1246
|
+
onUpdate: (v) => {
|
|
1247
|
+
var _a;
|
|
1248
|
+
onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(v);
|
|
1249
|
+
(_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, v);
|
|
1250
|
+
},
|
|
1251
|
+
onComplete,
|
|
1252
|
+
onStop,
|
|
1253
|
+
});
|
|
1224
1254
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1234
|
-
return output;
|
|
1235
|
-
};
|
|
1236
|
-
};
|
|
1237
|
-
const mixObject = (origin, target) => {
|
|
1238
|
-
const output = { ...origin, ...target };
|
|
1239
|
-
const blendValue = {};
|
|
1240
|
-
for (const key in output) {
|
|
1241
|
-
if (origin[key] !== undefined && target[key] !== undefined) {
|
|
1242
|
-
blendValue[key] = getMixer(origin[key], target[key]);
|
|
1243
|
-
}
|
|
1255
|
+
function startSpring(options) {
|
|
1256
|
+
startAnimation({
|
|
1257
|
+
type: "spring",
|
|
1258
|
+
stiffness: bounceStiffness,
|
|
1259
|
+
damping: bounceDamping,
|
|
1260
|
+
restDelta,
|
|
1261
|
+
...options,
|
|
1262
|
+
});
|
|
1244
1263
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
};
|
|
1252
|
-
const mixComplex = (origin, target) => {
|
|
1253
|
-
const template = complex.createTransformer(target);
|
|
1254
|
-
const originStats = analyseComplexValue(origin);
|
|
1255
|
-
const targetStats = analyseComplexValue(target);
|
|
1256
|
-
const canInterpolate = originStats.numColors === targetStats.numColors &&
|
|
1257
|
-
originStats.numNumbers >= targetStats.numNumbers;
|
|
1258
|
-
if (canInterpolate) {
|
|
1259
|
-
return pipe(mixArray(originStats.values, targetStats.values), template);
|
|
1264
|
+
if (isOutOfBounds(origin)) {
|
|
1265
|
+
// Start the animation with spring if outside the defined boundaries
|
|
1266
|
+
startSpring({
|
|
1267
|
+
velocity,
|
|
1268
|
+
keyframes: [origin, findNearestBoundary(origin)],
|
|
1269
|
+
});
|
|
1260
1270
|
}
|
|
1261
1271
|
else {
|
|
1262
|
-
|
|
1263
|
-
|
|
1272
|
+
/**
|
|
1273
|
+
* Or if the value is out of bounds, simulate the inertia movement
|
|
1274
|
+
* with the decay animation.
|
|
1275
|
+
*
|
|
1276
|
+
* Pre-calculate the target so we can detect if it's out-of-bounds.
|
|
1277
|
+
* If it is, we want to check per frame when to switch to a spring
|
|
1278
|
+
* animation
|
|
1279
|
+
*/
|
|
1280
|
+
let target = power * velocity + origin;
|
|
1281
|
+
if (typeof modifyTarget !== "undefined")
|
|
1282
|
+
target = modifyTarget(target);
|
|
1283
|
+
const boundary = findNearestBoundary(target);
|
|
1284
|
+
const heading = boundary === min ? -1 : 1;
|
|
1285
|
+
let prev;
|
|
1286
|
+
let current;
|
|
1287
|
+
const checkBoundary = (v) => {
|
|
1288
|
+
prev = current;
|
|
1289
|
+
current = v;
|
|
1290
|
+
velocity = velocityPerSecond(v - prev, frameData.delta);
|
|
1291
|
+
if ((heading === 1 && v > boundary) ||
|
|
1292
|
+
(heading === -1 && v < boundary)) {
|
|
1293
|
+
startSpring({ keyframes: [v, boundary], velocity });
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
startAnimation({
|
|
1297
|
+
type: "decay",
|
|
1298
|
+
keyframes: [origin, 0],
|
|
1299
|
+
velocity,
|
|
1300
|
+
timeConstant,
|
|
1301
|
+
power,
|
|
1302
|
+
restDelta,
|
|
1303
|
+
modifyTarget,
|
|
1304
|
+
onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
|
|
1305
|
+
});
|
|
1264
1306
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
Given a lower limit and an upper limit, we return the progress
|
|
1271
|
-
(expressed as a number 0-1) represented by the given value, and
|
|
1272
|
-
limit that progress to within 0-1.
|
|
1307
|
+
return {
|
|
1308
|
+
stop: () => currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop(),
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1273
1311
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1312
|
+
const underDampedSpring = () => ({
|
|
1313
|
+
type: "spring",
|
|
1314
|
+
stiffness: 500,
|
|
1315
|
+
damping: 25,
|
|
1316
|
+
restSpeed: 10,
|
|
1317
|
+
});
|
|
1318
|
+
const criticallyDampedSpring = (target) => ({
|
|
1319
|
+
type: "spring",
|
|
1320
|
+
stiffness: 550,
|
|
1321
|
+
damping: target === 0 ? 2 * Math.sqrt(550) : 30,
|
|
1322
|
+
restSpeed: 10,
|
|
1323
|
+
});
|
|
1324
|
+
const linearTween = () => ({
|
|
1325
|
+
type: "keyframes",
|
|
1326
|
+
ease: "linear",
|
|
1327
|
+
duration: 0.3,
|
|
1328
|
+
});
|
|
1329
|
+
const keyframesTransition = {
|
|
1330
|
+
type: "keyframes",
|
|
1331
|
+
duration: 0.8,
|
|
1282
1332
|
};
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
return
|
|
1333
|
+
const defaultTransitions = {
|
|
1334
|
+
x: underDampedSpring,
|
|
1335
|
+
y: underDampedSpring,
|
|
1336
|
+
z: underDampedSpring,
|
|
1337
|
+
rotate: underDampedSpring,
|
|
1338
|
+
rotateX: underDampedSpring,
|
|
1339
|
+
rotateY: underDampedSpring,
|
|
1340
|
+
rotateZ: underDampedSpring,
|
|
1341
|
+
scaleX: criticallyDampedSpring,
|
|
1342
|
+
scaleY: criticallyDampedSpring,
|
|
1343
|
+
scale: criticallyDampedSpring,
|
|
1344
|
+
opacity: linearTween,
|
|
1345
|
+
backgroundColor: linearTween,
|
|
1346
|
+
color: linearTween,
|
|
1347
|
+
default: criticallyDampedSpring,
|
|
1348
|
+
};
|
|
1349
|
+
const getDefaultTransition = (valueKey, { keyframes }) => {
|
|
1350
|
+
if (keyframes.length > 2) {
|
|
1351
|
+
return keyframesTransition;
|
|
1302
1352
|
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
const mixers = [];
|
|
1307
|
-
const mixerFactory = customMixer || detectMixerFactory(output[0]);
|
|
1308
|
-
const numMixers = output.length - 1;
|
|
1309
|
-
for (let i = 0; i < numMixers; i++) {
|
|
1310
|
-
let mixer = mixerFactory(output[i], output[i + 1]);
|
|
1311
|
-
if (ease) {
|
|
1312
|
-
const easingFunction = Array.isArray(ease) ? ease[i] : ease;
|
|
1313
|
-
mixer = pipe(easingFunction, mixer);
|
|
1314
|
-
}
|
|
1315
|
-
mixers.push(mixer);
|
|
1353
|
+
else {
|
|
1354
|
+
const factory = defaultTransitions[valueKey] || defaultTransitions.default;
|
|
1355
|
+
return factory(keyframes[1]);
|
|
1316
1356
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1357
|
+
};
|
|
1358
|
+
|
|
1319
1359
|
/**
|
|
1320
|
-
*
|
|
1321
|
-
*
|
|
1322
|
-
* Accepts:
|
|
1323
|
-
* - Numbers
|
|
1324
|
-
* - Colors (hex, hsl, hsla, rgb, rgba)
|
|
1325
|
-
* - Complex (combinations of one or more numbers or strings)
|
|
1326
|
-
*
|
|
1327
|
-
* ```jsx
|
|
1328
|
-
* const mixColor = interpolate([0, 1], ['#fff', '#000'])
|
|
1329
|
-
*
|
|
1330
|
-
* mixColor(0.5) // 'rgba(128, 128, 128, 1)'
|
|
1331
|
-
* ```
|
|
1360
|
+
* Check if a value is animatable. Examples:
|
|
1332
1361
|
*
|
|
1333
|
-
*
|
|
1334
|
-
*
|
|
1362
|
+
* ✅: 100, "100px", "#fff"
|
|
1363
|
+
* ❌: "block", "url(2.jpg)"
|
|
1364
|
+
* @param value
|
|
1335
1365
|
*
|
|
1336
|
-
* @
|
|
1366
|
+
* @internal
|
|
1337
1367
|
*/
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
// If
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1368
|
+
const isAnimatable = (key, value) => {
|
|
1369
|
+
// If the list of keys tat might be non-animatable grows, replace with Set
|
|
1370
|
+
if (key === "zIndex")
|
|
1371
|
+
return false;
|
|
1372
|
+
// If it's a number or a keyframes array, we can animate it. We might at some point
|
|
1373
|
+
// need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
|
|
1374
|
+
// but for now lets leave it like this for performance reasons
|
|
1375
|
+
if (typeof value === "number" || Array.isArray(value))
|
|
1376
|
+
return true;
|
|
1377
|
+
if (typeof value === "string" && // It's animatable if we have a string
|
|
1378
|
+
complex.test(value) && // And it contains numbers and/or colors
|
|
1379
|
+
!value.startsWith("url(") // Unless it starts with "url("
|
|
1380
|
+
) {
|
|
1381
|
+
return true;
|
|
1346
1382
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1383
|
+
return false;
|
|
1384
|
+
};
|
|
1385
|
+
|
|
1386
|
+
/**
|
|
1387
|
+
* Properties that should default to 1 or 100%
|
|
1388
|
+
*/
|
|
1389
|
+
const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
|
|
1390
|
+
function applyDefaultFilter(v) {
|
|
1391
|
+
const [name, value] = v.slice(0, -1).split("(");
|
|
1392
|
+
if (name === "drop-shadow")
|
|
1393
|
+
return v;
|
|
1394
|
+
const [number] = value.match(floatRegex) || [];
|
|
1395
|
+
if (!number)
|
|
1396
|
+
return v;
|
|
1397
|
+
const unit = value.replace(number, "");
|
|
1398
|
+
let defaultValue = maxDefaults.has(name) ? 1 : 0;
|
|
1399
|
+
if (number !== value)
|
|
1400
|
+
defaultValue *= 100;
|
|
1401
|
+
return name + "(" + defaultValue + unit + ")";
|
|
1363
1402
|
}
|
|
1403
|
+
const functionRegex = /([a-z-]*)\(.*?\)/g;
|
|
1404
|
+
const filter = {
|
|
1405
|
+
...complex,
|
|
1406
|
+
getAnimatableNone: (v) => {
|
|
1407
|
+
const functions = v.match(functionRegex);
|
|
1408
|
+
return functions ? functions.map(applyDefaultFilter).join(" ") : v;
|
|
1409
|
+
},
|
|
1410
|
+
};
|
|
1364
1411
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1412
|
+
const int = {
|
|
1413
|
+
...number,
|
|
1414
|
+
transform: Math.round,
|
|
1415
|
+
};
|
|
1416
|
+
|
|
1417
|
+
const numberValueTypes = {
|
|
1418
|
+
// Border props
|
|
1419
|
+
borderWidth: px,
|
|
1420
|
+
borderTopWidth: px,
|
|
1421
|
+
borderRightWidth: px,
|
|
1422
|
+
borderBottomWidth: px,
|
|
1423
|
+
borderLeftWidth: px,
|
|
1424
|
+
borderRadius: px,
|
|
1425
|
+
radius: px,
|
|
1426
|
+
borderTopLeftRadius: px,
|
|
1427
|
+
borderTopRightRadius: px,
|
|
1428
|
+
borderBottomRightRadius: px,
|
|
1429
|
+
borderBottomLeftRadius: px,
|
|
1430
|
+
// Positioning props
|
|
1431
|
+
width: px,
|
|
1432
|
+
maxWidth: px,
|
|
1433
|
+
height: px,
|
|
1434
|
+
maxHeight: px,
|
|
1435
|
+
size: px,
|
|
1436
|
+
top: px,
|
|
1437
|
+
right: px,
|
|
1438
|
+
bottom: px,
|
|
1439
|
+
left: px,
|
|
1440
|
+
// Spacing props
|
|
1441
|
+
padding: px,
|
|
1442
|
+
paddingTop: px,
|
|
1443
|
+
paddingRight: px,
|
|
1444
|
+
paddingBottom: px,
|
|
1445
|
+
paddingLeft: px,
|
|
1446
|
+
margin: px,
|
|
1447
|
+
marginTop: px,
|
|
1448
|
+
marginRight: px,
|
|
1449
|
+
marginBottom: px,
|
|
1450
|
+
marginLeft: px,
|
|
1451
|
+
// Transform props
|
|
1452
|
+
rotate: degrees,
|
|
1453
|
+
rotateX: degrees,
|
|
1454
|
+
rotateY: degrees,
|
|
1455
|
+
rotateZ: degrees,
|
|
1456
|
+
scale,
|
|
1457
|
+
scaleX: scale,
|
|
1458
|
+
scaleY: scale,
|
|
1459
|
+
scaleZ: scale,
|
|
1460
|
+
skew: degrees,
|
|
1461
|
+
skewX: degrees,
|
|
1462
|
+
skewY: degrees,
|
|
1463
|
+
distance: px,
|
|
1464
|
+
translateX: px,
|
|
1465
|
+
translateY: px,
|
|
1466
|
+
translateZ: px,
|
|
1467
|
+
x: px,
|
|
1468
|
+
y: px,
|
|
1469
|
+
z: px,
|
|
1470
|
+
perspective: px,
|
|
1471
|
+
transformPerspective: px,
|
|
1472
|
+
opacity: alpha,
|
|
1473
|
+
originX: progressPercentage,
|
|
1474
|
+
originY: progressPercentage,
|
|
1475
|
+
originZ: px,
|
|
1476
|
+
// Misc
|
|
1477
|
+
zIndex: int,
|
|
1478
|
+
// SVG
|
|
1479
|
+
fillOpacity: alpha,
|
|
1480
|
+
strokeOpacity: alpha,
|
|
1481
|
+
numOctaves: int,
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
/**
|
|
1485
|
+
* A map of default value types for common values
|
|
1486
|
+
*/
|
|
1487
|
+
const defaultValueTypes = {
|
|
1488
|
+
...numberValueTypes,
|
|
1489
|
+
// Color props
|
|
1490
|
+
color,
|
|
1491
|
+
backgroundColor: color,
|
|
1492
|
+
outlineColor: color,
|
|
1493
|
+
fill: color,
|
|
1494
|
+
stroke: color,
|
|
1495
|
+
// Border props
|
|
1496
|
+
borderColor: color,
|
|
1497
|
+
borderTopColor: color,
|
|
1498
|
+
borderRightColor: color,
|
|
1499
|
+
borderBottomColor: color,
|
|
1500
|
+
borderLeftColor: color,
|
|
1501
|
+
filter,
|
|
1502
|
+
WebkitFilter: filter,
|
|
1503
|
+
};
|
|
1504
|
+
/**
|
|
1505
|
+
* Gets the default ValueType for the provided value key
|
|
1506
|
+
*/
|
|
1507
|
+
const getDefaultValueType = (key) => defaultValueTypes[key];
|
|
1508
|
+
|
|
1509
|
+
function getAnimatableNone(key, value) {
|
|
1510
|
+
var _a;
|
|
1511
|
+
let defaultValueType = getDefaultValueType(key);
|
|
1512
|
+
if (defaultValueType !== filter)
|
|
1513
|
+
defaultValueType = complex;
|
|
1514
|
+
// If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
|
|
1515
|
+
return (_a = defaultValueType.getAnimatableNone) === null || _a === void 0 ? void 0 : _a.call(defaultValueType, value);
|
|
1367
1516
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* Decide whether a transition is defined on a given Transition.
|
|
1520
|
+
* This filters out orchestration options and returns true
|
|
1521
|
+
* if any options are left.
|
|
1522
|
+
*/
|
|
1523
|
+
function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, ...transition }) {
|
|
1524
|
+
return !!Object.keys(transition).length;
|
|
1371
1525
|
}
|
|
1372
|
-
function
|
|
1373
|
-
return
|
|
1526
|
+
function isZero(value) {
|
|
1527
|
+
return (value === 0 ||
|
|
1528
|
+
(typeof value === "string" &&
|
|
1529
|
+
parseFloat(value) === 0 &&
|
|
1530
|
+
value.indexOf(" ") === -1));
|
|
1374
1531
|
}
|
|
1375
|
-
function
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
* Convert values to an array if they've been given as from/to options
|
|
1383
|
-
*/
|
|
1384
|
-
const values = Array.isArray(to) ? to : [from, to];
|
|
1385
|
-
/**
|
|
1386
|
-
* Create a times array based on the provided 0-1 offsets
|
|
1387
|
-
*/
|
|
1388
|
-
const times = convertOffsetToTimes(
|
|
1389
|
-
// Only use the provided offsets if they're the correct length
|
|
1390
|
-
// TODO Maybe we should warn here if there's a length mismatch
|
|
1391
|
-
offset && offset.length === values.length
|
|
1392
|
-
? offset
|
|
1393
|
-
: defaultOffset(values), duration);
|
|
1394
|
-
function createInterpolator() {
|
|
1395
|
-
return interpolate(times, values, {
|
|
1396
|
-
ease: Array.isArray(ease) ? ease : defaultEasing(values, ease),
|
|
1397
|
-
});
|
|
1398
|
-
}
|
|
1399
|
-
let interpolator = createInterpolator();
|
|
1400
|
-
return {
|
|
1401
|
-
next: (t) => {
|
|
1402
|
-
state.value = interpolator(t);
|
|
1403
|
-
state.done = t >= duration;
|
|
1404
|
-
return state;
|
|
1405
|
-
},
|
|
1406
|
-
flipTarget: () => {
|
|
1407
|
-
values.reverse();
|
|
1408
|
-
interpolator = createInterpolator();
|
|
1409
|
-
},
|
|
1410
|
-
};
|
|
1532
|
+
function getZeroUnit(potentialUnitType) {
|
|
1533
|
+
return typeof potentialUnitType === "number"
|
|
1534
|
+
? 0
|
|
1535
|
+
: getAnimatableNone("", potentialUnitType);
|
|
1536
|
+
}
|
|
1537
|
+
function getValueTransition(transition, key) {
|
|
1538
|
+
return transition[key] || transition["default"] || transition;
|
|
1411
1539
|
}
|
|
1412
1540
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
const maxDamping = 1;
|
|
1418
|
-
function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
|
|
1419
|
-
let envelope;
|
|
1420
|
-
let derivative;
|
|
1421
|
-
warning(duration <= maxDuration * 1000, "Spring duration must be 10 seconds or less");
|
|
1422
|
-
let dampingRatio = 1 - bounce;
|
|
1423
|
-
/**
|
|
1424
|
-
* Restrict dampingRatio and duration to within acceptable ranges.
|
|
1425
|
-
*/
|
|
1426
|
-
dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
|
|
1427
|
-
duration = clamp(minDuration, maxDuration, duration / 1000);
|
|
1428
|
-
if (dampingRatio < 1) {
|
|
1429
|
-
/**
|
|
1430
|
-
* Underdamped spring
|
|
1431
|
-
*/
|
|
1432
|
-
envelope = (undampedFreq) => {
|
|
1433
|
-
const exponentialDecay = undampedFreq * dampingRatio;
|
|
1434
|
-
const delta = exponentialDecay * duration;
|
|
1435
|
-
const a = exponentialDecay - velocity;
|
|
1436
|
-
const b = calcAngularFreq(undampedFreq, dampingRatio);
|
|
1437
|
-
const c = Math.exp(-delta);
|
|
1438
|
-
return safeMin - (a / b) * c;
|
|
1439
|
-
};
|
|
1440
|
-
derivative = (undampedFreq) => {
|
|
1441
|
-
const exponentialDecay = undampedFreq * dampingRatio;
|
|
1442
|
-
const delta = exponentialDecay * duration;
|
|
1443
|
-
const d = delta * velocity + velocity;
|
|
1444
|
-
const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
|
|
1445
|
-
const f = Math.exp(-delta);
|
|
1446
|
-
const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
|
|
1447
|
-
const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
|
|
1448
|
-
return (factor * ((d - e) * f)) / g;
|
|
1449
|
-
};
|
|
1450
|
-
}
|
|
1451
|
-
else {
|
|
1541
|
+
function getKeyframes(value, valueName, target, transition) {
|
|
1542
|
+
const isTargetAnimatable = isAnimatable(valueName, target);
|
|
1543
|
+
let origin = transition.from !== undefined ? transition.from : value.get();
|
|
1544
|
+
if (origin === "none" && isTargetAnimatable && typeof target === "string") {
|
|
1452
1545
|
/**
|
|
1453
|
-
*
|
|
1546
|
+
* If we're trying to animate from "none", try and get an animatable version
|
|
1547
|
+
* of the target. This could be improved to work both ways.
|
|
1454
1548
|
*/
|
|
1455
|
-
|
|
1456
|
-
const a = Math.exp(-undampedFreq * duration);
|
|
1457
|
-
const b = (undampedFreq - velocity) * duration + 1;
|
|
1458
|
-
return -safeMin + a * b;
|
|
1459
|
-
};
|
|
1460
|
-
derivative = (undampedFreq) => {
|
|
1461
|
-
const a = Math.exp(-undampedFreq * duration);
|
|
1462
|
-
const b = (velocity - undampedFreq) * (duration * duration);
|
|
1463
|
-
return a * b;
|
|
1464
|
-
};
|
|
1465
|
-
}
|
|
1466
|
-
const initialGuess = 5 / duration;
|
|
1467
|
-
const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
|
|
1468
|
-
duration = duration * 1000;
|
|
1469
|
-
if (isNaN(undampedFreq)) {
|
|
1470
|
-
return {
|
|
1471
|
-
stiffness: 100,
|
|
1472
|
-
damping: 10,
|
|
1473
|
-
duration,
|
|
1474
|
-
};
|
|
1475
|
-
}
|
|
1476
|
-
else {
|
|
1477
|
-
const stiffness = Math.pow(undampedFreq, 2) * mass;
|
|
1478
|
-
return {
|
|
1479
|
-
stiffness,
|
|
1480
|
-
damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
|
|
1481
|
-
duration,
|
|
1482
|
-
};
|
|
1549
|
+
origin = getAnimatableNone(valueName, target);
|
|
1483
1550
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
function approximateRoot(envelope, derivative, initialGuess) {
|
|
1487
|
-
let result = initialGuess;
|
|
1488
|
-
for (let i = 1; i < rootIterations; i++) {
|
|
1489
|
-
result = result - envelope(result) / derivative(result);
|
|
1551
|
+
else if (isZero(origin) && typeof target === "string") {
|
|
1552
|
+
origin = getZeroUnit(target);
|
|
1490
1553
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
const durationKeys = ["duration", "bounce"];
|
|
1498
|
-
const physicsKeys = ["stiffness", "damping", "mass"];
|
|
1499
|
-
function isSpringType(options, keys) {
|
|
1500
|
-
return keys.some((key) => options[key] !== undefined);
|
|
1501
|
-
}
|
|
1502
|
-
function getSpringOptions(options) {
|
|
1503
|
-
let springOptions = {
|
|
1504
|
-
velocity: 0.0,
|
|
1505
|
-
stiffness: 100,
|
|
1506
|
-
damping: 10,
|
|
1507
|
-
mass: 1.0,
|
|
1508
|
-
isResolvedFromDuration: false,
|
|
1509
|
-
...options,
|
|
1510
|
-
};
|
|
1511
|
-
// stiffness/damping/mass overrides duration/bounce
|
|
1512
|
-
if (!isSpringType(options, physicsKeys) &&
|
|
1513
|
-
isSpringType(options, durationKeys)) {
|
|
1514
|
-
const derived = findSpring(options);
|
|
1515
|
-
springOptions = {
|
|
1516
|
-
...springOptions,
|
|
1517
|
-
...derived,
|
|
1518
|
-
velocity: 0.0,
|
|
1519
|
-
mass: 1.0,
|
|
1520
|
-
};
|
|
1521
|
-
springOptions.isResolvedFromDuration = true;
|
|
1554
|
+
else if (!Array.isArray(target) &&
|
|
1555
|
+
isZero(target) &&
|
|
1556
|
+
typeof origin === "string") {
|
|
1557
|
+
target = getZeroUnit(origin);
|
|
1522
1558
|
}
|
|
1523
|
-
return springOptions;
|
|
1524
|
-
}
|
|
1525
|
-
/**
|
|
1526
|
-
* This is based on the spring implementation of Wobble https://github.com/skevy/wobble
|
|
1527
|
-
*/
|
|
1528
|
-
function spring({ from = 0.0, to = 1.0, restSpeed = 2, restDelta = 0.01, ...options }) {
|
|
1529
1559
|
/**
|
|
1530
|
-
*
|
|
1531
|
-
* to reduce GC during animation.
|
|
1560
|
+
* If the target has been defined as a series of keyframes
|
|
1532
1561
|
*/
|
|
1533
|
-
|
|
1534
|
-
let { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
|
|
1535
|
-
let resolveSpring = zero;
|
|
1536
|
-
let resolveVelocity = zero;
|
|
1537
|
-
function createSpring() {
|
|
1538
|
-
const initialVelocity = velocity ? -(velocity / 1000) : 0.0;
|
|
1539
|
-
const initialDelta = to - from;
|
|
1540
|
-
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
|
|
1541
|
-
const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
|
|
1562
|
+
if (Array.isArray(target)) {
|
|
1542
1563
|
/**
|
|
1543
|
-
*
|
|
1544
|
-
*
|
|
1564
|
+
* Ensure an initial wildcard keyframe is hydrated by the origin.
|
|
1565
|
+
* TODO: Support extra wildcard keyframes i.e [1, null, 0]
|
|
1545
1566
|
*/
|
|
1546
|
-
if (
|
|
1547
|
-
|
|
1548
|
-
}
|
|
1549
|
-
if (dampingRatio < 1) {
|
|
1550
|
-
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
|
|
1551
|
-
// Underdamped spring
|
|
1552
|
-
resolveSpring = (t) => {
|
|
1553
|
-
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
1554
|
-
return (to -
|
|
1555
|
-
envelope *
|
|
1556
|
-
(((initialVelocity +
|
|
1557
|
-
dampingRatio * undampedAngularFreq * initialDelta) /
|
|
1558
|
-
angularFreq) *
|
|
1559
|
-
Math.sin(angularFreq * t) +
|
|
1560
|
-
initialDelta * Math.cos(angularFreq * t)));
|
|
1561
|
-
};
|
|
1562
|
-
resolveVelocity = (t) => {
|
|
1563
|
-
// TODO Resolve these calculations with the above
|
|
1564
|
-
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
1565
|
-
return (dampingRatio *
|
|
1566
|
-
undampedAngularFreq *
|
|
1567
|
-
envelope *
|
|
1568
|
-
((Math.sin(angularFreq * t) *
|
|
1569
|
-
(initialVelocity +
|
|
1570
|
-
dampingRatio *
|
|
1571
|
-
undampedAngularFreq *
|
|
1572
|
-
initialDelta)) /
|
|
1573
|
-
angularFreq +
|
|
1574
|
-
initialDelta * Math.cos(angularFreq * t)) -
|
|
1575
|
-
envelope *
|
|
1576
|
-
(Math.cos(angularFreq * t) *
|
|
1577
|
-
(initialVelocity +
|
|
1578
|
-
dampingRatio *
|
|
1579
|
-
undampedAngularFreq *
|
|
1580
|
-
initialDelta) -
|
|
1581
|
-
angularFreq *
|
|
1582
|
-
initialDelta *
|
|
1583
|
-
Math.sin(angularFreq * t)));
|
|
1584
|
-
};
|
|
1585
|
-
}
|
|
1586
|
-
else if (dampingRatio === 1) {
|
|
1587
|
-
// Critically damped spring
|
|
1588
|
-
resolveSpring = (t) => to -
|
|
1589
|
-
Math.exp(-undampedAngularFreq * t) *
|
|
1590
|
-
(initialDelta +
|
|
1591
|
-
(initialVelocity + undampedAngularFreq * initialDelta) *
|
|
1592
|
-
t);
|
|
1593
|
-
}
|
|
1594
|
-
else {
|
|
1595
|
-
// Overdamped spring
|
|
1596
|
-
const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
|
|
1597
|
-
resolveSpring = (t) => {
|
|
1598
|
-
const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
|
|
1599
|
-
// When performing sinh or cosh values can hit Infinity so we cap them here
|
|
1600
|
-
const freqForT = Math.min(dampedAngularFreq * t, 300);
|
|
1601
|
-
return (to -
|
|
1602
|
-
(envelope *
|
|
1603
|
-
((initialVelocity +
|
|
1604
|
-
dampingRatio * undampedAngularFreq * initialDelta) *
|
|
1605
|
-
Math.sinh(freqForT) +
|
|
1606
|
-
dampedAngularFreq *
|
|
1607
|
-
initialDelta *
|
|
1608
|
-
Math.cosh(freqForT))) /
|
|
1609
|
-
dampedAngularFreq);
|
|
1610
|
-
};
|
|
1567
|
+
if (target[0] === null) {
|
|
1568
|
+
target[0] = origin;
|
|
1611
1569
|
}
|
|
1570
|
+
return target;
|
|
1571
|
+
}
|
|
1572
|
+
else {
|
|
1573
|
+
return [origin, target];
|
|
1612
1574
|
}
|
|
1613
|
-
createSpring();
|
|
1614
|
-
return {
|
|
1615
|
-
next: (t) => {
|
|
1616
|
-
const current = resolveSpring(t);
|
|
1617
|
-
if (!isResolvedFromDuration) {
|
|
1618
|
-
const currentVelocity = resolveVelocity(t) * 1000;
|
|
1619
|
-
const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
|
|
1620
|
-
const isBelowDisplacementThreshold = Math.abs(to - current) <= restDelta;
|
|
1621
|
-
state.done =
|
|
1622
|
-
isBelowVelocityThreshold && isBelowDisplacementThreshold;
|
|
1623
|
-
}
|
|
1624
|
-
else {
|
|
1625
|
-
state.done = t >= duration;
|
|
1626
|
-
}
|
|
1627
|
-
state.value = state.done ? to : current;
|
|
1628
|
-
return state;
|
|
1629
|
-
},
|
|
1630
|
-
flipTarget: () => {
|
|
1631
|
-
velocity = -velocity;
|
|
1632
|
-
[from, to] = [to, from];
|
|
1633
|
-
createSpring();
|
|
1634
|
-
},
|
|
1635
|
-
};
|
|
1636
1575
|
}
|
|
1637
|
-
spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
|
|
1638
|
-
const zero = (_t) => 0;
|
|
1639
1576
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
if (target !== ideal)
|
|
1654
|
-
amplitude = target - from;
|
|
1655
|
-
return {
|
|
1656
|
-
next: (t) => {
|
|
1657
|
-
const delta = -amplitude * Math.exp(-t / timeConstant);
|
|
1658
|
-
state.done = !(delta > restDelta || delta < -restDelta);
|
|
1659
|
-
state.value = state.done ? target : target + delta;
|
|
1660
|
-
return state;
|
|
1661
|
-
},
|
|
1662
|
-
flipTarget: () => { },
|
|
1577
|
+
const featureTests = {
|
|
1578
|
+
waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
|
|
1579
|
+
};
|
|
1580
|
+
const results = {};
|
|
1581
|
+
const supports = {};
|
|
1582
|
+
/**
|
|
1583
|
+
* Generate features tests that cache their results.
|
|
1584
|
+
*/
|
|
1585
|
+
for (const key in featureTests) {
|
|
1586
|
+
supports[key] = () => {
|
|
1587
|
+
if (results[key] === undefined)
|
|
1588
|
+
results[key] = featureTests[key]();
|
|
1589
|
+
return results[key];
|
|
1663
1590
|
};
|
|
1664
1591
|
}
|
|
1665
1592
|
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
return
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1593
|
+
/**
|
|
1594
|
+
* A list of values that can be hardware-accelerated.
|
|
1595
|
+
*/
|
|
1596
|
+
const acceleratedValues = new Set([]);
|
|
1597
|
+
const createMotionValueAnimation = (valueName, value, target, transition = {}) => {
|
|
1598
|
+
return (onComplete) => {
|
|
1599
|
+
const valueTransition = getValueTransition(transition, valueName) || {};
|
|
1600
|
+
/**
|
|
1601
|
+
* Most transition values are currently completely overwritten by value-specific
|
|
1602
|
+
* transitions. In the future it'd be nicer to blend these transitions. But for now
|
|
1603
|
+
* delay actually does inherit from the root transition if not value-specific.
|
|
1604
|
+
*/
|
|
1605
|
+
const delay = valueTransition.delay || transition.delay || 0;
|
|
1606
|
+
/**
|
|
1607
|
+
* Elapsed isn't a public transition option but can be passed through from
|
|
1608
|
+
* optimized appear effects in milliseconds.
|
|
1609
|
+
*/
|
|
1610
|
+
let { elapsed = 0 } = transition;
|
|
1611
|
+
elapsed = elapsed - secondsToMilliseconds(delay);
|
|
1612
|
+
const keyframes = getKeyframes(value, valueName, target, valueTransition);
|
|
1613
|
+
/**
|
|
1614
|
+
* Check if we're able to animate between the start and end keyframes,
|
|
1615
|
+
* and throw a warning if we're attempting to animate between one that's
|
|
1616
|
+
* animatable and another that isn't.
|
|
1617
|
+
*/
|
|
1618
|
+
const originKeyframe = keyframes[0];
|
|
1619
|
+
const targetKeyframe = keyframes[keyframes.length - 1];
|
|
1620
|
+
const isOriginAnimatable = isAnimatable(valueName, originKeyframe);
|
|
1621
|
+
const isTargetAnimatable = isAnimatable(valueName, targetKeyframe);
|
|
1622
|
+
warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${valueName} 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.`);
|
|
1623
|
+
let options = {
|
|
1624
|
+
keyframes,
|
|
1625
|
+
velocity: value.getVelocity(),
|
|
1626
|
+
...valueTransition,
|
|
1627
|
+
elapsed,
|
|
1628
|
+
onUpdate: (v) => {
|
|
1629
|
+
value.set(v);
|
|
1630
|
+
valueTransition.onUpdate && valueTransition.onUpdate(v);
|
|
1631
|
+
},
|
|
1632
|
+
onComplete: () => {
|
|
1633
|
+
onComplete();
|
|
1634
|
+
valueTransition.onComplete && valueTransition.onComplete();
|
|
1635
|
+
},
|
|
1636
|
+
};
|
|
1637
|
+
if (!isOriginAnimatable ||
|
|
1638
|
+
!isTargetAnimatable ||
|
|
1639
|
+
instantAnimationState.current ||
|
|
1640
|
+
valueTransition.type === false) {
|
|
1641
|
+
/**
|
|
1642
|
+
* If we can't animate this value, or the global instant animation flag is set,
|
|
1643
|
+
* or this is simply defined as an instant transition, return an instant transition.
|
|
1644
|
+
*/
|
|
1645
|
+
return createInstantAnimation(options);
|
|
1710
1646
|
}
|
|
1711
|
-
else {
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1647
|
+
else if (valueTransition.type === "inertia") {
|
|
1648
|
+
/**
|
|
1649
|
+
* If this is an inertia animation, we currently don't support pre-generating
|
|
1650
|
+
* keyframes for this as such it must always run on the main thread.
|
|
1651
|
+
*/
|
|
1652
|
+
const animation = inertia(options);
|
|
1653
|
+
return () => animation.stop();
|
|
1715
1654
|
}
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
delta = -delta;
|
|
1726
|
-
elapsed += delta;
|
|
1727
|
-
if (!isComplete) {
|
|
1728
|
-
const state = animation.next(Math.max(0, elapsed));
|
|
1729
|
-
latest = state.value;
|
|
1730
|
-
if (interpolateFromNumber)
|
|
1731
|
-
latest = interpolateFromNumber(latest);
|
|
1732
|
-
isComplete = isForwardPlayback ? state.done : elapsed <= 0;
|
|
1655
|
+
/**
|
|
1656
|
+
* If there's no transition defined for this value, we can generate
|
|
1657
|
+
* unqiue transition settings for this value.
|
|
1658
|
+
*/
|
|
1659
|
+
if (!isTransitionDefined(valueTransition)) {
|
|
1660
|
+
options = {
|
|
1661
|
+
...options,
|
|
1662
|
+
...getDefaultTransition(valueName, options),
|
|
1663
|
+
};
|
|
1733
1664
|
}
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1665
|
+
/**
|
|
1666
|
+
* Both WAAPI and our internal animation functions use durations
|
|
1667
|
+
* as defined by milliseconds, while our external API defines them
|
|
1668
|
+
* as seconds.
|
|
1669
|
+
*/
|
|
1670
|
+
if (options.duration) {
|
|
1671
|
+
options.duration = secondsToMilliseconds(options.duration);
|
|
1672
|
+
}
|
|
1673
|
+
if (options.repeatDelay) {
|
|
1674
|
+
options.repeatDelay = secondsToMilliseconds(options.repeatDelay);
|
|
1675
|
+
}
|
|
1676
|
+
const canAccelerateAnimation = acceleratedValues.has(valueName) &&
|
|
1677
|
+
supports.waapi() &&
|
|
1678
|
+
value.owner &&
|
|
1679
|
+
!value.owner.getProps().onUpdate &&
|
|
1680
|
+
!options.repeat;
|
|
1681
|
+
if (canAccelerateAnimation) {
|
|
1682
|
+
/**
|
|
1683
|
+
* If this animation is capable of being run via WAAPI, then do so.
|
|
1684
|
+
*
|
|
1685
|
+
* TODO: Currently no values are hardware accelerated so this clause
|
|
1686
|
+
* will never trigger. Animation to be added in subsequent PR.
|
|
1687
|
+
*/
|
|
1688
|
+
return createAcceleratedAnimation();
|
|
1689
|
+
}
|
|
1690
|
+
else {
|
|
1691
|
+
/**
|
|
1692
|
+
* Otherwise, fall back to the main thread.
|
|
1693
|
+
*/
|
|
1694
|
+
const animation = animate$1(options);
|
|
1695
|
+
return () => animation.stop();
|
|
1746
1696
|
}
|
|
1747
|
-
}
|
|
1748
|
-
function play() {
|
|
1749
|
-
onPlay === null || onPlay === void 0 ? void 0 : onPlay();
|
|
1750
|
-
driverControls = driver(update);
|
|
1751
|
-
driverControls.start();
|
|
1752
|
-
}
|
|
1753
|
-
autoplay && play();
|
|
1754
|
-
return {
|
|
1755
|
-
stop: () => {
|
|
1756
|
-
onStop === null || onStop === void 0 ? void 0 : onStop();
|
|
1757
|
-
driverControls.stop();
|
|
1758
|
-
},
|
|
1759
1697
|
};
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1700
|
+
function addUniqueItem(arr, item) {
|
|
1701
|
+
if (arr.indexOf(item) === -1)
|
|
1702
|
+
arr.push(item);
|
|
1703
|
+
}
|
|
1704
|
+
function removeItem(arr, item) {
|
|
1705
|
+
const index = arr.indexOf(item);
|
|
1706
|
+
if (index > -1)
|
|
1707
|
+
arr.splice(index, 1);
|
|
1760
1708
|
}
|
|
1761
1709
|
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
return (min !== undefined && v < min) || (max !== undefined && v > max);
|
|
1710
|
+
class SubscriptionManager {
|
|
1711
|
+
constructor() {
|
|
1712
|
+
this.subscriptions = [];
|
|
1766
1713
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
if (max === undefined)
|
|
1771
|
-
return min;
|
|
1772
|
-
return Math.abs(min - v) < Math.abs(max - v) ? min : max;
|
|
1714
|
+
add(handler) {
|
|
1715
|
+
addUniqueItem(this.subscriptions, handler);
|
|
1716
|
+
return () => removeItem(this.subscriptions, handler);
|
|
1773
1717
|
}
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1718
|
+
notify(a, b, c) {
|
|
1719
|
+
const numSubscriptions = this.subscriptions.length;
|
|
1720
|
+
if (!numSubscriptions)
|
|
1721
|
+
return;
|
|
1722
|
+
if (numSubscriptions === 1) {
|
|
1723
|
+
/**
|
|
1724
|
+
* If there's only a single handler we can just call it without invoking a loop.
|
|
1725
|
+
*/
|
|
1726
|
+
this.subscriptions[0](a, b, c);
|
|
1727
|
+
}
|
|
1728
|
+
else {
|
|
1729
|
+
for (let i = 0; i < numSubscriptions; i++) {
|
|
1730
|
+
/**
|
|
1731
|
+
* Check whether the handler exists before firing as it's possible
|
|
1732
|
+
* the subscriptions were modified during this loop running.
|
|
1733
|
+
*/
|
|
1734
|
+
const handler = this.subscriptions[i];
|
|
1735
|
+
handler && handler(a, b, c);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1787
1738
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
type: "spring",
|
|
1791
|
-
stiffness: bounceStiffness,
|
|
1792
|
-
damping: bounceDamping,
|
|
1793
|
-
restDelta,
|
|
1794
|
-
...options,
|
|
1795
|
-
});
|
|
1739
|
+
getSize() {
|
|
1740
|
+
return this.subscriptions.length;
|
|
1796
1741
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
startSpring({ from, velocity, to: boundaryNearest(from) });
|
|
1742
|
+
clear() {
|
|
1743
|
+
this.subscriptions.length = 0;
|
|
1800
1744
|
}
|
|
1801
|
-
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
const isFloat = (value) => {
|
|
1748
|
+
return !isNaN(parseFloat(value));
|
|
1749
|
+
};
|
|
1750
|
+
/**
|
|
1751
|
+
* `MotionValue` is used to track the state and velocity of motion values.
|
|
1752
|
+
*
|
|
1753
|
+
* @public
|
|
1754
|
+
*/
|
|
1755
|
+
class MotionValue {
|
|
1756
|
+
/**
|
|
1757
|
+
* @param init - The initiating value
|
|
1758
|
+
* @param config - Optional configuration options
|
|
1759
|
+
*
|
|
1760
|
+
* - `transformer`: A function to transform incoming values with.
|
|
1761
|
+
*
|
|
1762
|
+
* @internal
|
|
1763
|
+
*/
|
|
1764
|
+
constructor(init) {
|
|
1802
1765
|
/**
|
|
1803
|
-
*
|
|
1804
|
-
*
|
|
1766
|
+
* This will be replaced by the build step with the latest version number.
|
|
1767
|
+
* When MotionValues are provided to motion components, warn if versions are mixed.
|
|
1768
|
+
*/
|
|
1769
|
+
this.version = "7.8.1";
|
|
1770
|
+
/**
|
|
1771
|
+
* Duration, in milliseconds, since last updating frame.
|
|
1805
1772
|
*
|
|
1806
|
-
*
|
|
1807
|
-
* If it is, we want to check per frame when to switch to a spring
|
|
1808
|
-
* animation
|
|
1773
|
+
* @internal
|
|
1809
1774
|
*/
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1775
|
+
this.timeDelta = 0;
|
|
1776
|
+
/**
|
|
1777
|
+
* Timestamp of the last time this `MotionValue` was updated.
|
|
1778
|
+
*
|
|
1779
|
+
* @internal
|
|
1780
|
+
*/
|
|
1781
|
+
this.lastUpdated = 0;
|
|
1782
|
+
/**
|
|
1783
|
+
* Functions to notify when the `MotionValue` updates.
|
|
1784
|
+
*
|
|
1785
|
+
* @internal
|
|
1786
|
+
*/
|
|
1787
|
+
this.updateSubscribers = new SubscriptionManager();
|
|
1788
|
+
/**
|
|
1789
|
+
* Functions to notify when the velocity updates.
|
|
1790
|
+
*
|
|
1791
|
+
* @internal
|
|
1792
|
+
*/
|
|
1793
|
+
this.velocityUpdateSubscribers = new SubscriptionManager();
|
|
1794
|
+
/**
|
|
1795
|
+
* Functions to notify when the `MotionValue` updates and `render` is set to `true`.
|
|
1796
|
+
*
|
|
1797
|
+
* @internal
|
|
1798
|
+
*/
|
|
1799
|
+
this.renderSubscribers = new SubscriptionManager();
|
|
1800
|
+
/**
|
|
1801
|
+
* Tracks whether this value can output a velocity. Currently this is only true
|
|
1802
|
+
* if the value is numerical, but we might be able to widen the scope here and support
|
|
1803
|
+
* other value types.
|
|
1804
|
+
*
|
|
1805
|
+
* @internal
|
|
1806
|
+
*/
|
|
1807
|
+
this.canTrackVelocity = false;
|
|
1808
|
+
this.updateAndNotify = (v, render = true) => {
|
|
1809
|
+
this.prev = this.current;
|
|
1810
|
+
this.current = v;
|
|
1811
|
+
// Update timestamp
|
|
1812
|
+
const { delta, timestamp } = frameData;
|
|
1813
|
+
if (this.lastUpdated !== timestamp) {
|
|
1814
|
+
this.timeDelta = delta;
|
|
1815
|
+
this.lastUpdated = timestamp;
|
|
1816
|
+
sync.postRender(this.scheduleVelocityCheck);
|
|
1817
|
+
}
|
|
1818
|
+
// Update update subscribers
|
|
1819
|
+
if (this.prev !== this.current) {
|
|
1820
|
+
this.updateSubscribers.notify(this.current);
|
|
1821
|
+
}
|
|
1822
|
+
// Update velocity subscribers
|
|
1823
|
+
if (this.velocityUpdateSubscribers.getSize()) {
|
|
1824
|
+
this.velocityUpdateSubscribers.notify(this.getVelocity());
|
|
1825
|
+
}
|
|
1826
|
+
// Update render subscribers
|
|
1827
|
+
if (render) {
|
|
1828
|
+
this.renderSubscribers.notify(this.current);
|
|
1829
|
+
}
|
|
1830
|
+
};
|
|
1831
|
+
/**
|
|
1832
|
+
* Schedule a velocity check for the next frame.
|
|
1833
|
+
*
|
|
1834
|
+
* This is an instanced and bound function to prevent generating a new
|
|
1835
|
+
* function once per frame.
|
|
1836
|
+
*
|
|
1837
|
+
* @internal
|
|
1838
|
+
*/
|
|
1839
|
+
this.scheduleVelocityCheck = () => sync.postRender(this.velocityCheck);
|
|
1840
|
+
/**
|
|
1841
|
+
* Updates `prev` with `current` if the value hasn't been updated this frame.
|
|
1842
|
+
* This ensures velocity calculations return `0`.
|
|
1843
|
+
*
|
|
1844
|
+
* This is an instanced and bound function to prevent generating a new
|
|
1845
|
+
* function once per frame.
|
|
1846
|
+
*
|
|
1847
|
+
* @internal
|
|
1848
|
+
*/
|
|
1849
|
+
this.velocityCheck = ({ timestamp }) => {
|
|
1850
|
+
if (timestamp !== this.lastUpdated) {
|
|
1851
|
+
this.prev = this.current;
|
|
1852
|
+
this.velocityUpdateSubscribers.notify(this.getVelocity());
|
|
1824
1853
|
}
|
|
1825
1854
|
};
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
velocity,
|
|
1830
|
-
timeConstant,
|
|
1831
|
-
power,
|
|
1832
|
-
restDelta,
|
|
1833
|
-
modifyTarget,
|
|
1834
|
-
onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
|
|
1835
|
-
});
|
|
1855
|
+
this.hasAnimated = false;
|
|
1856
|
+
this.prev = this.current = init;
|
|
1857
|
+
this.canTrackVelocity = isFloat(this.current);
|
|
1836
1858
|
}
|
|
1837
|
-
return {
|
|
1838
|
-
stop: () => currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop(),
|
|
1839
|
-
};
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
/**
|
|
1843
|
-
* Decide whether a transition is defined on a given Transition.
|
|
1844
|
-
* This filters out orchestration options and returns true
|
|
1845
|
-
* if any options are left.
|
|
1846
|
-
*/
|
|
1847
|
-
function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, ...transition }) {
|
|
1848
|
-
return !!Object.keys(transition).length;
|
|
1849
|
-
}
|
|
1850
|
-
/**
|
|
1851
|
-
* Convert Framer Motion's Transition type into Popmotion-compatible options.
|
|
1852
|
-
*/
|
|
1853
|
-
function convertTransitionToAnimationOptions({ ease, times, ...transition }) {
|
|
1854
|
-
const options = { ...transition };
|
|
1855
|
-
if (times)
|
|
1856
|
-
options["offset"] = times;
|
|
1857
1859
|
/**
|
|
1858
|
-
*
|
|
1860
|
+
* Adds a function that will be notified when the `MotionValue` is updated.
|
|
1861
|
+
*
|
|
1862
|
+
* It returns a function that, when called, will cancel the subscription.
|
|
1863
|
+
*
|
|
1864
|
+
* When calling `onChange` inside a React component, it should be wrapped with the
|
|
1865
|
+
* `useEffect` hook. As it returns an unsubscribe function, this should be returned
|
|
1866
|
+
* from the `useEffect` function to ensure you don't add duplicate subscribers..
|
|
1867
|
+
*
|
|
1868
|
+
* ```jsx
|
|
1869
|
+
* export const MyComponent = () => {
|
|
1870
|
+
* const x = useMotionValue(0)
|
|
1871
|
+
* const y = useMotionValue(0)
|
|
1872
|
+
* const opacity = useMotionValue(1)
|
|
1873
|
+
*
|
|
1874
|
+
* useEffect(() => {
|
|
1875
|
+
* function updateOpacity() {
|
|
1876
|
+
* const maxXY = Math.max(x.get(), y.get())
|
|
1877
|
+
* const newOpacity = transform(maxXY, [0, 100], [1, 0])
|
|
1878
|
+
* opacity.set(newOpacity)
|
|
1879
|
+
* }
|
|
1880
|
+
*
|
|
1881
|
+
* const unsubscribeX = x.onChange(updateOpacity)
|
|
1882
|
+
* const unsubscribeY = y.onChange(updateOpacity)
|
|
1883
|
+
*
|
|
1884
|
+
* return () => {
|
|
1885
|
+
* unsubscribeX()
|
|
1886
|
+
* unsubscribeY()
|
|
1887
|
+
* }
|
|
1888
|
+
* }, [])
|
|
1889
|
+
*
|
|
1890
|
+
* return <motion.div style={{ x }} />
|
|
1891
|
+
* }
|
|
1892
|
+
* ```
|
|
1893
|
+
*
|
|
1894
|
+
* @privateRemarks
|
|
1895
|
+
*
|
|
1896
|
+
* We could look into a `useOnChange` hook if the above lifecycle management proves confusing.
|
|
1897
|
+
*
|
|
1898
|
+
* ```jsx
|
|
1899
|
+
* useOnChange(x, () => {})
|
|
1900
|
+
* ```
|
|
1901
|
+
*
|
|
1902
|
+
* @param subscriber - A function that receives the latest value.
|
|
1903
|
+
* @returns A function that, when called, will cancel this subscription.
|
|
1904
|
+
*
|
|
1905
|
+
* @public
|
|
1859
1906
|
*/
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1907
|
+
onChange(subscription) {
|
|
1908
|
+
return this.updateSubscribers.add(subscription);
|
|
1909
|
+
}
|
|
1910
|
+
clearListeners() {
|
|
1911
|
+
this.updateSubscribers.clear();
|
|
1912
|
+
}
|
|
1864
1913
|
/**
|
|
1865
|
-
*
|
|
1914
|
+
* Adds a function that will be notified when the `MotionValue` requests a render.
|
|
1915
|
+
*
|
|
1916
|
+
* @param subscriber - A function that's provided the latest value.
|
|
1917
|
+
* @returns A function that, when called, will cancel this subscription.
|
|
1918
|
+
*
|
|
1919
|
+
* @internal
|
|
1866
1920
|
*/
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1921
|
+
onRenderRequest(subscription) {
|
|
1922
|
+
// Render immediately
|
|
1923
|
+
subscription(this.get());
|
|
1924
|
+
return this.renderSubscribers.add(subscription);
|
|
1871
1925
|
}
|
|
1872
1926
|
/**
|
|
1873
|
-
*
|
|
1927
|
+
* Attaches a passive effect to the `MotionValue`.
|
|
1928
|
+
*
|
|
1929
|
+
* @internal
|
|
1874
1930
|
*/
|
|
1875
|
-
|
|
1876
|
-
|
|
1931
|
+
attach(passiveEffect) {
|
|
1932
|
+
this.passiveEffect = passiveEffect;
|
|
1933
|
+
}
|
|
1877
1934
|
/**
|
|
1878
|
-
*
|
|
1879
|
-
*
|
|
1880
|
-
*
|
|
1935
|
+
* Sets the state of the `MotionValue`.
|
|
1936
|
+
*
|
|
1937
|
+
* @remarks
|
|
1938
|
+
*
|
|
1939
|
+
* ```jsx
|
|
1940
|
+
* const x = useMotionValue(0)
|
|
1941
|
+
* x.set(10)
|
|
1942
|
+
* ```
|
|
1943
|
+
*
|
|
1944
|
+
* @param latest - Latest value to set.
|
|
1945
|
+
* @param render - Whether to notify render subscribers. Defaults to `true`
|
|
1946
|
+
*
|
|
1947
|
+
* @public
|
|
1881
1948
|
*/
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
function getDelayFromTransition(transition, key) {
|
|
1890
|
-
const valueTransition = getValueTransition(transition, key) || {};
|
|
1891
|
-
return valueTransition.delay !== undefined
|
|
1892
|
-
? valueTransition.delay
|
|
1893
|
-
: transition.delay !== undefined
|
|
1894
|
-
? transition.delay
|
|
1895
|
-
: 0;
|
|
1896
|
-
}
|
|
1897
|
-
function hydrateKeyframes(options) {
|
|
1898
|
-
if (Array.isArray(options.to) && options.to[0] === null) {
|
|
1899
|
-
options.to = [...options.to];
|
|
1900
|
-
options.to[0] = options.from;
|
|
1949
|
+
set(v, render = true) {
|
|
1950
|
+
if (!render || !this.passiveEffect) {
|
|
1951
|
+
this.updateAndNotify(v, render);
|
|
1952
|
+
}
|
|
1953
|
+
else {
|
|
1954
|
+
this.passiveEffect(v, this.updateAndNotify);
|
|
1955
|
+
}
|
|
1901
1956
|
}
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1957
|
+
/**
|
|
1958
|
+
* Returns the latest state of `MotionValue`
|
|
1959
|
+
*
|
|
1960
|
+
* @returns - The latest state of `MotionValue`
|
|
1961
|
+
*
|
|
1962
|
+
* @public
|
|
1963
|
+
*/
|
|
1964
|
+
get() {
|
|
1965
|
+
return this.current;
|
|
1907
1966
|
}
|
|
1908
|
-
hydrateKeyframes(options);
|
|
1909
1967
|
/**
|
|
1910
|
-
*
|
|
1968
|
+
* @public
|
|
1911
1969
|
*/
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
...transition,
|
|
1915
|
-
...getDefaultTransition(key, options.to),
|
|
1916
|
-
};
|
|
1970
|
+
getPrevious() {
|
|
1971
|
+
return this.prev;
|
|
1917
1972
|
}
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
* If we're trying to animate from "none", try and get an animatable version
|
|
1933
|
-
* of the target. This could be improved to work both ways.
|
|
1934
|
-
*/
|
|
1935
|
-
origin = getAnimatableNone(key, target);
|
|
1973
|
+
/**
|
|
1974
|
+
* Returns the latest velocity of `MotionValue`
|
|
1975
|
+
*
|
|
1976
|
+
* @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
|
|
1977
|
+
*
|
|
1978
|
+
* @public
|
|
1979
|
+
*/
|
|
1980
|
+
getVelocity() {
|
|
1981
|
+
// This could be isFloat(this.prev) && isFloat(this.current), but that would be wasteful
|
|
1982
|
+
return this.canTrackVelocity
|
|
1983
|
+
? // These casts could be avoided if parseFloat would be typed better
|
|
1984
|
+
velocityPerSecond(parseFloat(this.current) -
|
|
1985
|
+
parseFloat(this.prev), this.timeDelta)
|
|
1986
|
+
: 0;
|
|
1936
1987
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1988
|
+
/**
|
|
1989
|
+
* Registers a new animation to control this `MotionValue`. Only one
|
|
1990
|
+
* animation can drive a `MotionValue` at one time.
|
|
1991
|
+
*
|
|
1992
|
+
* ```jsx
|
|
1993
|
+
* value.start()
|
|
1994
|
+
* ```
|
|
1995
|
+
*
|
|
1996
|
+
* @param animation - A function that starts the provided animation
|
|
1997
|
+
*
|
|
1998
|
+
* @internal
|
|
1999
|
+
*/
|
|
2000
|
+
start(animation) {
|
|
2001
|
+
this.stop();
|
|
2002
|
+
return new Promise((resolve) => {
|
|
2003
|
+
this.hasAnimated = true;
|
|
2004
|
+
this.stopAnimation = animation(resolve);
|
|
2005
|
+
}).then(() => this.clearAnimation());
|
|
1939
2006
|
}
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
2007
|
+
/**
|
|
2008
|
+
* Stop the currently active animation.
|
|
2009
|
+
*
|
|
2010
|
+
* @public
|
|
2011
|
+
*/
|
|
2012
|
+
stop() {
|
|
2013
|
+
if (this.stopAnimation)
|
|
2014
|
+
this.stopAnimation();
|
|
2015
|
+
this.clearAnimation();
|
|
1944
2016
|
}
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
onComplete,
|
|
1953
|
-
onUpdate: (v) => value.set(v),
|
|
1954
|
-
};
|
|
1955
|
-
return valueTransition.type === "inertia" ||
|
|
1956
|
-
valueTransition.type === "decay"
|
|
1957
|
-
? inertia({ ...options, ...valueTransition })
|
|
1958
|
-
: animate$1({
|
|
1959
|
-
...getPopmotionAnimationOptions(valueTransition, options, key),
|
|
1960
|
-
onUpdate: (v) => {
|
|
1961
|
-
options.onUpdate(v);
|
|
1962
|
-
valueTransition.onUpdate && valueTransition.onUpdate(v);
|
|
1963
|
-
},
|
|
1964
|
-
onComplete: () => {
|
|
1965
|
-
options.onComplete();
|
|
1966
|
-
valueTransition.onComplete && valueTransition.onComplete();
|
|
1967
|
-
},
|
|
1968
|
-
});
|
|
2017
|
+
/**
|
|
2018
|
+
* Returns `true` if this value is currently animating.
|
|
2019
|
+
*
|
|
2020
|
+
* @public
|
|
2021
|
+
*/
|
|
2022
|
+
isAnimating() {
|
|
2023
|
+
return !!this.stopAnimation;
|
|
1969
2024
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2025
|
+
clearAnimation() {
|
|
2026
|
+
this.stopAnimation = null;
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Destroy and clean up subscribers to this `MotionValue`.
|
|
2030
|
+
*
|
|
2031
|
+
* The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
|
|
2032
|
+
* handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
|
|
2033
|
+
* created a `MotionValue` via the `motionValue` function.
|
|
2034
|
+
*
|
|
2035
|
+
* @public
|
|
2036
|
+
*/
|
|
2037
|
+
destroy() {
|
|
2038
|
+
this.updateSubscribers.clear();
|
|
2039
|
+
this.renderSubscribers.clear();
|
|
2040
|
+
this.stop();
|
|
1977
2041
|
}
|
|
1978
|
-
return !isOriginAnimatable ||
|
|
1979
|
-
!isTargetAnimatable ||
|
|
1980
|
-
valueTransition.type === false
|
|
1981
|
-
? set
|
|
1982
|
-
: start;
|
|
1983
|
-
}
|
|
1984
|
-
function isZero(value) {
|
|
1985
|
-
return (value === 0 ||
|
|
1986
|
-
(typeof value === "string" &&
|
|
1987
|
-
parseFloat(value) === 0 &&
|
|
1988
|
-
value.indexOf(" ") === -1));
|
|
1989
|
-
}
|
|
1990
|
-
function getZeroUnit(potentialUnitType) {
|
|
1991
|
-
return typeof potentialUnitType === "number"
|
|
1992
|
-
? 0
|
|
1993
|
-
: getAnimatableNone("", potentialUnitType);
|
|
1994
|
-
}
|
|
1995
|
-
function getValueTransition(transition, key) {
|
|
1996
|
-
return transition[key] || transition["default"] || transition;
|
|
1997
2042
|
}
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
* Framer Motion and Popmotion
|
|
2001
|
-
*/
|
|
2002
|
-
function startAnimation(key, value, target, transition = {}) {
|
|
2003
|
-
if (instantAnimationState.current) {
|
|
2004
|
-
transition = { type: false };
|
|
2005
|
-
}
|
|
2006
|
-
return value.start((onComplete) => {
|
|
2007
|
-
let controls;
|
|
2008
|
-
const animation = getAnimation(key, value, target, transition, onComplete);
|
|
2009
|
-
const delayBy = getDelayFromTransition(transition, key);
|
|
2010
|
-
const start = () => (controls = animation());
|
|
2011
|
-
let cancelDelay;
|
|
2012
|
-
if (delayBy) {
|
|
2013
|
-
cancelDelay = delay(start, secondsToMilliseconds(delayBy));
|
|
2014
|
-
}
|
|
2015
|
-
else {
|
|
2016
|
-
start();
|
|
2017
|
-
}
|
|
2018
|
-
return () => {
|
|
2019
|
-
cancelDelay && cancelDelay();
|
|
2020
|
-
controls && controls.stop();
|
|
2021
|
-
};
|
|
2022
|
-
});
|
|
2043
|
+
function motionValue(init) {
|
|
2044
|
+
return new MotionValue(init);
|
|
2023
2045
|
}
|
|
2024
2046
|
|
|
2047
|
+
const isMotionValue = (value) => !!(value === null || value === void 0 ? void 0 : value.getVelocity);
|
|
2048
|
+
|
|
2025
2049
|
/**
|
|
2026
2050
|
* Animate a single value or a `MotionValue`.
|
|
2027
2051
|
*
|
|
@@ -2051,7 +2075,7 @@
|
|
|
2051
2075
|
*/
|
|
2052
2076
|
function animate(from, to, transition = {}) {
|
|
2053
2077
|
const value = isMotionValue(from) ? from : motionValue(from);
|
|
2054
|
-
|
|
2078
|
+
value.start(createMotionValueAnimation("", value, to, transition));
|
|
2055
2079
|
return {
|
|
2056
2080
|
stop: () => value.stop(),
|
|
2057
2081
|
isAnimating: () => value.isAnimating(),
|
|
@@ -2595,6 +2619,14 @@
|
|
|
2595
2619
|
}
|
|
2596
2620
|
}
|
|
2597
2621
|
|
|
2622
|
+
const isKeyframesTarget = (v) => {
|
|
2623
|
+
return Array.isArray(v);
|
|
2624
|
+
};
|
|
2625
|
+
|
|
2626
|
+
const isCustomValue = (v) => {
|
|
2627
|
+
return Boolean(v && typeof v === "object" && v.mix && v.toValue);
|
|
2628
|
+
};
|
|
2629
|
+
|
|
2598
2630
|
/**
|
|
2599
2631
|
* If the provided value is a MotionValue, this returns the actual value, otherwise just the value itself
|
|
2600
2632
|
*
|
|
@@ -4877,7 +4909,7 @@
|
|
|
4877
4909
|
* and warn against mismatches.
|
|
4878
4910
|
*/
|
|
4879
4911
|
{
|
|
4880
|
-
warnOnce(nextValue.version === "7.
|
|
4912
|
+
warnOnce(nextValue.version === "7.8.1", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.8.1 may not work as expected.`);
|
|
4881
4913
|
}
|
|
4882
4914
|
}
|
|
4883
4915
|
else if (isMotionValue(prevValue)) {
|