motion 12.7.3 → 12.7.5-alpha.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 (174) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/debug.js +22 -14
  3. package/dist/cjs/index.js +4113 -3624
  4. package/dist/cjs/mini.js +403 -324
  5. package/dist/cjs/react-client.js +3151 -3245
  6. package/dist/cjs/react-m.js +169 -166
  7. package/dist/cjs/react-mini.js +330 -251
  8. package/dist/es/framer-motion/dist/es/animation/animate/sequence.mjs +1 -1
  9. package/dist/es/framer-motion/dist/es/animation/animators/waapi/animate-elements.mjs +81 -9
  10. package/dist/es/framer-motion/dist/es/animation/interfaces/motion-value.mjs +11 -30
  11. package/dist/es/framer-motion/dist/es/animation/interfaces/visual-element-target.mjs +1 -1
  12. package/dist/es/framer-motion/dist/es/animation/optimized-appear/store-id.mjs +1 -1
  13. package/dist/es/framer-motion/dist/es/animation/sequence/create.mjs +3 -3
  14. package/dist/es/framer-motion/dist/es/animation/sequence/utils/edit.mjs +2 -2
  15. package/dist/es/framer-motion/dist/es/animation/utils/default-transitions.mjs +1 -1
  16. package/dist/es/framer-motion/dist/es/animation/utils/stagger.mjs +1 -1
  17. package/dist/es/framer-motion/dist/es/components/AnimatePresence/PresenceChild.mjs +26 -23
  18. package/dist/es/framer-motion/dist/es/components/Reorder/utils/check-reorder.mjs +1 -1
  19. package/dist/es/framer-motion/dist/es/gestures/drag/VisualElementDragControls.mjs +2 -2
  20. package/dist/es/framer-motion/dist/es/gestures/drag/utils/constraints.mjs +2 -2
  21. package/dist/es/framer-motion/dist/es/gestures/focus.mjs +1 -1
  22. package/dist/es/framer-motion/dist/es/gestures/pan/PanSession.mjs +1 -1
  23. package/dist/es/framer-motion/dist/es/motion/utils/is-forced-motion-value.mjs +1 -1
  24. package/dist/es/framer-motion/dist/es/projection/animation/mix-values.mjs +3 -3
  25. package/dist/es/framer-motion/dist/es/projection/geometry/delta-apply.mjs +1 -1
  26. package/dist/es/framer-motion/dist/es/projection/geometry/delta-calc.mjs +1 -1
  27. package/dist/es/framer-motion/dist/es/projection/geometry/delta-remove.mjs +2 -2
  28. package/dist/es/framer-motion/dist/es/projection/node/create-projection-node.mjs +3 -5
  29. package/dist/es/framer-motion/dist/es/projection/styles/scale-border-radius.mjs +1 -1
  30. package/dist/es/framer-motion/dist/es/projection/styles/scale-box-shadow.mjs +2 -2
  31. package/dist/es/framer-motion/dist/es/projection/styles/scale-correction.mjs +1 -1
  32. package/dist/es/framer-motion/dist/es/render/VisualElement.mjs +7 -7
  33. package/dist/es/framer-motion/dist/es/render/dom/DOMVisualElement.mjs +1 -1
  34. package/dist/es/framer-motion/dist/es/render/dom/scroll/attach-animation.mjs +17 -0
  35. package/dist/es/framer-motion/dist/es/render/dom/scroll/attach-function.mjs +23 -0
  36. package/dist/es/framer-motion/dist/es/render/dom/scroll/index.mjs +6 -82
  37. package/dist/es/framer-motion/dist/es/render/dom/scroll/offsets/index.mjs +3 -3
  38. package/dist/es/framer-motion/dist/es/render/dom/scroll/utils/get-timeline.mjs +29 -0
  39. package/dist/es/framer-motion/dist/es/render/html/HTMLVisualElement.mjs +3 -3
  40. package/dist/es/framer-motion/dist/es/render/html/utils/build-styles.mjs +4 -4
  41. package/dist/es/framer-motion/dist/es/render/html/utils/build-transform.mjs +3 -3
  42. package/dist/es/framer-motion/dist/es/render/svg/SVGVisualElement.mjs +2 -2
  43. package/dist/es/framer-motion/dist/es/render/svg/config-motion.mjs +1 -1
  44. package/dist/es/framer-motion/dist/es/render/svg/utils/path.mjs +1 -1
  45. package/dist/es/framer-motion/dist/es/render/svg/utils/scrape-motion-values.mjs +1 -1
  46. package/dist/es/framer-motion/dist/es/render/svg/utils/transform-origin.mjs +1 -1
  47. package/dist/es/framer-motion/dist/es/render/utils/motion-values.mjs +1 -1
  48. package/dist/es/framer-motion/dist/es/utils/delay.mjs +1 -1
  49. package/dist/es/framer-motion/dist/es/utils/transform.mjs +1 -1
  50. package/dist/es/framer-motion/dist/es/utils/use-cycle.mjs +1 -1
  51. package/dist/es/framer-motion/dist/es/utils/use-instant-transition.mjs +4 -4
  52. package/dist/es/framer-motion/dist/es/value/use-spring.mjs +2 -2
  53. package/dist/es/framer-motion/dist/es/value/use-will-change/get-will-change-name.mjs +2 -2
  54. package/dist/es/motion/lib/index.mjs +109 -26
  55. package/dist/es/motion/lib/react.mjs +108 -32
  56. package/dist/es/motion-dom/dist/es/animation/AsyncMotionValueAnimation.mjs +179 -0
  57. package/dist/es/motion-dom/dist/es/animation/GroupAnimation.mjs +6 -15
  58. package/dist/es/{framer-motion/dist/es/animation/animators/MainThreadAnimation.mjs → motion-dom/dist/es/animation/JSAnimation.mjs} +108 -156
  59. package/dist/es/motion-dom/dist/es/animation/NativeAnimation.mjs +64 -67
  60. package/dist/es/motion-dom/dist/es/animation/NativeAnimationExtended.mjs +65 -0
  61. package/dist/es/motion-dom/dist/es/animation/NativeAnimationWrapper.mjs +14 -0
  62. package/dist/es/{framer-motion/dist/es/animation/animators → motion-dom/dist/es/animation}/drivers/driver-frameloop.mjs +2 -2
  63. package/dist/es/{framer-motion → motion-dom}/dist/es/animation/generators/keyframes.mjs +5 -5
  64. package/dist/es/{framer-motion → motion-dom}/dist/es/animation/generators/spring/find.mjs +1 -1
  65. package/dist/es/{framer-motion → motion-dom}/dist/es/animation/generators/spring/index.mjs +5 -6
  66. package/dist/es/{framer-motion/dist/es/render/dom → motion-dom/dist/es/animation/keyframes}/DOMKeyframesResolver.mjs +9 -8
  67. package/dist/es/{framer-motion/dist/es/render/utils → motion-dom/dist/es/animation/keyframes}/KeyframesResolver.mjs +28 -35
  68. package/dist/es/motion-dom/dist/es/animation/keyframes/get-final.mjs +3 -4
  69. package/dist/es/{framer-motion/dist/es/utils → motion-dom/dist/es/animation/keyframes}/offsets/fill.mjs +2 -2
  70. package/dist/es/motion-dom/dist/es/animation/keyframes/utils/apply-px-defaults.mjs +11 -0
  71. package/dist/es/motion-dom/dist/es/animation/keyframes/utils/fill-wildcards.mjs +7 -0
  72. package/dist/es/{framer-motion/dist/es/animation → motion-dom/dist/es/animation/keyframes}/utils/is-none.mjs +1 -1
  73. package/dist/es/{framer-motion/dist/es/render/html → motion-dom/dist/es/animation/keyframes}/utils/make-none-animatable.mjs +1 -1
  74. package/dist/es/{framer-motion/dist/es/render/dom → motion-dom/dist/es/animation/keyframes}/utils/unit-conversion.mjs +2 -2
  75. package/dist/es/motion-dom/dist/es/animation/utils/WithPromise.mjs +28 -0
  76. package/dist/es/motion-dom/dist/es/animation/utils/active-animations.mjs +9 -0
  77. package/dist/es/{framer-motion/dist/es/animation/animators → motion-dom/dist/es/animation}/utils/can-animate.mjs +3 -3
  78. package/dist/es/{framer-motion/dist/es/render/dom → motion-dom/dist/es/animation}/utils/css-variables-conversion.mjs +2 -2
  79. package/dist/es/motion-dom/dist/es/animation/utils/replace-transition-type.mjs +18 -0
  80. package/dist/es/motion-dom/dist/es/animation/waapi/easing/is-supported.mjs +1 -1
  81. package/dist/es/motion-dom/dist/es/animation/waapi/easing/map-easing.mjs +5 -3
  82. package/dist/es/motion-dom/dist/es/animation/waapi/start-waapi-animation.mjs +6 -4
  83. package/dist/es/motion-dom/dist/es/animation/waapi/supports/waapi.mjs +39 -0
  84. package/dist/es/motion-dom/dist/es/animation/waapi/utils/apply-generator.mjs +2 -1
  85. package/dist/es/motion-dom/dist/es/animation/waapi/utils/unsupported-easing.mjs +20 -0
  86. package/dist/es/motion-dom/dist/es/frameloop/batcher.mjs +2 -1
  87. package/dist/es/motion-dom/dist/es/frameloop/order.mjs +1 -0
  88. package/dist/es/motion-dom/dist/es/render/dom/is-css-var.mjs +3 -0
  89. package/dist/es/motion-dom/dist/es/render/dom/style-computed.mjs +10 -0
  90. package/dist/es/motion-dom/dist/es/render/dom/style-set.mjs +9 -0
  91. package/dist/es/{framer-motion/dist/es/render/html → motion-dom/dist/es/render}/utils/keys-transform.mjs +1 -1
  92. package/dist/es/{framer-motion/dist/es/render/dom → motion-dom/dist/es}/scroll/observe.mjs +1 -1
  93. package/dist/es/motion-dom/dist/es/stats/index.mjs +2 -0
  94. package/dist/es/{framer-motion → motion-dom}/dist/es/utils/interpolate.mjs +4 -3
  95. package/dist/es/{framer-motion → motion-dom}/dist/es/utils/mix/color.mjs +3 -3
  96. package/dist/es/{framer-motion → motion-dom}/dist/es/utils/mix/complex.mjs +5 -5
  97. package/dist/es/motion-dom/dist/es/value/index.mjs +3 -1
  98. package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/color/rgba.mjs +2 -2
  99. package/dist/es/{framer-motion/dist/es/render/dom/value-types → motion-dom/dist/es/value/types}/dimensions.mjs +3 -3
  100. package/dist/es/{framer-motion/dist/es/render/dom/value-types/type-int.mjs → motion-dom/dist/es/value/types/int.mjs} +1 -1
  101. package/dist/es/{framer-motion/dist/es/render/dom/value-types → motion-dom/dist/es/value/types/maps}/defaults.mjs +2 -2
  102. package/dist/es/{framer-motion/dist/es/render/dom/value-types/number-browser.mjs → motion-dom/dist/es/value/types/maps/number.mjs} +13 -3
  103. package/dist/es/{framer-motion/dist/es/render/dom/value-types → motion-dom/dist/es/value/types/maps}/transform.mjs +2 -2
  104. package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/numbers/index.mjs +1 -1
  105. package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/numbers/units.mjs +3 -2
  106. package/dist/es/{framer-motion/dist/es/render/dom/value-types → motion-dom/dist/es/value/types/utils}/animatable-none.mjs +4 -4
  107. package/dist/es/{framer-motion/dist/es/render/dom/value-types → motion-dom/dist/es/value/types/utils}/find.mjs +4 -4
  108. package/dist/es/motion-dom/dist/es/view/index.mjs +64 -0
  109. package/dist/es/motion-dom/dist/es/view/queue.mjs +52 -0
  110. package/dist/es/motion-dom/dist/es/view/start.mjs +155 -0
  111. package/dist/es/motion-dom/dist/es/view/utils/choose-layer-type.mjs +11 -0
  112. package/dist/es/motion-dom/dist/es/view/utils/css.mjs +32 -0
  113. package/dist/es/motion-dom/dist/es/view/utils/get-layer-name.mjs +8 -0
  114. package/dist/es/motion-dom/dist/es/view/utils/get-view-animations.mjs +12 -0
  115. package/dist/es/motion-dom/dist/es/view/utils/has-target.mjs +5 -0
  116. package/dist/es/{framer-motion → motion-utils}/dist/es/easing/cubic-bezier.mjs +1 -1
  117. package/dist/es/{framer-motion → motion-utils}/dist/es/easing/steps.mjs +1 -1
  118. package/dist/es/{framer-motion → motion-utils}/dist/es/easing/utils/get-easing-for-segment.mjs +1 -1
  119. package/dist/es/{framer-motion → motion-utils}/dist/es/easing/utils/map.mjs +7 -4
  120. package/dist/es/motion-utils/dist/es/global-config.mjs +1 -4
  121. package/dist/es/motion-utils/dist/es/warn-once.mjs +4 -1
  122. package/dist/motion.dev.js +4108 -3619
  123. package/dist/motion.js +1 -1
  124. package/package.json +3 -3
  125. package/dist/es/framer-motion/dist/es/animation/animators/AcceleratedAnimation.mjs +0 -324
  126. package/dist/es/framer-motion/dist/es/animation/animators/BaseAnimation.mjs +0 -120
  127. package/dist/es/framer-motion/dist/es/animation/animators/waapi/utils/supports-waapi.mjs +0 -5
  128. package/dist/es/framer-motion/dist/es/render/dom/value-types/number.mjs +0 -18
  129. package/dist/es/framer-motion/dist/es/utils/use-instant-transition-state.mjs +0 -5
  130. package/dist/es/motion-dom/dist/es/animation/keyframes/hydrate.mjs +0 -26
  131. package/dist/es/motion-dom/dist/es/animation/waapi/utils/attach-timeline.mjs +0 -6
  132. package/dist/es/motion-dom/dist/es/render/dom/style.mjs +0 -15
  133. /package/dist/es/{framer-motion → motion-dom}/dist/es/animation/generators/inertia.mjs +0 -0
  134. /package/dist/es/{framer-motion → motion-dom}/dist/es/animation/generators/spring/defaults.mjs +0 -0
  135. /package/dist/es/{framer-motion → motion-dom}/dist/es/animation/generators/utils/velocity.mjs +0 -0
  136. /package/dist/es/{framer-motion/dist/es/utils → motion-dom/dist/es/animation/keyframes}/offsets/default.mjs +0 -0
  137. /package/dist/es/{framer-motion/dist/es/utils → motion-dom/dist/es/animation/keyframes}/offsets/time.mjs +0 -0
  138. /package/dist/es/{framer-motion → motion-dom}/dist/es/animation/utils/is-animatable.mjs +0 -0
  139. /package/dist/es/{framer-motion/dist/es/render/dom → motion-dom/dist/es/animation}/utils/is-css-variable.mjs +0 -0
  140. /package/dist/es/{framer-motion/dist/es/animation/animators → motion-dom/dist/es/animation/waapi}/utils/accelerated-values.mjs +0 -0
  141. /package/dist/es/{framer-motion/dist/es/render/html/utils → motion-dom/dist/es/render/dom}/parse-transform.mjs +0 -0
  142. /package/dist/es/{framer-motion/dist/es/render/html → motion-dom/dist/es/render}/utils/keys-position.mjs +0 -0
  143. /package/dist/es/{framer-motion → motion-dom}/dist/es/utils/mix/immediate.mjs +0 -0
  144. /package/dist/es/{framer-motion → motion-dom}/dist/es/utils/mix/index.mjs +0 -0
  145. /package/dist/es/{framer-motion → motion-dom}/dist/es/utils/mix/number.mjs +0 -0
  146. /package/dist/es/{framer-motion → motion-dom}/dist/es/utils/mix/visibility.mjs +0 -0
  147. /package/dist/es/{framer-motion/dist/es/render/dom/value-types/type-auto.mjs → motion-dom/dist/es/value/types/auto.mjs} +0 -0
  148. /package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/color/hex.mjs +0 -0
  149. /package/dist/es/{framer-motion/dist/es/utils → motion-dom/dist/es/value/types/color}/hsla-to-rgba.mjs +0 -0
  150. /package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/color/hsla.mjs +0 -0
  151. /package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/color/index.mjs +0 -0
  152. /package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/color/utils.mjs +0 -0
  153. /package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/complex/filter.mjs +0 -0
  154. /package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/complex/index.mjs +0 -0
  155. /package/dist/es/{framer-motion/dist/es/render/dom/value-types → motion-dom/dist/es/value/types}/test.mjs +0 -0
  156. /package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/utils/color-regex.mjs +0 -0
  157. /package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/utils/float-regex.mjs +0 -0
  158. /package/dist/es/{framer-motion/dist/es/render/dom/value-types → motion-dom/dist/es/value/types/utils}/get-as-type.mjs +0 -0
  159. /package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/utils/is-nullish.mjs +0 -0
  160. /package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/utils/sanitize.mjs +0 -0
  161. /package/dist/es/{framer-motion → motion-dom}/dist/es/value/types/utils/single-color-regex.mjs +0 -0
  162. /package/dist/es/{framer-motion/dist/es/utils → motion-utils/dist/es}/clamp.mjs +0 -0
  163. /package/dist/es/{framer-motion → motion-utils}/dist/es/easing/anticipate.mjs +0 -0
  164. /package/dist/es/{framer-motion → motion-utils}/dist/es/easing/back.mjs +0 -0
  165. /package/dist/es/{framer-motion → motion-utils}/dist/es/easing/circ.mjs +0 -0
  166. /package/dist/es/{framer-motion → motion-utils}/dist/es/easing/ease.mjs +0 -0
  167. /package/dist/es/{framer-motion → motion-utils}/dist/es/easing/modifiers/mirror.mjs +0 -0
  168. /package/dist/es/{framer-motion → motion-utils}/dist/es/easing/modifiers/reverse.mjs +0 -0
  169. /package/dist/es/{motion-dom/dist/es → motion-utils/dist/es/easing}/utils/is-bezier-definition.mjs +0 -0
  170. /package/dist/es/{framer-motion → motion-utils}/dist/es/easing/utils/is-easing-array.mjs +0 -0
  171. /package/dist/es/{framer-motion/dist/es/utils → motion-utils/dist/es}/is-numerical-string.mjs +0 -0
  172. /package/dist/es/{framer-motion/dist/es/utils → motion-utils/dist/es}/is-zero-value-string.mjs +0 -0
  173. /package/dist/es/{framer-motion/dist/es/utils → motion-utils/dist/es}/pipe.mjs +0 -0
  174. /package/dist/es/{framer-motion/dist/es/utils → motion-utils/dist/es}/wrap.mjs +0 -0
