PluginProbe
Elementor Website Builder – more than just a page builder / 3.35.9
Elementor Website Builder – more than just a page builder v3.35.9
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / assets / lib / motion / motion.js

motion.js in Elementor Website Builder – more than just a page builder 3.35.9, at assets/lib/motion/motion.js

7,623 lines 284.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3 typeof define === 'function' && define.amd ? define(['exports'], factory) :
4 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Motion = {}));
5 })(this, (function (exports) { 'use strict';
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 // Adapted from array-move
17 function moveItem([...arr], fromIndex, toIndex) {
18 const startIndex = fromIndex < 0 ? arr.length + fromIndex : fromIndex;
19 if (startIndex >= 0 && startIndex < arr.length) {
20 const endIndex = toIndex < 0 ? arr.length + toIndex : toIndex;
21 const [item] = arr.splice(fromIndex, 1);
22 arr.splice(endIndex, 0, item);
23 }
24 return arr;
25 }
26
27 const clamp = (min, max, v) => {
28 if (v > max)
29 return max;
30 if (v < min)
31 return min;
32 return v;
33 };
34
35 function formatErrorMessage(message, errorCode) {
36 return errorCode
37 ? `${message}. For more information and steps for solving, visit https://motion.dev/troubleshooting/${errorCode}`
38 : message;
39 }
40
41 exports.warning = () => { };
42 exports.invariant = () => { };
43 {
44 exports.warning = (check, message, errorCode) => {
45 if (!check && typeof console !== "undefined") {
46 console.warn(formatErrorMessage(message, errorCode));
47 }
48 };
49 exports.invariant = (check, message, errorCode) => {
50 if (!check) {
51 throw new Error(formatErrorMessage(message, errorCode));
52 }
53 };
54 }
55
56 const MotionGlobalConfig = {};
57
58 /**
59 * Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1"
60 */
61 const isNumericalString = (v) => /^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(v);
62
63 function isObject(value) {
64 return typeof value === "object" && value !== null;
65 }
66
67 /**
68 * Check if the value is a zero value string like "0px" or "0%"
69 */
70 const isZeroValueString = (v) => /^0[^.\s]+$/u.test(v);
71
72 /*#__NO_SIDE_EFFECTS__*/
73 function memo(callback) {
74 let result;
75 return () => {
76 if (result === undefined)
77 result = callback();
78 return result;
79 };
80 }
81
82 /*#__NO_SIDE_EFFECTS__*/
83 const noop = (any) => any;
84
85 /**
86 * Pipe
87 * Compose other transformers to run linearily
88 * pipe(min(20), max(40))
89 * @param {...functions} transformers
90 * @return {function}
91 */
92 const combineFunctions = (a, b) => (v) => b(a(v));
93 const pipe = (...transformers) => transformers.reduce(combineFunctions);
94
95 /*
96 Progress within given range
97
98 Given a lower limit and an upper limit, we return the progress
99 (expressed as a number 0-1) represented by the given value, and
100 limit that progress to within 0-1.
101
102 @param [number]: Lower limit
103 @param [number]: Upper limit
104 @param [number]: Value to find progress within given range
105 @return [number]: Progress of value within range as expressed 0-1
106 */
107 /*#__NO_SIDE_EFFECTS__*/
108 const progress = (from, to, value) => {
109 const toFromDifference = to - from;
110 return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
111 };
112
113 class SubscriptionManager {
114 constructor() {
115 this.subscriptions = [];
116 }
117 add(handler) {
118 addUniqueItem(this.subscriptions, handler);
119 return () => removeItem(this.subscriptions, handler);
120 }
121 notify(a, b, c) {
122 const numSubscriptions = this.subscriptions.length;
123 if (!numSubscriptions)
124 return;
125 if (numSubscriptions === 1) {
126 /**
127 * If there's only a single handler we can just call it without invoking a loop.
128 */
129 this.subscriptions[0](a, b, c);
130 }
131 else {
132 for (let i = 0; i < numSubscriptions; i++) {
133 /**
134 * Check whether the handler exists before firing as it's possible
135 * the subscriptions were modified during this loop running.
136 */
137 const handler = this.subscriptions[i];
138 handler && handler(a, b, c);
139 }
140 }
141 }
142 getSize() {
143 return this.subscriptions.length;
144 }
145 clear() {
146 this.subscriptions.length = 0;
147 }
148 }
149
150 /**
151 * Converts seconds to milliseconds
152 *
153 * @param seconds - Time in seconds.
154 * @return milliseconds - Converted time in milliseconds.
155 */
156 /*#__NO_SIDE_EFFECTS__*/
157 const secondsToMilliseconds = (seconds) => seconds * 1000;
158 /*#__NO_SIDE_EFFECTS__*/
159 const millisecondsToSeconds = (milliseconds) => milliseconds / 1000;
160
161 /*
162 Convert velocity into velocity per second
163
164 @param [number]: Unit per frame
165 @param [number]: Frame duration in ms
166 */
167 function velocityPerSecond(velocity, frameDuration) {
168 return frameDuration ? velocity * (1000 / frameDuration) : 0;
169 }
170
171 const warned = new Set();
172 function hasWarned(message) {
173 return warned.has(message);
174 }
175 function warnOnce(condition, message, errorCode) {
176 if (condition || warned.has(message))
177 return;
178 console.warn(formatErrorMessage(message, errorCode));
179 warned.add(message);
180 }
181
182 const wrap = (min, max, v) => {
183 const rangeSize = max - min;
184 return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
185 };
186
187 /*
188 Bezier function generator
189 This has been modified from Gaëtan Renaudeau's BezierEasing
190 https://github.com/gre/bezier-easing/blob/master/src/index.js
191 https://github.com/gre/bezier-easing/blob/master/LICENSE
192
193 I've removed the newtonRaphsonIterate algo because in benchmarking it
194 wasn't noticeably faster than binarySubdivision, indeed removing it
195 usually improved times, depending on the curve.
196 I also removed the lookup table, as for the added bundle size and loop we're
197 only cutting ~4 or so subdivision iterations. I bumped the max iterations up
198 to 12 to compensate and this still tended to be faster for no perceivable
199 loss in accuracy.
200 Usage
201 const easeOut = cubicBezier(.17,.67,.83,.67);
202 const x = easeOut(0.5); // returns 0.627...
203 */
204 // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
205 const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) *
206 t;
207 const subdivisionPrecision = 0.0000001;
208 const subdivisionMaxIterations = 12;
209 function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
210 let currentX;
211 let currentT;
212 let i = 0;
213 do {
214 currentT = lowerBound + (upperBound - lowerBound) / 2.0;
215 currentX = calcBezier(currentT, mX1, mX2) - x;
216 if (currentX > 0.0) {
217 upperBound = currentT;
218 }
219 else {
220 lowerBound = currentT;
221 }
222 } while (Math.abs(currentX) > subdivisionPrecision &&
223 ++i < subdivisionMaxIterations);
224 return currentT;
225 }
226 function cubicBezier(mX1, mY1, mX2, mY2) {
227 // If this is a linear gradient, return linear easing
228 if (mX1 === mY1 && mX2 === mY2)
229 return noop;
230 const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
231 // If animation is at start/end, return t without easing
232 return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
233 }
234
235 // Accepts an easing function and returns a new one that outputs mirrored values for
236 // the second half of the animation. Turns easeIn into easeInOut.
237 const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2;
238
239 // Accepts an easing function and returns a new one that outputs reversed values.
240 // Turns easeIn into easeOut.
241 const reverseEasing = (easing) => (p) => 1 - easing(1 - p);
242
243 const backOut = /*@__PURE__*/ cubicBezier(0.33, 1.53, 0.69, 0.99);
244 const backIn = /*@__PURE__*/ reverseEasing(backOut);
245 const backInOut = /*@__PURE__*/ mirrorEasing(backIn);
246
247 const anticipate = (p) => (p *= 2) < 1 ? 0.5 * backIn(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1)));
248
249 const circIn = (p) => 1 - Math.sin(Math.acos(p));
250 const circOut = reverseEasing(circIn);
251 const circInOut = mirrorEasing(circIn);
252
253 const easeIn = /*@__PURE__*/ cubicBezier(0.42, 0, 1, 1);
254 const easeOut = /*@__PURE__*/ cubicBezier(0, 0, 0.58, 1);
255 const easeInOut = /*@__PURE__*/ cubicBezier(0.42, 0, 0.58, 1);
256
257 function steps(numSteps, direction = "end") {
258 return (progress) => {
259 progress =
260 direction === "end"
261 ? Math.min(progress, 0.999)
262 : Math.max(progress, 0.001);
263 const expanded = progress * numSteps;
264 const rounded = direction === "end" ? Math.floor(expanded) : Math.ceil(expanded);
265 return clamp(0, 1, rounded / numSteps);
266 };
267 }
268
269 const isEasingArray = (ease) => {
270 return Array.isArray(ease) && typeof ease[0] !== "number";
271 };
272
273 function getEasingForSegment(easing, i) {
274 return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing;
275 }
276
277 const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number";
278
279 const easingLookup = {
280 linear: noop,
281 easeIn,
282 easeInOut,
283 easeOut,
284 circIn,
285 circInOut,
286 circOut,
287 backIn,
288 backInOut,
289 backOut,
290 anticipate,
291 };
292 const isValidEasing = (easing) => {
293 return typeof easing === "string";
294 };
295 const easingDefinitionToFunction = (definition) => {
296 if (isBezierDefinition(definition)) {
297 // If cubic bezier definition, create bezier curve
298 exports.invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`, "cubic-bezier-length");
299 const [x1, y1, x2, y2] = definition;
300 return cubicBezier(x1, y1, x2, y2);
301 }
302 else if (isValidEasing(definition)) {
303 // Else lookup from table
304 exports.invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`, "invalid-easing-type");
305 return easingLookup[definition];
306 }
307 return definition;
308 };
309
310 const stepsOrder = [
311 "setup", // Compute
312 "read", // Read
313 "resolveKeyframes", // Write/Read/Write/Read
314 "preUpdate", // Compute
315 "update", // Compute
316 "preRender", // Compute
317 "render", // Write
318 "postRender", // Compute
319 ];
320
321 const statsBuffer = {
322 value: null,
323 addProjectionMetrics: null,
324 };
325
326 function createRenderStep(runNextFrame, stepName) {
327 /**
328 * We create and reuse two queues, one to queue jobs for the current frame
329 * and one for the next. We reuse to avoid triggering GC after x frames.
330 */
331 let thisFrame = new Set();
332 let nextFrame = new Set();
333 /**
334 * Track whether we're currently processing jobs in this step. This way
335 * we can decide whether to schedule new jobs for this frame or next.
336 */
337 let isProcessing = false;
338 let flushNextFrame = false;
339 /**
340 * A set of processes which were marked keepAlive when scheduled.
341 */
342 const toKeepAlive = new WeakSet();
343 let latestFrameData = {
344 delta: 0.0,
345 timestamp: 0.0,
346 isProcessing: false,
347 };
348 let numCalls = 0;
349 function triggerCallback(callback) {
350 if (toKeepAlive.has(callback)) {
351 step.schedule(callback);
352 runNextFrame();
353 }
354 numCalls++;
355 callback(latestFrameData);
356 }
357 const step = {
358 /**
359 * Schedule a process to run on the next frame.
360 */
361 schedule: (callback, keepAlive = false, immediate = false) => {
362 const addToCurrentFrame = immediate && isProcessing;
363 const queue = addToCurrentFrame ? thisFrame : nextFrame;
364 if (keepAlive)
365 toKeepAlive.add(callback);
366 if (!queue.has(callback))
367 queue.add(callback);
368 return callback;
369 },
370 /**
371 * Cancel the provided callback from running on the next frame.
372 */
373 cancel: (callback) => {
374 nextFrame.delete(callback);
375 toKeepAlive.delete(callback);
376 },
377 /**
378 * Execute all schedule callbacks.
379 */
380 process: (frameData) => {
381 latestFrameData = frameData;
382 /**
383 * If we're already processing we've probably been triggered by a flushSync
384 * inside an existing process. Instead of executing, mark flushNextFrame
385 * as true and ensure we flush the following frame at the end of this one.
386 */
387 if (isProcessing) {
388 flushNextFrame = true;
389 return;
390 }
391 isProcessing = true;
392 [thisFrame, nextFrame] = [nextFrame, thisFrame];
393 // Execute this frame
394 thisFrame.forEach(triggerCallback);
395 /**
396 * If we're recording stats then
397 */
398 if (stepName && statsBuffer.value) {
399 statsBuffer.value.frameloop[stepName].push(numCalls);
400 }
401 numCalls = 0;
402 // Clear the frame so no callbacks remain. This is to avoid
403 // memory leaks should this render step not run for a while.
404 thisFrame.clear();
405 isProcessing = false;
406 if (flushNextFrame) {
407 flushNextFrame = false;
408 step.process(frameData);
409 }
410 },
411 };
412 return step;
413 }
414
415 const maxElapsed$1 = 40;
416 function createRenderBatcher(scheduleNextBatch, allowKeepAlive) {
417 let runNextFrame = false;
418 let useDefaultElapsed = true;
419 const state = {
420 delta: 0.0,
421 timestamp: 0.0,
422 isProcessing: false,
423 };
424 const flagRunNextFrame = () => (runNextFrame = true);
425 const steps = stepsOrder.reduce((acc, key) => {
426 acc[key] = createRenderStep(flagRunNextFrame, allowKeepAlive ? key : undefined);
427 return acc;
428 }, {});
429 const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender, } = steps;
430 const processBatch = () => {
431 const timestamp = MotionGlobalConfig.useManualTiming
432 ? state.timestamp
433 : performance.now();
434 runNextFrame = false;
435 if (!MotionGlobalConfig.useManualTiming) {
436 state.delta = useDefaultElapsed
437 ? 1000 / 60
438 : Math.max(Math.min(timestamp - state.timestamp, maxElapsed$1), 1);
439 }
440 state.timestamp = timestamp;
441 state.isProcessing = true;
442 // Unrolled render loop for better per-frame performance
443 setup.process(state);
444 read.process(state);
445 resolveKeyframes.process(state);
446 preUpdate.process(state);
447 update.process(state);
448 preRender.process(state);
449 render.process(state);
450 postRender.process(state);
451 state.isProcessing = false;
452 if (runNextFrame && allowKeepAlive) {
453 useDefaultElapsed = false;
454 scheduleNextBatch(processBatch);
455 }
456 };
457 const wake = () => {
458 runNextFrame = true;
459 useDefaultElapsed = true;
460 if (!state.isProcessing) {
461 scheduleNextBatch(processBatch);
462 }
463 };
464 const schedule = stepsOrder.reduce((acc, key) => {
465 const step = steps[key];
466 acc[key] = (process, keepAlive = false, immediate = false) => {
467 if (!runNextFrame)
468 wake();
469 return step.schedule(process, keepAlive, immediate);
470 };
471 return acc;
472 }, {});
473 const cancel = (process) => {
474 for (let i = 0; i < stepsOrder.length; i++) {
475 steps[stepsOrder[i]].cancel(process);
476 }
477 };
478 return { schedule, cancel, state, steps };
479 }
480
481 const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true);
482
483 let now;
484 function clearTime() {
485 now = undefined;
486 }
487 /**
488 * An eventloop-synchronous alternative to performance.now().
489 *
490 * Ensures that time measurements remain consistent within a synchronous context.
491 * Usually calling performance.now() twice within the same synchronous context
492 * will return different values which isn't useful for animations when we're usually
493 * trying to sync animations to the same frame.
494 */
495 const time = {
496 now: () => {
497 if (now === undefined) {
498 time.set(frameData.isProcessing || MotionGlobalConfig.useManualTiming
499 ? frameData.timestamp
500 : performance.now());
501 }
502 return now;
503 },
504 set: (newTime) => {
505 now = newTime;
506 queueMicrotask(clearTime);
507 },
508 };
509
510 const activeAnimations = {
511 layout: 0,
512 mainThread: 0,
513 waapi: 0,
514 };
515
516 const checkStringStartsWith = (token) => (key) => typeof key === "string" && key.startsWith(token);
517 const isCSSVariableName =
518 /*@__PURE__*/ checkStringStartsWith("--");
519 const startsAsVariableToken =
520 /*@__PURE__*/ checkStringStartsWith("var(--");
521 const isCSSVariableToken = (value) => {
522 const startsWithToken = startsAsVariableToken(value);
523 if (!startsWithToken)
524 return false;
525 // Ensure any comments are stripped from the value as this can harm performance of the regex.
526 return singleCssVariableRegex.test(value.split("/*")[0].trim());
527 };
528 const singleCssVariableRegex = /var\(--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)$/iu;
529
530 const number = {
531 test: (v) => typeof v === "number",
532 parse: parseFloat,
533 transform: (v) => v,
534 };
535 const alpha = {
536 ...number,
537 transform: (v) => clamp(0, 1, v),
538 };
539 const scale = {
540 ...number,
541 default: 1,
542 };
543
544 // If this number is a decimal, make it just five decimal places
545 // to avoid exponents
546 const sanitize = (v) => Math.round(v * 100000) / 100000;
547
548 const floatRegex = /-?(?:\d+(?:\.\d+)?|\.\d+)/gu;
549
550 function isNullish(v) {
551 return v == null;
552 }
553
554 const singleColorRegex = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))$/iu;
555
556 /**
557 * Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000,
558 * but false if a number or multiple colors
559 */
560 const isColorString = (type, testProp) => (v) => {
561 return Boolean((typeof v === "string" &&
562 singleColorRegex.test(v) &&
563 v.startsWith(type)) ||
564 (testProp &&
565 !isNullish(v) &&
566 Object.prototype.hasOwnProperty.call(v, testProp)));
567 };
568 const splitColor = (aName, bName, cName) => (v) => {
569 if (typeof v !== "string")
570 return v;
571 const [a, b, c, alpha] = v.match(floatRegex);
572 return {
573 [aName]: parseFloat(a),
574 [bName]: parseFloat(b),
575 [cName]: parseFloat(c),
576 alpha: alpha !== undefined ? parseFloat(alpha) : 1,
577 };
578 };
579
580 const clampRgbUnit = (v) => clamp(0, 255, v);
581 const rgbUnit = {
582 ...number,
583 transform: (v) => Math.round(clampRgbUnit(v)),
584 };
585 const rgba = {
586 test: /*@__PURE__*/ isColorString("rgb", "red"),
587 parse: /*@__PURE__*/ splitColor("red", "green", "blue"),
588 transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" +
589 rgbUnit.transform(red) +
590 ", " +
591 rgbUnit.transform(green) +
592 ", " +
593 rgbUnit.transform(blue) +
594 ", " +
595 sanitize(alpha.transform(alpha$1)) +
596 ")",
597 };
598
599 function parseHex(v) {
600 let r = "";
601 let g = "";
602 let b = "";
603 let a = "";
604 // If we have 6 characters, ie #FF0000
605 if (v.length > 5) {
606 r = v.substring(1, 3);
607 g = v.substring(3, 5);
608 b = v.substring(5, 7);
609 a = v.substring(7, 9);
610 // Or we have 3 characters, ie #F00
611 }
612 else {
613 r = v.substring(1, 2);
614 g = v.substring(2, 3);
615 b = v.substring(3, 4);
616 a = v.substring(4, 5);
617 r += r;
618 g += g;
619 b += b;
620 a += a;
621 }
622 return {
623 red: parseInt(r, 16),
624 green: parseInt(g, 16),
625 blue: parseInt(b, 16),
626 alpha: a ? parseInt(a, 16) / 255 : 1,
627 };
628 }
629 const hex = {
630 test: /*@__PURE__*/ isColorString("#"),
631 parse: parseHex,
632 transform: rgba.transform,
633 };
634
635 /*#__NO_SIDE_EFFECTS__*/
636 const createUnitType = (unit) => ({
637 test: (v) => typeof v === "string" && v.endsWith(unit) && v.split(" ").length === 1,
638 parse: parseFloat,
639 transform: (v) => `${v}${unit}`,
640 });
641 const degrees = /*@__PURE__*/ createUnitType("deg");
642 const percent = /*@__PURE__*/ createUnitType("%");
643 const px = /*@__PURE__*/ createUnitType("px");
644 const vh = /*@__PURE__*/ createUnitType("vh");
645 const vw = /*@__PURE__*/ createUnitType("vw");
646 const progressPercentage = /*@__PURE__*/ (() => ({
647 ...percent,
648 parse: (v) => percent.parse(v) / 100,
649 transform: (v) => percent.transform(v * 100),
650 }))();
651
652 const hsla = {
653 test: /*@__PURE__*/ isColorString("hsl", "hue"),
654 parse: /*@__PURE__*/ splitColor("hue", "saturation", "lightness"),
655 transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => {
656 return ("hsla(" +
657 Math.round(hue) +
658 ", " +
659 percent.transform(sanitize(saturation)) +
660 ", " +
661 percent.transform(sanitize(lightness)) +
662 ", " +
663 sanitize(alpha.transform(alpha$1)) +
664 ")");
665 },
666 };
667
668 const color = {
669 test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v),
670 parse: (v) => {
671 if (rgba.test(v)) {
672 return rgba.parse(v);
673 }
674 else if (hsla.test(v)) {
675 return hsla.parse(v);
676 }
677 else {
678 return hex.parse(v);
679 }
680 },
681 transform: (v) => {
682 return typeof v === "string"
683 ? v
684 : v.hasOwnProperty("red")
685 ? rgba.transform(v)
686 : hsla.transform(v);
687 },
688 getAnimatableNone: (v) => {
689 const parsed = color.parse(v);
690 parsed.alpha = 0;
691 return color.transform(parsed);
692 },
693 };
694
695 const colorRegex = /(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))/giu;
696
697 function test(v) {
698 return (isNaN(v) &&
699 typeof v === "string" &&
700 (v.match(floatRegex)?.length || 0) +
701 (v.match(colorRegex)?.length || 0) >
702 0);
703 }
704 const NUMBER_TOKEN = "number";
705 const COLOR_TOKEN = "color";
706 const VAR_TOKEN = "var";
707 const VAR_FUNCTION_TOKEN = "var(";
708 const SPLIT_TOKEN = "${}";
709 // this regex consists of the `singleCssVariableRegex|rgbHSLValueRegex|digitRegex`
710 const complexRegex = /var\s*\(\s*--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)|#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\)|-?(?:\d+(?:\.\d+)?|\.\d+)/giu;
711 function analyseComplexValue(value) {
712 const originalValue = value.toString();
713 const values = [];
714 const indexes = {
715 color: [],
716 number: [],
717 var: [],
718 };
719 const types = [];
720 let i = 0;
721 const tokenised = originalValue.replace(complexRegex, (parsedValue) => {
722 if (color.test(parsedValue)) {
723 indexes.color.push(i);
724 types.push(COLOR_TOKEN);
725 values.push(color.parse(parsedValue));
726 }
727 else if (parsedValue.startsWith(VAR_FUNCTION_TOKEN)) {
728 indexes.var.push(i);
729 types.push(VAR_TOKEN);
730 values.push(parsedValue);
731 }
732 else {
733 indexes.number.push(i);
734 types.push(NUMBER_TOKEN);
735 values.push(parseFloat(parsedValue));
736 }
737 ++i;
738 return SPLIT_TOKEN;
739 });
740 const split = tokenised.split(SPLIT_TOKEN);
741 return { values, split, indexes, types };
742 }
743 function parseComplexValue(v) {
744 return analyseComplexValue(v).values;
745 }
746 function createTransformer(source) {
747 const { split, types } = analyseComplexValue(source);
748 const numSections = split.length;
749 return (v) => {
750 let output = "";
751 for (let i = 0; i < numSections; i++) {
752 output += split[i];
753 if (v[i] !== undefined) {
754 const type = types[i];
755 if (type === NUMBER_TOKEN) {
756 output += sanitize(v[i]);
757 }
758 else if (type === COLOR_TOKEN) {
759 output += color.transform(v[i]);
760 }
761 else {
762 output += v[i];
763 }
764 }
765 }
766 return output;
767 };
768 }
769 const convertNumbersToZero = (v) => typeof v === "number" ? 0 : color.test(v) ? color.getAnimatableNone(v) : v;
770 function getAnimatableNone$1(v) {
771 const parsed = parseComplexValue(v);
772 const transformer = createTransformer(v);
773 return transformer(parsed.map(convertNumbersToZero));
774 }
775 const complex = {
776 test,
777 parse: parseComplexValue,
778 createTransformer,
779 getAnimatableNone: getAnimatableNone$1,
780 };
781
782 // Adapted from https://gist.github.com/mjackson/5311256
783 function hueToRgb(p, q, t) {
784 if (t < 0)
785 t += 1;
786 if (t > 1)
787 t -= 1;
788 if (t < 1 / 6)
789 return p + (q - p) * 6 * t;
790 if (t < 1 / 2)
791 return q;
792 if (t < 2 / 3)
793 return p + (q - p) * (2 / 3 - t) * 6;
794 return p;
795 }
796 function hslaToRgba({ hue, saturation, lightness, alpha }) {
797 hue /= 360;
798 saturation /= 100;
799 lightness /= 100;
800 let red = 0;
801 let green = 0;
802 let blue = 0;
803 if (!saturation) {
804 red = green = blue = lightness;
805 }
806 else {
807 const q = lightness < 0.5
808 ? lightness * (1 + saturation)
809 : lightness + saturation - lightness * saturation;
810 const p = 2 * lightness - q;
811 red = hueToRgb(p, q, hue + 1 / 3);
812 green = hueToRgb(p, q, hue);
813 blue = hueToRgb(p, q, hue - 1 / 3);
814 }
815 return {
816 red: Math.round(red * 255),
817 green: Math.round(green * 255),
818 blue: Math.round(blue * 255),
819 alpha,
820 };
821 }
822
823 function mixImmediate(a, b) {
824 return (p) => (p > 0 ? b : a);
825 }
826
827 /*
828 Value in range from progress
829
830 Given a lower limit and an upper limit, we return the value within
831 that range as expressed by progress (usually a number from 0 to 1)
832
833 So progress = 0.5 would change
834
835 from -------- to
836
837 to
838
839 from ---- to
840
841 E.g. from = 10, to = 20, progress = 0.5 => 15
842
843 @param [number]: Lower limit of range
844 @param [number]: Upper limit of range
845 @param [number]: The progress between lower and upper limits expressed 0-1
846 @return [number]: Value as calculated from progress within range (not limited within range)
847 */
848 const mixNumber$1 = (from, to, progress) => {
849 return from + (to - from) * progress;
850 };
851
852 // Linear color space blending
853 // Explained https://www.youtube.com/watch?v=LKnqECcg6Gw
854 // Demonstrated http://codepen.io/osublake/pen/xGVVaN
855 const mixLinearColor = (from, to, v) => {
856 const fromExpo = from * from;
857 const expo = v * (to * to - fromExpo) + fromExpo;
858 return expo < 0 ? 0 : Math.sqrt(expo);
859 };
860 const colorTypes = [hex, rgba, hsla];
861 const getColorType = (v) => colorTypes.find((type) => type.test(v));
862 function asRGBA(color) {
863 const type = getColorType(color);
864 exports.warning(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`, "color-not-animatable");
865 if (!Boolean(type))
866 return false;
867 let model = type.parse(color);
868 if (type === hsla) {
869 // TODO Remove this cast - needed since Motion's stricter typing
870 model = hslaToRgba(model);
871 }
872 return model;
873 }
874 const mixColor = (from, to) => {
875 const fromRGBA = asRGBA(from);
876 const toRGBA = asRGBA(to);
877 if (!fromRGBA || !toRGBA) {
878 return mixImmediate(from, to);
879 }
880 const blended = { ...fromRGBA };
881 return (v) => {
882 blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v);
883 blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v);
884 blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v);
885 blended.alpha = mixNumber$1(fromRGBA.alpha, toRGBA.alpha, v);
886 return rgba.transform(blended);
887 };
888 };
889
890 const invisibleValues = new Set(["none", "hidden"]);
891 /**
892 * Returns a function that, when provided a progress value between 0 and 1,
893 * will return the "none" or "hidden" string only when the progress is that of
894 * the origin or target.
895 */
896 function mixVisibility(origin, target) {
897 if (invisibleValues.has(origin)) {
898 return (p) => (p <= 0 ? origin : target);
899 }
900 else {
901 return (p) => (p >= 1 ? target : origin);
902 }
903 }
904
905 function mixNumber(a, b) {
906 return (p) => mixNumber$1(a, b, p);
907 }
908 function getMixer(a) {
909 if (typeof a === "number") {
910 return mixNumber;
911 }
912 else if (typeof a === "string") {
913 return isCSSVariableToken(a)
914 ? mixImmediate
915 : color.test(a)
916 ? mixColor
917 : mixComplex;
918 }
919 else if (Array.isArray(a)) {
920 return mixArray;
921 }
922 else if (typeof a === "object") {
923 return color.test(a) ? mixColor : mixObject;
924 }
925 return mixImmediate;
926 }
927 function mixArray(a, b) {
928 const output = [...a];
929 const numValues = output.length;
930 const blendValue = a.map((v, i) => getMixer(v)(v, b[i]));
931 return (p) => {
932 for (let i = 0; i < numValues; i++) {
933 output[i] = blendValue[i](p);
934 }
935 return output;
936 };
937 }
938 function mixObject(a, b) {
939 const output = { ...a, ...b };
940 const blendValue = {};
941 for (const key in output) {
942 if (a[key] !== undefined && b[key] !== undefined) {
943 blendValue[key] = getMixer(a[key])(a[key], b[key]);
944 }
945 }
946 return (v) => {
947 for (const key in blendValue) {
948 output[key] = blendValue[key](v);
949 }
950 return output;
951 };
952 }
953 function matchOrder(origin, target) {
954 const orderedOrigin = [];
955 const pointers = { color: 0, var: 0, number: 0 };
956 for (let i = 0; i < target.values.length; i++) {
957 const type = target.types[i];
958 const originIndex = origin.indexes[type][pointers[type]];
959 const originValue = origin.values[originIndex] ?? 0;
960 orderedOrigin[i] = originValue;
961 pointers[type]++;
962 }
963 return orderedOrigin;
964 }
965 const mixComplex = (origin, target) => {
966 const template = complex.createTransformer(target);
967 const originStats = analyseComplexValue(origin);
968 const targetStats = analyseComplexValue(target);
969 const canInterpolate = originStats.indexes.var.length === targetStats.indexes.var.length &&
970 originStats.indexes.color.length === targetStats.indexes.color.length &&
971 originStats.indexes.number.length >= targetStats.indexes.number.length;
972 if (canInterpolate) {
973 if ((invisibleValues.has(origin) &&
974 !targetStats.values.length) ||
975 (invisibleValues.has(target) &&
976 !originStats.values.length)) {
977 return mixVisibility(origin, target);
978 }
979 return pipe(mixArray(matchOrder(originStats, targetStats), targetStats.values), template);
980 }
981 else {
982 exports.warning(true, `Complex values '${origin}' and '${target}' too different to mix. Ensure all colors are of the same type, and that each contains the same quantity of number and color values. Falling back to instant transition.`, "complex-values-different");
983 return mixImmediate(origin, target);
984 }
985 };
986
987 function mix(from, to, p) {
988 if (typeof from === "number" &&
989 typeof to === "number" &&
990 typeof p === "number") {
991 return mixNumber$1(from, to, p);
992 }
993 const mixer = getMixer(from);
994 return mixer(from, to);
995 }
996
997 const frameloopDriver = (update) => {
998 const passTimestamp = ({ timestamp }) => update(timestamp);
999 return {
1000 start: (keepAlive = true) => frame.update(passTimestamp, keepAlive),
1001 stop: () => cancelFrame(passTimestamp),
1002 /**
1003 * If we're processing this frame we can use the
1004 * framelocked timestamp to keep things in sync.
1005 */
1006 now: () => (frameData.isProcessing ? frameData.timestamp : time.now()),
1007 };
1008 };
1009
1010 const generateLinearEasing = (easing, duration, // as milliseconds
1011 resolution = 10 // as milliseconds
1012 ) => {
1013 let points = "";
1014 const numPoints = Math.max(Math.round(duration / resolution), 2);
1015 for (let i = 0; i < numPoints; i++) {
1016 points += Math.round(easing(i / (numPoints - 1)) * 10000) / 10000 + ", ";
1017 }
1018 return `linear(${points.substring(0, points.length - 2)})`;
1019 };
1020
1021 /**
1022 * Implement a practical max duration for keyframe generation
1023 * to prevent infinite loops
1024 */
1025 const maxGeneratorDuration = 20000;
1026 function calcGeneratorDuration(generator) {
1027 let duration = 0;
1028 const timeStep = 50;
1029 let state = generator.next(duration);
1030 while (!state.done && duration < maxGeneratorDuration) {
1031 duration += timeStep;
1032 state = generator.next(duration);
1033 }
1034 return duration >= maxGeneratorDuration ? Infinity : duration;
1035 }
1036
1037 /**
1038 * Create a progress => progress easing function from a generator.
1039 */
1040 function createGeneratorEasing(options, scale = 100, createGenerator) {
1041 const generator = createGenerator({ ...options, keyframes: [0, scale] });
1042 const duration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);
1043 return {
1044 type: "keyframes",
1045 ease: (progress) => {
1046 return generator.next(duration * progress).value / scale;
1047 },
1048 duration: millisecondsToSeconds(duration),
1049 };
1050 }
1051
1052 const velocitySampleDuration = 5; // ms
1053 function calcGeneratorVelocity(resolveValue, t, current) {
1054 const prevT = Math.max(t - velocitySampleDuration, 0);
1055 return velocityPerSecond(current - resolveValue(prevT), t - prevT);
1056 }
1057
1058 const springDefaults = {
1059 // Default spring physics
1060 stiffness: 100,
1061 damping: 10,
1062 mass: 1.0,
1063 velocity: 0.0,
1064 // Default duration/bounce-based options
1065 duration: 800, // in ms
1066 bounce: 0.3,
1067 visualDuration: 0.3, // in seconds
1068 // Rest thresholds
1069 restSpeed: {
1070 granular: 0.01,
1071 default: 2,
1072 },
1073 restDelta: {
1074 granular: 0.005,
1075 default: 0.5,
1076 },
1077 // Limits
1078 minDuration: 0.01, // in seconds
1079 maxDuration: 10.0, // in seconds
1080 minDamping: 0.05,
1081 maxDamping: 1,
1082 };
1083
1084 const safeMin = 0.001;
1085 function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, velocity = springDefaults.velocity, mass = springDefaults.mass, }) {
1086 let envelope;
1087 let derivative;
1088 exports.warning(duration <= secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less", "spring-duration-limit");
1089 let dampingRatio = 1 - bounce;
1090 /**
1091 * Restrict dampingRatio and duration to within acceptable ranges.
1092 */
1093 dampingRatio = clamp(springDefaults.minDamping, springDefaults.maxDamping, dampingRatio);
1094 duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, millisecondsToSeconds(duration));
1095 if (dampingRatio < 1) {
1096 /**
1097 * Underdamped spring
1098 */
1099 envelope = (undampedFreq) => {
1100 const exponentialDecay = undampedFreq * dampingRatio;
1101 const delta = exponentialDecay * duration;
1102 const a = exponentialDecay - velocity;
1103 const b = calcAngularFreq(undampedFreq, dampingRatio);
1104 const c = Math.exp(-delta);
1105 return safeMin - (a / b) * c;
1106 };
1107 derivative = (undampedFreq) => {
1108 const exponentialDecay = undampedFreq * dampingRatio;
1109 const delta = exponentialDecay * duration;
1110 const d = delta * velocity + velocity;
1111 const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
1112 const f = Math.exp(-delta);
1113 const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
1114 const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
1115 return (factor * ((d - e) * f)) / g;
1116 };
1117 }
1118 else {
1119 /**
1120 * Critically-damped spring
1121 */
1122 envelope = (undampedFreq) => {
1123 const a = Math.exp(-undampedFreq * duration);
1124 const b = (undampedFreq - velocity) * duration + 1;
1125 return -safeMin + a * b;
1126 };
1127 derivative = (undampedFreq) => {
1128 const a = Math.exp(-undampedFreq * duration);
1129 const b = (velocity - undampedFreq) * (duration * duration);
1130 return a * b;
1131 };
1132 }
1133 const initialGuess = 5 / duration;
1134 const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
1135 duration = secondsToMilliseconds(duration);
1136 if (isNaN(undampedFreq)) {
1137 return {
1138 stiffness: springDefaults.stiffness,
1139 damping: springDefaults.damping,
1140 duration,
1141 };
1142 }
1143 else {
1144 const stiffness = Math.pow(undampedFreq, 2) * mass;
1145 return {
1146 stiffness,
1147 damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
1148 duration,
1149 };
1150 }
1151 }
1152 const rootIterations = 12;
1153 function approximateRoot(envelope, derivative, initialGuess) {
1154 let result = initialGuess;
1155 for (let i = 1; i < rootIterations; i++) {
1156 result = result - envelope(result) / derivative(result);
1157 }
1158 return result;
1159 }
1160 function calcAngularFreq(undampedFreq, dampingRatio) {
1161 return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
1162 }
1163
1164 const durationKeys = ["duration", "bounce"];
1165 const physicsKeys = ["stiffness", "damping", "mass"];
1166 function isSpringType(options, keys) {
1167 return keys.some((key) => options[key] !== undefined);
1168 }
1169 function getSpringOptions(options) {
1170 let springOptions = {
1171 velocity: springDefaults.velocity,
1172 stiffness: springDefaults.stiffness,
1173 damping: springDefaults.damping,
1174 mass: springDefaults.mass,
1175 isResolvedFromDuration: false,
1176 ...options,
1177 };
1178 // stiffness/damping/mass overrides duration/bounce
1179 if (!isSpringType(options, physicsKeys) &&
1180 isSpringType(options, durationKeys)) {
1181 if (options.visualDuration) {
1182 const visualDuration = options.visualDuration;
1183 const root = (2 * Math.PI) / (visualDuration * 1.2);
1184 const stiffness = root * root;
1185 const damping = 2 *
1186 clamp(0.05, 1, 1 - (options.bounce || 0)) *
1187 Math.sqrt(stiffness);
1188 springOptions = {
1189 ...springOptions,
1190 mass: springDefaults.mass,
1191 stiffness,
1192 damping,
1193 };
1194 }
1195 else {
1196 const derived = findSpring(options);
1197 springOptions = {
1198 ...springOptions,
1199 ...derived,
1200 mass: springDefaults.mass,
1201 };
1202 springOptions.isResolvedFromDuration = true;
1203 }
1204 }
1205 return springOptions;
1206 }
1207 function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) {
1208 const options = typeof optionsOrVisualDuration !== "object"
1209 ? {
1210 visualDuration: optionsOrVisualDuration,
1211 keyframes: [0, 1],
1212 bounce,
1213 }
1214 : optionsOrVisualDuration;
1215 let { restSpeed, restDelta } = options;
1216 const origin = options.keyframes[0];
1217 const target = options.keyframes[options.keyframes.length - 1];
1218 /**
1219 * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
1220 * to reduce GC during animation.
1221 */
1222 const state = { done: false, value: origin };
1223 const { stiffness, damping, mass, duration, velocity, isResolvedFromDuration, } = getSpringOptions({
1224 ...options,
1225 velocity: -millisecondsToSeconds(options.velocity || 0),
1226 });
1227 const initialVelocity = velocity || 0.0;
1228 const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
1229 const initialDelta = target - origin;
1230 const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass));
1231 /**
1232 * If we're working on a granular scale, use smaller defaults for determining
1233 * when the spring is finished.
1234 *
1235 * These defaults have been selected emprically based on what strikes a good
1236 * ratio between feeling good and finishing as soon as changes are imperceptible.
1237 */
1238 const isGranularScale = Math.abs(initialDelta) < 5;
1239 restSpeed || (restSpeed = isGranularScale
1240 ? springDefaults.restSpeed.granular
1241 : springDefaults.restSpeed.default);
1242 restDelta || (restDelta = isGranularScale
1243 ? springDefaults.restDelta.granular
1244 : springDefaults.restDelta.default);
1245 let resolveSpring;
1246 if (dampingRatio < 1) {
1247 const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
1248 // Underdamped spring
1249 resolveSpring = (t) => {
1250 const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1251 return (target -
1252 envelope *
1253 (((initialVelocity +
1254 dampingRatio * undampedAngularFreq * initialDelta) /
1255 angularFreq) *
1256 Math.sin(angularFreq * t) +
1257 initialDelta * Math.cos(angularFreq * t)));
1258 };
1259 }
1260 else if (dampingRatio === 1) {
1261 // Critically damped spring
1262 resolveSpring = (t) => target -
1263 Math.exp(-undampedAngularFreq * t) *
1264 (initialDelta +
1265 (initialVelocity + undampedAngularFreq * initialDelta) * t);
1266 }
1267 else {
1268 // Overdamped spring
1269 const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
1270 resolveSpring = (t) => {
1271 const envelope = Math.exp(-dampingRatio * undampedAngularFreq * t);
1272 // When performing sinh or cosh values can hit Infinity so we cap them here
1273 const freqForT = Math.min(dampedAngularFreq * t, 300);
1274 return (target -
1275 (envelope *
1276 ((initialVelocity +
1277 dampingRatio * undampedAngularFreq * initialDelta) *
1278 Math.sinh(freqForT) +
1279 dampedAngularFreq *
1280 initialDelta *
1281 Math.cosh(freqForT))) /
1282 dampedAngularFreq);
1283 };
1284 }
1285 const generator = {
1286 calculatedDuration: isResolvedFromDuration ? duration || null : null,
1287 next: (t) => {
1288 const current = resolveSpring(t);
1289 if (!isResolvedFromDuration) {
1290 let currentVelocity = t === 0 ? initialVelocity : 0.0;
1291 /**
1292 * We only need to calculate velocity for under-damped springs
1293 * as over- and critically-damped springs can't overshoot, so
1294 * checking only for displacement is enough.
1295 */
1296 if (dampingRatio < 1) {
1297 currentVelocity =
1298 t === 0
1299 ? secondsToMilliseconds(initialVelocity)
1300 : calcGeneratorVelocity(resolveSpring, t, current);
1301 }
1302 const isBelowVelocityThreshold = Math.abs(currentVelocity) <= restSpeed;
1303 const isBelowDisplacementThreshold = Math.abs(target - current) <= restDelta;
1304 state.done =
1305 isBelowVelocityThreshold && isBelowDisplacementThreshold;
1306 }
1307 else {
1308 state.done = t >= duration;
1309 }
1310 state.value = state.done ? target : current;
1311 return state;
1312 },
1313 toString: () => {
1314 const calculatedDuration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);
1315 const easing = generateLinearEasing((progress) => generator.next(calculatedDuration * progress).value, calculatedDuration, 30);
1316 return calculatedDuration + "ms " + easing;
1317 },
1318 toTransition: () => { },
1319 };
1320 return generator;
1321 }
1322 spring.applyToOptions = (options) => {
1323 const generatorOptions = createGeneratorEasing(options, 100, spring);
1324 options.ease = generatorOptions.ease;
1325 options.duration = secondsToMilliseconds(generatorOptions.duration);
1326 options.type = "keyframes";
1327 return options;
1328 };
1329
1330 function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
1331 const origin = keyframes[0];
1332 const state = {
1333 done: false,
1334 value: origin,
1335 };
1336 const isOutOfBounds = (v) => (min !== undefined && v < min) || (max !== undefined && v > max);
1337 const nearestBoundary = (v) => {
1338 if (min === undefined)
1339 return max;
1340 if (max === undefined)
1341 return min;
1342 return Math.abs(min - v) < Math.abs(max - v) ? min : max;
1343 };
1344 let amplitude = power * velocity;
1345 const ideal = origin + amplitude;
1346 const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
1347 /**
1348 * If the target has changed we need to re-calculate the amplitude, otherwise
1349 * the animation will start from the wrong position.
1350 */
1351 if (target !== ideal)
1352 amplitude = target - origin;
1353 const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant);
1354 const calcLatest = (t) => target + calcDelta(t);
1355 const applyFriction = (t) => {
1356 const delta = calcDelta(t);
1357 const latest = calcLatest(t);
1358 state.done = Math.abs(delta) <= restDelta;
1359 state.value = state.done ? target : latest;
1360 };
1361 /**
1362 * Ideally this would resolve for t in a stateless way, we could
1363 * do that by always precalculating the animation but as we know
1364 * this will be done anyway we can assume that spring will
1365 * be discovered during that.
1366 */
1367 let timeReachedBoundary;
1368 let spring$1;
1369 const checkCatchBoundary = (t) => {
1370 if (!isOutOfBounds(state.value))
1371 return;
1372 timeReachedBoundary = t;
1373 spring$1 = spring({
1374 keyframes: [state.value, nearestBoundary(state.value)],
1375 velocity: calcGeneratorVelocity(calcLatest, t, state.value), // TODO: This should be passing * 1000
1376 damping: bounceDamping,
1377 stiffness: bounceStiffness,
1378 restDelta,
1379 restSpeed,
1380 });
1381 };
1382 checkCatchBoundary(0);
1383 return {
1384 calculatedDuration: null,
1385 next: (t) => {
1386 /**
1387 * We need to resolve the friction to figure out if we need a
1388 * spring but we don't want to do this twice per frame. So here
1389 * we flag if we updated for this frame and later if we did
1390 * we can skip doing it again.
1391 */
1392 let hasUpdatedFrame = false;
1393 if (!spring$1 && timeReachedBoundary === undefined) {
1394 hasUpdatedFrame = true;
1395 applyFriction(t);
1396 checkCatchBoundary(t);
1397 }
1398 /**
1399 * If we have a spring and the provided t is beyond the moment the friction
1400 * animation crossed the min/max boundary, use the spring.
1401 */
1402 if (timeReachedBoundary !== undefined && t >= timeReachedBoundary) {
1403 return spring$1.next(t - timeReachedBoundary);
1404 }
1405 else {
1406 !hasUpdatedFrame && applyFriction(t);
1407 return state;
1408 }
1409 },
1410 };
1411 }
1412
1413 function createMixers(output, ease, customMixer) {
1414 const mixers = [];
1415 const mixerFactory = customMixer || MotionGlobalConfig.mix || mix;
1416 const numMixers = output.length - 1;
1417 for (let i = 0; i < numMixers; i++) {
1418 let mixer = mixerFactory(output[i], output[i + 1]);
1419 if (ease) {
1420 const easingFunction = Array.isArray(ease) ? ease[i] || noop : ease;
1421 mixer = pipe(easingFunction, mixer);
1422 }
1423 mixers.push(mixer);
1424 }
1425 return mixers;
1426 }
1427 /**
1428 * Create a function that maps from a numerical input array to a generic output array.
1429 *
1430 * Accepts:
1431 * - Numbers
1432 * - Colors (hex, hsl, hsla, rgb, rgba)
1433 * - Complex (combinations of one or more numbers or strings)
1434 *
1435 * ```jsx
1436 * const mixColor = interpolate([0, 1], ['#fff', '#000'])
1437 *
1438 * mixColor(0.5) // 'rgba(128, 128, 128, 1)'
1439 * ```
1440 *
1441 * TODO Revisit this approach once we've moved to data models for values,
1442 * probably not needed to pregenerate mixer functions.
1443 *
1444 * @public
1445 */
1446 function interpolate(input, output, { clamp: isClamp = true, ease, mixer } = {}) {
1447 const inputLength = input.length;
1448 exports.invariant(inputLength === output.length, "Both input and output ranges must be the same length", "range-length");
1449 /**
1450 * If we're only provided a single input, we can just make a function
1451 * that returns the output.
1452 */
1453 if (inputLength === 1)
1454 return () => output[0];
1455 if (inputLength === 2 && output[0] === output[1])
1456 return () => output[1];
1457 const isZeroDeltaRange = input[0] === input[1];
1458 // If input runs highest -> lowest, reverse both arrays
1459 if (input[0] > input[inputLength - 1]) {
1460 input = [...input].reverse();
1461 output = [...output].reverse();
1462 }
1463 const mixers = createMixers(output, ease, mixer);
1464 const numMixers = mixers.length;
1465 const interpolator = (v) => {
1466 if (isZeroDeltaRange && v < input[0])
1467 return output[0];
1468 let i = 0;
1469 if (numMixers > 1) {
1470 for (; i < input.length - 2; i++) {
1471 if (v < input[i + 1])
1472 break;
1473 }
1474 }
1475 const progressInRange = progress(input[i], input[i + 1], v);
1476 return mixers[i](progressInRange);
1477 };
1478 return isClamp
1479 ? (v) => interpolator(clamp(input[0], input[inputLength - 1], v))
1480 : interpolator;
1481 }
1482
1483 function fillOffset(offset, remaining) {
1484 const min = offset[offset.length - 1];
1485 for (let i = 1; i <= remaining; i++) {
1486 const offsetProgress = progress(0, remaining, i);
1487 offset.push(mixNumber$1(min, 1, offsetProgress));
1488 }
1489 }
1490
1491 function defaultOffset$1(arr) {
1492 const offset = [0];
1493 fillOffset(offset, arr.length - 1);
1494 return offset;
1495 }
1496
1497 function convertOffsetToTimes(offset, duration) {
1498 return offset.map((o) => o * duration);
1499 }
1500
1501 function defaultEasing(values, easing) {
1502 return values.map(() => easing || easeInOut).splice(0, values.length - 1);
1503 }
1504 function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "easeInOut", }) {
1505 /**
1506 * Easing functions can be externally defined as strings. Here we convert them
1507 * into actual functions.
1508 */
1509 const easingFunctions = isEasingArray(ease)
1510 ? ease.map(easingDefinitionToFunction)
1511 : easingDefinitionToFunction(ease);
1512 /**
1513 * This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
1514 * to reduce GC during animation.
1515 */
1516 const state = {
1517 done: false,
1518 value: keyframeValues[0],
1519 };
1520 /**
1521 * Create a times array based on the provided 0-1 offsets
1522 */
1523 const absoluteTimes = convertOffsetToTimes(
1524 // Only use the provided offsets if they're the correct length
1525 // TODO Maybe we should warn here if there's a length mismatch
1526 times && times.length === keyframeValues.length
1527 ? times
1528 : defaultOffset$1(keyframeValues), duration);
1529 const mapTimeToKeyframe = interpolate(absoluteTimes, keyframeValues, {
1530 ease: Array.isArray(easingFunctions)
1531 ? easingFunctions
1532 : defaultEasing(keyframeValues, easingFunctions),
1533 });
1534 return {
1535 calculatedDuration: duration,
1536 next: (t) => {
1537 state.value = mapTimeToKeyframe(t);
1538 state.done = t >= duration;
1539 return state;
1540 },
1541 };
1542 }
1543
1544 const isNotNull$1 = (value) => value !== null;
1545 function getFinalKeyframe$1(keyframes, { repeat, repeatType = "loop" }, finalKeyframe, speed = 1) {
1546 const resolvedKeyframes = keyframes.filter(isNotNull$1);
1547 const useFirstKeyframe = speed < 0 || (repeat && repeatType !== "loop" && repeat % 2 === 1);
1548 const index = useFirstKeyframe ? 0 : resolvedKeyframes.length - 1;
1549 return !index || finalKeyframe === undefined
1550 ? resolvedKeyframes[index]
1551 : finalKeyframe;
1552 }
1553
1554 const transitionTypeMap = {
1555 decay: inertia,
1556 inertia,
1557 tween: keyframes,
1558 keyframes: keyframes,
1559 spring,
1560 };
1561 function replaceTransitionType(transition) {
1562 if (typeof transition.type === "string") {
1563 transition.type = transitionTypeMap[transition.type];
1564 }
1565 }
1566
1567 class WithPromise {
1568 constructor() {
1569 this.updateFinished();
1570 }
1571 get finished() {
1572 return this._finished;
1573 }
1574 updateFinished() {
1575 this._finished = new Promise((resolve) => {
1576 this.resolve = resolve;
1577 });
1578 }
1579 notifyFinished() {
1580 this.resolve();
1581 }
1582 /**
1583 * Allows the animation to be awaited.
1584 *
1585 * @deprecated Use `finished` instead.
1586 */
1587 then(onResolve, onReject) {
1588 return this.finished.then(onResolve, onReject);
1589 }
1590 }
1591
1592 const percentToProgress = (percent) => percent / 100;
1593 class JSAnimation extends WithPromise {
1594 constructor(options) {
1595 super();
1596 this.state = "idle";
1597 this.startTime = null;
1598 this.isStopped = false;
1599 /**
1600 * The current time of the animation.
1601 */
1602 this.currentTime = 0;
1603 /**
1604 * The time at which the animation was paused.
1605 */
1606 this.holdTime = null;
1607 /**
1608 * Playback speed as a factor. 0 would be stopped, -1 reverse and 2 double speed.
1609 */
1610 this.playbackSpeed = 1;
1611 /**
1612 * This method is bound to the instance to fix a pattern where
1613 * animation.stop is returned as a reference from a useEffect.
1614 */
1615 this.stop = () => {
1616 const { motionValue } = this.options;
1617 if (motionValue && motionValue.updatedAt !== time.now()) {
1618 this.tick(time.now());
1619 }
1620 this.isStopped = true;
1621 if (this.state === "idle")
1622 return;
1623 this.teardown();
1624 this.options.onStop?.();
1625 };
1626 activeAnimations.mainThread++;
1627 this.options = options;
1628 this.initAnimation();
1629 this.play();
1630 if (options.autoplay === false)
1631 this.pause();
1632 }
1633 initAnimation() {
1634 const { options } = this;
1635 replaceTransitionType(options);
1636 const { type = keyframes, repeat = 0, repeatDelay = 0, repeatType, velocity = 0, } = options;
1637 let { keyframes: keyframes$1 } = options;
1638 const generatorFactory = type || keyframes;
1639 if (generatorFactory !== keyframes) {
1640 exports.invariant(keyframes$1.length <= 2, `Only two keyframes currently supported with spring and inertia animations. Trying to animate ${keyframes$1}`, "spring-two-frames");
1641 }
1642 if (generatorFactory !== keyframes &&
1643 typeof keyframes$1[0] !== "number") {
1644 this.mixKeyframes = pipe(percentToProgress, mix(keyframes$1[0], keyframes$1[1]));
1645 keyframes$1 = [0, 100];
1646 }
1647 const generator = generatorFactory({ ...options, keyframes: keyframes$1 });
1648 /**
1649 * If we have a mirror repeat type we need to create a second generator that outputs the
1650 * mirrored (not reversed) animation and later ping pong between the two generators.
1651 */
1652 if (repeatType === "mirror") {
1653 this.mirroredGenerator = generatorFactory({
1654 ...options,
1655 keyframes: [...keyframes$1].reverse(),
1656 velocity: -velocity,
1657 });
1658 }
1659 /**
1660 * If duration is undefined and we have repeat options,
1661 * we need to calculate a duration from the generator.
1662 *
1663 * We set it to the generator itself to cache the duration.
1664 * Any timeline resolver will need to have already precalculated
1665 * the duration by this step.
1666 */
1667 if (generator.calculatedDuration === null) {
1668 generator.calculatedDuration = calcGeneratorDuration(generator);
1669 }
1670 const { calculatedDuration } = generator;
1671 this.calculatedDuration = calculatedDuration;
1672 this.resolvedDuration = calculatedDuration + repeatDelay;
1673 this.totalDuration = this.resolvedDuration * (repeat + 1) - repeatDelay;
1674 this.generator = generator;
1675 }
1676 updateTime(timestamp) {
1677 const animationTime = Math.round(timestamp - this.startTime) * this.playbackSpeed;
1678 // Update currentTime
1679 if (this.holdTime !== null) {
1680 this.currentTime = this.holdTime;
1681 }
1682 else {
1683 // Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
1684 // 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
1685 // example.
1686 this.currentTime = animationTime;
1687 }
1688 }
1689 tick(timestamp, sample = false) {
1690 const { generator, totalDuration, mixKeyframes, mirroredGenerator, resolvedDuration, calculatedDuration, } = this;
1691 if (this.startTime === null)
1692 return generator.next(0);
1693 const { delay = 0, keyframes, repeat, repeatType, repeatDelay, type, onUpdate, finalKeyframe, } = this.options;
1694 /**
1695 * requestAnimationFrame timestamps can come through as lower than
1696 * the startTime as set by performance.now(). Here we prevent this,
1697 * though in the future it could be possible to make setting startTime
1698 * a pending operation that gets resolved here.
1699 */
1700 if (this.speed > 0) {
1701 this.startTime = Math.min(this.startTime, timestamp);
1702 }
1703 else if (this.speed < 0) {
1704 this.startTime = Math.min(timestamp - totalDuration / this.speed, this.startTime);
1705 }
1706 if (sample) {
1707 this.currentTime = timestamp;
1708 }
1709 else {
1710 this.updateTime(timestamp);
1711 }
1712 // Rebase on delay
1713 const timeWithoutDelay = this.currentTime - delay * (this.playbackSpeed >= 0 ? 1 : -1);
1714 const isInDelayPhase = this.playbackSpeed >= 0
1715 ? timeWithoutDelay < 0
1716 : timeWithoutDelay > totalDuration;
1717 this.currentTime = Math.max(timeWithoutDelay, 0);
1718 // If this animation has finished, set the current time to the total duration.
1719 if (this.state === "finished" && this.holdTime === null) {
1720 this.currentTime = totalDuration;
1721 }
1722 let elapsed = this.currentTime;
1723 let frameGenerator = generator;
1724 if (repeat) {
1725 /**
1726 * Get the current progress (0-1) of the animation. If t is >
1727 * than duration we'll get values like 2.5 (midway through the
1728 * third iteration)
1729 */
1730 const progress = Math.min(this.currentTime, totalDuration) / resolvedDuration;
1731 /**
1732 * Get the current iteration (0 indexed). For instance the floor of
1733 * 2.5 is 2.
1734 */
1735 let currentIteration = Math.floor(progress);
1736 /**
1737 * Get the current progress of the iteration by taking the remainder
1738 * so 2.5 is 0.5 through iteration 2
1739 */
1740 let iterationProgress = progress % 1.0;
1741 /**
1742 * If iteration progress is 1 we count that as the end
1743 * of the previous iteration.
1744 */
1745 if (!iterationProgress && progress >= 1) {
1746 iterationProgress = 1;
1747 }
1748 iterationProgress === 1 && currentIteration--;
1749 currentIteration = Math.min(currentIteration, repeat + 1);
1750 /**
1751 * Reverse progress if we're not running in "normal" direction
1752 */
1753 const isOddIteration = Boolean(currentIteration % 2);
1754 if (isOddIteration) {
1755 if (repeatType === "reverse") {
1756 iterationProgress = 1 - iterationProgress;
1757 if (repeatDelay) {
1758 iterationProgress -= repeatDelay / resolvedDuration;
1759 }
1760 }
1761 else if (repeatType === "mirror") {
1762 frameGenerator = mirroredGenerator;
1763 }
1764 }
1765 elapsed = clamp(0, 1, iterationProgress) * resolvedDuration;
1766 }
1767 /**
1768 * If we're in negative time, set state as the initial keyframe.
1769 * This prevents delay: x, duration: 0 animations from finishing
1770 * instantly.
1771 */
1772 const state = isInDelayPhase
1773 ? { done: false, value: keyframes[0] }
1774 : frameGenerator.next(elapsed);
1775 if (mixKeyframes) {
1776 state.value = mixKeyframes(state.value);
1777 }
1778 let { done } = state;
1779 if (!isInDelayPhase && calculatedDuration !== null) {
1780 done =
1781 this.playbackSpeed >= 0
1782 ? this.currentTime >= totalDuration
1783 : this.currentTime <= 0;
1784 }
1785 const isAnimationFinished = this.holdTime === null &&
1786 (this.state === "finished" || (this.state === "running" && done));
1787 // TODO: The exception for inertia could be cleaner here
1788 if (isAnimationFinished && type !== inertia) {
1789 state.value = getFinalKeyframe$1(keyframes, this.options, finalKeyframe, this.speed);
1790 }
1791 if (onUpdate) {
1792 onUpdate(state.value);
1793 }
1794 if (isAnimationFinished) {
1795 this.finish();
1796 }
1797 return state;
1798 }
1799 /**
1800 * Allows the returned animation to be awaited or promise-chained. Currently
1801 * resolves when the animation finishes at all but in a future update could/should
1802 * reject if its cancels.
1803 */
1804 then(resolve, reject) {
1805 return this.finished.then(resolve, reject);
1806 }
1807 get duration() {
1808 return millisecondsToSeconds(this.calculatedDuration);
1809 }
1810 get iterationDuration() {
1811 const { delay = 0 } = this.options || {};
1812 return this.duration + millisecondsToSeconds(delay);
1813 }
1814 get time() {
1815 return millisecondsToSeconds(this.currentTime);
1816 }
1817 set time(newTime) {
1818 newTime = secondsToMilliseconds(newTime);
1819 this.currentTime = newTime;
1820 if (this.startTime === null ||
1821 this.holdTime !== null ||
1822 this.playbackSpeed === 0) {
1823 this.holdTime = newTime;
1824 }
1825 else if (this.driver) {
1826 this.startTime = this.driver.now() - newTime / this.playbackSpeed;
1827 }
1828 this.driver?.start(false);
1829 }
1830 get speed() {
1831 return this.playbackSpeed;
1832 }
1833 set speed(newSpeed) {
1834 this.updateTime(time.now());
1835 const hasChanged = this.playbackSpeed !== newSpeed;
1836 this.playbackSpeed = newSpeed;
1837 if (hasChanged) {
1838 this.time = millisecondsToSeconds(this.currentTime);
1839 }
1840 }
1841 play() {
1842 if (this.isStopped)
1843 return;
1844 const { driver = frameloopDriver, startTime } = this.options;
1845 if (!this.driver) {
1846 this.driver = driver((timestamp) => this.tick(timestamp));
1847 }
1848 this.options.onPlay?.();
1849 const now = this.driver.now();
1850 if (this.state === "finished") {
1851 this.updateFinished();
1852 this.startTime = now;
1853 }
1854 else if (this.holdTime !== null) {
1855 this.startTime = now - this.holdTime;
1856 }
1857 else if (!this.startTime) {
1858 this.startTime = startTime ?? now;
1859 }
1860 if (this.state === "finished" && this.speed < 0) {
1861 this.startTime += this.calculatedDuration;
1862 }
1863 this.holdTime = null;
1864 /**
1865 * Set playState to running only after we've used it in
1866 * the previous logic.
1867 */
1868 this.state = "running";
1869 this.driver.start();
1870 }
1871 pause() {
1872 this.state = "paused";
1873 this.updateTime(time.now());
1874 this.holdTime = this.currentTime;
1875 }
1876 complete() {
1877 if (this.state !== "running") {
1878 this.play();
1879 }
1880 this.state = "finished";
1881 this.holdTime = null;
1882 }
1883 finish() {
1884 this.notifyFinished();
1885 this.teardown();
1886 this.state = "finished";
1887 this.options.onComplete?.();
1888 }
1889 cancel() {
1890 this.holdTime = null;
1891 this.startTime = 0;
1892 this.tick(0);
1893 this.teardown();
1894 this.options.onCancel?.();
1895 }
1896 teardown() {
1897 this.state = "idle";
1898 this.stopDriver();
1899 this.startTime = this.holdTime = null;
1900 activeAnimations.mainThread--;
1901 }
1902 stopDriver() {
1903 if (!this.driver)
1904 return;
1905 this.driver.stop();
1906 this.driver = undefined;
1907 }
1908 sample(sampleTime) {
1909 this.startTime = 0;
1910 return this.tick(sampleTime, true);
1911 }
1912 attachTimeline(timeline) {
1913 if (this.options.allowFlatten) {
1914 this.options.type = "keyframes";
1915 this.options.ease = "linear";
1916 this.initAnimation();
1917 }
1918 this.driver?.stop();
1919 return timeline.observe(this);
1920 }
1921 }
1922 // Legacy function support
1923 function animateValue(options) {
1924 return new JSAnimation(options);
1925 }
1926
1927 function fillWildcards(keyframes) {
1928 for (let i = 1; i < keyframes.length; i++) {
1929 keyframes[i] ?? (keyframes[i] = keyframes[i - 1]);
1930 }
1931 }
1932
1933 const radToDeg = (rad) => (rad * 180) / Math.PI;
1934 const rotate = (v) => {
1935 const angle = radToDeg(Math.atan2(v[1], v[0]));
1936 return rebaseAngle(angle);
1937 };
1938 const matrix2dParsers = {
1939 x: 4,
1940 y: 5,
1941 translateX: 4,
1942 translateY: 5,
1943 scaleX: 0,
1944 scaleY: 3,
1945 scale: (v) => (Math.abs(v[0]) + Math.abs(v[3])) / 2,
1946 rotate,
1947 rotateZ: rotate,
1948 skewX: (v) => radToDeg(Math.atan(v[1])),
1949 skewY: (v) => radToDeg(Math.atan(v[2])),
1950 skew: (v) => (Math.abs(v[1]) + Math.abs(v[2])) / 2,
1951 };
1952 const rebaseAngle = (angle) => {
1953 angle = angle % 360;
1954 if (angle < 0)
1955 angle += 360;
1956 return angle;
1957 };
1958 const rotateZ = rotate;
1959 const scaleX = (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1]);
1960 const scaleY = (v) => Math.sqrt(v[4] * v[4] + v[5] * v[5]);
1961 const matrix3dParsers = {
1962 x: 12,
1963 y: 13,
1964 z: 14,
1965 translateX: 12,
1966 translateY: 13,
1967 translateZ: 14,
1968 scaleX,
1969 scaleY,
1970 scale: (v) => (scaleX(v) + scaleY(v)) / 2,
1971 rotateX: (v) => rebaseAngle(radToDeg(Math.atan2(v[6], v[5]))),
1972 rotateY: (v) => rebaseAngle(radToDeg(Math.atan2(-v[2], v[0]))),
1973 rotateZ,
1974 rotate: rotateZ,
1975 skewX: (v) => radToDeg(Math.atan(v[4])),
1976 skewY: (v) => radToDeg(Math.atan(v[1])),
1977 skew: (v) => (Math.abs(v[1]) + Math.abs(v[4])) / 2,
1978 };
1979 function defaultTransformValue(name) {
1980 return name.includes("scale") ? 1 : 0;
1981 }
1982 function parseValueFromTransform(transform, name) {
1983 if (!transform || transform === "none") {
1984 return defaultTransformValue(name);
1985 }
1986 const matrix3dMatch = transform.match(/^matrix3d\(([-\d.e\s,]+)\)$/u);
1987 let parsers;
1988 let match;
1989 if (matrix3dMatch) {
1990 parsers = matrix3dParsers;
1991 match = matrix3dMatch;
1992 }
1993 else {
1994 const matrix2dMatch = transform.match(/^matrix\(([-\d.e\s,]+)\)$/u);
1995 parsers = matrix2dParsers;
1996 match = matrix2dMatch;
1997 }
1998 if (!match) {
1999 return defaultTransformValue(name);
2000 }
2001 const valueParser = parsers[name];
2002 const values = match[1].split(",").map(convertTransformToNumber);
2003 return typeof valueParser === "function"
2004 ? valueParser(values)
2005 : values[valueParser];
2006 }
2007 const readTransformValue = (instance, name) => {
2008 const { transform = "none" } = getComputedStyle(instance);
2009 return parseValueFromTransform(transform, name);
2010 };
2011 function convertTransformToNumber(value) {
2012 return parseFloat(value.trim());
2013 }
2014
2015 /**
2016 * Generate a list of every possible transform key.
2017 */
2018 const transformPropOrder = [
2019 "transformPerspective",
2020 "x",
2021 "y",
2022 "z",
2023 "translateX",
2024 "translateY",
2025 "translateZ",
2026 "scale",
2027 "scaleX",
2028 "scaleY",
2029 "rotate",
2030 "rotateX",
2031 "rotateY",
2032 "rotateZ",
2033 "skew",
2034 "skewX",
2035 "skewY",
2036 ];
2037 /**
2038 * A quick lookup for transform props.
2039 */
2040 const transformProps = /*@__PURE__*/ (() => new Set(transformPropOrder))();
2041
2042 const isNumOrPxType = (v) => v === number || v === px;
2043 const transformKeys = new Set(["x", "y", "z"]);
2044 const nonTranslationalTransformKeys = transformPropOrder.filter((key) => !transformKeys.has(key));
2045 function removeNonTranslationalTransform(visualElement) {
2046 const removedTransforms = [];
2047 nonTranslationalTransformKeys.forEach((key) => {
2048 const value = visualElement.getValue(key);
2049 if (value !== undefined) {
2050 removedTransforms.push([key, value.get()]);
2051 value.set(key.startsWith("scale") ? 1 : 0);
2052 }
2053 });
2054 return removedTransforms;
2055 }
2056 const positionalValues = {
2057 // Dimensions
2058 width: ({ x }, { paddingLeft = "0", paddingRight = "0" }) => x.max - x.min - parseFloat(paddingLeft) - parseFloat(paddingRight),
2059 height: ({ y }, { paddingTop = "0", paddingBottom = "0" }) => y.max - y.min - parseFloat(paddingTop) - parseFloat(paddingBottom),
2060 top: (_bbox, { top }) => parseFloat(top),
2061 left: (_bbox, { left }) => parseFloat(left),
2062 bottom: ({ y }, { top }) => parseFloat(top) + (y.max - y.min),
2063 right: ({ x }, { left }) => parseFloat(left) + (x.max - x.min),
2064 // Transform
2065 x: (_bbox, { transform }) => parseValueFromTransform(transform, "x"),
2066 y: (_bbox, { transform }) => parseValueFromTransform(transform, "y"),
2067 };
2068 // Alias translate longform names
2069 positionalValues.translateX = positionalValues.x;
2070 positionalValues.translateY = positionalValues.y;
2071
2072 const toResolve = new Set();
2073 let isScheduled = false;
2074 let anyNeedsMeasurement = false;
2075 let isForced = false;
2076 function measureAllKeyframes() {
2077 if (anyNeedsMeasurement) {
2078 const resolversToMeasure = Array.from(toResolve).filter((resolver) => resolver.needsMeasurement);
2079 const elementsToMeasure = new Set(resolversToMeasure.map((resolver) => resolver.element));
2080 const transformsToRestore = new Map();
2081 /**
2082 * Write pass
2083 * If we're measuring elements we want to remove bounding box-changing transforms.
2084 */
2085 elementsToMeasure.forEach((element) => {
2086 const removedTransforms = removeNonTranslationalTransform(element);
2087 if (!removedTransforms.length)
2088 return;
2089 transformsToRestore.set(element, removedTransforms);
2090 element.render();
2091 });
2092 // Read
2093 resolversToMeasure.forEach((resolver) => resolver.measureInitialState());
2094 // Write
2095 elementsToMeasure.forEach((element) => {
2096 element.render();
2097 const restore = transformsToRestore.get(element);
2098 if (restore) {
2099 restore.forEach(([key, value]) => {
2100 element.getValue(key)?.set(value);
2101 });
2102 }
2103 });
2104 // Read
2105 resolversToMeasure.forEach((resolver) => resolver.measureEndState());
2106 // Write
2107 resolversToMeasure.forEach((resolver) => {
2108 if (resolver.suspendedScrollY !== undefined) {
2109 window.scrollTo(0, resolver.suspendedScrollY);
2110 }
2111 });
2112 }
2113 anyNeedsMeasurement = false;
2114 isScheduled = false;
2115 toResolve.forEach((resolver) => resolver.complete(isForced));
2116 toResolve.clear();
2117 }
2118 function readAllKeyframes() {
2119 toResolve.forEach((resolver) => {
2120 resolver.readKeyframes();
2121 if (resolver.needsMeasurement) {
2122 anyNeedsMeasurement = true;
2123 }
2124 });
2125 }
2126 function flushKeyframeResolvers() {
2127 isForced = true;
2128 readAllKeyframes();
2129 measureAllKeyframes();
2130 isForced = false;
2131 }
2132 class KeyframeResolver {
2133 constructor(unresolvedKeyframes, onComplete, name, motionValue, element, isAsync = false) {
2134 this.state = "pending";
2135 /**
2136 * Track whether this resolver is async. If it is, it'll be added to the
2137 * resolver queue and flushed in the next frame. Resolvers that aren't going
2138 * to trigger read/write thrashing don't need to be async.
2139 */
2140 this.isAsync = false;
2141 /**
2142 * Track whether this resolver needs to perform a measurement
2143 * to resolve its keyframes.
2144 */
2145 this.needsMeasurement = false;
2146 this.unresolvedKeyframes = [...unresolvedKeyframes];
2147 this.onComplete = onComplete;
2148 this.name = name;
2149 this.motionValue = motionValue;
2150 this.element = element;
2151 this.isAsync = isAsync;
2152 }
2153 scheduleResolve() {
2154 this.state = "scheduled";
2155 if (this.isAsync) {
2156 toResolve.add(this);
2157 if (!isScheduled) {
2158 isScheduled = true;
2159 frame.read(readAllKeyframes);
2160 frame.resolveKeyframes(measureAllKeyframes);
2161 }
2162 }
2163 else {
2164 this.readKeyframes();
2165 this.complete();
2166 }
2167 }
2168 readKeyframes() {
2169 const { unresolvedKeyframes, name, element, motionValue } = this;
2170 // If initial keyframe is null we need to read it from the DOM
2171 if (unresolvedKeyframes[0] === null) {
2172 const currentValue = motionValue?.get();
2173 // TODO: This doesn't work if the final keyframe is a wildcard
2174 const finalKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
2175 if (currentValue !== undefined) {
2176 unresolvedKeyframes[0] = currentValue;
2177 }
2178 else if (element && name) {
2179 const valueAsRead = element.readValue(name, finalKeyframe);
2180 if (valueAsRead !== undefined && valueAsRead !== null) {
2181 unresolvedKeyframes[0] = valueAsRead;
2182 }
2183 }
2184 if (unresolvedKeyframes[0] === undefined) {
2185 unresolvedKeyframes[0] = finalKeyframe;
2186 }
2187 if (motionValue && currentValue === undefined) {
2188 motionValue.set(unresolvedKeyframes[0]);
2189 }
2190 }
2191 fillWildcards(unresolvedKeyframes);
2192 }
2193 setFinalKeyframe() { }
2194 measureInitialState() { }
2195 renderEndStyles() { }
2196 measureEndState() { }
2197 complete(isForcedComplete = false) {
2198 this.state = "complete";
2199 this.onComplete(this.unresolvedKeyframes, this.finalKeyframe, isForcedComplete);
2200 toResolve.delete(this);
2201 }
2202 cancel() {
2203 if (this.state === "scheduled") {
2204 toResolve.delete(this);
2205 this.state = "pending";
2206 }
2207 }
2208 resume() {
2209 if (this.state === "pending")
2210 this.scheduleResolve();
2211 }
2212 }
2213
2214 const isCSSVar = (name) => name.startsWith("--");
2215
2216 function setStyle(element, name, value) {
2217 isCSSVar(name)
2218 ? element.style.setProperty(name, value)
2219 : (element.style[name] = value);
2220 }
2221
2222 const supportsScrollTimeline = /* @__PURE__ */ memo(() => window.ScrollTimeline !== undefined);
2223
2224 /**
2225 * Add the ability for test suites to manually set support flags
2226 * to better test more environments.
2227 */
2228 const supportsFlags = {};
2229
2230 function memoSupports(callback, supportsFlag) {
2231 const memoized = memo(callback);
2232 return () => supportsFlags[supportsFlag] ?? memoized();
2233 }
2234
2235 const supportsLinearEasing = /*@__PURE__*/ memoSupports(() => {
2236 try {
2237 document
2238 .createElement("div")
2239 .animate({ opacity: 0 }, { easing: "linear(0, 1)" });
2240 }
2241 catch (e) {
2242 return false;
2243 }
2244 return true;
2245 }, "linearEasing");
2246
2247 const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
2248
2249 const supportedWaapiEasing = {
2250 linear: "linear",
2251 ease: "ease",
2252 easeIn: "ease-in",
2253 easeOut: "ease-out",
2254 easeInOut: "ease-in-out",
2255 circIn: /*@__PURE__*/ cubicBezierAsString([0, 0.65, 0.55, 1]),
2256 circOut: /*@__PURE__*/ cubicBezierAsString([0.55, 0, 1, 0.45]),
2257 backIn: /*@__PURE__*/ cubicBezierAsString([0.31, 0.01, 0.66, -0.59]),
2258 backOut: /*@__PURE__*/ cubicBezierAsString([0.33, 1.53, 0.69, 0.99]),
2259 };
2260
2261 function mapEasingToNativeEasing(easing, duration) {
2262 if (!easing) {
2263 return undefined;
2264 }
2265 else if (typeof easing === "function") {
2266 return supportsLinearEasing()
2267 ? generateLinearEasing(easing, duration)
2268 : "ease-out";
2269 }
2270 else if (isBezierDefinition(easing)) {
2271 return cubicBezierAsString(easing);
2272 }
2273 else if (Array.isArray(easing)) {
2274 return easing.map((segmentEasing) => mapEasingToNativeEasing(segmentEasing, duration) ||
2275 supportedWaapiEasing.easeOut);
2276 }
2277 else {
2278 return supportedWaapiEasing[easing];
2279 }
2280 }
2281
2282 function startWaapiAnimation(element, valueName, keyframes, { delay = 0, duration = 300, repeat = 0, repeatType = "loop", ease = "easeOut", times, } = {}, pseudoElement = undefined) {
2283 const keyframeOptions = {
2284 [valueName]: keyframes,
2285 };
2286 if (times)
2287 keyframeOptions.offset = times;
2288 const easing = mapEasingToNativeEasing(ease, duration);
2289 /**
2290 * If this is an easing array, apply to keyframes, not animation as a whole
2291 */
2292 if (Array.isArray(easing))
2293 keyframeOptions.easing = easing;
2294 if (statsBuffer.value) {
2295 activeAnimations.waapi++;
2296 }
2297 const options = {
2298 delay,
2299 duration,
2300 easing: !Array.isArray(easing) ? easing : "linear",
2301 fill: "both",
2302 iterations: repeat + 1,
2303 direction: repeatType === "reverse" ? "alternate" : "normal",
2304 };
2305 if (pseudoElement)
2306 options.pseudoElement = pseudoElement;
2307 const animation = element.animate(keyframeOptions, options);
2308 if (statsBuffer.value) {
2309 animation.finished.finally(() => {
2310 activeAnimations.waapi--;
2311 });
2312 }
2313 return animation;
2314 }
2315
2316 function isGenerator(type) {
2317 return typeof type === "function" && "applyToOptions" in type;
2318 }
2319
2320 function applyGeneratorOptions({ type, ...options }) {
2321 if (isGenerator(type) && supportsLinearEasing()) {
2322 return type.applyToOptions(options);
2323 }
2324 else {
2325 options.duration ?? (options.duration = 300);
2326 options.ease ?? (options.ease = "easeOut");
2327 }
2328 return options;
2329 }
2330
2331 /**
2332 * NativeAnimation implements AnimationPlaybackControls for the browser's Web Animations API.
2333 */
2334 class NativeAnimation extends WithPromise {
2335 constructor(options) {
2336 super();
2337 this.finishedTime = null;
2338 this.isStopped = false;
2339 if (!options)
2340 return;
2341 const { element, name, keyframes, pseudoElement, allowFlatten = false, finalKeyframe, onComplete, } = options;
2342 this.isPseudoElement = Boolean(pseudoElement);
2343 this.allowFlatten = allowFlatten;
2344 this.options = options;
2345 exports.invariant(typeof options.type !== "string", `Mini animate() doesn't support "type" as a string.`, "mini-spring");
2346 const transition = applyGeneratorOptions(options);
2347 this.animation = startWaapiAnimation(element, name, keyframes, transition, pseudoElement);
2348 if (transition.autoplay === false) {
2349 this.animation.pause();
2350 }
2351 this.animation.onfinish = () => {
2352 this.finishedTime = this.time;
2353 if (!pseudoElement) {
2354 const keyframe = getFinalKeyframe$1(keyframes, this.options, finalKeyframe, this.speed);
2355 if (this.updateMotionValue) {
2356 this.updateMotionValue(keyframe);
2357 }
2358 else {
2359 /**
2360 * If we can, we want to commit the final style as set by the user,
2361 * rather than the computed keyframe value supplied by the animation.
2362 */
2363 setStyle(element, name, keyframe);
2364 }
2365 this.animation.cancel();
2366 }
2367 onComplete?.();
2368 this.notifyFinished();
2369 };
2370 }
2371 play() {
2372 if (this.isStopped)
2373 return;
2374 this.animation.play();
2375 if (this.state === "finished") {
2376 this.updateFinished();
2377 }
2378 }
2379 pause() {
2380 this.animation.pause();
2381 }
2382 complete() {
2383 this.animation.finish?.();
2384 }
2385 cancel() {
2386 try {
2387 this.animation.cancel();
2388 }
2389 catch (e) { }
2390 }
2391 stop() {
2392 if (this.isStopped)
2393 return;
2394 this.isStopped = true;
2395 const { state } = this;
2396 if (state === "idle" || state === "finished") {
2397 return;
2398 }
2399 if (this.updateMotionValue) {
2400 this.updateMotionValue();
2401 }
2402 else {
2403 this.commitStyles();
2404 }
2405 if (!this.isPseudoElement)
2406 this.cancel();
2407 }
2408 /**
2409 * WAAPI doesn't natively have any interruption capabilities.
2410 *
2411 * In this method, we commit styles back to the DOM before cancelling
2412 * the animation.
2413 *
2414 * This is designed to be overridden by NativeAnimationExtended, which
2415 * will create a renderless JS animation and sample it twice to calculate
2416 * its current value, "previous" value, and therefore allow
2417 * Motion to also correctly calculate velocity for any subsequent animation
2418 * while deferring the commit until the next animation frame.
2419 */
2420 commitStyles() {
2421 if (!this.isPseudoElement) {
2422 this.animation.commitStyles?.();
2423 }
2424 }
2425 get duration() {
2426 const duration = this.animation.effect?.getComputedTiming?.().duration || 0;
2427 return millisecondsToSeconds(Number(duration));
2428 }
2429 get iterationDuration() {
2430 const { delay = 0 } = this.options || {};
2431 return this.duration + millisecondsToSeconds(delay);
2432 }
2433 get time() {
2434 return millisecondsToSeconds(Number(this.animation.currentTime) || 0);
2435 }
2436 set time(newTime) {
2437 this.finishedTime = null;
2438 this.animation.currentTime = secondsToMilliseconds(newTime);
2439 }
2440 /**
2441 * The playback speed of the animation.
2442 * 1 = normal speed, 2 = double speed, 0.5 = half speed.
2443 */
2444 get speed() {
2445 return this.animation.playbackRate;
2446 }
2447 set speed(newSpeed) {
2448 // Allow backwards playback after finishing
2449 if (newSpeed < 0)
2450 this.finishedTime = null;
2451 this.animation.playbackRate = newSpeed;
2452 }
2453 get state() {
2454 return this.finishedTime !== null
2455 ? "finished"
2456 : this.animation.playState;
2457 }
2458 get startTime() {
2459 return Number(this.animation.startTime);
2460 }
2461 set startTime(newStartTime) {
2462 this.animation.startTime = newStartTime;
2463 }
2464 /**
2465 * Attaches a timeline to the animation, for instance the `ScrollTimeline`.
2466 */
2467 attachTimeline({ timeline, observe }) {
2468 if (this.allowFlatten) {
2469 this.animation.effect?.updateTiming({ easing: "linear" });
2470 }
2471 this.animation.onfinish = null;
2472 if (timeline && supportsScrollTimeline()) {
2473 this.animation.timeline = timeline;
2474 return noop;
2475 }
2476 else {
2477 return observe(this);
2478 }
2479 }
2480 }
2481
2482 const unsupportedEasingFunctions = {
2483 anticipate,
2484 backInOut,
2485 circInOut,
2486 };
2487 function isUnsupportedEase(key) {
2488 return key in unsupportedEasingFunctions;
2489 }
2490 function replaceStringEasing(transition) {
2491 if (typeof transition.ease === "string" &&
2492 isUnsupportedEase(transition.ease)) {
2493 transition.ease = unsupportedEasingFunctions[transition.ease];
2494 }
2495 }
2496
2497 /**
2498 * 10ms is chosen here as it strikes a balance between smooth
2499 * results (more than one keyframe per frame at 60fps) and
2500 * keyframe quantity.
2501 */
2502 const sampleDelta = 10; //ms
2503 class NativeAnimationExtended extends NativeAnimation {
2504 constructor(options) {
2505 /**
2506 * The base NativeAnimation function only supports a subset
2507 * of Motion easings, and WAAPI also only supports some
2508 * easing functions via string/cubic-bezier definitions.
2509 *
2510 * This function replaces those unsupported easing functions
2511 * with a JS easing function. This will later get compiled
2512 * to a linear() easing function.
2513 */
2514 replaceStringEasing(options);
2515 /**
2516 * Ensure we replace the transition type with a generator function
2517 * before passing to WAAPI.
2518 *
2519 * TODO: Does this have a better home? It could be shared with
2520 * JSAnimation.
2521 */
2522 replaceTransitionType(options);
2523 super(options);
2524 if (options.startTime) {
2525 this.startTime = options.startTime;
2526 }
2527 this.options = options;
2528 }
2529 /**
2530 * WAAPI doesn't natively have any interruption capabilities.
2531 *
2532 * Rather than read commited styles back out of the DOM, we can
2533 * create a renderless JS animation and sample it twice to calculate
2534 * its current value, "previous" value, and therefore allow
2535 * Motion to calculate velocity for any subsequent animation.
2536 */
2537 updateMotionValue(value) {
2538 const { motionValue, onUpdate, onComplete, element, ...options } = this.options;
2539 if (!motionValue)
2540 return;
2541 if (value !== undefined) {
2542 motionValue.set(value);
2543 return;
2544 }
2545 const sampleAnimation = new JSAnimation({
2546 ...options,
2547 autoplay: false,
2548 });
2549 const sampleTime = secondsToMilliseconds(this.finishedTime ?? this.time);
2550 motionValue.setWithVelocity(sampleAnimation.sample(sampleTime - sampleDelta).value, sampleAnimation.sample(sampleTime).value, sampleDelta);
2551 sampleAnimation.stop();
2552 }
2553 }
2554
2555 /**
2556 * Check if a value is animatable. Examples:
2557 *
2558 * �
2559 : 100, "100px", "#fff"
2560 * ❌: "block", "url(2.jpg)"
2561 * @param value
2562 *
2563 * @internal
2564 */
2565 const isAnimatable = (value, name) => {
2566 // If the list of keys that might be non-animatable grows, replace with Set
2567 if (name === "zIndex")
2568 return false;
2569 // If it's a number or a keyframes array, we can animate it. We might at some point
2570 // need to do a deep isAnimatable check of keyframes, or let Popmotion handle this,
2571 // but for now lets leave it like this for performance reasons
2572 if (typeof value === "number" || Array.isArray(value))
2573 return true;
2574 if (typeof value === "string" && // It's animatable if we have a string
2575 (complex.test(value) || value === "0") && // And it contains numbers and/or colors
2576 !value.startsWith("url(") // Unless it starts with "url("
2577 ) {
2578 return true;
2579 }
2580 return false;
2581 };
2582
2583 function hasKeyframesChanged(keyframes) {
2584 const current = keyframes[0];
2585 if (keyframes.length === 1)
2586 return true;
2587 for (let i = 0; i < keyframes.length; i++) {
2588 if (keyframes[i] !== current)
2589 return true;
2590 }
2591 }
2592 function canAnimate(keyframes, name, type, velocity) {
2593 /**
2594 * Check if we're able to animate between the start and end keyframes,
2595 * and throw a warning if we're attempting to animate between one that's
2596 * animatable and another that isn't.
2597 */
2598 const originKeyframe = keyframes[0];
2599 if (originKeyframe === null)
2600 return false;
2601 /**
2602 * These aren't traditionally animatable but we do support them.
2603 * In future we could look into making this more generic or replacing
2604 * this function with mix() === mixImmediate
2605 */
2606 if (name === "display" || name === "visibility")
2607 return true;
2608 const targetKeyframe = keyframes[keyframes.length - 1];
2609 const isOriginAnimatable = isAnimatable(originKeyframe, name);
2610 const isTargetAnimatable = isAnimatable(targetKeyframe, name);
2611 exports.warning(isOriginAnimatable === isTargetAnimatable, `You are trying to animate ${name} from "${originKeyframe}" to "${targetKeyframe}". "${isOriginAnimatable ? targetKeyframe : originKeyframe}" is not an animatable value.`, "value-not-animatable");
2612 // Always skip if any of these are true
2613 if (!isOriginAnimatable || !isTargetAnimatable) {
2614 return false;
2615 }
2616 return (hasKeyframesChanged(keyframes) ||
2617 ((type === "spring" || isGenerator(type)) && velocity));
2618 }
2619
2620 function makeAnimationInstant(options) {
2621 options.duration = 0;
2622 options.type = "keyframes";
2623 }
2624
2625 /**
2626 * A list of values that can be hardware-accelerated.
2627 */
2628 const acceleratedValues$1 = new Set([
2629 "opacity",
2630 "clipPath",
2631 "filter",
2632 "transform",
2633 // TODO: Could be re-enabled now we have support for linear() easing
2634 // "background-color"
2635 ]);
2636 const supportsWaapi = /*@__PURE__*/ memo(() => Object.hasOwnProperty.call(Element.prototype, "animate"));
2637 function supportsBrowserAnimation(options) {
2638 const { motionValue, name, repeatDelay, repeatType, damping, type } = options;
2639 const subject = motionValue?.owner?.current;
2640 /**
2641 * We use this check instead of isHTMLElement() because we explicitly
2642 * **don't** want elements in different timing contexts (i.e. popups)
2643 * to be accelerated, as it's not possible to sync these animations
2644 * properly with those driven from the main window frameloop.
2645 */
2646 if (!(subject instanceof HTMLElement)) {
2647 return false;
2648 }
2649 const { onUpdate, transformTemplate } = motionValue.owner.getProps();
2650 return (supportsWaapi() &&
2651 name &&
2652 acceleratedValues$1.has(name) &&
2653 (name !== "transform" || !transformTemplate) &&
2654 /**
2655 * If we're outputting values to onUpdate then we can't use WAAPI as there's
2656 * no way to read the value from WAAPI every frame.
2657 */
2658 !onUpdate &&
2659 !repeatDelay &&
2660 repeatType !== "mirror" &&
2661 damping !== 0 &&
2662 type !== "inertia");
2663 }
2664
2665 /**
2666 * Maximum time allowed between an animation being created and it being
2667 * resolved for us to use the latter as the start time.
2668 *
2669 * This is to ensure that while we prefer to "start" an animation as soon
2670 * as it's triggered, we also want to avoid a visual jump if there's a big delay
2671 * between these two moments.
2672 */
2673 const MAX_RESOLVE_DELAY = 40;
2674 class AsyncMotionValueAnimation extends WithPromise {
2675 constructor({ autoplay = true, delay = 0, type = "keyframes", repeat = 0, repeatDelay = 0, repeatType = "loop", keyframes, name, motionValue, element, ...options }) {
2676 super();
2677 /**
2678 * Bound to support return animation.stop pattern
2679 */
2680 this.stop = () => {
2681 if (this._animation) {
2682 this._animation.stop();
2683 this.stopTimeline?.();
2684 }
2685 this.keyframeResolver?.cancel();
2686 };
2687 this.createdAt = time.now();
2688 const optionsWithDefaults = {
2689 autoplay,
2690 delay,
2691 type,
2692 repeat,
2693 repeatDelay,
2694 repeatType,
2695 name,
2696 motionValue,
2697 element,
2698 ...options,
2699 };
2700 const KeyframeResolver$1 = element?.KeyframeResolver || KeyframeResolver;
2701 this.keyframeResolver = new KeyframeResolver$1(keyframes, (resolvedKeyframes, finalKeyframe, forced) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe, optionsWithDefaults, !forced), name, motionValue, element);
2702 this.keyframeResolver?.scheduleResolve();
2703 }
2704 onKeyframesResolved(keyframes, finalKeyframe, options, sync) {
2705 this.keyframeResolver = undefined;
2706 const { name, type, velocity, delay, isHandoff, onUpdate } = options;
2707 this.resolvedAt = time.now();
2708 /**
2709 * If we can't animate this value with the resolved keyframes
2710 * then we should complete it immediately.
2711 */
2712 if (!canAnimate(keyframes, name, type, velocity)) {
2713 if (MotionGlobalConfig.instantAnimations || !delay) {
2714 onUpdate?.(getFinalKeyframe$1(keyframes, options, finalKeyframe));
2715 }
2716 keyframes[0] = keyframes[keyframes.length - 1];
2717 makeAnimationInstant(options);
2718 options.repeat = 0;
2719 }
2720 /**
2721 * Resolve startTime for the animation.
2722 *
2723 * This method uses the createdAt and resolvedAt to calculate the
2724 * animation startTime. *Ideally*, we would use the createdAt time as t=0
2725 * as the following frame would then be the first frame of the animation in
2726 * progress, which would feel snappier.
2727 *
2728 * However, if there's a delay (main thread work) between the creation of
2729 * the animation and the first commited frame, we prefer to use resolvedAt
2730 * to avoid a sudden jump into the animation.
2731 */
2732 const startTime = sync
2733 ? !this.resolvedAt
2734 ? this.createdAt
2735 : this.resolvedAt - this.createdAt > MAX_RESOLVE_DELAY
2736 ? this.resolvedAt
2737 : this.createdAt
2738 : undefined;
2739 const resolvedOptions = {
2740 startTime,
2741 finalKeyframe,
2742 ...options,
2743 keyframes,
2744 };
2745 /**
2746 * Animate via WAAPI if possible. If this is a handoff animation, the optimised animation will be running via
2747 * WAAPI. Therefore, this animation must be JS to ensure it runs "under" the
2748 * optimised animation.
2749 */
2750 const animation = !isHandoff && supportsBrowserAnimation(resolvedOptions)
2751 ? new NativeAnimationExtended({
2752 ...resolvedOptions,
2753 element: resolvedOptions.motionValue.owner.current,
2754 })
2755 : new JSAnimation(resolvedOptions);
2756 animation.finished.then(() => this.notifyFinished()).catch(noop);
2757 if (this.pendingTimeline) {
2758 this.stopTimeline = animation.attachTimeline(this.pendingTimeline);
2759 this.pendingTimeline = undefined;
2760 }
2761 this._animation = animation;
2762 }
2763 get finished() {
2764 if (!this._animation) {
2765 return this._finished;
2766 }
2767 else {
2768 return this.animation.finished;
2769 }
2770 }
2771 then(onResolve, _onReject) {
2772 return this.finished.finally(onResolve).then(() => { });
2773 }
2774 get animation() {
2775 if (!this._animation) {
2776 this.keyframeResolver?.resume();
2777 flushKeyframeResolvers();
2778 }
2779 return this._animation;
2780 }
2781 get duration() {
2782 return this.animation.duration;
2783 }
2784 get iterationDuration() {
2785 return this.animation.iterationDuration;
2786 }
2787 get time() {
2788 return this.animation.time;
2789 }
2790 set time(newTime) {
2791 this.animation.time = newTime;
2792 }
2793 get speed() {
2794 return this.animation.speed;
2795 }
2796 get state() {
2797 return this.animation.state;
2798 }
2799 set speed(newSpeed) {
2800 this.animation.speed = newSpeed;
2801 }
2802 get startTime() {
2803 return this.animation.startTime;
2804 }
2805 attachTimeline(timeline) {
2806 if (this._animation) {
2807 this.stopTimeline = this.animation.attachTimeline(timeline);
2808 }
2809 else {
2810 this.pendingTimeline = timeline;
2811 }
2812 return () => this.stop();
2813 }
2814 play() {
2815 this.animation.play();
2816 }
2817 pause() {
2818 this.animation.pause();
2819 }
2820 complete() {
2821 this.animation.complete();
2822 }
2823 cancel() {
2824 if (this._animation) {
2825 this.animation.cancel();
2826 }
2827 this.keyframeResolver?.cancel();
2828 }
2829 }
2830
2831 class GroupAnimation {
2832 constructor(animations) {
2833 // Bound to accomadate common `return animation.stop` pattern
2834 this.stop = () => this.runAll("stop");
2835 this.animations = animations.filter(Boolean);
2836 }
2837 get finished() {
2838 return Promise.all(this.animations.map((animation) => animation.finished));
2839 }
2840 /**
2841 * TODO: Filter out cancelled or stopped animations before returning
2842 */
2843 getAll(propName) {
2844 return this.animations[0][propName];
2845 }
2846 setAll(propName, newValue) {
2847 for (let i = 0; i < this.animations.length; i++) {
2848 this.animations[i][propName] = newValue;
2849 }
2850 }
2851 attachTimeline(timeline) {
2852 const subscriptions = this.animations.map((animation) => animation.attachTimeline(timeline));
2853 return () => {
2854 subscriptions.forEach((cancel, i) => {
2855 cancel && cancel();
2856 this.animations[i].stop();
2857 });
2858 };
2859 }
2860 get time() {
2861 return this.getAll("time");
2862 }
2863 set time(time) {
2864 this.setAll("time", time);
2865 }
2866 get speed() {
2867 return this.getAll("speed");
2868 }
2869 set speed(speed) {
2870 this.setAll("speed", speed);
2871 }
2872 get state() {
2873 return this.getAll("state");
2874 }
2875 get startTime() {
2876 return this.getAll("startTime");
2877 }
2878 get duration() {
2879 return getMax(this.animations, "duration");
2880 }
2881 get iterationDuration() {
2882 return getMax(this.animations, "iterationDuration");
2883 }
2884 runAll(methodName) {
2885 this.animations.forEach((controls) => controls[methodName]());
2886 }
2887 play() {
2888 this.runAll("play");
2889 }
2890 pause() {
2891 this.runAll("pause");
2892 }
2893 cancel() {
2894 this.runAll("cancel");
2895 }
2896 complete() {
2897 this.runAll("complete");
2898 }
2899 }
2900 function getMax(animations, propName) {
2901 let max = 0;
2902 for (let i = 0; i < animations.length; i++) {
2903 const value = animations[i][propName];
2904 if (value !== null && value > max) {
2905 max = value;
2906 }
2907 }
2908 return max;
2909 }
2910
2911 class GroupAnimationWithThen extends GroupAnimation {
2912 then(onResolve, _onReject) {
2913 return this.finished.finally(onResolve).then(() => { });
2914 }
2915 }
2916
2917 class NativeAnimationWrapper extends NativeAnimation {
2918 constructor(animation) {
2919 super();
2920 this.animation = animation;
2921 animation.onfinish = () => {
2922 this.finishedTime = this.time;
2923 this.notifyFinished();
2924 };
2925 }
2926 }
2927
2928 const animationMaps = new WeakMap();
2929 const animationMapKey = (name, pseudoElement = "") => `${name}:${pseudoElement}`;
2930 function getAnimationMap(element) {
2931 const map = animationMaps.get(element) || new Map();
2932 animationMaps.set(element, map);
2933 return map;
2934 }
2935
2936 /**
2937 * Parse Framer's special CSS variable format into a CSS token and a fallback.
2938 *
2939 * ```
2940 * `var(--foo, #fff)` => [`--foo`, '#fff']
2941 * ```
2942 *
2943 * @param current
2944 */
2945 const splitCSSVariableRegex =
2946 // eslint-disable-next-line redos-detector/no-unsafe-regex -- false positive, as it can match a lot of words
2947 /^var\(--(?:([\w-]+)|([\w-]+), ?([a-zA-Z\d ()%#.,-]+))\)/u;
2948 function parseCSSVariable(current) {
2949 const match = splitCSSVariableRegex.exec(current);
2950 if (!match)
2951 return [,];
2952 const [, token1, token2, fallback] = match;
2953 return [`--${token1 ?? token2}`, fallback];
2954 }
2955 const maxDepth = 4;
2956 function getVariableValue(current, element, depth = 1) {
2957 exports.invariant(depth <= maxDepth, `Max CSS variable fallback depth detected in property "${current}". This may indicate a circular fallback dependency.`, "max-css-var-depth");
2958 const [token, fallback] = parseCSSVariable(current);
2959 // No CSS variable detected
2960 if (!token)
2961 return;
2962 // Attempt to read this CSS variable off the element
2963 const resolved = window.getComputedStyle(element).getPropertyValue(token);
2964 if (resolved) {
2965 const trimmed = resolved.trim();
2966 return isNumericalString(trimmed) ? parseFloat(trimmed) : trimmed;
2967 }
2968 return isCSSVariableToken(fallback)
2969 ? getVariableValue(fallback, element, depth + 1)
2970 : fallback;
2971 }
2972
2973 function getValueTransition$1(transition, key) {
2974 return (transition?.[key] ??
2975 transition?.["default"] ??
2976 transition);
2977 }
2978
2979 const positionalKeys = new Set([
2980 "width",
2981 "height",
2982 "top",
2983 "left",
2984 "right",
2985 "bottom",
2986 ...transformPropOrder,
2987 ]);
2988
2989 /**
2990 * ValueType for "auto"
2991 */
2992 const auto = {
2993 test: (v) => v === "auto",
2994 parse: (v) => v,
2995 };
2996
2997 /**
2998 * Tests a provided value against a ValueType
2999 */
3000 const testValueType = (v) => (type) => type.test(v);
3001
3002 /**
3003 * A list of value types commonly used for dimensions
3004 */
3005 const dimensionValueTypes = [number, px, percent, degrees, vw, vh, auto];
3006 /**
3007 * Tests a dimensional value against the list of dimension ValueTypes
3008 */
3009 const findDimensionValueType = (v) => dimensionValueTypes.find(testValueType(v));
3010
3011 function isNone(value) {
3012 if (typeof value === "number") {
3013 return value === 0;
3014 }
3015 else if (value !== null) {
3016 return value === "none" || value === "0" || isZeroValueString(value);
3017 }
3018 else {
3019 return true;
3020 }
3021 }
3022
3023 /**
3024 * Properties that should default to 1 or 100%
3025 */
3026 const maxDefaults = new Set(["brightness", "contrast", "saturate", "opacity"]);
3027 function applyDefaultFilter(v) {
3028 const [name, value] = v.slice(0, -1).split("(");
3029 if (name === "drop-shadow")
3030 return v;
3031 const [number] = value.match(floatRegex) || [];
3032 if (!number)
3033 return v;
3034 const unit = value.replace(number, "");
3035 let defaultValue = maxDefaults.has(name) ? 1 : 0;
3036 if (number !== value)
3037 defaultValue *= 100;
3038 return name + "(" + defaultValue + unit + ")";
3039 }
3040 const functionRegex = /\b([a-z-]*)\(.*?\)/gu;
3041 const filter = {
3042 ...complex,
3043 getAnimatableNone: (v) => {
3044 const functions = v.match(functionRegex);
3045 return functions ? functions.map(applyDefaultFilter).join(" ") : v;
3046 },
3047 };
3048
3049 const int = {
3050 ...number,
3051 transform: Math.round,
3052 };
3053
3054 const transformValueTypes = {
3055 rotate: degrees,
3056 rotateX: degrees,
3057 rotateY: degrees,
3058 rotateZ: degrees,
3059 scale,
3060 scaleX: scale,
3061 scaleY: scale,
3062 scaleZ: scale,
3063 skew: degrees,
3064 skewX: degrees,
3065 skewY: degrees,
3066 distance: px,
3067 translateX: px,
3068 translateY: px,
3069 translateZ: px,
3070 x: px,
3071 y: px,
3072 z: px,
3073 perspective: px,
3074 transformPerspective: px,
3075 opacity: alpha,
3076 originX: progressPercentage,
3077 originY: progressPercentage,
3078 originZ: px,
3079 };
3080
3081 const numberValueTypes = {
3082 // Border props
3083 borderWidth: px,
3084 borderTopWidth: px,
3085 borderRightWidth: px,
3086 borderBottomWidth: px,
3087 borderLeftWidth: px,
3088 borderRadius: px,
3089 radius: px,
3090 borderTopLeftRadius: px,
3091 borderTopRightRadius: px,
3092 borderBottomRightRadius: px,
3093 borderBottomLeftRadius: px,
3094 // Positioning props
3095 width: px,
3096 maxWidth: px,
3097 height: px,
3098 maxHeight: px,
3099 top: px,
3100 right: px,
3101 bottom: px,
3102 left: px,
3103 // Spacing props
3104 padding: px,
3105 paddingTop: px,
3106 paddingRight: px,
3107 paddingBottom: px,
3108 paddingLeft: px,
3109 margin: px,
3110 marginTop: px,
3111 marginRight: px,
3112 marginBottom: px,
3113 marginLeft: px,
3114 // Misc
3115 backgroundPositionX: px,
3116 backgroundPositionY: px,
3117 ...transformValueTypes,
3118 zIndex: int,
3119 // SVG
3120 fillOpacity: alpha,
3121 strokeOpacity: alpha,
3122 numOctaves: int,
3123 };
3124
3125 /**
3126 * A map of default value types for common values
3127 */
3128 const defaultValueTypes = {
3129 ...numberValueTypes,
3130 // Color props
3131 color,
3132 backgroundColor: color,
3133 outlineColor: color,
3134 fill: color,
3135 stroke: color,
3136 // Border props
3137 borderColor: color,
3138 borderTopColor: color,
3139 borderRightColor: color,
3140 borderBottomColor: color,
3141 borderLeftColor: color,
3142 filter,
3143 WebkitFilter: filter,
3144 };
3145 /**
3146 * Gets the default ValueType for the provided value key
3147 */
3148 const getDefaultValueType = (key) => defaultValueTypes[key];
3149
3150 function getAnimatableNone(key, value) {
3151 let defaultValueType = getDefaultValueType(key);
3152 if (defaultValueType !== filter)
3153 defaultValueType = complex;
3154 // If value is not recognised as animatable, ie "none", create an animatable version origin based on the target
3155 return defaultValueType.getAnimatableNone
3156 ? defaultValueType.getAnimatableNone(value)
3157 : undefined;
3158 }
3159
3160 /**
3161 * If we encounter keyframes like "none" or "0" and we also have keyframes like
3162 * "#fff" or "200px 200px" we want to find a keyframe to serve as a template for
3163 * the "none" keyframes. In this case "#fff" or "200px 200px" - then these get turned into
3164 * zero equivalents, i.e. "#fff0" or "0px 0px".
3165 */
3166 const invalidTemplates = new Set(["auto", "none", "0"]);
3167 function makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name) {
3168 let i = 0;
3169 let animatableTemplate = undefined;
3170 while (i < unresolvedKeyframes.length && !animatableTemplate) {
3171 const keyframe = unresolvedKeyframes[i];
3172 if (typeof keyframe === "string" &&
3173 !invalidTemplates.has(keyframe) &&
3174 analyseComplexValue(keyframe).values.length) {
3175 animatableTemplate = unresolvedKeyframes[i];
3176 }
3177 i++;
3178 }
3179 if (animatableTemplate && name) {
3180 for (const noneIndex of noneKeyframeIndexes) {
3181 unresolvedKeyframes[noneIndex] = getAnimatableNone(name, animatableTemplate);
3182 }
3183 }
3184 }
3185
3186 class DOMKeyframesResolver extends KeyframeResolver {
3187 constructor(unresolvedKeyframes, onComplete, name, motionValue, element) {
3188 super(unresolvedKeyframes, onComplete, name, motionValue, element, true);
3189 }
3190 readKeyframes() {
3191 const { unresolvedKeyframes, element, name } = this;
3192 if (!element || !element.current)
3193 return;
3194 super.readKeyframes();
3195 /**
3196 * If any keyframe is a CSS variable, we need to find its value by sampling the element
3197 */
3198 for (let i = 0; i < unresolvedKeyframes.length; i++) {
3199 let keyframe = unresolvedKeyframes[i];
3200 if (typeof keyframe === "string") {
3201 keyframe = keyframe.trim();
3202 if (isCSSVariableToken(keyframe)) {
3203 const resolved = getVariableValue(keyframe, element.current);
3204 if (resolved !== undefined) {
3205 unresolvedKeyframes[i] = resolved;
3206 }
3207 if (i === unresolvedKeyframes.length - 1) {
3208 this.finalKeyframe = keyframe;
3209 }
3210 }
3211 }
3212 }
3213 /**
3214 * Resolve "none" values. We do this potentially twice - once before and once after measuring keyframes.
3215 * This could be seen as inefficient but it's a trade-off to avoid measurements in more situations, which
3216 * have a far bigger performance impact.
3217 */
3218 this.resolveNoneKeyframes();
3219 /**
3220 * Check to see if unit type has changed. If so schedule jobs that will
3221 * temporarily set styles to the destination keyframes.
3222 * Skip if we have more than two keyframes or this isn't a positional value.
3223 * TODO: We can throw if there are multiple keyframes and the value type changes.
3224 */
3225 if (!positionalKeys.has(name) || unresolvedKeyframes.length !== 2) {
3226 return;
3227 }
3228 const [origin, target] = unresolvedKeyframes;
3229 const originType = findDimensionValueType(origin);
3230 const targetType = findDimensionValueType(target);
3231 /**
3232 * Either we don't recognise these value types or we can animate between them.
3233 */
3234 if (originType === targetType)
3235 return;
3236 /**
3237 * If both values are numbers or pixels, we can animate between them by
3238 * converting them to numbers.
3239 */
3240 if (isNumOrPxType(originType) && isNumOrPxType(targetType)) {
3241 for (let i = 0; i < unresolvedKeyframes.length; i++) {
3242 const value = unresolvedKeyframes[i];
3243 if (typeof value === "string") {
3244 unresolvedKeyframes[i] = parseFloat(value);
3245 }
3246 }
3247 }
3248 else if (positionalValues[name]) {
3249 /**
3250 * Else, the only way to resolve this is by measuring the element.
3251 */
3252 this.needsMeasurement = true;
3253 }
3254 }
3255 resolveNoneKeyframes() {
3256 const { unresolvedKeyframes, name } = this;
3257 const noneKeyframeIndexes = [];
3258 for (let i = 0; i < unresolvedKeyframes.length; i++) {
3259 if (unresolvedKeyframes[i] === null ||
3260 isNone(unresolvedKeyframes[i])) {
3261 noneKeyframeIndexes.push(i);
3262 }
3263 }
3264 if (noneKeyframeIndexes.length) {
3265 makeNoneKeyframesAnimatable(unresolvedKeyframes, noneKeyframeIndexes, name);
3266 }
3267 }
3268 measureInitialState() {
3269 const { element, unresolvedKeyframes, name } = this;
3270 if (!element || !element.current)
3271 return;
3272 if (name === "height") {
3273 this.suspendedScrollY = window.pageYOffset;
3274 }
3275 this.measuredOrigin = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
3276 unresolvedKeyframes[0] = this.measuredOrigin;
3277 // Set final key frame to measure after next render
3278 const measureKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
3279 if (measureKeyframe !== undefined) {
3280 element.getValue(name, measureKeyframe).jump(measureKeyframe, false);
3281 }
3282 }
3283 measureEndState() {
3284 const { element, name, unresolvedKeyframes } = this;
3285 if (!element || !element.current)
3286 return;
3287 const value = element.getValue(name);
3288 value && value.jump(this.measuredOrigin, false);
3289 const finalKeyframeIndex = unresolvedKeyframes.length - 1;
3290 const finalKeyframe = unresolvedKeyframes[finalKeyframeIndex];
3291 unresolvedKeyframes[finalKeyframeIndex] = positionalValues[name](element.measureViewportBox(), window.getComputedStyle(element.current));
3292 if (finalKeyframe !== null && this.finalKeyframe === undefined) {
3293 this.finalKeyframe = finalKeyframe;
3294 }
3295 // If we removed transform values, reapply them before the next render
3296 if (this.removedTransforms?.length) {
3297 this.removedTransforms.forEach(([unsetTransformName, unsetTransformValue]) => {
3298 element
3299 .getValue(unsetTransformName)
3300 .set(unsetTransformValue);
3301 });
3302 }
3303 this.resolveNoneKeyframes();
3304 }
3305 }
3306
3307 const pxValues = new Set([
3308 // Border props
3309 "borderWidth",
3310 "borderTopWidth",
3311 "borderRightWidth",
3312 "borderBottomWidth",
3313 "borderLeftWidth",
3314 "borderRadius",
3315 "radius",
3316 "borderTopLeftRadius",
3317 "borderTopRightRadius",
3318 "borderBottomRightRadius",
3319 "borderBottomLeftRadius",
3320 // Positioning props
3321 "width",
3322 "maxWidth",
3323 "height",
3324 "maxHeight",
3325 "top",
3326 "right",
3327 "bottom",
3328 "left",
3329 // Spacing props
3330 "padding",
3331 "paddingTop",
3332 "paddingRight",
3333 "paddingBottom",
3334 "paddingLeft",
3335 "margin",
3336 "marginTop",
3337 "marginRight",
3338 "marginBottom",
3339 "marginLeft",
3340 // Misc
3341 "backgroundPositionX",
3342 "backgroundPositionY",
3343 ]);
3344
3345 function applyPxDefaults(keyframes, name) {
3346 for (let i = 0; i < keyframes.length; i++) {
3347 if (typeof keyframes[i] === "number" && pxValues.has(name)) {
3348 keyframes[i] = keyframes[i] + "px";
3349 }
3350 }
3351 }
3352
3353 function isWaapiSupportedEasing(easing) {
3354 return Boolean((typeof easing === "function" && supportsLinearEasing()) ||
3355 !easing ||
3356 (typeof easing === "string" &&
3357 (easing in supportedWaapiEasing || supportsLinearEasing())) ||
3358 isBezierDefinition(easing) ||
3359 (Array.isArray(easing) && easing.every(isWaapiSupportedEasing)));
3360 }
3361
3362 const supportsPartialKeyframes = /*@__PURE__*/ memo(() => {
3363 try {
3364 document.createElement("div").animate({ opacity: [1] });
3365 }
3366 catch (e) {
3367 return false;
3368 }
3369 return true;
3370 });
3371
3372 /**
3373 * A list of values that can be hardware-accelerated.
3374 */
3375 const acceleratedValues = new Set([
3376 "opacity",
3377 "clipPath",
3378 "filter",
3379 "transform",
3380 // TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved
3381 // or until we implement support for linear() easing.
3382 // "background-color"
3383 ]);
3384
3385 function camelToDash$1(str) {
3386 return str.replace(/([A-Z])/g, (match) => `-${match.toLowerCase()}`);
3387 }
3388
3389 function resolveElements(elementOrSelector, scope, selectorCache) {
3390 if (elementOrSelector instanceof EventTarget) {
3391 return [elementOrSelector];
3392 }
3393 else if (typeof elementOrSelector === "string") {
3394 let root = document;
3395 if (scope) {
3396 root = scope.current;
3397 }
3398 const elements = selectorCache?.[elementOrSelector] ??
3399 root.querySelectorAll(elementOrSelector);
3400 return elements ? Array.from(elements) : [];
3401 }
3402 return Array.from(elementOrSelector);
3403 }
3404
3405 function createSelectorEffect(subjectEffect) {
3406 return (subject, values) => {
3407 const elements = resolveElements(subject);
3408 const subscriptions = [];
3409 for (const element of elements) {
3410 const remove = subjectEffect(element, values);
3411 subscriptions.push(remove);
3412 }
3413 return () => {
3414 for (const remove of subscriptions)
3415 remove();
3416 };
3417 };
3418 }
3419
3420 /**
3421 * Provided a value and a ValueType, returns the value as that value type.
3422 */
3423 const getValueAsType = (value, type) => {
3424 return type && typeof value === "number"
3425 ? type.transform(value)
3426 : value;
3427 };
3428
3429 class MotionValueState {
3430 constructor() {
3431 this.latest = {};
3432 this.values = new Map();
3433 }
3434 set(name, value, render, computed, useDefaultValueType = true) {
3435 const existingValue = this.values.get(name);
3436 if (existingValue) {
3437 existingValue.onRemove();
3438 }
3439 const onChange = () => {
3440 const v = value.get();
3441 if (useDefaultValueType) {
3442 this.latest[name] = getValueAsType(v, numberValueTypes[name]);
3443 }
3444 else {
3445 this.latest[name] = v;
3446 }
3447 render && frame.render(render);
3448 };
3449 onChange();
3450 const cancelOnChange = value.on("change", onChange);
3451 computed && value.addDependent(computed);
3452 const remove = () => {
3453 cancelOnChange();
3454 render && cancelFrame(render);
3455 this.values.delete(name);
3456 computed && value.removeDependent(computed);
3457 };
3458 this.values.set(name, { value, onRemove: remove });
3459 return remove;
3460 }
3461 get(name) {
3462 return this.values.get(name)?.value;
3463 }
3464 destroy() {
3465 for (const value of this.values.values()) {
3466 value.onRemove();
3467 }
3468 }
3469 }
3470
3471 function createEffect(addValue) {
3472 const stateCache = new WeakMap();
3473 const subscriptions = [];
3474 return (subject, values) => {
3475 const state = stateCache.get(subject) ?? new MotionValueState();
3476 stateCache.set(subject, state);
3477 for (const key in values) {
3478 const value = values[key];
3479 const remove = addValue(subject, state, key, value);
3480 subscriptions.push(remove);
3481 }
3482 return () => {
3483 for (const cancel of subscriptions)
3484 cancel();
3485 };
3486 };
3487 }
3488
3489 function canSetAsProperty(element, name) {
3490 if (!(name in element))
3491 return false;
3492 const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(element), name) ||
3493 Object.getOwnPropertyDescriptor(element, name);
3494 // Check if it has a setter
3495 return descriptor && typeof descriptor.set === "function";
3496 }
3497 const addAttrValue = (element, state, key, value) => {
3498 const isProp = canSetAsProperty(element, key);
3499 const name = isProp
3500 ? key
3501 : key.startsWith("data") || key.startsWith("aria")
3502 ? camelToDash$1(key)
3503 : key;
3504 /**
3505 * Set attribute directly via property if available
3506 */
3507 const render = isProp
3508 ? () => {
3509 element[name] = state.latest[key];
3510 }
3511 : () => {
3512 const v = state.latest[key];
3513 if (v === null || v === undefined) {
3514 element.removeAttribute(name);
3515 }
3516 else {
3517 element.setAttribute(name, String(v));
3518 }
3519 };
3520 return state.set(key, value, render);
3521 };
3522 const attrEffect = /*@__PURE__*/ createSelectorEffect(
3523 /*@__PURE__*/ createEffect(addAttrValue));
3524
3525 const propEffect = /*@__PURE__*/ createEffect((subject, state, key, value) => {
3526 return state.set(key, value, () => {
3527 subject[key] = state.latest[key];
3528 }, undefined, false);
3529 });
3530
3531 /**
3532 * Checks if an element is an HTML element in a way
3533 * that works across iframes
3534 */
3535 function isHTMLElement(element) {
3536 return isObject(element) && "offsetHeight" in element;
3537 }
3538
3539 /**
3540 * Maximum time between the value of two frames, beyond which we
3541 * assume the velocity has since been 0.
3542 */
3543 const MAX_VELOCITY_DELTA = 30;
3544 const isFloat = (value) => {
3545 return !isNaN(parseFloat(value));
3546 };
3547 const collectMotionValues = {
3548 current: undefined,
3549 };
3550 /**
3551 * `MotionValue` is used to track the state and velocity of motion values.
3552 *
3553 * @public
3554 */
3555 class MotionValue {
3556 /**
3557 * @param init - The initiating value
3558 * @param config - Optional configuration options
3559 *
3560 * - `transformer`: A function to transform incoming values with.
3561 */
3562 constructor(init, options = {}) {
3563 /**
3564 * Tracks whether this value can output a velocity. Currently this is only true
3565 * if the value is numerical, but we might be able to widen the scope here and support
3566 * other value types.
3567 *
3568 * @internal
3569 */
3570 this.canTrackVelocity = null;
3571 /**
3572 * An object containing a SubscriptionManager for each active event.
3573 */
3574 this.events = {};
3575 this.updateAndNotify = (v) => {
3576 const currentTime = time.now();
3577 /**
3578 * If we're updating the value during another frame or eventloop
3579 * than the previous frame, then the we set the previous frame value
3580 * to current.
3581 */
3582 if (this.updatedAt !== currentTime) {
3583 this.setPrevFrameValue();
3584 }
3585 this.prev = this.current;
3586 this.setCurrent(v);
3587 // Update update subscribers
3588 if (this.current !== this.prev) {
3589 this.events.change?.notify(this.current);
3590 if (this.dependents) {
3591 for (const dependent of this.dependents) {
3592 dependent.dirty();
3593 }
3594 }
3595 }
3596 };
3597 this.hasAnimated = false;
3598 this.setCurrent(init);
3599 this.owner = options.owner;
3600 }
3601 setCurrent(current) {
3602 this.current = current;
3603 this.updatedAt = time.now();
3604 if (this.canTrackVelocity === null && current !== undefined) {
3605 this.canTrackVelocity = isFloat(this.current);
3606 }
3607 }
3608 setPrevFrameValue(prevFrameValue = this.current) {
3609 this.prevFrameValue = prevFrameValue;
3610 this.prevUpdatedAt = this.updatedAt;
3611 }
3612 /**
3613 * Adds a function that will be notified when the `MotionValue` is updated.
3614 *
3615 * It returns a function that, when called, will cancel the subscription.
3616 *
3617 * When calling `onChange` inside a React component, it should be wrapped with the
3618 * `useEffect` hook. As it returns an unsubscribe function, this should be returned
3619 * from the `useEffect` function to ensure you don't add duplicate subscribers..
3620 *
3621 * ```jsx
3622 * export const MyComponent = () => {
3623 * const x = useMotionValue(0)
3624 * const y = useMotionValue(0)
3625 * const opacity = useMotionValue(1)
3626 *
3627 * useEffect(() => {
3628 * function updateOpacity() {
3629 * const maxXY = Math.max(x.get(), y.get())
3630 * const newOpacity = transform(maxXY, [0, 100], [1, 0])
3631 * opacity.set(newOpacity)
3632 * }
3633 *
3634 * const unsubscribeX = x.on("change", updateOpacity)
3635 * const unsubscribeY = y.on("change", updateOpacity)
3636 *
3637 * return () => {
3638 * unsubscribeX()
3639 * unsubscribeY()
3640 * }
3641 * }, [])
3642 *
3643 * return <motion.div style={{ x }} />
3644 * }
3645 * ```
3646 *
3647 * @param subscriber - A function that receives the latest value.
3648 * @returns A function that, when called, will cancel this subscription.
3649 *
3650 * @deprecated
3651 */
3652 onChange(subscription) {
3653 {
3654 warnOnce(false, `value.onChange(callback) is deprecated. Switch to value.on("change", callback).`);
3655 }
3656 return this.on("change", subscription);
3657 }
3658 on(eventName, callback) {
3659 if (!this.events[eventName]) {
3660 this.events[eventName] = new SubscriptionManager();
3661 }
3662 const unsubscribe = this.events[eventName].add(callback);
3663 if (eventName === "change") {
3664 return () => {
3665 unsubscribe();
3666 /**
3667 * If we have no more change listeners by the start
3668 * of the next frame, stop active animations.
3669 */
3670 frame.read(() => {
3671 if (!this.events.change.getSize()) {
3672 this.stop();
3673 }
3674 });
3675 };
3676 }
3677 return unsubscribe;
3678 }
3679 clearListeners() {
3680 for (const eventManagers in this.events) {
3681 this.events[eventManagers].clear();
3682 }
3683 }
3684 /**
3685 * Attaches a passive effect to the `MotionValue`.
3686 */
3687 attach(passiveEffect, stopPassiveEffect) {
3688 this.passiveEffect = passiveEffect;
3689 this.stopPassiveEffect = stopPassiveEffect;
3690 }
3691 /**
3692 * Sets the state of the `MotionValue`.
3693 *
3694 * @remarks
3695 *
3696 * ```jsx
3697 * const x = useMotionValue(0)
3698 * x.set(10)
3699 * ```
3700 *
3701 * @param latest - Latest value to set.
3702 * @param render - Whether to notify render subscribers. Defaults to `true`
3703 *
3704 * @public
3705 */
3706 set(v) {
3707 if (!this.passiveEffect) {
3708 this.updateAndNotify(v);
3709 }
3710 else {
3711 this.passiveEffect(v, this.updateAndNotify);
3712 }
3713 }
3714 setWithVelocity(prev, current, delta) {
3715 this.set(current);
3716 this.prev = undefined;
3717 this.prevFrameValue = prev;
3718 this.prevUpdatedAt = this.updatedAt - delta;
3719 }
3720 /**
3721 * Set the state of the `MotionValue`, stopping any active animations,
3722 * effects, and resets velocity to `0`.
3723 */
3724 jump(v, endAnimation = true) {
3725 this.updateAndNotify(v);
3726 this.prev = v;
3727 this.prevUpdatedAt = this.prevFrameValue = undefined;
3728 endAnimation && this.stop();
3729 if (this.stopPassiveEffect)
3730 this.stopPassiveEffect();
3731 }
3732 dirty() {
3733 this.events.change?.notify(this.current);
3734 }
3735 addDependent(dependent) {
3736 if (!this.dependents) {
3737 this.dependents = new Set();
3738 }
3739 this.dependents.add(dependent);
3740 }
3741 removeDependent(dependent) {
3742 if (this.dependents) {
3743 this.dependents.delete(dependent);
3744 }
3745 }
3746 /**
3747 * Returns the latest state of `MotionValue`
3748 *
3749 * @returns - The latest state of `MotionValue`
3750 *
3751 * @public
3752 */
3753 get() {
3754 if (collectMotionValues.current) {
3755 collectMotionValues.current.push(this);
3756 }
3757 return this.current;
3758 }
3759 /**
3760 * @public
3761 */
3762 getPrevious() {
3763 return this.prev;
3764 }
3765 /**
3766 * Returns the latest velocity of `MotionValue`
3767 *
3768 * @returns - The latest velocity of `MotionValue`. Returns `0` if the state is non-numerical.
3769 *
3770 * @public
3771 */
3772 getVelocity() {
3773 const currentTime = time.now();
3774 if (!this.canTrackVelocity ||
3775 this.prevFrameValue === undefined ||
3776 currentTime - this.updatedAt > MAX_VELOCITY_DELTA) {
3777 return 0;
3778 }
3779 const delta = Math.min(this.updatedAt - this.prevUpdatedAt, MAX_VELOCITY_DELTA);
3780 // Casts because of parseFloat's poor typing
3781 return velocityPerSecond(parseFloat(this.current) -
3782 parseFloat(this.prevFrameValue), delta);
3783 }
3784 /**
3785 * Registers a new animation to control this `MotionValue`. Only one
3786 * animation can drive a `MotionValue` at one time.
3787 *
3788 * ```jsx
3789 * value.start()
3790 * ```
3791 *
3792 * @param animation - A function that starts the provided animation
3793 */
3794 start(startAnimation) {
3795 this.stop();
3796 return new Promise((resolve) => {
3797 this.hasAnimated = true;
3798 this.animation = startAnimation(resolve);
3799 if (this.events.animationStart) {
3800 this.events.animationStart.notify();
3801 }
3802 }).then(() => {
3803 if (this.events.animationComplete) {
3804 this.events.animationComplete.notify();
3805 }
3806 this.clearAnimation();
3807 });
3808 }
3809 /**
3810 * Stop the currently active animation.
3811 *
3812 * @public
3813 */
3814 stop() {
3815 if (this.animation) {
3816 this.animation.stop();
3817 if (this.events.animationCancel) {
3818 this.events.animationCancel.notify();
3819 }
3820 }
3821 this.clearAnimation();
3822 }
3823 /**
3824 * Returns `true` if this value is currently animating.
3825 *
3826 * @public
3827 */
3828 isAnimating() {
3829 return !!this.animation;
3830 }
3831 clearAnimation() {
3832 delete this.animation;
3833 }
3834 /**
3835 * Destroy and clean up subscribers to this `MotionValue`.
3836 *
3837 * The `MotionValue` hooks like `useMotionValue` and `useTransform` automatically
3838 * handle the lifecycle of the returned `MotionValue`, so this method is only necessary if you've manually
3839 * created a `MotionValue` via the `motionValue` function.
3840 *
3841 * @public
3842 */
3843 destroy() {
3844 this.dependents?.clear();
3845 this.events.destroy?.notify();
3846 this.clearListeners();
3847 this.stop();
3848 if (this.stopPassiveEffect) {
3849 this.stopPassiveEffect();
3850 }
3851 }
3852 }
3853 function motionValue(init, options) {
3854 return new MotionValue(init, options);
3855 }
3856
3857 const translateAlias$1 = {
3858 x: "translateX",
3859 y: "translateY",
3860 z: "translateZ",
3861 transformPerspective: "perspective",
3862 };
3863 function buildTransform$1(state) {
3864 let transform = "";
3865 let transformIsDefault = true;
3866 /**
3867 * Loop over all possible transforms in order, adding the ones that
3868 * are present to the transform string.
3869 */
3870 for (let i = 0; i < transformPropOrder.length; i++) {
3871 const key = transformPropOrder[i];
3872 const value = state.latest[key];
3873 if (value === undefined)
3874 continue;
3875 let valueIsDefault = true;
3876 if (typeof value === "number") {
3877 valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
3878 }
3879 else {
3880 valueIsDefault = parseFloat(value) === 0;
3881 }
3882 if (!valueIsDefault) {
3883 transformIsDefault = false;
3884 const transformName = translateAlias$1[key] || key;
3885 const valueToRender = state.latest[key];
3886 transform += `${transformName}(${valueToRender}) `;
3887 }
3888 }
3889 return transformIsDefault ? "none" : transform.trim();
3890 }
3891
3892 const originProps = new Set(["originX", "originY", "originZ"]);
3893 const addStyleValue = (element, state, key, value) => {
3894 let render = undefined;
3895 let computed = undefined;
3896 if (transformProps.has(key)) {
3897 if (!state.get("transform")) {
3898 // If this is an HTML element, we need to set the transform-box to fill-box
3899 // to normalise the transform relative to the element's bounding box
3900 if (!isHTMLElement(element) && !state.get("transformBox")) {
3901 addStyleValue(element, state, "transformBox", new MotionValue("fill-box"));
3902 }
3903 state.set("transform", new MotionValue("none"), () => {
3904 element.style.transform = buildTransform$1(state);
3905 });
3906 }
3907 computed = state.get("transform");
3908 }
3909 else if (originProps.has(key)) {
3910 if (!state.get("transformOrigin")) {
3911 state.set("transformOrigin", new MotionValue(""), () => {
3912 const originX = state.latest.originX ?? "50%";
3913 const originY = state.latest.originY ?? "50%";
3914 const originZ = state.latest.originZ ?? 0;
3915 element.style.transformOrigin = `${originX} ${originY} ${originZ}`;
3916 });
3917 }
3918 computed = state.get("transformOrigin");
3919 }
3920 else if (isCSSVar(key)) {
3921 render = () => {
3922 element.style.setProperty(key, state.latest[key]);
3923 };
3924 }
3925 else {
3926 render = () => {
3927 element.style[key] = state.latest[key];
3928 };
3929 }
3930 return state.set(key, value, render, computed);
3931 };
3932 const styleEffect = /*@__PURE__*/ createSelectorEffect(
3933 /*@__PURE__*/ createEffect(addStyleValue));
3934
3935 const toPx = px.transform;
3936 function addSVGPathValue(element, state, key, value) {
3937 frame.render(() => element.setAttribute("pathLength", "1"));
3938 if (key === "pathOffset") {
3939 return state.set(key, value, () => element.setAttribute("stroke-dashoffset", toPx(-state.latest[key])));
3940 }
3941 else {
3942 if (!state.get("stroke-dasharray")) {
3943 state.set("stroke-dasharray", new MotionValue("1 1"), () => {
3944 const { pathLength = 1, pathSpacing } = state.latest;
3945 element.setAttribute("stroke-dasharray", `${toPx(pathLength)} ${toPx(pathSpacing ?? 1 - Number(pathLength))}`);
3946 });
3947 }
3948 return state.set(key, value, undefined, state.get("stroke-dasharray"));
3949 }
3950 }
3951 const addSVGValue = (element, state, key, value) => {
3952 if (key.startsWith("path")) {
3953 return addSVGPathValue(element, state, key, value);
3954 }
3955 else if (key.startsWith("attr")) {
3956 return addAttrValue(element, state, convertAttrKey(key), value);
3957 }
3958 const handler = key in element.style ? addStyleValue : addAttrValue;
3959 return handler(element, state, key, value);
3960 };
3961 const svgEffect = /*@__PURE__*/ createSelectorEffect(
3962 /*@__PURE__*/ createEffect(addSVGValue));
3963 function convertAttrKey(key) {
3964 return key.replace(/^attr([A-Z])/, (_, firstChar) => firstChar.toLowerCase());
3965 }
3966
3967 const { schedule: microtask, cancel: cancelMicrotask } =
3968 /* @__PURE__ */ createRenderBatcher(queueMicrotask, false);
3969
3970 const isDragging = {
3971 x: false,
3972 y: false,
3973 };
3974 function isDragActive() {
3975 return isDragging.x || isDragging.y;
3976 }
3977
3978 function setDragLock(axis) {
3979 if (axis === "x" || axis === "y") {
3980 if (isDragging[axis]) {
3981 return null;
3982 }
3983 else {
3984 isDragging[axis] = true;
3985 return () => {
3986 isDragging[axis] = false;
3987 };
3988 }
3989 }
3990 else {
3991 if (isDragging.x || isDragging.y) {
3992 return null;
3993 }
3994 else {
3995 isDragging.x = isDragging.y = true;
3996 return () => {
3997 isDragging.x = isDragging.y = false;
3998 };
3999 }
4000 }
4001 }
4002
4003 function setupGesture(elementOrSelector, options) {
4004 const elements = resolveElements(elementOrSelector);
4005 const gestureAbortController = new AbortController();
4006 const eventOptions = {
4007 passive: true,
4008 ...options,
4009 signal: gestureAbortController.signal,
4010 };
4011 const cancel = () => gestureAbortController.abort();
4012 return [elements, eventOptions, cancel];
4013 }
4014
4015 function isValidHover(event) {
4016 return !(event.pointerType === "touch" || isDragActive());
4017 }
4018 /**
4019 * Create a hover gesture. hover() is different to .addEventListener("pointerenter")
4020 * in that it has an easier syntax, filters out polyfilled touch events, interoperates
4021 * with drag gestures, and automatically removes the "pointerennd" event listener when the hover ends.
4022 *
4023 * @public
4024 */
4025 function hover(elementOrSelector, onHoverStart, options = {}) {
4026 const [elements, eventOptions, cancel] = setupGesture(elementOrSelector, options);
4027 const onPointerEnter = (enterEvent) => {
4028 if (!isValidHover(enterEvent))
4029 return;
4030 const { target } = enterEvent;
4031 const onHoverEnd = onHoverStart(target, enterEvent);
4032 if (typeof onHoverEnd !== "function" || !target)
4033 return;
4034 const onPointerLeave = (leaveEvent) => {
4035 if (!isValidHover(leaveEvent))
4036 return;
4037 onHoverEnd(leaveEvent);
4038 target.removeEventListener("pointerleave", onPointerLeave);
4039 };
4040 target.addEventListener("pointerleave", onPointerLeave, eventOptions);
4041 };
4042 elements.forEach((element) => {
4043 element.addEventListener("pointerenter", onPointerEnter, eventOptions);
4044 });
4045 return cancel;
4046 }
4047
4048 /**
4049 * Recursively traverse up the tree to check whether the provided child node
4050 * is the parent or a descendant of it.
4051 *
4052 * @param parent - Element to find
4053 * @param child - Element to test against parent
4054 */
4055 const isNodeOrChild = (parent, child) => {
4056 if (!child) {
4057 return false;
4058 }
4059 else if (parent === child) {
4060 return true;
4061 }
4062 else {
4063 return isNodeOrChild(parent, child.parentElement);
4064 }
4065 };
4066
4067 const isPrimaryPointer = (event) => {
4068 if (event.pointerType === "mouse") {
4069 return typeof event.button !== "number" || event.button <= 0;
4070 }
4071 else {
4072 /**
4073 * isPrimary is true for all mice buttons, whereas every touch point
4074 * is regarded as its own input. So subsequent concurrent touch points
4075 * will be false.
4076 *
4077 * Specifically match against false here as incomplete versions of
4078 * PointerEvents in very old browser might have it set as undefined.
4079 */
4080 return event.isPrimary !== false;
4081 }
4082 };
4083
4084 const focusableElements = new Set([
4085 "BUTTON",
4086 "INPUT",
4087 "SELECT",
4088 "TEXTAREA",
4089 "A",
4090 ]);
4091 function isElementKeyboardAccessible(element) {
4092 return (focusableElements.has(element.tagName) ||
4093 element.tabIndex !== -1);
4094 }
4095
4096 const isPressing = new WeakSet();
4097
4098 /**
4099 * Filter out events that are not "Enter" keys.
4100 */
4101 function filterEvents(callback) {
4102 return (event) => {
4103 if (event.key !== "Enter")
4104 return;
4105 callback(event);
4106 };
4107 }
4108 function firePointerEvent(target, type) {
4109 target.dispatchEvent(new PointerEvent("pointer" + type, { isPrimary: true, bubbles: true }));
4110 }
4111 const enableKeyboardPress = (focusEvent, eventOptions) => {
4112 const element = focusEvent.currentTarget;
4113 if (!element)
4114 return;
4115 const handleKeydown = filterEvents(() => {
4116 if (isPressing.has(element))
4117 return;
4118 firePointerEvent(element, "down");
4119 const handleKeyup = filterEvents(() => {
4120 firePointerEvent(element, "up");
4121 });
4122 const handleBlur = () => firePointerEvent(element, "cancel");
4123 element.addEventListener("keyup", handleKeyup, eventOptions);
4124 element.addEventListener("blur", handleBlur, eventOptions);
4125 });
4126 element.addEventListener("keydown", handleKeydown, eventOptions);
4127 /**
4128 * Add an event listener that fires on blur to remove the keydown events.
4129 */
4130 element.addEventListener("blur", () => element.removeEventListener("keydown", handleKeydown), eventOptions);
4131 };
4132
4133 /**
4134 * Filter out events that are not primary pointer events, or are triggering
4135 * while a Motion gesture is active.
4136 */
4137 function isValidPressEvent(event) {
4138 return isPrimaryPointer(event) && !isDragActive();
4139 }
4140 /**
4141 * Create a press gesture.
4142 *
4143 * Press is different to `"pointerdown"`, `"pointerup"` in that it
4144 * automatically filters out secondary pointer events like right
4145 * click and multitouch.
4146 *
4147 * It also adds accessibility support for keyboards, where
4148 * an element with a press gesture will receive focus and
4149 * trigger on Enter `"keydown"` and `"keyup"` events.
4150 *
4151 * This is different to a browser's `"click"` event, which does
4152 * respond to keyboards but only for the `"click"` itself, rather
4153 * than the press start and end/cancel. The element also needs
4154 * to be focusable for this to work, whereas a press gesture will
4155 * make an element focusable by default.
4156 *
4157 * @public
4158 */
4159 function press(targetOrSelector, onPressStart, options = {}) {
4160 const [targets, eventOptions, cancelEvents] = setupGesture(targetOrSelector, options);
4161 const startPress = (startEvent) => {
4162 const target = startEvent.currentTarget;
4163 if (!isValidPressEvent(startEvent))
4164 return;
4165 isPressing.add(target);
4166 const onPressEnd = onPressStart(target, startEvent);
4167 const onPointerEnd = (endEvent, success) => {
4168 window.removeEventListener("pointerup", onPointerUp);
4169 window.removeEventListener("pointercancel", onPointerCancel);
4170 if (isPressing.has(target)) {
4171 isPressing.delete(target);
4172 }
4173 if (!isValidPressEvent(endEvent)) {
4174 return;
4175 }
4176 if (typeof onPressEnd === "function") {
4177 onPressEnd(endEvent, { success });
4178 }
4179 };
4180 const onPointerUp = (upEvent) => {
4181 onPointerEnd(upEvent, target === window ||
4182 target === document ||
4183 options.useGlobalTarget ||
4184 isNodeOrChild(target, upEvent.target));
4185 };
4186 const onPointerCancel = (cancelEvent) => {
4187 onPointerEnd(cancelEvent, false);
4188 };
4189 window.addEventListener("pointerup", onPointerUp, eventOptions);
4190 window.addEventListener("pointercancel", onPointerCancel, eventOptions);
4191 };
4192 targets.forEach((target) => {
4193 const pointerDownTarget = options.useGlobalTarget ? window : target;
4194 pointerDownTarget.addEventListener("pointerdown", startPress, eventOptions);
4195 if (isHTMLElement(target)) {
4196 target.addEventListener("focus", (event) => enableKeyboardPress(event, eventOptions));
4197 if (!isElementKeyboardAccessible(target) &&
4198 !target.hasAttribute("tabindex")) {
4199 target.tabIndex = 0;
4200 }
4201 }
4202 });
4203 return cancelEvents;
4204 }
4205
4206 function getComputedStyle$2(element, name) {
4207 const computedStyle = window.getComputedStyle(element);
4208 return isCSSVar(name)
4209 ? computedStyle.getPropertyValue(name)
4210 : computedStyle[name];
4211 }
4212
4213 /**
4214 * Checks if an element is an SVG element in a way
4215 * that works across iframes
4216 */
4217 function isSVGElement(element) {
4218 return isObject(element) && "ownerSVGElement" in element;
4219 }
4220
4221 const resizeHandlers = new WeakMap();
4222 let observer;
4223 const getSize = (borderBoxAxis, svgAxis, htmlAxis) => (target, borderBoxSize) => {
4224 if (borderBoxSize && borderBoxSize[0]) {
4225 return borderBoxSize[0][(borderBoxAxis + "Size")];
4226 }
4227 else if (isSVGElement(target) && "getBBox" in target) {
4228 return target.getBBox()[svgAxis];
4229 }
4230 else {
4231 return target[htmlAxis];
4232 }
4233 };
4234 const getWidth = /*@__PURE__*/ getSize("inline", "width", "offsetWidth");
4235 const getHeight = /*@__PURE__*/ getSize("block", "height", "offsetHeight");
4236 function notifyTarget({ target, borderBoxSize }) {
4237 resizeHandlers.get(target)?.forEach((handler) => {
4238 handler(target, {
4239 get width() {
4240 return getWidth(target, borderBoxSize);
4241 },
4242 get height() {
4243 return getHeight(target, borderBoxSize);
4244 },
4245 });
4246 });
4247 }
4248 function notifyAll(entries) {
4249 entries.forEach(notifyTarget);
4250 }
4251 function createResizeObserver() {
4252 if (typeof ResizeObserver === "undefined")
4253 return;
4254 observer = new ResizeObserver(notifyAll);
4255 }
4256 function resizeElement(target, handler) {
4257 if (!observer)
4258 createResizeObserver();
4259 const elements = resolveElements(target);
4260 elements.forEach((element) => {
4261 let elementHandlers = resizeHandlers.get(element);
4262 if (!elementHandlers) {
4263 elementHandlers = new Set();
4264 resizeHandlers.set(element, elementHandlers);
4265 }
4266 elementHandlers.add(handler);
4267 observer?.observe(element);
4268 });
4269 return () => {
4270 elements.forEach((element) => {
4271 const elementHandlers = resizeHandlers.get(element);
4272 elementHandlers?.delete(handler);
4273 if (!elementHandlers?.size) {
4274 observer?.unobserve(element);
4275 }
4276 });
4277 };
4278 }
4279
4280 const windowCallbacks = new Set();
4281 let windowResizeHandler;
4282 function createWindowResizeHandler() {
4283 windowResizeHandler = () => {
4284 const info = {
4285 get width() {
4286 return window.innerWidth;
4287 },
4288 get height() {
4289 return window.innerHeight;
4290 },
4291 };
4292 windowCallbacks.forEach((callback) => callback(info));
4293 };
4294 window.addEventListener("resize", windowResizeHandler);
4295 }
4296 function resizeWindow(callback) {
4297 windowCallbacks.add(callback);
4298 if (!windowResizeHandler)
4299 createWindowResizeHandler();
4300 return () => {
4301 windowCallbacks.delete(callback);
4302 if (!windowCallbacks.size &&
4303 typeof windowResizeHandler === "function") {
4304 window.removeEventListener("resize", windowResizeHandler);
4305 windowResizeHandler = undefined;
4306 }
4307 };
4308 }
4309
4310 function resize(a, b) {
4311 return typeof a === "function" ? resizeWindow(a) : resizeElement(a, b);
4312 }
4313
4314 function observeTimeline(update, timeline) {
4315 let prevProgress;
4316 const onFrame = () => {
4317 const { currentTime } = timeline;
4318 const percentage = currentTime === null ? 0 : currentTime.value;
4319 const progress = percentage / 100;
4320 if (prevProgress !== progress) {
4321 update(progress);
4322 }
4323 prevProgress = progress;
4324 };
4325 frame.preUpdate(onFrame, true);
4326 return () => cancelFrame(onFrame);
4327 }
4328
4329 function record() {
4330 const { value } = statsBuffer;
4331 if (value === null) {
4332 cancelFrame(record);
4333 return;
4334 }
4335 value.frameloop.rate.push(frameData.delta);
4336 value.animations.mainThread.push(activeAnimations.mainThread);
4337 value.animations.waapi.push(activeAnimations.waapi);
4338 value.animations.layout.push(activeAnimations.layout);
4339 }
4340 function mean(values) {
4341 return values.reduce((acc, value) => acc + value, 0) / values.length;
4342 }
4343 function summarise(values, calcAverage = mean) {
4344 if (values.length === 0) {
4345 return {
4346 min: 0,
4347 max: 0,
4348 avg: 0,
4349 };
4350 }
4351 return {
4352 min: Math.min(...values),
4353 max: Math.max(...values),
4354 avg: calcAverage(values),
4355 };
4356 }
4357 const msToFps = (ms) => Math.round(1000 / ms);
4358 function clearStatsBuffer() {
4359 statsBuffer.value = null;
4360 statsBuffer.addProjectionMetrics = null;
4361 }
4362 function reportStats() {
4363 const { value } = statsBuffer;
4364 if (!value) {
4365 throw new Error("Stats are not being measured");
4366 }
4367 clearStatsBuffer();
4368 cancelFrame(record);
4369 const summary = {
4370 frameloop: {
4371 setup: summarise(value.frameloop.setup),
4372 rate: summarise(value.frameloop.rate),
4373 read: summarise(value.frameloop.read),
4374 resolveKeyframes: summarise(value.frameloop.resolveKeyframes),
4375 preUpdate: summarise(value.frameloop.preUpdate),
4376 update: summarise(value.frameloop.update),
4377 preRender: summarise(value.frameloop.preRender),
4378 render: summarise(value.frameloop.render),
4379 postRender: summarise(value.frameloop.postRender),
4380 },
4381 animations: {
4382 mainThread: summarise(value.animations.mainThread),
4383 waapi: summarise(value.animations.waapi),
4384 layout: summarise(value.animations.layout),
4385 },
4386 layoutProjection: {
4387 nodes: summarise(value.layoutProjection.nodes),
4388 calculatedTargetDeltas: summarise(value.layoutProjection.calculatedTargetDeltas),
4389 calculatedProjections: summarise(value.layoutProjection.calculatedProjections),
4390 },
4391 };
4392 /**
4393 * Convert the rate to FPS
4394 */
4395 const { rate } = summary.frameloop;
4396 rate.min = msToFps(rate.min);
4397 rate.max = msToFps(rate.max);
4398 rate.avg = msToFps(rate.avg);
4399 [rate.min, rate.max] = [rate.max, rate.min];
4400 return summary;
4401 }
4402 function recordStats() {
4403 if (statsBuffer.value) {
4404 clearStatsBuffer();
4405 throw new Error("Stats are already being measured");
4406 }
4407 const newStatsBuffer = statsBuffer;
4408 newStatsBuffer.value = {
4409 frameloop: {
4410 setup: [],
4411 rate: [],
4412 read: [],
4413 resolveKeyframes: [],
4414 preUpdate: [],
4415 update: [],
4416 preRender: [],
4417 render: [],
4418 postRender: [],
4419 },
4420 animations: {
4421 mainThread: [],
4422 waapi: [],
4423 layout: [],
4424 },
4425 layoutProjection: {
4426 nodes: [],
4427 calculatedTargetDeltas: [],
4428 calculatedProjections: [],
4429 },
4430 };
4431 newStatsBuffer.addProjectionMetrics = (metrics) => {
4432 const { layoutProjection } = newStatsBuffer.value;
4433 layoutProjection.nodes.push(metrics.nodes);
4434 layoutProjection.calculatedTargetDeltas.push(metrics.calculatedTargetDeltas);
4435 layoutProjection.calculatedProjections.push(metrics.calculatedProjections);
4436 };
4437 frame.postRender(record, true);
4438 return reportStats;
4439 }
4440
4441 /**
4442 * Checks if an element is specifically an SVGSVGElement (the root SVG element)
4443 * in a way that works across iframes
4444 */
4445 function isSVGSVGElement(element) {
4446 return isSVGElement(element) && element.tagName === "svg";
4447 }
4448
4449 function getOriginIndex(from, total) {
4450 if (from === "first") {
4451 return 0;
4452 }
4453 else {
4454 const lastIndex = total - 1;
4455 return from === "last" ? lastIndex : lastIndex / 2;
4456 }
4457 }
4458 function stagger(duration = 0.1, { startDelay = 0, from = 0, ease } = {}) {
4459 return (i, total) => {
4460 const fromIndex = typeof from === "number" ? from : getOriginIndex(from, total);
4461 const distance = Math.abs(fromIndex - i);
4462 let delay = duration * distance;
4463 if (ease) {
4464 const maxDelay = total * duration;
4465 const easingFunction = easingDefinitionToFunction(ease);
4466 delay = easingFunction(delay / maxDelay) * maxDelay;
4467 }
4468 return startDelay + delay;
4469 };
4470 }
4471
4472 function transform(...args) {
4473 const useImmediate = !Array.isArray(args[0]);
4474 const argOffset = useImmediate ? 0 : -1;
4475 const inputValue = args[0 + argOffset];
4476 const inputRange = args[1 + argOffset];
4477 const outputRange = args[2 + argOffset];
4478 const options = args[3 + argOffset];
4479 const interpolator = interpolate(inputRange, outputRange, options);
4480 return useImmediate ? interpolator(inputValue) : interpolator;
4481 }
4482
4483 function subscribeValue(inputValues, outputValue, getLatest) {
4484 const update = () => outputValue.set(getLatest());
4485 const scheduleUpdate = () => frame.preRender(update, false, true);
4486 const subscriptions = inputValues.map((v) => v.on("change", scheduleUpdate));
4487 outputValue.on("destroy", () => {
4488 subscriptions.forEach((unsubscribe) => unsubscribe());
4489 cancelFrame(update);
4490 });
4491 }
4492
4493 /**
4494 * Create a `MotionValue` that transforms the output of other `MotionValue`s by
4495 * passing their latest values through a transform function.
4496 *
4497 * Whenever a `MotionValue` referred to in the provided function is updated,
4498 * it will be re-evaluated.
4499 *
4500 * ```jsx
4501 * const x = motionValue(0)
4502 * const y = transformValue(() => x.get() * 2) // double x
4503 * ```
4504 *
4505 * @param transformer - A transform function. This function must be pure with no side-effects or conditional statements.
4506 * @returns `MotionValue`
4507 *
4508 * @public
4509 */
4510 function transformValue(transform) {
4511 const collectedValues = [];
4512 /**
4513 * Open session of collectMotionValues. Any MotionValue that calls get()
4514 * inside transform will be saved into this array.
4515 */
4516 collectMotionValues.current = collectedValues;
4517 const initialValue = transform();
4518 collectMotionValues.current = undefined;
4519 const value = motionValue(initialValue);
4520 subscribeValue(collectedValues, value, transform);
4521 return value;
4522 }
4523
4524 /**
4525 * Create a `MotionValue` that maps the output of another `MotionValue` by
4526 * mapping it from one range of values into another.
4527 *
4528 * @remarks
4529 *
4530 * Given an input range of `[-200, -100, 100, 200]` and an output range of
4531 * `[0, 1, 1, 0]`, the returned `MotionValue` will:
4532 *
4533 * - When provided a value between `-200` and `-100`, will return a value between `0` and `1`.
4534 * - When provided a value between `-100` and `100`, will return `1`.
4535 * - When provided a value between `100` and `200`, will return a value between `1` and `0`
4536 *
4537 * The input range must be a linear series of numbers. The output range
4538 * can be any value type supported by Motion: numbers, colors, shadows, etc.
4539 *
4540 * Every value in the output range must be of the same type and in the same format.
4541 *
4542 * ```jsx
4543 * const x = motionValue(0)
4544 * const xRange = [-200, -100, 100, 200]
4545 * const opacityRange = [0, 1, 1, 0]
4546 * const opacity = mapValue(x, xRange, opacityRange)
4547 * ```
4548 *
4549 * @param inputValue - `MotionValue`
4550 * @param inputRange - A linear series of numbers (either all increasing or decreasing)
4551 * @param outputRange - A series of numbers, colors or strings. Must be the same length as `inputRange`.
4552 * @param options -
4553 *
4554 * - clamp: boolean. Clamp values to within the given range. Defaults to `true`
4555 * - ease: EasingFunction[]. Easing functions to use on the interpolations between each value in the input and output ranges. If provided as an array, the array must be one item shorter than the input and output ranges, as the easings apply to the transition between each.
4556 *
4557 * @returns `MotionValue`
4558 *
4559 * @public
4560 */
4561 function mapValue(inputValue, inputRange, outputRange, options) {
4562 const map = transform(inputRange, outputRange, options);
4563 return transformValue(() => map(inputValue.get()));
4564 }
4565
4566 const isMotionValue = (value) => Boolean(value && value.getVelocity);
4567
4568 /**
4569 * Create a `MotionValue` that animates to its latest value using a spring.
4570 * Can either be a value or track another `MotionValue`.
4571 *
4572 * ```jsx
4573 * const x = motionValue(0)
4574 * const y = transformValue(() => x.get() * 2) // double x
4575 * ```
4576 *
4577 * @param transformer - A transform function. This function must be pure with no side-effects or conditional statements.
4578 * @returns `MotionValue`
4579 *
4580 * @public
4581 */
4582 function springValue(source, options) {
4583 const initialValue = isMotionValue(source) ? source.get() : source;
4584 const value = motionValue(initialValue);
4585 attachSpring(value, source, options);
4586 return value;
4587 }
4588 function attachSpring(value, source, options) {
4589 const initialValue = value.get();
4590 let activeAnimation = null;
4591 let latestValue = initialValue;
4592 let latestSetter;
4593 const unit = typeof initialValue === "string"
4594 ? initialValue.replace(/[\d.-]/g, "")
4595 : undefined;
4596 const stopAnimation = () => {
4597 if (activeAnimation) {
4598 activeAnimation.stop();
4599 activeAnimation = null;
4600 }
4601 };
4602 const startAnimation = () => {
4603 stopAnimation();
4604 activeAnimation = new JSAnimation({
4605 keyframes: [asNumber(value.get()), asNumber(latestValue)],
4606 velocity: value.getVelocity(),
4607 type: "spring",
4608 restDelta: 0.001,
4609 restSpeed: 0.01,
4610 ...options,
4611 onUpdate: latestSetter,
4612 });
4613 };
4614 value.attach((v, set) => {
4615 latestValue = v;
4616 latestSetter = (latest) => set(parseValue(latest, unit));
4617 frame.postRender(startAnimation);
4618 }, stopAnimation);
4619 if (isMotionValue(source)) {
4620 const removeSourceOnChange = source.on("change", (v) => value.set(parseValue(v, unit)));
4621 const removeValueOnDestroy = value.on("destroy", removeSourceOnChange);
4622 return () => {
4623 removeSourceOnChange();
4624 removeValueOnDestroy();
4625 };
4626 }
4627 return stopAnimation;
4628 }
4629 function parseValue(v, unit) {
4630 return unit ? v + unit : v;
4631 }
4632 function asNumber(v) {
4633 return typeof v === "number" ? v : parseFloat(v);
4634 }
4635
4636 /**
4637 * A list of all ValueTypes
4638 */
4639 const valueTypes = [...dimensionValueTypes, color, complex];
4640 /**
4641 * Tests a value against the list of ValueTypes
4642 */
4643 const findValueType = (v) => valueTypes.find(testValueType(v));
4644
4645 function chooseLayerType(valueName) {
4646 if (valueName === "layout")
4647 return "group";
4648 if (valueName === "enter" || valueName === "new")
4649 return "new";
4650 if (valueName === "exit" || valueName === "old")
4651 return "old";
4652 return "group";
4653 }
4654
4655 let pendingRules = {};
4656 let style = null;
4657 const css = {
4658 set: (selector, values) => {
4659 pendingRules[selector] = values;
4660 },
4661 commit: () => {
4662 if (!style) {
4663 style = document.createElement("style");
4664 style.id = "motion-view";
4665 }
4666 let cssText = "";
4667 for (const selector in pendingRules) {
4668 const rule = pendingRules[selector];
4669 cssText += `${selector} {\n`;
4670 for (const [property, value] of Object.entries(rule)) {
4671 cssText += ` ${property}: ${value};\n`;
4672 }
4673 cssText += "}\n";
4674 }
4675 style.textContent = cssText;
4676 document.head.appendChild(style);
4677 pendingRules = {};
4678 },
4679 remove: () => {
4680 if (style && style.parentElement) {
4681 style.parentElement.removeChild(style);
4682 }
4683 },
4684 };
4685
4686 function getViewAnimationLayerInfo(pseudoElement) {
4687 const match = pseudoElement.match(/::view-transition-(old|new|group|image-pair)\((.*?)\)/);
4688 if (!match)
4689 return null;
4690 return { layer: match[2], type: match[1] };
4691 }
4692
4693 function filterViewAnimations(animation) {
4694 const { effect } = animation;
4695 if (!effect)
4696 return false;
4697 return (effect.target === document.documentElement &&
4698 effect.pseudoElement?.startsWith("::view-transition"));
4699 }
4700 function getViewAnimations() {
4701 return document.getAnimations().filter(filterViewAnimations);
4702 }
4703
4704 function hasTarget(target, targets) {
4705 return targets.has(target) && Object.keys(targets.get(target)).length > 0;
4706 }
4707
4708 const definitionNames = ["layout", "enter", "exit", "new", "old"];
4709 function startViewAnimation(builder) {
4710 const { update, targets, options: defaultOptions } = builder;
4711 if (!document.startViewTransition) {
4712 return new Promise(async (resolve) => {
4713 await update();
4714 resolve(new GroupAnimation([]));
4715 });
4716 }
4717 // TODO: Go over existing targets and ensure they all have ids
4718 /**
4719 * If we don't have any animations defined for the root target,
4720 * remove it from being captured.
4721 */
4722 if (!hasTarget("root", targets)) {
4723 css.set(":root", {
4724 "view-transition-name": "none",
4725 });
4726 }
4727 /**
4728 * Set the timing curve to linear for all view transition layers.
4729 * This gets baked into the keyframes, which can't be changed
4730 * without breaking the generated animation.
4731 *
4732 * This allows us to set easing via updateTiming - which can be changed.
4733 */
4734 css.set("::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*)", { "animation-timing-function": "linear !important" });
4735 css.commit(); // Write
4736 const transition = document.startViewTransition(async () => {
4737 await update();
4738 // TODO: Go over new targets and ensure they all have ids
4739 });
4740 transition.finished.finally(() => {
4741 css.remove(); // Write
4742 });
4743 return new Promise((resolve) => {
4744 transition.ready.then(() => {
4745 const generatedViewAnimations = getViewAnimations();
4746 const animations = [];
4747 /**
4748 * Create animations for each of our explicitly-defined subjects.
4749 */
4750 targets.forEach((definition, target) => {
4751 // TODO: If target is not "root", resolve elements
4752 // and iterate over each
4753 for (const key of definitionNames) {
4754 if (!definition[key])
4755 continue;
4756 const { keyframes, options } = definition[key];
4757 for (let [valueName, valueKeyframes] of Object.entries(keyframes)) {
4758 if (!valueKeyframes)
4759 continue;
4760 const valueOptions = {
4761 ...getValueTransition$1(defaultOptions, valueName),
4762 ...getValueTransition$1(options, valueName),
4763 };
4764 const type = chooseLayerType(key);
4765 /**
4766 * If this is an opacity animation, and keyframes are not an array,
4767 * we need to convert them into an array and set an initial value.
4768 */
4769 if (valueName === "opacity" &&
4770 !Array.isArray(valueKeyframes)) {
4771 const initialValue = type === "new" ? 0 : 1;
4772 valueKeyframes = [initialValue, valueKeyframes];
4773 }
4774 /**
4775 * Resolve stagger function if provided.
4776 */
4777 if (typeof valueOptions.delay === "function") {
4778 valueOptions.delay = valueOptions.delay(0, 1);
4779 }
4780 valueOptions.duration && (valueOptions.duration = secondsToMilliseconds(valueOptions.duration));
4781 valueOptions.delay && (valueOptions.delay = secondsToMilliseconds(valueOptions.delay));
4782 const animation = new NativeAnimation({
4783 ...valueOptions,
4784 element: document.documentElement,
4785 name: valueName,
4786 pseudoElement: `::view-transition-${type}(${target})`,
4787 keyframes: valueKeyframes,
4788 });
4789 animations.push(animation);
4790 }
4791 }
4792 });
4793 /**
4794 * Handle browser generated animations
4795 */
4796 for (const animation of generatedViewAnimations) {
4797 if (animation.playState === "finished")
4798 continue;
4799 const { effect } = animation;
4800 if (!effect || !(effect instanceof KeyframeEffect))
4801 continue;
4802 const { pseudoElement } = effect;
4803 if (!pseudoElement)
4804 continue;
4805 const name = getViewAnimationLayerInfo(pseudoElement);
4806 if (!name)
4807 continue;
4808 const targetDefinition = targets.get(name.layer);
4809 if (!targetDefinition) {
4810 /**
4811 * If transition name is group then update the timing of the animation
4812 * whereas if it's old or new then we could possibly replace it using
4813 * the above method.
4814 */
4815 const transitionName = name.type === "group" ? "layout" : "";
4816 let animationTransition = {
4817 ...getValueTransition$1(defaultOptions, transitionName),
4818 };
4819 animationTransition.duration && (animationTransition.duration = secondsToMilliseconds(animationTransition.duration));
4820 animationTransition =
4821 applyGeneratorOptions(animationTransition);
4822 const easing = mapEasingToNativeEasing(animationTransition.ease, animationTransition.duration);
4823 effect.updateTiming({
4824 delay: secondsToMilliseconds(animationTransition.delay ?? 0),
4825 duration: animationTransition.duration,
4826 easing,
4827 });
4828 animations.push(new NativeAnimationWrapper(animation));
4829 }
4830 else if (hasOpacity(targetDefinition, "enter") &&
4831 hasOpacity(targetDefinition, "exit") &&
4832 effect
4833 .getKeyframes()
4834 .some((keyframe) => keyframe.mixBlendMode)) {
4835 animations.push(new NativeAnimationWrapper(animation));
4836 }
4837 else {
4838 animation.cancel();
4839 }
4840 }
4841 resolve(new GroupAnimation(animations));
4842 });
4843 });
4844 }
4845 function hasOpacity(target, key) {
4846 return target?.[key]?.keyframes.opacity;
4847 }
4848
4849 let builders = [];
4850 let current = null;
4851 function next() {
4852 current = null;
4853 const [nextBuilder] = builders;
4854 if (nextBuilder)
4855 start(nextBuilder);
4856 }
4857 function start(builder) {
4858 removeItem(builders, builder);
4859 current = builder;
4860 startViewAnimation(builder).then((animation) => {
4861 builder.notifyReady(animation);
4862 animation.finished.finally(next);
4863 });
4864 }
4865 function processQueue() {
4866 /**
4867 * Iterate backwards over the builders array. We can ignore the
4868 * "wait" animations. If we have an interrupting animation in the
4869 * queue then we need to batch all preceeding animations into it.
4870 * Currently this only batches the update functions but will also
4871 * need to batch the targets.
4872 */
4873 for (let i = builders.length - 1; i >= 0; i--) {
4874 const builder = builders[i];
4875 const { interrupt } = builder.options;
4876 if (interrupt === "immediate") {
4877 const batchedUpdates = builders.slice(0, i + 1).map((b) => b.update);
4878 const remaining = builders.slice(i + 1);
4879 builder.update = () => {
4880 batchedUpdates.forEach((update) => update());
4881 };
4882 // Put the current builder at the front, followed by any "wait" builders
4883 builders = [builder, ...remaining];
4884 break;
4885 }
4886 }
4887 if (!current || builders[0]?.options.interrupt === "immediate") {
4888 next();
4889 }
4890 }
4891 function addToQueue(builder) {
4892 builders.push(builder);
4893 microtask.render(processQueue);
4894 }
4895
4896 class ViewTransitionBuilder {
4897 constructor(update, options = {}) {
4898 this.currentSubject = "root";
4899 this.targets = new Map();
4900 this.notifyReady = noop;
4901 this.readyPromise = new Promise((resolve) => {
4902 this.notifyReady = resolve;
4903 });
4904 this.update = update;
4905 this.options = {
4906 interrupt: "wait",
4907 ...options,
4908 };
4909 addToQueue(this);
4910 }
4911 get(subject) {
4912 this.currentSubject = subject;
4913 return this;
4914 }
4915 layout(keyframes, options) {
4916 this.updateTarget("layout", keyframes, options);
4917 return this;
4918 }
4919 new(keyframes, options) {
4920 this.updateTarget("new", keyframes, options);
4921 return this;
4922 }
4923 old(keyframes, options) {
4924 this.updateTarget("old", keyframes, options);
4925 return this;
4926 }
4927 enter(keyframes, options) {
4928 this.updateTarget("enter", keyframes, options);
4929 return this;
4930 }
4931 exit(keyframes, options) {
4932 this.updateTarget("exit", keyframes, options);
4933 return this;
4934 }
4935 crossfade(options) {
4936 this.updateTarget("enter", { opacity: 1 }, options);
4937 this.updateTarget("exit", { opacity: 0 }, options);
4938 return this;
4939 }
4940 updateTarget(target, keyframes, options = {}) {
4941 const { currentSubject, targets } = this;
4942 if (!targets.has(currentSubject)) {
4943 targets.set(currentSubject, {});
4944 }
4945 const targetData = targets.get(currentSubject);
4946 targetData[target] = { keyframes, options };
4947 }
4948 then(resolve, reject) {
4949 return this.readyPromise.then(resolve, reject);
4950 }
4951 }
4952 function animateView(update, defaultOptions = {}) {
4953 return new ViewTransitionBuilder(update, defaultOptions);
4954 }
4955
4956 /**
4957 * @deprecated
4958 *
4959 * Import as `frame` instead.
4960 */
4961 const sync = frame;
4962 /**
4963 * @deprecated
4964 *
4965 * Use cancelFrame(callback) instead.
4966 */
4967 const cancelSync = stepsOrder.reduce((acc, key) => {
4968 acc[key] = (process) => cancelFrame(process);
4969 return acc;
4970 }, {});
4971
4972 function isDOMKeyframes(keyframes) {
4973 return typeof keyframes === "object" && !Array.isArray(keyframes);
4974 }
4975
4976 function resolveSubjects(subject, keyframes, scope, selectorCache) {
4977 if (typeof subject === "string" && isDOMKeyframes(keyframes)) {
4978 return resolveElements(subject, scope, selectorCache);
4979 }
4980 else if (subject instanceof NodeList) {
4981 return Array.from(subject);
4982 }
4983 else if (Array.isArray(subject)) {
4984 return subject;
4985 }
4986 else {
4987 return [subject];
4988 }
4989 }
4990
4991 function calculateRepeatDuration(duration, repeat, _repeatDelay) {
4992 return duration * (repeat + 1);
4993 }
4994
4995 /**
4996 * Given a absolute or relative time definition and current/prev time state of the sequence,
4997 * calculate an absolute time for the next keyframes.
4998 */
4999 function calcNextTime(current, next, prev, labels) {
5000 if (typeof next === "number") {
5001 return next;
5002 }
5003 else if (next.startsWith("-") || next.startsWith("+")) {
5004 return Math.max(0, current + parseFloat(next));
5005 }
5006 else if (next === "<") {
5007 return prev;
5008 }
5009 else if (next.startsWith("<")) {
5010 return Math.max(0, prev + parseFloat(next.slice(1)));
5011 }
5012 else {
5013 return labels.get(next) ?? current;
5014 }
5015 }
5016
5017 function eraseKeyframes(sequence, startTime, endTime) {
5018 for (let i = 0; i < sequence.length; i++) {
5019 const keyframe = sequence[i];
5020 if (keyframe.at > startTime && keyframe.at < endTime) {
5021 removeItem(sequence, keyframe);
5022 // If we remove this item we have to push the pointer back one
5023 i--;
5024 }
5025 }
5026 }
5027 function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
5028 /**
5029 * Erase every existing value between currentTime and targetTime,
5030 * this will essentially splice this timeline into any currently
5031 * defined ones.
5032 */
5033 eraseKeyframes(sequence, startTime, endTime);
5034 for (let i = 0; i < keyframes.length; i++) {
5035 sequence.push({
5036 value: keyframes[i],
5037 at: mixNumber$1(startTime, endTime, offset[i]),
5038 easing: getEasingForSegment(easing, i),
5039 });
5040 }
5041 }
5042
5043 /**
5044 * Take an array of times that represent repeated keyframes. For instance
5045 * if we have original times of [0, 0.5, 1] then our repeated times will
5046 * be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back
5047 * down to a 0-1 scale.
5048 */
5049 function normalizeTimes(times, repeat) {
5050 for (let i = 0; i < times.length; i++) {
5051 times[i] = times[i] / (repeat + 1);
5052 }
5053 }
5054
5055 function compareByTime(a, b) {
5056 if (a.at === b.at) {
5057 if (a.value === null)
5058 return 1;
5059 if (b.value === null)
5060 return -1;
5061 return 0;
5062 }
5063 else {
5064 return a.at - b.at;
5065 }
5066 }
5067
5068 const defaultSegmentEasing = "easeInOut";
5069 const MAX_REPEAT = 20;
5070 function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
5071 const defaultDuration = defaultTransition.duration || 0.3;
5072 const animationDefinitions = new Map();
5073 const sequences = new Map();
5074 const elementCache = {};
5075 const timeLabels = new Map();
5076 let prevTime = 0;
5077 let currentTime = 0;
5078 let totalDuration = 0;
5079 /**
5080 * Build the timeline by mapping over the sequence array and converting
5081 * the definitions into keyframes and offsets with absolute time values.
5082 * These will later get converted into relative offsets in a second pass.
5083 */
5084 for (let i = 0; i < sequence.length; i++) {
5085 const segment = sequence[i];
5086 /**
5087 * If this is a timeline label, mark it and skip the rest of this iteration.
5088 */
5089 if (typeof segment === "string") {
5090 timeLabels.set(segment, currentTime);
5091 continue;
5092 }
5093 else if (!Array.isArray(segment)) {
5094 timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
5095 continue;
5096 }
5097 let [subject, keyframes, transition = {}] = segment;
5098 /**
5099 * If a relative or absolute time value has been specified we need to resolve
5100 * it in relation to the currentTime.
5101 */
5102 if (transition.at !== undefined) {
5103 currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels);
5104 }
5105 /**
5106 * Keep track of the maximum duration in this definition. This will be
5107 * applied to currentTime once the definition has been parsed.
5108 */
5109 let maxDuration = 0;
5110 const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
5111 const valueKeyframesAsList = keyframesAsList(valueKeyframes);
5112 const { delay = 0, times = defaultOffset$1(valueKeyframesAsList), type = "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
5113 let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
5114 /**
5115 * Resolve stagger() if defined.
5116 */
5117 const calculatedDelay = typeof delay === "function"
5118 ? delay(elementIndex, numSubjects)
5119 : delay;
5120 /**
5121 * If this animation should and can use a spring, generate a spring easing function.
5122 */
5123 const numKeyframes = valueKeyframesAsList.length;
5124 const createGenerator = isGenerator(type)
5125 ? type
5126 : generators?.[type || "keyframes"];
5127 if (numKeyframes <= 2 && createGenerator) {
5128 /**
5129 * As we're creating an easing function from a spring,
5130 * ideally we want to generate it using the real distance
5131 * between the two keyframes. However this isn't always
5132 * possible - in these situations we use 0-100.
5133 */
5134 let absoluteDelta = 100;
5135 if (numKeyframes === 2 &&
5136 isNumberKeyframesArray(valueKeyframesAsList)) {
5137 const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
5138 absoluteDelta = Math.abs(delta);
5139 }
5140 const springTransition = { ...remainingTransition };
5141 if (duration !== undefined) {
5142 springTransition.duration = secondsToMilliseconds(duration);
5143 }
5144 const springEasing = createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
5145 ease = springEasing.ease;
5146 duration = springEasing.duration;
5147 }
5148 duration ?? (duration = defaultDuration);
5149 const startTime = currentTime + calculatedDelay;
5150 /**
5151 * If there's only one time offset of 0, fill in a second with length 1
5152 */
5153 if (times.length === 1 && times[0] === 0) {
5154 times[1] = 1;
5155 }
5156 /**
5157 * Fill out if offset if fewer offsets than keyframes
5158 */
5159 const remainder = times.length - valueKeyframesAsList.length;
5160 remainder > 0 && fillOffset(times, remainder);
5161 /**
5162 * If only one value has been set, ie [1], push a null to the start of
5163 * the keyframe array. This will let us mark a keyframe at this point
5164 * that will later be hydrated with the previous value.
5165 */
5166 valueKeyframesAsList.length === 1 &&
5167 valueKeyframesAsList.unshift(null);
5168 /**
5169 * Handle repeat options
5170 */
5171 if (repeat) {
5172 exports.invariant(repeat < MAX_REPEAT, "Repeat count too high, must be less than 20", "repeat-count-high");
5173 duration = calculateRepeatDuration(duration, repeat);
5174 const originalKeyframes = [...valueKeyframesAsList];
5175 const originalTimes = [...times];
5176 ease = Array.isArray(ease) ? [...ease] : [ease];
5177 const originalEase = [...ease];
5178 for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
5179 valueKeyframesAsList.push(...originalKeyframes);
5180 for (let keyframeIndex = 0; keyframeIndex < originalKeyframes.length; keyframeIndex++) {
5181 times.push(originalTimes[keyframeIndex] + (repeatIndex + 1));
5182 ease.push(keyframeIndex === 0
5183 ? "linear"
5184 : getEasingForSegment(originalEase, keyframeIndex - 1));
5185 }
5186 }
5187 normalizeTimes(times, repeat);
5188 }
5189 const targetTime = startTime + duration;
5190 /**
5191 * Add keyframes, mapping offsets to absolute time.
5192 */
5193 addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
5194 maxDuration = Math.max(calculatedDelay + duration, maxDuration);
5195 totalDuration = Math.max(targetTime, totalDuration);
5196 };
5197 if (isMotionValue(subject)) {
5198 const subjectSequence = getSubjectSequence(subject, sequences);
5199 resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
5200 }
5201 else {
5202 const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
5203 const numSubjects = subjects.length;
5204 /**
5205 * For every element in this segment, process the defined values.
5206 */
5207 for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
5208 /**
5209 * Cast necessary, but we know these are of this type
5210 */
5211 keyframes = keyframes;
5212 transition = transition;
5213 const thisSubject = subjects[subjectIndex];
5214 const subjectSequence = getSubjectSequence(thisSubject, sequences);
5215 for (const key in keyframes) {
5216 resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
5217 }
5218 }
5219 }
5220 prevTime = currentTime;
5221 currentTime += maxDuration;
5222 }
5223 /**
5224 * For every element and value combination create a new animation.
5225 */
5226 sequences.forEach((valueSequences, element) => {
5227 for (const key in valueSequences) {
5228 const valueSequence = valueSequences[key];
5229 /**
5230 * Arrange all the keyframes in ascending time order.
5231 */
5232 valueSequence.sort(compareByTime);
5233 const keyframes = [];
5234 const valueOffset = [];
5235 const valueEasing = [];
5236 /**
5237 * For each keyframe, translate absolute times into
5238 * relative offsets based on the total duration of the timeline.
5239 */
5240 for (let i = 0; i < valueSequence.length; i++) {
5241 const { at, value, easing } = valueSequence[i];
5242 keyframes.push(value);
5243 valueOffset.push(progress(0, totalDuration, at));
5244 valueEasing.push(easing || "easeOut");
5245 }
5246 /**
5247 * If the first keyframe doesn't land on offset: 0
5248 * provide one by duplicating the initial keyframe. This ensures
5249 * it snaps to the first keyframe when the animation starts.
5250 */
5251 if (valueOffset[0] !== 0) {
5252 valueOffset.unshift(0);
5253 keyframes.unshift(keyframes[0]);
5254 valueEasing.unshift(defaultSegmentEasing);
5255 }
5256 /**
5257 * If the last keyframe doesn't land on offset: 1
5258 * provide one with a null wildcard value. This will ensure it
5259 * stays static until the end of the animation.
5260 */
5261 if (valueOffset[valueOffset.length - 1] !== 1) {
5262 valueOffset.push(1);
5263 keyframes.push(null);
5264 }
5265 if (!animationDefinitions.has(element)) {
5266 animationDefinitions.set(element, {
5267 keyframes: {},
5268 transition: {},
5269 });
5270 }
5271 const definition = animationDefinitions.get(element);
5272 definition.keyframes[key] = keyframes;
5273 definition.transition[key] = {
5274 ...defaultTransition,
5275 duration: totalDuration,
5276 ease: valueEasing,
5277 times: valueOffset,
5278 ...sequenceTransition,
5279 };
5280 }
5281 });
5282 return animationDefinitions;
5283 }
5284 function getSubjectSequence(subject, sequences) {
5285 !sequences.has(subject) && sequences.set(subject, {});
5286 return sequences.get(subject);
5287 }
5288 function getValueSequence(name, sequences) {
5289 if (!sequences[name])
5290 sequences[name] = [];
5291 return sequences[name];
5292 }
5293 function keyframesAsList(keyframes) {
5294 return Array.isArray(keyframes) ? keyframes : [keyframes];
5295 }
5296 function getValueTransition(transition, key) {
5297 return transition && transition[key]
5298 ? {
5299 ...transition,
5300 ...transition[key],
5301 }
5302 : { ...transition };
5303 }
5304 const isNumber = (keyframe) => typeof keyframe === "number";
5305 const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
5306
5307 const visualElementStore = new WeakMap();
5308
5309 const isKeyframesTarget = (v) => {
5310 return Array.isArray(v);
5311 };
5312
5313 function getValueState(visualElement) {
5314 const state = [{}, {}];
5315 visualElement?.values.forEach((value, key) => {
5316 state[0][key] = value.get();
5317 state[1][key] = value.getVelocity();
5318 });
5319 return state;
5320 }
5321 function resolveVariantFromProps(props, definition, custom, visualElement) {
5322 /**
5323 * If the variant definition is a function, resolve.
5324 */
5325 if (typeof definition === "function") {
5326 const [current, velocity] = getValueState(visualElement);
5327 definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
5328 }
5329 /**
5330 * If the variant definition is a variant label, or
5331 * the function returned a variant label, resolve.
5332 */
5333 if (typeof definition === "string") {
5334 definition = props.variants && props.variants[definition];
5335 }
5336 /**
5337 * At this point we've resolved both functions and variant labels,
5338 * but the resolved variant label might itself have been a function.
5339 * If so, resolve. This can only have returned a valid target object.
5340 */
5341 if (typeof definition === "function") {
5342 const [current, velocity] = getValueState(visualElement);
5343 definition = definition(custom !== undefined ? custom : props.custom, current, velocity);
5344 }
5345 return definition;
5346 }
5347
5348 function resolveVariant(visualElement, definition, custom) {
5349 const props = visualElement.getProps();
5350 return resolveVariantFromProps(props, definition, props.custom, visualElement);
5351 }
5352
5353 /**
5354 * Set VisualElement's MotionValue, creating a new MotionValue for it if
5355 * it doesn't exist.
5356 */
5357 function setMotionValue(visualElement, key, value) {
5358 if (visualElement.hasValue(key)) {
5359 visualElement.getValue(key).set(value);
5360 }
5361 else {
5362 visualElement.addValue(key, motionValue(value));
5363 }
5364 }
5365 function resolveFinalValueInKeyframes(v) {
5366 // TODO maybe throw if v.length - 1 is placeholder token?
5367 return isKeyframesTarget(v) ? v[v.length - 1] || 0 : v;
5368 }
5369 function setTarget(visualElement, definition) {
5370 const resolved = resolveVariant(visualElement, definition);
5371 let { transitionEnd = {}, transition = {}, ...target } = resolved || {};
5372 target = { ...target, ...transitionEnd };
5373 for (const key in target) {
5374 const value = resolveFinalValueInKeyframes(target[key]);
5375 setMotionValue(visualElement, key, value);
5376 }
5377 }
5378
5379 function isWillChangeMotionValue(value) {
5380 return Boolean(isMotionValue(value) && value.add);
5381 }
5382
5383 function addValueToWillChange(visualElement, key) {
5384 const willChange = visualElement.getValue("willChange");
5385 /**
5386 * It could be that a user has set willChange to a regular MotionValue,
5387 * in which case we can't add the value to it.
5388 */
5389 if (isWillChangeMotionValue(willChange)) {
5390 return willChange.add(key);
5391 }
5392 else if (!willChange && MotionGlobalConfig.WillChange) {
5393 const newWillChange = new MotionGlobalConfig.WillChange("auto");
5394 visualElement.addValue("willChange", newWillChange);
5395 newWillChange.add(key);
5396 }
5397 }
5398
5399 /**
5400 * Convert camelCase to dash-case properties.
5401 */
5402 const camelToDash = (str) => str.replace(/([a-z])([A-Z])/gu, "$1-$2").toLowerCase();
5403
5404 const optimizedAppearDataId = "framerAppearId";
5405 const optimizedAppearDataAttribute = "data-" + camelToDash(optimizedAppearDataId);
5406
5407 function getOptimisedAppearId(visualElement) {
5408 return visualElement.props[optimizedAppearDataAttribute];
5409 }
5410
5411 const isNotNull = (value) => value !== null;
5412 function getFinalKeyframe(keyframes, { repeat, repeatType = "loop" }, finalKeyframe) {
5413 const resolvedKeyframes = keyframes.filter(isNotNull);
5414 const index = repeat && repeatType !== "loop" && repeat % 2 === 1
5415 ? 0
5416 : resolvedKeyframes.length - 1;
5417 return !index || finalKeyframe === undefined
5418 ? resolvedKeyframes[index]
5419 : finalKeyframe;
5420 }
5421
5422 const underDampedSpring = {
5423 type: "spring",
5424 stiffness: 500,
5425 damping: 25,
5426 restSpeed: 10,
5427 };
5428 const criticallyDampedSpring = (target) => ({
5429 type: "spring",
5430 stiffness: 550,
5431 damping: target === 0 ? 2 * Math.sqrt(550) : 30,
5432 restSpeed: 10,
5433 });
5434 const keyframesTransition = {
5435 type: "keyframes",
5436 duration: 0.8,
5437 };
5438 /**
5439 * Default easing curve is a slightly shallower version of
5440 * the default browser easing curve.
5441 */
5442 const ease = {
5443 type: "keyframes",
5444 ease: [0.25, 0.1, 0.35, 1],
5445 duration: 0.3,
5446 };
5447 const getDefaultTransition = (valueKey, { keyframes }) => {
5448 if (keyframes.length > 2) {
5449 return keyframesTransition;
5450 }
5451 else if (transformProps.has(valueKey)) {
5452 return valueKey.startsWith("scale")
5453 ? criticallyDampedSpring(keyframes[1])
5454 : underDampedSpring;
5455 }
5456 return ease;
5457 };
5458
5459 /**
5460 * Decide whether a transition is defined on a given Transition.
5461 * This filters out orchestration options and returns true
5462 * if any options are left.
5463 */
5464 function isTransitionDefined({ when, delay: _delay, delayChildren, staggerChildren, staggerDirection, repeat, repeatType, repeatDelay, from, elapsed, ...transition }) {
5465 return !!Object.keys(transition).length;
5466 }
5467
5468 const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => {
5469 const valueTransition = getValueTransition$1(transition, name) || {};
5470 /**
5471 * Most transition values are currently completely overwritten by value-specific
5472 * transitions. In the future it'd be nicer to blend these transitions. But for now
5473 * delay actually does inherit from the root transition if not value-specific.
5474 */
5475 const delay = valueTransition.delay || transition.delay || 0;
5476 /**
5477 * Elapsed isn't a public transition option but can be passed through from
5478 * optimized appear effects in milliseconds.
5479 */
5480 let { elapsed = 0 } = transition;
5481 elapsed = elapsed - secondsToMilliseconds(delay);
5482 const options = {
5483 keyframes: Array.isArray(target) ? target : [null, target],
5484 ease: "easeOut",
5485 velocity: value.getVelocity(),
5486 ...valueTransition,
5487 delay: -elapsed,
5488 onUpdate: (v) => {
5489 value.set(v);
5490 valueTransition.onUpdate && valueTransition.onUpdate(v);
5491 },
5492 onComplete: () => {
5493 onComplete();
5494 valueTransition.onComplete && valueTransition.onComplete();
5495 },
5496 name,
5497 motionValue: value,
5498 element: isHandoff ? undefined : element,
5499 };
5500 /**
5501 * If there's no transition defined for this value, we can generate
5502 * unique transition settings for this value.
5503 */
5504 if (!isTransitionDefined(valueTransition)) {
5505 Object.assign(options, getDefaultTransition(name, options));
5506 }
5507 /**
5508 * Both WAAPI and our internal animation functions use durations
5509 * as defined by milliseconds, while our external API defines them
5510 * as seconds.
5511 */
5512 options.duration && (options.duration = secondsToMilliseconds(options.duration));
5513 options.repeatDelay && (options.repeatDelay = secondsToMilliseconds(options.repeatDelay));
5514 /**
5515 * Support deprecated way to set initial value. Prefer keyframe syntax.
5516 */
5517 if (options.from !== undefined) {
5518 options.keyframes[0] = options.from;
5519 }
5520 let shouldSkip = false;
5521 if (options.type === false ||
5522 (options.duration === 0 && !options.repeatDelay)) {
5523 makeAnimationInstant(options);
5524 if (options.delay === 0) {
5525 shouldSkip = true;
5526 }
5527 }
5528 if (MotionGlobalConfig.instantAnimations ||
5529 MotionGlobalConfig.skipAnimations) {
5530 shouldSkip = true;
5531 makeAnimationInstant(options);
5532 options.delay = 0;
5533 }
5534 /**
5535 * If the transition type or easing has been explicitly set by the user
5536 * then we don't want to allow flattening the animation.
5537 */
5538 options.allowFlatten = !valueTransition.type && !valueTransition.ease;
5539 /**
5540 * If we can or must skip creating the animation, and apply only
5541 * the final keyframe, do so. We also check once keyframes are resolved but
5542 * this early check prevents the need to create an animation at all.
5543 */
5544 if (shouldSkip && !isHandoff && value.get() !== undefined) {
5545 const finalKeyframe = getFinalKeyframe(options.keyframes, valueTransition);
5546 if (finalKeyframe !== undefined) {
5547 frame.update(() => {
5548 options.onUpdate(finalKeyframe);
5549 options.onComplete();
5550 });
5551 return;
5552 }
5553 }
5554 return valueTransition.isSync
5555 ? new JSAnimation(options)
5556 : new AsyncMotionValueAnimation(options);
5557 };
5558
5559 /**
5560 * Decide whether we should block this animation. Previously, we achieved this
5561 * just by checking whether the key was listed in protectedKeys, but this
5562 * posed problems if an animation was triggered by afterChildren and protectedKeys
5563 * had been set to true in the meantime.
5564 */
5565 function shouldBlockAnimation({ protectedKeys, needsAnimating }, key) {
5566 const shouldBlock = protectedKeys.hasOwnProperty(key) && needsAnimating[key] !== true;
5567 needsAnimating[key] = false;
5568 return shouldBlock;
5569 }
5570 function animateTarget(visualElement, targetAndTransition, { delay = 0, transitionOverride, type } = {}) {
5571 let { transition = visualElement.getDefaultTransition(), transitionEnd, ...target } = targetAndTransition;
5572 if (transitionOverride)
5573 transition = transitionOverride;
5574 const animations = [];
5575 const animationTypeState = type &&
5576 visualElement.animationState &&
5577 visualElement.animationState.getState()[type];
5578 for (const key in target) {
5579 const value = visualElement.getValue(key, visualElement.latestValues[key] ?? null);
5580 const valueTarget = target[key];
5581 if (valueTarget === undefined ||
5582 (animationTypeState &&
5583 shouldBlockAnimation(animationTypeState, key))) {
5584 continue;
5585 }
5586 const valueTransition = {
5587 delay,
5588 ...getValueTransition$1(transition || {}, key),
5589 };
5590 /**
5591 * If the value is already at the defined target, skip the animation.
5592 */
5593 const currentValue = value.get();
5594 if (currentValue !== undefined &&
5595 !value.isAnimating &&
5596 !Array.isArray(valueTarget) &&
5597 valueTarget === currentValue &&
5598 !valueTransition.velocity) {
5599 continue;
5600 }
5601 /**
5602 * If this is the first time a value is being animated, check
5603 * to see if we're handling off from an existing animation.
5604 */
5605 let isHandoff = false;
5606 if (window.MotionHandoffAnimation) {
5607 const appearId = getOptimisedAppearId(visualElement);
5608 if (appearId) {
5609 const startTime = window.MotionHandoffAnimation(appearId, key, frame);
5610 if (startTime !== null) {
5611 valueTransition.startTime = startTime;
5612 isHandoff = true;
5613 }
5614 }
5615 }
5616 addValueToWillChange(visualElement, key);
5617 value.start(animateMotionValue(key, value, valueTarget, visualElement.shouldReduceMotion && positionalKeys.has(key)
5618 ? { type: false }
5619 : valueTransition, visualElement, isHandoff));
5620 const animation = value.animation;
5621 if (animation) {
5622 animations.push(animation);
5623 }
5624 }
5625 if (transitionEnd) {
5626 Promise.all(animations).then(() => {
5627 frame.update(() => {
5628 transitionEnd && setTarget(visualElement, transitionEnd);
5629 });
5630 });
5631 }
5632 return animations;
5633 }
5634
5635 /**
5636 * Bounding boxes tend to be defined as top, left, right, bottom. For various operations
5637 * it's easier to consider each axis individually. This function returns a bounding box
5638 * as a map of single-axis min/max values.
5639 */
5640 function convertBoundingBoxToBox({ top, left, right, bottom, }) {
5641 return {
5642 x: { min: left, max: right },
5643 y: { min: top, max: bottom },
5644 };
5645 }
5646 /**
5647 * Applies a TransformPoint function to a bounding box. TransformPoint is usually a function
5648 * provided by Framer to allow measured points to be corrected for device scaling. This is used
5649 * when measuring DOM elements and DOM event points.
5650 */
5651 function transformBoxPoints(point, transformPoint) {
5652 if (!transformPoint)
5653 return point;
5654 const topLeft = transformPoint({ x: point.left, y: point.top });
5655 const bottomRight = transformPoint({ x: point.right, y: point.bottom });
5656 return {
5657 top: topLeft.y,
5658 left: topLeft.x,
5659 bottom: bottomRight.y,
5660 right: bottomRight.x,
5661 };
5662 }
5663
5664 function measureViewportBox(instance, transformPoint) {
5665 return convertBoundingBoxToBox(transformBoxPoints(instance.getBoundingClientRect(), transformPoint));
5666 }
5667
5668 const featureProps = {
5669 animation: [
5670 "animate",
5671 "variants",
5672 "whileHover",
5673 "whileTap",
5674 "exit",
5675 "whileInView",
5676 "whileFocus",
5677 "whileDrag",
5678 ],
5679 exit: ["exit"],
5680 drag: ["drag", "dragControls"],
5681 focus: ["whileFocus"],
5682 hover: ["whileHover", "onHoverStart", "onHoverEnd"],
5683 tap: ["whileTap", "onTap", "onTapStart", "onTapCancel"],
5684 pan: ["onPan", "onPanStart", "onPanSessionStart", "onPanEnd"],
5685 inView: ["whileInView", "onViewportEnter", "onViewportLeave"],
5686 layout: ["layout", "layoutId"],
5687 };
5688 const featureDefinitions = {};
5689 for (const key in featureProps) {
5690 featureDefinitions[key] = {
5691 isEnabled: (props) => featureProps[key].some((name) => !!props[name]),
5692 };
5693 }
5694
5695 const createAxis = () => ({ min: 0, max: 0 });
5696 const createBox = () => ({
5697 x: createAxis(),
5698 y: createAxis(),
5699 });
5700
5701 const isBrowser = typeof window !== "undefined";
5702
5703 // Does this device prefer reduced motion? Returns `null` server-side.
5704 const prefersReducedMotion = { current: null };
5705 const hasReducedMotionListener = { current: false };
5706
5707 function initPrefersReducedMotion() {
5708 hasReducedMotionListener.current = true;
5709 if (!isBrowser)
5710 return;
5711 if (window.matchMedia) {
5712 const motionMediaQuery = window.matchMedia("(prefers-reduced-motion)");
5713 const setReducedMotionPreferences = () => (prefersReducedMotion.current = motionMediaQuery.matches);
5714 motionMediaQuery.addEventListener("change", setReducedMotionPreferences);
5715 setReducedMotionPreferences();
5716 }
5717 else {
5718 prefersReducedMotion.current = false;
5719 }
5720 }
5721
5722 function isAnimationControls(v) {
5723 return (v !== null &&
5724 typeof v === "object" &&
5725 typeof v.start === "function");
5726 }
5727
5728 /**
5729 * Decides if the supplied variable is variant label
5730 */
5731 function isVariantLabel(v) {
5732 return typeof v === "string" || Array.isArray(v);
5733 }
5734
5735 const variantPriorityOrder = [
5736 "animate",
5737 "whileInView",
5738 "whileFocus",
5739 "whileHover",
5740 "whileTap",
5741 "whileDrag",
5742 "exit",
5743 ];
5744 const variantProps = ["initial", ...variantPriorityOrder];
5745
5746 function isControllingVariants(props) {
5747 return (isAnimationControls(props.animate) ||
5748 variantProps.some((name) => isVariantLabel(props[name])));
5749 }
5750 function isVariantNode(props) {
5751 return Boolean(isControllingVariants(props) || props.variants);
5752 }
5753
5754 function updateMotionValuesFromProps(element, next, prev) {
5755 for (const key in next) {
5756 const nextValue = next[key];
5757 const prevValue = prev[key];
5758 if (isMotionValue(nextValue)) {
5759 /**
5760 * If this is a motion value found in props or style, we want to add it
5761 * to our visual element's motion value map.
5762 */
5763 element.addValue(key, nextValue);
5764 }
5765 else if (isMotionValue(prevValue)) {
5766 /**
5767 * If we're swapping from a motion value to a static value,
5768 * create a new motion value from that
5769 */
5770 element.addValue(key, motionValue(nextValue, { owner: element }));
5771 }
5772 else if (prevValue !== nextValue) {
5773 /**
5774 * If this is a flat value that has changed, update the motion value
5775 * or create one if it doesn't exist. We only want to do this if we're
5776 * not handling the value with our animation state.
5777 */
5778 if (element.hasValue(key)) {
5779 const existingValue = element.getValue(key);
5780 if (existingValue.liveStyle === true) {
5781 existingValue.jump(nextValue);
5782 }
5783 else if (!existingValue.hasAnimated) {
5784 existingValue.set(nextValue);
5785 }
5786 }
5787 else {
5788 const latestValue = element.getStaticValue(key);
5789 element.addValue(key, motionValue(latestValue !== undefined ? latestValue : nextValue, { owner: element }));
5790 }
5791 }
5792 }
5793 // Handle removed values
5794 for (const key in prev) {
5795 if (next[key] === undefined)
5796 element.removeValue(key);
5797 }
5798 return next;
5799 }
5800
5801 const propEventHandlers = [
5802 "AnimationStart",
5803 "AnimationComplete",
5804 "Update",
5805 "BeforeLayoutMeasure",
5806 "LayoutMeasure",
5807 "LayoutAnimationStart",
5808 "LayoutAnimationComplete",
5809 ];
5810 /**
5811 * A VisualElement is an imperative abstraction around UI elements such as
5812 * HTMLElement, SVGElement, Three.Object3D etc.
5813 */
5814 class VisualElement {
5815 /**
5816 * This method takes React props and returns found MotionValues. For example, HTML
5817 * MotionValues will be found within the style prop, whereas for Three.js within attribute arrays.
5818 *
5819 * This isn't an abstract method as it needs calling in the constructor, but it is
5820 * intended to be one.
5821 */
5822 scrapeMotionValuesFromProps(_props, _prevProps, _visualElement) {
5823 return {};
5824 }
5825 constructor({ parent, props, presenceContext, reducedMotionConfig, blockInitialAnimation, visualState, }, options = {}) {
5826 /**
5827 * A reference to the current underlying Instance, e.g. a HTMLElement
5828 * or Three.Mesh etc.
5829 */
5830 this.current = null;
5831 /**
5832 * A set containing references to this VisualElement's children.
5833 */
5834 this.children = new Set();
5835 /**
5836 * Determine what role this visual element should take in the variant tree.
5837 */
5838 this.isVariantNode = false;
5839 this.isControllingVariants = false;
5840 /**
5841 * Decides whether this VisualElement should animate in reduced motion
5842 * mode.
5843 *
5844 * TODO: This is currently set on every individual VisualElement but feels
5845 * like it could be set globally.
5846 */
5847 this.shouldReduceMotion = null;
5848 /**
5849 * A map of all motion values attached to this visual element. Motion
5850 * values are source of truth for any given animated value. A motion
5851 * value might be provided externally by the component via props.
5852 */
5853 this.values = new Map();
5854 this.KeyframeResolver = KeyframeResolver;
5855 /**
5856 * Cleanup functions for active features (hover/tap/exit etc)
5857 */
5858 this.features = {};
5859 /**
5860 * A map of every subscription that binds the provided or generated
5861 * motion values onChange listeners to this visual element.
5862 */
5863 this.valueSubscriptions = new Map();
5864 /**
5865 * A reference to the previously-provided motion values as returned
5866 * from scrapeMotionValuesFromProps. We use the keys in here to determine
5867 * if any motion values need to be removed after props are updated.
5868 */
5869 this.prevMotionValues = {};
5870 /**
5871 * An object containing a SubscriptionManager for each active event.
5872 */
5873 this.events = {};
5874 /**
5875 * An object containing an unsubscribe function for each prop event subscription.
5876 * For example, every "Update" event can have multiple subscribers via
5877 * VisualElement.on(), but only one of those can be defined via the onUpdate prop.
5878 */
5879 this.propEventSubscriptions = {};
5880 this.notifyUpdate = () => this.notify("Update", this.latestValues);
5881 this.render = () => {
5882 if (!this.current)
5883 return;
5884 this.triggerBuild();
5885 this.renderInstance(this.current, this.renderState, this.props.style, this.projection);
5886 };
5887 this.renderScheduledAt = 0.0;
5888 this.scheduleRender = () => {
5889 const now = time.now();
5890 if (this.renderScheduledAt < now) {
5891 this.renderScheduledAt = now;
5892 frame.render(this.render, false, true);
5893 }
5894 };
5895 const { latestValues, renderState } = visualState;
5896 this.latestValues = latestValues;
5897 this.baseTarget = { ...latestValues };
5898 this.initialValues = props.initial ? { ...latestValues } : {};
5899 this.renderState = renderState;
5900 this.parent = parent;
5901 this.props = props;
5902 this.presenceContext = presenceContext;
5903 this.depth = parent ? parent.depth + 1 : 0;
5904 this.reducedMotionConfig = reducedMotionConfig;
5905 this.options = options;
5906 this.blockInitialAnimation = Boolean(blockInitialAnimation);
5907 this.isControllingVariants = isControllingVariants(props);
5908 this.isVariantNode = isVariantNode(props);
5909 if (this.isVariantNode) {
5910 this.variantChildren = new Set();
5911 }
5912 this.manuallyAnimateOnMount = Boolean(parent && parent.current);
5913 /**
5914 * Any motion values that are provided to the element when created
5915 * aren't yet bound to the element, as this would technically be impure.
5916 * However, we iterate through the motion values and set them to the
5917 * initial values for this component.
5918 *
5919 * TODO: This is impure and we should look at changing this to run on mount.
5920 * Doing so will break some tests but this isn't necessarily a breaking change,
5921 * more a reflection of the test.
5922 */
5923 const { willChange, ...initialMotionValues } = this.scrapeMotionValuesFromProps(props, {}, this);
5924 for (const key in initialMotionValues) {
5925 const value = initialMotionValues[key];
5926 if (latestValues[key] !== undefined && isMotionValue(value)) {
5927 value.set(latestValues[key]);
5928 }
5929 }
5930 }
5931 mount(instance) {
5932 this.current = instance;
5933 visualElementStore.set(instance, this);
5934 if (this.projection && !this.projection.instance) {
5935 this.projection.mount(instance);
5936 }
5937 if (this.parent && this.isVariantNode && !this.isControllingVariants) {
5938 this.removeFromVariantTree = this.parent.addVariantChild(this);
5939 }
5940 this.values.forEach((value, key) => this.bindToMotionValue(key, value));
5941 if (!hasReducedMotionListener.current) {
5942 initPrefersReducedMotion();
5943 }
5944 this.shouldReduceMotion =
5945 this.reducedMotionConfig === "never"
5946 ? false
5947 : this.reducedMotionConfig === "always"
5948 ? true
5949 : prefersReducedMotion.current;
5950 {
5951 warnOnce(this.shouldReduceMotion !== true, "You have Reduced Motion enabled on your device. Animations may not appear as expected.", "reduced-motion-disabled");
5952 }
5953 this.parent?.addChild(this);
5954 this.update(this.props, this.presenceContext);
5955 }
5956 unmount() {
5957 this.projection && this.projection.unmount();
5958 cancelFrame(this.notifyUpdate);
5959 cancelFrame(this.render);
5960 this.valueSubscriptions.forEach((remove) => remove());
5961 this.valueSubscriptions.clear();
5962 this.removeFromVariantTree && this.removeFromVariantTree();
5963 this.parent?.removeChild(this);
5964 for (const key in this.events) {
5965 this.events[key].clear();
5966 }
5967 for (const key in this.features) {
5968 const feature = this.features[key];
5969 if (feature) {
5970 feature.unmount();
5971 feature.isMounted = false;
5972 }
5973 }
5974 this.current = null;
5975 }
5976 addChild(child) {
5977 this.children.add(child);
5978 this.enteringChildren ?? (this.enteringChildren = new Set());
5979 this.enteringChildren.add(child);
5980 }
5981 removeChild(child) {
5982 this.children.delete(child);
5983 this.enteringChildren && this.enteringChildren.delete(child);
5984 }
5985 bindToMotionValue(key, value) {
5986 if (this.valueSubscriptions.has(key)) {
5987 this.valueSubscriptions.get(key)();
5988 }
5989 const valueIsTransform = transformProps.has(key);
5990 if (valueIsTransform && this.onBindTransform) {
5991 this.onBindTransform();
5992 }
5993 const removeOnChange = value.on("change", (latestValue) => {
5994 this.latestValues[key] = latestValue;
5995 this.props.onUpdate && frame.preRender(this.notifyUpdate);
5996 if (valueIsTransform && this.projection) {
5997 this.projection.isTransformDirty = true;
5998 }
5999 this.scheduleRender();
6000 });
6001 let removeSyncCheck;
6002 if (window.MotionCheckAppearSync) {
6003 removeSyncCheck = window.MotionCheckAppearSync(this, key, value);
6004 }
6005 this.valueSubscriptions.set(key, () => {
6006 removeOnChange();
6007 if (removeSyncCheck)
6008 removeSyncCheck();
6009 if (value.owner)
6010 value.stop();
6011 });
6012 }
6013 sortNodePosition(other) {
6014 /**
6015 * If these nodes aren't even of the same type we can't compare their depth.
6016 */
6017 if (!this.current ||
6018 !this.sortInstanceNodePosition ||
6019 this.type !== other.type) {
6020 return 0;
6021 }
6022 return this.sortInstanceNodePosition(this.current, other.current);
6023 }
6024 updateFeatures() {
6025 let key = "animation";
6026 for (key in featureDefinitions) {
6027 const featureDefinition = featureDefinitions[key];
6028 if (!featureDefinition)
6029 continue;
6030 const { isEnabled, Feature: FeatureConstructor } = featureDefinition;
6031 /**
6032 * If this feature is enabled but not active, make a new instance.
6033 */
6034 if (!this.features[key] &&
6035 FeatureConstructor &&
6036 isEnabled(this.props)) {
6037 this.features[key] = new FeatureConstructor(this);
6038 }
6039 /**
6040 * If we have a feature, mount or update it.
6041 */
6042 if (this.features[key]) {
6043 const feature = this.features[key];
6044 if (feature.isMounted) {
6045 feature.update();
6046 }
6047 else {
6048 feature.mount();
6049 feature.isMounted = true;
6050 }
6051 }
6052 }
6053 }
6054 triggerBuild() {
6055 this.build(this.renderState, this.latestValues, this.props);
6056 }
6057 /**
6058 * Measure the current viewport box with or without transforms.
6059 * Only measures axis-aligned boxes, rotate and skew must be manually
6060 * removed with a re-render to work.
6061 */
6062 measureViewportBox() {
6063 return this.current
6064 ? this.measureInstanceViewportBox(this.current, this.props)
6065 : createBox();
6066 }
6067 getStaticValue(key) {
6068 return this.latestValues[key];
6069 }
6070 setStaticValue(key, value) {
6071 this.latestValues[key] = value;
6072 }
6073 /**
6074 * Update the provided props. Ensure any newly-added motion values are
6075 * added to our map, old ones removed, and listeners updated.
6076 */
6077 update(props, presenceContext) {
6078 if (props.transformTemplate || this.props.transformTemplate) {
6079 this.scheduleRender();
6080 }
6081 this.prevProps = this.props;
6082 this.props = props;
6083 this.prevPresenceContext = this.presenceContext;
6084 this.presenceContext = presenceContext;
6085 /**
6086 * Update prop event handlers ie onAnimationStart, onAnimationComplete
6087 */
6088 for (let i = 0; i < propEventHandlers.length; i++) {
6089 const key = propEventHandlers[i];
6090 if (this.propEventSubscriptions[key]) {
6091 this.propEventSubscriptions[key]();
6092 delete this.propEventSubscriptions[key];
6093 }
6094 const listenerName = ("on" + key);
6095 const listener = props[listenerName];
6096 if (listener) {
6097 this.propEventSubscriptions[key] = this.on(key, listener);
6098 }
6099 }
6100 this.prevMotionValues = updateMotionValuesFromProps(this, this.scrapeMotionValuesFromProps(props, this.prevProps, this), this.prevMotionValues);
6101 if (this.handleChildMotionValue) {
6102 this.handleChildMotionValue();
6103 }
6104 }
6105 getProps() {
6106 return this.props;
6107 }
6108 /**
6109 * Returns the variant definition with a given name.
6110 */
6111 getVariant(name) {
6112 return this.props.variants ? this.props.variants[name] : undefined;
6113 }
6114 /**
6115 * Returns the defined default transition on this component.
6116 */
6117 getDefaultTransition() {
6118 return this.props.transition;
6119 }
6120 getTransformPagePoint() {
6121 return this.props.transformPagePoint;
6122 }
6123 getClosestVariantNode() {
6124 return this.isVariantNode
6125 ? this
6126 : this.parent
6127 ? this.parent.getClosestVariantNode()
6128 : undefined;
6129 }
6130 /**
6131 * Add a child visual element to our set of children.
6132 */
6133 addVariantChild(child) {
6134 const closestVariantNode = this.getClosestVariantNode();
6135 if (closestVariantNode) {
6136 closestVariantNode.variantChildren &&
6137 closestVariantNode.variantChildren.add(child);
6138 return () => closestVariantNode.variantChildren.delete(child);
6139 }
6140 }
6141 /**
6142 * Add a motion value and bind it to this visual element.
6143 */
6144 addValue(key, value) {
6145 // Remove existing value if it exists
6146 const existingValue = this.values.get(key);
6147 if (value !== existingValue) {
6148 if (existingValue)
6149 this.removeValue(key);
6150 this.bindToMotionValue(key, value);
6151 this.values.set(key, value);
6152 this.latestValues[key] = value.get();
6153 }
6154 }
6155 /**
6156 * Remove a motion value and unbind any active subscriptions.
6157 */
6158 removeValue(key) {
6159 this.values.delete(key);
6160 const unsubscribe = this.valueSubscriptions.get(key);
6161 if (unsubscribe) {
6162 unsubscribe();
6163 this.valueSubscriptions.delete(key);
6164 }
6165 delete this.latestValues[key];
6166 this.removeValueFromRenderState(key, this.renderState);
6167 }
6168 /**
6169 * Check whether we have a motion value for this key
6170 */
6171 hasValue(key) {
6172 return this.values.has(key);
6173 }
6174 getValue(key, defaultValue) {
6175 if (this.props.values && this.props.values[key]) {
6176 return this.props.values[key];
6177 }
6178 let value = this.values.get(key);
6179 if (value === undefined && defaultValue !== undefined) {
6180 value = motionValue(defaultValue === null ? undefined : defaultValue, { owner: this });
6181 this.addValue(key, value);
6182 }
6183 return value;
6184 }
6185 /**
6186 * If we're trying to animate to a previously unencountered value,
6187 * we need to check for it in our state and as a last resort read it
6188 * directly from the instance (which might have performance implications).
6189 */
6190 readValue(key, target) {
6191 let value = this.latestValues[key] !== undefined || !this.current
6192 ? this.latestValues[key]
6193 : this.getBaseTargetFromProps(this.props, key) ??
6194 this.readValueFromInstance(this.current, key, this.options);
6195 if (value !== undefined && value !== null) {
6196 if (typeof value === "string" &&
6197 (isNumericalString(value) || isZeroValueString(value))) {
6198 // If this is a number read as a string, ie "0" or "200", convert it to a number
6199 value = parseFloat(value);
6200 }
6201 else if (!findValueType(value) && complex.test(target)) {
6202 value = getAnimatableNone(key, target);
6203 }
6204 this.setBaseTarget(key, isMotionValue(value) ? value.get() : value);
6205 }
6206 return isMotionValue(value) ? value.get() : value;
6207 }
6208 /**
6209 * Set the base target to later animate back to. This is currently
6210 * only hydrated on creation and when we first read a value.
6211 */
6212 setBaseTarget(key, value) {
6213 this.baseTarget[key] = value;
6214 }
6215 /**
6216 * Find the base target for a value thats been removed from all animation
6217 * props.
6218 */
6219 getBaseTarget(key) {
6220 const { initial } = this.props;
6221 let valueFromInitial;
6222 if (typeof initial === "string" || typeof initial === "object") {
6223 const variant = resolveVariantFromProps(this.props, initial, this.presenceContext?.custom);
6224 if (variant) {
6225 valueFromInitial = variant[key];
6226 }
6227 }
6228 /**
6229 * If this value still exists in the current initial variant, read that.
6230 */
6231 if (initial && valueFromInitial !== undefined) {
6232 return valueFromInitial;
6233 }
6234 /**
6235 * Alternatively, if this VisualElement config has defined a getBaseTarget
6236 * so we can read the value from an alternative source, try that.
6237 */
6238 const target = this.getBaseTargetFromProps(this.props, key);
6239 if (target !== undefined && !isMotionValue(target))
6240 return target;
6241 /**
6242 * If the value was initially defined on initial, but it doesn't any more,
6243 * return undefined. Otherwise return the value as initially read from the DOM.
6244 */
6245 return this.initialValues[key] !== undefined &&
6246 valueFromInitial === undefined
6247 ? undefined
6248 : this.baseTarget[key];
6249 }
6250 on(eventName, callback) {
6251 if (!this.events[eventName]) {
6252 this.events[eventName] = new SubscriptionManager();
6253 }
6254 return this.events[eventName].add(callback);
6255 }
6256 notify(eventName, ...args) {
6257 if (this.events[eventName]) {
6258 this.events[eventName].notify(...args);
6259 }
6260 }
6261 scheduleRenderMicrotask() {
6262 microtask.render(this.render);
6263 }
6264 }
6265
6266 class DOMVisualElement extends VisualElement {
6267 constructor() {
6268 super(...arguments);
6269 this.KeyframeResolver = DOMKeyframesResolver;
6270 }
6271 sortInstanceNodePosition(a, b) {
6272 /**
6273 * compareDocumentPosition returns a bitmask, by using the bitwise &
6274 * we're returning true if 2 in that bitmask is set to true. 2 is set
6275 * to true if b preceeds a.
6276 */
6277 return a.compareDocumentPosition(b) & 2 ? 1 : -1;
6278 }
6279 getBaseTargetFromProps(props, key) {
6280 return props.style
6281 ? props.style[key]
6282 : undefined;
6283 }
6284 removeValueFromRenderState(key, { vars, style }) {
6285 delete vars[key];
6286 delete style[key];
6287 }
6288 handleChildMotionValue() {
6289 if (this.childSubscription) {
6290 this.childSubscription();
6291 delete this.childSubscription;
6292 }
6293 const { children } = this.props;
6294 if (isMotionValue(children)) {
6295 this.childSubscription = children.on("change", (latest) => {
6296 if (this.current) {
6297 this.current.textContent = `${latest}`;
6298 }
6299 });
6300 }
6301 }
6302 }
6303
6304 const translateAlias = {
6305 x: "translateX",
6306 y: "translateY",
6307 z: "translateZ",
6308 transformPerspective: "perspective",
6309 };
6310 const numTransforms = transformPropOrder.length;
6311 /**
6312 * Build a CSS transform style from individual x/y/scale etc properties.
6313 *
6314 * This outputs with a default order of transforms/scales/rotations, this can be customised by
6315 * providing a transformTemplate function.
6316 */
6317 function buildTransform(latestValues, transform, transformTemplate) {
6318 // The transform string we're going to build into.
6319 let transformString = "";
6320 let transformIsDefault = true;
6321 /**
6322 * Loop over all possible transforms in order, adding the ones that
6323 * are present to the transform string.
6324 */
6325 for (let i = 0; i < numTransforms; i++) {
6326 const key = transformPropOrder[i];
6327 const value = latestValues[key];
6328 if (value === undefined)
6329 continue;
6330 let valueIsDefault = true;
6331 if (typeof value === "number") {
6332 valueIsDefault = value === (key.startsWith("scale") ? 1 : 0);
6333 }
6334 else {
6335 valueIsDefault = parseFloat(value) === 0;
6336 }
6337 if (!valueIsDefault || transformTemplate) {
6338 const valueAsType = getValueAsType(value, numberValueTypes[key]);
6339 if (!valueIsDefault) {
6340 transformIsDefault = false;
6341 const transformName = translateAlias[key] || key;
6342 transformString += `${transformName}(${valueAsType}) `;
6343 }
6344 if (transformTemplate) {
6345 transform[key] = valueAsType;
6346 }
6347 }
6348 }
6349 transformString = transformString.trim();
6350 // If we have a custom `transform` template, pass our transform values and
6351 // generated transformString to that before returning
6352 if (transformTemplate) {
6353 transformString = transformTemplate(transform, transformIsDefault ? "" : transformString);
6354 }
6355 else if (transformIsDefault) {
6356 transformString = "none";
6357 }
6358 return transformString;
6359 }
6360
6361 function buildHTMLStyles(state, latestValues, transformTemplate) {
6362 const { style, vars, transformOrigin } = state;
6363 // Track whether we encounter any transform or transformOrigin values.
6364 let hasTransform = false;
6365 let hasTransformOrigin = false;
6366 /**
6367 * Loop over all our latest animated values and decide whether to handle them
6368 * as a style or CSS variable.
6369 *
6370 * Transforms and transform origins are kept separately for further processing.
6371 */
6372 for (const key in latestValues) {
6373 const value = latestValues[key];
6374 if (transformProps.has(key)) {
6375 // If this is a transform, flag to enable further transform processing
6376 hasTransform = true;
6377 continue;
6378 }
6379 else if (isCSSVariableName(key)) {
6380 vars[key] = value;
6381 continue;
6382 }
6383 else {
6384 // Convert the value to its default value type, ie 0 -> "0px"
6385 const valueAsType = getValueAsType(value, numberValueTypes[key]);
6386 if (key.startsWith("origin")) {
6387 // If this is a transform origin, flag and enable further transform-origin processing
6388 hasTransformOrigin = true;
6389 transformOrigin[key] =
6390 valueAsType;
6391 }
6392 else {
6393 style[key] = valueAsType;
6394 }
6395 }
6396 }
6397 if (!latestValues.transform) {
6398 if (hasTransform || transformTemplate) {
6399 style.transform = buildTransform(latestValues, state.transform, transformTemplate);
6400 }
6401 else if (style.transform) {
6402 /**
6403 * If we have previously created a transform but currently don't have any,
6404 * reset transform style to none.
6405 */
6406 style.transform = "none";
6407 }
6408 }
6409 /**
6410 * Build a transformOrigin style. Uses the same defaults as the browser for
6411 * undefined origins.
6412 */
6413 if (hasTransformOrigin) {
6414 const { originX = "50%", originY = "50%", originZ = 0, } = transformOrigin;
6415 style.transformOrigin = `${originX} ${originY} ${originZ}`;
6416 }
6417 }
6418
6419 function renderHTML(element, { style, vars }, styleProp, projection) {
6420 const elementStyle = element.style;
6421 let key;
6422 for (key in style) {
6423 // CSSStyleDeclaration has [index: number]: string; in the types, so we use that as key type.
6424 elementStyle[key] = style[key];
6425 }
6426 // Write projection styles directly to element style
6427 projection?.applyProjectionStyles(elementStyle, styleProp);
6428 for (key in vars) {
6429 // Loop over any CSS variables and assign those.
6430 // They can only be assigned using `setProperty`.
6431 elementStyle.setProperty(key, vars[key]);
6432 }
6433 }
6434
6435 const scaleCorrectors = {};
6436
6437 function isForcedMotionValue(key, { layout, layoutId }) {
6438 return (transformProps.has(key) ||
6439 key.startsWith("origin") ||
6440 ((layout || layoutId !== undefined) &&
6441 (!!scaleCorrectors[key] || key === "opacity")));
6442 }
6443
6444 function scrapeMotionValuesFromProps$1(props, prevProps, visualElement) {
6445 const { style } = props;
6446 const newValues = {};
6447 for (const key in style) {
6448 if (isMotionValue(style[key]) ||
6449 (prevProps.style &&
6450 isMotionValue(prevProps.style[key])) ||
6451 isForcedMotionValue(key, props) ||
6452 visualElement?.getValue(key)?.liveStyle !== undefined) {
6453 newValues[key] = style[key];
6454 }
6455 }
6456 return newValues;
6457 }
6458
6459 function getComputedStyle$1(element) {
6460 return window.getComputedStyle(element);
6461 }
6462 class HTMLVisualElement extends DOMVisualElement {
6463 constructor() {
6464 super(...arguments);
6465 this.type = "html";
6466 this.renderInstance = renderHTML;
6467 }
6468 readValueFromInstance(instance, key) {
6469 if (transformProps.has(key)) {
6470 return this.projection?.isProjecting
6471 ? defaultTransformValue(key)
6472 : readTransformValue(instance, key);
6473 }
6474 else {
6475 const computedStyle = getComputedStyle$1(instance);
6476 const value = (isCSSVariableName(key)
6477 ? computedStyle.getPropertyValue(key)
6478 : computedStyle[key]) || 0;
6479 return typeof value === "string" ? value.trim() : value;
6480 }
6481 }
6482 measureInstanceViewportBox(instance, { transformPagePoint }) {
6483 return measureViewportBox(instance, transformPagePoint);
6484 }
6485 build(renderState, latestValues, props) {
6486 buildHTMLStyles(renderState, latestValues, props.transformTemplate);
6487 }
6488 scrapeMotionValuesFromProps(props, prevProps, visualElement) {
6489 return scrapeMotionValuesFromProps$1(props, prevProps, visualElement);
6490 }
6491 }
6492
6493 function isObjectKey(key, object) {
6494 return key in object;
6495 }
6496 class ObjectVisualElement extends VisualElement {
6497 constructor() {
6498 super(...arguments);
6499 this.type = "object";
6500 }
6501 readValueFromInstance(instance, key) {
6502 if (isObjectKey(key, instance)) {
6503 const value = instance[key];
6504 if (typeof value === "string" || typeof value === "number") {
6505 return value;
6506 }
6507 }
6508 return undefined;
6509 }
6510 getBaseTargetFromProps() {
6511 return undefined;
6512 }
6513 removeValueFromRenderState(key, renderState) {
6514 delete renderState.output[key];
6515 }
6516 measureInstanceViewportBox() {
6517 return createBox();
6518 }
6519 build(renderState, latestValues) {
6520 Object.assign(renderState.output, latestValues);
6521 }
6522 renderInstance(instance, { output }) {
6523 Object.assign(instance, output);
6524 }
6525 sortInstanceNodePosition() {
6526 return 0;
6527 }
6528 }
6529
6530 const dashKeys = {
6531 offset: "stroke-dashoffset",
6532 array: "stroke-dasharray",
6533 };
6534 const camelKeys = {
6535 offset: "strokeDashoffset",
6536 array: "strokeDasharray",
6537 };
6538 /**
6539 * Build SVG path properties. Uses the path's measured length to convert
6540 * our custom pathLength, pathSpacing and pathOffset into stroke-dashoffset
6541 * and stroke-dasharray attributes.
6542 *
6543 * This function is mutative to reduce per-frame GC.
6544 */
6545 function buildSVGPath(attrs, length, spacing = 1, offset = 0, useDashCase = true) {
6546 // Normalise path length by setting SVG attribute pathLength to 1
6547 attrs.pathLength = 1;
6548 // We use dash case when setting attributes directly to the DOM node and camel case
6549 // when defining props on a React component.
6550 const keys = useDashCase ? dashKeys : camelKeys;
6551 // Build the dash offset
6552 attrs[keys.offset] = px.transform(-offset);
6553 // Build the dash array
6554 const pathLength = px.transform(length);
6555 const pathSpacing = px.transform(spacing);
6556 attrs[keys.array] = `${pathLength} ${pathSpacing}`;
6557 }
6558
6559 /**
6560 * Build SVG visual attributes, like cx and style.transform
6561 */
6562 function buildSVGAttrs(state, { attrX, attrY, attrScale, pathLength, pathSpacing = 1, pathOffset = 0,
6563 // This is object creation, which we try to avoid per-frame.
6564 ...latest }, isSVGTag, transformTemplate, styleProp) {
6565 buildHTMLStyles(state, latest, transformTemplate);
6566 /**
6567 * For svg tags we just want to make sure viewBox is animatable and treat all the styles
6568 * as normal HTML tags.
6569 */
6570 if (isSVGTag) {
6571 if (state.style.viewBox) {
6572 state.attrs.viewBox = state.style.viewBox;
6573 }
6574 return;
6575 }
6576 state.attrs = state.style;
6577 state.style = {};
6578 const { attrs, style } = state;
6579 /**
6580 * However, we apply transforms as CSS transforms.
6581 * So if we detect a transform, transformOrigin we take it from attrs and copy it into style.
6582 */
6583 if (attrs.transform) {
6584 style.transform = attrs.transform;
6585 delete attrs.transform;
6586 }
6587 if (style.transform || attrs.transformOrigin) {
6588 style.transformOrigin = attrs.transformOrigin ?? "50% 50%";
6589 delete attrs.transformOrigin;
6590 }
6591 if (style.transform) {
6592 /**
6593 * SVG's element transform-origin uses its own median as a reference.
6594 * Therefore, transformBox becomes a fill-box
6595 */
6596 style.transformBox = styleProp?.transformBox ?? "fill-box";
6597 delete attrs.transformBox;
6598 }
6599 // Render attrX/attrY/attrScale as attributes
6600 if (attrX !== undefined)
6601 attrs.x = attrX;
6602 if (attrY !== undefined)
6603 attrs.y = attrY;
6604 if (attrScale !== undefined)
6605 attrs.scale = attrScale;
6606 // Build SVG path if one has been defined
6607 if (pathLength !== undefined) {
6608 buildSVGPath(attrs, pathLength, pathSpacing, pathOffset, false);
6609 }
6610 }
6611
6612 /**
6613 * A set of attribute names that are always read/written as camel case.
6614 */
6615 const camelCaseAttributes = new Set([
6616 "baseFrequency",
6617 "diffuseConstant",
6618 "kernelMatrix",
6619 "kernelUnitLength",
6620 "keySplines",
6621 "keyTimes",
6622 "limitingConeAngle",
6623 "markerHeight",
6624 "markerWidth",
6625 "numOctaves",
6626 "targetX",
6627 "targetY",
6628 "surfaceScale",
6629 "specularConstant",
6630 "specularExponent",
6631 "stdDeviation",
6632 "tableValues",
6633 "viewBox",
6634 "gradientTransform",
6635 "pathLength",
6636 "startOffset",
6637 "textLength",
6638 "lengthAdjust",
6639 ]);
6640
6641 const isSVGTag = (tag) => typeof tag === "string" && tag.toLowerCase() === "svg";
6642
6643 function renderSVG(element, renderState, _styleProp, projection) {
6644 renderHTML(element, renderState, undefined, projection);
6645 for (const key in renderState.attrs) {
6646 element.setAttribute(!camelCaseAttributes.has(key) ? camelToDash(key) : key, renderState.attrs[key]);
6647 }
6648 }
6649
6650 function scrapeMotionValuesFromProps(props, prevProps, visualElement) {
6651 const newValues = scrapeMotionValuesFromProps$1(props, prevProps, visualElement);
6652 for (const key in props) {
6653 if (isMotionValue(props[key]) ||
6654 isMotionValue(prevProps[key])) {
6655 const targetKey = transformPropOrder.indexOf(key) !== -1
6656 ? "attr" + key.charAt(0).toUpperCase() + key.substring(1)
6657 : key;
6658 newValues[targetKey] = props[key];
6659 }
6660 }
6661 return newValues;
6662 }
6663
6664 class SVGVisualElement extends DOMVisualElement {
6665 constructor() {
6666 super(...arguments);
6667 this.type = "svg";
6668 this.isSVGTag = false;
6669 this.measureInstanceViewportBox = createBox;
6670 }
6671 getBaseTargetFromProps(props, key) {
6672 return props[key];
6673 }
6674 readValueFromInstance(instance, key) {
6675 if (transformProps.has(key)) {
6676 const defaultType = getDefaultValueType(key);
6677 return defaultType ? defaultType.default || 0 : 0;
6678 }
6679 key = !camelCaseAttributes.has(key) ? camelToDash(key) : key;
6680 return instance.getAttribute(key);
6681 }
6682 scrapeMotionValuesFromProps(props, prevProps, visualElement) {
6683 return scrapeMotionValuesFromProps(props, prevProps, visualElement);
6684 }
6685 build(renderState, latestValues, props) {
6686 buildSVGAttrs(renderState, latestValues, this.isSVGTag, props.transformTemplate, props.style);
6687 }
6688 renderInstance(instance, renderState, styleProp, projection) {
6689 renderSVG(instance, renderState, styleProp, projection);
6690 }
6691 mount(instance) {
6692 this.isSVGTag = isSVGTag(instance.tagName);
6693 super.mount(instance);
6694 }
6695 }
6696
6697 function createDOMVisualElement(element) {
6698 const options = {
6699 presenceContext: null,
6700 props: {},
6701 visualState: {
6702 renderState: {
6703 transform: {},
6704 transformOrigin: {},
6705 style: {},
6706 vars: {},
6707 attrs: {},
6708 },
6709 latestValues: {},
6710 },
6711 };
6712 const node = isSVGElement(element) && !isSVGSVGElement(element)
6713 ? new SVGVisualElement(options)
6714 : new HTMLVisualElement(options);
6715 node.mount(element);
6716 visualElementStore.set(element, node);
6717 }
6718 function createObjectVisualElement(subject) {
6719 const options = {
6720 presenceContext: null,
6721 props: {},
6722 visualState: {
6723 renderState: {
6724 output: {},
6725 },
6726 latestValues: {},
6727 },
6728 };
6729 const node = new ObjectVisualElement(options);
6730 node.mount(subject);
6731 visualElementStore.set(subject, node);
6732 }
6733
6734 function animateSingleValue(value, keyframes, options) {
6735 const motionValue$1 = isMotionValue(value) ? value : motionValue(value);
6736 motionValue$1.start(animateMotionValue("", motionValue$1, keyframes, options));
6737 return motionValue$1.animation;
6738 }
6739
6740 function isSingleValue(subject, keyframes) {
6741 return (isMotionValue(subject) ||
6742 typeof subject === "number" ||
6743 (typeof subject === "string" && !isDOMKeyframes(keyframes)));
6744 }
6745 /**
6746 * Implementation
6747 */
6748 function animateSubject(subject, keyframes, options, scope) {
6749 const animations = [];
6750 if (isSingleValue(subject, keyframes)) {
6751 animations.push(animateSingleValue(subject, isDOMKeyframes(keyframes)
6752 ? keyframes.default || keyframes
6753 : keyframes, options ? options.default || options : options));
6754 }
6755 else {
6756 const subjects = resolveSubjects(subject, keyframes, scope);
6757 const numSubjects = subjects.length;
6758 exports.invariant(Boolean(numSubjects), "No valid elements provided.", "no-valid-elements");
6759 for (let i = 0; i < numSubjects; i++) {
6760 const thisSubject = subjects[i];
6761 exports.invariant(thisSubject !== null, "You're trying to perform an animation on null. Ensure that selectors are correctly finding elements and refs are correctly hydrated.", "animate-null");
6762 const createVisualElement = thisSubject instanceof Element
6763 ? createDOMVisualElement
6764 : createObjectVisualElement;
6765 if (!visualElementStore.has(thisSubject)) {
6766 createVisualElement(thisSubject);
6767 }
6768 const visualElement = visualElementStore.get(thisSubject);
6769 const transition = { ...options };
6770 /**
6771 * Resolve stagger function if provided.
6772 */
6773 if ("delay" in transition &&
6774 typeof transition.delay === "function") {
6775 transition.delay = transition.delay(i, numSubjects);
6776 }
6777 animations.push(...animateTarget(visualElement, { ...keyframes, transition }, {}));
6778 }
6779 }
6780 return animations;
6781 }
6782
6783 function animateSequence(sequence, options, scope) {
6784 const animations = [];
6785 const animationDefinitions = createAnimationsFromSequence(sequence, options, scope, { spring });
6786 animationDefinitions.forEach(({ keyframes, transition }, subject) => {
6787 animations.push(...animateSubject(subject, keyframes, transition));
6788 });
6789 return animations;
6790 }
6791
6792 function isSequence(value) {
6793 return Array.isArray(value) && value.some(Array.isArray);
6794 }
6795 /**
6796 * Creates an animation function that is optionally scoped
6797 * to a specific element.
6798 */
6799 function createScopedAnimate(scope) {
6800 /**
6801 * Implementation
6802 */
6803 function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) {
6804 let animations = [];
6805 let animationOnComplete;
6806 if (isSequence(subjectOrSequence)) {
6807 animations = animateSequence(subjectOrSequence, optionsOrKeyframes, scope);
6808 }
6809 else {
6810 // Extract top-level onComplete so it doesn't get applied per-value
6811 const { onComplete, ...rest } = options || {};
6812 if (typeof onComplete === "function") {
6813 animationOnComplete = onComplete;
6814 }
6815 animations = animateSubject(subjectOrSequence, optionsOrKeyframes, rest, scope);
6816 }
6817 const animation = new GroupAnimationWithThen(animations);
6818 if (animationOnComplete) {
6819 animation.finished.then(animationOnComplete);
6820 }
6821 if (scope) {
6822 scope.animations.push(animation);
6823 animation.finished.then(() => {
6824 removeItem(scope.animations, animation);
6825 });
6826 }
6827 return animation;
6828 }
6829 return scopedAnimate;
6830 }
6831 const animate = createScopedAnimate();
6832
6833 function animateElements(elementOrSelector, keyframes, options, scope) {
6834 const elements = resolveElements(elementOrSelector, scope);
6835 const numElements = elements.length;
6836 exports.invariant(Boolean(numElements), "No valid elements provided.", "no-valid-elements");
6837 /**
6838 * WAAPI doesn't support interrupting animations.
6839 *
6840 * Therefore, starting animations requires a three-step process:
6841 * 1. Stop existing animations (write styles to DOM)
6842 * 2. Resolve keyframes (read styles from DOM)
6843 * 3. Create new animations (write styles to DOM)
6844 *
6845 * The hybrid `animate()` function uses AsyncAnimation to resolve
6846 * keyframes before creating new animations, which removes style
6847 * thrashing. Here, we have much stricter filesize constraints.
6848 * Therefore we do this in a synchronous way that ensures that
6849 * at least within `animate()` calls there is no style thrashing.
6850 *
6851 * In the motion-native-animate-mini-interrupt benchmark this
6852 * was 80% faster than a single loop.
6853 */
6854 const animationDefinitions = [];
6855 /**
6856 * Step 1: Build options and stop existing animations (write)
6857 */
6858 for (let i = 0; i < numElements; i++) {
6859 const element = elements[i];
6860 const elementTransition = { ...options };
6861 /**
6862 * Resolve stagger function if provided.
6863 */
6864 if (typeof elementTransition.delay === "function") {
6865 elementTransition.delay = elementTransition.delay(i, numElements);
6866 }
6867 for (const valueName in keyframes) {
6868 let valueKeyframes = keyframes[valueName];
6869 if (!Array.isArray(valueKeyframes)) {
6870 valueKeyframes = [valueKeyframes];
6871 }
6872 const valueOptions = {
6873 ...getValueTransition$1(elementTransition, valueName),
6874 };
6875 valueOptions.duration && (valueOptions.duration = secondsToMilliseconds(valueOptions.duration));
6876 valueOptions.delay && (valueOptions.delay = secondsToMilliseconds(valueOptions.delay));
6877 /**
6878 * If there's an existing animation playing on this element then stop it
6879 * before creating a new one.
6880 */
6881 const map = getAnimationMap(element);
6882 const key = animationMapKey(valueName, valueOptions.pseudoElement || "");
6883 const currentAnimation = map.get(key);
6884 currentAnimation && currentAnimation.stop();
6885 animationDefinitions.push({
6886 map,
6887 key,
6888 unresolvedKeyframes: valueKeyframes,
6889 options: {
6890 ...valueOptions,
6891 element,
6892 name: valueName,
6893 allowFlatten: !elementTransition.type && !elementTransition.ease,
6894 },
6895 });
6896 }
6897 }
6898 /**
6899 * Step 2: Resolve keyframes (read)
6900 */
6901 for (let i = 0; i < animationDefinitions.length; i++) {
6902 const { unresolvedKeyframes, options: animationOptions } = animationDefinitions[i];
6903 const { element, name, pseudoElement } = animationOptions;
6904 if (!pseudoElement && unresolvedKeyframes[0] === null) {
6905 unresolvedKeyframes[0] = getComputedStyle$2(element, name);
6906 }
6907 fillWildcards(unresolvedKeyframes);
6908 applyPxDefaults(unresolvedKeyframes, name);
6909 /**
6910 * If we only have one keyframe, explicitly read the initial keyframe
6911 * from the computed style. This is to ensure consistency with WAAPI behaviour
6912 * for restarting animations, for instance .play() after finish, when it
6913 * has one vs two keyframes.
6914 */
6915 if (!pseudoElement && unresolvedKeyframes.length < 2) {
6916 unresolvedKeyframes.unshift(getComputedStyle$2(element, name));
6917 }
6918 animationOptions.keyframes = unresolvedKeyframes;
6919 }
6920 /**
6921 * Step 3: Create new animations (write)
6922 */
6923 const animations = [];
6924 for (let i = 0; i < animationDefinitions.length; i++) {
6925 const { map, key, options: animationOptions } = animationDefinitions[i];
6926 const animation = new NativeAnimation(animationOptions);
6927 map.set(key, animation);
6928 animation.finished.finally(() => map.delete(key));
6929 animations.push(animation);
6930 }
6931 return animations;
6932 }
6933
6934 const createScopedWaapiAnimate = (scope) => {
6935 function scopedAnimate(elementOrSelector, keyframes, options) {
6936 return new GroupAnimationWithThen(animateElements(elementOrSelector, keyframes, options, scope));
6937 }
6938 return scopedAnimate;
6939 };
6940 const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
6941
6942 /**
6943 * A time in milliseconds, beyond which we consider the scroll velocity to be 0.
6944 */
6945 const maxElapsed = 50;
6946 const createAxisInfo = () => ({
6947 current: 0,
6948 offset: [],
6949 progress: 0,
6950 scrollLength: 0,
6951 targetOffset: 0,
6952 targetLength: 0,
6953 containerLength: 0,
6954 velocity: 0,
6955 });
6956 const createScrollInfo = () => ({
6957 time: 0,
6958 x: createAxisInfo(),
6959 y: createAxisInfo(),
6960 });
6961 const keys = {
6962 x: {
6963 length: "Width",
6964 position: "Left",
6965 },
6966 y: {
6967 length: "Height",
6968 position: "Top",
6969 },
6970 };
6971 function updateAxisInfo(element, axisName, info, time) {
6972 const axis = info[axisName];
6973 const { length, position } = keys[axisName];
6974 const prev = axis.current;
6975 const prevTime = info.time;
6976 axis.current = element[`scroll${position}`];
6977 axis.scrollLength = element[`scroll${length}`] - element[`client${length}`];
6978 axis.offset.length = 0;
6979 axis.offset[0] = 0;
6980 axis.offset[1] = axis.scrollLength;
6981 axis.progress = progress(0, axis.scrollLength, axis.current);
6982 const elapsed = time - prevTime;
6983 axis.velocity =
6984 elapsed > maxElapsed
6985 ? 0
6986 : velocityPerSecond(axis.current - prev, elapsed);
6987 }
6988 function updateScrollInfo(element, info, time) {
6989 updateAxisInfo(element, "x", info, time);
6990 updateAxisInfo(element, "y", info, time);
6991 info.time = time;
6992 }
6993
6994 function calcInset(element, container) {
6995 const inset = { x: 0, y: 0 };
6996 let current = element;
6997 while (current && current !== container) {
6998 if (isHTMLElement(current)) {
6999 inset.x += current.offsetLeft;
7000 inset.y += current.offsetTop;
7001 current = current.offsetParent;
7002 }
7003 else if (current.tagName === "svg") {
7004 /**
7005 * This isn't an ideal approach to measuring the offset of <svg /> tags.
7006 * It would be preferable, given they behave like HTMLElements in most ways
7007 * to use offsetLeft/Top. But these don't exist on <svg />. Likewise we
7008 * can't use .getBBox() like most SVG elements as these provide the offset
7009 * relative to the SVG itself, which for <svg /> is usually 0x0.
7010 */
7011 const svgBoundingBox = current.getBoundingClientRect();
7012 current = current.parentElement;
7013 const parentBoundingBox = current.getBoundingClientRect();
7014 inset.x += svgBoundingBox.left - parentBoundingBox.left;
7015 inset.y += svgBoundingBox.top - parentBoundingBox.top;
7016 }
7017 else if (current instanceof SVGGraphicsElement) {
7018 const { x, y } = current.getBBox();
7019 inset.x += x;
7020 inset.y += y;
7021 let svg = null;
7022 let parent = current.parentNode;
7023 while (!svg) {
7024 if (parent.tagName === "svg") {
7025 svg = parent;
7026 }
7027 parent = current.parentNode;
7028 }
7029 current = svg;
7030 }
7031 else {
7032 break;
7033 }
7034 }
7035 return inset;
7036 }
7037
7038 const namedEdges = {
7039 start: 0,
7040 center: 0.5,
7041 end: 1,
7042 };
7043 function resolveEdge(edge, length, inset = 0) {
7044 let delta = 0;
7045 /**
7046 * If we have this edge defined as a preset, replace the definition
7047 * with the numerical value.
7048 */
7049 if (edge in namedEdges) {
7050 edge = namedEdges[edge];
7051 }
7052 /**
7053 * Handle unit values
7054 */
7055 if (typeof edge === "string") {
7056 const asNumber = parseFloat(edge);
7057 if (edge.endsWith("px")) {
7058 delta = asNumber;
7059 }
7060 else if (edge.endsWith("%")) {
7061 edge = asNumber / 100;
7062 }
7063 else if (edge.endsWith("vw")) {
7064 delta = (asNumber / 100) * document.documentElement.clientWidth;
7065 }
7066 else if (edge.endsWith("vh")) {
7067 delta = (asNumber / 100) * document.documentElement.clientHeight;
7068 }
7069 else {
7070 edge = asNumber;
7071 }
7072 }
7073 /**
7074 * If the edge is defined as a number, handle as a progress value.
7075 */
7076 if (typeof edge === "number") {
7077 delta = length * edge;
7078 }
7079 return inset + delta;
7080 }
7081
7082 const defaultOffset = [0, 0];
7083 function resolveOffset(offset, containerLength, targetLength, targetInset) {
7084 let offsetDefinition = Array.isArray(offset) ? offset : defaultOffset;
7085 let targetPoint = 0;
7086 let containerPoint = 0;
7087 if (typeof offset === "number") {
7088 /**
7089 * If we're provided offset: [0, 0.5, 1] then each number x should become
7090 * [x, x], so we default to the behaviour of mapping 0 => 0 of both target
7091 * and container etc.
7092 */
7093 offsetDefinition = [offset, offset];
7094 }
7095 else if (typeof offset === "string") {
7096 offset = offset.trim();
7097 if (offset.includes(" ")) {
7098 offsetDefinition = offset.split(" ");
7099 }
7100 else {
7101 /**
7102 * If we're provided a definition like "100px" then we want to apply
7103 * that only to the top of the target point, leaving the container at 0.
7104 * Whereas a named offset like "end" should be applied to both.
7105 */
7106 offsetDefinition = [offset, namedEdges[offset] ? offset : `0`];
7107 }
7108 }
7109 targetPoint = resolveEdge(offsetDefinition[0], targetLength, targetInset);
7110 containerPoint = resolveEdge(offsetDefinition[1], containerLength);
7111 return targetPoint - containerPoint;
7112 }
7113
7114 const ScrollOffset = {
7115 Enter: [
7116 [0, 1],
7117 [1, 1],
7118 ],
7119 Exit: [
7120 [0, 0],
7121 [1, 0],
7122 ],
7123 Any: [
7124 [1, 0],
7125 [0, 1],
7126 ],
7127 All: [
7128 [0, 0],
7129 [1, 1],
7130 ],
7131 };
7132
7133 const point = { x: 0, y: 0 };
7134 function getTargetSize(target) {
7135 return "getBBox" in target && target.tagName !== "svg"
7136 ? target.getBBox()
7137 : { width: target.clientWidth, height: target.clientHeight };
7138 }
7139 function resolveOffsets(container, info, options) {
7140 const { offset: offsetDefinition = ScrollOffset.All } = options;
7141 const { target = container, axis = "y" } = options;
7142 const lengthLabel = axis === "y" ? "height" : "width";
7143 const inset = target !== container ? calcInset(target, container) : point;
7144 /**
7145 * Measure the target and container. If they're the same thing then we
7146 * use the container's scrollWidth/Height as the target, from there
7147 * all other calculations can remain the same.
7148 */
7149 const targetSize = target === container
7150 ? { width: container.scrollWidth, height: container.scrollHeight }
7151 : getTargetSize(target);
7152 const containerSize = {
7153 width: container.clientWidth,
7154 height: container.clientHeight,
7155 };
7156 /**
7157 * Reset the length of the resolved offset array rather than creating a new one.
7158 * TODO: More reusable data structures for targetSize/containerSize would also be good.
7159 */
7160 info[axis].offset.length = 0;
7161 /**
7162 * Populate the offset array by resolving the user's offset definition into
7163 * a list of pixel scroll offets.
7164 */
7165 let hasChanged = !info[axis].interpolate;
7166 const numOffsets = offsetDefinition.length;
7167 for (let i = 0; i < numOffsets; i++) {
7168 const offset = resolveOffset(offsetDefinition[i], containerSize[lengthLabel], targetSize[lengthLabel], inset[axis]);
7169 if (!hasChanged && offset !== info[axis].interpolatorOffsets[i]) {
7170 hasChanged = true;
7171 }
7172 info[axis].offset[i] = offset;
7173 }
7174 /**
7175 * If the pixel scroll offsets have changed, create a new interpolator function
7176 * to map scroll value into a progress.
7177 */
7178 if (hasChanged) {
7179 info[axis].interpolate = interpolate(info[axis].offset, defaultOffset$1(offsetDefinition), { clamp: false });
7180 info[axis].interpolatorOffsets = [...info[axis].offset];
7181 }
7182 info[axis].progress = clamp(0, 1, info[axis].interpolate(info[axis].current));
7183 }
7184
7185 function measure(container, target = container, info) {
7186 /**
7187 * Find inset of target within scrollable container
7188 */
7189 info.x.targetOffset = 0;
7190 info.y.targetOffset = 0;
7191 if (target !== container) {
7192 let node = target;
7193 while (node && node !== container) {
7194 info.x.targetOffset += node.offsetLeft;
7195 info.y.targetOffset += node.offsetTop;
7196 node = node.offsetParent;
7197 }
7198 }
7199 info.x.targetLength =
7200 target === container ? target.scrollWidth : target.clientWidth;
7201 info.y.targetLength =
7202 target === container ? target.scrollHeight : target.clientHeight;
7203 info.x.containerLength = container.clientWidth;
7204 info.y.containerLength = container.clientHeight;
7205 /**
7206 * In development mode ensure scroll containers aren't position: static as this makes
7207 * it difficult to measure their relative positions.
7208 */
7209 {
7210 if (container && target && target !== container) {
7211 warnOnce(getComputedStyle(container).position !== "static", "Please ensure that the container has a non-static position, like 'relative', 'fixed', or 'absolute' to ensure scroll offset is calculated correctly.");
7212 }
7213 }
7214 }
7215 function createOnScrollHandler(element, onScroll, info, options = {}) {
7216 return {
7217 measure: (time) => {
7218 measure(element, options.target, info);
7219 updateScrollInfo(element, info, time);
7220 if (options.offset || options.target) {
7221 resolveOffsets(element, info, options);
7222 }
7223 },
7224 notify: () => onScroll(info),
7225 };
7226 }
7227
7228 const scrollListeners = new WeakMap();
7229 const resizeListeners = new WeakMap();
7230 const onScrollHandlers = new WeakMap();
7231 const getEventTarget = (element) => element === document.scrollingElement ? window : element;
7232 function scrollInfo(onScroll, { container = document.scrollingElement, ...options } = {}) {
7233 if (!container)
7234 return noop;
7235 let containerHandlers = onScrollHandlers.get(container);
7236 /**
7237 * Get the onScroll handlers for this container.
7238 * If one isn't found, create a new one.
7239 */
7240 if (!containerHandlers) {
7241 containerHandlers = new Set();
7242 onScrollHandlers.set(container, containerHandlers);
7243 }
7244 /**
7245 * Create a new onScroll handler for the provided callback.
7246 */
7247 const info = createScrollInfo();
7248 const containerHandler = createOnScrollHandler(container, onScroll, info, options);
7249 containerHandlers.add(containerHandler);
7250 /**
7251 * Check if there's a scroll event listener for this container.
7252 * If not, create one.
7253 */
7254 if (!scrollListeners.has(container)) {
7255 const measureAll = () => {
7256 for (const handler of containerHandlers) {
7257 handler.measure(frameData.timestamp);
7258 }
7259 frame.preUpdate(notifyAll);
7260 };
7261 const notifyAll = () => {
7262 for (const handler of containerHandlers) {
7263 handler.notify();
7264 }
7265 };
7266 const listener = () => frame.read(measureAll);
7267 scrollListeners.set(container, listener);
7268 const target = getEventTarget(container);
7269 window.addEventListener("resize", listener, { passive: true });
7270 if (container !== document.documentElement) {
7271 resizeListeners.set(container, resize(container, listener));
7272 }
7273 target.addEventListener("scroll", listener, { passive: true });
7274 listener();
7275 }
7276 const listener = scrollListeners.get(container);
7277 frame.read(listener, false, true);
7278 return () => {
7279 cancelFrame(listener);
7280 /**
7281 * Check if we even have any handlers for this container.
7282 */
7283 const currentHandlers = onScrollHandlers.get(container);
7284 if (!currentHandlers)
7285 return;
7286 currentHandlers.delete(containerHandler);
7287 if (currentHandlers.size)
7288 return;
7289 /**
7290 * If no more handlers, remove the scroll listener too.
7291 */
7292 const scrollListener = scrollListeners.get(container);
7293 scrollListeners.delete(container);
7294 if (scrollListener) {
7295 getEventTarget(container).removeEventListener("scroll", scrollListener);
7296 resizeListeners.get(container)?.();
7297 window.removeEventListener("resize", scrollListener);
7298 }
7299 };
7300 }
7301
7302 const timelineCache = new Map();
7303 function scrollTimelineFallback(options) {
7304 const currentTime = { value: 0 };
7305 const cancel = scrollInfo((info) => {
7306 currentTime.value = info[options.axis].progress * 100;
7307 }, options);
7308 return { currentTime, cancel };
7309 }
7310 function getTimeline({ source, container, ...options }) {
7311 const { axis } = options;
7312 if (source)
7313 container = source;
7314 const containerCache = timelineCache.get(container) ?? new Map();
7315 timelineCache.set(container, containerCache);
7316 const targetKey = options.target ?? "self";
7317 const targetCache = containerCache.get(targetKey) ?? {};
7318 const axisKey = axis + (options.offset ?? []).join(",");
7319 if (!targetCache[axisKey]) {
7320 targetCache[axisKey] =
7321 !options.target && supportsScrollTimeline()
7322 ? new ScrollTimeline({ source: container, axis })
7323 : scrollTimelineFallback({ container, ...options });
7324 }
7325 return targetCache[axisKey];
7326 }
7327
7328 function attachToAnimation(animation, options) {
7329 const timeline = getTimeline(options);
7330 return animation.attachTimeline({
7331 timeline: options.target ? undefined : timeline,
7332 observe: (valueAnimation) => {
7333 valueAnimation.pause();
7334 return observeTimeline((progress) => {
7335 valueAnimation.time =
7336 valueAnimation.iterationDuration * progress;
7337 }, timeline);
7338 },
7339 });
7340 }
7341
7342 /**
7343 * If the onScroll function has two arguments, it's expecting
7344 * more specific information about the scroll from scrollInfo.
7345 */
7346 function isOnScrollWithInfo(onScroll) {
7347 return onScroll.length === 2;
7348 }
7349 function attachToFunction(onScroll, options) {
7350 if (isOnScrollWithInfo(onScroll)) {
7351 return scrollInfo((info) => {
7352 onScroll(info[options.axis].progress, info);
7353 }, options);
7354 }
7355 else {
7356 return observeTimeline(onScroll, getTimeline(options));
7357 }
7358 }
7359
7360 function scroll(onScroll, { axis = "y", container = document.scrollingElement, ...options } = {}) {
7361 if (!container)
7362 return noop;
7363 const optionsWithDefaults = { axis, container, ...options };
7364 return typeof onScroll === "function"
7365 ? attachToFunction(onScroll, optionsWithDefaults)
7366 : attachToAnimation(onScroll, optionsWithDefaults);
7367 }
7368
7369 const thresholds = {
7370 some: 0,
7371 all: 1,
7372 };
7373 function inView(elementOrSelector, onStart, { root, margin: rootMargin, amount = "some" } = {}) {
7374 const elements = resolveElements(elementOrSelector);
7375 const activeIntersections = new WeakMap();
7376 const onIntersectionChange = (entries) => {
7377 entries.forEach((entry) => {
7378 const onEnd = activeIntersections.get(entry.target);
7379 /**
7380 * If there's no change to the intersection, we don't need to
7381 * do anything here.
7382 */
7383 if (entry.isIntersecting === Boolean(onEnd))
7384 return;
7385 if (entry.isIntersecting) {
7386 const newOnEnd = onStart(entry.target, entry);
7387 if (typeof newOnEnd === "function") {
7388 activeIntersections.set(entry.target, newOnEnd);
7389 }
7390 else {
7391 observer.unobserve(entry.target);
7392 }
7393 }
7394 else if (typeof onEnd === "function") {
7395 onEnd(entry);
7396 activeIntersections.delete(entry.target);
7397 }
7398 });
7399 };
7400 const observer = new IntersectionObserver(onIntersectionChange, {
7401 root,
7402 rootMargin,
7403 threshold: typeof amount === "number" ? amount : thresholds[amount],
7404 });
7405 elements.forEach((element) => observer.observe(element));
7406 return () => observer.disconnect();
7407 }
7408
7409 /**
7410 * Timeout defined in ms
7411 */
7412 function delay(callback, timeout) {
7413 const start = time.now();
7414 const checkElapsed = ({ timestamp }) => {
7415 const elapsed = timestamp - start;
7416 if (elapsed >= timeout) {
7417 cancelFrame(checkElapsed);
7418 callback(elapsed - timeout);
7419 }
7420 };
7421 frame.setup(checkElapsed, true);
7422 return () => cancelFrame(checkElapsed);
7423 }
7424 function delayInSeconds(callback, timeout) {
7425 return delay(callback, secondsToMilliseconds(timeout));
7426 }
7427
7428 const distance = (a, b) => Math.abs(a - b);
7429 function distance2D(a, b) {
7430 // Multi-dimensional
7431 const xDelta = distance(a.x, b.x);
7432 const yDelta = distance(a.y, b.y);
7433 return Math.sqrt(xDelta ** 2 + yDelta ** 2);
7434 }
7435
7436 exports.AsyncMotionValueAnimation = AsyncMotionValueAnimation;
7437 exports.DOMKeyframesResolver = DOMKeyframesResolver;
7438 exports.GroupAnimation = GroupAnimation;
7439 exports.GroupAnimationWithThen = GroupAnimationWithThen;
7440 exports.JSAnimation = JSAnimation;
7441 exports.KeyframeResolver = KeyframeResolver;
7442 exports.MotionGlobalConfig = MotionGlobalConfig;
7443 exports.MotionValue = MotionValue;
7444 exports.NativeAnimation = NativeAnimation;
7445 exports.NativeAnimationExtended = NativeAnimationExtended;
7446 exports.NativeAnimationWrapper = NativeAnimationWrapper;
7447 exports.SubscriptionManager = SubscriptionManager;
7448 exports.ViewTransitionBuilder = ViewTransitionBuilder;
7449 exports.acceleratedValues = acceleratedValues;
7450 exports.activeAnimations = activeAnimations;
7451 exports.addAttrValue = addAttrValue;
7452 exports.addStyleValue = addStyleValue;
7453 exports.addUniqueItem = addUniqueItem;
7454 exports.alpha = alpha;
7455 exports.analyseComplexValue = analyseComplexValue;
7456 exports.animate = animate;
7457 exports.animateMini = animateMini;
7458 exports.animateValue = animateValue;
7459 exports.animateView = animateView;
7460 exports.animationMapKey = animationMapKey;
7461 exports.anticipate = anticipate;
7462 exports.applyGeneratorOptions = applyGeneratorOptions;
7463 exports.applyPxDefaults = applyPxDefaults;
7464 exports.attachSpring = attachSpring;
7465 exports.attrEffect = attrEffect;
7466 exports.backIn = backIn;
7467 exports.backInOut = backInOut;
7468 exports.backOut = backOut;
7469 exports.calcGeneratorDuration = calcGeneratorDuration;
7470 exports.cancelFrame = cancelFrame;
7471 exports.cancelMicrotask = cancelMicrotask;
7472 exports.cancelSync = cancelSync;
7473 exports.circIn = circIn;
7474 exports.circInOut = circInOut;
7475 exports.circOut = circOut;
7476 exports.clamp = clamp;
7477 exports.collectMotionValues = collectMotionValues;
7478 exports.color = color;
7479 exports.complex = complex;
7480 exports.convertOffsetToTimes = convertOffsetToTimes;
7481 exports.createGeneratorEasing = createGeneratorEasing;
7482 exports.createRenderBatcher = createRenderBatcher;
7483 exports.createScopedAnimate = createScopedAnimate;
7484 exports.cubicBezier = cubicBezier;
7485 exports.cubicBezierAsString = cubicBezierAsString;
7486 exports.defaultEasing = defaultEasing;
7487 exports.defaultOffset = defaultOffset$1;
7488 exports.defaultTransformValue = defaultTransformValue;
7489 exports.defaultValueTypes = defaultValueTypes;
7490 exports.degrees = degrees;
7491 exports.delay = delayInSeconds;
7492 exports.dimensionValueTypes = dimensionValueTypes;
7493 exports.distance = distance;
7494 exports.distance2D = distance2D;
7495 exports.easeIn = easeIn;
7496 exports.easeInOut = easeInOut;
7497 exports.easeOut = easeOut;
7498 exports.easingDefinitionToFunction = easingDefinitionToFunction;
7499 exports.fillOffset = fillOffset;
7500 exports.fillWildcards = fillWildcards;
7501 exports.findDimensionValueType = findDimensionValueType;
7502 exports.findValueType = findValueType;
7503 exports.flushKeyframeResolvers = flushKeyframeResolvers;
7504 exports.frame = frame;
7505 exports.frameData = frameData;
7506 exports.frameSteps = frameSteps;
7507 exports.generateLinearEasing = generateLinearEasing;
7508 exports.getAnimatableNone = getAnimatableNone;
7509 exports.getAnimationMap = getAnimationMap;
7510 exports.getComputedStyle = getComputedStyle$2;
7511 exports.getDefaultValueType = getDefaultValueType;
7512 exports.getEasingForSegment = getEasingForSegment;
7513 exports.getMixer = getMixer;
7514 exports.getOriginIndex = getOriginIndex;
7515 exports.getValueAsType = getValueAsType;
7516 exports.getValueTransition = getValueTransition$1;
7517 exports.getVariableValue = getVariableValue;
7518 exports.getViewAnimationLayerInfo = getViewAnimationLayerInfo;
7519 exports.getViewAnimations = getViewAnimations;
7520 exports.hasWarned = hasWarned;
7521 exports.hex = hex;
7522 exports.hover = hover;
7523 exports.hsla = hsla;
7524 exports.hslaToRgba = hslaToRgba;
7525 exports.inView = inView;
7526 exports.inertia = inertia;
7527 exports.interpolate = interpolate;
7528 exports.invisibleValues = invisibleValues;
7529 exports.isBezierDefinition = isBezierDefinition;
7530 exports.isCSSVariableName = isCSSVariableName;
7531 exports.isCSSVariableToken = isCSSVariableToken;
7532 exports.isDragActive = isDragActive;
7533 exports.isDragging = isDragging;
7534 exports.isEasingArray = isEasingArray;
7535 exports.isGenerator = isGenerator;
7536 exports.isHTMLElement = isHTMLElement;
7537 exports.isMotionValue = isMotionValue;
7538 exports.isNodeOrChild = isNodeOrChild;
7539 exports.isNumericalString = isNumericalString;
7540 exports.isObject = isObject;
7541 exports.isPrimaryPointer = isPrimaryPointer;
7542 exports.isSVGElement = isSVGElement;
7543 exports.isSVGSVGElement = isSVGSVGElement;
7544 exports.isWaapiSupportedEasing = isWaapiSupportedEasing;
7545 exports.isZeroValueString = isZeroValueString;
7546 exports.keyframes = keyframes;
7547 exports.makeAnimationInstant = makeAnimationInstant;
7548 exports.mapEasingToNativeEasing = mapEasingToNativeEasing;
7549 exports.mapValue = mapValue;
7550 exports.maxGeneratorDuration = maxGeneratorDuration;
7551 exports.memo = memo;
7552 exports.microtask = microtask;
7553 exports.millisecondsToSeconds = millisecondsToSeconds;
7554 exports.mirrorEasing = mirrorEasing;
7555 exports.mix = mix;
7556 exports.mixArray = mixArray;
7557 exports.mixColor = mixColor;
7558 exports.mixComplex = mixComplex;
7559 exports.mixImmediate = mixImmediate;
7560 exports.mixLinearColor = mixLinearColor;
7561 exports.mixNumber = mixNumber$1;
7562 exports.mixObject = mixObject;
7563 exports.mixVisibility = mixVisibility;
7564 exports.motionValue = motionValue;
7565 exports.moveItem = moveItem;
7566 exports.noop = noop;
7567 exports.number = number;
7568 exports.numberValueTypes = numberValueTypes;
7569 exports.observeTimeline = observeTimeline;
7570 exports.parseCSSVariable = parseCSSVariable;
7571 exports.parseValueFromTransform = parseValueFromTransform;
7572 exports.percent = percent;
7573 exports.pipe = pipe;
7574 exports.positionalKeys = positionalKeys;
7575 exports.press = press;
7576 exports.progress = progress;
7577 exports.progressPercentage = progressPercentage;
7578 exports.propEffect = propEffect;
7579 exports.px = px;
7580 exports.readTransformValue = readTransformValue;
7581 exports.recordStats = recordStats;
7582 exports.removeItem = removeItem;
7583 exports.resize = resize;
7584 exports.resolveElements = resolveElements;
7585 exports.reverseEasing = reverseEasing;
7586 exports.rgbUnit = rgbUnit;
7587 exports.rgba = rgba;
7588 exports.scale = scale;
7589 exports.scroll = scroll;
7590 exports.scrollInfo = scrollInfo;
7591 exports.secondsToMilliseconds = secondsToMilliseconds;
7592 exports.setDragLock = setDragLock;
7593 exports.setStyle = setStyle;
7594 exports.spring = spring;
7595 exports.springValue = springValue;
7596 exports.stagger = stagger;
7597 exports.startWaapiAnimation = startWaapiAnimation;
7598 exports.statsBuffer = statsBuffer;
7599 exports.steps = steps;
7600 exports.styleEffect = styleEffect;
7601 exports.supportedWaapiEasing = supportedWaapiEasing;
7602 exports.supportsBrowserAnimation = supportsBrowserAnimation;
7603 exports.supportsFlags = supportsFlags;
7604 exports.supportsLinearEasing = supportsLinearEasing;
7605 exports.supportsPartialKeyframes = supportsPartialKeyframes;
7606 exports.supportsScrollTimeline = supportsScrollTimeline;
7607 exports.svgEffect = svgEffect;
7608 exports.sync = sync;
7609 exports.testValueType = testValueType;
7610 exports.time = time;
7611 exports.transform = transform;
7612 exports.transformPropOrder = transformPropOrder;
7613 exports.transformProps = transformProps;
7614 exports.transformValue = transformValue;
7615 exports.transformValueTypes = transformValueTypes;
7616 exports.velocityPerSecond = velocityPerSecond;
7617 exports.vh = vh;
7618 exports.vw = vw;
7619 exports.warnOnce = warnOnce;
7620 exports.wrap = wrap;
7621
7622 }));
7623