framer-motion 10.2.3 → 10.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/cjs/dom-entry.js +1 -1
  2. package/dist/cjs/index.js +18 -20
  3. package/dist/cjs/{wrap-27fda06a.js → wrap-62da7859.js} +456 -437
  4. package/dist/dom-entry.d.ts +485 -37
  5. package/dist/es/animation/GroupPlaybackControls.mjs +25 -0
  6. package/dist/es/animation/animate.mjs +2 -3
  7. package/dist/es/animation/create-instant-animation.mjs +13 -3
  8. package/dist/es/animation/generators/inertia.mjs +87 -0
  9. package/dist/es/animation/{legacy-popmotion → generators}/keyframes.mjs +8 -15
  10. package/dist/es/animation/{legacy-popmotion/find-spring.mjs → generators/spring/find.mjs} +6 -5
  11. package/dist/es/animation/generators/spring/index.mjs +129 -0
  12. package/dist/es/animation/generators/utils/velocity.mjs +9 -0
  13. package/dist/es/animation/index.mjs +2 -10
  14. package/dist/es/animation/js/driver-frameloop.mjs +12 -0
  15. package/dist/es/animation/js/index.mjs +206 -0
  16. package/dist/es/animation/optimized-appear/handoff.mjs +3 -1
  17. package/dist/es/animation/waapi/create-accelerated-animation.mjs +16 -10
  18. package/dist/es/frameloop/index.mjs +3 -4
  19. package/dist/es/gestures/pan/PanSession.mjs +2 -2
  20. package/dist/es/index.mjs +2 -3
  21. package/dist/es/render/utils/motion-values.mjs +1 -1
  22. package/dist/es/utils/time-conversion.mjs +2 -1
  23. package/dist/es/value/index.mjs +3 -3
  24. package/dist/es/value/use-spring.mjs +1 -1
  25. package/dist/es/value/use-velocity.mjs +4 -6
  26. package/dist/framer-motion.dev.js +475 -458
  27. package/dist/framer-motion.js +1 -1
  28. package/dist/index.d.ts +175 -218
  29. package/dist/projection.dev.js +5849 -5830
  30. package/dist/three-entry.d.ts +62 -63
  31. package/package.json +7 -11
  32. package/dist/es/animation/legacy-popmotion/decay.mjs +0 -34
  33. package/dist/es/animation/legacy-popmotion/index.mjs +0 -163
  34. package/dist/es/animation/legacy-popmotion/inertia.mjs +0 -90
  35. package/dist/es/animation/legacy-popmotion/spring.mjs +0 -143
  36. package/dist/es/frameloop/on-next-frame.mjs +0 -12
@@ -157,17 +157,6 @@ const numberValueTypes = {
157
157
  const combineFunctions = (a, b) => (v) => b(a(v));
158
158
  const pipe = (...transformers) => transformers.reduce(combineFunctions);
159
159
 
160
- /*
161
- Detect and load appropriate clock setting for the execution environment
162
- */
163
- const defaultTimestep = (1 / 60) * 1000;
164
- const getCurrentTime = typeof performance !== "undefined"
165
- ? () => performance.now()
166
- : () => Date.now();
167
- const onNextFrame = typeof window !== "undefined"
168
- ? (callback) => window.requestAnimationFrame(callback)
169
- : (callback) => setTimeout(() => callback(getCurrentTime()), defaultTimestep);
170
-
171
160
  function createRenderStep(runNextFrame) {
172
161
  /**
173
162
  * We create and reuse two arrays, one to queue jobs for the current frame
@@ -296,7 +285,7 @@ const processStep = (stepId) => steps[stepId].process(frameData);
296
285
  const processFrame = (timestamp) => {
297
286
  runNextFrame = false;
298
287
  frameData.delta = useDefaultElapsed
299
- ? defaultTimestep
288
+ ? 1000 / 60
300
289
  : Math.max(Math.min(timestamp - frameData.timestamp, maxElapsed$1), 1);
301
290
  frameData.timestamp = timestamp;
302
291
  isProcessing = true;
@@ -304,14 +293,14 @@ const processFrame = (timestamp) => {
304
293
  isProcessing = false;
305
294
  if (runNextFrame) {
306
295
  useDefaultElapsed = false;
307
- onNextFrame(processFrame);
296
+ requestAnimationFrame(processFrame);
308
297
  }
309
298
  };
310
299
  const startLoop = () => {
311
300
  runNextFrame = true;
312
301
  useDefaultElapsed = true;
313
302
  if (!isProcessing)
314
- onNextFrame(processFrame);
303
+ requestAnimationFrame(processFrame);
315
304
  };
316
305
 
317
306
  const noop = (any) => any;
@@ -415,7 +404,7 @@ class MotionValue {
415
404
  * This will be replaced by the build step with the latest version number.
416
405
  * When MotionValues are provided to motion components, warn if versions are mixed.
417
406
  */
418
- this.version = "10.2.3";
407
+ this.version = "10.2.4";
419
408
  /**
420
409
  * Duration, in milliseconds, since last updating frame.
421
410
  *
@@ -661,7 +650,7 @@ class MotionValue {
661
650
  this.stop();
662
651
  return new Promise((resolve) => {
663
652
  this.hasAnimated = true;
664
- this.animation = startAnimation(resolve) || null;
653
+ this.animation = startAnimation(resolve);
665
654
  if (this.events.animationStart) {
666
655
  this.events.animationStart.notify();
667
656
  }
@@ -695,7 +684,7 @@ class MotionValue {
695
684
  return !!this.animation;
696
685
  }
697
686
  clearAnimation() {
698
- this.animation = null;
687
+ delete this.animation;
699
688
  }
700
689
  /**
701
690
  * Destroy and clean up subscribers to this `MotionValue`.
@@ -970,11 +959,74 @@ if (process.env.NODE_ENV !== "production") {
970
959
  * @return milliseconds - Converted time in milliseconds.
971
960
  */
972
961
  const secondsToMilliseconds = (seconds) => seconds * 1000;
962
+ const millisecondsToSeconds = (milliseconds) => milliseconds / 1000;
973
963
 
974
964
  const instantAnimationState = {
975
965
  current: false,
976
966
  };
977
967
 
968
+ function isWaapiSupportedEasing(easing) {
969
+ return (!easing || // Default easing
970
+ Array.isArray(easing) || // Bezier curve
971
+ (typeof easing === "string" && supportedWaapiEasing[easing]));
972
+ }
973
+ const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
974
+ const supportedWaapiEasing = {
975
+ linear: "linear",
976
+ ease: "ease",
977
+ easeIn: "ease-in",
978
+ easeOut: "ease-out",
979
+ easeInOut: "ease-in-out",
980
+ circIn: cubicBezierAsString([0, 0.65, 0.55, 1]),
981
+ circOut: cubicBezierAsString([0.55, 0, 1, 0.45]),
982
+ backIn: cubicBezierAsString([0.31, 0.01, 0.66, -0.59]),
983
+ backOut: cubicBezierAsString([0.33, 1.53, 0.69, 0.99]),
984
+ };
985
+ function mapEasingToNativeEasing(easing) {
986
+ if (!easing)
987
+ return undefined;
988
+ return Array.isArray(easing)
989
+ ? cubicBezierAsString(easing)
990
+ : supportedWaapiEasing[easing];
991
+ }
992
+
993
+ function animateStyle(element, valueName, keyframes, { delay = 0, duration, repeat = 0, repeatType = "loop", ease, times, } = {}) {
994
+ const keyframeOptions = { [valueName]: keyframes };
995
+ if (times)
996
+ keyframeOptions.offset = times;
997
+ return element.animate(keyframeOptions, {
998
+ delay,
999
+ duration,
1000
+ easing: mapEasingToNativeEasing(ease),
1001
+ fill: "both",
1002
+ iterations: repeat + 1,
1003
+ direction: repeatType === "reverse" ? "alternate" : "normal",
1004
+ });
1005
+ }
1006
+
1007
+ const featureTests = {
1008
+ waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
1009
+ };
1010
+ const results = {};
1011
+ const supports = {};
1012
+ /**
1013
+ * Generate features tests that cache their results.
1014
+ */
1015
+ for (const key in featureTests) {
1016
+ supports[key] = () => {
1017
+ if (results[key] === undefined)
1018
+ results[key] = featureTests[key]();
1019
+ return results[key];
1020
+ };
1021
+ }
1022
+
1023
+ function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }) {
1024
+ const index = repeat && repeatType !== "loop" && repeat % 2 === 1
1025
+ ? 0
1026
+ : keyframes.length - 1;
1027
+ return keyframes[index];
1028
+ }
1029
+
978
1030
  // Accepts an easing function and returns a new one that outputs mirrored values for