package/dist/cjs/mini.js CHANGED
@@ -59,187 +59,154 @@ const secondsToMilliseconds = (seconds) => seconds * 1000;
59
59
  /*#__NO_SIDE_EFFECTS__*/
60
60
  const millisecondsToSeconds = (milliseconds) => milliseconds / 1000;
61
61
 
62
- const supportsScrollTimeline = /* @__PURE__ */ memo(() => window.ScrollTimeline !== undefined);
62
+ const wrap = (min, max, v) => {
63
+ const rangeSize = max - min;
64
+ return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
65
+ };
63
66
 
64
- class GroupAnimation {
65
- constructor(animations) {
66
- // Bound to accomodate common `return animation.stop` pattern
67
- this.stop = () => this.runAll("stop");
68
- this.animations = animations.filter(Boolean);
69
- }
70
- get finished() {
71
- return Promise.all(this.animations.map((animation) => animation.finished));
72
- }
73
- /**
74
- * TODO: Filter out cancelled or stopped animations before returning
75
- */
76
- getAll(propName) {
77
- return this.animations[0][propName];
78
- }
79
- setAll(propName, newValue) {
80
- for (let i = 0; i < this.animations.length; i++) {
81
- this.animations[i][propName] = newValue;
82
- }
83
- }
84
- attachTimeline(timeline, fallback) {
85
- const subscriptions = this.animations.map((animation) => {
86
- if (supportsScrollTimeline() && animation.attachTimeline) {
87
- return animation.attachTimeline(timeline);
88
- }
89
- else if (typeof fallback === "function") {
90
- return fallback(animation);
91
- }
92
- });
93
- return () => {
94
- subscriptions.forEach((cancel, i) => {
95
- cancel && cancel();
96
- this.animations[i].stop();
97
- });
98
- };
99
- }
100
- get time() {
101
- return this.getAll("time");
102
- }
103
- set time(time) {
104
- this.setAll("time", time);
105
- }
106
- get speed() {
107
- return this.getAll("speed");
108
- }
109
- set speed(speed) {
110
- this.setAll("speed", speed);
111
- }
112
- get startTime() {
113
- return this.getAll("startTime");
114
- }
115
- get duration() {
116
- let max = 0;
117
- for (let i = 0; i < this.animations.length; i++) {
118
- max = Math.max(max, this.animations[i].duration);
119
- }
120
- return max;
121
- }
122
- runAll(methodName) {
123
- this.animations.forEach((controls) => controls[methodName]());
124
- }
125
- flatten() {
126
- this.runAll("flatten");
127
- }
128
- play() {
129
- this.runAll("play");
130
- }
131
- pause() {
132
- this.runAll("pause");
133
- }
134
- cancel() {
135
- this.runAll("cancel");
67
+ const isEasingArray = (ease) => {
68
+ return Array.isArray(ease) && typeof ease[0] !== "number";
69
+ };
70
+
71
+ function getEasingForSegment(easing, i) {
72
+ return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing;
73
+ }
74
+
75
+ const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number";
76
+
77
+ /*
78
+ Value in range from progress
79
+
80
+ Given a lower limit and an upper limit, we return the value within
81
+ that range as expressed by progress (usually a number from 0 to 1)
82
+
83
+ So progress = 0.5 would change
84
+
85
+ from -------- to
86
+
87
+ to
88
+
89
+ from ---- to
90
+
91
+ E.g. from = 10, to = 20, progress = 0.5 => 15
92
+
93
+ @param [number]: Lower limit of range
94
+ @param [number]: Upper limit of range
95
+ @param [number]: The progress between lower and upper limits expressed 0-1
96
+ @return [number]: Value as calculated from progress within range (not limited within range)
97
+ */
98
+ const mixNumber = (from, to, progress) => {
99
+ return from + (to - from) * progress;
100
+ };
101
+
102
+ const generateLinearEasing = (easing, duration, // as milliseconds
103
+ resolution = 10 // as milliseconds
104
+ ) => {
105
+ let points = "";
106
+ const numPoints = Math.max(Math.round(duration / resolution), 2);
107
+ for (let i = 0; i < numPoints; i++) {
108
+ points += easing(i / (numPoints - 1)) + ", ";
136
109
  }
137
- complete() {
138
- this.runAll("complete");
110
+ return `linear(${points.substring(0, points.length - 2)})`;
111
+ };
112
+
113
+ /**
114
+ * Implement a practical max duration for keyframe generation
115
+ * to prevent infinite loops
116
+ */
117
+ const maxGeneratorDuration = 20000;
118
+ function calcGeneratorDuration(generator) {
119
+ let duration = 0;
120
+ const timeStep = 50;
121
+ let state = generator.next(duration);
122
+ while (!state.done && duration < maxGeneratorDuration) {
123
+ duration += timeStep;
124
+ state = generator.next(duration);
139
125
  }
126
+ return duration >= maxGeneratorDuration ? Infinity : duration;
140
127
  }
141
128
 
142
- class GroupAnimationWithThen extends GroupAnimation {
143
- then(onResolve, _onReject) {
144
- return this.finished.finally(onResolve).then(() => { });
129
+ /**
130
+ * Create a progress => progress easing function from a generator.
131
+ */
132
+ function createGeneratorEasing(options, scale = 100, createGenerator) {
133
+ const generator = createGenerator({ ...options, keyframes: [0, scale] });
134
+ const duration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);
135
+ return {
136
+ type: "keyframes",
137
+ ease: (progress) => {
138
+ return generator.next(duration * progress).value / scale;
139
+ },
140
+ duration: millisecondsToSeconds(duration),
141
+ };
142
+ }
143
+
144
+ function fillOffset(offset, remaining) {
145
+ const min = offset[offset.length - 1];
146
+ for (let i = 1; i <= remaining; i++) {
147
+ const offsetProgress = progress(0, remaining, i);
148
+ offset.push(mixNumber(min, 1, offsetProgress));
145
149
  }
146
150
  }
147
151
 
148
- const isCSSVar = (name) => name.startsWith("--");
149
- const style = {
150
- set: (element, name, value) => {
151
- isCSSVar(name)
152
- ? element.style.setProperty(name, value)
153
- : (element.style[name] = value);
154
- },
155
- get: (element, name) => {
156
- return isCSSVar(name)
157
- ? element.style.getPropertyValue(name)
158
- : element.style[name];
159
- },
160
- };
152
+ function defaultOffset(arr) {
153
+ const offset = [0];
154
+ fillOffset(offset, arr.length - 1);
155
+ return offset;
156
+ }
161
157
 
162
158
  const isNotNull = (value) => value !== null;
163
- function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
159
+ function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe, speed = 1) {
164
160
  const resolvedKeyframes = keyframes.filter(isNotNull);
165
- const index = repeat && repeatType !== "loop" && repeat % 2 === 1
166
- ? 0
167
- : resolvedKeyframes.length - 1;
161
+ const useFirstKeyframe = speed < 0 || (repeat && repeatType !== "loop" && repeat % 2 === 1);
162
+ const index = useFirstKeyframe ? 0 : resolvedKeyframes.length - 1;
168
163
  return !index || finalKeyframe === undefined
169
164
  ? resolvedKeyframes[index]
170
165
  : finalKeyframe;
171
166
  }
172
167
 
173
- const supportsPartialKeyframes = /*@__PURE__*/ memo(() => {
174
- try {
175
- document.createElement("div").animate({ opacity: [1] });
168
+ class WithPromise {
169
+ constructor() {
170
+ this.count = 0;
171
+ this.updateFinished();
176
172
  }
177
- catch (e) {
178
- return false;
173
+ get finished() {
174
+ return this._finished;
179
175
  }
180
- return true;
181
- });
182
-
183
- const pxValues = new Set([
184
- // Border props
185
- "borderWidth",
186
- "borderTopWidth",
187
- "borderRightWidth",
188
- "borderBottomWidth",
189
- "borderLeftWidth",
190
- "borderRadius",
191
- "radius",
192
- "borderTopLeftRadius",
193
- "borderTopRightRadius",
194
- "borderBottomRightRadius",
195
- "borderBottomLeftRadius",
196
- // Positioning props
197
- "width",
198
- "maxWidth",
199
- "height",
200
- "maxHeight",
201
- "top",
202
- "right",
203
- "bottom",
204
- "left",
205
- // Spacing props
206
- "padding",
207
- "paddingTop",
208
- "paddingRight",
209
- "paddingBottom",
210
- "paddingLeft",
211
- "margin",
212
- "marginTop",
213
- "marginRight",
214
- "marginBottom",
215
- "marginLeft",
216
- // Misc
217
- "backgroundPositionX",
218
- "backgroundPositionY",
219
- ]);
220
-
221
- function hydrateKeyframes(element, name, keyframes, pseudoElement) {
222
- if (!Array.isArray(keyframes)) {
223
- keyframes = [keyframes];
176
+ updateFinished() {
177
+ this.count++;
178
+ this._finished = new Promise((resolve) => {
179
+ this.resolve = resolve;
180
+ });
224
181
  }
225
- for (let i = 0; i < keyframes.length; i++) {
226
- if (keyframes[i] === null) {
227
- keyframes[i] =
228
- i === 0 && !pseudoElement
229
- ? style.get(element, name)
230
- : keyframes[i - 1];
231
- }
232
- if (typeof keyframes[i] === "number" && pxValues.has(name)) {
233
- keyframes[i] = keyframes[i] + "px";
234
- }
182
+ notifyFinished() {
183
+ this.resolve();
235
184
  }
236
- if (!pseudoElement && !supportsPartialKeyframes() && keyframes.length < 2) {
237
- keyframes.unshift(style.get(element, name));
185
+ /**
186
+ * Allows the animation to be awaited.
187
+ *
188
+ * @deprecated Use `finished` instead.
189
+ */
190
+ then(onResolve, onReject) {
191
+ return this.finished.then(onResolve, onReject);
238
192
  }
239
- return keyframes;
240
193
  }
241
194
 
242
- const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number";
195
+ function fillWildcards(keyframes) {
196
+ for (let i = 1; i < keyframes.length; i++) {
197
+ keyframes[i] ?? (keyframes[i] = keyframes[i - 1]);
198
+ }
199
+ }
200
+
201
+ const isCSSVar = (name) => name.startsWith("--");
202
+
203
+ function setStyle(element, name, value) {
204
+ isCSSVar(name)
205
+ ? element.style.setProperty(name, value)
206
+ : (element.style[name] = value);
207
+ }
208
+
209
+ const supportsScrollTimeline = /* @__PURE__ */ memo(() => window.ScrollTimeline !== undefined);
243
210
 
244
211
  /**
245
212
  * Add the ability for test suites to manually set support flags
@@ -264,17 +231,6 @@ const supportsLinearEasing = /*@__PURE__*/ memoSupports(() => {
264
231
  return true;
265
232
  }, "linearEasing");
266
233
 
267
- const generateLinearEasing = (easing, duration, // as milliseconds
268
- resolution = 10 // as milliseconds
269
- ) => {
270
- let points = "";
271
- const numPoints = Math.max(Math.round(duration / resolution), 2);
272
- for (let i = 0; i < numPoints; i++) {
273
- points += easing(i / (numPoints - 1)) + ", ";
274
- }
275
- return `linear(${points.substring(0, points.length - 2)})`;
276
- };
277
-
278
234
  const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
279
235
 
280
236
  const supportedWaapiEasing = {
@@ -293,8 +249,10 @@ function mapEasingToNativeEasing(easing, duration) {
293
249
  if (!easing) {
294
250
  return undefined;
295
251
  }
296
- else if (typeof easing === "function" && supportsLinearEasing()) {
297
- return generateLinearEasing(easing, duration);
252
+ else if (typeof easing === "function") {
253
+ return supportsLinearEasing()
254
+ ? generateLinearEasing(easing, duration)
255
+ : "ease-out";
298
256
  }
299
257
  else if (isBezierDefinition(easing)) {
300
258
  return cubicBezierAsString(easing);
@@ -308,7 +266,7 @@ function mapEasingToNativeEasing(easing, duration) {
308
266
  }
309
267
  }
310
268
 
311
- function startWaapiAnimation(element, valueName, keyframes, { delay = 0, duration = 300, repeat = 0, repeatType = "loop", ease = "easeInOut", times, } = {}, pseudoElement = undefined) {
269
+ function startWaapiAnimation(element, valueName, keyframes, { delay = 0, duration = 300, repeat = 0, repeatType = "loop", ease = "easeOut", times, } = {}, pseudoElement = undefined) {
312
270
  const keyframeOptions = {
313
271
  [valueName]: keyframes,
314
272
  };
@@ -320,15 +278,17 @@ function startWaapiAnimation(element, valueName, keyframes, { delay = 0, duratio
320
278
  */
321
279
  if (Array.isArray(easing))
322
280
  keyframeOptions.easing = easing;
323
- const animation = element.animate(keyframeOptions, {
281
+ const options = {
324
282
  delay,
325
283
  duration,
326
284
  easing: !Array.isArray(easing) ? easing : "linear",
327
285
  fill: "both",
328
286
  iterations: repeat + 1,
329
287
  direction: repeatType === "reverse" ? "alternate" : "normal",
330
- pseudoElement,
331
- });
288
+ };
289
+ if (pseudoElement)
290
+ options.pseudoElement = pseudoElement;
291
+ const animation = element.animate(keyframeOptions, options);
332
292
  return animation;
333
293
  }
334
294
 
@@ -337,7 +297,7 @@ function isGenerator(type) {
337
297
  }
338
298
 
339
299
  function applyGeneratorOptions({ type, ...options }) {
340
- if (isGenerator(type)) {
300
+ if (isGenerator(type) && supportsLinearEasing()) {
341
301
  return type.applyToOptions(options);
342
302
  }
343
303
  else {
@@ -347,86 +307,81 @@ function applyGeneratorOptions({ type, ...options }) {
347
307
  return options;
348
308
  }
349
309
 
350
- const animationMaps = new WeakMap();
351
- const animationMapKey = (name, pseudoElement) => `${name}:${pseudoElement}`;
352
- function getAnimationMap(element) {
353
- const map = animationMaps.get(element) || new Map();
354
- animationMaps.set(element, map);
355
- return map;
356
- }
357
310
  /**
358
311
  * NativeAnimation implements AnimationPlaybackControls for the browser's Web Animations API.
359
312
  */
360
- class NativeAnimation {
313
+ class NativeAnimation extends WithPromise {
361
314
  constructor(options) {
362
- /**
363
- * If we already have an animation, we don't need to instantiate one
364
- * and can just use this as a controls interface.
365
- */
366
- if ("animation" in options) {
367
- this.animation = options.animation;
315
+ super();
316
+ this.finishedTime = null;
317
+ this.isStopped = false;
318
+ if (!options)
368
319
  return;
369
- }
370
- const { element, name, keyframes: unresolvedKeyframes, pseudoElement, allowFlatten = false, } = options;
371
- let { transition } = options;
320
+ const { element, name, keyframes, pseudoElement, allowFlatten = false, finalKeyframe, } = options;
372
321
  this.isPseudoElement = Boolean(pseudoElement);
373
322
  this.allowFlatten = allowFlatten;
374
- /**
375
- * Stop any existing animations on the element before reading existing keyframes.
376
- *
377
- * TODO: Check for VisualElement before using animation state. This is a fallback
378
- * for mini animate(). Do this when implementing NativeAnimationExtended.
379
- */
380
- const animationMap = getAnimationMap(element);
381
- const key = animationMapKey(name, pseudoElement || "");
382
- const currentAnimation = animationMap.get(key);
383
- currentAnimation && currentAnimation.stop();
384
- /**
385
- * TODO: If these keyframes aren't correctly hydrated then we want to throw
386
- * run an instant animation.
387
- */
388
- const keyframes = hydrateKeyframes(element, name, unresolvedKeyframes, pseudoElement);
389
- invariant(typeof transition.type !== "string", `animateMini doesn't support "type" as a string. Did you mean to import { spring } from "motion"?`);
390
- transition = applyGeneratorOptions(transition);
323
+ this.options = options;
324
+ invariant(typeof options.type !== "string", `animateMini doesn't support "type" as a string. Did you mean to import { spring } from "motion"?`);
325
+ const transition = applyGeneratorOptions(options);
391
326
  this.animation = startWaapiAnimation(element, name, keyframes, transition, pseudoElement);
392
327
  if (transition.autoplay === false) {
393
328
  this.animation.pause();
394
329
  }
395
- this.removeAnimation = () => animationMap.delete(key);
396
330
  this.animation.onfinish = () => {
331
+ this.finishedTime = this.time;
397
332
  if (!pseudoElement) {
398
- style.set(element, name, getFinalKeyframe(keyframes, transition));
399
- this.cancel();
333
+ const keyframe = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
334
+ if (this.updateMotionValue) {
335
+ this.updateMotionValue(keyframe);
336
+ }
337
+ else {
338
+ /**
339
+ * If we can, we want to commit the final style as set by the user,
340
+ * rather than the computed keyframe value supplied by the animation.
341
+ */
342
+ setStyle(element, name, keyframe);
343
+ }
344
+ this.animation.cancel();
400
345
  }
346
+ this.notifyFinished();
401
347
  };
402
- /**
403
- * TODO: Check for VisualElement before using animation state.
404
- */
405
- animationMap.set(key, this);
406
348
  }
407
349
  play() {
350
+ if (this.isStopped)
351
+ return;
408
352
  this.animation.play();
353
+ if (this.state === "finished") {
354
+ this.updateFinished();
355
+ }
409
356
  }
410
357
  pause() {
411
358
  this.animation.pause();
412
359
  }
413
360
  complete() {
414
- this.animation.finish();
361
+ this.animation.finish?.();
415
362
  }
416
363
  cancel() {
417
364
  try {
418
365
  this.animation.cancel();
419
366
  }
420
367
  catch (e) { }
421
- this.removeAnimation();
422
368
  }
423
369
  stop() {
370
+ if (this.isStopped)
371
+ return;
372
+ this.isStopped = true;
424
373
  const { state } = this;
425
374
  if (state === "idle" || state === "finished") {
426
375
  return;
427
376
  }
428
- this.commitStyles();
429
- this.cancel();
377
+ if (this.updateMotionValue) {
378
+ this.updateMotionValue();
379
+ }
380
+ else {
381
+ this.commitStyles();
382
+ }
383
+ if (!this.isPseudoElement)
384
+ this.cancel();
430
385
  }
431
386
  /**
432
387
  * WAAPI doesn't natively have any interruption capabilities.
@@ -446,13 +401,14 @@ class NativeAnimation {
446
401
  }
447
402
  }
448
403
  get duration() {
449
- const duration = this.animation.effect?.getComputedTiming().duration || 0;
404
+ const duration = this.animation.effect?.getComputedTiming?.().duration || 0;
450
405
  return millisecondsToSeconds(Number(duration));
451
406
  }
452
407
  get time() {
453
408
  return millisecondsToSeconds(Number(this.animation.currentTime) || 0);
454
409
  }
455
410
  set time(newTime) {
411
+ this.finishedTime = null;
456
412
  this.animation.currentTime = secondsToMilliseconds(newTime);
457
413
  }
458
414
  /**
@@ -463,75 +419,175 @@ class NativeAnimation {
463
419
  return this.animation.playbackRate;
464
420
  }
465
421
  set speed(newSpeed) {
422
+ // Allow backwards playback after finishing
423
+ if (newSpeed < 0)
424
+ this.finishedTime = null;
466
425
  this.animation.playbackRate = newSpeed;
467
426
  }
468
427
  get state() {
469
- return this.animation.playState;
428
+ return this.finishedTime !== null
429
+ ? "finished"
430
+ : this.animation.playState;
470
431
  }
471
432
  get startTime() {
472
433
  return Number(this.animation.startTime);
473
434
  }
474
- get finished() {
475
- return this.animation.finished;
435
+ set startTime(newStartTime) {
436
+ this.animation.startTime = newStartTime;
476
437
  }
477
- flatten() {
438
+ /**
439
+ * Attaches a timeline to the animation, for instance the `ScrollTimeline`.
440
+ */
441
+ attachTimeline({ timeline, observe }) {
478
442
  if (this.allowFlatten) {
479
443
  this.animation.effect?.updateTiming({ easing: "linear" });
480
444
  }
445
+ this.animation.onfinish = null;
446
+ if (supportsScrollTimeline()) {
447
+ this.animation.timeline = timeline;
448
+ return noop;
449
+ }
450
+ else {
451
+ return observe(this);
452
+ }
453
+ }
454
+ }
455
+
456
+ class GroupAnimation {
457
+ constructor(animations) {
458
+ // Bound to accomadate common `return animation.stop` pattern
459
+ this.stop = () => this.runAll("stop");
460
+ this.animations = animations.filter(Boolean);
461
+ }
462
+ get finished() {
463
+ return Promise.all(this.animations.map((animation) => animation.finished));
481
464
  }
482
465
  /**
483
- * Attaches a timeline to the animation, for instance the `ScrollTimeline`.
466
+ * TODO: Filter out cancelled or stopped animations before returning
484
467
  */
468
+ getAll(propName) {
469
+ return this.animations[0][propName];
470
+ }
471
+ setAll(propName, newValue) {
472
+ for (let i = 0; i < this.animations.length; i++) {
473
+ this.animations[i][propName] = newValue;
474
+ }
475
+ }
485
476
  attachTimeline(timeline) {
486
- this.animation.timeline = timeline;
487
- this.animation.onfinish = null;
488
- return noop;
477
+ const subscriptions = this.animations.map((animation) => animation.attachTimeline(timeline));
478
+ return () => {
479
+ subscriptions.forEach((cancel, i) => {
480
+ cancel && cancel();
481
+ this.animations[i].stop();
482
+ });
483
+ };
489
484
  }
490
- /**
491
- * Allows the animation to be awaited.
492
- *
493
- * @deprecated Use `finished` instead.
494
- */
495
- then(onResolve, onReject) {
496
- return this.finished.then(onResolve).catch(onReject);
485
+ get time() {
486
+ return this.getAll("time");
487
+ }
488
+ set time(time) {
489
+ this.setAll("time", time);
490
+ }
491
+ get speed() {
492
+ return this.getAll("speed");
493
+ }
494
+ set speed(speed) {
495
+ this.setAll("speed", speed);
496
+ }
497
+ get state() {
498
+ return this.getAll("state");
499
+ }
500
+ get startTime() {
501
+ return this.getAll("startTime");
502
+ }
503
+ get duration() {
504
+ let max = 0;
505
+ for (let i = 0; i < this.animations.length; i++) {
506
+ max = Math.max(max, this.animations[i].duration);
507
+ }
508
+ return max;
509
+ }
510
+ runAll(methodName) {
511
+ this.animations.forEach((controls) => controls[methodName]());
512
+ }
513
+ play() {
514
+ this.runAll("play");
515
+ }
516
+ pause() {
517
+ this.runAll("pause");
518
+ }
519
+ cancel() {
520
+ this.runAll("cancel");
521
+ }
522
+ complete() {
523
+ this.runAll("complete");
524
+ }
525
+ }
526
+
527
+ class GroupAnimationWithThen extends GroupAnimation {
528
+ then(onResolve, _onReject) {
529
+ return this.finished.finally(onResolve).then(() => { });
497
530
  }
498
531
  }
499
532
 
533
+ const animationMaps = new WeakMap();
534
+ const animationMapKey = (name, pseudoElement = "") => `${name}:${pseudoElement}`;
535
+ function getAnimationMap(element) {
536
+ const map = animationMaps.get(element) || new Map();
537
+ animationMaps.set(element, map);
538
+ return map;
539
+ }
540
+
500
541
  function getValueTransition$1(transition, key) {
501
542
  return (transition?.[key] ??
502
543
  transition?.["default"] ??
503
544
  transition);
504
545
  }
505
546
 
506
- /**
507
- * Implement a practical max duration for keyframe generation
508
- * to prevent infinite loops
509
- */
510
- const maxGeneratorDuration = 20000;
511
- function calcGeneratorDuration(generator) {
512
- let duration = 0;
513
- const timeStep = 50;
514
- let state = generator.next(duration);
515
- while (!state.done && duration < maxGeneratorDuration) {
516
- duration += timeStep;
517
- state = generator.next(duration);
518
- }
519
- return duration >= maxGeneratorDuration ? Infinity : duration;
520
- }
547
+ const pxValues = new Set([
548
+ // Border props
549
+ "borderWidth",
550
+ "borderTopWidth",
551
+ "borderRightWidth",
552
+ "borderBottomWidth",
553
+ "borderLeftWidth",
554
+ "borderRadius",
555
+ "radius",
556
+ "borderTopLeftRadius",
557
+ "borderTopRightRadius",
558
+ "borderBottomRightRadius",
559
+ "borderBottomLeftRadius",
560
+ // Positioning props
561
+ "width",
562
+ "maxWidth",
563
+ "height",
564
+ "maxHeight",
565
+ "top",
566
+ "right",
567
+ "bottom",
568
+ "left",
569
+ // Spacing props
570
+ "padding",
571
+ "paddingTop",
572
+ "paddingRight",
573
+ "paddingBottom",
574
+ "paddingLeft",
575
+ "margin",
576
+ "marginTop",
577
+ "marginRight",
578
+ "marginBottom",
579
+ "marginLeft",
580
+ // Misc
581
+ "backgroundPositionX",
582
+ "backgroundPositionY",
583
+ ]);
521
584
 
522
- /**
523
- * Create a progress => progress easing function from a generator.
524
- */
525
- function createGeneratorEasing(options, scale = 100, createGenerator) {
526
- const generator = createGenerator({ ...options, keyframes: [0, scale] });
527
- const duration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);
528
- return {
529
- type: "keyframes",
530
- ease: (progress) => {
531
- return generator.next(duration * progress).value / scale;
532
- },
533
- duration: millisecondsToSeconds(duration),
534
- };
585
+ function applyPxDefaults(keyframes, name) {
586
+ for (let i = 0; i < keyframes.length; i++) {
587
+ if (typeof keyframes[i] === "number" && pxValues.has(name)) {
588
+ keyframes[i] = keyframes[i] + "px";
589
+ }
590
+ }
535
591
  }
536
592
 
537
593
  function resolveElements(elementOrSelector, scope, selectorCache) {
@@ -550,56 +606,11 @@ function resolveElements(elementOrSelector, scope, selectorCache) {
550
606
  return Array.from(elementOrSelector);
551
607
  }
552
608
 
553
- const wrap = (min, max, v) => {
554
- const rangeSize = max - min;
555
- return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
556
- };
557
-
558
- const isEasingArray = (ease) => {
559
- return Array.isArray(ease) && typeof ease[0] !== "number";
560
- };
561
-
562
- function getEasingForSegment(easing, i) {
563
- return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing;
564
- }
565
-
566
- /*
567
- Value in range from progress
568
-
569
- Given a lower limit and an upper limit, we return the value within
570
- that range as expressed by progress (usually a number from 0 to 1)
571
-
572
- So progress = 0.5 would change
573
-
574
- from -------- to
575
-
576
- to
577
-
578
- from ---- to
579
-
580
- E.g. from = 10, to = 20, progress = 0.5 => 15
581
-
582
- @param [number]: Lower limit of range
583
- @param [number]: Upper limit of range
584
- @param [number]: The progress between lower and upper limits expressed 0-1
585
- @return [number]: Value as calculated from progress within range (not limited within range)
586
- */
587
- const mixNumber = (from, to, progress) => {
588
- return from + (to - from) * progress;
589
- };
590
-
591
- function fillOffset(offset, remaining) {
592
- const min = offset[offset.length - 1];
593
- for (let i = 1; i <= remaining; i++) {
594
- const offsetProgress = progress(0, remaining, i);
595
- offset.push(mixNumber(min, 1, offsetProgress));
596
- }
597
- }
598
-
599
- function defaultOffset(arr) {
600
- const offset = [0];
601
- fillOffset(offset, arr.length - 1);
602
- return offset;
609
+ function getComputedStyle(element, name) {
610
+ const computedStyle = window.getComputedStyle(element);
611
+ return isCSSVar(name)
612
+ ? computedStyle.getPropertyValue(name)
613
+ : computedStyle[name];
603
614
  }
604
615
 
605
616
  const isMotionValue = (value) => Boolean(value && value.getVelocity);
@@ -940,7 +951,27 @@ function animateElements(elementOrSelector, keyframes, options, scope) {
940
951
  const elements = resolveElements(elementOrSelector, scope);
941
952
  const numElements = elements.length;
942
953
  invariant(Boolean(numElements), "No valid element provided.");
943
- const animations = [];
954
+ /**
955
+ * WAAPI doesn't support interrupting animations.
956
+ *
957
+ * Therefore, starting animations requires a three-step process:
958
+ * 1. Stop existing animations (write styles to DOM)
959
+ * 2. Resolve keyframes (read styles from DOM)
960
+ * 3. Create new animations (write styles to DOM)
961
+ *
962
+ * The hybrid `animate()` function uses AsyncAnimation to resolve
963
+ * keyframes before creating new animations, which removes style
964
+ * thrashing. Here, we have much stricter filesize constraints.
965
+ * Therefore we do this in a synchronous way that ensures that
966
+ * at least within `animate()` calls there is no style thrashing.
967
+ *
968
+ * In the motion-native-animate-mini-interrupt benchmark this
969
+ * was 80% faster than a single loop.
970
+ */
971
+ const animationDefinitions = [];
972
+ /**
973
+ * Step 1: Build options and stop existing animations (write)
974
+ */
944
975
  for (let i = 0; i < numElements; i++) {
945
976
  const element = elements[i];
946
977
  const elementTransition = { ...options };
@@ -951,20 +982,68 @@ function animateElements(elementOrSelector, keyframes, options, scope) {
951
982
  elementTransition.delay = elementTransition.delay(i, numElements);
952
983
  }
953
984
  for (const valueName in keyframes) {
954
- const valueKeyframes = keyframes[valueName];
985
+ let valueKeyframes = keyframes[valueName];
986
+ if (!Array.isArray(valueKeyframes)) {
987
+ valueKeyframes = [valueKeyframes];
988
+ }
955
989
  const valueOptions = {
956
990
  ...getValueTransition$1(elementTransition, valueName),
957
991
  };
958
992
  valueOptions.duration && (valueOptions.duration = secondsToMilliseconds(valueOptions.duration));
959
993
  valueOptions.delay && (valueOptions.delay = secondsToMilliseconds(valueOptions.delay));
960
- animations.push(new NativeAnimation({
961
- element,
962
- name: valueName,
963
- keyframes: valueKeyframes,
964
- transition: valueOptions,
965
- allowFlatten: !elementTransition.type && !elementTransition.ease,
966
- }));
994
+ /**
995
+ * If there's an existing animation playing on this element then stop it
996
+ * before creating a new one.
997
+ */
998
+ const map = getAnimationMap(element);
999
+ const key = animationMapKey(valueName, valueOptions.pseudoElement || "");
1000
+ const currentAnimation = map.get(key);
1001
+ currentAnimation && currentAnimation.stop();
1002
+ animationDefinitions.push({
1003
+ map,
1004
+ key,
1005
+ unresolvedKeyframes: valueKeyframes,
1006
+ options: {
1007
+ ...valueOptions,
1008
+ element,
1009
+ name: valueName,
1010
+ allowFlatten: !elementTransition.type && !elementTransition.ease,
1011
+ },
1012
+ });
1013
+ }
1014
+ }
1015
+ /**
1016
+ * Step 2: Resolve keyframes (read)
1017
+ */
1018
+ for (let i = 0; i < animationDefinitions.length; i++) {
1019
+ const { unresolvedKeyframes, options: animationOptions } = animationDefinitions[i];
1020
+ const { element, name, pseudoElement } = animationOptions;
1021
+ if (!pseudoElement && unresolvedKeyframes[0] === null) {
1022
+ unresolvedKeyframes[0] = getComputedStyle(element, name);
967
1023
  }
1024
+ fillWildcards(unresolvedKeyframes);
1025
+ applyPxDefaults(unresolvedKeyframes, name);
1026
+ /**
1027
+ * If we only have one keyframe, explicitly read the initial keyframe
1028
+ * from the computed style. This is to ensure consistency with WAAPI behaviour
1029
+ * for restarting animations, for instance .play() after finish, when it
1030
+ * has one vs two keyframes.
1031
+ */
1032
+ if (!pseudoElement && unresolvedKeyframes.length < 2) {
1033
+ unresolvedKeyframes.unshift(getComputedStyle(element, name));
1034
+ }
1035
+ animationOptions.keyframes = unresolvedKeyframes;
1036
+ }
1037
+ /**
1038
+ * Step 3: Create new animations (write)
1039
+ */
1040
+ const animations = [];
1041
+ for (let i = 0; i < animationDefinitions.length; i++) {
1042
+ const { map, key, options: animationOptions } = animationDefinitions[i];
1043
+ const animation = new NativeAnimation(animationOptions);
1044
+ map.set(key, animation);
1045
+ animation.finished.finally(() => map.delete(key));
1046
+ animations.push(animation);
968
1047
  }
969
1048
  return animations;
970
1049
  }