motion 12.4.13 → 12.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/cjs/debug.js +12 -12
  2. package/dist/cjs/index.js +1277 -1302
  3. package/dist/cjs/mini.js +7 -7
  4. package/dist/cjs/react-client.js +535 -540
  5. package/dist/cjs/react-m.js +20 -19
  6. package/dist/cjs/react-mini.js +1 -1
  7. package/dist/es/framer-motion/dist/es/animation/animate/single-value.mjs +2 -1
  8. package/dist/es/framer-motion/dist/es/animation/animators/BaseAnimation.mjs +2 -1
  9. package/dist/es/framer-motion/dist/es/animation/animators/MainThreadAnimation.mjs +1 -1
  10. package/dist/es/framer-motion/dist/es/animation/animators/drivers/driver-frameloop.mjs +3 -2
  11. package/dist/es/framer-motion/dist/es/animation/animators/waapi/index.mjs +2 -2
  12. package/dist/es/framer-motion/dist/es/animation/generators/utils/velocity.mjs +2 -1
  13. package/dist/es/framer-motion/dist/es/animation/interfaces/motion-value.mjs +2 -2
  14. package/dist/es/framer-motion/dist/es/animation/interfaces/visual-element-target.mjs +1 -1
  15. package/dist/es/framer-motion/dist/es/animation/optimized-appear/start.mjs +4 -4
  16. package/dist/es/framer-motion/dist/es/animation/sequence/utils/edit.mjs +2 -1
  17. package/dist/es/framer-motion/dist/es/components/AnimatePresence/index.mjs +1 -1
  18. package/dist/es/framer-motion/dist/es/components/Reorder/utils/check-reorder.mjs +2 -1
  19. package/dist/es/framer-motion/dist/es/gestures/drag/VisualElementDragControls.mjs +1 -1
  20. package/dist/es/framer-motion/dist/es/gestures/hover.mjs +1 -1
  21. package/dist/es/framer-motion/dist/es/gestures/pan/PanSession.mjs +1 -1
  22. package/dist/es/framer-motion/dist/es/gestures/pan/index.mjs +1 -1
  23. package/dist/es/framer-motion/dist/es/gestures/press.mjs +1 -1
  24. package/dist/es/framer-motion/dist/es/motion/features/layout/MeasureLayout.mjs +3 -2
  25. package/dist/es/framer-motion/dist/es/motion/utils/use-visual-element.mjs +7 -6
  26. package/dist/es/framer-motion/dist/es/projection/node/create-projection-node.mjs +6 -6
  27. package/dist/es/framer-motion/dist/es/projection/shared/stack.mjs +2 -1
  28. package/dist/es/framer-motion/dist/es/render/VisualElement.mjs +6 -5
  29. package/dist/es/framer-motion/dist/es/render/components/create-proxy.mjs +2 -1
  30. package/dist/es/framer-motion/dist/es/render/dom/DOMVisualElement.mjs +1 -1
  31. package/dist/es/framer-motion/dist/es/render/dom/scroll/info.mjs +1 -1
  32. package/dist/es/framer-motion/dist/es/render/dom/scroll/observe.mjs +2 -1
  33. package/dist/es/framer-motion/dist/es/render/dom/scroll/on-scroll-handler.mjs +2 -1
  34. package/dist/es/framer-motion/dist/es/render/dom/scroll/track.mjs +2 -1
  35. package/dist/es/framer-motion/dist/es/render/svg/SVGVisualElement.mjs +2 -1
  36. package/dist/es/framer-motion/dist/es/render/svg/config-motion.mjs +2 -1
  37. package/dist/es/framer-motion/dist/es/render/utils/KeyframesResolver.mjs +2 -1
  38. package/dist/es/framer-motion/dist/es/render/utils/flat-tree.mjs +2 -1
  39. package/dist/es/framer-motion/dist/es/render/utils/motion-values.mjs +4 -3
  40. package/dist/es/framer-motion/dist/es/render/utils/setters.mjs +2 -1
  41. package/dist/es/framer-motion/dist/es/utils/delay.mjs +2 -2
  42. package/dist/es/framer-motion/dist/es/utils/reduced-motion/use-reduced-motion.mjs +2 -1
  43. package/dist/es/framer-motion/dist/es/utils/use-animation-frame.mjs +2 -1
  44. package/dist/es/framer-motion/dist/es/utils/use-force-update.mjs +2 -1
  45. package/dist/es/framer-motion/dist/es/utils/use-instant-transition.mjs +2 -1
  46. package/dist/es/framer-motion/dist/es/value/scroll/use-element-scroll.mjs +2 -1
  47. package/dist/es/framer-motion/dist/es/value/scroll/use-viewport-scroll.mjs +2 -1
  48. package/dist/es/framer-motion/dist/es/value/use-combine-values.mjs +3 -2
  49. package/dist/es/framer-motion/dist/es/value/use-computed.mjs +2 -1
  50. package/dist/es/framer-motion/dist/es/value/use-inverted-scale.mjs +3 -3
  51. package/dist/es/framer-motion/dist/es/value/use-motion-value.mjs +2 -1
  52. package/dist/es/framer-motion/dist/es/value/use-scroll.mjs +4 -4
  53. package/dist/es/framer-motion/dist/es/value/use-spring.mjs +1 -1
  54. package/dist/es/framer-motion/dist/es/value/use-transform.mjs +1 -1
  55. package/dist/es/framer-motion/dist/es/value/use-velocity.mjs +2 -1
  56. package/dist/es/framer-motion/dist/es/value/use-will-change/WillChangeMotionValue.mjs +3 -2
  57. package/dist/es/motion/lib/debug.mjs +1 -1
  58. package/dist/es/motion/lib/index.mjs +3 -4
  59. package/dist/es/motion/lib/react.mjs +19 -20
  60. package/dist/es/{framer-motion → motion-dom}/dist/es/frameloop/batcher.mjs +2 -1
  61. package/dist/es/{framer-motion → motion-dom}/dist/es/frameloop/frame.mjs +1 -1
  62. package/dist/es/motion-dom/dist/es/frameloop/microtask.mjs +6 -0
  63. package/dist/es/{framer-motion → motion-dom}/dist/es/frameloop/sync-time.mjs +2 -1
  64. package/dist/es/motion-dom/dist/es/utils/supports/scroll-timeline.mjs +1 -1
  65. package/dist/es/{framer-motion → motion-dom}/dist/es/value/index.mjs +6 -11
  66. package/dist/motion.dev.js +1277 -1302
  67. package/dist/motion.js +1 -1
  68. package/package.json +3 -3
  69. package/dist/es/framer-motion/dist/es/frameloop/index-legacy.mjs +0 -20
  70. package/dist/es/framer-motion/dist/es/frameloop/microtask.mjs +0 -5
  71. package/dist/es/{framer-motion → motion-dom}/dist/es/frameloop/order.mjs +0 -0
  72. package/dist/es/{framer-motion → motion-dom}/dist/es/frameloop/render-step.mjs +0 -0
  73. package/dist/es/{framer-motion → motion-dom}/dist/es/stats/animation-count.mjs +0 -0
  74. package/dist/es/{framer-motion → motion-dom}/dist/es/stats/buffer.mjs +0 -0
  75. package/dist/es/{framer-motion → motion-dom}/dist/es/stats/index.mjs +1 -1
  76. /package/dist/es/{framer-motion/dist/es/utils → motion-utils/dist/es}/array.mjs +0 -0
  77. /package/dist/es/{framer-motion/dist/es/utils/GlobalConfig.mjs → motion-utils/dist/es/global-config.mjs} +0 -0
  78. /package/dist/es/{framer-motion/dist/es/utils → motion-utils/dist/es}/subscription-manager.mjs +0 -0
  79. /package/dist/es/{framer-motion/dist/es/utils → motion-utils/dist/es}/velocity-per-second.mjs +0 -0
  80. /package/dist/es/{framer-motion/dist/es/utils → motion-utils/dist/es}/warn-once.mjs +0 -0
package/dist/cjs/index.js CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ function addUniqueItem(arr, item) {
6
+ if (arr.indexOf(item) === -1)
7
+ arr.push(item);
8
+ }
9
+ function removeItem(arr, item) {
10
+ const index = arr.indexOf(item);
11
+ if (index > -1)
12
+ arr.splice(index, 1);
13
+ }
14
+
5
15
  /*#__NO_SIDE_EFFECTS__*/
6
16
  const noop = (any) => any;
7
17
 
@@ -20,6 +30,11 @@ if (process.env.NODE_ENV !== "production") {
20
30
  };
21
31
  }
22
32
 
33
+ const MotionGlobalConfig = {
34
+ skipAnimations: false,
35
+ useManualTiming: false,
36
+ };
37
+
23
38
  /*#__NO_SIDE_EFFECTS__*/
