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