framer-motion 10.2.2 → 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-b7ab39cb.js → wrap-62da7859.js} +456 -438
  4. package/dist/dom-entry.d.ts +497 -49
  5. package/dist/es/animation/GroupPlaybackControls.mjs +25 -0
  6. package/dist/es/animation/animate.mjs +2 -4
  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 -459
  27. package/dist/framer-motion.js +1 -1
  28. package/dist/index.d.ts +70 -114
  29. package/dist/projection.dev.js +5849 -5831
  30. package/dist/three-entry.d.ts +11 -9
  31. package/package.json +7 -7
  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
@@ -1394,17 +1394,6 @@
1394
1394
  update() { }
1395
1395
  }
1396
1396
 
1397
- /*
1398
- Detect and load appropriate clock setting for the execution environment
1399
- */
1400
- const defaultTimestep = (1 / 60) * 1000;
1401
- const getCurrentTime = typeof performance !== "undefined"
1402
- ? () => performance.now()
1403
- : () => Date.now();
1404
- const onNextFrame = typeof window !== "undefined"
1405
- ? (callback) => window.requestAnimationFrame(callback)
1406
- : (callback) => setTimeout(() => callback(getCurrentTime()), defaultTimestep);
1407
-
1408
1397
  function createRenderStep(runNextFrame) {
1409
1398
  /**
1410
1399
  * We create and reuse two arrays, one to queue jobs for the current frame
@@ -1533,7 +1522,7 @@
1533
1522
  const processFrame = (timestamp) => {
1534
1523
  runNextFrame = false;
1535
1524
  frameData.delta = useDefaultElapsed
1536
- ? defaultTimestep
1525
+ ? 1000 / 60
1537
1526
  : Math.max(Math.min(timestamp - frameData.timestamp, maxElapsed$1), 1);
1538
1527
  frameData.timestamp = timestamp;
1539
1528
  isProcessing = true;
@@ -1541,14 +1530,14 @@
1541
1530
  isProcessing = false;
1542
1531
  if (runNextFrame) {
1543
1532
  useDefaultElapsed = false;
1544
- onNextFrame(processFrame);
1533
+ requestAnimationFrame(processFrame);
1545
1534
  }
1546
1535
  };
1547
1536
  const startLoop = () => {
1548
1537
  runNextFrame = true;
1549
1538
  useDefaultElapsed = true;
1550
1539
  if (!isProcessing)
1551
- onNextFrame(processFrame);
1540
+ requestAnimationFrame(processFrame);
1552
1541
  };
1553
1542
 
1554
1543
  function addHoverEvent(node, isActive) {
@@ -1995,7 +1984,7 @@
1995
1984
  * This will be replaced by the build step with the latest version number.
1996
1985
  * When MotionValues are provided to motion components, warn if versions are mixed.
1997
1986
  */
1998
- this.version = "10.2.2";
1987
+ this.version = "10.2.4";
1999
1988
  /**
2000
1989
  * Duration, in milliseconds, since last updating frame.
2001
1990
  *
@@ -2241,7 +2230,7 @@
2241
2230
  this.stop();
2242
2231
  return new Promise((resolve) => {
2243
2232
  this.hasAnimated = true;
2244
- this.animation = startAnimation(resolve) || null;
2233
+ this.animation = startAnimation(resolve);
2245
2234
  if (this.events.animationStart) {
2246
2235
  this.events.animationStart.notify();
2247
2236
  }
@@ -2275,7 +2264,7 @@
2275
2264
  return !!this.animation;
2276
2265
  }
2277
2266
  clearAnimation() {
2278
- this.animation = null;
2267
+ delete this.animation;
2279
2268
  }
2280
2269
  /**
2281
2270
  * Destroy and clean up subscribers to this `MotionValue`.
@@ -2723,11 +2712,74 @@
2723
2712
  * @return milliseconds - Converted time in milliseconds.
2724
2713
  */
2725
2714
  const secondsToMilliseconds = (seconds) => seconds * 1000;
2715
+ const millisecondsToSeconds = (milliseconds) => milliseconds / 1000;
2726
2716
 
2727
2717
  const instantAnimationState = {
2728
2718
  current: false,
2729
2719
  };
2730
2720
 
2721
+ function isWaapiSupportedEasing(easing) {
2722
+ return (!easing || // Default easing
2723
+ Array.isArray(easing) || // Bezier curve
2724
+ (typeof easing === "string" && supportedWaapiEasing[easing]));
2725
+ }
2726
+ const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
2727
+ const supportedWaapiEasing = {
2728
+ linear: "linear",
2729
+ ease: "ease",
2730
+ easeIn: "ease-in",
2731
+ easeOut: "ease-out",
2732
+ easeInOut: "ease-in-out",
2733
+ circIn: cubicBezierAsString([0, 0.65, 0.55, 1]),
2734
+ circOut: cubicBezierAsString([0.55, 0, 1, 0.45]),
2735
+ backIn: cubicBezierAsString([0.31, 0.01, 0.66, -0.59]),
2736
+ backOut: cubicBezierAsString([0.33, 1.53, 0.69, 0.99]),
2737
+ };
2738
+ function mapEasingToNativeEasing(easing) {
2739
+ if (!easing)
2740
+ return undefined;
2741
+ return Array.isArray(easing)
2742
+ ? cubicBezierAsString(easing)
2743
+ : supportedWaapiEasing[easing];
2744
+ }
2745
+
2746
+ function animateStyle(element, valueName, keyframes, { delay = 0, duration, repeat = 0, repeatType = "loop", ease, times, } = {}) {
2747
+ const keyframeOptions = { [valueName]: keyframes };
2748
+ if (times)
2749
+ keyframeOptions.offset = times;
2750
+ return element.animate(keyframeOptions, {
2751
+ delay,
2752
+ duration,
2753
+ easing: mapEasingToNativeEasing(ease),
2754
+ fill: "both",
2755
+ iterations: repeat + 1,
2756
+ direction: repeatType === "reverse" ? "alternate" : "normal",
2757
+ });
2758
+ }
2759
+
2760
+ const featureTests = {
2761
+ waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
2762
+ };
2763
+ const results = {};
2764
+ const supports = {};
2765
+ /**
2766
+ * Generate features tests that cache their results.
2767
+ */
2768
+ for (const key in featureTests) {
2769
+ supports[key] = () => {
2770
+ if (results[key] === undefined)
2771
+ results[key] = featureTests[key]();
2772
+ return results[key];
2773
+ };
2774
+ }
2775
+
2776
+ function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }) {
2777
+ const index = repeat && repeatType !== "loop" && repeat % 2 === 1
2778
+ ? 0
2779
+ : keyframes.length - 1;
2780
+ return keyframes[index];
2781
+ }
2782
+
2731
2783
  // Accepts an easing function and returns a new one that outputs mirrored values for