24
39
  function memo(callback) {
25
40
  let result;
@@ -48,6 +63,43 @@ const progress = (from, to, value) => {
48
63
  return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
49
64
  };
50
65
 
66
+ class SubscriptionManager {
67
+ constructor() {
68
+ this.subscriptions = [];
69
+ }
70
+ add(handler) {
71
+ addUniqueItem(this.subscriptions, handler);
72
+ return () => removeItem(this.subscriptions, handler);
73
+ }
74
+ notify(a, b, c) {
75
+ const numSubscriptions = this.subscriptions.length;
76
+ if (!numSubscriptions)
77
+ return;
78
+ if (numSubscriptions === 1) {
79
+ /**
80
+ * If there's only a single handler we can just call it without invoking a loop.
81
+ */
82
+ this.subscriptions[0](a, b, c);
83
+ }
84
+ else {
85
+ for (let i = 0; i < numSubscriptions; i++) {
86
+ /**
87
+ * Check whether the handler exists before firing as it's possible
88
+ * the subscriptions were modified during this loop running.
89
+ */
90
+ const handler = this.subscriptions[i];
91
+ handler && handler(a, b, c);
92
+ }
93
+ }
94
+ }
95
+ getSize() {
96
+ return this.subscriptions.length;
97
+ }
98
+ clear() {
99
+ this.subscriptions.length = 0;
100
+ }
101
+ }
102
+
51
103
  /**
52
104
  * Converts seconds to milliseconds
53
105
  *
@@ -59,7 +111,27 @@ const secondsToMilliseconds = (seconds) => seconds * 1000;
59
111
  /*#__NO_SIDE_EFFECTS__*/
60
112
  const millisecondsToSeconds = (milliseconds) => milliseconds / 1000;
61
113
 
62
- const supportsScrollTimeline = memo(() => window.ScrollTimeline !== undefined);
114
+ /*
115
+ Convert velocity into velocity per second
116
+
117
+ @param [number]: Unit per frame
118
+ @param [number]: Frame duration in ms
119
+ */
120
+ function velocityPerSecond(velocity, frameDuration) {
121
+ return frameDuration ? velocity * (1000 / frameDuration) : 0;
122
+ }
123
+
124
+ const warned = new Set();
125
+ function warnOnce(condition, message, element) {
126
+ if (condition || warned.has(message))
127
+ return;
128
+ console.warn(message);
129
+ if (element)
130
+ console.warn(element);
131
+ warned.add(message);
132
+ }
133
+
134
+ const supportsScrollTimeline = /* @__PURE__ */ memo(() => window.ScrollTimeline !== undefined);
63
135
 
64
136
  class BaseGroupPlaybackControls {
65
137
  constructor(animations) {
@@ -354,103 +426,297 @@ function mapEasingToNativeEasing(easing, duration) {
354
426
  }
355
427
  }
356
428
 
357
- const isDragging = {
358
- x: false,
359
- y: false,
429
+ const stepsOrder = [
430
+ "read", // Read
431
+ "resolveKeyframes", // Write/Read/Write/Read
432
+ "update", // Compute
433
+ "preRender", // Compute
434
+ "render", // Write
435
+ "postRender", // Compute
436
+ ];
437
+
438
+ const statsBuffer = {
439
+ value: null,
440
+ addProjectionMetrics: null,
360
441
  };
361
- function isDragActive() {
362
- return isDragging.x || isDragging.y;
363
- }
364
442
 
365
- function resolveElements(elementOrSelector, scope, selectorCache) {
366
- var _a;
367
- if (elementOrSelector instanceof EventTarget) {
368
- return [elementOrSelector];
369
- }
370
- else if (typeof elementOrSelector === "string") {
371
- let root = document;
372
- if (scope) {
373
- // TODO: Refactor to utils package
374
- // invariant(
375
- // Boolean(scope.current),
376
- // "Scope provided, but no element detected."
377
- // )
378
- root = scope.current;
443
+ function createRenderStep(runNextFrame, stepName) {
444
+ /**
445
+ * We create and reuse two queues, one to queue jobs for the current frame
446
+ * and one for the next. We reuse to avoid triggering GC after x frames.
447
+ */
448
+ let thisFrame = new Set();
449
+ let nextFrame = new Set();
450
+ /**
451
+ * Track whether we're currently processing jobs in this step. This way
452
+ * we can decide whether to schedule new jobs for this frame or next.
453
+ */
454
+ let isProcessing = false;
455
+ let flushNextFrame = false;
456
+ /**
457
+ * A set of processes which were marked keepAlive when scheduled.
458
+ */
459
+ const toKeepAlive = new WeakSet();
460
+ let latestFrameData = {
461
+ delta: 0.0,
462
+ timestamp: 0.0,
463
+ isProcessing: false,
464
+ };
465
+ let numCalls = 0;
466
+ function triggerCallback(callback) {
467
+ if (toKeepAlive.has(callback)) {
468
+ step.schedule(callback);
469
+ runNextFrame();
379
470
  }
380
- const elements = (_a = selectorCache === null || selectorCache === void 0 ? void 0 : selectorCache[elementOrSelector]) !== null && _a !== void 0 ? _a : root.querySelectorAll(elementOrSelector);
381
- return elements ? Array.from(elements) : [];
471
+ numCalls++;
472
+ callback(latestFrameData);
382
473
  }
383
- return Array.from(elementOrSelector);
384
- }
385
-
386
- function setupGesture(elementOrSelector, options) {
387
- const elements = resolveElements(elementOrSelector);
388
- const gestureAbortController = new AbortController();
389
- const eventOptions = {
390
- passive: true,
391
- ...options,
392
- signal: gestureAbortController.signal,
393
- };
394
- const cancel = () => gestureAbortController.abort();
395
- return [elements, eventOptions, cancel];
396
- }
397
-
398
- function isValidHover(event) {
399
- return !(event.pointerType === "touch" || isDragActive());
400
- }
401
- /**
402
- * Create a hover gesture. hover() is different to .addEventListener("pointerenter")
403
- * in that it has an easier syntax, filters out polyfilled touch events, interoperates
404
- * with drag gestures, and automatically removes the "pointerennd" event listener when the hover ends.
405
- *
406
- * @public
407
- */
408
- function hover(elementOrSelector, onHoverStart, options = {}) {
409
- const [elements, eventOptions, cancel] = setupGesture(elementOrSelector, options);
410
- const onPointerEnter = (enterEvent) => {
411
- if (!isValidHover(enterEvent))
412
- return;
413
- const { target } = enterEvent;
414
- const onHoverEnd = onHoverStart(target, enterEvent);
415
- if (typeof onHoverEnd !== "function" || !target)
416
- return;
417
- const onPointerLeave = (leaveEvent) => {
418
- if (!isValidHover(leaveEvent))
474
+ const step = {
475
+ /**
476
+ * Schedule a process to run on the next frame.
477
+ */
478
+ schedule: (callback, keepAlive = false, immediate = false) => {
479
+ const addToCurrentFrame = immediate && isProcessing;
480
+ const queue = addToCurrentFrame ? thisFrame : nextFrame;
481
+ if (keepAlive)
482
+ toKeepAlive.add(callback);
483
+ if (!queue.has(callback))
484
+ queue.add(callback);
485
+ return callback;
486
+ },
487
+ /**
488
+ * Cancel the provided callback from running on the next frame.
489
+ */
490
+ cancel: (callback) => {
491
+ nextFrame.delete(callback);
492
+ toKeepAlive.delete(callback);
493
+ },
494
+ /**
495
+ * Execute all schedule callbacks.
496
+ */
497
+ process: (frameData) => {
498
+ latestFrameData = frameData;
499
+ /**
500
+ * If we're already processing we've probably been triggered by a flushSync
501
+ * inside an existing process. Instead of executing, mark flushNextFrame
502
+ * as true and ensure we flush the following frame at the end of this one.
503
+ */
504
+ if (isProcessing) {
505
+ flushNextFrame = true;
419
506
  return;
420
- onHoverEnd(leaveEvent);
421
- target.removeEventListener("pointerleave", onPointerLeave);
422
- };
423
- target.addEventListener("pointerleave", onPointerLeave, eventOptions);
507
+ }
508
+ isProcessing = true;
509
+ [thisFrame, nextFrame] = [nextFrame, thisFrame];
510
+ // Execute this frame
511
+ thisFrame.forEach(triggerCallback);
512
+ /**
513
+ * If we're recording stats then
514
+ */
515
+ if (stepName && statsBuffer.value) {
516
+ statsBuffer.value.frameloop[stepName].push(numCalls);
517
+ }
518
+ numCalls = 0;
519
+ // Clear the frame so no callbacks remain. This is to avoid
520
+ // memory leaks should this render step not run for a while.
521
+ thisFrame.clear();
522
+ isProcessing = false;
523
+ if (flushNextFrame) {
524
+ flushNextFrame = false;
525
+ step.process(frameData);
526
+ }
527
+ },
424
528
  };
425
- elements.forEach((element) => {
426
- element.addEventListener("pointerenter", onPointerEnter, eventOptions);
427
- });
428
- return cancel;
429
- }
430
-
431
- function capturePointer(event, action) {
432
- const actionName = `${action}PointerCapture`;
433
- if (event.target instanceof Element &&
434
- actionName in event.target &&
435
- event.pointerId !== undefined) {
436
- try {
437
- event.target[actionName](event.pointerId);
438
- }
439
- catch (e) { }
440
- }
529
+ return step;
441
530
  }
442
531
 
443
- /**
444
- * Recursively traverse up the tree to check whether the provided child node
445
- * is the parent or a descendant of it.
446
- *
447
- * @param parent - Element to find
448
- * @param child - Element to test against parent
449
- */
450
- const isNodeOrChild = (parent, child) => {
451
- if (!child) {
452
- return false;
453
- }
532
+ const maxElapsed$1 = 40;
533
+ function createRenderBatcher(scheduleNextBatch, allowKeepAlive) {
534
+ let runNextFrame = false;
535
+ let useDefaultElapsed = true;
536
+ const state = {
537
+ delta: 0.0,
538
+ timestamp: 0.0,
539
+ isProcessing: false,
540
+ };
541
+ const flagRunNextFrame = () => (runNextFrame = true);
542
+ const steps = stepsOrder.reduce((acc, key) => {
543
+ acc[key] = createRenderStep(flagRunNextFrame, allowKeepAlive ? key : undefined);
544
+ return acc;
545
+ }, {});
546
+ const { read, resolveKeyframes, update, preRender, render, postRender } = steps;
547
+ const processBatch = () => {
548
+ const timestamp = performance.now();
549
+ runNextFrame = false;
550
+ {
551
+ state.delta = useDefaultElapsed
552
+ ? 1000 / 60
553
+ : Math.max(Math.min(timestamp - state.timestamp, maxElapsed$1), 1);
554
+ }
555
+ state.timestamp = timestamp;
556
+ state.isProcessing = true;
557
+ // Unrolled render loop for better per-frame performance
558
+ read.process(state);
559
+ resolveKeyframes.process(state);
560
+ update.process(state);
561
+ preRender.process(state);
562
+ render.process(state);
563
+ postRender.process(state);
564
+ state.isProcessing = false;
565
+ if (runNextFrame && allowKeepAlive) {
566
+ useDefaultElapsed = false;
567
+ scheduleNextBatch(processBatch);
568
+ }
569
+ };
570
+ const wake = () => {
571
+ runNextFrame = true;
572
+ useDefaultElapsed = true;
573
+ if (!state.isProcessing) {
574
+ scheduleNextBatch(processBatch);
575
+ }
576
+ };
577
+ const schedule = stepsOrder.reduce((acc, key) => {
578
+ const step = steps[key];
579
+ acc[key] = (process, keepAlive = false, immediate = false) => {
580
+ if (!runNextFrame)
581
+ wake();
582
+ return step.schedule(process, keepAlive, immediate);
583
+ };
584
+ return acc;
585
+ }, {});
586
+ const cancel = (process) => {
587
+ for (let i = 0; i < stepsOrder.length; i++) {
588
+ steps[stepsOrder[i]].cancel(process);
589
+ }
590
+ };
591
+ return { schedule, cancel, state, steps };
592
+ }
593
+
594
+ const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true);
595
+
596
+ let now;
597
+ function clearTime() {
598
+ now = undefined;
599
+ }
600
+ /**
601
+ * An eventloop-synchronous alternative to performance.now().
602
+ *
603
+ * Ensures that time measurements remain consistent within a synchronous context.
604
+ * Usually calling performance.now() twice within the same synchronous context
605
+ * will return different values which isn't useful for animations when we're usually
606
+ * trying to sync animations to the same frame.
607
+ */
608
+ const time = {
609
+ now: () => {
610
+ if (now === undefined) {
611
+ time.set(frameData.isProcessing || MotionGlobalConfig.useManualTiming
612
+ ? frameData.timestamp
613
+ : performance.now());
614
+ }
615
+ return now;
616
+ },
617
+ set: (newTime) => {
618
+ now = newTime;
619
+ queueMicrotask(clearTime);
620
+ },
621
+ };
622
+
623
+ const isDragging = {
624
+ x: false,
625
+ y: false,
626
+ };
627
+ function isDragActive() {
628
+ return isDragging.x || isDragging.y;
629
+ }
630
+
631
+ function resolveElements(elementOrSelector, scope, selectorCache) {
632
+ var _a;
633
+ if (elementOrSelector instanceof EventTarget) {
634
+ return [elementOrSelector];
635
+ }
636
+ else if (typeof elementOrSelector === "string") {
637
+ let root = document;
638
+ if (scope) {
639
+ // TODO: Refactor to utils package
640
+ // invariant(
641
+ // Boolean(scope.current),
642
+ // "Scope provided, but no element detected."
643
+ // )
644
+ root = scope.current;
645
+ }
646
+ const elements = (_a = selectorCache === null || selectorCache === void 0 ? void 0 : selectorCache[elementOrSelector]) !== null && _a !== void 0 ? _a : root.querySelectorAll(elementOrSelector);
647
+ return elements ? Array.from(elements) : [];
648
+ }
649
+ return Array.from(elementOrSelector);
650
+ }
651
+
652
+ function setupGesture(elementOrSelector, options) {
653
+ const elements = resolveElements(elementOrSelector);
654
+ const gestureAbortController = new AbortController();
655
+ const eventOptions = {
656
+ passive: true,
657
+ ...options,
658
+ signal: gestureAbortController.signal,
659
+ };
660
+ const cancel = () => gestureAbortController.abort();
661
+ return [elements, eventOptions, cancel];
662
+ }
663
+
664
+ function isValidHover(event) {
665
+ return !(event.pointerType === "touch" || isDragActive());
666
+ }
667
+ /**
668
+ * Create a hover gesture. hover() is different to .addEventListener("pointerenter")
669
+ * in that it has an easier syntax, filters out polyfilled touch events, interoperates
670
+ * with drag gestures, and automatically removes the "pointerennd" event listener when the hover ends.
671
+ *
672
+ * @public
673
+ */
674
+ function hover(elementOrSelector, onHoverStart, options = {}) {
675
+ const [elements, eventOptions, cancel] = setupGesture(elementOrSelector, options);
676
+ const onPointerEnter = (enterEvent) => {
677
+ if (!isValidHover(enterEvent))
678
+ return;
679
+ const { target } = enterEvent;
680
+ const onHoverEnd = onHoverStart(target, enterEvent);
681
+ if (typeof onHoverEnd !== "function" || !target)
682
+ return;
683
+ const onPointerLeave = (leaveEvent) => {
684
+ if (!isValidHover(leaveEvent))
685
+ return;
686
+ onHoverEnd(leaveEvent);
687
+ target.removeEventListener("pointerleave", onPointerLeave);
688
+ };
689
+ target.addEventListener("pointerleave", onPointerLeave, eventOptions);
690
+ };
691
+ elements.forEach((element) => {
692
+ element.addEventListener("pointerenter", onPointerEnter, eventOptions);
693
+ });
694
+ return cancel;
695
+ }
696
+
697
+ function capturePointer(event, action) {
698
+ const actionName = `${action}PointerCapture`;
699
+ if (event.target instanceof Element &&
700
+ actionName in event.target &&
701
+ event.pointerId !== undefined) {
702
+ try {
703
+ event.target[actionName](event.pointerId);
704
+ }
705
+ catch (e) { }
706
+ }
707
+ }
708
+
709
+ /**
710
+ * Recursively traverse up the tree to check whether the provided child node
711
+ * is the parent or a descendant of it.
712
+ *
713
+ * @param parent - Element to find
714
+ * @param child - Element to test against parent
715
+ */
716
+ const isNodeOrChild = (parent, child) => {
717
+ if (!child) {
718
+ return false;
719
+ }
454
720
  else if (parent === child) {
455
721
  return true;
456
722
  }
@@ -622,1290 +888,1018 @@ function checkOutside(event, rect) {
622
888
  event.clientY > rect.bottom);
623
889
  }
624
890
 
625
- const clamp = (min, max, v) => {
626
- if (v > max)
627
- return max;
628
- if (v < min)
629
- return min;
630
- return v;
631
- };
632
-
633
- /*
634
- Convert velocity into velocity per second
635
-
636
- @param [number]: Unit per frame
637
- @param [number]: Frame duration in ms
638
- */
639
- function velocityPerSecond(velocity, frameDuration) {
640
- return frameDuration ? velocity * (1000 / frameDuration) : 0;
641
- }
642
-
643
- const velocitySampleDuration = 5; // ms
644
- function calcGeneratorVelocity(resolveValue, t, current) {
645
- const prevT = Math.max(t - velocitySampleDuration, 0);
646
- return velocityPerSecond(current - resolveValue(prevT), t - prevT);
647
- }
648
-
649
- const springDefaults = {
650
- // Default spring physics
651
- stiffness: 100,
652
- damping: 10,
653
- mass: 1.0,
654
- velocity: 0.0,
655
- // Default duration/bounce-based options
656
- duration: 800, // in ms
657
- bounce: 0.3,
658
- visualDuration: 0.3, // in seconds
659
- // Rest thresholds
660
- restSpeed: {
661
- granular: 0.01,
662
- default: 2,
663
- },
664
- restDelta: {
665
- granular: 0.005,
666
- default: 0.5,
667
- },
668
- // Limits
669
- minDuration: 0.01, // in seconds
670
- maxDuration: 10.0, // in seconds
671
- minDamping: 0.05,
672
- maxDamping: 1,
891
+ /**
892
+ * Maximum time between the value of two frames, beyond which we
893
+ * assume the velocity has since been 0.
894
+ */
895
+ const MAX_VELOCITY_DELTA = 30;
896
+ const isFloat = (value) => {
897
+ return !isNaN(parseFloat(value));
673
898
  };
674
-
675
- const safeMin = 0.001;
676
- function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass, }) {
677
- let envelope;
678
- let derivative;
679
- warning(duration <= secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less");
680
- let dampingRatio = 1 - bounce;
899
+ /**
900
+ * `MotionValue` is used to track the state and velocity of motion values.
901
+ *
902
+ * @public
903
+ */
904
+ class MotionValue {
681
905
  /**
682
- * Restrict dampingRatio and duration to within acceptable ranges.
906
+ * @param init - The initiating value
907
+ * @param config - Optional configuration options
908
+ *
909
+ * - `transformer`: A function to transform incoming values with.
683
910
  */
684
- dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio);
685
- duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, millisecondsToSeconds(duration));
686
- if (dampingRatio < 1) {
911
+ constructor(init, options = {}) {
687
912
  /**
688
- * Underdamped spring
913
+ * This will be replaced by the build step with the latest version number.
914
+ * When MotionValues are provided to motion components, warn if versions are mixed.
689
915
  */
690
- envelope = (undampedFreq) => {
691
- const exponentialDecay = undampedFreq * dampingRatio;
692
- const delta = exponentialDecay * duration;
693
- const a = exponentialDecay - velocity;
694
- const b = calcAngularFreq(undampedFreq, dampingRatio);
695
- const c = Math.exp(-delta);
696
- return safeMin - (a / b) * c;
697
- };
698
- derivative = (undampedFreq) => {
699
- const exponentialDecay = undampedFreq * dampingRatio;
700
- const delta = exponentialDecay * duration;
701
- const d = delta * velocity + velocity;
702
- const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
703
- const f = Math.exp(-delta);
704
- const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
705
- const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
706
- return (factor * ((d - e) * f)) / g;
707
- };
708
- }
709
- else {
916
+ this.version = "12.5.0";
710
917
  /**
711
- * Critically-damped spring
918
+ * Tracks whether this value can output a velocity. Currently this is only true
919
+ * if the value is numerical, but we might be able to widen the scope here and support
920
+ * other value types.
921
+ *
922
+ * @internal
712
923
  */
713
- envelope = (undampedFreq) => {
714
- const a = Math.exp(-undampedFreq * duration);
715
- const b = (undampedFreq - velocity) * duration + 1;
716
- return -safeMin + a * b;
717
- };
718
- derivative = (undampedFreq) => {
719
- const a = Math.exp(-undampedFreq * duration);
720
- const b = (velocity - undampedFreq) * (duration * duration);
721
- return a * b;
924
+ this.canTrackVelocity = null;
925
+ /**
926
+ * An object containing a SubscriptionManager for each active event.
927
+ */
928
+ this.events = {};
929
+ this.updateAndNotify = (v, render = true) => {
930
+ const currentTime = time.now();
931
+ /**
932
+ * If we're updating the value during another frame or eventloop
933
+ * than the previous frame, then the we set the previous frame value
934
+ * to current.
935
+ */
936
+ if (this.updatedAt !== currentTime) {
937
+ this.setPrevFrameValue();
938
+ }
939
+ this.prev = this.current;
940
+ this.setCurrent(v);
941
+ // Update update subscribers
942
+ if (this.current !== this.prev && this.events.change) {
943
+ this.events.change.notify(this.current);
944
+ }
945
+ // Update render subscribers
946
+ if (render && this.events.renderRequest) {
947
+ this.events.renderRequest.notify(this.current);
948
+ }
722
949
  };
950
+ this.hasAnimated = false;
951
+ this.setCurrent(init);
952
+ this.owner = options.owner;
723
953
  }
724
- const initialGuess = 5 / duration;
725
- const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
726
- duration = secondsToMilliseconds(duration);
727
- if (isNaN(undampedFreq)) {
728
- return {
729
- stiffness: springDefaults.stiffness,
730
- damping: springDefaults.damping,
731
- duration,
732
- };
954
+ setCurrent(current) {
955
+ this.current = current;
956
+ this.updatedAt = time.now();
957
+ if (this.canTrackVelocity === null && current !== undefined) {
958
+ this.canTrackVelocity = isFloat(this.current);
959
+ }
733
960
  }
734
- else {
735
- const stiffness = Math.pow(undampedFreq, 2) * mass;
736
- return {
737
- stiffness,
738
- damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
739
- duration,
740
- };
961
+ setPrevFrameValue(prevFrameValue = this.current) {
962
+ this.prevFrameValue = prevFrameValue;
963
+ this.prevUpdatedAt = this.updatedAt;
741
964
  }
742
- }
743
- const rootIterations = 12;
744
- function approximateRoot(envelope, derivative, initialGuess) {
745
- let result = initialGuess;
746
- for (let i = 1; i < rootIterations; i++) {
747
- result = result - envelope(result) / derivative(result);
965
+ /**
966
+ * Adds a function that will be notified when the `MotionValue` is updated.
967
+ *
968
+ * It returns a function that, when called, will cancel the subscription.
969
+ *
970
+ * When calling `onChange` inside a React component, it should be wrapped with the
971
+ * `useEffect` hook. As it returns an unsubscribe function, this should be returned
972
+ * from the `useEffect` function to ensure you don't add duplicate subscribers..
973
+ *
974
+ * ```jsx
975
+ * export const MyComponent = () => {
976
+ * const x = useMotionValue(0)
977
+ * const y = useMotionValue(0)
978
+ * const opacity = useMotionValue(1)
979
+ *
980
+ * useEffect(() => {
981
+ * function updateOpacity() {
982
+ * const maxXY = Math.max(x.get(), y.get())
983
+ * const newOpacity = transform(maxXY, [0, 100], [1, 0])
984
+ * opacity.set(newOpacity)
985
+ * }
986
+ *
987
+ * const unsubscribeX = x.on("change", updateOpacity)
988
+ * const unsubscribeY = y.on("change", updateOpacity)
989
+ *
990
+ * return () => {
991
+ * unsubscribeX()
992
+ * unsubscribeY()
993
+ * }
994
+ * }, [])
995
+ *
996
+ * return <motion.div style={{ x }} />
997
+ * }
998
+ * ```
999
+ *
1000
+ * @param subscriber - A function that receives the latest value.
1001
+ * @returns A function that, when called, will cancel this subscription.
1002
+ *
1003
+ * @deprecated
1004
+ */
1005
+ onChange(subscription) {
1006
+ if (process.env.NODE_ENV !== "production") {
1007
+ warnOnce(false, `value.onChange(callback) is deprecated. Switch to value.on("change", callback).`);
1008
+ }
1009
+ return this.on("change", subscription);
748
1010
  }
749
- return result;
750
- }
751
- function calcAngularFreq(undampedFreq, dampingRatio) {
752
- return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
753
- }
754
-
755
- const durationKeys = ["duration", "bounce"];
756
- const physicsKeys = ["stiffness", "damping", "mass"];
757
- function isSpringType(options, keys) {
758
- return keys.some((key) => options[key] !== undefined);
759
- }
760
- function getSpringOptions(options) {
761
- let springOptions = {
762
- velocity: springDefaults.velocity,
763
- stiffness: springDefaults.stiffness,
764
- damping: springDefaults.damping,
765
- mass: springDefaults.mass,
766
- isResolvedFromDuration: false,
767
- ...options,
768
- };
769
- // stiffness/damping/mass overrides duration/bounce
770
- if (!isSpringType(options, physicsKeys) &&
771
- isSpringType(options, durationKeys)) {
772
- if (options.visualDuration) {
773
- const visualDuration = options.visualDuration;
774
- const root = (2 * Math.PI) / (visualDuration * 1.2);
775
- const stiffness = root * root;
776
- const damping = 2 *
777
- clamp(0.05, 1, 1 - (options.bounce || 0)) *
778
- Math.sqrt(stiffness);
779
- springOptions = {
780
- ...springOptions,
781
- mass: springDefaults.mass,
782
- stiffness,
783
- damping,
784
- };
1011
+ on(eventName, callback) {
1012
+ if (!this.events[eventName]) {
1013
+ this.events[eventName] = new SubscriptionManager();
785
1014
  }
786
- else {
787
- const derived = findSpring(options);
788
- springOptions = {
789
- ...springOptions,
790
- ...derived,
791
- mass: springDefaults.mass,
1015
+ const unsubscribe = this.events[eventName].add(callback);
1016
+ if (eventName === "change") {
1017
+ return () => {
1018
+ unsubscribe();
1019
+ /**
1020
+ * If we have no more change listeners by the start
1021
+ * of the next frame, stop active animations.
1022
+ */
1023
+ frame.read(() => {
1024
+ if (!this.events.change.getSize()) {
1025
+ this.stop();
1026
+ }
1027
+ });
792
1028
  };
793
- springOptions.isResolvedFromDuration = true;
794
1029
  }
1030
+ return unsubscribe;
795
1031
  }
796
- return springOptions;
797
- }
798
- function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) {
799
- const options = typeof optionsOrVisualDuration !== "object"
800
- ? {
801
- visualDuration: optionsOrVisualDuration,
802
- keyframes: [0, 1],
803
- bounce,
1032
+ clearListeners() {
1033
+ for (const eventManagers in this.events) {
1034
+ this.events[eventManagers].clear();
804
1035
  }
805
- : optionsOrVisualDuration;
806
- let { restSpeed, restDelta } = options;
807
- const origin = options.keyframes[0];
808
- const target = options.keyframes[options.keyframes.length - 1];
1036
+ }
809
1037
  /**
810
- * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
811
- * to reduce GC during animation.
1038
+ * Attaches a passive effect to the `MotionValue`.
812
1039
  */
813
- const state = { done: false, value: origin };
814
- const { stiffness, damping, mass, duration, velocity, isResolvedFromDuration, } = getSpringOptions({
815
- ...options,
816
- velocity: -millisecondsToSeconds(options.velocity || 0),
817
- });
818
- const initialVelocity = velocity || 0.0;
819
- const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
820
- const initialDelta = target - origin;
821
- const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass));
1040
+ attach(passiveEffect, stopPassiveEffect) {
1041
+ this.passiveEffect = passiveEffect;
1042
+ this.stopPassiveEffect = stopPassiveEffect;
1043
+ }
822
1044
  /**
823
- * If we're working on a granular scale, use smaller defaults for determining
824
- * when the spring is finished.
1045
+ * Sets the state of the `MotionValue`.
825
1046
  *
826
- * These defaults have been selected emprically based on what strikes a good
827
- * ratio between feeling good and finishing as soon as changes are imperceptible.
828
- */
829
- const isGranularScale = Math.abs(initialDelta) < 5;
830
- restSpeed || (restSpeed = isGranularScale
831
- ? springDefaults.restSpeed.granular
832
- : springDefaults.restSpeed.default);
833
- restDelta || (restDelta = isGranularScale
834
- ? springDefaults.restDelta.granular
835
- : springDefaults.restDelta.default);
836
- let resolveSpring;
837
- if (dampingRatio < 1) {
838
- const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
839
- // Underdamped spring
840
- resolveSpring = (t) => {
841
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
842
- return (target -
843
- envelope *
844
- (((initialVelocity +
845
- dampingRatio * undampedAngularFreq * initialDelta) /
846
- angularFreq) *
847
- Math.sin(angularFreq * t) +
848
- initialDelta * Math.cos(angularFreq * t)));
849
- };
1047
+ * @remarks
1048
+ *
1049
+ * ```jsx
1050
+ * const x = useMotionValue(0)
1051
+ * x.set(10)
1052
+ * ```
1053
+ *
1054
+ * @param latest - Latest value to set.
1055
+ * @param render - Whether to notify render subscribers. Defaults to `true`
1056
+ *
1057
+ * @public
1058
+ */
1059
+ set(v, render = true) {
1060
+ if (!render || !this.passiveEffect) {
1061
+ this.updateAndNotify(v, render);
1062
+ }
1063
+ else {
1064
+ this.passiveEffect(v, this.updateAndNotify);
1065
+ }
850
1066
  }
851
- else if (dampingRatio === 1) {
852
- // Critically damped spring
853
- resolveSpring = (t) => target -
854
- Math.exp(-undampedAngularFreq * t) *
855
- (initialDelta +
856
- (initialVelocity + undampedAngularFreq * initialDelta) * t);
1067
+ setWithVelocity(prev, current, delta) {
1068
+ this.set(current);
1069
+ this.prev = undefined;
1070
+ this.prevFrameValue = prev;
1071
+ this.prevUpdatedAt = this.updatedAt - delta;
857
1072
  }
858
- else {
859
- // Overdamped spring
860
- const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
861
- resolveSpring = (t) => {
862
- const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
863
- // When performing sinh or cosh values can hit Infinity so we cap them here
864
- const freqForT = Math.min(dampedAngularFreq * t, 300);
865
- return (target -
866
- (envelope *
867
- ((initialVelocity +
868
- dampingRatio * undampedAngularFreq * initialDelta) *
869
- Math.sinh(freqForT) +
870
- dampedAngularFreq *
871
- initialDelta *
872
- Math.cosh(freqForT))) /
873
- dampedAngularFreq);
874
- };
1073
+ /**
1074
+ * Set the state of the `MotionValue`, stopping any active animations,
1075
+ * effects, and resets velocity to `0`.
1076
+ */
1077
+ jump(v, endAnimation = true) {
1078
+ this.updateAndNotify(v);
1079
+ this.prev = v;
1080
+ this.prevUpdatedAt = this.prevFrameValue = undefined;
1081
+ endAnimation && this.stop();
1082
+ if (this.stopPassiveEffect)
1083
+ this.stopPassiveEffect();
875
1084
  }
876
- const generator = {
877
- calculatedDuration: isResolvedFromDuration ? duration || null : null,
878
- next: (t) => {
879
- const current = resolveSpring(t);
880
- if (!isResolvedFromDuration) {
881
- let currentVelocity = 0.0;
882
- /**
883
- * We only need to calculate velocity for under-damped springs
884
- * as over- and critically-damped springs can't overshoot, so
885
- * checking only for displacement is enough.
886
- */
887
- if (dampingRatio < 1) {
888
- currentVelocity =
889
- t === 0
890
- ? secondsToMilliseconds(initialVelocity)
891
- : calcGeneratorVelocity(resolveSpring, t, current);
892
- }
893
- const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
894
- const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
895
- state.done =
896
- isBelowVelocityThreshold && isBelowDisplacementThreshold;
1085
+ /**
1086
+ * Returns the latest state of `MotionValue`
1087
+ *
1088
+ * @returns - The latest state of `MotionValue`
1089
+ *
1090
+ * @public
1091
+ */
1092
+ get() {
1093
+ return this.current;
1094
+ }
1095
+ /**
1096
+ * @public
1097
+ */
1098
+ getPrevious() {
1099
+ return this.prev;
1100
+ }
1101
+ /**
1102
+ * Returns the latest velocity of `MotionValue`
1103
+ *
1104
+ * @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
1105
+ *
1106
+ * @public
1107
+ */
1108
+ getVelocity() {
1109
+ const currentTime = time.now();
1110
+ if (!this.canTrackVelocity ||
1111
+ this.prevFrameValue === undefined ||
1112
+ currentTime - this.updatedAt > MAX_VELOCITY_DELTA) {
1113
+ return 0;
1114
+ }
1115
+ const delta = Math.min(this.updatedAt - this.prevUpdatedAt, MAX_VELOCITY_DELTA);
1116
+ // Casts because of parseFloat's poor typing
1117
+ return velocityPerSecond(parseFloat(this.current) -
1118
+ parseFloat(this.prevFrameValue), delta);
1119
+ }
1120
+ /**
1121
+ * Registers a new animation to control this `MotionValue`. Only one
1122
+ * animation can drive a `MotionValue` at one time.
1123
+ *
1124
+ * ```jsx
1125
+ * value.start()
1126
+ * ```
1127
+ *
1128
+ * @param animation - A function that starts the provided animation
1129
+ */
1130
+ start(startAnimation) {
1131
+ this.stop();
1132
+ return new Promise((resolve) => {
1133
+ this.hasAnimated = true;
1134
+ this.animation = startAnimation(resolve);
1135
+ if (this.events.animationStart) {
1136
+ this.events.animationStart.notify();
897
1137
  }
898
- else {
899
- state.done = t >= duration;
1138
+ }).then(() => {
1139
+ if (this.events.animationComplete) {
1140
+ this.events.animationComplete.notify();
900
1141
  }
901
- state.value = state.done ? target : current;
902
- return state;
903
- },
904
- toString: () => {
905
- const calculatedDuration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);
906
- const easing = generateLinearEasing((progress) => generator.next(calculatedDuration * progress).value, calculatedDuration, 30);
907
- return calculatedDuration + "ms " + easing;
908
- },
909
- };
910
- return generator;
1142
+ this.clearAnimation();
1143
+ });
1144
+ }
1145
+ /**
1146
+ * Stop the currently active animation.
1147
+ *
1148
+ * @public
1149
+ */
1150
+ stop() {
1151
+ if (this.animation) {
1152
+ this.animation.stop();
1153
+ if (this.events.animationCancel) {
1154
+ this.events.animationCancel.notify();
1155
+ }
1156
+ }
1157
+ this.clearAnimation();
1158
+ }
1159
+ /**
1160
+ * Returns `true` if this value is currently animating.
1161
+ *
1162
+ * @public
1163
+ */
1164
+ isAnimating() {
1165
+ return !!this.animation;
1166
+ }
1167
+ clearAnimation() {
1168
+ delete this.animation;
1169
+ }
1170
+ /**
1171
+ * Destroy and clean up subscribers to this `MotionValue`.
1172
+ *
1173
+ * The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
1174
+ * handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
1175
+ * created a `MotionValue` via the `motionValue` function.
1176
+ *
1177
+ * @public
1178
+ */
1179
+ destroy() {
1180
+ this.clearListeners();
1181
+ this.stop();
1182
+ if (this.stopPassiveEffect) {
1183
+ this.stopPassiveEffect();
1184
+ }
1185
+ }
911
1186
  }
912
-
913
- const wrap = (min, max, v) => {
914
- const rangeSize = max - min;
915
- return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
916
- };
917
-
918
- const isEasingArray = (ease) => {
919
- return Array.isArray(ease) && typeof ease[0] !== "number";
920
- };
921
-
922
- function getEasingForSegment(easing, i) {
923
- return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing;
1187
+ function motionValue(init, options) {
1188
+ return new MotionValue(init, options);
924
1189
  }
925
1190
 
926
- /*
927
- Value in range from progress
928
-
929
- Given a lower limit and an upper limit, we return the value within
930
- that range as expressed by progress (usually a number from 0 to 1)
931
-
932
- So progress = 0.5 would change
933
-
934
- from -------- to
935
-
936
- to
937
-
938
- from ---- to
939
-
940
- E.g. from = 10, to = 20, progress = 0.5 => 15
941
-
942
- @param [number]: Lower limit of range
943
- @param [number]: Upper limit of range
944
- @param [number]: The progress between lower and upper limits expressed 0-1
945
- @return [number]: Value as calculated from progress within range (not limited within range)
946
- */
947
- const mixNumber$1 = (from, to, progress) => {
948
- return from + (to - from) * progress;
1191
+ const clamp = (min, max, v) => {
1192
+ if (v > max)
1193
+ return max;
1194
+ if (v < min)
1195
+ return min;
1196
+ return v;
949
1197
  };
950
1198
 
951
- function fillOffset(offset, remaining) {
952
- const min = offset[offset.length - 1];
953
- for (let i = 1; i <= remaining; i++) {
954
- const offsetProgress = progress(0, remaining, i);
955
- offset.push(mixNumber$1(min, 1, offsetProgress));
956
- }
1199
+ const velocitySampleDuration = 5; // ms
1200
+ function calcGeneratorVelocity(resolveValue, t, current) {
1201
+ const prevT = Math.max(t - velocitySampleDuration, 0);
1202
+ return velocityPerSecond(current - resolveValue(prevT), t - prevT);
957
1203
  }
958
1204
 
959
- function defaultOffset$1(arr) {
960
- const offset = [0];
961
- fillOffset(offset, arr.length - 1);
962
- return offset;
963
- }
964
-
965
- const isMotionValue = (value) => Boolean(value && value.getVelocity);
966
-
967
- function isDOMKeyframes(keyframes) {
968
- return typeof keyframes === "object" && !Array.isArray(keyframes);
969
- }
1205
+ const springDefaults = {
1206
+ // Default spring physics
1207
+ stiffness: 100,
1208
+ damping: 10,
1209
+ mass: 1.0,
1210
+ velocity: 0.0,
1211
+ // Default duration/bounce-based options
1212
+ duration: 800, // in ms
1213
+ bounce: 0.3,
1214
+ visualDuration: 0.3, // in seconds
1215
+ // Rest thresholds
1216
+ restSpeed: {
1217
+ granular: 0.01,
1218
+ default: 2,
1219
+ },
1220
+ restDelta: {
1221
+ granular: 0.005,
1222
+ default: 0.5,
1223
+ },
1224
+ // Limits
1225
+ minDuration: 0.01, // in seconds
1226
+ maxDuration: 10.0, // in seconds
1227
+ minDamping: 0.05,
1228
+ maxDamping: 1,
1229
+ };
970
1230
 
971
- function resolveSubjects(subject, keyframes, scope, selectorCache) {
972
- if (typeof subject === "string" && isDOMKeyframes(keyframes)) {
973
- return resolveElements(subject, scope, selectorCache);
974
- }
975
- else if (subject instanceof NodeList) {
976
- return Array.from(subject);
977
- }
978
- else if (Array.isArray(subject)) {
979
- return subject;
1231
+ const safeMin = 0.001;
1232
+ function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass, }) {
1233
+ let envelope;
1234
+ let derivative;
1235
+ warning(duration <= secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less");
1236
+ let dampingRatio = 1 - bounce;
1237
+ /**
1238
+ * Restrict dampingRatio and duration to within acceptable ranges.
1239
+ */
1240
+ dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio);
1241
+ duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, millisecondsToSeconds(duration));
1242
+ if (dampingRatio < 1) {
1243
+ /**
1244
+ * Underdamped spring
1245
+ */
1246
+ envelope = (undampedFreq) => {
1247
+ const exponentialDecay = undampedFreq * dampingRatio;
1248
+ const delta = exponentialDecay * duration;
1249
+ const a = exponentialDecay - velocity;
1250
+ const b = calcAngularFreq(undampedFreq, dampingRatio);
1251
+ const c = Math.exp(-delta);
1252
+ return safeMin - (a / b) * c;
1253
+ };
1254
+ derivative = (undampedFreq) => {
1255
+ const exponentialDecay = undampedFreq * dampingRatio;
1256
+ const delta = exponentialDecay * duration;
1257
+ const d = delta * velocity + velocity;
1258
+ const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
1259
+ const f = Math.exp(-delta);
1260
+ const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
1261
+ const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
1262
+ return (factor * ((d - e) * f)) / g;
1263
+ };
980
1264
  }
981
1265
  else {
982
- return [subject];
983
- }
984
- }
985
-
986
- function calculateRepeatDuration(duration, repeat, _repeatDelay) {
987
- return duration * (repeat + 1);
988
- }
989
-
990
- /**
991
- * Given a absolute or relative time definition and current/prev time state of the sequence,
992
- * calculate an absolute time for the next keyframes.
993
- */
994
- function calcNextTime(current, next, prev, labels) {
995
- var _a;
996
- if (typeof next === "number") {
997
- return next;
998
- }
999
- else if (next.startsWith("-") || next.startsWith("+")) {
1000
- return Math.max(0, current + parseFloat(next));
1266
+ /**
1267
+ * Critically-damped spring
1268
+ */
1269
+ envelope = (undampedFreq) => {
1270
+ const a = Math.exp(-undampedFreq * duration);
1271
+ const b = (undampedFreq - velocity) * duration + 1;
1272
+ return -safeMin + a * b;
1273
+ };
1274
+ derivative = (undampedFreq) => {
1275
+ const a = Math.exp(-undampedFreq * duration);
1276
+ const b = (velocity - undampedFreq) * (duration * duration);
1277
+ return a * b;
1278
+ };
1001
1279
  }
1002
- else if (next === "<") {
1003
- return prev;
1280
+ const initialGuess = 5 / duration;
1281
+ const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
1282
+ duration = secondsToMilliseconds(duration);
1283
+ if (isNaN(undampedFreq)) {
1284
+ return {
1285
+ stiffness: springDefaults.stiffness,
1286
+ damping: springDefaults.damping,
1287
+ duration,
1288
+ };
1004
1289
  }
1005
1290
  else {
1006
- return (_a = labels.get(next)) !== null && _a !== void 0 ? _a : current;
1291
+ const stiffness = Math.pow(undampedFreq, 2) * mass;
1292
+ return {
1293
+ stiffness,
1294
+ damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
1295
+ duration,
1296
+ };
1007
1297
  }
1008
1298
  }
1009
-
1010
- function addUniqueItem(arr, item) {
1011
- if (arr.indexOf(item) === -1)
1012
- arr.push(item);
1299
+ const rootIterations = 12;
1300
+ function approximateRoot(envelope, derivative, initialGuess) {
1301
+ let result = initialGuess;
1302
+ for (let i = 1; i < rootIterations; i++) {
1303
+ result = result - envelope(result) / derivative(result);
1304
+ }
1305
+ return result;
1013
1306
  }
1014
- function removeItem(arr, item) {
1015
- const index = arr.indexOf(item);
1016
- if (index > -1)
1017
- arr.splice(index, 1);
1307
+ function calcAngularFreq(undampedFreq, dampingRatio) {
1308
+ return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
1018
1309
  }
1019
1310
 
1020
- function eraseKeyframes(sequence, startTime, endTime) {
1021
- for (let i = 0; i < sequence.length; i++) {
1022
- const keyframe = sequence[i];
1023
- if (keyframe.at > startTime && keyframe.at < endTime) {
1024
- removeItem(sequence, keyframe);
1025
- // If we remove this item we have to push the pointer back one
1026
- i--;
1311
+ const durationKeys = ["duration", "bounce"];
1312
+ const physicsKeys = ["stiffness", "damping", "mass"];
1313
+ function isSpringType(options, keys) {
1314
+ return keys.some((key) => options[key] !== undefined);
1315
+ }
1316
+ function getSpringOptions(options) {
1317
+ let springOptions = {
1318
+ velocity: springDefaults.velocity,
1319
+ stiffness: springDefaults.stiffness,
1320
+ damping: springDefaults.damping,
1321
+ mass: springDefaults.mass,
1322
+ isResolvedFromDuration: false,
1323
+ ...options,
1324
+ };
1325
+ // stiffness/damping/mass overrides duration/bounce
1326
+ if (!isSpringType(options, physicsKeys) &&
1327
+ isSpringType(options, durationKeys)) {
1328
+ if (options.visualDuration) {
1329
+ const visualDuration = options.visualDuration;
1330
+ const root = (2 * Math.PI) / (visualDuration * 1.2);
1331
+ const stiffness = root * root;
1332
+ const damping = 2 *
1333
+ clamp(0.05, 1, 1 - (options.bounce || 0)) *
1334
+ Math.sqrt(stiffness);
1335
+ springOptions = {
1336
+ ...springOptions,
1337
+ mass: springDefaults.mass,
1338
+ stiffness,
1339
+ damping,
1340
+ };
1341
+ }
1342
+ else {
1343
+ const derived = findSpring(options);
1344
+ springOptions = {
1345
+ ...springOptions,
1346
+ ...derived,
1347
+ mass: springDefaults.mass,
1348
+ };
1349
+ springOptions.isResolvedFromDuration = true;
1027
1350
  }
1028
1351
  }
1352
+ return springOptions;
1029
1353
  }
1030
- function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
1354
+ function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) {
1355
+ const options = typeof optionsOrVisualDuration !== "object"
1356
+ ? {
1357
+ visualDuration: optionsOrVisualDuration,
1358
+ keyframes: [0, 1],
1359
+ bounce,
1360
+ }
1361
+ : optionsOrVisualDuration;
1362
+ let { restSpeed, restDelta } = options;
1363
+ const origin = options.keyframes[0];
1364
+ const target = options.keyframes[options.keyframes.length - 1];
1031
1365
  /**
1032
- * Erase every existing value between currentTime and targetTime,
1033
- * this will essentially splice this timeline into any currently
1034
- * defined ones.
1366
+ * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
1367
+ * to reduce GC during animation.
1035
1368
  */
1036
- eraseKeyframes(sequence, startTime, endTime);
1037
- for (let i = 0; i < keyframes.length; i++) {
1038
- sequence.push({
1039
- value: keyframes[i],
1040
- at: mixNumber$1(startTime, endTime, offset[i]),
1041
- easing: getEasingForSegment(easing, i),
1042
- });
1369
+ const state = { done: false, value: origin };
1370
+ const { stiffness, damping, mass, duration, velocity, isResolvedFromDuration, } = getSpringOptions({
1371
+ ...options,
1372
+ velocity: -millisecondsToSeconds(options.velocity || 0),
1373
+ });
1374
+ const initialVelocity = velocity || 0.0;
1375
+ const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
1376
+ const initialDelta = target - origin;
1377
+ const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass));
1378
+ /**
1379
+ * If we're working on a granular scale, use smaller defaults for determining
1380
+ * when the spring is finished.
1381
+ *
1382
+ * These defaults have been selected emprically based on what strikes a good
1383
+ * ratio between feeling good and finishing as soon as changes are imperceptible.
1384
+ */
1385
+ const isGranularScale = Math.abs(initialDelta) < 5;
1386
+ restSpeed || (restSpeed = isGranularScale
1387
+ ? springDefaults.restSpeed.granular
1388
+ : springDefaults.restSpeed.default);
1389
+ restDelta || (restDelta = isGranularScale
1390
+ ? springDefaults.restDelta.granular
1391
+ : springDefaults.restDelta.default);
1392
+ let resolveSpring;
1393
+ if (dampingRatio < 1) {
1394
+ const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
1395
+ // Underdamped spring
1396
+ resolveSpring = (t) => {
1397
+ const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1398
+ return (target -
1399
+ envelope *
1400
+ (((initialVelocity +
1401
+ dampingRatio * undampedAngularFreq * initialDelta) /
1402
+ angularFreq) *
1403
+ Math.sin(angularFreq * t) +
1404
+ initialDelta * Math.cos(angularFreq * t)));
1405
+ };
1043
1406
  }
1044
- }
1045
-
1046
- /**
1047
- * Take an array of times that represent repeated keyframes. For instance
1048
- * if we have original times of [0, 0.5, 1] then our repeated times will
1049
- * be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back
1050
- * down to a 0-1 scale.
1051
- */
1052
- function normalizeTimes(times, repeat) {
1053
- for (let i = 0; i < times.length; i++) {
1054
- times[i] = times[i] / (repeat + 1);
1055
- }
1056
- }
1057
-
1058
- function compareByTime(a, b) {
1059
- if (a.at === b.at) {
1060
- if (a.value === null)
1061
- return 1;
1062
- if (b.value === null)
1063
- return -1;
1064
- return 0;
1407
+ else if (dampingRatio === 1) {
1408
+ // Critically damped spring
1409
+ resolveSpring = (t) => target -
1410
+ Math.exp(-undampedAngularFreq * t) *
1411
+ (initialDelta +
1412
+ (initialVelocity + undampedAngularFreq * initialDelta) * t);
1065
1413
  }
1066
1414
  else {
1067
- return a.at - b.at;
1068
- }
1069
- }
1070
-
1071
- const defaultSegmentEasing = "easeInOut";
1072
- const MAX_REPEAT = 20;
1073
- function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
1074
- const defaultDuration = defaultTransition.duration || 0.3;
1075
- const animationDefinitions = new Map();
1076
- const sequences = new Map();
1077
- const elementCache = {};
1078
- const timeLabels = new Map();
1079
- let prevTime = 0;
1080
- let currentTime = 0;
1081
- let totalDuration = 0;
1082
- /**
1083
- * Build the timeline by mapping over the sequence array and converting
1084
- * the definitions into keyframes and offsets with absolute time values.
1085
- * These will later get converted into relative offsets in a second pass.
1086
- */
1087
- for (let i = 0; i < sequence.length; i++) {
1088
- const segment = sequence[i];
1089
- /**
1090
- * If this is a timeline label, mark it and skip the rest of this iteration.
1091
- */
1092
- if (typeof segment === "string") {
1093
- timeLabels.set(segment, currentTime);
1094
- continue;
1095
- }
1096
- else if (!Array.isArray(segment)) {
1097
- timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
1098
- continue;
1099
- }
1100
- let [subject, keyframes, transition = {}] = segment;
1101
- /**
1102
- * If a relative or absolute time value has been specified we need to resolve
1103
- * it in relation to the currentTime.
1104
- */
1105
- if (transition.at !== undefined) {
1106
- currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels);
1107
- }
1108
- /**
1109
- * Keep track of the maximum duration in this definition. This will be
1110
- * applied to currentTime once the definition has been parsed.
1111
- */
1112
- let maxDuration = 0;
1113
- const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
1114
- const valueKeyframesAsList = keyframesAsList(valueKeyframes);
1115
- const { delay = 0, times = defaultOffset$1(valueKeyframesAsList), type = "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
1116
- let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
1117
- /**
1118
- * Resolve stagger() if defined.
1119
- */
1120
- const calculatedDelay = typeof delay === "function"
1121
- ? delay(elementIndex, numSubjects)
1122
- : delay;
1123
- /**
1124
- * If this animation should and can use a spring, generate a spring easing function.
1125
- */
1126
- const numKeyframes = valueKeyframesAsList.length;
1127
- const createGenerator = isGenerator(type)
1128
- ? type
1129
- : generators === null || generators === void 0 ? void 0 : generators[type];
1130
- if (numKeyframes <= 2 && createGenerator) {
1131
- /**
1132
- * As we're creating an easing function from a spring,
1133
- * ideally we want to generate it using the real distance
1134
- * between the two keyframes. However this isn't always
1135
- * possible - in these situations we use 0-100.
1136
- */
1137
- let absoluteDelta = 100;
1138
- if (numKeyframes === 2 &&
1139
- isNumberKeyframesArray(valueKeyframesAsList)) {
1140
- const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
1141
- absoluteDelta = Math.abs(delta);
1142
- }
1143
- const springTransition = { ...remainingTransition };
1144
- if (duration !== undefined) {
1145
- springTransition.duration = secondsToMilliseconds(duration);
1146
- }
1147
- const springEasing = createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
1148
- ease = springEasing.ease;
1149
- duration = springEasing.duration;
1150
- }
1151
- duration !== null && duration !== void 0 ? duration : (duration = defaultDuration);
1152
- const startTime = currentTime + calculatedDelay;
1153
- /**
1154
- * If there's only one time offset of 0, fill in a second with length 1
1155
- */
1156
- if (times.length === 1 && times[0] === 0) {
1157
- times[1] = 1;
1158
- }
1159
- /**
1160
- * Fill out if offset if fewer offsets than keyframes
1161
- */
1162
- const remainder = times.length - valueKeyframesAsList.length;
1163
- remainder > 0 && fillOffset(times, remainder);
1164
- /**
1165
- * If only one value has been set, ie [1], push a null to the start of
1166
- * the keyframe array. This will let us mark a keyframe at this point
1167
- * that will later be hydrated with the previous value.
1168
- */
1169
- valueKeyframesAsList.length === 1 &&
1170
- valueKeyframesAsList.unshift(null);
1171
- /**
1172
- * Handle repeat options
1173
- */
1174
- if (repeat) {
1175
- exports.invariant(repeat < MAX_REPEAT, "Repeat count too high, must be less than 20");
1176
- duration = calculateRepeatDuration(duration, repeat);
1177
- const originalKeyframes = [...valueKeyframesAsList];
1178
- const originalTimes = [...times];
1179
- ease = Array.isArray(ease) ? [...ease] : [ease];
1180
- const originalEase = [...ease];
1181
- for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
1182
- valueKeyframesAsList.push(...originalKeyframes);
1183
- for (let keyframeIndex = 0; keyframeIndex < originalKeyframes.length; keyframeIndex++) {
1184
- times.push(originalTimes[keyframeIndex] + (repeatIndex + 1));
1185
- ease.push(keyframeIndex === 0
1186
- ? "linear"
1187
- : getEasingForSegment(originalEase, keyframeIndex - 1));
1188
- }
1189
- }
1190
- normalizeTimes(times, repeat);
1191
- }
1192
- const targetTime = startTime + duration;
1193
- /**
1194
- * Add keyframes, mapping offsets to absolute time.
1195
- */
1196
- addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
1197
- maxDuration = Math.max(calculatedDelay + duration, maxDuration);
1198
- totalDuration = Math.max(targetTime, totalDuration);
1415
+ // Overdamped spring
1416
+ const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
1417
+ resolveSpring = (t) => {
1418
+ const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1419
+ // When performing sinh or cosh values can hit Infinity so we cap them here
1420
+ const freqForT = Math.min(dampedAngularFreq * t, 300);
1421
+ return (target -
1422
+ (envelope *
1423
+ ((initialVelocity +
1424
+ dampingRatio * undampedAngularFreq * initialDelta) *
1425
+ Math.sinh(freqForT) +
1426
+ dampedAngularFreq *
1427
+ initialDelta *
1428
+ Math.cosh(freqForT))) /
1429
+ dampedAngularFreq);
1199
1430
  };
1200
- if (isMotionValue(subject)) {
1201
- const subjectSequence = getSubjectSequence(subject, sequences);
1202
- resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
1203
- }
1204
- else {
1205
- const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
1206
- const numSubjects = subjects.length;
1207
- /**
1208
- * For every element in this segment, process the defined values.
1209
- */
1210
- for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
1431
+ }
1432
+ const generator = {
1433
+ calculatedDuration: isResolvedFromDuration ? duration || null : null,
1434
+ next: (t) => {
1435
+ const current = resolveSpring(t);
1436
+ if (!isResolvedFromDuration) {
1437
+ let currentVelocity = 0.0;
1211
1438
  /**
1212
- * Cast necessary, but we know these are of this type
1439
+ * We only need to calculate velocity for under-damped springs
1440
+ * as over- and critically-damped springs can't overshoot, so
1441
+ * checking only for displacement is enough.
1213
1442
  */
1214
- keyframes = keyframes;
1215
- transition = transition;
1216
- const thisSubject = subjects[subjectIndex];
1217
- const subjectSequence = getSubjectSequence(thisSubject, sequences);
1218
- for (const key in keyframes) {
1219
- resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
1220
- }
1221
- }
1222
- }
1223
- prevTime = currentTime;
1224
- currentTime += maxDuration;
1225
- }
1226
- /**
1227
- * For every element and value combination create a new animation.
1228
- */
1229
- sequences.forEach((valueSequences, element) => {
1230
- for (const key in valueSequences) {
1231
- const valueSequence = valueSequences[key];
1232
- /**
1233
- * Arrange all the keyframes in ascending time order.
1234
- */
1235
- valueSequence.sort(compareByTime);
1236
- const keyframes = [];
1237
- const valueOffset = [];
1238
- const valueEasing = [];
1239
- /**
1240
- * For each keyframe, translate absolute times into
1241
- * relative offsets based on the total duration of the timeline.
1242
- */
1243
- for (let i = 0; i < valueSequence.length; i++) {
1244
- const { at, value, easing } = valueSequence[i];
1245
- keyframes.push(value);
1246
- valueOffset.push(progress(0, totalDuration, at));
1247
- valueEasing.push(easing || "easeOut");
1248
- }
1249
- /**
1250
- * If the first keyframe doesn't land on offset: 0
1251
- * provide one by duplicating the initial keyframe. This ensures
1252
- * it snaps to the first keyframe when the animation starts.
1253
- */
1254
- if (valueOffset[0] !== 0) {
1255
- valueOffset.unshift(0);
1256
- keyframes.unshift(keyframes[0]);
1257
- valueEasing.unshift(defaultSegmentEasing);
1258
- }
1259
- /**
1260
- * If the last keyframe doesn't land on offset: 1
1261
- * provide one with a null wildcard value. This will ensure it
1262
- * stays static until the end of the animation.
1263
- */
1264
- if (valueOffset[valueOffset.length - 1] !== 1) {
1265
- valueOffset.push(1);
1266
- keyframes.push(null);
1267
- }
1268
- if (!animationDefinitions.has(element)) {
1269
- animationDefinitions.set(element, {
1270
- keyframes: {},
1271
- transition: {},
1272
- });
1273
- }
1274
- const definition = animationDefinitions.get(element);
1275
- definition.keyframes[key] = keyframes;
1276
- definition.transition[key] = {
1277
- ...defaultTransition,
1278
- duration: totalDuration,
1279
- ease: valueEasing,
1280
- times: valueOffset,
1281
- ...sequenceTransition,
1282
- };
1283
- }
1284
- });
1285
- return animationDefinitions;
1286
- }
1287
- function getSubjectSequence(subject, sequences) {
1288
- !sequences.has(subject) && sequences.set(subject, {});
1289
- return sequences.get(subject);
1290
- }
1291
- function getValueSequence(name, sequences) {
1292
- if (!sequences[name])
1293
- sequences[name] = [];
1294
- return sequences[name];
1295
- }
1296
- function keyframesAsList(keyframes) {
1297
- return Array.isArray(keyframes) ? keyframes : [keyframes];
1298
- }
1299
- function getValueTransition(transition, key) {
1300
- return transition && transition[key]
1301
- ? {
1302
- ...transition,
1303
- ...transition[key],
1304
- }
1305
- : { ...transition };
1306
- }
1307
- const isNumber = (keyframe) => typeof keyframe === "number";
1308
- const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
1309
-
1310
- const visualElementStore = new WeakMap();
1311
-
1312
- /**
1313
- * Generate a list of every possible transform key.
1314
- */
1315
- const transformPropOrder = [
1316
- "transformPerspective",
1317
- "x",
1318
- "y",
1319
- "z",
1320
- "translateX",
1321
- "translateY",
1322
- "translateZ",
1323
- "scale",
1324
- "scaleX",
1325
- "scaleY",
1326
- "rotate",
1327
- "rotateX",
1328
- "rotateY",
1329
- "rotateZ",
1330
- "skew",
1331
- "skewX",
1332
- "skewY",
1333
- ];
1334
- /**
1335
- * A quick lookup for transform props.
1336
- */
1337
- const transformProps = new Set(transformPropOrder);
1338
-
1339
- const positionalKeys = new Set([
1340
- "width",
1341
- "height",
1342
- "top",
1343
- "left",
1344
- "right",
1345
- "bottom",
1346
- ...transformPropOrder,
1347
- ]);
1348
-
1349
- const isKeyframesTarget = (v) => {
1350
- return Array.isArray(v);
1351
- };
1352
-
1353
- const resolveFinalValueInKeyframes = (v) => {
1354
- // TODO maybe throw if v.length - 1 is placeholder token?
1355
- return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
1356
- };
1357
-
1358
- const MotionGlobalConfig = {
1359
- skipAnimations: false,
1360
- useManualTiming: false,
1361
- };
1362
-
1363
- const stepsOrder = [
1364
- "read", // Read
1365
- "resolveKeyframes", // Write/Read/Write/Read
1366
- "update", // Compute
1367
- "preRender", // Compute
1368
- "render", // Write
1369
- "postRender", // Compute
1370
- ];
1371
-
1372
- const statsBuffer = {
1373
- value: null,
1374
- addProjectionMetrics: null,
1375
- };
1376
-
1377
- function createRenderStep(runNextFrame, stepName) {
1378
- /**
1379
- * We create and reuse two queues, one to queue jobs for the current frame
1380
- * and one for the next. We reuse to avoid triggering GC after x frames.
1381
- */
1382
- let thisFrame = new Set();
1383
- let nextFrame = new Set();
1384
- /**
1385
- * Track whether we're currently processing jobs in this step. This way
1386
- * we can decide whether to schedule new jobs for this frame or next.
1387
- */
1388
- let isProcessing = false;
1389
- let flushNextFrame = false;
1390
- /**
1391
- * A set of processes which were marked keepAlive when scheduled.
1392
- */
1393
- const toKeepAlive = new WeakSet();
1394
- let latestFrameData = {
1395
- delta: 0.0,
1396
- timestamp: 0.0,
1397
- isProcessing: false,
1398
- };
1399
- let numCalls = 0;
1400
- function triggerCallback(callback) {
1401
- if (toKeepAlive.has(callback)) {
1402
- step.schedule(callback);
1403
- runNextFrame();
1404
- }
1405
- numCalls++;
1406
- callback(latestFrameData);
1407
- }
1408
- const step = {
1409
- /**
1410
- * Schedule a process to run on the next frame.
1411
- */
1412
- schedule: (callback, keepAlive = false, immediate = false) => {
1413
- const addToCurrentFrame = immediate && isProcessing;
1414
- const queue = addToCurrentFrame ? thisFrame : nextFrame;
1415
- if (keepAlive)
1416
- toKeepAlive.add(callback);
1417
- if (!queue.has(callback))
1418
- queue.add(callback);
1419
- return callback;
1420
- },
1421
- /**
1422
- * Cancel the provided callback from running on the next frame.
1423
- */
1424
- cancel: (callback) => {
1425
- nextFrame.delete(callback);
1426
- toKeepAlive.delete(callback);
1427
- },
1428
- /**
1429
- * Execute all schedule callbacks.
1430
- */
1431
- process: (frameData) => {
1432
- latestFrameData = frameData;
1433
- /**
1434
- * If we're already processing we've probably been triggered by a flushSync
1435
- * inside an existing process. Instead of executing, mark flushNextFrame
1436
- * as true and ensure we flush the following frame at the end of this one.
1437
- */
1438
- if (isProcessing) {
1439
- flushNextFrame = true;
1440
- return;
1441
- }
1442
- isProcessing = true;
1443
- [thisFrame, nextFrame] = [nextFrame, thisFrame];
1444
- // Execute this frame
1445
- thisFrame.forEach(triggerCallback);
1446
- /**
1447
- * If we're recording stats then
1448
- */
1449
- if (stepName && statsBuffer.value) {
1450
- statsBuffer.value.frameloop[stepName].push(numCalls);
1451
- }
1452
- numCalls = 0;
1453
- // Clear the frame so no callbacks remain. This is to avoid
1454
- // memory leaks should this render step not run for a while.
1455
- thisFrame.clear();
1456
- isProcessing = false;
1457
- if (flushNextFrame) {
1458
- flushNextFrame = false;
1459
- step.process(frameData);
1460
- }
1461
- },
1462
- };
1463
- return step;
1464
- }
1465
-
1466
- const maxElapsed$1 = 40;
1467
- function createRenderBatcher(scheduleNextBatch, allowKeepAlive) {
1468
- let runNextFrame = false;
1469
- let useDefaultElapsed = true;
1470
- const state = {
1471
- delta: 0.0,
1472
- timestamp: 0.0,
1473
- isProcessing: false,
1474
- };
1475
- const flagRunNextFrame = () => (runNextFrame = true);
1476
- const steps = stepsOrder.reduce((acc, key) => {
1477
- acc[key] = createRenderStep(flagRunNextFrame, allowKeepAlive ? key : undefined);
1478
- return acc;
1479
- }, {});
1480
- const { read, resolveKeyframes, update, preRender, render, postRender } = steps;
1481
- const processBatch = () => {
1482
- const timestamp = performance.now();
1483
- runNextFrame = false;
1484
- {
1485
- state.delta = useDefaultElapsed
1486
- ? 1000 / 60
1487
- : Math.max(Math.min(timestamp - state.timestamp, maxElapsed$1), 1);
1488
- }
1489
- state.timestamp = timestamp;
1490
- state.isProcessing = true;
1491
- // Unrolled render loop for better per-frame performance
1492
- read.process(state);
1493
- resolveKeyframes.process(state);
1494
- update.process(state);
1495
- preRender.process(state);
1496
- render.process(state);
1497
- postRender.process(state);
1498
- state.isProcessing = false;
1499
- if (runNextFrame && allowKeepAlive) {
1500
- useDefaultElapsed = false;
1501
- scheduleNextBatch(processBatch);
1502
- }
1503
- };
1504
- const wake = () => {
1505
- runNextFrame = true;
1506
- useDefaultElapsed = true;
1507
- if (!state.isProcessing) {
1508
- scheduleNextBatch(processBatch);
1509
- }
1510
- };
1511
- const schedule = stepsOrder.reduce((acc, key) => {
1512
- const step = steps[key];
1513
- acc[key] = (process, keepAlive = false, immediate = false) => {
1514
- if (!runNextFrame)
1515
- wake();
1516
- return step.schedule(process, keepAlive, immediate);
1517
- };
1518
- return acc;
1519
- }, {});
1520
- const cancel = (process) => {
1521
- for (let i = 0; i < stepsOrder.length; i++) {
1522
- steps[stepsOrder[i]].cancel(process);
1523
- }
1443
+ if (dampingRatio < 1) {
1444
+ currentVelocity =
1445
+ t === 0
1446
+ ? secondsToMilliseconds(initialVelocity)
1447
+ : calcGeneratorVelocity(resolveSpring, t, current);
1448
+ }
1449
+ const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
1450
+ const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
1451
+ state.done =
1452
+ isBelowVelocityThreshold && isBelowDisplacementThreshold;
1453
+ }
1454
+ else {
1455
+ state.done = t >= duration;
1456
+ }
1457
+ state.value = state.done ? target : current;
1458
+ return state;
1459
+ },
1460
+ toString: () => {
1461
+ const calculatedDuration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);
1462
+ const easing = generateLinearEasing((progress) => generator.next(calculatedDuration * progress).value, calculatedDuration, 30);
1463
+ return calculatedDuration + "ms " + easing;
1464
+ },
1524
1465
  };
1525
- return { schedule, cancel, state, steps };
1466
+ return generator;
1526
1467
  }
1527
1468
 
1528
- const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true);
1469
+ const wrap = (min, max, v) => {
1470
+ const rangeSize = max - min;
1471
+ return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
1472
+ };
1529
1473
 
1530
- let now;
1531
- function clearTime() {
1532
- now = undefined;
1474
+ const isEasingArray = (ease) => {
1475
+ return Array.isArray(ease) && typeof ease[0] !== "number";
1476
+ };
1477
+
1478
+ function getEasingForSegment(easing, i) {
1479
+ return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing;
1533
1480
  }
1534
- /**
1535
- * An eventloop-synchronous alternative to performance.now().
1536
- *
1537
- * Ensures that time measurements remain consistent within a synchronous context.
1538
- * Usually calling performance.now() twice within the same synchronous context
1539
- * will return different values which isn't useful for animations when we're usually
1540
- * trying to sync animations to the same frame.
1541
- */
1542
- const time = {
1543
- now: () => {
1544
- if (now === undefined) {
1545
- time.set(frameData.isProcessing || MotionGlobalConfig.useManualTiming
1546
- ? frameData.timestamp
1547
- : performance.now());
1548
- }
1549
- return now;
1550
- },
1551
- set: (newTime) => {
1552
- now = newTime;
1553
- queueMicrotask(clearTime);
1554
- },
1481
+
1482
+ /*
1483
+ Value in range from progress
1484
+
1485
+ Given a lower limit and an upper limit, we return the value within
1486
+ that range as expressed by progress (usually a number from 0 to 1)
1487
+
1488
+ So progress = 0.5 would change
1489
+
1490
+ from -------- to
1491
+
1492
+ to
1493
+
1494
+ from ---- to
1495
+
1496
+ E.g. from = 10, to = 20, progress = 0.5 => 15
1497
+
1498
+ @param [number]: Lower limit of range
1499
+ @param [number]: Upper limit of range
1500
+ @param [number]: The progress between lower and upper limits expressed 0-1
1501
+ @return [number]: Value as calculated from progress within range (not limited within range)
1502
+ */
1503
+ const mixNumber$1 = (from, to, progress) => {
1504
+ return from + (to - from) * progress;
1555
1505
  };
1556
1506
 
1557
- class SubscriptionManager {
1558
- constructor() {
1559
- this.subscriptions = [];
1560
- }
1561
- add(handler) {
1562
- addUniqueItem(this.subscriptions, handler);
1563
- return () => removeItem(this.subscriptions, handler);
1564
- }
1565
- notify(a, b, c) {
1566
- const numSubscriptions = this.subscriptions.length;
1567
- if (!numSubscriptions)
1568
- return;
1569
- if (numSubscriptions === 1) {
1570
- /**
1571
- * If there's only a single handler we can just call it without invoking a loop.
1572
- */
1573
- this.subscriptions[0](a, b, c);
1574
- }
1575
- else {
1576
- for (let i = 0; i < numSubscriptions; i++) {
1577
- /**
1578
- * Check whether the handler exists before firing as it's possible
1579
- * the subscriptions were modified during this loop running.
1580
- */
1581
- const handler = this.subscriptions[i];
1582
- handler && handler(a, b, c);
1583
- }
1584
- }
1585
- }
1586
- getSize() {
1587
- return this.subscriptions.length;
1588
- }
1589
- clear() {
1590
- this.subscriptions.length = 0;
1507
+ function fillOffset(offset, remaining) {
1508
+ const min = offset[offset.length - 1];
1509
+ for (let i = 1; i <= remaining; i++) {
1510
+ const offsetProgress = progress(0, remaining, i);
1511
+ offset.push(mixNumber$1(min, 1, offsetProgress));
1591
1512
  }
1592
1513
  }
1593
1514
 
1594
- const warned = new Set();
1595
- function warnOnce(condition, message, element) {
1596
- if (condition || warned.has(message))
1597
- return;
1598
- console.warn(message);
1599
- if (element)
1600
- console.warn(element);
1601
- warned.add(message);
1515
+ function defaultOffset$1(arr) {
1516
+ const offset = [0];
1517
+ fillOffset(offset, arr.length - 1);
1518
+ return offset;
1602
1519
  }
1603
1520
 
1604
- /**
1605
- * Maximum time between the value of two frames, beyond which we
1606
- * assume the velocity has since been 0.
1607
- */
1608
- const MAX_VELOCITY_DELTA = 30;
1609
- const isFloat = (value) => {
1610
- return !isNaN(parseFloat(value));
1611
- };
1612
- /**
1613
- * `MotionValue` is used to track the state and velocity of motion values.
1614
- *
1615
- * @public
1616
- */
1617
- class MotionValue {
1618
- /**
1619
- * @param init - The initiating value
1620
- * @param config - Optional configuration options
1621
- *
1622
- * - `transformer`: A function to transform incoming values with.
1623
- *
1624
- * @internal
1625
- */
1626
- constructor(init, options = {}) {
1627
- /**
1628
- * This will be replaced by the build step with the latest version number.
1629
- * When MotionValues are provided to motion components, warn if versions are mixed.
1630
- */
1631
- this.version = "12.4.13";
1632
- /**
1633
- * Tracks whether this value can output a velocity. Currently this is only true
1634
- * if the value is numerical, but we might be able to widen the scope here and support
1635
- * other value types.
1636
- *
1637
- * @internal
1638
- */
1639
- this.canTrackVelocity = null;
1640
- /**
1641
- * An object containing a SubscriptionManager for each active event.
1642
- */
1643
- this.events = {};
1644
- this.updateAndNotify = (v, render = true) => {
1645
- const currentTime = time.now();
1646
- /**
1647
- * If we're updating the value during another frame or eventloop
1648
- * than the previous frame, then the we set the previous frame value
1649
- * to current.
1650
- */
1651
- if (this.updatedAt !== currentTime) {
1652
- this.setPrevFrameValue();
1653
- }
1654
- this.prev = this.current;
1655
- this.setCurrent(v);
1656
- // Update update subscribers
1657
- if (this.current !== this.prev && this.events.change) {
1658
- this.events.change.notify(this.current);
1659
- }
1660
- // Update render subscribers
1661
- if (render && this.events.renderRequest) {
1662
- this.events.renderRequest.notify(this.current);
1663
- }
1664
- };
1665
- this.hasAnimated = false;
1666
- this.setCurrent(init);
1667
- this.owner = options.owner;
1668
- }
1669
- setCurrent(current) {
1670
- this.current = current;
1671
- this.updatedAt = time.now();
1672
- if (this.canTrackVelocity === null && current !== undefined) {
1673
- this.canTrackVelocity = isFloat(this.current);
1674
- }
1675
- }
1676
- setPrevFrameValue(prevFrameValue = this.current) {
1677
- this.prevFrameValue = prevFrameValue;
1678
- this.prevUpdatedAt = this.updatedAt;
1521
+ const isMotionValue = (value) => Boolean(value && value.getVelocity);
1522
+
1523
+ function isDOMKeyframes(keyframes) {
1524
+ return typeof keyframes === "object" && !Array.isArray(keyframes);
1525
+ }
1526
+
1527
+ function resolveSubjects(subject, keyframes, scope, selectorCache) {
1528
+ if (typeof subject === "string" && isDOMKeyframes(keyframes)) {
1529
+ return resolveElements(subject, scope, selectorCache);
1679
1530
  }
1680
- /**
1681
- * Adds a function that will be notified when the `MotionValue` is updated.
1682
- *
1683
- * It returns a function that, when called, will cancel the subscription.
1684
- *
1685
- * When calling `onChange` inside a React component, it should be wrapped with the
1686
- * `useEffect` hook. As it returns an unsubscribe function, this should be returned
1687
- * from the `useEffect` function to ensure you don't add duplicate subscribers..
1688
- *
1689
- * ```jsx
1690
- * export const MyComponent = () => {
1691
- * const x = useMotionValue(0)
1692
- * const y = useMotionValue(0)
1693
- * const opacity = useMotionValue(1)
1694
- *
1695
- * useEffect(() => {
1696
- * function updateOpacity() {
1697
- * const maxXY = Math.max(x.get(), y.get())
1698
- * const newOpacity = transform(maxXY, [0, 100], [1, 0])
1699
- * opacity.set(newOpacity)
1700
- * }
1701
- *
1702
- * const unsubscribeX = x.on("change", updateOpacity)
1703
- * const unsubscribeY = y.on("change", updateOpacity)
1704
- *
1705
- * return () => {
1706
- * unsubscribeX()
1707
- * unsubscribeY()
1708
- * }
1709
- * }, [])
1710
- *
1711
- * return <motion.div style={{ x }} />
1712
- * }
1713
- * ```
1714
- *
1715
- * @param subscriber - A function that receives the latest value.
1716
- * @returns A function that, when called, will cancel this subscription.
1717
- *
1718
- * @deprecated
1719
- */
1720
- onChange(subscription) {
1721
- if (process.env.NODE_ENV !== "production") {
1722
- warnOnce(false, `value.onChange(callback) is deprecated. Switch to value.on("change", callback).`);
1723
- }
1724
- return this.on("change", subscription);
1531
+ else if (subject instanceof NodeList) {
1532
+ return Array.from(subject);
1725
1533
  }
1726
- on(eventName, callback) {
1727
- if (!this.events[eventName]) {
1728
- this.events[eventName] = new SubscriptionManager();
1729
- }
1730
- const unsubscribe = this.events[eventName].add(callback);
1731
- if (eventName === "change") {
1732
- return () => {
1733
- unsubscribe();
1734
- /**
1735
- * If we have no more change listeners by the start
1736
- * of the next frame, stop active animations.
1737
- */
1738
- frame.read(() => {
1739
- if (!this.events.change.getSize()) {
1740
- this.stop();
1741
- }
1742
- });
1743
- };
1744
- }
1745
- return unsubscribe;
1534
+ else if (Array.isArray(subject)) {
1535
+ return subject;
1746
1536
  }
1747
- clearListeners() {
1748
- for (const eventManagers in this.events) {
1749
- this.events[eventManagers].clear();
1750
- }
1537
+ else {
1538
+ return [subject];
1751
1539
  }
1752
- /**
1753
- * Attaches a passive effect to the `MotionValue`.
1754
- *
1755
- * @internal
1756
- */
1757
- attach(passiveEffect, stopPassiveEffect) {
1758
- this.passiveEffect = passiveEffect;
1759
- this.stopPassiveEffect = stopPassiveEffect;
1540
+ }
1541
+
1542
+ function calculateRepeatDuration(duration, repeat, _repeatDelay) {
1543
+ return duration * (repeat + 1);
1544
+ }
1545
+
1546
+ /**
1547
+ * Given a absolute or relative time definition and current/prev time state of the sequence,
1548
+ * calculate an absolute time for the next keyframes.
1549
+ */
1550
+ function calcNextTime(current, next, prev, labels) {
1551
+ var _a;
1552
+ if (typeof next === "number") {
1553
+ return next;
1760
1554
  }
1761
- /**
1762
- * Sets the state of the `MotionValue`.
1763
- *
1764
- * @remarks
1765
- *
1766
- * ```jsx
1767
- * const x = useMotionValue(0)
1768
- * x.set(10)
1769
- * ```
1770
- *
1771
- * @param latest - Latest value to set.
1772
- * @param render - Whether to notify render subscribers. Defaults to `true`
1773
- *
1774
- * @public
1775
- */
1776
- set(v, render = true) {
1777
- if (!render || !this.passiveEffect) {
1778
- this.updateAndNotify(v, render);
1779
- }
1780
- else {
1781
- this.passiveEffect(v, this.updateAndNotify);
1782
- }
1555
+ else if (next.startsWith("-") || next.startsWith("+")) {
1556
+ return Math.max(0, current + parseFloat(next));
1783
1557
  }
1784
- setWithVelocity(prev, current, delta) {
1785
- this.set(current);
1786
- this.prev = undefined;
1787
- this.prevFrameValue = prev;
1788
- this.prevUpdatedAt = this.updatedAt - delta;
1558
+ else if (next === "<") {
1559
+ return prev;
1789
1560
  }
1790
- /**
1791
- * Set the state of the `MotionValue`, stopping any active animations,
1792
- * effects, and resets velocity to `0`.
1793
- */
1794
- jump(v, endAnimation = true) {
1795
- this.updateAndNotify(v);
1796
- this.prev = v;
1797
- this.prevUpdatedAt = this.prevFrameValue = undefined;
1798
- endAnimation && this.stop();
1799
- if (this.stopPassiveEffect)
1800
- this.stopPassiveEffect();
1561
+ else {
1562
+ return (_a = labels.get(next)) !== null && _a !== void 0 ? _a : current;
1801
1563
  }
1802
- /**
1803
- * Returns the latest state of `MotionValue`
1804
- *
1805
- * @returns - The latest state of `MotionValue`
1806
- *
1807
- * @public
1808
- */
1809
- get() {
1810
- return this.current;
1564
+ }
1565
+
1566
+ function eraseKeyframes(sequence, startTime, endTime) {
1567
+ for (let i = 0; i < sequence.length; i++) {
1568
+ const keyframe = sequence[i];
1569
+ if (keyframe.at > startTime && keyframe.at < endTime) {
1570
+ removeItem(sequence, keyframe);
1571
+ // If we remove this item we have to push the pointer back one
1572
+ i--;
1573
+ }
1811
1574
  }
1575
+ }
1576
+ function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
1812
1577
  /**
1813
- * @public
1578
+ * Erase every existing value between currentTime and targetTime,
1579
+ * this will essentially splice this timeline into any currently
1580
+ * defined ones.
1814
1581
  */
1815
- getPrevious() {
1816
- return this.prev;
1582
+ eraseKeyframes(sequence, startTime, endTime);
1583
+ for (let i = 0; i < keyframes.length; i++) {
1584
+ sequence.push({
1585
+ value: keyframes[i],
1586
+ at: mixNumber$1(startTime, endTime, offset[i]),
1587
+ easing: getEasingForSegment(easing, i),
1588
+ });
1817
1589
  }
1818
- /**
1819
- * Returns the latest velocity of `MotionValue`
1820
- *
1821
- * @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
1822
- *
1823
- * @public
1824
- */
1825
- getVelocity() {
1826
- const currentTime = time.now();
1827
- if (!this.canTrackVelocity ||
1828
- this.prevFrameValue === undefined ||
1829
- currentTime - this.updatedAt > MAX_VELOCITY_DELTA) {
1830
- return 0;
1831
- }
1832
- const delta = Math.min(this.updatedAt - this.prevUpdatedAt, MAX_VELOCITY_DELTA);
1833
- // Casts because of parseFloat's poor typing
1834
- return velocityPerSecond(parseFloat(this.current) -
1835
- parseFloat(this.prevFrameValue), delta);
1590
+ }
1591
+
1592
+ /**
1593
+ * Take an array of times that represent repeated keyframes. For instance
1594
+ * if we have original times of [0, 0.5, 1] then our repeated times will
1595
+ * be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back
1596
+ * down to a 0-1 scale.
1597
+ */
1598
+ function normalizeTimes(times, repeat) {
1599
+ for (let i = 0; i < times.length; i++) {
1600
+ times[i] = times[i] / (repeat + 1);
1601
+ }
1602
+ }
1603
+
1604
+ function compareByTime(a, b) {
1605
+ if (a.at === b.at) {
1606
+ if (a.value === null)
1607
+ return 1;
1608
+ if (b.value === null)
1609
+ return -1;
1610
+ return 0;
1611
+ }
1612
+ else {
1613
+ return a.at - b.at;
1836
1614
  }
1615
+ }
1616
+
1617
+ const defaultSegmentEasing = "easeInOut";
1618
+ const MAX_REPEAT = 20;
1619
+ function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
1620
+ const defaultDuration = defaultTransition.duration || 0.3;
1621
+ const animationDefinitions = new Map();
1622
+ const sequences = new Map();
1623
+ const elementCache = {};
1624
+ const timeLabels = new Map();
1625
+ let prevTime = 0;
1626
+ let currentTime = 0;
1627
+ let totalDuration = 0;
1837
1628
  /**
1838
- * Registers a new animation to control this `MotionValue`. Only one
1839
- * animation can drive a `MotionValue` at one time.
1840
- *
1841
- * ```jsx
1842
- * value.start()
1843
- * ```
1844
- *
1845
- * @param animation - A function that starts the provided animation
1846
- *
1847
- * @internal
1629
+ * Build the timeline by mapping over the sequence array and converting
1630
+ * the definitions into keyframes and offsets with absolute time values.
1631
+ * These will later get converted into relative offsets in a second pass.
1848
1632
  */
1849
- start(startAnimation) {
1850
- this.stop();
1851
- return new Promise((resolve) => {
1852
- this.hasAnimated = true;
1853
- this.animation = startAnimation(resolve);
1854
- if (this.events.animationStart) {
1855
- this.events.animationStart.notify();
1633
+ for (let i = 0; i < sequence.length; i++) {
1634
+ const segment = sequence[i];
1635
+ /**
1636
+ * If this is a timeline label, mark it and skip the rest of this iteration.
1637
+ */
1638
+ if (typeof segment === "string") {
1639
+ timeLabels.set(segment, currentTime);
1640
+ continue;
1641
+ }
1642
+ else if (!Array.isArray(segment)) {
1643
+ timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
1644
+ continue;
1645
+ }
1646
+ let [subject, keyframes, transition = {}] = segment;
1647
+ /**
1648
+ * If a relative or absolute time value has been specified we need to resolve
1649
+ * it in relation to the currentTime.
1650
+ */
1651
+ if (transition.at !== undefined) {
1652
+ currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels);
1653
+ }
1654
+ /**
1655
+ * Keep track of the maximum duration in this definition. This will be
1656
+ * applied to currentTime once the definition has been parsed.
1657
+ */
1658
+ let maxDuration = 0;
1659
+ const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
1660
+ const valueKeyframesAsList = keyframesAsList(valueKeyframes);
1661
+ const { delay = 0, times = defaultOffset$1(valueKeyframesAsList), type = "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
1662
+ let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
1663
+ /**
1664
+ * Resolve stagger() if defined.
1665
+ */
1666
+ const calculatedDelay = typeof delay === "function"
1667
+ ? delay(elementIndex, numSubjects)
1668
+ : delay;
1669
+ /**
1670
+ * If this animation should and can use a spring, generate a spring easing function.
1671
+ */
1672
+ const numKeyframes = valueKeyframesAsList.length;
1673
+ const createGenerator = isGenerator(type)
1674
+ ? type
1675
+ : generators === null || generators === void 0 ? void 0 : generators[type];
1676
+ if (numKeyframes <= 2 && createGenerator) {
1677
+ /**
1678
+ * As we're creating an easing function from a spring,
1679
+ * ideally we want to generate it using the real distance
1680
+ * between the two keyframes. However this isn't always
1681
+ * possible - in these situations we use 0-100.
1682
+ */
1683
+ let absoluteDelta = 100;
1684
+ if (numKeyframes === 2 &&
1685
+ isNumberKeyframesArray(valueKeyframesAsList)) {
1686
+ const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
1687
+ absoluteDelta = Math.abs(delta);
1688
+ }
1689
+ const springTransition = { ...remainingTransition };
1690
+ if (duration !== undefined) {
1691
+ springTransition.duration = secondsToMilliseconds(duration);
1692
+ }
1693
+ const springEasing = createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
1694
+ ease = springEasing.ease;
1695
+ duration = springEasing.duration;
1856
1696
  }
1857
- }).then(() => {
1858
- if (this.events.animationComplete) {
1859
- this.events.animationComplete.notify();
1697
+ duration !== null && duration !== void 0 ? duration : (duration = defaultDuration);
1698
+ const startTime = currentTime + calculatedDelay;
1699
+ /**
1700
+ * If there's only one time offset of 0, fill in a second with length 1
1701
+ */
1702
+ if (times.length === 1 && times[0] === 0) {
1703
+ times[1] = 1;
1860
1704
  }
1861
- this.clearAnimation();
1862
- });
1863
- }
1864
- /**
1865
- * Stop the currently active animation.
1866
- *
1867
- * @public
1868
- */
1869
- stop() {
1870
- if (this.animation) {
1871
- this.animation.stop();
1872
- if (this.events.animationCancel) {
1873
- this.events.animationCancel.notify();
1705
+ /**
1706
+ * Fill out if offset if fewer offsets than keyframes
1707
+ */
1708
+ const remainder = times.length - valueKeyframesAsList.length;
1709
+ remainder > 0 && fillOffset(times, remainder);
1710
+ /**
1711
+ * If only one value has been set, ie [1], push a null to the start of
1712
+ * the keyframe array. This will let us mark a keyframe at this point
1713
+ * that will later be hydrated with the previous value.
1714
+ */
1715
+ valueKeyframesAsList.length === 1 &&
1716
+ valueKeyframesAsList.unshift(null);
1717
+ /**
1718
+ * Handle repeat options
1719
+ */
1720
+ if (repeat) {
1721
+ exports.invariant(repeat < MAX_REPEAT, "Repeat count too high, must be less than 20");
1722
+ duration = calculateRepeatDuration(duration, repeat);
1723
+ const originalKeyframes = [...valueKeyframesAsList];
1724
+ const originalTimes = [...times];
1725
+ ease = Array.isArray(ease) ? [...ease] : [ease];
1726
+ const originalEase = [...ease];
1727
+ for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
1728
+ valueKeyframesAsList.push(...originalKeyframes);
1729
+ for (let keyframeIndex = 0; keyframeIndex < originalKeyframes.length; keyframeIndex++) {
1730
+ times.push(originalTimes[keyframeIndex] + (repeatIndex + 1));
1731
+ ease.push(keyframeIndex === 0
1732
+ ? "linear"
1733
+ : getEasingForSegment(originalEase, keyframeIndex - 1));
1734
+ }
1735
+ }
1736
+ normalizeTimes(times, repeat);
1874
1737
  }
1738
+ const targetTime = startTime + duration;
1739
+ /**
1740
+ * Add keyframes, mapping offsets to absolute time.
1741
+ */
1742
+ addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
1743
+ maxDuration = Math.max(calculatedDelay + duration, maxDuration);
1744
+ totalDuration = Math.max(targetTime, totalDuration);
1745
+ };
1746
+ if (isMotionValue(subject)) {
1747
+ const subjectSequence = getSubjectSequence(subject, sequences);
1748
+ resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
1875
1749
  }
1876
- this.clearAnimation();
1877
- }
1878
- /**
1879
- * Returns `true` if this value is currently animating.
1880
- *
1881
- * @public
1882
- */
1883
- isAnimating() {
1884
- return !!this.animation;
1885
- }
1886
- clearAnimation() {
1887
- delete this.animation;
1750
+ else {
1751
+ const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
1752
+ const numSubjects = subjects.length;
1753
+ /**
1754
+ * For every element in this segment, process the defined values.
1755
+ */
1756
+ for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
1757
+ /**
1758
+ * Cast necessary, but we know these are of this type
1759
+ */
1760
+ keyframes = keyframes;
1761
+ transition = transition;
1762
+ const thisSubject = subjects[subjectIndex];
1763
+ const subjectSequence = getSubjectSequence(thisSubject, sequences);
1764
+ for (const key in keyframes) {
1765
+ resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
1766
+ }
1767
+ }
1768
+ }
1769
+ prevTime = currentTime;
1770
+ currentTime += maxDuration;
1888
1771
  }
1889
1772
  /**
1890
- * Destroy and clean up subscribers to this `MotionValue`.
1891
- *
1892
- * The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
1893
- * handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
1894
- * created a `MotionValue` via the `motionValue` function.
1895
- *
1896
- * @public
1773
+ * For every element and value combination create a new animation.
1897
1774
  */
1898
- destroy() {
1899
- this.clearListeners();
1900
- this.stop();
1901
- if (this.stopPassiveEffect) {
1902
- this.stopPassiveEffect();
1775
+ sequences.forEach((valueSequences, element) => {
1776
+ for (const key in valueSequences) {
1777
+ const valueSequence = valueSequences[key];
1778
+ /**
1779
+ * Arrange all the keyframes in ascending time order.
1780
+ */
1781
+ valueSequence.sort(compareByTime);
1782
+ const keyframes = [];
1783
+ const valueOffset = [];
1784
+ const valueEasing = [];
1785
+ /**
1786
+ * For each keyframe, translate absolute times into
1787
+ * relative offsets based on the total duration of the timeline.
1788
+ */
1789
+ for (let i = 0; i < valueSequence.length; i++) {
1790
+ const { at, value, easing } = valueSequence[i];
1791
+ keyframes.push(value);
1792
+ valueOffset.push(progress(0, totalDuration, at));
1793
+ valueEasing.push(easing || "easeOut");
1794
+ }
1795
+ /**
1796
+ * If the first keyframe doesn't land on offset: 0
1797
+ * provide one by duplicating the initial keyframe. This ensures
1798
+ * it snaps to the first keyframe when the animation starts.
1799
+ */
1800
+ if (valueOffset[0] !== 0) {
1801
+ valueOffset.unshift(0);
1802
+ keyframes.unshift(keyframes[0]);
1803
+ valueEasing.unshift(defaultSegmentEasing);
1804
+ }
1805
+ /**
1806
+ * If the last keyframe doesn't land on offset: 1
1807
+ * provide one with a null wildcard value. This will ensure it
1808
+ * stays static until the end of the animation.
1809
+ */
1810
+ if (valueOffset[valueOffset.length - 1] !== 1) {
1811
+ valueOffset.push(1);
1812
+ keyframes.push(null);
1813
+ }
1814
+ if (!animationDefinitions.has(element)) {
1815
+ animationDefinitions.set(element, {
1816
+ keyframes: {},
1817
+ transition: {},
1818
+ });
1819
+ }
1820
+ const definition = animationDefinitions.get(element);
1821
+ definition.keyframes[key] = keyframes;
1822
+ definition.transition[key] = {
1823
+ ...defaultTransition,
1824
+ duration: totalDuration,
1825
+ ease: valueEasing,
1826
+ times: valueOffset,
1827
+ ...sequenceTransition,
1828
+ };
1903
1829
  }
1904
- }
1830
+ });
1831
+ return animationDefinitions;
1905
1832
  }
1906
- function motionValue(init, options) {
1907
- return new MotionValue(init, options);
1833
+ function getSubjectSequence(subject, sequences) {
1834
+ !sequences.has(subject) && sequences.set(subject, {});
1835
+ return sequences.get(subject);
1836
+ }
1837
+ function getValueSequence(name, sequences) {
1838
+ if (!sequences[name])
1839
+ sequences[name] = [];
1840
+ return sequences[name];
1841
+ }
1842
+ function keyframesAsList(keyframes) {
1843
+ return Array.isArray(keyframes) ? keyframes : [keyframes];
1844
+ }
1845
+ function getValueTransition(transition, key) {
1846
+ return transition && transition[key]
1847
+ ? {
1848
+ ...transition,
1849
+ ...transition[key],
1850
+ }
1851
+ : { ...transition };
1908
1852
  }
1853
+ const isNumber = (keyframe) => typeof keyframe === "number";
1854
+ const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
1855
+
1856
+ const visualElementStore = new WeakMap();
1857
+
1858
+ /**
1859
+ * Generate a list of every possible transform key.
1860
+ */
1861
+ const transformPropOrder = [
1862
+ "transformPerspective",
1863
+ "x",
1864
+ "y",
1865
+ "z",
1866
+ "translateX",
1867
+ "translateY",
1868
+ "translateZ",
1869
+ "scale",
1870
+ "scaleX",
1871
+ "scaleY",
1872
+ "rotate",
1873
+ "rotateX",
1874
+ "rotateY",
1875
+ "rotateZ",
1876
+ "skew",
1877
+ "skewX",
1878
+ "skewY",
1879
+ ];
1880
+ /**
1881
+ * A quick lookup for transform props.
1882
+ */
1883
+ const transformProps = new Set(transformPropOrder);
1884
+
1885
+ const positionalKeys = new Set([
1886
+ "width",
1887
+ "height",
1888
+ "top",
1889
+ "left",
1890
+ "right",
1891
+ "bottom",
1892
+ ...transformPropOrder,
1893
+ ]);
1894
+
1895
+ const isKeyframesTarget = (v) => {
1896
+ return Array.isArray(v);
1897
+ };
1898
+
1899
+ const resolveFinalValueInKeyframes = (v) => {
1900
+ // TODO maybe throw if v.length - 1 is placeholder token?
1901
+ return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
1902
+ };
1909
1903
 
1910
1904
  function getValueState(visualElement) {
1911
1905
  const state = [{}, {}];
@@ -4635,7 +4629,7 @@ function updateMotionValuesFromProps(element, next, prev) {
4635
4629
  * and warn against mismatches.
4636
4630
  */
4637
4631
  if (process.env.NODE_ENV === "development") {
4638
- warnOnce(nextValue.version === "12.4.13", `Attempting to mix Motion versions ${nextValue.version} with 12.4.13 may not work as expected.`);
4632
+ warnOnce(nextValue.version === "12.5.0", `Attempting to mix Motion versions ${nextValue.version} with 12.5.0 may not work as expected.`);
4639
4633
  }
4640
4634
  }
4641
4635
  else if (isMotionValue(prevValue)) {
@@ -6588,22 +6582,6 @@ function transform(...args) {
6588
6582
  return useImmediate ? interpolator(inputValue) : interpolator;
6589
6583
  }
6590
6584
 
6591
- /**
6592
- * @deprecated
6593
- *
6594
- * Import as `frame` instead.
6595
- */
6596
- const sync = frame;
6597
- /**
6598
- * @deprecated
6599
- *
6600
- * Use cancelFrame(callback) instead.
6601
- */
6602
- const cancelSync = stepsOrder.reduce((acc, key) => {
6603
- acc[key] = (process) => cancelFrame(process);
6604
- return acc;
6605
- }, {});
6606
-
6607
6585
  exports.MotionValue = MotionValue;
6608
6586
  exports.animate = animate;
6609
6587
  exports.animateMini = animateMini;
@@ -6612,7 +6590,6 @@ exports.backIn = backIn;
6612
6590
  exports.backInOut = backInOut;
6613
6591
  exports.backOut = backOut;
6614
6592
  exports.cancelFrame = cancelFrame;
6615
- exports.cancelSync = cancelSync;
6616
6593
  exports.circIn = circIn;
6617
6594
  exports.circInOut = circInOut;
6618
6595
  exports.circOut = circOut;
@@ -6627,7 +6604,6 @@ exports.easeInOut = easeInOut;
6627
6604
  exports.easeOut = easeOut;
6628
6605
  exports.frame = frame;
6629
6606
  exports.frameData = frameData;
6630
- exports.frameSteps = frameSteps;
6631
6607
  exports.hover = hover;
6632
6608
  exports.inView = inView;
6633
6609
  exports.inertia = inertia;
@@ -6647,7 +6623,6 @@ exports.scrollInfo = scrollInfo;
6647
6623
  exports.spring = spring;
6648
6624
  exports.stagger = stagger;
6649
6625
  exports.steps = steps;
6650
- exports.sync = sync;
6651
6626
  exports.time = time;
6652
6627
  exports.transform = transform;
6653
6628
  exports.wrap = wrap;