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.
Files changed (43) hide show
  1. package/dist/cjs/index.js +1966 -1860
  2. package/dist/es/animation/animate.mjs +2 -2
  3. package/dist/es/animation/create-accelerated-animation.mjs +8 -0
  4. package/dist/es/animation/create-instant-animation.mjs +12 -0
  5. package/dist/es/animation/{animation-controls.mjs → hooks/animation-controls.mjs} +2 -2
  6. package/dist/es/animation/{use-animated-state.mjs → hooks/use-animated-state.mjs} +6 -6
  7. package/dist/es/animation/{use-animation.mjs → hooks/use-animation.mjs} +1 -1
  8. package/dist/es/animation/index.mjs +121 -0
  9. package/dist/es/animation/legacy-popmotion/decay.mjs +11 -4
  10. package/dist/es/animation/legacy-popmotion/index.mjs +23 -15
  11. package/dist/es/animation/legacy-popmotion/inertia.mjs +14 -8
  12. package/dist/es/animation/legacy-popmotion/keyframes.mjs +21 -13
  13. package/dist/es/animation/legacy-popmotion/spring.mjs +33 -39
  14. package/dist/es/animation/optimized-appear/data-id.mjs +6 -0
  15. package/dist/es/animation/optimized-appear/handoff.mjs +34 -0
  16. package/dist/es/animation/optimized-appear/start.mjs +15 -0
  17. package/dist/es/animation/optimized-appear/store-id.mjs +3 -0
  18. package/dist/es/animation/utils/default-transitions.mjs +9 -14
  19. package/dist/es/animation/utils/keyframes.mjs +41 -0
  20. package/dist/es/animation/utils/transitions.mjs +1 -171
  21. package/dist/es/animation/waapi/easing.mjs +3 -0
  22. package/dist/es/animation/waapi/index.mjs +16 -0
  23. package/dist/es/animation/waapi/supports.mjs +17 -0
  24. package/dist/es/gestures/drag/VisualElementDragControls.mjs +2 -2
  25. package/dist/es/index.mjs +6 -3
  26. package/dist/es/render/utils/animation.mjs +15 -3
  27. package/dist/es/render/utils/motion-values.mjs +1 -1
  28. package/dist/es/utils/delay.mjs +3 -0
  29. package/dist/es/value/index.mjs +2 -2
  30. package/dist/es/value/use-spring.mjs +1 -2
  31. package/dist/framer-motion.dev.js +1971 -1865
  32. package/dist/framer-motion.js +1 -1
  33. package/dist/index.d.ts +424 -341
  34. package/dist/projection.dev.js +1655 -1623
  35. package/dist/size-rollup-dom-animation-assets.js +1 -1
  36. package/dist/size-rollup-dom-animation.js +1 -1
  37. package/dist/size-rollup-dom-max-assets.js +1 -1
  38. package/dist/size-rollup-dom-max.js +1 -1
  39. package/dist/size-rollup-motion.js +1 -1
  40. package/dist/size-webpack-dom-animation.js +1 -1
  41. package/dist/size-webpack-dom-max.js +1 -1
  42. package/dist/three-entry.d.ts +289 -282
  43. package/package.json +11 -9