979
1031
  // the second half of the animation. Turns easeIn into easeInOut.
980
1032
  const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
@@ -1343,8 +1395,7 @@ const isEasingArray = (ease) => {
1343
1395
  function defaultEasing(values, easing) {
1344
1396
  return values.map(() => easing || easeInOut).splice(0, values.length - 1);
1345
1397
  }
1346
- function keyframes({ keyframes: keyframeValues, ease = easeInOut, times, duration = 300, }) {
1347
- keyframeValues = [...keyframeValues];
1398
+ function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "easeInOut", }) {
1348
1399
  /**
1349
1400
  * Easing functions can be externally defined as strings. Here we convert them
1350
1401
  * into actual functions.
@@ -1369,42 +1420,42 @@ function keyframes({ keyframes: keyframeValues, ease = easeInOut, times, duratio
1369
1420
  times && times.length === keyframeValues.length
1370
1421
  ? times
1371
1422
  : defaultOffset$1(keyframeValues), duration);
1372
- function createInterpolator() {
1373
- return interpolate(absoluteTimes, keyframeValues, {
1374
- ease: Array.isArray(easingFunctions)
1375
- ? easingFunctions
1376
- : defaultEasing(keyframeValues, easingFunctions),
1377
- });
1378
- }
1379
- let interpolator = createInterpolator();
1423
+ const mapTimeToKeyframe = interpolate(absoluteTimes, keyframeValues, {
1424
+ ease: Array.isArray(easingFunctions)
1425
+ ? easingFunctions
1426
+ : defaultEasing(keyframeValues, easingFunctions),
1427
+ });
1380
1428
  return {
1429
+ calculatedDuration: duration,
1381
1430
  next: (t) => {
1382
- state.value = interpolator(t);
1431
+ state.value = mapTimeToKeyframe(t);
1383
1432
  state.done = t >= duration;
1384
1433
  return state;
1385
1434
  },
1386
- flipTarget: () => {
1387
- keyframeValues.reverse();
1388
- interpolator = createInterpolator();
1389
- },
1390
1435
  };
1391
1436
  }
1392
1437
 
1438
+ const velocitySampleDuration = 5; // ms
1439
+ function calcGeneratorVelocity(resolveValue, t, current) {
1440
+ const prevT = Math.max(t - velocitySampleDuration, 0);
1441
+ return velocityPerSecond(current - resolveValue(prevT), t - prevT);
1442
+ }
1443
+
1393
1444
  const safeMin = 0.001;
1394
1445
  const minDuration = 0.01;
1395
- const maxDuration = 10.0;
1446
+ const maxDuration$2 = 10.0;
1396
1447
  const minDamping = 0.05;
1397
1448
  const maxDamping = 1;
1398
1449
  function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
1399
1450
  let envelope;
1400
1451
  let derivative;
1401
- exports.warning(duration <= maxDuration * 1000, "Spring duration must be 10 seconds or less");
1452
+ exports.warning(duration <= secondsToMilliseconds(maxDuration$2), "Spring duration must be 10 seconds or less");
1402
1453
  let dampingRatio = 1 - bounce;
1403
1454
  /**
1404
1455
  * Restrict dampingRatio and duration to within acceptable ranges.
1405
1456
  */
1406
1457
  dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
1407
- duration = clamp(minDuration, maxDuration, duration / 1000);
1458
+ duration = clamp(minDuration, maxDuration$2, millisecondsToSeconds(duration));
1408
1459
  if (dampingRatio < 1) {
1409
1460
  /**
1410
1461
  * Underdamped spring
@@ -1445,7 +1496,7 @@ function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, })
1445
1496
  }
1446
1497
  const initialGuess = 5 / duration;
1447
1498
  const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
1448
- duration = duration * 1000;
1499
+ duration = secondsToMilliseconds(duration);
1449
1500
  if (isNaN(undampedFreq)) {
1450
1501
  return {
1451
1502
  stiffness: 100,
@@ -1502,78 +1553,71 @@ function getSpringOptions(options) {
1502
1553
  }
1503
1554
  return springOptions;
1504
1555
  }
1505
- const velocitySampleDuration = 5;
1506
- /**
1507
- * This is based on the spring implementation of Wobble https://github.com/skevy/wobble
1508
- */
1509
1556
  function spring({ keyframes, restDelta, restSpeed, ...options }) {
1510
- let origin = keyframes[0];
1511
- let target = keyframes[keyframes.length - 1];
1557
+ const origin = keyframes[0];
1558
+ const target = keyframes[keyframes.length - 1];
1512
1559
  /**
1513
1560
  * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
1514
1561
  * to reduce GC during animation.
1515
1562
  */
1516
1563
  const state = { done: false, value: origin };
1517
1564
  const { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
1518
- let resolveSpring = zero;
1519
- let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
1565
+ const initialVelocity = velocity ? -millisecondsToSeconds(velocity) : 0.0;
1520
1566
  const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
1521
- function createSpring() {
1522
- const initialDelta = target - origin;
1523
- const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
1524
- /**
1525
- * If we're working on a granular scale, use smaller defaults for determining
1526
- * when the spring is finished.
1527
- *
1528
- * These defaults have been selected emprically based on what strikes a good
1529
- * ratio between feeling good and finishing as soon as changes are imperceptible.
1530
- */
1531
- const isGranularScale = Math.abs(initialDelta) < 5;
1532
- restSpeed || (restSpeed = isGranularScale ? 0.01 : 2);
1533
- restDelta || (restDelta = isGranularScale ? 0.005 : 0.5);
1534
- if (dampingRatio < 1) {
1535
- const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
1536
- // Underdamped spring
1537
- resolveSpring = (t) => {
1538
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1539
- return (target -
1540
- envelope *
1541
- (((initialVelocity +
1542
- dampingRatio * undampedAngularFreq * initialDelta) /
1543
- angularFreq) *
1544
- Math.sin(angularFreq * t) +
1545
- initialDelta * Math.cos(angularFreq * t)));
1546
- };
1547
- }
1548
- else if (dampingRatio === 1) {
1549
- // Critically damped spring
1550
- resolveSpring = (t) => target -
1551
- Math.exp(-undampedAngularFreq * t) *
1552
- (initialDelta +
1553
- (initialVelocity + undampedAngularFreq * initialDelta) *
1554
- t);
1555
- }
1556
- else {
1557
- // Overdamped spring
1558
- const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
1559
- resolveSpring = (t) => {
1560
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1561
- // When performing sinh or cosh values can hit Infinity so we cap them here
1562
- const freqForT = Math.min(dampedAngularFreq * t, 300);
1563
- return (target -
1564
- (envelope *
1565
- ((initialVelocity +
1566
- dampingRatio * undampedAngularFreq * initialDelta) *
1567
- Math.sinh(freqForT) +
1568
- dampedAngularFreq *
1569
- initialDelta *
1570
- Math.cosh(freqForT))) /
1571
- dampedAngularFreq);
1572
- };
1573
- }
1567
+ const initialDelta = target - origin;
1568
+ const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass));
1569
+ /**
1570
+ * If we're working on a granular scale, use smaller defaults for determining
1571
+ * when the spring is finished.
1572
+ *
1573
+ * These defaults have been selected emprically based on what strikes a good
1574
+ * ratio between feeling good and finishing as soon as changes are imperceptible.
1575
+ */
1576
+ const isGranularScale = Math.abs(initialDelta) < 5;
1577
+ restSpeed || (restSpeed = isGranularScale ? 0.01 : 2);
1578
+ restDelta || (restDelta = isGranularScale ? 0.005 : 0.5);
1579
+ let resolveSpring;
1580
+ if (dampingRatio < 1) {
1581
+ const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
1582
+ // Underdamped spring
1583
+ resolveSpring = (t) => {
1584
+ const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1585
+ return (target -
1586
+ envelope *
1587
+ (((initialVelocity +
1588
+ dampingRatio * undampedAngularFreq * initialDelta) /
1589
+ angularFreq) *
1590
+ Math.sin(angularFreq * t) +
1591
+ initialDelta * Math.cos(angularFreq * t)));
1592
+ };
1593
+ }
1594
+ else if (dampingRatio === 1) {
1595
+ // Critically damped spring
1596
+ resolveSpring = (t) => target -
1597
+ Math.exp(-undampedAngularFreq * t) *
1598
+ (initialDelta +
1599
+ (initialVelocity + undampedAngularFreq * initialDelta) * t);
1600
+ }
1601
+ else {
1602
+ // Overdamped spring
1603
+ const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
1604
+ resolveSpring = (t) => {
1605
+ const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1606
+ // When performing sinh or cosh values can hit Infinity so we cap them here
1607
+ const freqForT = Math.min(dampedAngularFreq * t, 300);
1608
+ return (target -
1609
+ (envelope *
1610
+ ((initialVelocity +
1611
+ dampingRatio * undampedAngularFreq * initialDelta) *
1612
+ Math.sinh(freqForT) +
1613
+ dampedAngularFreq *
1614
+ initialDelta *
1615
+ Math.cosh(freqForT))) /
1616
+ dampedAngularFreq);
1617
+ };
1574
1618
  }
1575
- createSpring();
1576
1619
  return {
1620
+ calculatedDuration: isResolvedFromDuration ? duration || null : null,
1577
1621
  next: (t) => {
1578
1622
  const current = resolveSpring(t);
1579
1623
  if (!isResolvedFromDuration) {
@@ -1585,8 +1629,7 @@ function spring({ keyframes, restDelta, restSpeed, ...options }) {
1585
1629
  * checking only for displacement is enough.
1586
1630
  */
1587
1631
  if (dampingRatio < 1) {
1588
- const prevT = Math.max(0, t - velocitySampleDuration);
1589
- currentVelocity = velocityPerSecond(current - resolveSpring(prevT), t - prevT);
1632
+ currentVelocity = calcGeneratorVelocity(resolveSpring, t, current);
1590
1633
  }
1591
1634
  else {
1592
1635
  currentVelocity = 0;
@@ -1603,29 +1646,23 @@ function spring({ keyframes, restDelta, restSpeed, ...options }) {
1603
1646
  state.value = state.done ? target : current;
1604
1647
  return state;
1605
1648
  },
1606
- flipTarget: () => {
1607
- initialVelocity = -initialVelocity;
1608
- [origin, target] = [target, origin];
1609
- createSpring();
1610
- },
1611
1649
  };
1612
1650
  }
1613
- spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
1614
- const zero = (_t) => 0;
1615
1651
 
1616
- function decay({
1617
- /**
1618
- * The decay animation dynamically calculates an end of the animation
1619
- * based on the initial keyframe, so we only need to define a single keyframe
1620
- * as default.
1621
- */
1622
- keyframes = [0], velocity = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
1652
+ function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
1623
1653
  const origin = keyframes[0];
1624
- /**
1625
- * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
1626
- * to reduce GC during animation.
1627
- */
1628
- const state = { done: false, value: origin };
1654
+ const state = {
1655
+ done: false,
1656
+ value: origin,
1657
+ };
1658
+ const isOutOfBounds = (v) => (min !== undefined && v < min) || (max !== undefined && v > max);
1659
+ const nearestBoundary = (v) => {
1660
+ if (min === undefined)
1661
+ return max;
1662
+ if (max === undefined)
1663
+ return min;
1664
+ return Math.abs(min - v) < Math.abs(max - v) ? min : max;
1665
+ };
1629
1666
  let amplitude = power * velocity;
1630
1667
  const ideal = origin + amplitude;
1631
1668
  const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
@@ -1635,233 +1672,270 @@ keyframes = [0], velocity = 0, power = 0.8, timeConstant = 350, restDelta = 0.5,
1635
1672
  */
1636
1673
  if (target !== ideal)
1637
1674
  amplitude = target - origin;
1675
+ const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant);
1676
+ const calcLatest = (t) => target + calcDelta(t);
1677
+ const applyFriction = (t) => {
1678
+ const delta = calcDelta(t);
1679
+ const latest = calcLatest(t);
1680
+ state.done = Math.abs(delta) <= restDelta;
1681
+ state.value = state.done ? target : latest;
1682
+ };
1683
+ /**
1684
+ * Ideally this would resolve for t in a stateless way, we could
1685
+ * do that by always precalculating the animation but as we know
1686
+ * this will be done anyway we can assume that spring will
1687
+ * be discovered during that.
1688
+ */
1689
+ let timeReachedBoundary;
1690
+ let spring$1;
1691
+ const checkCatchBoundary = (t) => {
1692
+ if (!isOutOfBounds(state.value))
1693
+ return;
1694
+ timeReachedBoundary = t;
1695
+ spring$1 = spring({
1696
+ keyframes: [state.value, nearestBoundary(state.value)],
1697
+ velocity: calcGeneratorVelocity(calcLatest, t, state.value),
1698
+ damping: bounceDamping,
1699
+ stiffness: bounceStiffness,
1700
+ restDelta,
1701
+ restSpeed,
1702
+ });
1703
+ };
1704
+ checkCatchBoundary(0);
1638
1705
  return {
1706
+ calculatedDuration: null,
1639
1707
  next: (t) => {
1640
- const delta = -amplitude * Math.exp(-t / timeConstant);
1641
- state.done = !(delta > restDelta || delta < -restDelta);
1642
- state.value = state.done ? target : target + delta;
1643
- return state;
1708
+ /**
1709
+ * We need to resolve the friction to figure out if we need a
1710
+ * spring but we don't want to do this twice per frame. So here
1711
+ * we flag if we updated for this frame and later if we did
1712
+ * we can skip doing it again.
1713
+ */
1714
+ let hasUpdatedFrame = false;
1715
+ if (!spring$1 && timeReachedBoundary === undefined) {
1716
+ hasUpdatedFrame = true;
1717
+ applyFriction(t);
1718
+ checkCatchBoundary(t);
1719
+ }
1720
+ /**
1721
+ * If we have a spring and the provided t is beyond the moment the friction
1722
+ * animation crossed the min/max boundary, use the spring.
1723
+ */
1724
+ if (timeReachedBoundary !== undefined && t > timeReachedBoundary) {
1725
+ return spring$1.next(t - timeReachedBoundary);
1726
+ }
1727
+ else {
1728
+ !hasUpdatedFrame && applyFriction(t);
1729
+ return state;
1730
+ }
1644
1731
  },
1645
- flipTarget: () => { },
1646
1732
  };
1647
1733
  }
1648
1734
 
1649
- const types = {
1650
- decay,
1651
- keyframes: keyframes,
1652
- tween: keyframes,
1653
- spring,
1654
- };
1655
- function loopElapsed(elapsed, duration, delay = 0) {
1656
- return elapsed - duration - delay;
1657
- }
1658
- function reverseElapsed(elapsed, duration = 0, delay = 0, isForwardPlayback = true) {
1659
- return isForwardPlayback
1660
- ? loopElapsed(duration + -elapsed, duration, delay)
1661
- : duration - (elapsed - duration) + delay;
1662
- }
1663
- function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) {
1664
- return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay;
1665
- }
1666
- const framesync = (update) => {
1667
- const passTimestamp = ({ delta }) => update(delta);
1735
+ const frameloopDriver = (update) => {
1736
+ const passTimestamp = ({ timestamp }) => update(timestamp);
1668
1737
  return {
1669
1738
  start: () => sync.update(passTimestamp, true),
1670
1739
  stop: () => cancelSync.update(passTimestamp),
1740
+ now: () => performance.now(),
1671
1741
  };
1672
1742
  };
1673
- function animateValue({ duration, driver = framesync, elapsed = 0, repeat: repeatMax = 0, repeatType = "loop", repeatDelay = 0, keyframes: keyframes$1, autoplay = true, onPlay, onStop, onComplete, onRepeat, onUpdate, type = "keyframes", ...options }) {
1674
- const initialElapsed = elapsed;
1675
- let driverControls;
1676
- let repeatCount = 0;
1677
- let computedDuration = duration;
1678
- let isComplete = false;
1679
- let isForwardPlayback = true;
1680
- let interpolateFromNumber;
1681
- const animator = types[keyframes$1.length > 2 ? "keyframes" : type] || keyframes;
1682
- const origin = keyframes$1[0];
1683
- const target = keyframes$1[keyframes$1.length - 1];
1684
- let state = { done: false, value: origin };
1743
+
1744
+ const types = {
1745
+ decay: inertia,
1746
+ inertia,
1747
+ tween: keyframes,
1748
+ keyframes: keyframes,
1749
+ spring,
1750
+ };
1751
+ /**
1752
+ * Implement a practical max duration for keyframe generation
1753
+ * to prevent infinite loops
1754
+ */
1755
+ const maxDuration$1 = 20000;
1756
+ function calculateDuration(generator) {
1757
+ let duration = 0;
1758
+ const timeStep = 50;
1759
+ let state = generator.next(duration);
1760
+ while (!state.done && duration < maxDuration$1) {
1761
+ duration += timeStep;
1762
+ state = generator.next(duration);
1763
+ }
1764
+ return duration;
1765
+ }
1766
+ function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, keyframes: keyframes$1, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", onPlay, onStop, onComplete, onUpdate, ...options }) {
1767
+ let animationDriver;
1768
+ const generatorFactory = types[type] || keyframes;
1685
1769
  /**
1686
- * If this value needs interpolation (ie is non-numerical), set up an interpolator.
1687
- * TODO: Keyframes animation also performs this step. This could be removed so it only happens here.
1770
+ * If this isn't the keyframes generator and we've been provided
1771
+ * strings as keyframes, we need to interpolate these.
1772
+ * TODO: Support velocity for units and complex value types/
1688
1773
  */
1689
- const { needsInterpolation } = animator;
1690
- if (needsInterpolation && needsInterpolation(origin, target)) {
1691
- interpolateFromNumber = interpolate([0, 100], [origin, target], {
1774
+ let mapNumbersToKeyframes;
1775
+ if (generatorFactory !== keyframes &&
1776
+ typeof keyframes$1[0] !== "number") {
1777
+ mapNumbersToKeyframes = interpolate([0, 100], keyframes$1, {
1692
1778
  clamp: false,
1693
1779
  });
1694
1780
  keyframes$1 = [0, 100];
1695
1781
  }
1696
- const animation = animator({
1697
- ...options,
1698
- duration,
1699
- keyframes: keyframes$1,
1700
- });
1701
- function repeat() {
1702
- repeatCount++;
1703
- if (repeatType === "reverse") {
1704
- isForwardPlayback = repeatCount % 2 === 0;
1705
- elapsed = reverseElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback);
1782
+ const generator = generatorFactory({ ...options, keyframes: keyframes$1 });
1783
+ let mirroredGenerator;
1784
+ if (repeatType === "mirror") {
1785
+ mirroredGenerator = generatorFactory({
1786
+ ...options,
1787
+ keyframes: [...keyframes$1].reverse(),
1788
+ velocity: -(options.velocity || 0),
1789
+ });
1790
+ }
1791
+ let playState = "idle";
1792
+ let holdTime = null;
1793
+ let startTime = null;
1794
+ /**
1795
+ * If duration is undefined and we have repeat options,
1796
+ * we need to calculate a duration from the generator.
1797
+ *
1798
+ * We set it to the generator itself to cache the duration.
1799
+ * Any timeline resolver will need to have already precalculated
1800
+ * the duration by this step.
1801
+ */
1802
+ if (generator.calculatedDuration === null && repeat) {
1803
+ generator.calculatedDuration = calculateDuration(generator);
1804
+ }
1805
+ const { calculatedDuration } = generator;
1806
+ let resolvedDuration = Infinity;
1807
+ let totalDuration = Infinity;
1808
+ if (calculatedDuration) {
1809
+ resolvedDuration = calculatedDuration + repeatDelay;
1810
+ totalDuration = resolvedDuration * (repeat + 1) - repeatDelay;
1811
+ }
1812
+ let currentTime = 0;
1813
+ const tick = (timestamp) => {
1814
+ if (startTime === null)
1815
+ return;
1816
+ if (holdTime !== null) {
1817
+ currentTime = holdTime;
1706
1818
  }
1707
1819
  else {
1708
- elapsed = loopElapsed(elapsed, computedDuration, repeatDelay);
1709
- if (repeatType === "mirror")
1710
- animation.flipTarget();
1820
+ currentTime = timestamp - startTime;
1711
1821
  }
1712
- isComplete = false;
1713
- onRepeat && onRepeat();
1714
- }
1715
- function complete() {
1716
- driverControls && driverControls.stop();
1717
- onComplete && onComplete();
1718
- }
1719
- function update(delta) {
1720
- if (!isForwardPlayback)
1721
- delta = -delta;
1722
- elapsed += delta;
1723
- if (!isComplete) {
1724
- state = animation.next(Math.max(0, elapsed));
1725
- if (interpolateFromNumber)
1726
- state.value = interpolateFromNumber(state.value);
1727
- isComplete = isForwardPlayback ? state.done : elapsed <= 0;
1822
+ // Rebase on delay
1823
+ currentTime = Math.max(currentTime - delay, 0);
1824
+ /**
1825
+ * If this animation has finished, set the current time
1826
+ * to the total duration.
1827
+ */
1828
+ if (playState === "finished" && holdTime === null) {
1829
+ currentTime = totalDuration;
1728
1830
  }
1729
- onUpdate && onUpdate(state.value);
1730
- if (isComplete) {
1731
- if (repeatCount === 0) {
1732
- computedDuration =
1733
- computedDuration !== undefined ? computedDuration : elapsed;
1734
- }
1735
- if (repeatCount < repeatMax) {
1736
- hasRepeatDelayElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback) && repeat();
1831
+ let elapsed = currentTime;
1832
+ let frameGenerator = generator;
1833
+ if (repeat) {
1834
+ /**
1835
+ * Get the current progress (0-1) of the animation. If t is >
1836
+ * than duration we'll get values like 2.5 (midway through the
1837
+ * third iteration)
1838
+ */
1839
+ const progress = currentTime / resolvedDuration;
1840
+ /**
1841
+ * Get the current iteration (0 indexed). For instance the floor of
1842
+ * 2.5 is 2.
1843
+ */
1844
+ let currentIteration = Math.floor(progress);
1845
+ /**
1846
+ * Get the current progress of the iteration by taking the remainder
1847
+ * so 2.5 is 0.5 through iteration 2
1848
+ */
1849
+ let iterationProgress = progress % 1.0;
1850
+ /**
1851
+ * If iteration progress is 1 we count that as the end
1852
+ * of the previous iteration.
1853
+ */
1854
+ if (!iterationProgress && progress >= 1) {
1855
+ iterationProgress = 1;
1737
1856
  }
1738
- else {
1739
- complete();
1857
+ iterationProgress === 1 && currentIteration--;
1858
+ /**
1859
+ * Reverse progress if we're not running in "normal" direction
1860
+ */
1861
+ const iterationIsOdd = currentIteration % 2;
1862
+ if (iterationIsOdd) {
1863
+ if (repeatType === "reverse") {
1864
+ iterationProgress = 1 - iterationProgress;
1865
+ if (repeatDelay) {
1866
+ iterationProgress -= repeatDelay / resolvedDuration;
1867
+ }
1868
+ }
1869
+ else if (repeatType === "mirror") {
1870
+ frameGenerator = mirroredGenerator;
1871
+ }
1740
1872
  }
1873
+ const p = currentTime >= totalDuration
1874
+ ? repeatType === "reverse" && iterationIsOdd
1875
+ ? 0
1876
+ : 1
1877
+ : clamp(0, 1, iterationProgress);
1878
+ elapsed = p * resolvedDuration;
1741
1879
  }
1742
- }
1743
- function play() {
1880
+ const state = frameGenerator.next(elapsed);
1881
+ let { value, done } = state;
1882
+ if (onUpdate) {
1883
+ onUpdate(mapNumbersToKeyframes ? mapNumbersToKeyframes(value) : value);
1884
+ }
1885
+ if (calculatedDuration !== null) {
1886
+ done = currentTime >= totalDuration;
1887
+ }
1888
+ const isAnimationFinished = holdTime === null &&
1889
+ (playState === "finished" || (playState === "running" && done));
1890
+ if (isAnimationFinished) {
1891
+ playState = "finished";
1892
+ onComplete && onComplete();
1893
+ animationDriver && animationDriver.stop();
1894
+ }
1895
+ return state;
1896
+ };
1897
+ const play = () => {
1898
+ animationDriver = driver(tick);
1899
+ const now = animationDriver.now();
1744
1900
  onPlay && onPlay();
1745
- driverControls = driver(update);
1746
- driverControls.start();
1901
+ playState = "running";
1902
+ if (holdTime !== null) {
1903
+ startTime = now - holdTime;
1904
+ }
1905
+ else if (!startTime) {
1906
+ // TODO When implementing play/pause, check WAAPI
1907
+ // logic around finished animations
1908
+ startTime = now;
1909
+ }
1910
+ holdTime = null;
1911
+ animationDriver.start();
1912
+ };
1913
+ if (autoplay) {
1914
+ play();
1747
1915
  }
1748
- autoplay && play();
1749
- return {
1750
- stop: () => {
1751
- onStop && onStop();
1752
- driverControls && driverControls.stop();
1753
- },
1754
- /**
1755
- * Set the current time of the animation. This is purposefully
1756
- * mirroring the WAAPI animation API to make them interchanagable.
1757
- * Going forward this file should be ported more towards
1758
- * https://github.com/motiondivision/motionone/blob/main/packages/animation/src/Animation.ts
1759
- * Which behaviourally adheres to WAAPI as far as possible.
1760
- *
1761
- * WARNING: This is not safe to use for most animations. We currently
1762
- * only use it for handoff from WAAPI within Framer.
1763
- *
1764
- * This animation function consumes time every frame rather than being sampled for time.
1765
- * So the sample() method performs some headless frames to ensure
1766
- * repeats are handled correctly. Ideally in the future we will replace
1767
- * that method with this, once repeat calculations are pure.
1768
- */
1769
- set currentTime(t) {
1770
- elapsed = initialElapsed;
1771
- update(t);
1916
+ const controls = {
1917
+ get currentTime() {
1918
+ return millisecondsToSeconds(currentTime);
1772
1919
  },
1773
- /**
1774
- * animate() can't yet be sampled for time, instead it
1775
- * consumes time. So to sample it we have to run a low
1776
- * temporal-resolution version.
1777
- *
1778
- * isControlled should be set to true if sample is being run within
1779
- * a loop. This indicates that we're not arbitrarily sampling
1780
- * the animation but running it one step after another. Therefore
1781
- * we don't need to run a low-res version here. This is a stop-gap
1782
- * until a rewrite can sample for time.
1783
- */
1784
- sample: (t, isControlled = false) => {
1785
- elapsed = initialElapsed;
1786
- if (isControlled) {
1787
- update(t);
1788
- return state;
1920
+ set currentTime(newTime) {
1921
+ if (holdTime !== null || !animationDriver) {
1922
+ holdTime = 0;
1789
1923
  }
1790
- const sampleResolution = duration && typeof duration === "number"
1791
- ? Math.max(duration * 0.5, 50)
1792
- : 50;
1793
- let sampleElapsed = 0;
1794
- update(0);
1795
- while (sampleElapsed <= t) {
1796
- const remaining = t - sampleElapsed;
1797
- update(Math.min(remaining, sampleResolution));
1798
- sampleElapsed += sampleResolution;
1924
+ else {
1925
+ startTime =
1926
+ animationDriver.now() - secondsToMilliseconds(newTime);
1799
1927
  }
1800
- return state;
1928
+ },
1929
+ stop: () => {
1930
+ onStop && onStop();
1931
+ animationDriver && animationDriver.stop();
1932
+ },
1933
+ sample: (elapsed) => {
1934
+ startTime = 0;
1935
+ return tick(elapsed);
1801
1936
  },
1802
1937
  };
1803
- }
1804
-
1805
- function isWaapiSupportedEasing(easing) {
1806
- return (!easing || // Default easing
1807
- Array.isArray(easing) || // Bezier curve
1808
- (typeof easing === "string" && supportedWaapiEasing[easing]));
1809
- }
1810
- const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
1811
- const supportedWaapiEasing = {
1812
- linear: "linear",
1813
- ease: "ease",
1814
- easeIn: "ease-in",
1815
- easeOut: "ease-out",
1816
- easeInOut: "ease-in-out",
1817
- circIn: cubicBezierAsString([0, 0.65, 0.55, 1]),
1818
- circOut: cubicBezierAsString([0.55, 0, 1, 0.45]),
1819
- backIn: cubicBezierAsString([0.31, 0.01, 0.66, -0.59]),
1820
- backOut: cubicBezierAsString([0.33, 1.53, 0.69, 0.99]),
1821
- };
1822
- function mapEasingToNativeEasing(easing) {
1823
- if (!easing)
1824
- return undefined;
1825
- return Array.isArray(easing)
1826
- ? cubicBezierAsString(easing)
1827
- : supportedWaapiEasing[easing];
1828
- }
1829
-
1830
- function animateStyle(element, valueName, keyframes, { delay = 0, duration, repeat = 0, repeatType = "loop", ease, times, } = {}) {
1831
- const keyframeOptions = { [valueName]: keyframes };
1832
- if (times)
1833
- keyframeOptions.offset = times;
1834
- return element.animate(keyframeOptions, {
1835
- delay,
1836
- duration,
1837
- easing: mapEasingToNativeEasing(ease),
1838
- fill: "both",
1839
- iterations: repeat + 1,
1840
- direction: repeatType === "reverse" ? "alternate" : "normal",
1841
- });
1842
- }
1843
-
1844
- const featureTests = {
1845
- waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
1846
- };
1847
- const results = {};
1848
- const supports = {};
1849
- /**
1850
- * Generate features tests that cache their results.
1851
- */
1852
- for (const key in featureTests) {
1853
- supports[key] = () => {
1854
- if (results[key] === undefined)
1855
- results[key] = featureTests[key]();
1856
- return results[key];
1857
- };
1858
- }
1859
-
1860
- function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }) {
1861
- const index = repeat && repeatType !== "loop" && repeat % 2 === 1
1862
- ? 0
1863
- : keyframes.length - 1;
1864
- return keyframes[index];
1938
+ return controls;
1865
1939
  }
1866
1940
 
1867
1941
  /**
@@ -1880,6 +1954,11 @@ const acceleratedValues = new Set([
1880
1954
  * keyframe quantity.
1881
1955
  */
1882
1956
  const sampleDelta = 10; //ms
1957
+ /**
1958
+ * Implement a practical max duration for keyframe generation
1959
+ * to prevent infinite loops
1960
+ */
1961
+ const maxDuration = 20000;
1883
1962
  const requiresPregeneratedKeyframes = (valueName, options) => options.type === "spring" ||
1884
1963
  valueName === "backgroundColor" ||
1885
1964
  !isWaapiSupportedEasing(options.ease);
@@ -1888,10 +1967,11 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
1888
1967
  acceleratedValues.has(valueName) &&
1889
1968
  !options.repeatDelay &&
1890
1969
  options.repeatType !== "mirror" &&
1891
- options.damping !== 0;
1970
+ options.damping !== 0 &&
1971
+ options.type !== "inertia";
1892
1972
  if (!canAccelerateAnimation)
1893
1973
  return false;
1894
- let { keyframes, duration = 300, elapsed = 0, ease } = options;
1974
+ let { keyframes, duration = 300, ease } = options;
1895
1975
  /**
1896
1976
  * If this animation needs pre-generated keyframes then generate.
1897
1977
  */
@@ -1899,7 +1979,7 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
1899
1979
  const sampleAnimation = animateValue({
1900
1980
  ...options,
1901
1981
  repeat: 0,
1902
- elapsed: 0,
1982
+ delay: 0,
1903
1983
  });
1904
1984
  let state = { done: false, value: keyframes[0] };
1905
1985
  const pregeneratedKeyframes = [];
@@ -1908,8 +1988,8 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
1908
1988
  * we're heading for an infinite loop.
1909
1989
  */
1910
1990
  let t = 0;
1911
- while (!state.done && t < 20000) {
1912
- state = sampleAnimation.sample(t, true);
1991
+ while (!state.done && t < maxDuration) {
1992
+ state = sampleAnimation.sample(t);
1913
1993
  pregeneratedKeyframes.push(state.value);
1914
1994
  t += sampleDelta;
1915
1995
  }
@@ -1919,7 +1999,6 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
1919
1999
  }
1920
2000
  const animation = animateStyle(value.owner.current, valueName, keyframes, {
1921
2001
  ...options,
1922
- delay: -elapsed,
1923
2002
  duration,
1924
2003
  /**
1925
2004
  * This function is currently not called if ease is provided
@@ -1949,10 +2028,10 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
1949
2028
  */
1950
2029
  return {
1951
2030
  get currentTime() {
1952
- return animation.currentTime || 0;
2031
+ return millisecondsToSeconds(animation.currentTime || 0);
1953
2032
  },
1954
- set currentTime(t) {
1955
- animation.currentTime = t;
2033
+ set currentTime(newTime) {
2034
+ animation.currentTime = secondsToMilliseconds(newTime);
1956
2035
  },
1957
2036
  stop: () => {
1958
2037
  /**
@@ -1976,113 +2055,22 @@ function createAcceleratedAnimation(value, valueName, { onUpdate, onComplete, ..
1976
2055
  };
1977
2056
  }
1978
2057
 
1979
- /**
1980
- * Timeout defined in ms
1981
- */
1982
- function delay(callback, timeout) {
1983
- const start = performance.now();
1984
- const checkElapsed = ({ timestamp }) => {
1985
- const elapsed = timestamp - start;
1986
- if (elapsed >= timeout) {
1987
- cancelSync.read(checkElapsed);
1988
- callback(elapsed - timeout);
1989
- }
1990
- };
1991
- sync.read(checkElapsed, true);
1992
- return () => cancelSync.read(checkElapsed);
1993
- }
1994
-
1995
- function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
2058
+ function createInstantAnimation({ keyframes, delay: delayBy, onUpdate, onComplete, }) {
1996
2059
  const setValue = () => {
1997
2060
  onUpdate && onUpdate(keyframes[keyframes.length - 1]);
1998
2061
  onComplete && onComplete();
1999
- };
2000
- return elapsed ? { stop: delay(setValue, -elapsed) } : setValue();
2001
- }
2002
-
2003
- function inertia({ keyframes, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
2004
- const origin = keyframes[0];
2005
- let currentAnimation;
2006
- function isOutOfBounds(v) {
2007
- return (min !== undefined && v < min) || (max !== undefined && v > max);
2008
- }
2009
- function findNearestBoundary(v) {
2010
- if (min === undefined)
2011
- return max;
2012
- if (max === undefined)
2013
- return min;
2014
- return Math.abs(min - v) < Math.abs(max - v) ? min : max;
2015
- }
2016
- function startAnimation(options) {
2017
- currentAnimation && currentAnimation.stop();
2018
- currentAnimation = animateValue({
2019
- keyframes: [0, 1],
2020
- velocity: 0,
2021
- ...options,
2022
- driver,
2023
- onUpdate: (v) => {
2024
- onUpdate && onUpdate(v);
2025
- options.onUpdate && options.onUpdate(v);
2026
- },
2027
- onComplete,
2028
- onStop,
2029
- });
2030
- }
2031
- function startSpring(options) {
2032
- startAnimation({
2033
- type: "spring",
2034
- stiffness: bounceStiffness,
2035
- damping: bounceDamping,
2036
- restDelta,
2037
- ...options,
2038
- });
2039
- }
2040
- if (isOutOfBounds(origin)) {
2041
- // Start the animation with spring if outside the defined boundaries
2042
- startSpring({
2043
- velocity,
2044
- keyframes: [origin, findNearestBoundary(origin)],
2045
- });
2046
- }
2047
- else {
2048
- /**
2049
- * Or if the value is out of bounds, simulate the inertia movement
2050
- * with the decay animation.
2051
- *
2052
- * Pre-calculate the target so we can detect if it's out-of-bounds.
2053
- * If it is, we want to check per frame when to switch to a spring
2054
- * animation
2055
- */
2056
- let target = power * velocity + origin;
2057
- if (typeof modifyTarget !== "undefined")
2058
- target = modifyTarget(target);
2059
- const boundary = findNearestBoundary(target);
2060
- const heading = boundary === min ? -1 : 1;
2061
- let prev;
2062
- let current;
2063
- const checkBoundary = (v) => {
2064
- prev = current;
2065
- current = v;
2066
- velocity = velocityPerSecond(v - prev, frameData.delta);
2067
- if ((heading === 1 && v > boundary) ||
2068
- (heading === -1 && v < boundary)) {
2069
- startSpring({ keyframes: [v, boundary], velocity });
2070
- }
2062
+ return {
2063
+ stop: () => { },
2064
+ currentTime: 0,
2071
2065
  };
2072
- startAnimation({
2073
- type: "decay",
2074
- keyframes: [origin, 0],
2075
- velocity,
2076
- timeConstant,
2077
- power,
2078
- restDelta,
2079
- modifyTarget,
2080
- onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
2081
- });
2082
- }
2083
- return {
2084
- stop: () => currentAnimation && currentAnimation.stop(),
2085
2066
  };
2067
+ return delayBy
2068
+ ? animateValue({
2069
+ keyframes: [0, 1],
2070
+ duration: delayBy,
2071
+ onComplete: setValue,
2072
+ })
2073
+ : setValue();
2086
2074
  }
2087
2075
 
2088
2076
  const underDampedSpring = {
@@ -2238,7 +2226,7 @@ const createMotionValueAnimation = (valueName, value, target, transition = {}) =
2238
2226
  keyframes,
2239
2227
  velocity: value.getVelocity(),
2240
2228
  ...valueTransition,
2241
- elapsed,
2229
+ delay: -elapsed,
2242
2230
  onUpdate: (v) => {
2243
2231
  value.set(v);
2244
2232
  valueTransition.onUpdate && valueTransition.onUpdate(v);
@@ -2258,13 +2246,6 @@ const createMotionValueAnimation = (valueName, value, target, transition = {}) =
2258
2246
  */
2259
2247
  return createInstantAnimation(options);
2260
2248
  }
2261
- else if (valueTransition.type === "inertia") {
2262
- /**
2263
- * If this is an inertia animation, we currently don't support pre-generating
2264
- * keyframes for this as such it must always run on the main thread.
2265
- */
2266
- return inertia(options);
2267
- }
2268
2249
  /**
2269
2250
  * If there's no transition defined for this value, we can generate
2270
2251
  * unqiue transition settings for this value.
@@ -2311,6 +2292,30 @@ function distance2D(a, b) {
2311
2292
  return Math.sqrt(xDelta ** 2 + yDelta ** 2);
2312
2293
  }
2313
2294
 
2295
+ class GroupPlaybackControls {
2296
+ constructor(animations) {
2297
+ this.animations = animations.filter(Boolean);
2298
+ }
2299
+ /**
2300
+ * TODO: Filter out cancelled or stopped animations before returning
2301
+ */
2302
+ get currentTime() {
2303
+ return this.animations[0].currentTime;
2304
+ }
2305
+ /**
2306
+ * currentTime assignment could reasonably run every frame, so
2307
+ * we iterate using a normal loop to avoid function creation.
2308
+ */
2309
+ set currentTime(time) {
2310
+ for (let i = 0; i < this.animations.length; i++) {
2311
+ this.animations[i].currentTime = time;
2312
+ }
2313
+ }
2314
+ stop() {
2315
+ this.animations.forEach((controls) => controls.stop());
2316
+ }
2317
+ }
2318
+
2314
2319
  /**
2315
2320
  * Animate a single value or a `MotionValue`.
2316
2321
  *
@@ -2341,9 +2346,23 @@ function distance2D(a, b) {
2341
2346
  function animate(from, to, transition = {}) {
2342
2347
  const value = isMotionValue(from) ? from : motionValue(from);
2343
2348
  value.start(createMotionValueAnimation("", value, to, transition));
2344
- return {
2345
- stop: () => value.stop(),
2349
+ return value.animation || new GroupPlaybackControls([]);
2350
+ }
2351
+
2352
+ /**
2353
+ * Timeout defined in ms
2354
+ */
2355
+ function delay(callback, timeout) {
2356
+ const start = performance.now();
2357
+ const checkElapsed = ({ timestamp }) => {
2358
+ const elapsed = timestamp - start;
2359
+ if (elapsed >= timeout) {
2360
+ cancelSync.read(checkElapsed);
2361
+ callback(elapsed - timeout);
2362
+ }
2346
2363
  };
2364
+ sync.read(checkElapsed, true);
2365
+ return () => cancelSync.read(checkElapsed);
2347
2366
  }
2348
2367
 
2349
2368
  const isCustomValueType = (v) => {
@@ -2876,10 +2895,10 @@ exports.getAnimatableNone = getAnimatableNone;
2876
2895
  exports.getDefaultValueType = getDefaultValueType;
2877
2896
  exports.getValueTransition = getValueTransition;
2878
2897
  exports.inView = inView;
2879
- exports.inertia = inertia;
2880
2898
  exports.instantAnimationState = instantAnimationState;
2881
2899
  exports.interpolate = interpolate;
2882
2900
  exports.isMotionValue = isMotionValue;
2901
+ exports.millisecondsToSeconds = millisecondsToSeconds;
2883
2902
  exports.mix = mix;
2884
2903
  exports.motionValue = motionValue;
2885
2904
  exports.moveItem = moveItem;