2732
2784
  // the second half of the animation. Turns easeIn into easeInOut.
2733
2785
  const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
@@ -3096,8 +3148,7 @@
3096
3148
  function defaultEasing(values, easing) {
3097
3149
  return values.map(() => easing || easeInOut).splice(0, values.length - 1);
3098
3150
  }
3099
- function keyframes({ keyframes: keyframeValues, ease = easeInOut, times, duration = 300, }) {
3100
- keyframeValues = [...keyframeValues];
3151
+ function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "easeInOut", }) {
3101
3152
  /**
3102
3153
  * Easing functions can be externally defined as strings. Here we convert them
3103
3154
  * into actual functions.
@@ -3122,42 +3173,42 @@
3122
3173
  times && times.length === keyframeValues.length
3123
3174
  ? times
3124
3175
  : defaultOffset$1(keyframeValues), duration);
3125
- function createInterpolator() {
3126
- return interpolate(absoluteTimes, keyframeValues, {
3127
- ease: Array.isArray(easingFunctions)
3128
- ? easingFunctions
3129
- : defaultEasing(keyframeValues, easingFunctions),
3130
- });
3131
- }
3132
- let interpolator = createInterpolator();
3176
+ const mapTimeToKeyframe = interpolate(absoluteTimes, keyframeValues, {
3177
+ ease: Array.isArray(easingFunctions)
3178
+ ? easingFunctions
3179
+ : defaultEasing(keyframeValues, easingFunctions),
3180
+ });
3133
3181
  return {
3182
+ calculatedDuration: duration,
3134
3183
  next: (t) => {
3135
- state.value = interpolator(t);
3184
+ state.value = mapTimeToKeyframe(t);
3136
3185
  state.done = t >= duration;
3137
3186
  return state;
3138
3187
  },
3139
- flipTarget: () => {
3140
- keyframeValues.reverse();
3141
- interpolator = createInterpolator();
3142
- },
3143
3188
  };
3144
3189
  }
3145
3190
 
3191
+ const velocitySampleDuration = 5; // ms
3192
+ function calcGeneratorVelocity(resolveValue, t, current) {
3193
+ const prevT = Math.max(t - velocitySampleDuration, 0);
3194
+ return velocityPerSecond(current - resolveValue(prevT), t - prevT);
3195
+ }
3196
+
3146
3197
  const safeMin = 0.001;
3147
3198
  const minDuration = 0.01;
3148
- const maxDuration = 10.0;
3199
+ const maxDuration$2 = 10.0;
3149
3200
  const minDamping = 0.05;
3150
3201
  const maxDamping = 1;
3151
3202
  function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
3152
3203
  let envelope;
3153
3204
  let derivative;
3154
- exports.warning(duration <= maxDuration * 1000, "Spring duration must be 10 seconds or less");
3205
+ exports.warning(duration <= secondsToMilliseconds(maxDuration$2), "Spring duration must be 10 seconds or less");
3155
3206
  let dampingRatio = 1 - bounce;
3156
3207
  /**
3157
3208
  * Restrict dampingRatio and duration to within acceptable ranges.
3158
3209
  */
3159
3210
  dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
3160
- duration = clamp(minDuration, maxDuration, duration / 1000);
3211
+ duration = clamp(minDuration, maxDuration$2, millisecondsToSeconds(duration));
3161
3212
  if (dampingRatio < 1) {
3162
3213
  /**
3163
3214
  * Underdamped spring
@@ -3198,7 +3249,7 @@
3198
3249
  }
3199
3250
  const initialGuess = 5 / duration;
3200
3251
  const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
3201
- duration = duration * 1000;
3252
+ duration = secondsToMilliseconds(duration);
3202
3253
  if (isNaN(undampedFreq)) {
3203
3254
  return {
3204
3255
  stiffness: 100,
@@ -3255,78 +3306,71 @@
3255
3306
  }
3256
3307
  return springOptions;
3257
3308
  }
3258
- const velocitySampleDuration = 5;
3259
- /**
3260
- * This is based on the spring implementation of Wobble https://github.com/skevy/wobble
3261
- */
3262
3309
  function spring({ keyframes, restDelta, restSpeed, ...options }) {
3263
- let origin = keyframes[0];
3264
- let target = keyframes[keyframes.length - 1];
3310
+ const origin = keyframes[0];
3311
+ const target = keyframes[keyframes.length - 1];
3265
3312
  /**
3266
3313
  * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
3267
3314
  * to reduce GC during animation.
3268
3315
  */
3269
3316
  const state = { done: false, value: origin };
3270
3317
  const { stiffness, damping, mass, velocity, duration, isResolvedFromDuration, } = getSpringOptions(options);
3271
- let resolveSpring = zero;
3272
- let initialVelocity = velocity ? -(velocity / 1000) : 0.0;
3318
+ const initialVelocity = velocity ? -millisecondsToSeconds(velocity) : 0.0;
3273
3319
  const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
3274
- function createSpring() {
3275
- const initialDelta = target - origin;
3276
- const undampedAngularFreq = Math.sqrt(stiffness / mass) / 1000;
3277
- /**
3278
- * If we're working on a granular scale, use smaller defaults for determining
3279
- * when the spring is finished.
3280
- *
3281
- * These defaults have been selected emprically based on what strikes a good
3282
- * ratio between feeling good and finishing as soon as changes are imperceptible.
3283
- */
3284
- const isGranularScale = Math.abs(initialDelta) < 5;
3285
- restSpeed || (restSpeed = isGranularScale ? 0.01 : 2);
3286
- restDelta || (restDelta = isGranularScale ? 0.005 : 0.5);
3287
- if (dampingRatio < 1) {
3288
- const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
3289
- // Underdamped spring
3290
- resolveSpring = (t) => {
3291
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
3292
- return (target -
3293
- envelope *
3294
- (((initialVelocity +
3295
- dampingRatio * undampedAngularFreq * initialDelta) /
3296
- angularFreq) *
3297
- Math.sin(angularFreq * t) +
3298
- initialDelta * Math.cos(angularFreq * t)));
3299
- };
3300
- }
3301
- else if (dampingRatio === 1) {
3302
- // Critically damped spring
3303
- resolveSpring = (t) => target -
3304
- Math.exp(-undampedAngularFreq * t) *
3305
- (initialDelta +
3306
- (initialVelocity + undampedAngularFreq * initialDelta) *
3307
- t);
3308
- }
3309
- else {
3310
- // Overdamped spring
3311
- const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
3312
- resolveSpring = (t) => {
3313
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
3314
- // When performing sinh or cosh values can hit Infinity so we cap them here
3315
- const freqForT = Math.min(dampedAngularFreq * t, 300);
3316
- return (target -
3317
- (envelope *
3318
- ((initialVelocity +
3319
- dampingRatio * undampedAngularFreq * initialDelta) *
3320
- Math.sinh(freqForT) +
3321
- dampedAngularFreq *
3322
- initialDelta *
3323
- Math.cosh(freqForT))) /
3324
- dampedAngularFreq);
3325
- };
3326
- }
3320
+ const initialDelta = target - origin;
3321
+ const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass));
3322
+ /**
3323
+ * If we're working on a granular scale, use smaller defaults for determining
3324
+ * when the spring is finished.
3325
+ *
3326
+ * These defaults have been selected emprically based on what strikes a good
3327
+ * ratio between feeling good and finishing as soon as changes are imperceptible.
3328
+ */
3329
+ const isGranularScale = Math.abs(initialDelta) < 5;
3330
+ restSpeed || (restSpeed = isGranularScale ? 0.01 : 2);
3331
+ restDelta || (restDelta = isGranularScale ? 0.005 : 0.5);
3332
+ let resolveSpring;
3333
+ if (dampingRatio < 1) {
3334
+ const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
3335
+ // Underdamped spring
3336
+ resolveSpring = (t) => {
3337
+ const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
3338
+ return (target -
3339
+ envelope *
3340
+ (((initialVelocity +
3341
+ dampingRatio * undampedAngularFreq * initialDelta) /
3342
+ angularFreq) *
3343
+ Math.sin(angularFreq * t) +
3344
+ initialDelta * Math.cos(angularFreq * t)));
3345
+ };
3346
+ }
3347
+ else if (dampingRatio === 1) {
3348
+ // Critically damped spring
3349
+ resolveSpring = (t) => target -
3350
+ Math.exp(-undampedAngularFreq * t) *
3351
+ (initialDelta +
3352
+ (initialVelocity + undampedAngularFreq * initialDelta) * t);
3353
+ }
3354
+ else {
3355
+ // Overdamped spring
3356
+ const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
3357
+ resolveSpring = (t) => {
3358
+ const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
3359
+ // When performing sinh or cosh values can hit Infinity so we cap them here
3360
+ const freqForT = Math.min(dampedAngularFreq * t, 300);
3361
+ return (target -
3362
+ (envelope *
3363
+ ((initialVelocity +
3364
+ dampingRatio * undampedAngularFreq * initialDelta) *
3365
+ Math.sinh(freqForT) +
3366
+ dampedAngularFreq *
3367
+ initialDelta *
3368
+ Math.cosh(freqForT))) /
3369
+ dampedAngularFreq);
3370
+ };
3327
3371
  }
3328
- createSpring();
3329
3372
  return {
3373
+ calculatedDuration: isResolvedFromDuration ? duration || null : null,
3330
3374
  next: (t) => {
3331
3375
  const current = resolveSpring(t);
3332
3376
  if (!isResolvedFromDuration) {
@@ -3338,8 +3382,7 @@
3338
3382
  * checking only for displacement is enough.
3339
3383
  */
3340
3384
  if (dampingRatio < 1) {
3341
- const prevT = Math.max(0, t - velocitySampleDuration);
3342
- currentVelocity = velocityPerSecond(current - resolveSpring(prevT), t - prevT);
3385
+ currentVelocity = calcGeneratorVelocity(resolveSpring, t, current);
3343
3386
  }
3344
3387
  else {
3345
3388
  currentVelocity = 0;
@@ -3356,29 +3399,23 @@
3356
3399
  state.value = state.done ? target : current;
3357
3400
  return state;
3358
3401
  },
3359
- flipTarget: () => {
3360
- initialVelocity = -initialVelocity;
3361
- [origin, target] = [target, origin];
3362
- createSpring();
3363
- },
3364
3402
  };
3365
3403
  }
3366
- spring.needsInterpolation = (a, b) => typeof a === "string" || typeof b === "string";
3367
- const zero = (_t) => 0;
3368
3404
 
3369
- function decay({
3370
- /**
3371
- * The decay animation dynamically calculates an end of the animation
3372
- * based on the initial keyframe, so we only need to define a single keyframe
3373
- * as default.
3374
- */
3375
- keyframes = [0], velocity = 0, power = 0.8, timeConstant = 350, restDelta = 0.5, modifyTarget, }) {
3405
+ function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
3376
3406
  const origin = keyframes[0];
3377
- /**
3378
- * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
3379
- * to reduce GC during animation.
3380
- */
3381
- const state = { done: false, value: origin };
3407
+ const state = {
3408
+ done: false,
3409
+ value: origin,
3410
+ };
3411
+ const isOutOfBounds = (v) => (min !== undefined && v < min) || (max !== undefined && v > max);
3412
+ const nearestBoundary = (v) => {
3413
+ if (min === undefined)
3414
+ return max;
3415
+ if (max === undefined)
3416
+ return min;
3417
+ return Math.abs(min - v) < Math.abs(max - v) ? min : max;
3418
+ };
3382
3419
  let amplitude = power * velocity;
3383
3420
  const ideal = origin + amplitude;
3384
3421
  const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
@@ -3388,233 +3425,270 @@
3388
3425
  */
3389
3426
  if (target !== ideal)
3390
3427
  amplitude = target - origin;
3428
+ const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant);
3429
+ const calcLatest = (t) => target + calcDelta(t);
3430
+ const applyFriction = (t) => {
3431
+ const delta = calcDelta(t);
3432
+ const latest = calcLatest(t);
3433
+ state.done = Math.abs(delta) <= restDelta;
3434
+ state.value = state.done ? target : latest;
3435
+ };
3436
+ /**
3437
+ * Ideally this would resolve for t in a stateless way, we could
3438
+ * do that by always precalculating the animation but as we know
3439
+ * this will be done anyway we can assume that spring will
3440
+ * be discovered during that.
3441
+ */
3442
+ let timeReachedBoundary;
3443
+ let spring$1;
3444
+ const checkCatchBoundary = (t) => {
3445
+ if (!isOutOfBounds(state.value))
3446
+ return;
3447
+ timeReachedBoundary = t;
3448
+ spring$1 = spring({
3449
+ keyframes: [state.value, nearestBoundary(state.value)],
3450
+ velocity: calcGeneratorVelocity(calcLatest, t, state.value),
3451
+ damping: bounceDamping,
3452
+ stiffness: bounceStiffness,
3453
+ restDelta,
3454
+ restSpeed,
3455
+ });
3456
+ };
3457
+ checkCatchBoundary(0);
3391
3458
  return {
3459
+ calculatedDuration: null,
3392
3460
  next: (t) => {
3393
- const delta = -amplitude * Math.exp(-t / timeConstant);
3394
- state.done = !(delta > restDelta || delta < -restDelta);
3395
- state.value = state.done ? target : target + delta;
3396
- return state;
3461
+ /**
3462
+ * We need to resolve the friction to figure out if we need a
3463
+ * spring but we don't want to do this twice per frame. So here
3464
+ * we flag if we updated for this frame and later if we did
3465
+ * we can skip doing it again.
3466
+ */
3467
+ let hasUpdatedFrame = false;
3468
+ if (!spring$1 && timeReachedBoundary === undefined) {
3469
+ hasUpdatedFrame = true;
3470
+ applyFriction(t);
3471
+ checkCatchBoundary(t);
3472
+ }
3473
+ /**
3474
+ * If we have a spring and the provided t is beyond the moment the friction
3475
+ * animation crossed the min/max boundary, use the spring.
3476
+ */
3477
+ if (timeReachedBoundary !== undefined && t > timeReachedBoundary) {
3478
+ return spring$1.next(t - timeReachedBoundary);
3479
+ }
3480
+ else {
3481
+ !hasUpdatedFrame && applyFriction(t);
3482
+ return state;
3483
+ }
3397
3484
  },
3398
- flipTarget: () => { },
3399
3485
  };
3400
3486
  }
3401
3487
 
3402
- const types = {
3403
- decay,
3404
- keyframes: keyframes,
3405
- tween: keyframes,
3406
- spring,
3407
- };
3408
- function loopElapsed(elapsed, duration, delay = 0) {
3409
- return elapsed - duration - delay;
3410
- }
3411
- function reverseElapsed(elapsed, duration = 0, delay = 0, isForwardPlayback = true) {
3412
- return isForwardPlayback
3413
- ? loopElapsed(duration + -elapsed, duration, delay)
3414
- : duration - (elapsed - duration) + delay;
3415
- }
3416
- function hasRepeatDelayElapsed(elapsed, duration, delay, isForwardPlayback) {
3417
- return isForwardPlayback ? elapsed >= duration + delay : elapsed <= -delay;
3418
- }
3419
- const framesync = (update) => {
3420
- const passTimestamp = ({ delta }) => update(delta);
3488
+ const frameloopDriver = (update) => {
3489
+ const passTimestamp = ({ timestamp }) => update(timestamp);
3421
3490
  return {
3422
3491
  start: () => sync.update(passTimestamp, true),
3423
3492
  stop: () => cancelSync.update(passTimestamp),
3493
+ now: () => performance.now(),
3424
3494
  };
3425
3495
  };
3426
- 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 }) {
3427
- const initialElapsed = elapsed;
3428
- let driverControls;
3429
- let repeatCount = 0;
3430
- let computedDuration = duration;
3431
- let isComplete = false;
3432
- let isForwardPlayback = true;
3433
- let interpolateFromNumber;
3434
- const animator = types[keyframes$1.length > 2 ? "keyframes" : type] || keyframes;
3435
- const origin = keyframes$1[0];
3436
- const target = keyframes$1[keyframes$1.length - 1];
3437
- let state = { done: false, value: origin };
3496
+
3497
+ const types = {
3498
+ decay: inertia,
3499
+ inertia,
3500
+ tween: keyframes,
3501
+ keyframes: keyframes,
3502
+ spring,
3503
+ };
3504
+ /**
3505
+ * Implement a practical max duration for keyframe generation
3506
+ * to prevent infinite loops
3507
+ */
3508
+ const maxDuration$1 = 20000;
3509
+ function calculateDuration(generator) {
3510
+ let duration = 0;
3511
+ const timeStep = 50;
3512
+ let state = generator.next(duration);
3513
+ while (!state.done && duration < maxDuration$1) {
3514
+ duration += timeStep;
3515
+ state = generator.next(duration);
3516
+ }
3517
+ return duration;
3518
+ }
3519
+ function animateValue({ autoplay = true, delay = 0, driver = frameloopDriver, keyframes: keyframes$1, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", onPlay, onStop, onComplete, onUpdate, ...options }) {
3520
+ let animationDriver;
3521
+ const generatorFactory = types[type] || keyframes;
3438
3522
  /**
3439
- * If this value needs interpolation (ie is non-numerical), set up an interpolator.
3440
- * TODO: Keyframes animation also performs this step. This could be removed so it only happens here.
3523
+ * If this isn't the keyframes generator and we've been provided
3524
+ * strings as keyframes, we need to interpolate these.
3525
+ * TODO: Support velocity for units and complex value types/
3441
3526
  */
3442
- const { needsInterpolation } = animator;
3443
- if (needsInterpolation && needsInterpolation(origin, target)) {
3444
- interpolateFromNumber = interpolate([0, 100], [origin, target], {
3527
+ let mapNumbersToKeyframes;
3528
+ if (generatorFactory !== keyframes &&
3529
+ typeof keyframes$1[0] !== "number") {
3530
+ mapNumbersToKeyframes = interpolate([0, 100], keyframes$1, {
3445
3531
  clamp: false,
3446
3532
  });
3447
3533
  keyframes$1 = [0, 100];
3448
3534
  }
3449
- const animation = animator({
3450
- ...options,
3451
- duration,
3452
- keyframes: keyframes$1,
3453
- });
3454
- function repeat() {
3455
- repeatCount++;
3456
- if (repeatType === "reverse") {
3457
- isForwardPlayback = repeatCount % 2 === 0;
3458
- elapsed = reverseElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback);
3535
+ const generator = generatorFactory({ ...options, keyframes: keyframes$1 });
3536
+ let mirroredGenerator;
3537
+ if (repeatType === "mirror") {
3538
+ mirroredGenerator = generatorFactory({
3539
+ ...options,
3540
+ keyframes: [...keyframes$1].reverse(),
3541
+ velocity: -(options.velocity || 0),
3542
+ });
3543
+ }
3544
+ let playState = "idle";
3545
+ let holdTime = null;
3546
+ let startTime = null;
3547
+ /**
3548
+ * If duration is undefined and we have repeat options,
3549
+ * we need to calculate a duration from the generator.
3550
+ *
3551
+ * We set it to the generator itself to cache the duration.
3552
+ * Any timeline resolver will need to have already precalculated
3553
+ * the duration by this step.
3554
+ */
3555
+ if (generator.calculatedDuration === null && repeat) {
3556
+ generator.calculatedDuration = calculateDuration(generator);
3557
+ }
3558
+ const { calculatedDuration } = generator;
3559
+ let resolvedDuration = Infinity;
3560
+ let totalDuration = Infinity;
3561
+ if (calculatedDuration) {
3562
+ resolvedDuration = calculatedDuration + repeatDelay;
3563
+ totalDuration = resolvedDuration * (repeat + 1) - repeatDelay;
3564
+ }
3565
+ let currentTime = 0;
3566
+ const tick = (timestamp) => {
3567
+ if (startTime === null)
3568
+ return;
3569
+ if (holdTime !== null) {
3570
+ currentTime = holdTime;
3459
3571
  }
3460
3572
  else {
3461
- elapsed = loopElapsed(elapsed, computedDuration, repeatDelay);
3462
- if (repeatType === "mirror")
3463
- animation.flipTarget();
3573
+ currentTime = timestamp - startTime;
3464
3574
  }
3465
- isComplete = false;
3466
- onRepeat && onRepeat();
3467
- }
3468
- function complete() {
3469
- driverControls && driverControls.stop();
3470
- onComplete && onComplete();
3471
- }
3472
- function update(delta) {
3473
- if (!isForwardPlayback)
3474
- delta = -delta;
3475
- elapsed += delta;
3476
- if (!isComplete) {
3477
- state = animation.next(Math.max(0, elapsed));
3478
- if (interpolateFromNumber)
3479
- state.value = interpolateFromNumber(state.value);
3480
- isComplete = isForwardPlayback ? state.done : elapsed <= 0;
3481
- }
3482
- onUpdate && onUpdate(state.value);
3483
- if (isComplete) {
3484
- if (repeatCount === 0) {
3485
- computedDuration =
3486
- computedDuration !== undefined ? computedDuration : elapsed;
3487
- }
3488
- if (repeatCount < repeatMax) {
3489
- hasRepeatDelayElapsed(elapsed, computedDuration, repeatDelay, isForwardPlayback) && repeat();
3575
+ // Rebase on delay
3576
+ currentTime = Math.max(currentTime - delay, 0);
3577
+ /**
3578
+ * If this animation has finished, set the current time
3579
+ * to the total duration.
3580
+ */
3581
+ if (playState === "finished" && holdTime === null) {
3582
+ currentTime = totalDuration;
3583
+ }
3584
+ let elapsed = currentTime;
3585
+ let frameGenerator = generator;
3586
+ if (repeat) {
3587
+ /**
3588
+ * Get the current progress (0-1) of the animation. If t is >
3589
+ * than duration we'll get values like 2.5 (midway through the
3590
+ * third iteration)
3591
+ */
3592
+ const progress = currentTime / resolvedDuration;
3593
+ /**
3594
+ * Get the current iteration (0 indexed). For instance the floor of
3595
+ * 2.5 is 2.
3596
+ */
3597
+ let currentIteration = Math.floor(progress);
3598
+ /**
3599
+ * Get the current progress of the iteration by taking the remainder
3600
+ * so 2.5 is 0.5 through iteration 2
3601
+ */
3602
+ let iterationProgress = progress % 1.0;
3603
+ /**
3604
+ * If iteration progress is 1 we count that as the end
3605
+ * of the previous iteration.
3606
+ */
3607
+ if (!iterationProgress && progress >= 1) {
3608
+ iterationProgress = 1;
3490
3609
  }
3491
- else {
3492
- complete();
3610
+ iterationProgress === 1 && currentIteration--;
3611
+ /**
3612
+ * Reverse progress if we're not running in "normal" direction
3613
+ */
3614
+ const iterationIsOdd = currentIteration % 2;
3615
+ if (iterationIsOdd) {
3616
+ if (repeatType === "reverse") {
3617
+ iterationProgress = 1 - iterationProgress;
3618
+ if (repeatDelay) {
3619
+ iterationProgress -= repeatDelay / resolvedDuration;
3620
+ }
3621
+ }
3622
+ else if (repeatType === "mirror") {
3623
+ frameGenerator = mirroredGenerator;
3624
+ }
3493
3625
  }
3494
- }
3495
- }
3496
- function play() {
3626
+ const p = currentTime >= totalDuration
3627
+ ? repeatType === "reverse" && iterationIsOdd
3628
+ ? 0
3629
+ : 1
3630
+ : clamp(0, 1, iterationProgress);
3631
+ elapsed = p * resolvedDuration;
3632
+ }
3633
+ const state = frameGenerator.next(elapsed);
3634
+ let { value, done } = state;
3635
+ if (onUpdate) {
3636
+ onUpdate(mapNumbersToKeyframes ? mapNumbersToKeyframes(value) : value);
3637
+ }
3638
+ if (calculatedDuration !== null) {
3639
+ done = currentTime >= totalDuration;
3640
+ }
3641
+ const isAnimationFinished = holdTime === null &&
3642
+ (playState === "finished" || (playState === "running" && done));
3643
+ if (isAnimationFinished) {
3644
+ playState = "finished";
3645
+ onComplete && onComplete();
3646
+ animationDriver && animationDriver.stop();
3647
+ }
3648
+ return state;
3649
+ };
3650
+ const play = () => {
3651
+ animationDriver = driver(tick);
3652
+ const now = animationDriver.now();
3497
3653
  onPlay && onPlay();
3498
- driverControls = driver(update);
3499
- driverControls.start();
3654
+ playState = "running";
3655
+ if (holdTime !== null) {
3656
+ startTime = now - holdTime;
3657
+ }
3658
+ else if (!startTime) {
3659
+ // TODO When implementing play/pause, check WAAPI
3660
+ // logic around finished animations
3661
+ startTime = now;
3662
+ }
3663
+ holdTime = null;
3664
+ animationDriver.start();
3665
+ };
3666
+ if (autoplay) {
3667
+ play();
3500
3668
  }
3501
- autoplay && play();
3502
- return {
3503
- stop: () => {
3504
- onStop && onStop();
3505
- driverControls && driverControls.stop();
3506
- },
3507
- /**
3508
- * Set the current time of the animation. This is purposefully
3509
- * mirroring the WAAPI animation API to make them interchanagable.
3510
- * Going forward this file should be ported more towards
3511
- * https://github.com/motiondivision/motionone/blob/main/packages/animation/src/Animation.ts
3512
- * Which behaviourally adheres to WAAPI as far as possible.
3513
- *
3514
- * WARNING: This is not safe to use for most animations. We currently
3515
- * only use it for handoff from WAAPI within Framer.
3516
- *
3517
- * This animation function consumes time every frame rather than being sampled for time.
3518
- * So the sample() method performs some headless frames to ensure
3519
- * repeats are handled correctly. Ideally in the future we will replace
3520
- * that method with this, once repeat calculations are pure.
3521
- */
3522
- set currentTime(t) {
3523
- elapsed = initialElapsed;
3524
- update(t);
3669
+ const controls = {
3670
+ get currentTime() {
3671
+ return millisecondsToSeconds(currentTime);
3525
3672
  },
3526
- /**
3527
- * animate() can't yet be sampled for time, instead it
3528
- * consumes time. So to sample it we have to run a low
3529
- * temporal-resolution version.
3530
- *
3531
- * isControlled should be set to true if sample is being run within
3532
- * a loop. This indicates that we're not arbitrarily sampling
3533
- * the animation but running it one step after another. Therefore
3534
- * we don't need to run a low-res version here. This is a stop-gap
3535
- * until a rewrite can sample for time.
3536
- */
3537
- sample: (t, isControlled = false) => {
3538
- elapsed = initialElapsed;
3539
- if (isControlled) {
3540
- update(t);
3541
- return state;
3673
+ set currentTime(newTime) {
3674
+ if (holdTime !== null || !animationDriver) {
3675
+ holdTime = 0;
3542
3676
  }
3543
- const sampleResolution = duration && typeof duration === "number"
3544
- ? Math.max(duration * 0.5, 50)
3545
- : 50;
3546
- let sampleElapsed = 0;
3547
- update(0);
3548
- while (sampleElapsed <= t) {
3549
- const remaining = t - sampleElapsed;
3550
- update(Math.min(remaining, sampleResolution));
3551
- sampleElapsed += sampleResolution;
3677
+ else {
3678
+ startTime =
3679
+ animationDriver.now() - secondsToMilliseconds(newTime);
3552
3680
  }
3553
- return state;
3681
+ },
3682
+ stop: () => {
3683
+ onStop && onStop();
3684
+ animationDriver && animationDriver.stop();
3685
+ },
3686
+ sample: (elapsed) => {
3687
+ startTime = 0;
3688
+ return tick(elapsed);
3554
3689
  },
3555
3690
  };
3556
- }
3557
-
3558
- function isWaapiSupportedEasing(easing) {
3559
- return (!easing || // Default easing
3560
- Array.isArray(easing) || // Bezier curve
3561
- (typeof easing === "string" && supportedWaapiEasing[easing]));
3562
- }
3563
- const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
3564
- const supportedWaapiEasing = {
3565
- linear: "linear",
3566
- ease: "ease",
3567
- easeIn: "ease-in",
3568
- easeOut: "ease-out",
3569
- easeInOut: "ease-in-out",
3570
- circIn: cubicBezierAsString([0, 0.65, 0.55, 1]),
3571
- circOut: cubicBezierAsString([0.55, 0, 1, 0.45]),
3572
- backIn: cubicBezierAsString([0.31, 0.01, 0.66, -0.59]),
3573
- backOut: cubicBezierAsString([0.33, 1.53, 0.69, 0.99]),
3574
- };
3575
- function mapEasingToNativeEasing(easing) {
3576
- if (!easing)
3577
- return undefined;
3578
- return Array.isArray(easing)
3579
- ? cubicBezierAsString(easing)
3580
- : supportedWaapiEasing[easing];
3581
- }
3582
-
3583
- function animateStyle(element, valueName, keyframes, { delay = 0, duration, repeat = 0, repeatType = "loop", ease, times, } = {}) {
3584
- const keyframeOptions = { [valueName]: keyframes };
3585
- if (times)
3586
- keyframeOptions.offset = times;
3587
- return element.animate(keyframeOptions, {
3588
- delay,
3589
- duration,
3590
- easing: mapEasingToNativeEasing(ease),
3591
- fill: "both",
3592
- iterations: repeat + 1,
3593
- direction: repeatType === "reverse" ? "alternate" : "normal",
3594
- });
3595
- }
3596
-
3597
- const featureTests = {
3598
- waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
3599
- };
3600
- const results = {};
3601
- const supports = {};
3602
- /**
3603
- * Generate features tests that cache their results.
3604
- */
3605
- for (const key in featureTests) {
3606
- supports[key] = () => {
3607
- if (results[key] === undefined)
3608
- results[key] = featureTests[key]();
3609
- return results[key];
3610
- };
3611
- }
3612
-
3613
- function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }) {
3614
- const index = repeat && repeatType !== "loop" && repeat % 2 === 1
3615
- ? 0
3616
- : keyframes.length - 1;
3617
- return keyframes[index];
3691
+ return controls;
3618
3692
  }
3619
3693
 
3620
3694
  /**
@@ -3633,6 +3707,11 @@
3633
3707
  * keyframe quantity.
3634
3708
  */
3635
3709
  const sampleDelta = 10; //ms
3710
+ /**
3711
+ * Implement a practical max duration for keyframe generation
3712
+ * to prevent infinite loops
3713
+ */
3714
+ const maxDuration = 20000;
3636
3715
  const requiresPregeneratedKeyframes = (valueName, options) => options.type === "spring" ||
3637
3716
  valueName === "backgroundColor" ||
3638
3717
  !isWaapiSupportedEasing(options.ease);
@@ -3641,10 +3720,11 @@
3641
3720
  acceleratedValues.has(valueName) &&
3642
3721
  !options.repeatDelay &&
3643
3722
  options.repeatType !== "mirror" &&
3644
- options.damping !== 0;
3723
+ options.damping !== 0 &&
3724
+ options.type !== "inertia";
3645
3725
  if (!canAccelerateAnimation)
3646
3726
  return false;
3647
- let { keyframes, duration = 300, elapsed = 0, ease } = options;
3727
+ let { keyframes, duration = 300, ease } = options;
3648
3728
  /**
3649
3729
  * If this animation needs pre-generated keyframes then generate.
3650
3730
  */
@@ -3652,7 +3732,7 @@
3652
3732
  const sampleAnimation = animateValue({
3653
3733
  ...options,
3654
3734
  repeat: 0,
3655
- elapsed: 0,
3735
+ delay: 0,
3656
3736
  });
3657
3737
  let state = { done: false, value: keyframes[0] };
3658
3738
  const pregeneratedKeyframes = [];
@@ -3661,8 +3741,8 @@
3661
3741
  * we're heading for an infinite loop.
3662
3742
  */
3663
3743
  let t = 0;
3664
- while (!state.done && t < 20000) {
3665
- state = sampleAnimation.sample(t, true);
3744
+ while (!state.done && t < maxDuration) {
3745
+ state = sampleAnimation.sample(t);
3666
3746
  pregeneratedKeyframes.push(state.value);
3667
3747
  t += sampleDelta;
3668
3748
  }
@@ -3672,7 +3752,6 @@
3672
3752
  }
3673
3753
  const animation = animateStyle(value.owner.current, valueName, keyframes, {
3674
3754
  ...options,
3675
- delay: -elapsed,
3676
3755
  duration,
3677
3756
  /**
3678
3757
  * This function is currently not called if ease is provided
@@ -3702,10 +3781,10 @@
3702
3781
  */
3703
3782
  return {
3704
3783
  get currentTime() {
3705
- return animation.currentTime || 0;
3784
+ return millisecondsToSeconds(animation.currentTime || 0);
3706
3785
  },
3707
- set currentTime(t) {
3708
- animation.currentTime = t;
3786
+ set currentTime(newTime) {
3787
+ animation.currentTime = secondsToMilliseconds(newTime);
3709
3788
  },
3710
3789
  stop: () => {
3711
3790
  /**
@@ -3729,113 +3808,22 @@
3729
3808
  };
3730
3809
  }
3731
3810
 
3732
- /**
3733
- * Timeout defined in ms
3734
- */
3735
- function delay(callback, timeout) {
3736
- const start = performance.now();
3737
- const checkElapsed = ({ timestamp }) => {
3738
- const elapsed = timestamp - start;
3739
- if (elapsed >= timeout) {
3740
- cancelSync.read(checkElapsed);
3741
- callback(elapsed - timeout);
3742
- }
3743
- };
3744
- sync.read(checkElapsed, true);
3745
- return () => cancelSync.read(checkElapsed);
3746
- }
3747
-
3748
- function createInstantAnimation({ keyframes, elapsed, onUpdate, onComplete, }) {
3811
+ function createInstantAnimation({ keyframes, delay: delayBy, onUpdate, onComplete, }) {
3749
3812
  const setValue = () => {
3750
3813
  onUpdate && onUpdate(keyframes[keyframes.length - 1]);
3751
3814
  onComplete && onComplete();
3752
- };
3753
- return elapsed ? { stop: delay(setValue, -elapsed) } : setValue();
3754
- }
3755
-
3756
- function inertia({ keyframes, velocity = 0, min, max, power = 0.8, timeConstant = 750, bounceStiffness = 500, bounceDamping = 10, restDelta = 1, modifyTarget, driver, onUpdate, onComplete, onStop, }) {
3757
- const origin = keyframes[0];
3758
- let currentAnimation;
3759
- function isOutOfBounds(v) {
3760
- return (min !== undefined && v < min) || (max !== undefined && v > max);
3761
- }
3762
- function findNearestBoundary(v) {
3763
- if (min === undefined)
3764
- return max;
3765
- if (max === undefined)
3766
- return min;
3767
- return Math.abs(min - v) < Math.abs(max - v) ? min : max;
3768
- }
3769
- function startAnimation(options) {
3770
- currentAnimation && currentAnimation.stop();
3771
- currentAnimation = animateValue({
3772
- keyframes: [0, 1],
3773
- velocity: 0,
3774
- ...options,
3775
- driver,
3776
- onUpdate: (v) => {
3777
- onUpdate && onUpdate(v);
3778
- options.onUpdate && options.onUpdate(v);
3779
- },
3780
- onComplete,
3781
- onStop,
3782
- });
3783
- }
3784
- function startSpring(options) {
3785
- startAnimation({
3786
- type: "spring",
3787
- stiffness: bounceStiffness,
3788
- damping: bounceDamping,
3789
- restDelta,
3790
- ...options,
3791
- });
3792
- }
3793
- if (isOutOfBounds(origin)) {
3794
- // Start the animation with spring if outside the defined boundaries
3795
- startSpring({
3796
- velocity,
3797
- keyframes: [origin, findNearestBoundary(origin)],
3798
- });
3799
- }
3800
- else {
3801
- /**
3802
- * Or if the value is out of bounds, simulate the inertia movement
3803
- * with the decay animation.
3804
- *
3805
- * Pre-calculate the target so we can detect if it's out-of-bounds.
3806
- * If it is, we want to check per frame when to switch to a spring
3807
- * animation
3808
- */
3809
- let target = power * velocity + origin;
3810
- if (typeof modifyTarget !== "undefined")
3811
- target = modifyTarget(target);
3812
- const boundary = findNearestBoundary(target);
3813
- const heading = boundary === min ? -1 : 1;
3814
- let prev;
3815
- let current;
3816
- const checkBoundary = (v) => {
3817
- prev = current;
3818
- current = v;
3819
- velocity = velocityPerSecond(v - prev, frameData.delta);
3820
- if ((heading === 1 && v > boundary) ||
3821
- (heading === -1 && v < boundary)) {
3822
- startSpring({ keyframes: [v, boundary], velocity });
3823
- }
3815
+ return {
3816
+ stop: () => { },
3817
+ currentTime: 0,
3824
3818
  };
3825
- startAnimation({
3826
- type: "decay",
3827
- keyframes: [origin, 0],
3828
- velocity,
3829
- timeConstant,
3830
- power,
3831
- restDelta,
3832
- modifyTarget,
3833
- onUpdate: isOutOfBounds(target) ? checkBoundary : undefined,
3834
- });
3835
- }
3836
- return {
3837
- stop: () => currentAnimation && currentAnimation.stop(),
3838
3819
  };
3820
+ return delayBy
3821
+ ? animateValue({
3822
+ keyframes: [0, 1],
3823
+ duration: delayBy,
3824
+ onComplete: setValue,
3825
+ })
3826
+ : setValue();
3839
3827
  }
3840
3828
 
3841
3829
  const underDampedSpring = {
@@ -3991,7 +3979,7 @@
3991
3979
  keyframes,
3992
3980
  velocity: value.getVelocity(),
3993
3981
  ...valueTransition,
3994
- elapsed,
3982
+ delay: -elapsed,
3995
3983
  onUpdate: (v) => {
3996
3984
  value.set(v);
3997
3985
  valueTransition.onUpdate && valueTransition.onUpdate(v);
@@ -4011,13 +3999,6 @@
4011
3999
  */
4012
4000
  return createInstantAnimation(options);
4013
4001
  }
4014
- else if (valueTransition.type === "inertia") {
4015
- /**
4016
- * If this is an inertia animation, we currently don't support pre-generating
4017
- * keyframes for this as such it must always run on the main thread.
4018
- */
4019
- return inertia(options);
4020
- }
4021
4002
  /**
4022
4003
  * If there's no transition defined for this value, we can generate
4023
4004
  * unqiue transition settings for this value.
@@ -4702,7 +4683,7 @@
4702
4683
  if (!timestampedPoint) {
4703
4684
  return { x: 0, y: 0 };
4704
4685
  }
4705
- const time = (lastPoint.timestamp - timestampedPoint.timestamp) / 1000;
4686
+ const time = millisecondsToSeconds(lastPoint.timestamp - timestampedPoint.timestamp);
4706
4687
  if (time === 0) {
4707
4688
  return { x: 0, y: 0 };
4708
4689
  }
@@ -5581,6 +5562,30 @@
5581
5562
  }
5582
5563
  }
5583
5564
 
5565
+ class GroupPlaybackControls {
5566
+ constructor(animations) {
5567
+ this.animations = animations.filter(Boolean);
5568
+ }
5569
+ /**
5570
+ * TODO: Filter out cancelled or stopped animations before returning
5571
+ */
5572
+ get currentTime() {
5573
+ return this.animations[0].currentTime;
5574
+ }
5575
+ /**
5576
+ * currentTime assignment could reasonably run every frame, so
5577
+ * we iterate using a normal loop to avoid function creation.
5578
+ */
5579
+ set currentTime(time) {
5580
+ for (let i = 0; i < this.animations.length; i++) {
5581
+ this.animations[i].currentTime = time;
5582
+ }
5583
+ }
5584
+ stop() {
5585
+ this.animations.forEach((controls) => controls.stop());
5586
+ }
5587
+ }
5588
+
5584
5589
  /**
5585
5590
  * Animate a single value or a `MotionValue`.
5586
5591
  *
@@ -5611,10 +5616,7 @@
5611
5616
  function animate(from, to, transition = {}) {
5612
5617
  const value = isMotionValue(from) ? from : motionValue(from);
5613
5618
  value.start(createMotionValueAnimation("", value, to, transition));
5614
- return {
5615
- stop: () => value.stop(),
5616
- isAnimating: () => value.isAnimating(),
5617
- };
5619
+ return value.animation || new GroupPlaybackControls([]);
5618
5620
  }
5619
5621
 
5620
5622
  const borders = ["TopLeft", "TopRight", "BottomLeft", "BottomRight"];
@@ -5959,6 +5961,22 @@
5959
5961
  }
5960
5962
  }
5961
5963
 
5964
+ /**
5965
+ * Timeout defined in ms
5966
+ */
5967
+ function delay(callback, timeout) {
5968
+ const start = performance.now();
5969
+ const checkElapsed = ({ timestamp }) => {
5970
+ const elapsed = timestamp - start;
5971
+ if (elapsed >= timeout) {
5972
+ cancelSync.read(checkElapsed);
5973
+ callback(elapsed - timeout);
5974
+ }
5975
+ };
5976
+ sync.read(checkElapsed, true);
5977
+ return () => cancelSync.read(checkElapsed);
5978
+ }
5979
+
5962
5980
  function record(data) {
5963
5981
  if (window.MotionDebug) {
5964
5982
  window.MotionDebug.record(data);
@@ -7862,7 +7880,7 @@
7862
7880
  * and warn against mismatches.
7863
7881
  */
7864
7882
  {
7865
- warnOnce(nextValue.version === "10.2.2", `Attempting to mix Framer Motion versions ${nextValue.version} with 10.2.2 may not work as expected.`);
7883
+ warnOnce(nextValue.version === "10.2.4", `Attempting to mix Framer Motion versions ${nextValue.version} with 10.2.4 may not work as expected.`);
7866
7884
  }
7867
7885
  }
7868
7886
  else if (isMotionValue(prevValue)) {
@@ -9956,6 +9974,16 @@
9956
9974
  return value;
9957
9975
  }
9958
9976
 
9977
+ function useMotionValueEvent(value, event, callback) {
9978
+ /**
9979
+ * useInsertionEffect will create subscriptions before any other
9980
+ * effects will run. Effects run upwards through the tree so it
9981
+ * can be that binding a useLayoutEffect higher up the tree can
9982
+ * miss changes from lower down the tree.
9983
+ */
9984
+ React.useInsertionEffect(() => value.on(event, callback), [value, event, callback]);
9985
+ }
9986
+
9959
9987
  /**
9960
9988
  * Creates a `MotionValue` that updates when the velocity of the provided `MotionValue` changes.
9961
9989
  *
@@ -9969,11 +9997,9 @@
9969
9997
  */
9970
9998
  function useVelocity(value) {
9971
9999
  const velocity = useMotionValue(value.getVelocity());
9972
- React.useEffect(() => {
9973
- return value.on("velocityChange", (newVelocity) => {
9974
- velocity.set(newVelocity);
9975
- });
9976
- }, [value]);
10000
+ useMotionValueEvent(value, "velocityChange", (newVelocity) => {
10001
+ velocity.set(newVelocity);
10002
+ });
9977
10003
  return velocity;
9978
10004
  }
9979
10005
 
@@ -10092,16 +10118,6 @@
10092
10118
  return useConstant(() => new WillChangeMotionValue("auto"));
10093
10119
  }
10094
10120
 
10095
- function useMotionValueEvent(value, event, callback) {
10096
- /**
10097
- * useInsertionEffect will create subscriptions before any other
10098
- * effects will run. Effects run upwards through the tree so it
10099
- * can be that binding a useLayoutEffect higher up the tree can
10100
- * miss changes from lower down the tree.
10101
- */
10102
- React.useInsertionEffect(() => value.on(event, callback), [value, event, callback]);
10103
- }
10104
-
10105
10121
  /**
10106
10122
  * A hook that returns `true` if we should be using reduced motion based on the current device's Reduced Motion setting.
10107
10123
  *
@@ -10523,7 +10539,8 @@
10523
10539
  */
10524
10540
  sync.update(() => {
10525
10541
  if (value.animation) {
10526
- value.animation.currentTime = performance.now() - sampledTime;
10542
+ value.animation.currentTime =
10543
+ performance.now() - millisecondsToSeconds(sampledTime);
10527
10544
  }
10528
10545
  });
10529
10546
  /**
@@ -10756,7 +10773,6 @@
10756
10773
  exports.filterProps = filterProps;
10757
10774
  exports.frameData = frameData;
10758
10775
  exports.inView = inView;
10759
- exports.inertia = inertia;
10760
10776
  exports.interpolate = interpolate;
10761
10777
  exports.isBrowser = isBrowser;
10762
10778
  exports.isDragActive = isDragActive;