UNPKG

36.7 kBJavaScriptView Raw
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 const data = new WeakMap();
8 function getAnimationData(element) {
9 if (!data.has(element)) {
10 data.set(element, {
11 activeTransforms: [],
12 activeAnimations: {},
13 });
14 }
15 return data.get(element);
16 }
17
18 function addUniqueItem(array, item) {
19 array.indexOf(item) === -1 && array.push(item);
20 }
21 function removeItem(arr, item) {
22 const index = arr.indexOf(item);
23 index > -1 && arr.splice(index, 1);
24 }
25
26 const noop = () => { };
27 const noopReturn = (v) => v;
28
29 /**
30 * A list of all transformable axes. We'll use this list to generated a version
31 * of each axes for each transform.
32 */
33 const axes = ["", "X", "Y", "Z"];
34 /**
35 * An ordered array of each transformable value. By default, transform values
36 * will be sorted to this order.
37 */
38 const order = ["translate", "scale", "rotate", "skew"];
39 const transformAlias = {
40 x: "translateX",
41 y: "translateY",
42 z: "translateZ",
43 };
44 const rotation = {
45 syntax: "<angle>",
46 initialValue: "0deg",
47 toDefaultUnit: (v) => v + "deg",
48 };
49 const baseTransformProperties = {
50 translate: {
51 syntax: "<length-percentage>",
52 initialValue: "0px",
53 toDefaultUnit: (v) => v + "px",
54 },
55 rotate: rotation,
56 scale: {
57 syntax: "<number>",
58 initialValue: 1,
59 toDefaultUnit: noopReturn,
60 },
61 skew: rotation,
62 };
63 const transformPropertyDefinitions = new Map();
64 const asTransformCssVar = (name) => `--motion-${name}`;
65 /**
66 * Generate a list of every possible transform key
67 */
68 const transforms = ["x", "y", "z"];
69 order.forEach((name) => {
70 axes.forEach((axis) => {
71 transforms.push(name + axis);
72 transformPropertyDefinitions.set(asTransformCssVar(name + axis), baseTransformProperties[name]);
73 });
74 });
75 /**
76 * A function to use with Array.sort to sort transform keys by their default order.
77 */
78 const compareTransformOrder = (a, b) => transforms.indexOf(a) - transforms.indexOf(b);
79 /**
80 * Provide a quick way to check if a string is the name of a transform
81 */
82 const transformLookup = new Set(transforms);
83 const isTransform = (name) => transformLookup.has(name);
84 const addTransformToElement = (element, name) => {
85 const { activeTransforms } = getAnimationData(element);
86 addUniqueItem(activeTransforms, name);
87 element.style.transform = buildTransformTemplate(activeTransforms);
88 };
89 const buildTransformTemplate = (activeTransforms) => activeTransforms
90 .sort(compareTransformOrder)
91 .reduce(transformListToString, "")
92 .trim();
93 const transformListToString = (template, name) => `${template} ${name}(var(${asTransformCssVar(name)}))`;
94
95 const isCssVar = (name) => name.startsWith("--");
96 const registeredProperties = new Set();
97 function registerCssVariable(name) {
98 if (registeredProperties.has(name))
99 return;
100 registeredProperties.add(name);
101 try {
102 const { syntax, initialValue } = transformPropertyDefinitions.has(name)
103 ? transformPropertyDefinitions.get(name)
104 : {};
105 CSS.registerProperty({
106 name,
107 inherits: false,
108 syntax,
109 initialValue,
110 });
111 }
112 catch (e) { }
113 }
114
115 const ms = (seconds) => seconds * 1000;
116
117 function stopAnimation(animation) {
118 // Suppress error thrown by WAAPI
119 try {
120 animation.commitStyles();
121 animation.cancel();
122 }
123 catch (e) { }
124 }
125
126 const isCubicBezier = (easing) => Array.isArray(easing) && typeof easing[0] === "number";
127 const isEasingList = (easing) => Array.isArray(easing) && typeof easing[0] !== "number";
128 const convertEasing = (easing) => isCubicBezier(easing) ? cubicBezierAsString(easing) : easing;
129 const cubicBezierAsString = ([a, b, c, d]) => `cubic-bezier(${a}, ${b}, ${c}, ${d})`;
130
131 const testAnimation = (keyframes) => document.createElement("div").animate(keyframes, { duration: 0.001 });
132 const featureTests = {
133 cssRegisterProperty: () => typeof CSS !== "undefined" &&
134 Object.hasOwnProperty.call(CSS, "registerProperty"),
135 waapi: () => Object.hasOwnProperty.call(Element.prototype, "animate"),
136 partialKeyframes: () => {
137 try {
138 testAnimation({ opacity: [1] });
139 }
140 catch (e) {
141 return false;
142 }
143 return true;
144 },
145 finished: () => Boolean(testAnimation({ opacity: [0, 1] }).finished),
146 };
147 const results = {};
148 const supports = Object.keys(featureTests).reduce((acc, key) => {
149 acc[key] = () => {
150 if (results[key] === undefined)
151 results[key] = featureTests[key]();
152 return results[key];
153 };
154 return acc;
155 }, {});
156
157 const createCssVariableRenderer = (element, name) => {
158 return (latest) => element.style.setProperty(name, latest);
159 };
160 const createStyleRenderer = (element, name) => {
161 return (latest) => (element.style[name] = latest);
162 };
163
164 const defaults = {
165 duration: 0.3,
166 delay: 0,
167 endDelay: 0,
168 repeat: 0,
169 easing: "ease",
170 };
171
172 /*
173 Bezier function generator
174
175 This has been modified from Gaëtan Renaudeau's BezierEasing
176 https://github.com/gre/bezier-easing/blob/master/src/index.js
177 https://github.com/gre/bezier-easing/blob/master/LICENSE
178
179 I've removed the newtonRaphsonIterate algo because in benchmarking it
180 wasn't noticiably faster than binarySubdivision, indeed removing it
181 usually improved times, depending on the curve.
182
183 I also removed the lookup table, as for the added bundle size and loop we're
184 only cutting ~4 or so subdivision iterations. I bumped the max iterations up
185 to 12 to compensate and this still tended to be faster for no perceivable
186 loss in accuracy.
187
188 Usage
189 const easeOut = cubicBezier(.17,.67,.83,.67);
190 const x = easeOut(0.5); // returns 0.627...
191 */
192 // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
193 const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) * t;
194 const subdivisionPrecision = 0.0000001;
195 const subdivisionMaxIterations = 12;
196 function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) {
197 let currentX;
198 let currentT;
199 let i = 0;
200 do {
201 currentT = lowerBound + (upperBound - lowerBound) / 2.0;
202 currentX = calcBezier(currentT, mX1, mX2) - x;
203 if (currentX > 0.0) {
204 upperBound = currentT;
205 }
206 else {
207 lowerBound = currentT;
208 }
209 } while (Math.abs(currentX) > subdivisionPrecision &&
210 ++i < subdivisionMaxIterations);
211 return currentT;
212 }
213 function cubicBezier(mX1, mY1, mX2, mY2) {
214 // If this is a linear gradient, return linear easing
215 if (mX1 === mY1 && mX2 === mY2)
216 return noopReturn;
217 const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2);
218 // If animation is at start/end, return t without easing
219 return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2);
220 }
221
222 /*! *****************************************************************************
223 Copyright (c) Microsoft Corporation.
224
225 Permission to use, copy, modify, and/or distribute this software for any
226 purpose with or without fee is hereby granted.
227
228 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
229 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
230 AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
231 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
232 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
233 OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
234 PERFORMANCE OF THIS SOFTWARE.
235 ***************************************************************************** */
236
237 function __rest(s, e) {
238 var t = {};
239 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
240 t[p] = s[p];
241 if (s != null && typeof Object.getOwnPropertySymbols === "function")
242 for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
243 if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
244 t[p[i]] = s[p[i]];
245 }
246 return t;
247 }
248
249 var clamp = function (min, max, v) {
250 return Math.min(Math.max(v, min), max);
251 };
252
253 var progress = function (from, to, value) {
254 var toFromDifference = to - from;
255 return toFromDifference === 0 ? 1 : (value - from) / toFromDifference;
256 };
257
258 var mix = function (from, to, progress) {
259 return -progress * from + progress * to + from;
260 };
261
262 var wrap = function (min, max, v) {
263 var rangeSize = max - min;
264 return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
265 };
266
267 var steps = function (steps, direction) {
268 if (direction === void 0) { direction = 'end'; }
269 return function (progress) {
270 progress =
271 direction === 'end' ? Math.min(progress, 0.999) : Math.max(progress, 0.001);
272 var expanded = progress * steps;
273 var rounded = direction === 'end' ? Math.floor(expanded) : Math.ceil(expanded);
274 return clamp(0, 1, rounded / steps);
275 };
276 };
277
278 const namedEasings = {
279 ease: cubicBezier(0.25, 0.1, 0.25, 1.0),
280 "ease-in": cubicBezier(0.42, 0.0, 1.0, 1.0),
281 "ease-in-out": cubicBezier(0.42, 0.0, 0.58, 1.0),
282 "ease-out": cubicBezier(0.0, 0.0, 0.58, 1.0),
283 };
284 const functionArgsRegex = /\((.*?)\)/;
285 function getEasingFunction(definition) {
286 // If already an easing function, return
287 if (typeof definition === "function")
288 return definition;
289 // If an easing curve definition, return bezier function
290 if (Array.isArray(definition))
291 return cubicBezier(...definition);
292 // If we have a predefined easing function, return
293 if (namedEasings[definition])
294 return namedEasings[definition];
295 // If this is a steps function, attempt to create easing curve
296 if (definition.startsWith("steps")) {
297 const args = functionArgsRegex.exec(definition);
298 if (args) {
299 const argsArray = args[1].split(",");
300 return steps(parseFloat(argsArray[0]), argsArray[1].trim());
301 }
302 }
303 return noopReturn;
304 }
305
306 function getEasingForSegment(easing, i) {
307 return isEasingList(easing)
308 ? easing[wrap(0, easing.length, i)]
309 : easing;
310 }
311
312 function fillOffset(offset, remaining) {
313 const min = offset[offset.length - 1];
314 for (let i = 1; i <= remaining; i++) {
315 const offsetProgress = progress(0, remaining, i);
316 offset.push(mix(min, 1, offsetProgress));
317 }
318 }
319 function defaultOffset(length) {
320 const offset = [0];
321 fillOffset(offset, length - 1);
322 return offset;
323 }
324
325 const clampProgress = (p) => Math.min(1, Math.max(p, 0));
326 function slowInterpolateNumbers(output, input = defaultOffset(output.length), easing = noopReturn) {
327 const length = output.length;
328 /**
329 * If the input length is lower than the output we
330 * fill the input to match. This currently assumes the input
331 * is an animation progress value so is a good candidate for
332 * moving outside the function.
333 */
334 const remainder = length - input.length;
335 remainder > 0 && fillOffset(input, remainder);
336 return (t) => {
337 let i = 0;
338 for (; i < length - 2; i++) {
339 if (t < input[i + 1])
340 break;
341 }
342 let progressInRange = clampProgress(progress(input[i], input[i + 1], t));
343 const segmentEasing = getEasingForSegment(easing, i);
344 progressInRange = segmentEasing(progressInRange);
345 return mix(output[i], output[i + 1], progressInRange);
346 };
347 }
348
349 class Animation {
350 constructor(output, keyframes,
351 // TODO Merge in defaults
352 { easing = defaults.easing, duration = defaults.duration, delay = defaults.delay, endDelay = defaults.endDelay, offset, repeat = defaults.repeat, direction = "normal", }) {
353 this.startTime = 0;
354 this.rate = 1;
355 this.t = 0;
356 this.cancelT = 0;
357 this.playState = "idle";
358 this.finished = new Promise((resolve, reject) => {
359 this.resolve = resolve;
360 this.reject = reject;
361 });
362 const totalDuration = duration * (repeat + 1);
363 const interpolate = slowInterpolateNumbers(keyframes, offset, isEasingList(easing)
364 ? easing.map(getEasingFunction)
365 : getEasingFunction(easing));
366 this.tick = (timestamp) => {
367 if (this.playState === "finished") {
368 const latest = interpolate(1);
369 output(latest);
370 this.resolve(latest);
371 return;
372 }
373 if (this.pauseTime) {
374 timestamp = this.pauseTime;
375 }
376 let t = (timestamp - this.startTime) * this.rate;
377 this.t = t;
378 // Convert to seconds
379 t /= 1000;
380 // Rebase on delay
381 t = Math.max(t - delay, 0);
382 const progress = t / duration;
383 // TODO progress += iterationStart
384 let currentIteration = Math.floor(progress);
385 let iterationProgress = progress % 1.0;
386 if (!iterationProgress && progress >= 1) {
387 iterationProgress = 1;
388 }
389 if (iterationProgress === 1) {
390 currentIteration--;
391 }
392 // Reverse progress
393 const iterationIsOdd = currentIteration % 2;
394 if (direction === "reverse" ||
395 (direction === "alternate" && iterationIsOdd) ||
396 (direction === "alternate-reverse" && !iterationIsOdd)) {
397 iterationProgress = 1 - iterationProgress;
398 }
399 const interpolationIsFinished = t >= totalDuration;
400 const interpolationProgress = interpolationIsFinished
401 ? 1
402 : Math.min(iterationProgress, 1);
403 const latest = interpolate(interpolationProgress);
404 output(latest);
405 const isFinished = t >= totalDuration + endDelay;
406 if (isFinished) {
407 this.playState = "finished";
408 this.resolve(latest);
409 }
410 else if (this.playState !== "idle") {
411 requestAnimationFrame(this.tick);
412 }
413 };
414 this.play();
415 }
416 play() {
417 const now = performance.now();
418 this.playState = "running";
419 if (this.pauseTime) {
420 this.startTime = now - (this.pauseTime - this.startTime);
421 }
422 else if (!this.startTime) {
423 this.startTime = now;
424 }
425 this.pauseTime = undefined;
426 requestAnimationFrame(this.tick);
427 }
428 pause() {
429 this.playState = "paused";
430 this.pauseTime = performance.now();
431 }
432 finish() {
433 this.playState = "finished";
434 this.tick(0);
435 }
436 cancel() {
437 this.playState = "idle";
438 this.tick(this.cancelT);
439 this.reject(false);
440 }
441 reverse() {
442 this.rate *= -1;
443 }
444 commitStyles() {
445 this.cancelT = this.t;
446 }
447 get currentTime() {
448 return this.t;
449 }
450 set currentTime(t) {
451 if (this.pauseTime || this.rate === 0) {
452 this.pauseTime = t;
453 }
454 else {
455 this.startTime = performance.now() - t / this.rate;
456 }
457 }
458 get playbackRate() {
459 return this.rate;
460 }
461 set playbackRate(rate) {
462 this.rate = rate;
463 }
464 }
465 function animateNumber(output, keyframes = [0, 1], options = {}) {
466 return new Animation(output, keyframes, options);
467 }
468
469 const style = {
470 get: (element, name) => {
471 let value = isCssVar(name)
472 ? element.style.getPropertyValue(name)
473 : getComputedStyle(element)[name];
474 if (!value && value !== 0) {
475 const definition = transformPropertyDefinitions.get(name);
476 if (definition)
477 value = definition.initialValue;
478 }
479 return value;
480 },
481 };
482
483 function hydrateKeyframes(keyframes, element, name) {
484 for (let i = 0; i < keyframes.length; i++) {
485 if (keyframes[i] === null) {
486 keyframes[i] = i ? keyframes[i - 1] : style.get(element, name);
487 }
488 }
489 return keyframes;
490 }
491 const keyframesList = (keyframes) => Array.isArray(keyframes) ? keyframes : [keyframes];
492
493 function animateStyle(element, name, keyframesDefinition, options = {}) {
494 let { duration = defaults.duration, delay = defaults.delay, endDelay = defaults.endDelay, repeat = defaults.repeat, easing = defaults.easing, direction, offset, allowWebkitAcceleration = false, } = options;
495 const data = getAnimationData(element);
496 let canAnimateNatively = supports.waapi();
497 let render = noop;
498 const valueIsTransform = isTransform(name);
499 /**
500 * If this is an individual transform, we need to map its
501 * key to a CSS variable and update the element's transform style
502 */
503 if (valueIsTransform) {
504 if (transformAlias[name])
505 name = transformAlias[name];
506 addTransformToElement(element, name);
507 name = asTransformCssVar(name);
508 }
509 /**
510 * Get definition of value, this will be used to convert numerical
511 * keyframes into the default value type.
512 */
513 const definition = transformPropertyDefinitions.get(name);
514 /**
515 * Replace null values with the previous keyframe value, or read
516 * it from the DOM if it's the first keyframe.
517 *
518 * TODO: This needs to come after the valueIsTransform
519 * check so it can correctly read the underlying value.
520 * Should make a test for this.
521 */
522 let keyframes = hydrateKeyframes(keyframesList(keyframesDefinition), element, name);
523 stopCurrentAnimation(data, name);
524 /**
525 * If this is a CSS variable we need to register it with the browser
526 * before it can be animated natively. We also set it with setProperty
527 * rather than directly onto the element.style object.
528 */
529 if (isCssVar(name)) {
530 render = createCssVariableRenderer(element, name);
531 if (supports.cssRegisterProperty()) {
532 registerCssVariable(name);
533 }
534 else {
535 canAnimateNatively = false;
536 }
537 }
538 else {
539 render = createStyleRenderer(element, name);
540 }
541 let animation;
542 /**
543 * If we can animate this value with WAAPI, do so. Currently this only
544 * feature detects CSS.registerProperty but could check WAAPI too.
545 */
546 if (canAnimateNatively) {
547 /**
548 * Convert numbers to default value types. Currently this only supports
549 * transforms but it could also support other value types.
550 */
551 if (definition) {
552 keyframes = keyframes.map((value) => typeof value === "number" ? definition.toDefaultUnit(value) : value);
553 }
554 if (!supports.partialKeyframes() && keyframes.length === 1) {
555 keyframes.unshift(style.get(element, name));
556 }
557 const animationOptions = {
558 delay: ms(delay),
559 duration: ms(duration),
560 endDelay: ms(endDelay),
561 easing: !isEasingList(easing) ? convertEasing(easing) : undefined,
562 direction,
563 iterations: repeat + 1,
564 };
565 animation = element.animate({
566 [name]: keyframes,
567 offset,
568 easing: isEasingList(easing) ? easing.map(convertEasing) : undefined,
569 }, animationOptions);
570 /**
571 * Polyfill finished Promise in browsers that don't support it
572 */
573 if (!animation.finished) {
574 animation.finished = new Promise((resolve, reject) => {
575 animation.onfinish = resolve;
576 animation.oncancel = reject;
577 });
578 }
579 const target = keyframes[keyframes.length - 1];
580 animation.finished.then(() => render(target)).catch(noop);
581 /**
582 * This forces Webkit to run animations on the main thread by exploiting
583 * this condition:
584 * https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/platform/graphics/ca/GraphicsLayerCA.cpp?rev=281238#L1099
585 *
586 * This fixes Webkit's timing bugs, like accelerated animations falling
587 * out of sync with main thread animations and massive delays in starting
588 * accelerated animations in WKWebView.
589 */
590 if (!allowWebkitAcceleration)
591 animation.playbackRate = 1.000001;
592 }
593 else if (valueIsTransform && keyframes.every(isNumber)) {
594 if (keyframes.length === 1) {
595 keyframes.unshift(style.get(element, name) || (definition === null || definition === void 0 ? void 0 : definition.initialValue) || 0);
596 }
597 /**
598 * Transform styles are currently only accepted as numbers of
599 * their default value type, so here we loop through and map
600 * them to numbers.
601 */
602 keyframes = keyframes.map((value) => typeof value === "string" ? parseFloat(value) : value);
603 if (definition) {
604 const applyStyle = render;
605 render = (v) => applyStyle(definition.toDefaultUnit(v));
606 }
607 animation = animateNumber(render, keyframes, options);
608 }
609 else {
610 const target = keyframes[keyframes.length - 1];
611 render(definition && typeof target === "number"
612 ? definition.toDefaultUnit(target)
613 : target);
614 }
615 data.activeAnimations[name] = animation;
616 return animation;
617 }
618 function stopCurrentAnimation(data, name) {
619 if (data.activeAnimations[name]) {
620 stopAnimation(data.activeAnimations[name]);
621 data.activeAnimations[name] = undefined;
622 }
623 }
624 const isNumber = (value) => typeof value === "number";
625
626 const getOptions = (options, key) => options[key] ? Object.assign(Object.assign({}, options), options[key]) : Object.assign({}, options);
627
628 function resolveElements(elements, selectorCache) {
629 var _a;
630 if (typeof elements === "string") {
631 if (selectorCache) {
632 (_a = selectorCache[elements]) !== null && _a !== void 0 ? _a : (selectorCache[elements] = document.querySelectorAll(elements));
633 elements = selectorCache[elements];
634 }
635 else {
636 elements = document.querySelectorAll(elements);
637 }
638 }
639 else if (elements instanceof Element) {
640 elements = [elements];
641 }
642 return Array.from(elements);
643 }
644
645 function createAnimationControls(animations) {
646 // TODO Duplication with animate
647 const state = {
648 animations,
649 finished: Promise.all(animations.map((animation) => animation.finished)),
650 };
651 return new Proxy(state, controls);
652 }
653 const controls = {
654 get: (target, key) => {
655 var _a, _b;
656 switch (key) {
657 case "finished":
658 return target.finished;
659 case "currentTime":
660 // TODO Find first active animation
661 const duration = ((_a = target.animations[0]) === null || _a === void 0 ? void 0 : _a[key]) || 0;
662 return duration ? duration / 1000 : 0;
663 case "playbackRate":
664 // TODO Find first active animation
665 return (_b = target.animations[0]) === null || _b === void 0 ? void 0 : _b[key];
666 case "stop":
667 return () => target.animations.forEach(stopAnimation);
668 default:
669 return () => target.animations.forEach((animation) => animation[key]());
670 }
671 },
672 set: (target, key, value) => {
673 switch (key) {
674 case "currentTime":
675 value = ms(value);
676 case "currentTime":
677 case "playbackRate":
678 for (let i = 0; i < target.animations.length; i++) {
679 target.animations[i][key] = value;
680 }
681 return true;
682 }
683 return false;
684 },
685 };
686
687 function stagger(duration = 0.1, { start = 0, from = 0, easing } = {}) {
688 return (i, total) => {
689 const fromIndex = typeof from === "number" ? from : getFromIndex(from, total);
690 const distance = Math.abs(fromIndex - i);
691 let delay = duration * distance;
692 if (easing) {
693 const maxDelay = total * i;
694 const easingFunction = getEasingFunction(easing);
695 delay = easingFunction(delay / maxDelay) * maxDelay;
696 }
697 return start + delay;
698 };
699 }
700 function getFromIndex(from, total) {
701 if (from === "first") {
702 return 0;
703 }
704 else {
705 const lastIndex = total - 1;
706 return from === "last" ? lastIndex : lastIndex / 2;
707 }
708 }
709 function resolveOption(option, i, total) {
710 return typeof option === "function"
711 ? option(i, total)
712 : option;
713 }
714
715 function animate(elements, keyframes, options = {}) {
716 elements = resolveElements(elements);
717 const animations = [];
718 const numElements = elements.length;
719 for (let i = 0; i < numElements; i++) {
720 const element = elements[i];
721 for (const key in keyframes) {
722 const valueOptions = getOptions(options, key);
723 valueOptions.delay = resolveOption(valueOptions.delay, i, numElements);
724 const animation = animateStyle(element, key, keyframes[key], valueOptions);
725 animation && animations.push(animation);
726 }
727 }
728 return createAnimationControls(animations);
729 }
730
731 function calcNextTime(current, next, prev, labels) {
732 var _a;
733 if (typeof next === "number") {
734 return next;
735 }
736 else if (next.startsWith("-") || next.startsWith("+")) {
737 return Math.max(0, current + parseFloat(next));
738 }
739 else if (next === "<") {
740 return prev;
741 }
742 else {
743 return (_a = labels.get(next)) !== null && _a !== void 0 ? _a : current;
744 }
745 }
746
747 function eraseKeyframes(sequence, startTime, endTime) {
748 for (let i = 0; i < sequence.length; i++) {
749 const keyframe = sequence[i];
750 if (keyframe.at > startTime && keyframe.at < endTime) {
751 removeItem(sequence, keyframe);
752 // If we remove this item we have to push the pointer back one
753 i--;
754 }
755 }
756 }
757 function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
758 /**
759 * Erase every existing value between currentTime and targetTime,
760 * this will essentially splice this timeline into any currently
761 * defined ones.
762 */
763 eraseKeyframes(sequence, startTime, endTime);
764 for (let i = 0; i < keyframes.length; i++) {
765 sequence.push({
766 value: keyframes[i],
767 at: mix(startTime, endTime, offset[i]),
768 easing: getEasingForSegment(easing, i),
769 });
770 }
771 }
772
773 function compareByTime(a, b) {
774 if (a.at === b.at) {
775 return a.value === null ? 1 : -1;
776 }
777 else {
778 return a.at - b.at;
779 }
780 }
781
782 function timeline(definition, options = {}) {
783 const animations = [];
784 const animationDefinitions = createAnimationsFromTimeline(definition, options);
785 for (let i = 0; i < animationDefinitions.length; i++) {
786 const animation = animateStyle(...animationDefinitions[i]);
787 animation && animations.push(animation);
788 }
789 return createAnimationControls(animations);
790 }
791 function createAnimationsFromTimeline(definition, _a = {}) {
792 var { defaultOptions = {} } = _a, timelineOptions = __rest(_a, ["defaultOptions"]);
793 const animationDefinitions = [];
794 const elementSequences = new Map();
795 const elementCache = {};
796 const timeLabels = new Map();
797 let prevTime = 0;
798 let currentTime = 0;
799 let totalDuration = 0;
800 /**
801 * Build the timeline by mapping over the definition array and converting
802 * the definitions into keyframes and offsets with absolute time values.
803 * These will later get converted into relative offsets in a second pass.
804 */
805 for (let i = 0; i < definition.length; i++) {
806 const [elementDefinition, keyframes, options = {}] = definition[i];
807 /**
808 * If a relative or absolute time value has been specified we need to resolve
809 * it in relation to the currentTime.
810 */
811 if (options.at !== undefined) {
812 currentTime = calcNextTime(currentTime, options.at, prevTime, timeLabels);
813 }
814 /**
815 * Keep track of the maximum duration in this definition. This will be
816 * applied to currentTime once the definition has been parsed.
817 */
818 let maxDuration = 0;
819 /**
820 * Find all the elements specified in the definition and parse value
821 * keyframes from their timeline definitions.
822 */
823 const elements = resolveElements(elementDefinition, elementCache);
824 const numElements = elements.length;
825 for (let elementIndex = 0; elementIndex < numElements; elementIndex++) {
826 const element = elements[elementIndex];
827 const elementSequence = getElementSequence(element, elementSequences);
828 for (const key in keyframes) {
829 const valueSequence = getValueSequence(key, elementSequence);
830 const valueKeyframes = keyframesList(keyframes[key]);
831 const valueOptions = getOptions(options, key);
832 const { duration = defaultOptions.duration || defaults.duration, easing = defaultOptions.easing || defaults.easing, offset = defaultOffset(valueKeyframes.length), } = valueOptions;
833 const delay = resolveOption(options.delay, elementIndex, numElements) || 0;
834 const startTime = currentTime + delay;
835 const targetTime = startTime + duration;
836 if (offset.length === 1 && offset[0] === 0) {
837 offset[1] = 1;
838 }
839 /**
840 * Fill out if offset if fewer offsets than keyframes
841 */
842 const remainder = length - valueKeyframes.length;
843 remainder > 0 && fillOffset(offset, remainder);
844 /**
845 * If only one value has been set, ie [1], push a null to the start of
846 * the keyframe array. This will let us mark a keyframe at this point
847 * that will later be hydrated with the previous value.
848 */
849 valueKeyframes.length === 1 && valueKeyframes.unshift(null);
850 /**
851 * Add keyframes, mapping offsets to absolute time.
852 */
853 addKeyframes(valueSequence, valueKeyframes, easing, offset, startTime, targetTime);
854 maxDuration = Math.max(delay + duration, maxDuration);
855 totalDuration = Math.max(targetTime, totalDuration);
856 }
857 }
858 prevTime = currentTime;
859 currentTime += maxDuration;
860 }
861 /**
862 * For every element and value combination create a new animation.
863 */
864 elementSequences.forEach((valueSequences, element) => {
865 for (const key in valueSequences) {
866 const valueSequence = valueSequences[key];
867 /**
868 * Arrange all the keyframes in ascending time order.
869 */
870 valueSequence.sort(compareByTime);
871 const keyframes = [];
872 const valueOffset = [];
873 const valueEasing = [];
874 /**
875 * For each keyframe, translate absolute times into
876 * relative offsets based on the total duration of the timeline.
877 */
878 for (let i = 0; i < valueSequence.length; i++) {
879 const { at, value, easing } = valueSequence[i];
880 keyframes.push(value);
881 valueOffset.push(progress(0, totalDuration, at));
882 valueEasing.push(easing || defaults.easing);
883 }
884 /**
885 * If the generated animation doesn't end on the final keyframe,
886 * provide one with a null wildcard value. This will ensure it
887 * stays static until the end of the animation.
888 */
889 if (valueOffset[valueOffset.length - 1] !== 1) {
890 valueOffset.push(1);
891 keyframes.push(null);
892 }
893 animationDefinitions.push([
894 element,
895 key,
896 keyframes,
897 Object.assign(Object.assign(Object.assign({}, defaultOptions), { duration: totalDuration, easing: valueEasing, offset: valueOffset }), timelineOptions),
898 ]);
899 }
900 });
901 return animationDefinitions;
902 }
903 function getElementSequence(element, sequences) {
904 !sequences.has(element) && sequences.set(element, {});
905 return sequences.get(element);
906 }
907 function getValueSequence(name, sequences) {
908 if (!sequences[name])
909 sequences[name] = [];
910 return sequences[name];
911 }
912
913 exports.animate = animate;
914 exports.animateStyle = animateStyle;
915 exports.stagger = stagger;
916 exports.timeline = timeline;
917
918 Object.defineProperty(exports, '__esModule', { value: true });
919
920})));