@@ -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.react));
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 addUniqueItem(arr, item) {
165
- if (arr.indexOf(item) === -1)
166
- arr.push(item);
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
- function removeItem(arr, item) {
169
- const index = arr.indexOf(item);
170
- if (index > -1)
171
- arr.splice(index, 1);
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
- class SubscriptionManager {
175
- constructor() {
176
- this.subscriptions = [];
177
- }
178
- add(handler) {
179
- addUniqueItem(this.subscriptions, handler);
180
- return () => removeItem(this.subscriptions, handler);
181
- }
182
- notify(a, b, c) {
183
- const numSubscriptions = this.subscriptions.length;
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
- getSize() {
204
- return this.subscriptions.length;
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
- Convert velocity into velocity per second
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
- @param [number]: Unit per frame
215
- @param [number]: Frame duration in ms
216
- */
217
- function velocityPerSecond(velocity, frameDuration) {
218
- return frameDuration ? velocity * (1000 / frameDuration) : 0;
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 isFloat = (value) => {
222
- return !isNaN(parseFloat(value));
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
- * `MotionValue` is used to track the state and velocity of motion values.
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
- const numbers = v.match(floatRegex);
840
- if (numbers) {
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
- return { values, numColors, numNumbers, tokenised: v };
846
- }
847
- function parse(v) {
848
- return analyseComplexValue(v).values;
641
+ else if (typeof v === "object") {
642
+ return mixObject;
643
+ }
644
+ return mixNumber;
849
645
  }
850
- function createTransformer(source) {
851
- const { values, numColors, tokenised } = analyseComplexValue(source);
852
- const numValues = values.length;
853
- return (v) => {
854
- let output = tokenised;
855
- for (let i = 0; i < numValues; i++) {
856
- output = output.replace(i < numColors ? colorToken : numberToken, i < numColors
857
- ? color.transform(v[i])
858
- : sanitize(v[i]));
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
- return output;
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
- * Check if a value is animatable. Examples:
661
+ * Create a function that maps from a numerical input array to a generic output array.
873
662
  *
874
- * ✅: 100, "100px", "#fff"
875
- * ❌: "block", "url(2.jpg)"
876
- * @param value
663
+ * Accepts:
664
+ * - Numbers
665
+ * - Colors (hex, hsl, hsla, rgb, rgba)
666
+ * - Complex (combinations of one or more numbers or strings)
877
667
  *
878
- * @internal
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
- const isAnimatable = (key, value) => {
881
- // If the list of keys tat might be non-animatable grows, replace with Set
882
- if (key === "zIndex")
883
- return false;
884
- // If it's a number or a keyframes array, we can animate it. We might at some point
885
- // need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
886
- // but for now lets leave it like this for performance reasons
887
- if (typeof value === "number" || Array.isArray(value))
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
- return false;
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 isKeyframesTarget = (v) => {
899
- return Array.isArray(v);
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 underDampedSpring = () => ({
903
- type: "spring",
904
- stiffness: 500,
905
- damping: 25,
906
- restSpeed: 10,
907
- });
908
- const criticallyDampedSpring = (to) => ({
909
- type: "spring",
910
- stiffness: 550,
911
- damping: to === 0 ? 2 * Math.sqrt(550) : 30,
912
- restSpeed: 10,
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 getDefaultTransition = (valueKey, to) => {
941
- let transitionFactory;
942
- if (isKeyframesTarget(to)) {
943
- transitionFactory = keyframes$1;
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
- transitionFactory =
947
- defaultTransitions[valueKey] || defaultTransitions.default;
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 { to, ...transitionFactory(to) };
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
- const numberValueTypes = {
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
- * A map of default value types for common values
1052
- */
1053
- const defaultValueTypes = {
1054
- ...numberValueTypes,
1055
- // Color props
1056
- color,
1057
- backgroundColor: color,
1058
- outlineColor: color,
1059
- fill: color,
1060
- stroke: color,
1061
- // Border props
1062
- borderColor: color,
1063
- borderTopColor: color,
1064
- borderRightColor: color,
1065
- borderBottomColor: color,
1066
- borderLeftColor: color,
1067
- filter,
1068
- WebkitFilter: filter,
1069
- };
1070
- /**
1071
- * Gets the default ValueType for the provided value key
1072
- */
1073
- const getDefaultValueType = (key) => defaultValueTypes[key];
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
- function getAnimatableNone(key, value) {
1076
- var _a;
1077
- let defaultValueType = getDefaultValueType(key);
1078
- if (defaultValueType !== filter)
1079
- defaultValueType = complex;
1080
- // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
1081
- return (_a = defaultValueType.getAnimatableNone) === null || _a === void 0 ? void 0 : _a.call(defaultValueType, value);
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
- const instantAnimationState = {
1085
- current: false,
1086
- };
943
+ /*
944
+ Convert velocity into velocity per second
1087
945
 
1088
- const isCustomValue = (v) => {
1089
- return Boolean(v && typeof v === "object" && v.mix && v.toValue);
1090
- };
1091
- const resolveFinalValueInKeyframes = (v) => {
1092
- // TODO maybe throw if v.length - 1 is placeholder token?
1093
- return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
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
- function delay(callback, timeout) {
1097
- const start = performance.now();
1098
- const checkElapsed = ({ timestamp }) => {
1099
- const elapsed = timestamp - start;
1100
- if (elapsed >= timeout) {
1101
- cancelSync.read(checkElapsed);
1102
- callback(elapsed - timeout);
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
- Value in range from progress
1111
-
1112
- Given a lower limit and an upper limit, we return the value within
1113
- that range as expressed by progress (usually a number from 0 to 1)
1114
-
1115
- So progress = 0.5 would change
1116
-
1117
- from -------- to
1118
-
1119
- to
1120
-
1121
- from ---- to
1122
-
1123
- E.g. from = 10, to = 20, progress = 0.5 => 15
1124
-
1125
- @param [number]: Lower limit of range
1126
- @param [number]: Upper limit of range
1127
- @param [number]: The progress between lower and upper limits expressed 0-1
1128
- @return [number]: Value as calculated from progress within range (not limited within range)
1129
- */
1130
- const mix = (from, to, progress) => -progress * from + progress * to + from;
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
- // Adapted from https://gist.github.com/mjackson/5311256
1133
- function hueToRgb(p, q, t) {
1134
- if (t < 0)
1135
- t += 1;
1136
- if (t > 1)
1137
- t -= 1;
1138
- if (t < 1 / 6)
1139
- return p + (q - p) * 6 * t;
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 hslaToRgba({ hue, saturation, lightness, alpha }) {
1147
- hue /= 360;
1148
- saturation /= 100;
1149
- lightness /= 100;
1150
- let red = 0;
1151
- let green = 0;
1152
- let blue = 0;
1153
- if (!saturation) {
1154
- red = green = blue = lightness;
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
- else {
1157
- const q = lightness < 0.5
1158
- ? lightness * (1 + saturation)
1159
- : lightness + saturation - lightness * saturation;
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
- red: Math.round(red * 255),
1167
- green: Math.round(green * 255),
1168
- blue: Math.round(blue * 255),
1169
- alpha,
1219
+ stop: () => {
1220
+ onStop && onStop();
1221
+ driverControls.stop();
1222
+ },
1170
1223
  };
1171
1224
  }
1172
1225
 
1173
- // Linear color space blending
1174
- // Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
1175
- // Demonstrated http://codepen.io/osublake/pen/xGVVaN
1176
- const mixLinearColor = (from, to, v) => {
1177
- const fromExpo = from * from;
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
- else if (color.test(origin)) {
1220
- return mixColor(origin, target);
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
- else {
1223
- return mixComplex(origin, target);
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
- const mixArray = (from, to) => {
1227
- const output = [...from];
1228
- const numValues = output.length;
1229
- const blendValue = from.map((fromThis, i) => getMixer(fromThis, to[i]));
1230
- return (v) => {
1231
- for (let i = 0; i < numValues; i++) {
1232
- output[i] = blendValue[i](v);
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
- return (v) => {
1246
- for (const key in blendValue) {
1247
- output[key] = blendValue[key](v);
1248
- }
1249
- return output;
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
- 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.`);
1263
- return (p) => `${p > 0 ? target : origin}`;
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
- Progress within given range
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
- @param [number]: Lower limit
1275
- @param [number]: Upper limit
1276
- @param [number]: Value to find progress within given range
1277
- @return [number]: Progress of value within range as expressed 0-1
1278
- */
1279
- const progress = (from, to, value) => {
1280
- const toFromDifference = to - from;
1281
- return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
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
- const mixNumber = (from, to) => (p) => mix(from, to, p);
1285
- function detectMixerFactory(v) {
1286
- if (typeof v === "number") {
1287
- return mixNumber;
1288
- }
1289
- else if (typeof v === "string") {
1290
- if (color.test(v)) {
1291
- return mixColor;
1292
- }
1293
- else {
1294
- return mixComplex;
1295
- }
1296
- }
1297
- else if (Array.isArray(v)) {
1298
- return mixArray;
1299
- }
1300
- else if (typeof v === "object") {
1301
- return mixObject;
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
- return mixNumber;
1304
- }
1305
- function createMixers(output, ease, customMixer) {
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
- return mixers;
1318
- }
1357
+ };
1358
+
1319
1359
  /**
1320
- * Create a function that maps from a numerical input array to a generic output array.
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
- * TODO Revist this approach once we've moved to data models for values,
1334
- * probably not needed to pregenerate mixer functions.
1362
+ * ✅: 100, "100px", "#fff"
1363
+ * ❌: "block", "url(2.jpg)"
1364
+ * @param value
1335
1365
  *
1336
- * @public
1366
+ * @internal
1337
1367
  */
1338
- function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
1339
- const inputLength = input.length;
1340
- invariant(inputLength === output.length, "Both input and output ranges must be the same length");
1341
- 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.");
1342
- // If input runs highest -> lowest, reverse both arrays
1343
- if (input[0] > input[inputLength - 1]) {
1344
- input = [...input].reverse();
1345
- output = [...output].reverse();
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
- const mixers = createMixers(output, ease, mixer);
1348
- const numMixers = mixers.length;
1349
- const interpolator = (v) => {
1350
- let i = 0;
1351
- if (numMixers > 1) {
1352
- for (; i < input.length - 2; i++) {
1353
- if (v < input[i + 1])
1354
- break;
1355
- }
1356
- }
1357
- const progressInRange = progress(input[i], input[i + 1], v);
1358
- return mixers[i](progressInRange);
1359
- };
1360
- return isClamp
1361
- ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
1362
- : interpolator;
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
- function defaultEasing(values, easing) {
1366
- return values.map(() => easing || easeInOut).splice(0, values.length - 1);
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
- function defaultOffset(values) {
1369
- const numValues = values.length;
1370
- return values.map((_value, i) => i !== 0 ? i / (numValues - 1) : 0);
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 convertOffsetToTimes(offset, duration) {
1373
- return offset.map((o) => o * duration);
1526
+ function isZero(value) {
1527
+ return (value === 0 ||
1528
+ (typeof value === "string" &&
1529
+ parseFloat(value) === 0 &&
1530
+ value.indexOf(" ") === -1));
1374
1531
  }
1375
- function keyframes({ from = 0, to = 1, ease, offset, duration = 300, }) {
1376
- /**
1377
- * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
1378
- * to reduce GC during animation.
1379
- */
1380
- const state = { done: false, value: from };
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
- const safeMin = 0.001;
1414
- const minDuration = 0.01;
1415
- const maxDuration = 10.0;
1416
- const minDamping = 0.05;
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
- * Critically-damped spring
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
- envelope = (undampedFreq) => {
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
- const rootIterations = 12;
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
- return result;
1492
- }
1493
- function calcAngularFreq(undampedFreq, dampingRatio) {
1494
- return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
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
- * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
1531
- * to reduce GC during animation.
1560
+ * If the target has been defined as a series of keyframes
1532
1561
  */
1533
- const state = { done: false, value: from };
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
- * If we're working within what looks like a 0-1 range, change the default restDelta
1544
- * to 0.01
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 (restDelta === undefined) {
1547
- restDelta = Math.min(Math.abs(to - from) / 100, 0.4);
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
- function decay({ velocity = 0, from = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
1641
- /**
1642
- * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
1643
- * to reduce GC during animation.
1644
- */
1645
- const state = { done: false, value: from };
1646
- let amplitude = power * velocity;
1647
- const ideal = from + amplitude;
1648
- const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
1649
- /**
1650
- * If the target has changed we need to re-calculate the amplitude, otherwise
1651
- * the animation will start from the wrong position.
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
- const types = { decay, keyframes, spring };
1667
- function loopElapsed(elapsed, duration, delay = 0) {
1668
- return elapsed - duration - delay;
1669
- }
1670
- function reverseElapsed(elapsed, duration = 0, delay = 0, isForwardPlayback = true) {
1671
- return isForwardPlayback
1672
- ? loopElapsed(duration + -elapsed, duration, delay)
1673
- : duration - (elapsed - duration) + delay;
1674
- }
1675
- function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) {
1676
- return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay;
1677
- }
1678
- const framesync = (update) => {
1679
- const passTimestamp = ({ delta }) => update(delta);
1680
- return {
1681
- start: () => sync.update(passTimestamp, true),
1682
- stop: () => cancelSync.update(passTimestamp),
1683
- };
1684
- };
1685
- function animate$1({ from, autoplay = true, driver = framesync, elapsed = 0, repeat: repeatMax = 0, repeatType = "loop", repeatDelay = 0, onPlay, onStop, onComplete, onRepeat, onUpdate, type = "keyframes", ...options }) {
1686
- var _a, _b;
1687
- let { to } = options;
1688
- let driverControls;
1689
- let repeatCount = 0;
1690
- let computedDuration = options
1691
- .duration;
1692
- let latest;
1693
- let isComplete = false;
1694
- let isForwardPlayback = true;
1695
- let interpolateFromNumber;
1696
- const animator = types[Array.isArray(to) ? "keyframes" : type];
1697
- if ((_b = (_a = animator).needsInterpolation) === null || _b === void 0 ? void 0 : _b.call(_a, from, to)) {
1698
- interpolateFromNumber = interpolate([0, 100], [from, to], {
1699
- clamp: false,
1700
- });
1701
- from = 0;
1702
- to = 100;
1703
- }
1704
- const animation = animator({ ...options, from, to });
1705
- function repeat() {
1706
- repeatCount++;
1707
- if (repeatType === "reverse") {
1708
- isForwardPlayback = repeatCount % 2 === 0;
1709
- elapsed = reverseElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback);
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
- elapsed = loopElapsed(elapsed, computedDuration, repeatDelay);
1713
- if (repeatType === "mirror")
1714
- animation.flipTarget();
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
- isComplete = false;
1717
- onRepeat && onRepeat();
1718
- }
1719
- function complete() {
1720
- driverControls.stop();
1721
- onComplete && onComplete();
1722
- }
1723
- function update(delta) {
1724
- if (!isForwardPlayback)
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
- onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(latest);
1735
- if (isComplete) {
1736
- if (repeatCount === 0) {
1737
- computedDuration =
1738
- computedDuration !== undefined ? computedDuration : elapsed;
1739
- }
1740
- if (repeatCount < repeatMax) {
1741
- hasRepeatDelayElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback) && repeat();
1742
- }
1743
- else {
1744
- complete();
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
- function inertia({ from = 0, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
1763
- let currentAnimation;
1764
- function isOutOfBounds(v) {
1765
- return (min !== undefined && v < min) || (max !== undefined && v > max);
1710
+ class SubscriptionManager {
1711
+ constructor() {
1712
+ this.subscriptions = [];
1766
1713
  }
1767
- function boundaryNearest(v) {
1768
- if (min === undefined)
1769
- return max;
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
- function startAnimation(options) {
1775
- currentAnimation === null || currentAnimation === void 0 ? void 0 : currentAnimation.stop();
1776
- currentAnimation = animate$1({
1777
- ...options,
1778
- driver,
1779
- onUpdate: (v) => {
1780
- var _a;
1781
- onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(v);
1782
- (_a = options.onUpdate) === null || _a === void 0 ? void 0 : _a.call(options, v);
1783
- },
1784
- onComplete,
1785
- onStop,
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
- function startSpring(options) {
1789
- startAnimation({
1790
- type: "spring",
1791
- stiffness: bounceStiffness,
1792
- damping: bounceDamping,
1793
- restDelta,
1794
- ...options,
1795
- });
1739
+ getSize() {
1740
+ return this.subscriptions.length;
1796
1741
  }
1797
- if (isOutOfBounds(from)) {
1798
- // Start the animation with spring if outside the defined boundaries
1799
- startSpring({ from, velocity, to: boundaryNearest(from) });
1742
+ clear() {
1743
+ this.subscriptions.length = 0;
1800
1744
  }
1801
- else {
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
- * Or if the value is out of bounds, simulate the inertia movement
1804
- * with the decay animation.
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
- * Pre-calculate the target so we can detect if it's out-of-bounds.
1807
- * If it is, we want to check per frame when to switch to a spring
1808
- * animation
1773
+ * @internal
1809
1774
  */
1810
- let target = power * velocity + from;
1811
- if (typeof modifyTarget !== "undefined")
1812
- target = modifyTarget(target);
1813
- const boundary = boundaryNearest(target);
1814
- const heading = boundary === min ? -1 : 1;
1815
- let prev;
1816
- let current;
1817
- const checkBoundary = (v) => {
1818
- prev = current;
1819
- current = v;
1820
- velocity = velocityPerSecond(v - prev, frameData.delta);
1821
- if ((heading === 1 && v > boundary) ||
1822
- (heading === -1 && v < boundary)) {
1823
- startSpring({ from: v, to: boundary, velocity });
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
- startAnimation({
1827
- type: "decay",
1828
- from,
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
- * Convert any existing durations from seconds to milliseconds
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
- if (transition.duration)
1861
- options["duration"] = secondsToMilliseconds(transition.duration);
1862
- if (transition.repeatDelay)
1863
- options.repeatDelay = secondsToMilliseconds(transition.repeatDelay);
1907
+ onChange(subscription) {
1908
+ return this.updateSubscribers.add(subscription);
1909
+ }
1910
+ clearListeners() {
1911
+ this.updateSubscribers.clear();
1912
+ }
1864
1913
  /**
1865
- * Map easing names to Popmotion's easing functions
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
- if (ease) {
1868
- options["ease"] = isEasingArray(ease)
1869
- ? ease.map(easingDefinitionToFunction)
1870
- : easingDefinitionToFunction(ease);
1921
+ onRenderRequest(subscription) {
1922
+ // Render immediately
1923
+ subscription(this.get());
1924
+ return this.renderSubscribers.add(subscription);
1871
1925
  }
1872
1926
  /**
1873
- * Support legacy transition API
1927
+ * Attaches a passive effect to the `MotionValue`.
1928
+ *
1929
+ * @internal
1874
1930
  */
1875
- if (transition.type === "tween")
1876
- options.type = "keyframes";
1931
+ attach(passiveEffect) {
1932
+ this.passiveEffect = passiveEffect;
1933
+ }
1877
1934
  /**
1878
- * TODO: Popmotion 9 has the ability to automatically detect whether to use
1879
- * a keyframes or spring animation, but does so by detecting velocity and other spring options.
1880
- * It'd be good to introduce a similar thing here.
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
- if (transition.type !== "spring")
1883
- options.type = "keyframes";
1884
- return options;
1885
- }
1886
- /**
1887
- * Get the delay for a value by checking Transition with decreasing specificity.
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
- return options;
1903
- }
1904
- function getPopmotionAnimationOptions(transition, options, key) {
1905
- if (Array.isArray(options.to) && transition.duration === undefined) {
1906
- transition.duration = 0.8;
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
- * Get a default transition if none is determined to be defined.
1968
+ * @public
1911
1969
  */
1912
- if (!isTransitionDefined(transition)) {
1913
- transition = {
1914
- ...transition,
1915
- ...getDefaultTransition(key, options.to),
1916
- };
1970
+ getPrevious() {
1971
+ return this.prev;
1917
1972
  }
1918
- return {
1919
- ...options,
1920
- ...convertTransitionToAnimationOptions(transition),
1921
- };
1922
- }
1923
- /**
1924
- *
1925
- */
1926
- function getAnimation(key, value, target, transition, onComplete) {
1927
- const valueTransition = getValueTransition(transition, key) || {};
1928
- let origin = valueTransition.from !== undefined ? valueTransition.from : value.get();
1929
- const isTargetAnimatable = isAnimatable(key, target);
1930
- if (origin === "none" && isTargetAnimatable && typeof target === "string") {
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
- else if (isZero(origin) && typeof target === "string") {
1938
- origin = getZeroUnit(target);
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
- else if (!Array.isArray(target) &&
1941
- isZero(target) &&
1942
- typeof origin === "string") {
1943
- target = getZeroUnit(origin);
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
- const isOriginAnimatable = isAnimatable(key, origin);
1946
- warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${key} from "${origin}" to "${target}". ${origin} is not an animatable value - to enable this animation set ${origin} to a value animatable to ${target} via the \`style\` property.`);
1947
- function start() {
1948
- const options = {
1949
- from: origin,
1950
- to: target,
1951
- velocity: value.getVelocity(),
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
- function set() {
1971
- const finalTarget = resolveFinalValueInKeyframes(target);
1972
- value.set(finalTarget);
1973
- onComplete();
1974
- valueTransition.onUpdate && valueTransition.onUpdate(finalTarget);
1975
- valueTransition.onComplete && valueTransition.onComplete();
1976
- return { stop: () => { } };
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
- * Start animation on a MotionValue. This function is an interface between
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
- startAnimation("", value, to, transition);
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.7.3", `Attempting to mix Framer Motion versions ${nextValue.version} with 7.7.3 may not work as expected.`);
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)) {