source: trip-planner-front/node_modules/@angular/animations/fesm2015/browser.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 184.5 KB
Line 
1/**
2 * @license Angular v12.2.9
3 * (c) 2010-2021 Google LLC. https://angular.io/
4 * License: MIT
5 */
6
7import { ɵAnimationGroupPlayer, NoopAnimationPlayer, AUTO_STYLE, ɵPRE_STYLE, sequence, style } from '@angular/animations';
8import { Injectable } from '@angular/core';
9
10/**
11 * @license
12 * Copyright Google LLC All Rights Reserved.
13 *
14 * Use of this source code is governed by an MIT-style license that can be
15 * found in the LICENSE file at https://angular.io/license
16 */
17function isBrowser() {
18 return (typeof window !== 'undefined' && typeof window.document !== 'undefined');
19}
20function isNode() {
21 // Checking only for `process` isn't enough to identify whether or not we're in a Node
22 // environment, because Webpack by default will polyfill the `process`. While we can discern
23 // that Webpack polyfilled it by looking at `process.browser`, it's very Webpack-specific and
24 // might not be future-proof. Instead we look at the stringified version of `process` which
25 // is `[object process]` in Node and `[object Object]` when polyfilled.
26 return typeof process !== 'undefined' && {}.toString.call(process) === '[object process]';
27}
28function optimizeGroupPlayer(players) {
29 switch (players.length) {
30 case 0:
31 return new NoopAnimationPlayer();
32 case 1:
33 return players[0];
34 default:
35 return new ɵAnimationGroupPlayer(players);
36 }
37}
38function normalizeKeyframes(driver, normalizer, element, keyframes, preStyles = {}, postStyles = {}) {
39 const errors = [];
40 const normalizedKeyframes = [];
41 let previousOffset = -1;
42 let previousKeyframe = null;
43 keyframes.forEach(kf => {
44 const offset = kf['offset'];
45 const isSameOffset = offset == previousOffset;
46 const normalizedKeyframe = (isSameOffset && previousKeyframe) || {};
47 Object.keys(kf).forEach(prop => {
48 let normalizedProp = prop;
49 let normalizedValue = kf[prop];
50 if (prop !== 'offset') {
51 normalizedProp = normalizer.normalizePropertyName(normalizedProp, errors);
52 switch (normalizedValue) {
53 case ɵPRE_STYLE:
54 normalizedValue = preStyles[prop];
55 break;
56 case AUTO_STYLE:
57 normalizedValue = postStyles[prop];
58 break;
59 default:
60 normalizedValue =
61 normalizer.normalizeStyleValue(prop, normalizedProp, normalizedValue, errors);
62 break;
63 }
64 }
65 normalizedKeyframe[normalizedProp] = normalizedValue;
66 });
67 if (!isSameOffset) {
68 normalizedKeyframes.push(normalizedKeyframe);
69 }
70 previousKeyframe = normalizedKeyframe;
71 previousOffset = offset;
72 });
73 if (errors.length) {
74 const LINE_START = '\n - ';
75 throw new Error(`Unable to animate due to the following errors:${LINE_START}${errors.join(LINE_START)}`);
76 }
77 return normalizedKeyframes;
78}
79function listenOnPlayer(player, eventName, event, callback) {
80 switch (eventName) {
81 case 'start':
82 player.onStart(() => callback(event && copyAnimationEvent(event, 'start', player)));
83 break;
84 case 'done':
85 player.onDone(() => callback(event && copyAnimationEvent(event, 'done', player)));
86 break;
87 case 'destroy':
88 player.onDestroy(() => callback(event && copyAnimationEvent(event, 'destroy', player)));
89 break;
90 }
91}
92function copyAnimationEvent(e, phaseName, player) {
93 const totalTime = player.totalTime;
94 const disabled = player.disabled ? true : false;
95 const event = makeAnimationEvent(e.element, e.triggerName, e.fromState, e.toState, phaseName || e.phaseName, totalTime == undefined ? e.totalTime : totalTime, disabled);
96 const data = e['_data'];
97 if (data != null) {
98 event['_data'] = data;
99 }
100 return event;
101}
102function makeAnimationEvent(element, triggerName, fromState, toState, phaseName = '', totalTime = 0, disabled) {
103 return { element, triggerName, fromState, toState, phaseName, totalTime, disabled: !!disabled };
104}
105function getOrSetAsInMap(map, key, defaultValue) {
106 let value;
107 if (map instanceof Map) {
108 value = map.get(key);
109 if (!value) {
110 map.set(key, value = defaultValue);
111 }
112 }
113 else {
114 value = map[key];
115 if (!value) {
116 value = map[key] = defaultValue;
117 }
118 }
119 return value;
120}
121function parseTimelineCommand(command) {
122 const separatorPos = command.indexOf(':');
123 const id = command.substring(1, separatorPos);
124 const action = command.substr(separatorPos + 1);
125 return [id, action];
126}
127let _contains = (elm1, elm2) => false;
128const ɵ0 = _contains;
129let _matches = (element, selector) => false;
130const ɵ1 = _matches;
131let _query = (element, selector, multi) => {
132 return [];
133};
134const ɵ2 = _query;
135// Define utility methods for browsers and platform-server(domino) where Element
136// and utility methods exist.
137const _isNode = isNode();
138if (_isNode || typeof Element !== 'undefined') {
139 if (!isBrowser()) {
140 _contains = (elm1, elm2) => elm1.contains(elm2);
141 }
142 else {
143 _contains = (elm1, elm2) => {
144 while (elm2 && elm2 !== document.documentElement) {
145 if (elm2 === elm1) {
146 return true;
147 }
148 elm2 = elm2.parentNode || elm2.host; // consider host to support shadow DOM
149 }
150 return false;
151 };
152 }
153 _matches = (() => {
154 if (_isNode || Element.prototype.matches) {
155 return (element, selector) => element.matches(selector);
156 }
157 else {
158 const proto = Element.prototype;
159 const fn = proto.matchesSelector || proto.mozMatchesSelector || proto.msMatchesSelector ||
160 proto.oMatchesSelector || proto.webkitMatchesSelector;
161 if (fn) {
162 return (element, selector) => fn.apply(element, [selector]);
163 }
164 else {
165 return _matches;
166 }
167 }
168 })();
169 _query = (element, selector, multi) => {
170 let results = [];
171 if (multi) {
172 // DO NOT REFACTOR TO USE SPREAD SYNTAX.
173 // For element queries that return sufficiently large NodeList objects,
174 // using spread syntax to populate the results array causes a RangeError
175 // due to the call stack limit being reached. `Array.from` can not be used
176 // as well, since NodeList is not iterable in IE 11, see
177 // https://developer.mozilla.org/en-US/docs/Web/API/NodeList
178 // More info is available in #38551.
179 const elems = element.querySelectorAll(selector);
180 for (let i = 0; i < elems.length; i++) {
181 results.push(elems[i]);
182 }
183 }
184 else {
185 const elm = element.querySelector(selector);
186 if (elm) {
187 results.push(elm);
188 }
189 }
190 return results;
191 };
192}
193function containsVendorPrefix(prop) {
194 // Webkit is the only real popular vendor prefix nowadays
195 // cc: http://shouldiprefix.com/
196 return prop.substring(1, 6) == 'ebkit'; // webkit or Webkit
197}
198let _CACHED_BODY = null;
199let _IS_WEBKIT = false;
200function validateStyleProperty(prop) {
201 if (!_CACHED_BODY) {
202 _CACHED_BODY = getBodyNode() || {};
203 _IS_WEBKIT = _CACHED_BODY.style ? ('WebkitAppearance' in _CACHED_BODY.style) : false;
204 }
205 let result = true;
206 if (_CACHED_BODY.style && !containsVendorPrefix(prop)) {
207 result = prop in _CACHED_BODY.style;
208 if (!result && _IS_WEBKIT) {
209 const camelProp = 'Webkit' + prop.charAt(0).toUpperCase() + prop.substr(1);
210 result = camelProp in _CACHED_BODY.style;
211 }
212 }
213 return result;
214}
215function getBodyNode() {
216 if (typeof document != 'undefined') {
217 return document.body;
218 }
219 return null;
220}
221const matchesElement = _matches;
222const containsElement = _contains;
223const invokeQuery = _query;
224function hypenatePropsObject(object) {
225 const newObj = {};
226 Object.keys(object).forEach(prop => {
227 const newProp = prop.replace(/([a-z])([A-Z])/g, '$1-$2');
228 newObj[newProp] = object[prop];
229 });
230 return newObj;
231}
232
233/**
234 * @license
235 * Copyright Google LLC All Rights Reserved.
236 *
237 * Use of this source code is governed by an MIT-style license that can be
238 * found in the LICENSE file at https://angular.io/license
239 */
240/**
241 * @publicApi
242 */
243class NoopAnimationDriver {
244 validateStyleProperty(prop) {
245 return validateStyleProperty(prop);
246 }
247 matchesElement(element, selector) {
248 return matchesElement(element, selector);
249 }
250 containsElement(elm1, elm2) {
251 return containsElement(elm1, elm2);
252 }
253 query(element, selector, multi) {
254 return invokeQuery(element, selector, multi);
255 }
256 computeStyle(element, prop, defaultValue) {
257 return defaultValue || '';
258 }
259 animate(element, keyframes, duration, delay, easing, previousPlayers = [], scrubberAccessRequested) {
260 return new NoopAnimationPlayer(duration, delay);
261 }
262}
263NoopAnimationDriver.decorators = [
264 { type: Injectable }
265];
266/**
267 * @publicApi
268 */
269class AnimationDriver {
270}
271AnimationDriver.NOOP = ( /* @__PURE__ */new NoopAnimationDriver());
272
273/**
274 * @license
275 * Copyright Google LLC All Rights Reserved.
276 *
277 * Use of this source code is governed by an MIT-style license that can be
278 * found in the LICENSE file at https://angular.io/license
279 */
280const ONE_SECOND = 1000;
281const SUBSTITUTION_EXPR_START = '{{';
282const SUBSTITUTION_EXPR_END = '}}';
283const ENTER_CLASSNAME = 'ng-enter';
284const LEAVE_CLASSNAME = 'ng-leave';
285const ENTER_SELECTOR = '.ng-enter';
286const LEAVE_SELECTOR = '.ng-leave';
287const NG_TRIGGER_CLASSNAME = 'ng-trigger';
288const NG_TRIGGER_SELECTOR = '.ng-trigger';
289const NG_ANIMATING_CLASSNAME = 'ng-animating';
290const NG_ANIMATING_SELECTOR = '.ng-animating';
291function resolveTimingValue(value) {
292 if (typeof value == 'number')
293 return value;
294 const matches = value.match(/^(-?[\.\d]+)(m?s)/);
295 if (!matches || matches.length < 2)
296 return 0;
297 return _convertTimeValueToMS(parseFloat(matches[1]), matches[2]);
298}
299function _convertTimeValueToMS(value, unit) {
300 switch (unit) {
301 case 's':
302 return value * ONE_SECOND;
303 default: // ms or something else
304 return value;
305 }
306}
307function resolveTiming(timings, errors, allowNegativeValues) {
308 return timings.hasOwnProperty('duration') ?
309 timings :
310 parseTimeExpression(timings, errors, allowNegativeValues);
311}
312function parseTimeExpression(exp, errors, allowNegativeValues) {
313 const regex = /^(-?[\.\d]+)(m?s)(?:\s+(-?[\.\d]+)(m?s))?(?:\s+([-a-z]+(?:\(.+?\))?))?$/i;
314 let duration;
315 let delay = 0;
316 let easing = '';
317 if (typeof exp === 'string') {
318 const matches = exp.match(regex);
319 if (matches === null) {
320 errors.push(`The provided timing value "${exp}" is invalid.`);
321 return { duration: 0, delay: 0, easing: '' };
322 }
323 duration = _convertTimeValueToMS(parseFloat(matches[1]), matches[2]);
324 const delayMatch = matches[3];
325 if (delayMatch != null) {
326 delay = _convertTimeValueToMS(parseFloat(delayMatch), matches[4]);
327 }
328 const easingVal = matches[5];
329 if (easingVal) {
330 easing = easingVal;
331 }
332 }
333 else {
334 duration = exp;
335 }
336 if (!allowNegativeValues) {
337 let containsErrors = false;
338 let startIndex = errors.length;
339 if (duration < 0) {
340 errors.push(`Duration values below 0 are not allowed for this animation step.`);
341 containsErrors = true;
342 }
343 if (delay < 0) {
344 errors.push(`Delay values below 0 are not allowed for this animation step.`);
345 containsErrors = true;
346 }
347 if (containsErrors) {
348 errors.splice(startIndex, 0, `The provided timing value "${exp}" is invalid.`);
349 }
350 }
351 return { duration, delay, easing };
352}
353function copyObj(obj, destination = {}) {
354 Object.keys(obj).forEach(prop => {
355 destination[prop] = obj[prop];
356 });
357 return destination;
358}
359function normalizeStyles(styles) {
360 const normalizedStyles = {};
361 if (Array.isArray(styles)) {
362 styles.forEach(data => copyStyles(data, false, normalizedStyles));
363 }
364 else {
365 copyStyles(styles, false, normalizedStyles);
366 }
367 return normalizedStyles;
368}
369function copyStyles(styles, readPrototype, destination = {}) {
370 if (readPrototype) {
371 // we make use of a for-in loop so that the
372 // prototypically inherited properties are
373 // revealed from the backFill map
374 for (let prop in styles) {
375 destination[prop] = styles[prop];
376 }
377 }
378 else {
379 copyObj(styles, destination);
380 }
381 return destination;
382}
383function getStyleAttributeString(element, key, value) {
384 // Return the key-value pair string to be added to the style attribute for the
385 // given CSS style key.
386 if (value) {
387 return key + ':' + value + ';';
388 }
389 else {
390 return '';
391 }
392}
393function writeStyleAttribute(element) {
394 // Read the style property of the element and manually reflect it to the
395 // style attribute. This is needed because Domino on platform-server doesn't
396 // understand the full set of allowed CSS properties and doesn't reflect some
397 // of them automatically.
398 let styleAttrValue = '';
399 for (let i = 0; i < element.style.length; i++) {
400 const key = element.style.item(i);
401 styleAttrValue += getStyleAttributeString(element, key, element.style.getPropertyValue(key));
402 }
403 for (const key in element.style) {
404 // Skip internal Domino properties that don't need to be reflected.
405 if (!element.style.hasOwnProperty(key) || key.startsWith('_')) {
406 continue;
407 }
408 const dashKey = camelCaseToDashCase(key);
409 styleAttrValue += getStyleAttributeString(element, dashKey, element.style[key]);
410 }
411 element.setAttribute('style', styleAttrValue);
412}
413function setStyles(element, styles, formerStyles) {
414 if (element['style']) {
415 Object.keys(styles).forEach(prop => {
416 const camelProp = dashCaseToCamelCase(prop);
417 if (formerStyles && !formerStyles.hasOwnProperty(prop)) {
418 formerStyles[prop] = element.style[camelProp];
419 }
420 element.style[camelProp] = styles[prop];
421 });
422 // On the server set the 'style' attribute since it's not automatically reflected.
423 if (isNode()) {
424 writeStyleAttribute(element);
425 }
426 }
427}
428function eraseStyles(element, styles) {
429 if (element['style']) {
430 Object.keys(styles).forEach(prop => {
431 const camelProp = dashCaseToCamelCase(prop);
432 element.style[camelProp] = '';
433 });
434 // On the server set the 'style' attribute since it's not automatically reflected.
435 if (isNode()) {
436 writeStyleAttribute(element);
437 }
438 }
439}
440function normalizeAnimationEntry(steps) {
441 if (Array.isArray(steps)) {
442 if (steps.length == 1)
443 return steps[0];
444 return sequence(steps);
445 }
446 return steps;
447}
448function validateStyleParams(value, options, errors) {
449 const params = options.params || {};
450 const matches = extractStyleParams(value);
451 if (matches.length) {
452 matches.forEach(varName => {
453 if (!params.hasOwnProperty(varName)) {
454 errors.push(`Unable to resolve the local animation param ${varName} in the given list of values`);
455 }
456 });
457 }
458}
459const PARAM_REGEX = new RegExp(`${SUBSTITUTION_EXPR_START}\\s*(.+?)\\s*${SUBSTITUTION_EXPR_END}`, 'g');
460function extractStyleParams(value) {
461 let params = [];
462 if (typeof value === 'string') {
463 let match;
464 while (match = PARAM_REGEX.exec(value)) {
465 params.push(match[1]);
466 }
467 PARAM_REGEX.lastIndex = 0;
468 }
469 return params;
470}
471function interpolateParams(value, params, errors) {
472 const original = value.toString();
473 const str = original.replace(PARAM_REGEX, (_, varName) => {
474 let localVal = params[varName];
475 // this means that the value was never overridden by the data passed in by the user
476 if (!params.hasOwnProperty(varName)) {
477 errors.push(`Please provide a value for the animation param ${varName}`);
478 localVal = '';
479 }
480 return localVal.toString();
481 });
482 // we do this to assert that numeric values stay as they are
483 return str == original ? value : str;
484}
485function iteratorToArray(iterator) {
486 const arr = [];
487 let item = iterator.next();
488 while (!item.done) {
489 arr.push(item.value);
490 item = iterator.next();
491 }
492 return arr;
493}
494const DASH_CASE_REGEXP = /-+([a-z0-9])/g;
495function dashCaseToCamelCase(input) {
496 return input.replace(DASH_CASE_REGEXP, (...m) => m[1].toUpperCase());
497}
498function camelCaseToDashCase(input) {
499 return input.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
500}
501function allowPreviousPlayerStylesMerge(duration, delay) {
502 return duration === 0 || delay === 0;
503}
504function balancePreviousStylesIntoKeyframes(element, keyframes, previousStyles) {
505 const previousStyleProps = Object.keys(previousStyles);
506 if (previousStyleProps.length && keyframes.length) {
507 let startingKeyframe = keyframes[0];
508 let missingStyleProps = [];
509 previousStyleProps.forEach(prop => {
510 if (!startingKeyframe.hasOwnProperty(prop)) {
511 missingStyleProps.push(prop);
512 }
513 startingKeyframe[prop] = previousStyles[prop];
514 });
515 if (missingStyleProps.length) {
516 // tslint:disable-next-line
517 for (var i = 1; i < keyframes.length; i++) {
518 let kf = keyframes[i];
519 missingStyleProps.forEach(function (prop) {
520 kf[prop] = computeStyle(element, prop);
521 });
522 }
523 }
524 }
525 return keyframes;
526}
527function visitDslNode(visitor, node, context) {
528 switch (node.type) {
529 case 7 /* Trigger */:
530 return visitor.visitTrigger(node, context);
531 case 0 /* State */:
532 return visitor.visitState(node, context);
533 case 1 /* Transition */:
534 return visitor.visitTransition(node, context);
535 case 2 /* Sequence */:
536 return visitor.visitSequence(node, context);
537 case 3 /* Group */:
538 return visitor.visitGroup(node, context);
539 case 4 /* Animate */:
540 return visitor.visitAnimate(node, context);
541 case 5 /* Keyframes */:
542 return visitor.visitKeyframes(node, context);
543 case 6 /* Style */:
544 return visitor.visitStyle(node, context);
545 case 8 /* Reference */:
546 return visitor.visitReference(node, context);
547 case 9 /* AnimateChild */:
548 return visitor.visitAnimateChild(node, context);
549 case 10 /* AnimateRef */:
550 return visitor.visitAnimateRef(node, context);
551 case 11 /* Query */:
552 return visitor.visitQuery(node, context);
553 case 12 /* Stagger */:
554 return visitor.visitStagger(node, context);
555 default:
556 throw new Error(`Unable to resolve animation metadata node #${node.type}`);
557 }
558}
559function computeStyle(element, prop) {
560 return window.getComputedStyle(element)[prop];
561}
562
563/**
564 * @license
565 * Copyright Google LLC All Rights Reserved.
566 *
567 * Use of this source code is governed by an MIT-style license that can be
568 * found in the LICENSE file at https://angular.io/license
569 */
570const ANY_STATE = '*';
571function parseTransitionExpr(transitionValue, errors) {
572 const expressions = [];
573 if (typeof transitionValue == 'string') {
574 transitionValue.split(/\s*,\s*/).forEach(str => parseInnerTransitionStr(str, expressions, errors));
575 }
576 else {
577 expressions.push(transitionValue);
578 }
579 return expressions;
580}
581function parseInnerTransitionStr(eventStr, expressions, errors) {
582 if (eventStr[0] == ':') {
583 const result = parseAnimationAlias(eventStr, errors);
584 if (typeof result == 'function') {
585 expressions.push(result);
586 return;
587 }
588 eventStr = result;
589 }
590 const match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/);
591 if (match == null || match.length < 4) {
592 errors.push(`The provided transition expression "${eventStr}" is not supported`);
593 return expressions;
594 }
595 const fromState = match[1];
596 const separator = match[2];
597 const toState = match[3];
598 expressions.push(makeLambdaFromStates(fromState, toState));
599 const isFullAnyStateExpr = fromState == ANY_STATE && toState == ANY_STATE;
600 if (separator[0] == '<' && !isFullAnyStateExpr) {
601 expressions.push(makeLambdaFromStates(toState, fromState));
602 }
603}
604function parseAnimationAlias(alias, errors) {
605 switch (alias) {
606 case ':enter':
607 return 'void => *';
608 case ':leave':
609 return '* => void';
610 case ':increment':
611 return (fromState, toState) => parseFloat(toState) > parseFloat(fromState);
612 case ':decrement':
613 return (fromState, toState) => parseFloat(toState) < parseFloat(fromState);
614 default:
615 errors.push(`The transition alias value "${alias}" is not supported`);
616 return '* => *';
617 }
618}
619// DO NOT REFACTOR ... keep the follow set instantiations
620// with the values intact (closure compiler for some reason
621// removes follow-up lines that add the values outside of
622// the constructor...
623const TRUE_BOOLEAN_VALUES = new Set(['true', '1']);
624const FALSE_BOOLEAN_VALUES = new Set(['false', '0']);
625function makeLambdaFromStates(lhs, rhs) {
626 const LHS_MATCH_BOOLEAN = TRUE_BOOLEAN_VALUES.has(lhs) || FALSE_BOOLEAN_VALUES.has(lhs);
627 const RHS_MATCH_BOOLEAN = TRUE_BOOLEAN_VALUES.has(rhs) || FALSE_BOOLEAN_VALUES.has(rhs);
628 return (fromState, toState) => {
629 let lhsMatch = lhs == ANY_STATE || lhs == fromState;
630 let rhsMatch = rhs == ANY_STATE || rhs == toState;
631 if (!lhsMatch && LHS_MATCH_BOOLEAN && typeof fromState === 'boolean') {
632 lhsMatch = fromState ? TRUE_BOOLEAN_VALUES.has(lhs) : FALSE_BOOLEAN_VALUES.has(lhs);
633 }
634 if (!rhsMatch && RHS_MATCH_BOOLEAN && typeof toState === 'boolean') {
635 rhsMatch = toState ? TRUE_BOOLEAN_VALUES.has(rhs) : FALSE_BOOLEAN_VALUES.has(rhs);
636 }
637 return lhsMatch && rhsMatch;
638 };
639}
640
641/**
642 * @license
643 * Copyright Google LLC All Rights Reserved.
644 *
645 * Use of this source code is governed by an MIT-style license that can be
646 * found in the LICENSE file at https://angular.io/license
647 */
648const SELF_TOKEN = ':self';
649const SELF_TOKEN_REGEX = new RegExp(`\s*${SELF_TOKEN}\s*,?`, 'g');
650/*
651 * [Validation]
652 * The visitor code below will traverse the animation AST generated by the animation verb functions
653 * (the output is a tree of objects) and attempt to perform a series of validations on the data. The
654 * following corner-cases will be validated:
655 *
656 * 1. Overlap of animations
657 * Given that a CSS property cannot be animated in more than one place at the same time, it's
658 * important that this behavior is detected and validated. The way in which this occurs is that
659 * each time a style property is examined, a string-map containing the property will be updated with
660 * the start and end times for when the property is used within an animation step.
661 *
662 * If there are two or more parallel animations that are currently running (these are invoked by the
663 * group()) on the same element then the validator will throw an error. Since the start/end timing
664 * values are collected for each property then if the current animation step is animating the same
665 * property and its timing values fall anywhere into the window of time that the property is
666 * currently being animated within then this is what causes an error.
667 *
668 * 2. Timing values
669 * The validator will validate to see if a timing value of `duration delay easing` or
670 * `durationNumber` is valid or not.
671 *
672 * (note that upon validation the code below will replace the timing data with an object containing
673 * {duration,delay,easing}.
674 *
675 * 3. Offset Validation
676 * Each of the style() calls are allowed to have an offset value when placed inside of keyframes().
677 * Offsets within keyframes() are considered valid when:
678 *
679 * - No offsets are used at all
680 * - Each style() entry contains an offset value
681 * - Each offset is between 0 and 1
682 * - Each offset is greater to or equal than the previous one
683 *
684 * Otherwise an error will be thrown.
685 */
686function buildAnimationAst(driver, metadata, errors) {
687 return new AnimationAstBuilderVisitor(driver).build(metadata, errors);
688}
689const ROOT_SELECTOR = '';
690class AnimationAstBuilderVisitor {
691 constructor(_driver) {
692 this._driver = _driver;
693 }
694 build(metadata, errors) {
695 const context = new AnimationAstBuilderContext(errors);
696 this._resetContextStyleTimingState(context);
697 return visitDslNode(this, normalizeAnimationEntry(metadata), context);
698 }
699 _resetContextStyleTimingState(context) {
700 context.currentQuerySelector = ROOT_SELECTOR;
701 context.collectedStyles = {};
702 context.collectedStyles[ROOT_SELECTOR] = {};
703 context.currentTime = 0;
704 }
705 visitTrigger(metadata, context) {
706 let queryCount = context.queryCount = 0;
707 let depCount = context.depCount = 0;
708 const states = [];
709 const transitions = [];
710 if (metadata.name.charAt(0) == '@') {
711 context.errors.push('animation triggers cannot be prefixed with an `@` sign (e.g. trigger(\'@foo\', [...]))');
712 }
713 metadata.definitions.forEach(def => {
714 this._resetContextStyleTimingState(context);
715 if (def.type == 0 /* State */) {
716 const stateDef = def;
717 const name = stateDef.name;
718 name.toString().split(/\s*,\s*/).forEach(n => {
719 stateDef.name = n;
720 states.push(this.visitState(stateDef, context));
721 });
722 stateDef.name = name;
723 }
724 else if (def.type == 1 /* Transition */) {
725 const transition = this.visitTransition(def, context);
726 queryCount += transition.queryCount;
727 depCount += transition.depCount;
728 transitions.push(transition);
729 }
730 else {
731 context.errors.push('only state() and transition() definitions can sit inside of a trigger()');
732 }
733 });
734 return {
735 type: 7 /* Trigger */,
736 name: metadata.name,
737 states,
738 transitions,
739 queryCount,
740 depCount,
741 options: null
742 };
743 }
744 visitState(metadata, context) {
745 const styleAst = this.visitStyle(metadata.styles, context);
746 const astParams = (metadata.options && metadata.options.params) || null;
747 if (styleAst.containsDynamicStyles) {
748 const missingSubs = new Set();
749 const params = astParams || {};
750 styleAst.styles.forEach(value => {
751 if (isObject(value)) {
752 const stylesObj = value;
753 Object.keys(stylesObj).forEach(prop => {
754 extractStyleParams(stylesObj[prop]).forEach(sub => {
755 if (!params.hasOwnProperty(sub)) {
756 missingSubs.add(sub);
757 }
758 });
759 });
760 }
761 });
762 if (missingSubs.size) {
763 const missingSubsArr = iteratorToArray(missingSubs.values());
764 context.errors.push(`state("${metadata
765 .name}", ...) must define default values for all the following style substitutions: ${missingSubsArr.join(', ')}`);
766 }
767 }
768 return {
769 type: 0 /* State */,
770 name: metadata.name,
771 style: styleAst,
772 options: astParams ? { params: astParams } : null
773 };
774 }
775 visitTransition(metadata, context) {
776 context.queryCount = 0;
777 context.depCount = 0;
778 const animation = visitDslNode(this, normalizeAnimationEntry(metadata.animation), context);
779 const matchers = parseTransitionExpr(metadata.expr, context.errors);
780 return {
781 type: 1 /* Transition */,
782 matchers,
783 animation,
784 queryCount: context.queryCount,
785 depCount: context.depCount,
786 options: normalizeAnimationOptions(metadata.options)
787 };
788 }
789 visitSequence(metadata, context) {
790 return {
791 type: 2 /* Sequence */,
792 steps: metadata.steps.map(s => visitDslNode(this, s, context)),
793 options: normalizeAnimationOptions(metadata.options)
794 };
795 }
796 visitGroup(metadata, context) {
797 const currentTime = context.currentTime;
798 let furthestTime = 0;
799 const steps = metadata.steps.map(step => {
800 context.currentTime = currentTime;
801 const innerAst = visitDslNode(this, step, context);
802 furthestTime = Math.max(furthestTime, context.currentTime);
803 return innerAst;
804 });
805 context.currentTime = furthestTime;
806 return {
807 type: 3 /* Group */,
808 steps,
809 options: normalizeAnimationOptions(metadata.options)
810 };
811 }
812 visitAnimate(metadata, context) {
813 const timingAst = constructTimingAst(metadata.timings, context.errors);
814 context.currentAnimateTimings = timingAst;
815 let styleAst;
816 let styleMetadata = metadata.styles ? metadata.styles : style({});
817 if (styleMetadata.type == 5 /* Keyframes */) {
818 styleAst = this.visitKeyframes(styleMetadata, context);
819 }
820 else {
821 let styleMetadata = metadata.styles;
822 let isEmpty = false;
823 if (!styleMetadata) {
824 isEmpty = true;
825 const newStyleData = {};
826 if (timingAst.easing) {
827 newStyleData['easing'] = timingAst.easing;
828 }
829 styleMetadata = style(newStyleData);
830 }
831 context.currentTime += timingAst.duration + timingAst.delay;
832 const _styleAst = this.visitStyle(styleMetadata, context);
833 _styleAst.isEmptyStep = isEmpty;
834 styleAst = _styleAst;
835 }
836 context.currentAnimateTimings = null;
837 return {
838 type: 4 /* Animate */,
839 timings: timingAst,
840 style: styleAst,
841 options: null
842 };
843 }
844 visitStyle(metadata, context) {
845 const ast = this._makeStyleAst(metadata, context);
846 this._validateStyleAst(ast, context);
847 return ast;
848 }
849 _makeStyleAst(metadata, context) {
850 const styles = [];
851 if (Array.isArray(metadata.styles)) {
852 metadata.styles.forEach(styleTuple => {
853 if (typeof styleTuple == 'string') {
854 if (styleTuple == AUTO_STYLE) {
855 styles.push(styleTuple);
856 }
857 else {
858 context.errors.push(`The provided style string value ${styleTuple} is not allowed.`);
859 }
860 }
861 else {
862 styles.push(styleTuple);
863 }
864 });
865 }
866 else {
867 styles.push(metadata.styles);
868 }
869 let containsDynamicStyles = false;
870 let collectedEasing = null;
871 styles.forEach(styleData => {
872 if (isObject(styleData)) {
873 const styleMap = styleData;
874 const easing = styleMap['easing'];
875 if (easing) {
876 collectedEasing = easing;
877 delete styleMap['easing'];
878 }
879 if (!containsDynamicStyles) {
880 for (let prop in styleMap) {
881 const value = styleMap[prop];
882 if (value.toString().indexOf(SUBSTITUTION_EXPR_START) >= 0) {
883 containsDynamicStyles = true;
884 break;
885 }
886 }
887 }
888 }
889 });
890 return {
891 type: 6 /* Style */,
892 styles,
893 easing: collectedEasing,
894 offset: metadata.offset,
895 containsDynamicStyles,
896 options: null
897 };
898 }
899 _validateStyleAst(ast, context) {
900 const timings = context.currentAnimateTimings;
901 let endTime = context.currentTime;
902 let startTime = context.currentTime;
903 if (timings && startTime > 0) {
904 startTime -= timings.duration + timings.delay;
905 }
906 ast.styles.forEach(tuple => {
907 if (typeof tuple == 'string')
908 return;
909 Object.keys(tuple).forEach(prop => {
910 if (!this._driver.validateStyleProperty(prop)) {
911 context.errors.push(`The provided animation property "${prop}" is not a supported CSS property for animations`);
912 return;
913 }
914 const collectedStyles = context.collectedStyles[context.currentQuerySelector];
915 const collectedEntry = collectedStyles[prop];
916 let updateCollectedStyle = true;
917 if (collectedEntry) {
918 if (startTime != endTime && startTime >= collectedEntry.startTime &&
919 endTime <= collectedEntry.endTime) {
920 context.errors.push(`The CSS property "${prop}" that exists between the times of "${collectedEntry.startTime}ms" and "${collectedEntry
921 .endTime}ms" is also being animated in a parallel animation between the times of "${startTime}ms" and "${endTime}ms"`);
922 updateCollectedStyle = false;
923 }
924 // we always choose the smaller start time value since we
925 // want to have a record of the entire animation window where
926 // the style property is being animated in between
927 startTime = collectedEntry.startTime;
928 }
929 if (updateCollectedStyle) {
930 collectedStyles[prop] = { startTime, endTime };
931 }
932 if (context.options) {
933 validateStyleParams(tuple[prop], context.options, context.errors);
934 }
935 });
936 });
937 }
938 visitKeyframes(metadata, context) {
939 const ast = { type: 5 /* Keyframes */, styles: [], options: null };
940 if (!context.currentAnimateTimings) {
941 context.errors.push(`keyframes() must be placed inside of a call to animate()`);
942 return ast;
943 }
944 const MAX_KEYFRAME_OFFSET = 1;
945 let totalKeyframesWithOffsets = 0;
946 const offsets = [];
947 let offsetsOutOfOrder = false;
948 let keyframesOutOfRange = false;
949 let previousOffset = 0;
950 const keyframes = metadata.steps.map(styles => {
951 const style = this._makeStyleAst(styles, context);
952 let offsetVal = style.offset != null ? style.offset : consumeOffset(style.styles);
953 let offset = 0;
954 if (offsetVal != null) {
955 totalKeyframesWithOffsets++;
956 offset = style.offset = offsetVal;
957 }
958 keyframesOutOfRange = keyframesOutOfRange || offset < 0 || offset > 1;
959 offsetsOutOfOrder = offsetsOutOfOrder || offset < previousOffset;
960 previousOffset = offset;
961 offsets.push(offset);
962 return style;
963 });
964 if (keyframesOutOfRange) {
965 context.errors.push(`Please ensure that all keyframe offsets are between 0 and 1`);
966 }
967 if (offsetsOutOfOrder) {
968 context.errors.push(`Please ensure that all keyframe offsets are in order`);
969 }
970 const length = metadata.steps.length;
971 let generatedOffset = 0;
972 if (totalKeyframesWithOffsets > 0 && totalKeyframesWithOffsets < length) {
973 context.errors.push(`Not all style() steps within the declared keyframes() contain offsets`);
974 }
975 else if (totalKeyframesWithOffsets == 0) {
976 generatedOffset = MAX_KEYFRAME_OFFSET / (length - 1);
977 }
978 const limit = length - 1;
979 const currentTime = context.currentTime;
980 const currentAnimateTimings = context.currentAnimateTimings;
981 const animateDuration = currentAnimateTimings.duration;
982 keyframes.forEach((kf, i) => {
983 const offset = generatedOffset > 0 ? (i == limit ? 1 : (generatedOffset * i)) : offsets[i];
984 const durationUpToThisFrame = offset * animateDuration;
985 context.currentTime = currentTime + currentAnimateTimings.delay + durationUpToThisFrame;
986 currentAnimateTimings.duration = durationUpToThisFrame;
987 this._validateStyleAst(kf, context);
988 kf.offset = offset;
989 ast.styles.push(kf);
990 });
991 return ast;
992 }
993 visitReference(metadata, context) {
994 return {
995 type: 8 /* Reference */,
996 animation: visitDslNode(this, normalizeAnimationEntry(metadata.animation), context),
997 options: normalizeAnimationOptions(metadata.options)
998 };
999 }
1000 visitAnimateChild(metadata, context) {
1001 context.depCount++;
1002 return {
1003 type: 9 /* AnimateChild */,
1004 options: normalizeAnimationOptions(metadata.options)
1005 };
1006 }
1007 visitAnimateRef(metadata, context) {
1008 return {
1009 type: 10 /* AnimateRef */,
1010 animation: this.visitReference(metadata.animation, context),
1011 options: normalizeAnimationOptions(metadata.options)
1012 };
1013 }
1014 visitQuery(metadata, context) {
1015 const parentSelector = context.currentQuerySelector;
1016 const options = (metadata.options || {});
1017 context.queryCount++;
1018 context.currentQuery = metadata;
1019 const [selector, includeSelf] = normalizeSelector(metadata.selector);
1020 context.currentQuerySelector =
1021 parentSelector.length ? (parentSelector + ' ' + selector) : selector;
1022 getOrSetAsInMap(context.collectedStyles, context.currentQuerySelector, {});
1023 const animation = visitDslNode(this, normalizeAnimationEntry(metadata.animation), context);
1024 context.currentQuery = null;
1025 context.currentQuerySelector = parentSelector;
1026 return {
1027 type: 11 /* Query */,
1028 selector,
1029 limit: options.limit || 0,
1030 optional: !!options.optional,
1031 includeSelf,
1032 animation,
1033 originalSelector: metadata.selector,
1034 options: normalizeAnimationOptions(metadata.options)
1035 };
1036 }
1037 visitStagger(metadata, context) {
1038 if (!context.currentQuery) {
1039 context.errors.push(`stagger() can only be used inside of query()`);
1040 }
1041 const timings = metadata.timings === 'full' ?
1042 { duration: 0, delay: 0, easing: 'full' } :
1043 resolveTiming(metadata.timings, context.errors, true);
1044 return {
1045 type: 12 /* Stagger */,
1046 animation: visitDslNode(this, normalizeAnimationEntry(metadata.animation), context),
1047 timings,
1048 options: null
1049 };
1050 }
1051}
1052function normalizeSelector(selector) {
1053 const hasAmpersand = selector.split(/\s*,\s*/).find(token => token == SELF_TOKEN) ? true : false;
1054 if (hasAmpersand) {
1055 selector = selector.replace(SELF_TOKEN_REGEX, '');
1056 }
1057 // the :enter and :leave selectors are filled in at runtime during timeline building
1058 selector = selector.replace(/@\*/g, NG_TRIGGER_SELECTOR)
1059 .replace(/@\w+/g, match => NG_TRIGGER_SELECTOR + '-' + match.substr(1))
1060 .replace(/:animating/g, NG_ANIMATING_SELECTOR);
1061 return [selector, hasAmpersand];
1062}
1063function normalizeParams(obj) {
1064 return obj ? copyObj(obj) : null;
1065}
1066class AnimationAstBuilderContext {
1067 constructor(errors) {
1068 this.errors = errors;
1069 this.queryCount = 0;
1070 this.depCount = 0;
1071 this.currentTransition = null;
1072 this.currentQuery = null;
1073 this.currentQuerySelector = null;
1074 this.currentAnimateTimings = null;
1075 this.currentTime = 0;
1076 this.collectedStyles = {};
1077 this.options = null;
1078 }
1079}
1080function consumeOffset(styles) {
1081 if (typeof styles == 'string')
1082 return null;
1083 let offset = null;
1084 if (Array.isArray(styles)) {
1085 styles.forEach(styleTuple => {
1086 if (isObject(styleTuple) && styleTuple.hasOwnProperty('offset')) {
1087 const obj = styleTuple;
1088 offset = parseFloat(obj['offset']);
1089 delete obj['offset'];
1090 }
1091 });
1092 }
1093 else if (isObject(styles) && styles.hasOwnProperty('offset')) {
1094 const obj = styles;
1095 offset = parseFloat(obj['offset']);
1096 delete obj['offset'];
1097 }
1098 return offset;
1099}
1100function isObject(value) {
1101 return !Array.isArray(value) && typeof value == 'object';
1102}
1103function constructTimingAst(value, errors) {
1104 let timings = null;
1105 if (value.hasOwnProperty('duration')) {
1106 timings = value;
1107 }
1108 else if (typeof value == 'number') {
1109 const duration = resolveTiming(value, errors).duration;
1110 return makeTimingAst(duration, 0, '');
1111 }
1112 const strValue = value;
1113 const isDynamic = strValue.split(/\s+/).some(v => v.charAt(0) == '{' && v.charAt(1) == '{');
1114 if (isDynamic) {
1115 const ast = makeTimingAst(0, 0, '');
1116 ast.dynamic = true;
1117 ast.strValue = strValue;
1118 return ast;
1119 }
1120 timings = timings || resolveTiming(strValue, errors);
1121 return makeTimingAst(timings.duration, timings.delay, timings.easing);
1122}
1123function normalizeAnimationOptions(options) {
1124 if (options) {
1125 options = copyObj(options);
1126 if (options['params']) {
1127 options['params'] = normalizeParams(options['params']);
1128 }
1129 }
1130 else {
1131 options = {};
1132 }
1133 return options;
1134}
1135function makeTimingAst(duration, delay, easing) {
1136 return { duration, delay, easing };
1137}
1138
1139function createTimelineInstruction(element, keyframes, preStyleProps, postStyleProps, duration, delay, easing = null, subTimeline = false) {
1140 return {
1141 type: 1 /* TimelineAnimation */,
1142 element,
1143 keyframes,
1144 preStyleProps,
1145 postStyleProps,
1146 duration,
1147 delay,
1148 totalTime: duration + delay,
1149 easing,
1150 subTimeline
1151 };
1152}
1153
1154class ElementInstructionMap {
1155 constructor() {
1156 this._map = new Map();
1157 }
1158 consume(element) {
1159 let instructions = this._map.get(element);
1160 if (instructions) {
1161 this._map.delete(element);
1162 }
1163 else {
1164 instructions = [];
1165 }
1166 return instructions;
1167 }
1168 append(element, instructions) {
1169 let existingInstructions = this._map.get(element);
1170 if (!existingInstructions) {
1171 this._map.set(element, existingInstructions = []);
1172 }
1173 existingInstructions.push(...instructions);
1174 }
1175 has(element) {
1176 return this._map.has(element);
1177 }
1178 clear() {
1179 this._map.clear();
1180 }
1181}
1182
1183/**
1184 * @license
1185 * Copyright Google LLC All Rights Reserved.
1186 *
1187 * Use of this source code is governed by an MIT-style license that can be
1188 * found in the LICENSE file at https://angular.io/license
1189 */
1190const ONE_FRAME_IN_MILLISECONDS = 1;
1191const ENTER_TOKEN = ':enter';
1192const ENTER_TOKEN_REGEX = new RegExp(ENTER_TOKEN, 'g');
1193const LEAVE_TOKEN = ':leave';
1194const LEAVE_TOKEN_REGEX = new RegExp(LEAVE_TOKEN, 'g');
1195/*
1196 * The code within this file aims to generate web-animations-compatible keyframes from Angular's
1197 * animation DSL code.
1198 *
1199 * The code below will be converted from:
1200 *
1201 * ```
1202 * sequence([
1203 * style({ opacity: 0 }),
1204 * animate(1000, style({ opacity: 0 }))
1205 * ])
1206 * ```
1207 *
1208 * To:
1209 * ```
1210 * keyframes = [{ opacity: 0, offset: 0 }, { opacity: 1, offset: 1 }]
1211 * duration = 1000
1212 * delay = 0
1213 * easing = ''
1214 * ```
1215 *
1216 * For this operation to cover the combination of animation verbs (style, animate, group, etc...) a
1217 * combination of prototypical inheritance, AST traversal and merge-sort-like algorithms are used.
1218 *
1219 * [AST Traversal]
1220 * Each of the animation verbs, when executed, will return an string-map object representing what
1221 * type of action it is (style, animate, group, etc...) and the data associated with it. This means
1222 * that when functional composition mix of these functions is evaluated (like in the example above)
1223 * then it will end up producing a tree of objects representing the animation itself.
1224 *
1225 * When this animation object tree is processed by the visitor code below it will visit each of the
1226 * verb statements within the visitor. And during each visit it will build the context of the
1227 * animation keyframes by interacting with the `TimelineBuilder`.
1228 *
1229 * [TimelineBuilder]
1230 * This class is responsible for tracking the styles and building a series of keyframe objects for a
1231 * timeline between a start and end time. The builder starts off with an initial timeline and each
1232 * time the AST comes across a `group()`, `keyframes()` or a combination of the two wihtin a
1233 * `sequence()` then it will generate a sub timeline for each step as well as a new one after
1234 * they are complete.
1235 *
1236 * As the AST is traversed, the timing state on each of the timelines will be incremented. If a sub
1237 * timeline was created (based on one of the cases above) then the parent timeline will attempt to
1238 * merge the styles used within the sub timelines into itself (only with group() this will happen).
1239 * This happens with a merge operation (much like how the merge works in mergesort) and it will only
1240 * copy the most recently used styles from the sub timelines into the parent timeline. This ensures
1241 * that if the styles are used later on in another phase of the animation then they will be the most
1242 * up-to-date values.
1243 *
1244 * [How Missing Styles Are Updated]
1245 * Each timeline has a `backFill` property which is responsible for filling in new styles into
1246 * already processed keyframes if a new style shows up later within the animation sequence.
1247 *
1248 * ```
1249 * sequence([
1250 * style({ width: 0 }),
1251 * animate(1000, style({ width: 100 })),
1252 * animate(1000, style({ width: 200 })),
1253 * animate(1000, style({ width: 300 }))
1254 * animate(1000, style({ width: 400, height: 400 })) // notice how `height` doesn't exist anywhere
1255 * else
1256 * ])
1257 * ```
1258 *
1259 * What is happening here is that the `height` value is added later in the sequence, but is missing
1260 * from all previous animation steps. Therefore when a keyframe is created it would also be missing
1261 * from all previous keyframes up until where it is first used. For the timeline keyframe generation
1262 * to properly fill in the style it will place the previous value (the value from the parent
1263 * timeline) or a default value of `*` into the backFill object. Given that each of the keyframe
1264 * styles are objects that prototypically inhert from the backFill object, this means that if a
1265 * value is added into the backFill then it will automatically propagate any missing values to all
1266 * keyframes. Therefore the missing `height` value will be properly filled into the already
1267 * processed keyframes.
1268 *
1269 * When a sub-timeline is created it will have its own backFill property. This is done so that
1270 * styles present within the sub-timeline do not accidentally seep into the previous/future timeline
1271 * keyframes
1272 *
1273 * (For prototypically-inherited contents to be detected a `for(i in obj)` loop must be used.)
1274 *
1275 * [Validation]
1276 * The code in this file is not responsible for validation. That functionality happens with within
1277 * the `AnimationValidatorVisitor` code.
1278 */
1279function buildAnimationTimelines(driver, rootElement, ast, enterClassName, leaveClassName, startingStyles = {}, finalStyles = {}, options, subInstructions, errors = []) {
1280 return new AnimationTimelineBuilderVisitor().buildKeyframes(driver, rootElement, ast, enterClassName, leaveClassName, startingStyles, finalStyles, options, subInstructions, errors);
1281}
1282class AnimationTimelineBuilderVisitor {
1283 buildKeyframes(driver, rootElement, ast, enterClassName, leaveClassName, startingStyles, finalStyles, options, subInstructions, errors = []) {
1284 subInstructions = subInstructions || new ElementInstructionMap();
1285 const context = new AnimationTimelineContext(driver, rootElement, subInstructions, enterClassName, leaveClassName, errors, []);
1286 context.options = options;
1287 context.currentTimeline.setStyles([startingStyles], null, context.errors, options);
1288 visitDslNode(this, ast, context);
1289 // this checks to see if an actual animation happened
1290 const timelines = context.timelines.filter(timeline => timeline.containsAnimation());
1291 if (timelines.length && Object.keys(finalStyles).length) {
1292 const tl = timelines[timelines.length - 1];
1293 if (!tl.allowOnlyTimelineStyles()) {
1294 tl.setStyles([finalStyles], null, context.errors, options);
1295 }
1296 }
1297 return timelines.length ? timelines.map(timeline => timeline.buildKeyframes()) :
1298 [createTimelineInstruction(rootElement, [], [], [], 0, 0, '', false)];
1299 }
1300 visitTrigger(ast, context) {
1301 // these values are not visited in this AST
1302 }
1303 visitState(ast, context) {
1304 // these values are not visited in this AST
1305 }
1306 visitTransition(ast, context) {
1307 // these values are not visited in this AST
1308 }
1309 visitAnimateChild(ast, context) {
1310 const elementInstructions = context.subInstructions.consume(context.element);
1311 if (elementInstructions) {
1312 const innerContext = context.createSubContext(ast.options);
1313 const startTime = context.currentTimeline.currentTime;
1314 const endTime = this._visitSubInstructions(elementInstructions, innerContext, innerContext.options);
1315 if (startTime != endTime) {
1316 // we do this on the upper context because we created a sub context for
1317 // the sub child animations
1318 context.transformIntoNewTimeline(endTime);
1319 }
1320 }
1321 context.previousNode = ast;
1322 }
1323 visitAnimateRef(ast, context) {
1324 const innerContext = context.createSubContext(ast.options);
1325 innerContext.transformIntoNewTimeline();
1326 this.visitReference(ast.animation, innerContext);
1327 context.transformIntoNewTimeline(innerContext.currentTimeline.currentTime);
1328 context.previousNode = ast;
1329 }
1330 _visitSubInstructions(instructions, context, options) {
1331 const startTime = context.currentTimeline.currentTime;
1332 let furthestTime = startTime;
1333 // this is a special-case for when a user wants to skip a sub
1334 // animation from being fired entirely.
1335 const duration = options.duration != null ? resolveTimingValue(options.duration) : null;
1336 const delay = options.delay != null ? resolveTimingValue(options.delay) : null;
1337 if (duration !== 0) {
1338 instructions.forEach(instruction => {
1339 const instructionTimings = context.appendInstructionToTimeline(instruction, duration, delay);
1340 furthestTime =
1341 Math.max(furthestTime, instructionTimings.duration + instructionTimings.delay);
1342 });
1343 }
1344 return furthestTime;
1345 }
1346 visitReference(ast, context) {
1347 context.updateOptions(ast.options, true);
1348 visitDslNode(this, ast.animation, context);
1349 context.previousNode = ast;
1350 }
1351 visitSequence(ast, context) {
1352 const subContextCount = context.subContextCount;
1353 let ctx = context;
1354 const options = ast.options;
1355 if (options && (options.params || options.delay)) {
1356 ctx = context.createSubContext(options);
1357 ctx.transformIntoNewTimeline();
1358 if (options.delay != null) {
1359 if (ctx.previousNode.type == 6 /* Style */) {
1360 ctx.currentTimeline.snapshotCurrentStyles();
1361 ctx.previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
1362 }
1363 const delay = resolveTimingValue(options.delay);
1364 ctx.delayNextStep(delay);
1365 }
1366 }
1367 if (ast.steps.length) {
1368 ast.steps.forEach(s => visitDslNode(this, s, ctx));
1369 // this is here just incase the inner steps only contain or end with a style() call
1370 ctx.currentTimeline.applyStylesToKeyframe();
1371 // this means that some animation function within the sequence
1372 // ended up creating a sub timeline (which means the current
1373 // timeline cannot overlap with the contents of the sequence)
1374 if (ctx.subContextCount > subContextCount) {
1375 ctx.transformIntoNewTimeline();
1376 }
1377 }
1378 context.previousNode = ast;
1379 }
1380 visitGroup(ast, context) {
1381 const innerTimelines = [];
1382 let furthestTime = context.currentTimeline.currentTime;
1383 const delay = ast.options && ast.options.delay ? resolveTimingValue(ast.options.delay) : 0;
1384 ast.steps.forEach(s => {
1385 const innerContext = context.createSubContext(ast.options);
1386 if (delay) {
1387 innerContext.delayNextStep(delay);
1388 }
1389 visitDslNode(this, s, innerContext);
1390 furthestTime = Math.max(furthestTime, innerContext.currentTimeline.currentTime);
1391 innerTimelines.push(innerContext.currentTimeline);
1392 });
1393 // this operation is run after the AST loop because otherwise
1394 // if the parent timeline's collected styles were updated then
1395 // it would pass in invalid data into the new-to-be forked items
1396 innerTimelines.forEach(timeline => context.currentTimeline.mergeTimelineCollectedStyles(timeline));
1397 context.transformIntoNewTimeline(furthestTime);
1398 context.previousNode = ast;
1399 }
1400 _visitTiming(ast, context) {
1401 if (ast.dynamic) {
1402 const strValue = ast.strValue;
1403 const timingValue = context.params ? interpolateParams(strValue, context.params, context.errors) : strValue;
1404 return resolveTiming(timingValue, context.errors);
1405 }
1406 else {
1407 return { duration: ast.duration, delay: ast.delay, easing: ast.easing };
1408 }
1409 }
1410 visitAnimate(ast, context) {
1411 const timings = context.currentAnimateTimings = this._visitTiming(ast.timings, context);
1412 const timeline = context.currentTimeline;
1413 if (timings.delay) {
1414 context.incrementTime(timings.delay);
1415 timeline.snapshotCurrentStyles();
1416 }
1417 const style = ast.style;
1418 if (style.type == 5 /* Keyframes */) {
1419 this.visitKeyframes(style, context);
1420 }
1421 else {
1422 context.incrementTime(timings.duration);
1423 this.visitStyle(style, context);
1424 timeline.applyStylesToKeyframe();
1425 }
1426 context.currentAnimateTimings = null;
1427 context.previousNode = ast;
1428 }
1429 visitStyle(ast, context) {
1430 const timeline = context.currentTimeline;
1431 const timings = context.currentAnimateTimings;
1432 // this is a special case for when a style() call
1433 // directly follows an animate() call (but not inside of an animate() call)
1434 if (!timings && timeline.getCurrentStyleProperties().length) {
1435 timeline.forwardFrame();
1436 }
1437 const easing = (timings && timings.easing) || ast.easing;
1438 if (ast.isEmptyStep) {
1439 timeline.applyEmptyStep(easing);
1440 }
1441 else {
1442 timeline.setStyles(ast.styles, easing, context.errors, context.options);
1443 }
1444 context.previousNode = ast;
1445 }
1446 visitKeyframes(ast, context) {
1447 const currentAnimateTimings = context.currentAnimateTimings;
1448 const startTime = (context.currentTimeline).duration;
1449 const duration = currentAnimateTimings.duration;
1450 const innerContext = context.createSubContext();
1451 const innerTimeline = innerContext.currentTimeline;
1452 innerTimeline.easing = currentAnimateTimings.easing;
1453 ast.styles.forEach(step => {
1454 const offset = step.offset || 0;
1455 innerTimeline.forwardTime(offset * duration);
1456 innerTimeline.setStyles(step.styles, step.easing, context.errors, context.options);
1457 innerTimeline.applyStylesToKeyframe();
1458 });
1459 // this will ensure that the parent timeline gets all the styles from
1460 // the child even if the new timeline below is not used
1461 context.currentTimeline.mergeTimelineCollectedStyles(innerTimeline);
1462 // we do this because the window between this timeline and the sub timeline
1463 // should ensure that the styles within are exactly the same as they were before
1464 context.transformIntoNewTimeline(startTime + duration);
1465 context.previousNode = ast;
1466 }
1467 visitQuery(ast, context) {
1468 // in the event that the first step before this is a style step we need
1469 // to ensure the styles are applied before the children are animated
1470 const startTime = context.currentTimeline.currentTime;
1471 const options = (ast.options || {});
1472 const delay = options.delay ? resolveTimingValue(options.delay) : 0;
1473 if (delay &&
1474 (context.previousNode.type === 6 /* Style */ ||
1475 (startTime == 0 && context.currentTimeline.getCurrentStyleProperties().length))) {
1476 context.currentTimeline.snapshotCurrentStyles();
1477 context.previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
1478 }
1479 let furthestTime = startTime;
1480 const elms = context.invokeQuery(ast.selector, ast.originalSelector, ast.limit, ast.includeSelf, options.optional ? true : false, context.errors);
1481 context.currentQueryTotal = elms.length;
1482 let sameElementTimeline = null;
1483 elms.forEach((element, i) => {
1484 context.currentQueryIndex = i;
1485 const innerContext = context.createSubContext(ast.options, element);
1486 if (delay) {
1487 innerContext.delayNextStep(delay);
1488 }
1489 if (element === context.element) {
1490 sameElementTimeline = innerContext.currentTimeline;
1491 }
1492 visitDslNode(this, ast.animation, innerContext);
1493 // this is here just incase the inner steps only contain or end
1494 // with a style() call (which is here to signal that this is a preparatory
1495 // call to style an element before it is animated again)
1496 innerContext.currentTimeline.applyStylesToKeyframe();
1497 const endTime = innerContext.currentTimeline.currentTime;
1498 furthestTime = Math.max(furthestTime, endTime);
1499 });
1500 context.currentQueryIndex = 0;
1501 context.currentQueryTotal = 0;
1502 context.transformIntoNewTimeline(furthestTime);
1503 if (sameElementTimeline) {
1504 context.currentTimeline.mergeTimelineCollectedStyles(sameElementTimeline);
1505 context.currentTimeline.snapshotCurrentStyles();
1506 }
1507 context.previousNode = ast;
1508 }
1509 visitStagger(ast, context) {
1510 const parentContext = context.parentContext;
1511 const tl = context.currentTimeline;
1512 const timings = ast.timings;
1513 const duration = Math.abs(timings.duration);
1514 const maxTime = duration * (context.currentQueryTotal - 1);
1515 let delay = duration * context.currentQueryIndex;
1516 let staggerTransformer = timings.duration < 0 ? 'reverse' : timings.easing;
1517 switch (staggerTransformer) {
1518 case 'reverse':
1519 delay = maxTime - delay;
1520 break;
1521 case 'full':
1522 delay = parentContext.currentStaggerTime;
1523 break;
1524 }
1525 const timeline = context.currentTimeline;
1526 if (delay) {
1527 timeline.delayNextStep(delay);
1528 }
1529 const startingTime = timeline.currentTime;
1530 visitDslNode(this, ast.animation, context);
1531 context.previousNode = ast;
1532 // time = duration + delay
1533 // the reason why this computation is so complex is because
1534 // the inner timeline may either have a delay value or a stretched
1535 // keyframe depending on if a subtimeline is not used or is used.
1536 parentContext.currentStaggerTime =
1537 (tl.currentTime - startingTime) + (tl.startTime - parentContext.currentTimeline.startTime);
1538 }
1539}
1540const DEFAULT_NOOP_PREVIOUS_NODE = {};
1541class AnimationTimelineContext {
1542 constructor(_driver, element, subInstructions, _enterClassName, _leaveClassName, errors, timelines, initialTimeline) {
1543 this._driver = _driver;
1544 this.element = element;
1545 this.subInstructions = subInstructions;
1546 this._enterClassName = _enterClassName;
1547 this._leaveClassName = _leaveClassName;
1548 this.errors = errors;
1549 this.timelines = timelines;
1550 this.parentContext = null;
1551 this.currentAnimateTimings = null;
1552 this.previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
1553 this.subContextCount = 0;
1554 this.options = {};
1555 this.currentQueryIndex = 0;
1556 this.currentQueryTotal = 0;
1557 this.currentStaggerTime = 0;
1558 this.currentTimeline = initialTimeline || new TimelineBuilder(this._driver, element, 0);
1559 timelines.push(this.currentTimeline);
1560 }
1561 get params() {
1562 return this.options.params;
1563 }
1564 updateOptions(options, skipIfExists) {
1565 if (!options)
1566 return;
1567 const newOptions = options;
1568 let optionsToUpdate = this.options;
1569 // NOTE: this will get patched up when other animation methods support duration overrides
1570 if (newOptions.duration != null) {
1571 optionsToUpdate.duration = resolveTimingValue(newOptions.duration);
1572 }
1573 if (newOptions.delay != null) {
1574 optionsToUpdate.delay = resolveTimingValue(newOptions.delay);
1575 }
1576 const newParams = newOptions.params;
1577 if (newParams) {
1578 let paramsToUpdate = optionsToUpdate.params;
1579 if (!paramsToUpdate) {
1580 paramsToUpdate = this.options.params = {};
1581 }
1582 Object.keys(newParams).forEach(name => {
1583 if (!skipIfExists || !paramsToUpdate.hasOwnProperty(name)) {
1584 paramsToUpdate[name] = interpolateParams(newParams[name], paramsToUpdate, this.errors);
1585 }
1586 });
1587 }
1588 }
1589 _copyOptions() {
1590 const options = {};
1591 if (this.options) {
1592 const oldParams = this.options.params;
1593 if (oldParams) {
1594 const params = options['params'] = {};
1595 Object.keys(oldParams).forEach(name => {
1596 params[name] = oldParams[name];
1597 });
1598 }
1599 }
1600 return options;
1601 }
1602 createSubContext(options = null, element, newTime) {
1603 const target = element || this.element;
1604 const context = new AnimationTimelineContext(this._driver, target, this.subInstructions, this._enterClassName, this._leaveClassName, this.errors, this.timelines, this.currentTimeline.fork(target, newTime || 0));
1605 context.previousNode = this.previousNode;
1606 context.currentAnimateTimings = this.currentAnimateTimings;
1607 context.options = this._copyOptions();
1608 context.updateOptions(options);
1609 context.currentQueryIndex = this.currentQueryIndex;
1610 context.currentQueryTotal = this.currentQueryTotal;
1611 context.parentContext = this;
1612 this.subContextCount++;
1613 return context;
1614 }
1615 transformIntoNewTimeline(newTime) {
1616 this.previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
1617 this.currentTimeline = this.currentTimeline.fork(this.element, newTime);
1618 this.timelines.push(this.currentTimeline);
1619 return this.currentTimeline;
1620 }
1621 appendInstructionToTimeline(instruction, duration, delay) {
1622 const updatedTimings = {
1623 duration: duration != null ? duration : instruction.duration,
1624 delay: this.currentTimeline.currentTime + (delay != null ? delay : 0) + instruction.delay,
1625 easing: ''
1626 };
1627 const builder = new SubTimelineBuilder(this._driver, instruction.element, instruction.keyframes, instruction.preStyleProps, instruction.postStyleProps, updatedTimings, instruction.stretchStartingKeyframe);
1628 this.timelines.push(builder);
1629 return updatedTimings;
1630 }
1631 incrementTime(time) {
1632 this.currentTimeline.forwardTime(this.currentTimeline.duration + time);
1633 }
1634 delayNextStep(delay) {
1635 // negative delays are not yet supported
1636 if (delay > 0) {
1637 this.currentTimeline.delayNextStep(delay);
1638 }
1639 }
1640 invokeQuery(selector, originalSelector, limit, includeSelf, optional, errors) {
1641 let results = [];
1642 if (includeSelf) {
1643 results.push(this.element);
1644 }
1645 if (selector.length > 0) { // if :self is only used then the selector is empty
1646 selector = selector.replace(ENTER_TOKEN_REGEX, '.' + this._enterClassName);
1647 selector = selector.replace(LEAVE_TOKEN_REGEX, '.' + this._leaveClassName);
1648 const multi = limit != 1;
1649 let elements = this._driver.query(this.element, selector, multi);
1650 if (limit !== 0) {
1651 elements = limit < 0 ? elements.slice(elements.length + limit, elements.length) :
1652 elements.slice(0, limit);
1653 }
1654 results.push(...elements);
1655 }
1656 if (!optional && results.length == 0) {
1657 errors.push(`\`query("${originalSelector}")\` returned zero elements. (Use \`query("${originalSelector}", { optional: true })\` if you wish to allow this.)`);
1658 }
1659 return results;
1660 }
1661}
1662class TimelineBuilder {
1663 constructor(_driver, element, startTime, _elementTimelineStylesLookup) {
1664 this._driver = _driver;
1665 this.element = element;
1666 this.startTime = startTime;
1667 this._elementTimelineStylesLookup = _elementTimelineStylesLookup;
1668 this.duration = 0;
1669 this._previousKeyframe = {};
1670 this._currentKeyframe = {};
1671 this._keyframes = new Map();
1672 this._styleSummary = {};
1673 this._pendingStyles = {};
1674 this._backFill = {};
1675 this._currentEmptyStepKeyframe = null;
1676 if (!this._elementTimelineStylesLookup) {
1677 this._elementTimelineStylesLookup = new Map();
1678 }
1679 this._localTimelineStyles = Object.create(this._backFill, {});
1680 this._globalTimelineStyles = this._elementTimelineStylesLookup.get(element);
1681 if (!this._globalTimelineStyles) {
1682 this._globalTimelineStyles = this._localTimelineStyles;
1683 this._elementTimelineStylesLookup.set(element, this._localTimelineStyles);
1684 }
1685 this._loadKeyframe();
1686 }
1687 containsAnimation() {
1688 switch (this._keyframes.size) {
1689 case 0:
1690 return false;
1691 case 1:
1692 return this.getCurrentStyleProperties().length > 0;
1693 default:
1694 return true;
1695 }
1696 }
1697 getCurrentStyleProperties() {
1698 return Object.keys(this._currentKeyframe);
1699 }
1700 get currentTime() {
1701 return this.startTime + this.duration;
1702 }
1703 delayNextStep(delay) {
1704 // in the event that a style() step is placed right before a stagger()
1705 // and that style() step is the very first style() value in the animation
1706 // then we need to make a copy of the keyframe [0, copy, 1] so that the delay
1707 // properly applies the style() values to work with the stagger...
1708 const hasPreStyleStep = this._keyframes.size == 1 && Object.keys(this._pendingStyles).length;
1709 if (this.duration || hasPreStyleStep) {
1710 this.forwardTime(this.currentTime + delay);
1711 if (hasPreStyleStep) {
1712 this.snapshotCurrentStyles();
1713 }
1714 }
1715 else {
1716 this.startTime += delay;
1717 }
1718 }
1719 fork(element, currentTime) {
1720 this.applyStylesToKeyframe();
1721 return new TimelineBuilder(this._driver, element, currentTime || this.currentTime, this._elementTimelineStylesLookup);
1722 }
1723 _loadKeyframe() {
1724 if (this._currentKeyframe) {
1725 this._previousKeyframe = this._currentKeyframe;
1726 }
1727 this._currentKeyframe = this._keyframes.get(this.duration);
1728 if (!this._currentKeyframe) {
1729 this._currentKeyframe = Object.create(this._backFill, {});
1730 this._keyframes.set(this.duration, this._currentKeyframe);
1731 }
1732 }
1733 forwardFrame() {
1734 this.duration += ONE_FRAME_IN_MILLISECONDS;
1735 this._loadKeyframe();
1736 }
1737 forwardTime(time) {
1738 this.applyStylesToKeyframe();
1739 this.duration = time;
1740 this._loadKeyframe();
1741 }
1742 _updateStyle(prop, value) {
1743 this._localTimelineStyles[prop] = value;
1744 this._globalTimelineStyles[prop] = value;
1745 this._styleSummary[prop] = { time: this.currentTime, value };
1746 }
1747 allowOnlyTimelineStyles() {
1748 return this._currentEmptyStepKeyframe !== this._currentKeyframe;
1749 }
1750 applyEmptyStep(easing) {
1751 if (easing) {
1752 this._previousKeyframe['easing'] = easing;
1753 }
1754 // special case for animate(duration):
1755 // all missing styles are filled with a `*` value then
1756 // if any destination styles are filled in later on the same
1757 // keyframe then they will override the overridden styles
1758 // We use `_globalTimelineStyles` here because there may be
1759 // styles in previous keyframes that are not present in this timeline
1760 Object.keys(this._globalTimelineStyles).forEach(prop => {
1761 this._backFill[prop] = this._globalTimelineStyles[prop] || AUTO_STYLE;
1762 this._currentKeyframe[prop] = AUTO_STYLE;
1763 });
1764 this._currentEmptyStepKeyframe = this._currentKeyframe;
1765 }
1766 setStyles(input, easing, errors, options) {
1767 if (easing) {
1768 this._previousKeyframe['easing'] = easing;
1769 }
1770 const params = (options && options.params) || {};
1771 const styles = flattenStyles(input, this._globalTimelineStyles);
1772 Object.keys(styles).forEach(prop => {
1773 const val = interpolateParams(styles[prop], params, errors);
1774 this._pendingStyles[prop] = val;
1775 if (!this._localTimelineStyles.hasOwnProperty(prop)) {
1776 this._backFill[prop] = this._globalTimelineStyles.hasOwnProperty(prop) ?
1777 this._globalTimelineStyles[prop] :
1778 AUTO_STYLE;
1779 }
1780 this._updateStyle(prop, val);
1781 });
1782 }
1783 applyStylesToKeyframe() {
1784 const styles = this._pendingStyles;
1785 const props = Object.keys(styles);
1786 if (props.length == 0)
1787 return;
1788 this._pendingStyles = {};
1789 props.forEach(prop => {
1790 const val = styles[prop];
1791 this._currentKeyframe[prop] = val;
1792 });
1793 Object.keys(this._localTimelineStyles).forEach(prop => {
1794 if (!this._currentKeyframe.hasOwnProperty(prop)) {
1795 this._currentKeyframe[prop] = this._localTimelineStyles[prop];
1796 }
1797 });
1798 }
1799 snapshotCurrentStyles() {
1800 Object.keys(this._localTimelineStyles).forEach(prop => {
1801 const val = this._localTimelineStyles[prop];
1802 this._pendingStyles[prop] = val;
1803 this._updateStyle(prop, val);
1804 });
1805 }
1806 getFinalKeyframe() {
1807 return this._keyframes.get(this.duration);
1808 }
1809 get properties() {
1810 const properties = [];
1811 for (let prop in this._currentKeyframe) {
1812 properties.push(prop);
1813 }
1814 return properties;
1815 }
1816 mergeTimelineCollectedStyles(timeline) {
1817 Object.keys(timeline._styleSummary).forEach(prop => {
1818 const details0 = this._styleSummary[prop];
1819 const details1 = timeline._styleSummary[prop];
1820 if (!details0 || details1.time > details0.time) {
1821 this._updateStyle(prop, details1.value);
1822 }
1823 });
1824 }
1825 buildKeyframes() {
1826 this.applyStylesToKeyframe();
1827 const preStyleProps = new Set();
1828 const postStyleProps = new Set();
1829 const isEmpty = this._keyframes.size === 1 && this.duration === 0;
1830 let finalKeyframes = [];
1831 this._keyframes.forEach((keyframe, time) => {
1832 const finalKeyframe = copyStyles(keyframe, true);
1833 Object.keys(finalKeyframe).forEach(prop => {
1834 const value = finalKeyframe[prop];
1835 if (value == ɵPRE_STYLE) {
1836 preStyleProps.add(prop);
1837 }
1838 else if (value == AUTO_STYLE) {
1839 postStyleProps.add(prop);
1840 }
1841 });
1842 if (!isEmpty) {
1843 finalKeyframe['offset'] = time / this.duration;
1844 }
1845 finalKeyframes.push(finalKeyframe);
1846 });
1847 const preProps = preStyleProps.size ? iteratorToArray(preStyleProps.values()) : [];
1848 const postProps = postStyleProps.size ? iteratorToArray(postStyleProps.values()) : [];
1849 // special case for a 0-second animation (which is designed just to place styles onscreen)
1850 if (isEmpty) {
1851 const kf0 = finalKeyframes[0];
1852 const kf1 = copyObj(kf0);
1853 kf0['offset'] = 0;
1854 kf1['offset'] = 1;
1855 finalKeyframes = [kf0, kf1];
1856 }
1857 return createTimelineInstruction(this.element, finalKeyframes, preProps, postProps, this.duration, this.startTime, this.easing, false);
1858 }
1859}
1860class SubTimelineBuilder extends TimelineBuilder {
1861 constructor(driver, element, keyframes, preStyleProps, postStyleProps, timings, _stretchStartingKeyframe = false) {
1862 super(driver, element, timings.delay);
1863 this.keyframes = keyframes;
1864 this.preStyleProps = preStyleProps;
1865 this.postStyleProps = postStyleProps;
1866 this._stretchStartingKeyframe = _stretchStartingKeyframe;
1867 this.timings = { duration: timings.duration, delay: timings.delay, easing: timings.easing };
1868 }
1869 containsAnimation() {
1870 return this.keyframes.length > 1;
1871 }
1872 buildKeyframes() {
1873 let keyframes = this.keyframes;
1874 let { delay, duration, easing } = this.timings;
1875 if (this._stretchStartingKeyframe && delay) {
1876 const newKeyframes = [];
1877 const totalTime = duration + delay;
1878 const startingGap = delay / totalTime;
1879 // the original starting keyframe now starts once the delay is done
1880 const newFirstKeyframe = copyStyles(keyframes[0], false);
1881 newFirstKeyframe['offset'] = 0;
1882 newKeyframes.push(newFirstKeyframe);
1883 const oldFirstKeyframe = copyStyles(keyframes[0], false);
1884 oldFirstKeyframe['offset'] = roundOffset(startingGap);
1885 newKeyframes.push(oldFirstKeyframe);
1886 /*
1887 When the keyframe is stretched then it means that the delay before the animation
1888 starts is gone. Instead the first keyframe is placed at the start of the animation
1889 and it is then copied to where it starts when the original delay is over. This basically
1890 means nothing animates during that delay, but the styles are still renderered. For this
1891 to work the original offset values that exist in the original keyframes must be "warped"
1892 so that they can take the new keyframe + delay into account.
1893
1894 delay=1000, duration=1000, keyframes = 0 .5 1
1895
1896 turns into
1897
1898 delay=0, duration=2000, keyframes = 0 .33 .66 1
1899 */
1900 // offsets between 1 ... n -1 are all warped by the keyframe stretch
1901 const limit = keyframes.length - 1;
1902 for (let i = 1; i <= limit; i++) {
1903 let kf = copyStyles(keyframes[i], false);
1904 const oldOffset = kf['offset'];
1905 const timeAtKeyframe = delay + oldOffset * duration;
1906 kf['offset'] = roundOffset(timeAtKeyframe / totalTime);
1907 newKeyframes.push(kf);
1908 }
1909 // the new starting keyframe should be added at the start
1910 duration = totalTime;
1911 delay = 0;
1912 easing = '';
1913 keyframes = newKeyframes;
1914 }
1915 return createTimelineInstruction(this.element, keyframes, this.preStyleProps, this.postStyleProps, duration, delay, easing, true);
1916 }
1917}
1918function roundOffset(offset, decimalPoints = 3) {
1919 const mult = Math.pow(10, decimalPoints - 1);
1920 return Math.round(offset * mult) / mult;
1921}
1922function flattenStyles(input, allStyles) {
1923 const styles = {};
1924 let allProperties;
1925 input.forEach(token => {
1926 if (token === '*') {
1927 allProperties = allProperties || Object.keys(allStyles);
1928 allProperties.forEach(prop => {
1929 styles[prop] = AUTO_STYLE;
1930 });
1931 }
1932 else {
1933 copyStyles(token, false, styles);
1934 }
1935 });
1936 return styles;
1937}
1938
1939class Animation {
1940 constructor(_driver, input) {
1941 this._driver = _driver;
1942 const errors = [];
1943 const ast = buildAnimationAst(_driver, input, errors);
1944 if (errors.length) {
1945 const errorMessage = `animation validation failed:\n${errors.join('\n')}`;
1946 throw new Error(errorMessage);
1947 }
1948 this._animationAst = ast;
1949 }
1950 buildTimelines(element, startingStyles, destinationStyles, options, subInstructions) {
1951 const start = Array.isArray(startingStyles) ? normalizeStyles(startingStyles) :
1952 startingStyles;
1953 const dest = Array.isArray(destinationStyles) ? normalizeStyles(destinationStyles) :
1954 destinationStyles;
1955 const errors = [];
1956 subInstructions = subInstructions || new ElementInstructionMap();
1957 const result = buildAnimationTimelines(this._driver, element, this._animationAst, ENTER_CLASSNAME, LEAVE_CLASSNAME, start, dest, options, subInstructions, errors);
1958 if (errors.length) {
1959 const errorMessage = `animation building failed:\n${errors.join('\n')}`;
1960 throw new Error(errorMessage);
1961 }
1962 return result;
1963 }
1964}
1965
1966/**
1967 * @license
1968 * Copyright Google LLC All Rights Reserved.
1969 *
1970 * Use of this source code is governed by an MIT-style license that can be
1971 * found in the LICENSE file at https://angular.io/license
1972 */
1973/**
1974 * @publicApi
1975 */
1976class AnimationStyleNormalizer {
1977}
1978/**
1979 * @publicApi
1980 */
1981class NoopAnimationStyleNormalizer {
1982 normalizePropertyName(propertyName, errors) {
1983 return propertyName;
1984 }
1985 normalizeStyleValue(userProvidedProperty, normalizedProperty, value, errors) {
1986 return value;
1987 }
1988}
1989
1990/**
1991 * @license
1992 * Copyright Google LLC All Rights Reserved.
1993 *
1994 * Use of this source code is governed by an MIT-style license that can be
1995 * found in the LICENSE file at https://angular.io/license
1996 */
1997class WebAnimationsStyleNormalizer extends AnimationStyleNormalizer {
1998 normalizePropertyName(propertyName, errors) {
1999 return dashCaseToCamelCase(propertyName);
2000 }
2001 normalizeStyleValue(userProvidedProperty, normalizedProperty, value, errors) {
2002 let unit = '';
2003 const strVal = value.toString().trim();
2004 if (DIMENSIONAL_PROP_MAP[normalizedProperty] && value !== 0 && value !== '0') {
2005 if (typeof value === 'number') {
2006 unit = 'px';
2007 }
2008 else {
2009 const valAndSuffixMatch = value.match(/^[+-]?[\d\.]+([a-z]*)$/);
2010 if (valAndSuffixMatch && valAndSuffixMatch[1].length == 0) {
2011 errors.push(`Please provide a CSS unit value for ${userProvidedProperty}:${value}`);
2012 }
2013 }
2014 }
2015 return strVal + unit;
2016 }
2017}
2018const ɵ0$1 = () => makeBooleanMap('width,height,minWidth,minHeight,maxWidth,maxHeight,left,top,bottom,right,fontSize,outlineWidth,outlineOffset,paddingTop,paddingLeft,paddingBottom,paddingRight,marginTop,marginLeft,marginBottom,marginRight,borderRadius,borderWidth,borderTopWidth,borderLeftWidth,borderRightWidth,borderBottomWidth,textIndent,perspective'
2019 .split(','));
2020const DIMENSIONAL_PROP_MAP = (ɵ0$1)();
2021function makeBooleanMap(keys) {
2022 const map = {};
2023 keys.forEach(key => map[key] = true);
2024 return map;
2025}
2026
2027function createTransitionInstruction(element, triggerName, fromState, toState, isRemovalTransition, fromStyles, toStyles, timelines, queriedElements, preStyleProps, postStyleProps, totalTime, errors) {
2028 return {
2029 type: 0 /* TransitionAnimation */,
2030 element,
2031 triggerName,
2032 isRemovalTransition,
2033 fromState,
2034 fromStyles,
2035 toState,
2036 toStyles,
2037 timelines,
2038 queriedElements,
2039 preStyleProps,
2040 postStyleProps,
2041 totalTime,
2042 errors
2043 };
2044}
2045
2046const EMPTY_OBJECT = {};
2047class AnimationTransitionFactory {
2048 constructor(_triggerName, ast, _stateStyles) {
2049 this._triggerName = _triggerName;
2050 this.ast = ast;
2051 this._stateStyles = _stateStyles;
2052 }
2053 match(currentState, nextState, element, params) {
2054 return oneOrMoreTransitionsMatch(this.ast.matchers, currentState, nextState, element, params);
2055 }
2056 buildStyles(stateName, params, errors) {
2057 const backupStateStyler = this._stateStyles['*'];
2058 const stateStyler = this._stateStyles[stateName];
2059 const backupStyles = backupStateStyler ? backupStateStyler.buildStyles(params, errors) : {};
2060 return stateStyler ? stateStyler.buildStyles(params, errors) : backupStyles;
2061 }
2062 build(driver, element, currentState, nextState, enterClassName, leaveClassName, currentOptions, nextOptions, subInstructions, skipAstBuild) {
2063 const errors = [];
2064 const transitionAnimationParams = this.ast.options && this.ast.options.params || EMPTY_OBJECT;
2065 const currentAnimationParams = currentOptions && currentOptions.params || EMPTY_OBJECT;
2066 const currentStateStyles = this.buildStyles(currentState, currentAnimationParams, errors);
2067 const nextAnimationParams = nextOptions && nextOptions.params || EMPTY_OBJECT;
2068 const nextStateStyles = this.buildStyles(nextState, nextAnimationParams, errors);
2069 const queriedElements = new Set();
2070 const preStyleMap = new Map();
2071 const postStyleMap = new Map();
2072 const isRemoval = nextState === 'void';
2073 const animationOptions = { params: Object.assign(Object.assign({}, transitionAnimationParams), nextAnimationParams) };
2074 const timelines = skipAstBuild ?
2075 [] :
2076 buildAnimationTimelines(driver, element, this.ast.animation, enterClassName, leaveClassName, currentStateStyles, nextStateStyles, animationOptions, subInstructions, errors);
2077 let totalTime = 0;
2078 timelines.forEach(tl => {
2079 totalTime = Math.max(tl.duration + tl.delay, totalTime);
2080 });
2081 if (errors.length) {
2082 return createTransitionInstruction(element, this._triggerName, currentState, nextState, isRemoval, currentStateStyles, nextStateStyles, [], [], preStyleMap, postStyleMap, totalTime, errors);
2083 }
2084 timelines.forEach(tl => {
2085 const elm = tl.element;
2086 const preProps = getOrSetAsInMap(preStyleMap, elm, {});
2087 tl.preStyleProps.forEach(prop => preProps[prop] = true);
2088 const postProps = getOrSetAsInMap(postStyleMap, elm, {});
2089 tl.postStyleProps.forEach(prop => postProps[prop] = true);
2090 if (elm !== element) {
2091 queriedElements.add(elm);
2092 }
2093 });
2094 const queriedElementsList = iteratorToArray(queriedElements.values());
2095 return createTransitionInstruction(element, this._triggerName, currentState, nextState, isRemoval, currentStateStyles, nextStateStyles, timelines, queriedElementsList, preStyleMap, postStyleMap, totalTime);
2096 }
2097}
2098function oneOrMoreTransitionsMatch(matchFns, currentState, nextState, element, params) {
2099 return matchFns.some(fn => fn(currentState, nextState, element, params));
2100}
2101class AnimationStateStyles {
2102 constructor(styles, defaultParams, normalizer) {
2103 this.styles = styles;
2104 this.defaultParams = defaultParams;
2105 this.normalizer = normalizer;
2106 }
2107 buildStyles(params, errors) {
2108 const finalStyles = {};
2109 const combinedParams = copyObj(this.defaultParams);
2110 Object.keys(params).forEach(key => {
2111 const value = params[key];
2112 if (value != null) {
2113 combinedParams[key] = value;
2114 }
2115 });
2116 this.styles.styles.forEach(value => {
2117 if (typeof value !== 'string') {
2118 const styleObj = value;
2119 Object.keys(styleObj).forEach(prop => {
2120 let val = styleObj[prop];
2121 if (val.length > 1) {
2122 val = interpolateParams(val, combinedParams, errors);
2123 }
2124 const normalizedProp = this.normalizer.normalizePropertyName(prop, errors);
2125 val = this.normalizer.normalizeStyleValue(prop, normalizedProp, val, errors);
2126 finalStyles[normalizedProp] = val;
2127 });
2128 }
2129 });
2130 return finalStyles;
2131 }
2132}
2133
2134function buildTrigger(name, ast, normalizer) {
2135 return new AnimationTrigger(name, ast, normalizer);
2136}
2137class AnimationTrigger {
2138 constructor(name, ast, _normalizer) {
2139 this.name = name;
2140 this.ast = ast;
2141 this._normalizer = _normalizer;
2142 this.transitionFactories = [];
2143 this.states = {};
2144 ast.states.forEach(ast => {
2145 const defaultParams = (ast.options && ast.options.params) || {};
2146 this.states[ast.name] = new AnimationStateStyles(ast.style, defaultParams, _normalizer);
2147 });
2148 balanceProperties(this.states, 'true', '1');
2149 balanceProperties(this.states, 'false', '0');
2150 ast.transitions.forEach(ast => {
2151 this.transitionFactories.push(new AnimationTransitionFactory(name, ast, this.states));
2152 });
2153 this.fallbackTransition = createFallbackTransition(name, this.states, this._normalizer);
2154 }
2155 get containsQueries() {
2156 return this.ast.queryCount > 0;
2157 }
2158 matchTransition(currentState, nextState, element, params) {
2159 const entry = this.transitionFactories.find(f => f.match(currentState, nextState, element, params));
2160 return entry || null;
2161 }
2162 matchStyles(currentState, params, errors) {
2163 return this.fallbackTransition.buildStyles(currentState, params, errors);
2164 }
2165}
2166function createFallbackTransition(triggerName, states, normalizer) {
2167 const matchers = [(fromState, toState) => true];
2168 const animation = { type: 2 /* Sequence */, steps: [], options: null };
2169 const transition = {
2170 type: 1 /* Transition */,
2171 animation,
2172 matchers,
2173 options: null,
2174 queryCount: 0,
2175 depCount: 0
2176 };
2177 return new AnimationTransitionFactory(triggerName, transition, states);
2178}
2179function balanceProperties(obj, key1, key2) {
2180 if (obj.hasOwnProperty(key1)) {
2181 if (!obj.hasOwnProperty(key2)) {
2182 obj[key2] = obj[key1];
2183 }
2184 }
2185 else if (obj.hasOwnProperty(key2)) {
2186 obj[key1] = obj[key2];
2187 }
2188}
2189
2190/**
2191 * @license
2192 * Copyright Google LLC All Rights Reserved.
2193 *
2194 * Use of this source code is governed by an MIT-style license that can be
2195 * found in the LICENSE file at https://angular.io/license
2196 */
2197const EMPTY_INSTRUCTION_MAP = new ElementInstructionMap();
2198class TimelineAnimationEngine {
2199 constructor(bodyNode, _driver, _normalizer) {
2200 this.bodyNode = bodyNode;
2201 this._driver = _driver;
2202 this._normalizer = _normalizer;
2203 this._animations = {};
2204 this._playersById = {};
2205 this.players = [];
2206 }
2207 register(id, metadata) {
2208 const errors = [];
2209 const ast = buildAnimationAst(this._driver, metadata, errors);
2210 if (errors.length) {
2211 throw new Error(`Unable to build the animation due to the following errors: ${errors.join('\n')}`);
2212 }
2213 else {
2214 this._animations[id] = ast;
2215 }
2216 }
2217 _buildPlayer(i, preStyles, postStyles) {
2218 const element = i.element;
2219 const keyframes = normalizeKeyframes(this._driver, this._normalizer, element, i.keyframes, preStyles, postStyles);
2220 return this._driver.animate(element, keyframes, i.duration, i.delay, i.easing, [], true);
2221 }
2222 create(id, element, options = {}) {
2223 const errors = [];
2224 const ast = this._animations[id];
2225 let instructions;
2226 const autoStylesMap = new Map();
2227 if (ast) {
2228 instructions = buildAnimationTimelines(this._driver, element, ast, ENTER_CLASSNAME, LEAVE_CLASSNAME, {}, {}, options, EMPTY_INSTRUCTION_MAP, errors);
2229 instructions.forEach(inst => {
2230 const styles = getOrSetAsInMap(autoStylesMap, inst.element, {});
2231 inst.postStyleProps.forEach(prop => styles[prop] = null);
2232 });
2233 }
2234 else {
2235 errors.push('The requested animation doesn\'t exist or has already been destroyed');
2236 instructions = [];
2237 }
2238 if (errors.length) {
2239 throw new Error(`Unable to create the animation due to the following errors: ${errors.join('\n')}`);
2240 }
2241 autoStylesMap.forEach((styles, element) => {
2242 Object.keys(styles).forEach(prop => {
2243 styles[prop] = this._driver.computeStyle(element, prop, AUTO_STYLE);
2244 });
2245 });
2246 const players = instructions.map(i => {
2247 const styles = autoStylesMap.get(i.element);
2248 return this._buildPlayer(i, {}, styles);
2249 });
2250 const player = optimizeGroupPlayer(players);
2251 this._playersById[id] = player;
2252 player.onDestroy(() => this.destroy(id));
2253 this.players.push(player);
2254 return player;
2255 }
2256 destroy(id) {
2257 const player = this._getPlayer(id);
2258 player.destroy();
2259 delete this._playersById[id];
2260 const index = this.players.indexOf(player);
2261 if (index >= 0) {
2262 this.players.splice(index, 1);
2263 }
2264 }
2265 _getPlayer(id) {
2266 const player = this._playersById[id];
2267 if (!player) {
2268 throw new Error(`Unable to find the timeline player referenced by ${id}`);
2269 }
2270 return player;
2271 }
2272 listen(id, element, eventName, callback) {
2273 // triggerName, fromState, toState are all ignored for timeline animations
2274 const baseEvent = makeAnimationEvent(element, '', '', '');
2275 listenOnPlayer(this._getPlayer(id), eventName, baseEvent, callback);
2276 return () => { };
2277 }
2278 command(id, element, command, args) {
2279 if (command == 'register') {
2280 this.register(id, args[0]);
2281 return;
2282 }
2283 if (command == 'create') {
2284 const options = (args[0] || {});
2285 this.create(id, element, options);
2286 return;
2287 }
2288 const player = this._getPlayer(id);
2289 switch (command) {
2290 case 'play':
2291 player.play();
2292 break;
2293 case 'pause':
2294 player.pause();
2295 break;
2296 case 'reset':
2297 player.reset();
2298 break;
2299 case 'restart':
2300 player.restart();
2301 break;
2302 case 'finish':
2303 player.finish();
2304 break;
2305 case 'init':
2306 player.init();
2307 break;
2308 case 'setPosition':
2309 player.setPosition(parseFloat(args[0]));
2310 break;
2311 case 'destroy':
2312 this.destroy(id);
2313 break;
2314 }
2315 }
2316}
2317
2318/**
2319 * @license
2320 * Copyright Google LLC All Rights Reserved.
2321 *
2322 * Use of this source code is governed by an MIT-style license that can be
2323 * found in the LICENSE file at https://angular.io/license
2324 */
2325const QUEUED_CLASSNAME = 'ng-animate-queued';
2326const QUEUED_SELECTOR = '.ng-animate-queued';
2327const DISABLED_CLASSNAME = 'ng-animate-disabled';
2328const DISABLED_SELECTOR = '.ng-animate-disabled';
2329const STAR_CLASSNAME = 'ng-star-inserted';
2330const STAR_SELECTOR = '.ng-star-inserted';
2331const EMPTY_PLAYER_ARRAY = [];
2332const NULL_REMOVAL_STATE = {
2333 namespaceId: '',
2334 setForRemoval: false,
2335 setForMove: false,
2336 hasAnimation: false,
2337 removedBeforeQueried: false
2338};
2339const NULL_REMOVED_QUERIED_STATE = {
2340 namespaceId: '',
2341 setForMove: false,
2342 setForRemoval: false,
2343 hasAnimation: false,
2344 removedBeforeQueried: true
2345};
2346const REMOVAL_FLAG = '__ng_removed';
2347class StateValue {
2348 constructor(input, namespaceId = '') {
2349 this.namespaceId = namespaceId;
2350 const isObj = input && input.hasOwnProperty('value');
2351 const value = isObj ? input['value'] : input;
2352 this.value = normalizeTriggerValue(value);
2353 if (isObj) {
2354 const options = copyObj(input);
2355 delete options['value'];
2356 this.options = options;
2357 }
2358 else {
2359 this.options = {};
2360 }
2361 if (!this.options.params) {
2362 this.options.params = {};
2363 }
2364 }
2365 get params() {
2366 return this.options.params;
2367 }
2368 absorbOptions(options) {
2369 const newParams = options.params;
2370 if (newParams) {
2371 const oldParams = this.options.params;
2372 Object.keys(newParams).forEach(prop => {
2373 if (oldParams[prop] == null) {
2374 oldParams[prop] = newParams[prop];
2375 }
2376 });
2377 }
2378 }
2379}
2380const VOID_VALUE = 'void';
2381const DEFAULT_STATE_VALUE = new StateValue(VOID_VALUE);
2382class AnimationTransitionNamespace {
2383 constructor(id, hostElement, _engine) {
2384 this.id = id;
2385 this.hostElement = hostElement;
2386 this._engine = _engine;
2387 this.players = [];
2388 this._triggers = {};
2389 this._queue = [];
2390 this._elementListeners = new Map();
2391 this._hostClassName = 'ng-tns-' + id;
2392 addClass(hostElement, this._hostClassName);
2393 }
2394 listen(element, name, phase, callback) {
2395 if (!this._triggers.hasOwnProperty(name)) {
2396 throw new Error(`Unable to listen on the animation trigger event "${phase}" because the animation trigger "${name}" doesn\'t exist!`);
2397 }
2398 if (phase == null || phase.length == 0) {
2399 throw new Error(`Unable to listen on the animation trigger "${name}" because the provided event is undefined!`);
2400 }
2401 if (!isTriggerEventValid(phase)) {
2402 throw new Error(`The provided animation trigger event "${phase}" for the animation trigger "${name}" is not supported!`);
2403 }
2404 const listeners = getOrSetAsInMap(this._elementListeners, element, []);
2405 const data = { name, phase, callback };
2406 listeners.push(data);
2407 const triggersWithStates = getOrSetAsInMap(this._engine.statesByElement, element, {});
2408 if (!triggersWithStates.hasOwnProperty(name)) {
2409 addClass(element, NG_TRIGGER_CLASSNAME);
2410 addClass(element, NG_TRIGGER_CLASSNAME + '-' + name);
2411 triggersWithStates[name] = DEFAULT_STATE_VALUE;
2412 }
2413 return () => {
2414 // the event listener is removed AFTER the flush has occurred such
2415 // that leave animations callbacks can fire (otherwise if the node
2416 // is removed in between then the listeners would be deregistered)
2417 this._engine.afterFlush(() => {
2418 const index = listeners.indexOf(data);
2419 if (index >= 0) {
2420 listeners.splice(index, 1);
2421 }
2422 if (!this._triggers[name]) {
2423 delete triggersWithStates[name];
2424 }
2425 });
2426 };
2427 }
2428 register(name, ast) {
2429 if (this._triggers[name]) {
2430 // throw
2431 return false;
2432 }
2433 else {
2434 this._triggers[name] = ast;
2435 return true;
2436 }
2437 }
2438 _getTrigger(name) {
2439 const trigger = this._triggers[name];
2440 if (!trigger) {
2441 throw new Error(`The provided animation trigger "${name}" has not been registered!`);
2442 }
2443 return trigger;
2444 }
2445 trigger(element, triggerName, value, defaultToFallback = true) {
2446 const trigger = this._getTrigger(triggerName);
2447 const player = new TransitionAnimationPlayer(this.id, triggerName, element);
2448 let triggersWithStates = this._engine.statesByElement.get(element);
2449 if (!triggersWithStates) {
2450 addClass(element, NG_TRIGGER_CLASSNAME);
2451 addClass(element, NG_TRIGGER_CLASSNAME + '-' + triggerName);
2452 this._engine.statesByElement.set(element, triggersWithStates = {});
2453 }
2454 let fromState = triggersWithStates[triggerName];
2455 const toState = new StateValue(value, this.id);
2456 const isObj = value && value.hasOwnProperty('value');
2457 if (!isObj && fromState) {
2458 toState.absorbOptions(fromState.options);
2459 }
2460 triggersWithStates[triggerName] = toState;
2461 if (!fromState) {
2462 fromState = DEFAULT_STATE_VALUE;
2463 }
2464 const isRemoval = toState.value === VOID_VALUE;
2465 // normally this isn't reached by here, however, if an object expression
2466 // is passed in then it may be a new object each time. Comparing the value
2467 // is important since that will stay the same despite there being a new object.
2468 // The removal arc here is special cased because the same element is triggered
2469 // twice in the event that it contains animations on the outer/inner portions
2470 // of the host container
2471 if (!isRemoval && fromState.value === toState.value) {
2472 // this means that despite the value not changing, some inner params
2473 // have changed which means that the animation final styles need to be applied
2474 if (!objEquals(fromState.params, toState.params)) {
2475 const errors = [];
2476 const fromStyles = trigger.matchStyles(fromState.value, fromState.params, errors);
2477 const toStyles = trigger.matchStyles(toState.value, toState.params, errors);
2478 if (errors.length) {
2479 this._engine.reportError(errors);
2480 }
2481 else {
2482 this._engine.afterFlush(() => {
2483 eraseStyles(element, fromStyles);
2484 setStyles(element, toStyles);
2485 });
2486 }
2487 }
2488 return;
2489 }
2490 const playersOnElement = getOrSetAsInMap(this._engine.playersByElement, element, []);
2491 playersOnElement.forEach(player => {
2492 // only remove the player if it is queued on the EXACT same trigger/namespace
2493 // we only also deal with queued players here because if the animation has
2494 // started then we want to keep the player alive until the flush happens
2495 // (which is where the previousPlayers are passed into the new palyer)
2496 if (player.namespaceId == this.id && player.triggerName == triggerName && player.queued) {
2497 player.destroy();
2498 }
2499 });
2500 let transition = trigger.matchTransition(fromState.value, toState.value, element, toState.params);
2501 let isFallbackTransition = false;
2502 if (!transition) {
2503 if (!defaultToFallback)
2504 return;
2505 transition = trigger.fallbackTransition;
2506 isFallbackTransition = true;
2507 }
2508 this._engine.totalQueuedPlayers++;
2509 this._queue.push({ element, triggerName, transition, fromState, toState, player, isFallbackTransition });
2510 if (!isFallbackTransition) {
2511 addClass(element, QUEUED_CLASSNAME);
2512 player.onStart(() => {
2513 removeClass(element, QUEUED_CLASSNAME);
2514 });
2515 }
2516 player.onDone(() => {
2517 let index = this.players.indexOf(player);
2518 if (index >= 0) {
2519 this.players.splice(index, 1);
2520 }
2521 const players = this._engine.playersByElement.get(element);
2522 if (players) {
2523 let index = players.indexOf(player);
2524 if (index >= 0) {
2525 players.splice(index, 1);
2526 }
2527 }
2528 });
2529 this.players.push(player);
2530 playersOnElement.push(player);
2531 return player;
2532 }
2533 deregister(name) {
2534 delete this._triggers[name];
2535 this._engine.statesByElement.forEach((stateMap, element) => {
2536 delete stateMap[name];
2537 });
2538 this._elementListeners.forEach((listeners, element) => {
2539 this._elementListeners.set(element, listeners.filter(entry => {
2540 return entry.name != name;
2541 }));
2542 });
2543 }
2544 clearElementCache(element) {
2545 this._engine.statesByElement.delete(element);
2546 this._elementListeners.delete(element);
2547 const elementPlayers = this._engine.playersByElement.get(element);
2548 if (elementPlayers) {
2549 elementPlayers.forEach(player => player.destroy());
2550 this._engine.playersByElement.delete(element);
2551 }
2552 }
2553 _signalRemovalForInnerTriggers(rootElement, context) {
2554 const elements = this._engine.driver.query(rootElement, NG_TRIGGER_SELECTOR, true);
2555 // emulate a leave animation for all inner nodes within this node.
2556 // If there are no animations found for any of the nodes then clear the cache
2557 // for the element.
2558 elements.forEach(elm => {
2559 // this means that an inner remove() operation has already kicked off
2560 // the animation on this element...
2561 if (elm[REMOVAL_FLAG])
2562 return;
2563 const namespaces = this._engine.fetchNamespacesByElement(elm);
2564 if (namespaces.size) {
2565 namespaces.forEach(ns => ns.triggerLeaveAnimation(elm, context, false, true));
2566 }
2567 else {
2568 this.clearElementCache(elm);
2569 }
2570 });
2571 // If the child elements were removed along with the parent, their animations might not
2572 // have completed. Clear all the elements from the cache so we don't end up with a memory leak.
2573 this._engine.afterFlushAnimationsDone(() => elements.forEach(elm => this.clearElementCache(elm)));
2574 }
2575 triggerLeaveAnimation(element, context, destroyAfterComplete, defaultToFallback) {
2576 const triggerStates = this._engine.statesByElement.get(element);
2577 if (triggerStates) {
2578 const players = [];
2579 Object.keys(triggerStates).forEach(triggerName => {
2580 // this check is here in the event that an element is removed
2581 // twice (both on the host level and the component level)
2582 if (this._triggers[triggerName]) {
2583 const player = this.trigger(element, triggerName, VOID_VALUE, defaultToFallback);
2584 if (player) {
2585 players.push(player);
2586 }
2587 }
2588 });
2589 if (players.length) {
2590 this._engine.markElementAsRemoved(this.id, element, true, context);
2591 if (destroyAfterComplete) {
2592 optimizeGroupPlayer(players).onDone(() => this._engine.processLeaveNode(element));
2593 }
2594 return true;
2595 }
2596 }
2597 return false;
2598 }
2599 prepareLeaveAnimationListeners(element) {
2600 const listeners = this._elementListeners.get(element);
2601 const elementStates = this._engine.statesByElement.get(element);
2602 // if this statement fails then it means that the element was picked up
2603 // by an earlier flush (or there are no listeners at all to track the leave).
2604 if (listeners && elementStates) {
2605 const visitedTriggers = new Set();
2606 listeners.forEach(listener => {
2607 const triggerName = listener.name;
2608 if (visitedTriggers.has(triggerName))
2609 return;
2610 visitedTriggers.add(triggerName);
2611 const trigger = this._triggers[triggerName];
2612 const transition = trigger.fallbackTransition;
2613 const fromState = elementStates[triggerName] || DEFAULT_STATE_VALUE;
2614 const toState = new StateValue(VOID_VALUE);
2615 const player = new TransitionAnimationPlayer(this.id, triggerName, element);
2616 this._engine.totalQueuedPlayers++;
2617 this._queue.push({
2618 element,
2619 triggerName,
2620 transition,
2621 fromState,
2622 toState,
2623 player,
2624 isFallbackTransition: true
2625 });
2626 });
2627 }
2628 }
2629 removeNode(element, context) {
2630 const engine = this._engine;
2631 if (element.childElementCount) {
2632 this._signalRemovalForInnerTriggers(element, context);
2633 }
2634 // this means that a * => VOID animation was detected and kicked off
2635 if (this.triggerLeaveAnimation(element, context, true))
2636 return;
2637 // find the player that is animating and make sure that the
2638 // removal is delayed until that player has completed
2639 let containsPotentialParentTransition = false;
2640 if (engine.totalAnimations) {
2641 const currentPlayers = engine.players.length ? engine.playersByQueriedElement.get(element) : [];
2642 // when this `if statement` does not continue forward it means that
2643 // a previous animation query has selected the current element and
2644 // is animating it. In this situation want to continue forwards and
2645 // allow the element to be queued up for animation later.
2646 if (currentPlayers && currentPlayers.length) {
2647 containsPotentialParentTransition = true;
2648 }
2649 else {
2650 let parent = element;
2651 while (parent = parent.parentNode) {
2652 const triggers = engine.statesByElement.get(parent);
2653 if (triggers) {
2654 containsPotentialParentTransition = true;
2655 break;
2656 }
2657 }
2658 }
2659 }
2660 // at this stage we know that the element will either get removed
2661 // during flush or will be picked up by a parent query. Either way
2662 // we need to fire the listeners for this element when it DOES get
2663 // removed (once the query parent animation is done or after flush)
2664 this.prepareLeaveAnimationListeners(element);
2665 // whether or not a parent has an animation we need to delay the deferral of the leave
2666 // operation until we have more information (which we do after flush() has been called)
2667 if (containsPotentialParentTransition) {
2668 engine.markElementAsRemoved(this.id, element, false, context);
2669 }
2670 else {
2671 const removalFlag = element[REMOVAL_FLAG];
2672 if (!removalFlag || removalFlag === NULL_REMOVAL_STATE) {
2673 // we do this after the flush has occurred such
2674 // that the callbacks can be fired
2675 engine.afterFlush(() => this.clearElementCache(element));
2676 engine.destroyInnerAnimations(element);
2677 engine._onRemovalComplete(element, context);
2678 }
2679 }
2680 }
2681 insertNode(element, parent) {
2682 addClass(element, this._hostClassName);
2683 }
2684 drainQueuedTransitions(microtaskId) {
2685 const instructions = [];
2686 this._queue.forEach(entry => {
2687 const player = entry.player;
2688 if (player.destroyed)
2689 return;
2690 const element = entry.element;
2691 const listeners = this._elementListeners.get(element);
2692 if (listeners) {
2693 listeners.forEach((listener) => {
2694 if (listener.name == entry.triggerName) {
2695 const baseEvent = makeAnimationEvent(element, entry.triggerName, entry.fromState.value, entry.toState.value);
2696 baseEvent['_data'] = microtaskId;
2697 listenOnPlayer(entry.player, listener.phase, baseEvent, listener.callback);
2698 }
2699 });
2700 }
2701 if (player.markedForDestroy) {
2702 this._engine.afterFlush(() => {
2703 // now we can destroy the element properly since the event listeners have
2704 // been bound to the player
2705 player.destroy();
2706 });
2707 }
2708 else {
2709 instructions.push(entry);
2710 }
2711 });
2712 this._queue = [];
2713 return instructions.sort((a, b) => {
2714 // if depCount == 0 them move to front
2715 // otherwise if a contains b then move back
2716 const d0 = a.transition.ast.depCount;
2717 const d1 = b.transition.ast.depCount;
2718 if (d0 == 0 || d1 == 0) {
2719 return d0 - d1;
2720 }
2721 return this._engine.driver.containsElement(a.element, b.element) ? 1 : -1;
2722 });
2723 }
2724 destroy(context) {
2725 this.players.forEach(p => p.destroy());
2726 this._signalRemovalForInnerTriggers(this.hostElement, context);
2727 }
2728 elementContainsData(element) {
2729 let containsData = false;
2730 if (this._elementListeners.has(element))
2731 containsData = true;
2732 containsData =
2733 (this._queue.find(entry => entry.element === element) ? true : false) || containsData;
2734 return containsData;
2735 }
2736}
2737class TransitionAnimationEngine {
2738 constructor(bodyNode, driver, _normalizer) {
2739 this.bodyNode = bodyNode;
2740 this.driver = driver;
2741 this._normalizer = _normalizer;
2742 this.players = [];
2743 this.newHostElements = new Map();
2744 this.playersByElement = new Map();
2745 this.playersByQueriedElement = new Map();
2746 this.statesByElement = new Map();
2747 this.disabledNodes = new Set();
2748 this.totalAnimations = 0;
2749 this.totalQueuedPlayers = 0;
2750 this._namespaceLookup = {};
2751 this._namespaceList = [];
2752 this._flushFns = [];
2753 this._whenQuietFns = [];
2754 this.namespacesByHostElement = new Map();
2755 this.collectedEnterElements = [];
2756 this.collectedLeaveElements = [];
2757 // this method is designed to be overridden by the code that uses this engine
2758 this.onRemovalComplete = (element, context) => { };
2759 }
2760 /** @internal */
2761 _onRemovalComplete(element, context) {
2762 this.onRemovalComplete(element, context);
2763 }
2764 get queuedPlayers() {
2765 const players = [];
2766 this._namespaceList.forEach(ns => {
2767 ns.players.forEach(player => {
2768 if (player.queued) {
2769 players.push(player);
2770 }
2771 });
2772 });
2773 return players;
2774 }
2775 createNamespace(namespaceId, hostElement) {
2776 const ns = new AnimationTransitionNamespace(namespaceId, hostElement, this);
2777 if (this.bodyNode && this.driver.containsElement(this.bodyNode, hostElement)) {
2778 this._balanceNamespaceList(ns, hostElement);
2779 }
2780 else {
2781 // defer this later until flush during when the host element has
2782 // been inserted so that we know exactly where to place it in
2783 // the namespace list
2784 this.newHostElements.set(hostElement, ns);
2785 // given that this host element is apart of the animation code, it
2786 // may or may not be inserted by a parent node that is of an
2787 // animation renderer type. If this happens then we can still have
2788 // access to this item when we query for :enter nodes. If the parent
2789 // is a renderer then the set data-structure will normalize the entry
2790 this.collectEnterElement(hostElement);
2791 }
2792 return this._namespaceLookup[namespaceId] = ns;
2793 }
2794 _balanceNamespaceList(ns, hostElement) {
2795 const limit = this._namespaceList.length - 1;
2796 if (limit >= 0) {
2797 let found = false;
2798 for (let i = limit; i >= 0; i--) {
2799 const nextNamespace = this._namespaceList[i];
2800 if (this.driver.containsElement(nextNamespace.hostElement, hostElement)) {
2801 this._namespaceList.splice(i + 1, 0, ns);
2802 found = true;
2803 break;
2804 }
2805 }
2806 if (!found) {
2807 this._namespaceList.splice(0, 0, ns);
2808 }
2809 }
2810 else {
2811 this._namespaceList.push(ns);
2812 }
2813 this.namespacesByHostElement.set(hostElement, ns);
2814 return ns;
2815 }
2816 register(namespaceId, hostElement) {
2817 let ns = this._namespaceLookup[namespaceId];
2818 if (!ns) {
2819 ns = this.createNamespace(namespaceId, hostElement);
2820 }
2821 return ns;
2822 }
2823 registerTrigger(namespaceId, name, trigger) {
2824 let ns = this._namespaceLookup[namespaceId];
2825 if (ns && ns.register(name, trigger)) {
2826 this.totalAnimations++;
2827 }
2828 }
2829 destroy(namespaceId, context) {
2830 if (!namespaceId)
2831 return;
2832 const ns = this._fetchNamespace(namespaceId);
2833 this.afterFlush(() => {
2834 this.namespacesByHostElement.delete(ns.hostElement);
2835 delete this._namespaceLookup[namespaceId];
2836 const index = this._namespaceList.indexOf(ns);
2837 if (index >= 0) {
2838 this._namespaceList.splice(index, 1);
2839 }
2840 });
2841 this.afterFlushAnimationsDone(() => ns.destroy(context));
2842 }
2843 _fetchNamespace(id) {
2844 return this._namespaceLookup[id];
2845 }
2846 fetchNamespacesByElement(element) {
2847 // normally there should only be one namespace per element, however
2848 // if @triggers are placed on both the component element and then
2849 // its host element (within the component code) then there will be
2850 // two namespaces returned. We use a set here to simply the dedupe
2851 // of namespaces incase there are multiple triggers both the elm and host
2852 const namespaces = new Set();
2853 const elementStates = this.statesByElement.get(element);
2854 if (elementStates) {
2855 const keys = Object.keys(elementStates);
2856 for (let i = 0; i < keys.length; i++) {
2857 const nsId = elementStates[keys[i]].namespaceId;
2858 if (nsId) {
2859 const ns = this._fetchNamespace(nsId);
2860 if (ns) {
2861 namespaces.add(ns);
2862 }
2863 }
2864 }
2865 }
2866 return namespaces;
2867 }
2868 trigger(namespaceId, element, name, value) {
2869 if (isElementNode(element)) {
2870 const ns = this._fetchNamespace(namespaceId);
2871 if (ns) {
2872 ns.trigger(element, name, value);
2873 return true;
2874 }
2875 }
2876 return false;
2877 }
2878 insertNode(namespaceId, element, parent, insertBefore) {
2879 if (!isElementNode(element))
2880 return;
2881 // special case for when an element is removed and reinserted (move operation)
2882 // when this occurs we do not want to use the element for deletion later
2883 const details = element[REMOVAL_FLAG];
2884 if (details && details.setForRemoval) {
2885 details.setForRemoval = false;
2886 details.setForMove = true;
2887 const index = this.collectedLeaveElements.indexOf(element);
2888 if (index >= 0) {
2889 this.collectedLeaveElements.splice(index, 1);
2890 }
2891 }
2892 // in the event that the namespaceId is blank then the caller
2893 // code does not contain any animation code in it, but it is
2894 // just being called so that the node is marked as being inserted
2895 if (namespaceId) {
2896 const ns = this._fetchNamespace(namespaceId);
2897 // This if-statement is a workaround for router issue #21947.
2898 // The router sometimes hits a race condition where while a route
2899 // is being instantiated a new navigation arrives, triggering leave
2900 // animation of DOM that has not been fully initialized, until this
2901 // is resolved, we need to handle the scenario when DOM is not in a
2902 // consistent state during the animation.
2903 if (ns) {
2904 ns.insertNode(element, parent);
2905 }
2906 }
2907 // only *directives and host elements are inserted before
2908 if (insertBefore) {
2909 this.collectEnterElement(element);
2910 }
2911 }
2912 collectEnterElement(element) {
2913 this.collectedEnterElements.push(element);
2914 }
2915 markElementAsDisabled(element, value) {
2916 if (value) {
2917 if (!this.disabledNodes.has(element)) {
2918 this.disabledNodes.add(element);
2919 addClass(element, DISABLED_CLASSNAME);
2920 }
2921 }
2922 else if (this.disabledNodes.has(element)) {
2923 this.disabledNodes.delete(element);
2924 removeClass(element, DISABLED_CLASSNAME);
2925 }
2926 }
2927 removeNode(namespaceId, element, isHostElement, context) {
2928 if (isElementNode(element)) {
2929 const ns = namespaceId ? this._fetchNamespace(namespaceId) : null;
2930 if (ns) {
2931 ns.removeNode(element, context);
2932 }
2933 else {
2934 this.markElementAsRemoved(namespaceId, element, false, context);
2935 }
2936 if (isHostElement) {
2937 const hostNS = this.namespacesByHostElement.get(element);
2938 if (hostNS && hostNS.id !== namespaceId) {
2939 hostNS.removeNode(element, context);
2940 }
2941 }
2942 }
2943 else {
2944 this._onRemovalComplete(element, context);
2945 }
2946 }
2947 markElementAsRemoved(namespaceId, element, hasAnimation, context) {
2948 this.collectedLeaveElements.push(element);
2949 element[REMOVAL_FLAG] =
2950 { namespaceId, setForRemoval: context, hasAnimation, removedBeforeQueried: false };
2951 }
2952 listen(namespaceId, element, name, phase, callback) {
2953 if (isElementNode(element)) {
2954 return this._fetchNamespace(namespaceId).listen(element, name, phase, callback);
2955 }
2956 return () => { };
2957 }
2958 _buildInstruction(entry, subTimelines, enterClassName, leaveClassName, skipBuildAst) {
2959 return entry.transition.build(this.driver, entry.element, entry.fromState.value, entry.toState.value, enterClassName, leaveClassName, entry.fromState.options, entry.toState.options, subTimelines, skipBuildAst);
2960 }
2961 destroyInnerAnimations(containerElement) {
2962 let elements = this.driver.query(containerElement, NG_TRIGGER_SELECTOR, true);
2963 elements.forEach(element => this.destroyActiveAnimationsForElement(element));
2964 if (this.playersByQueriedElement.size == 0)
2965 return;
2966 elements = this.driver.query(containerElement, NG_ANIMATING_SELECTOR, true);
2967 elements.forEach(element => this.finishActiveQueriedAnimationOnElement(element));
2968 }
2969 destroyActiveAnimationsForElement(element) {
2970 const players = this.playersByElement.get(element);
2971 if (players) {
2972 players.forEach(player => {
2973 // special case for when an element is set for destruction, but hasn't started.
2974 // in this situation we want to delay the destruction until the flush occurs
2975 // so that any event listeners attached to the player are triggered.
2976 if (player.queued) {
2977 player.markedForDestroy = true;
2978 }
2979 else {
2980 player.destroy();
2981 }
2982 });
2983 }
2984 }
2985 finishActiveQueriedAnimationOnElement(element) {
2986 const players = this.playersByQueriedElement.get(element);
2987 if (players) {
2988 players.forEach(player => player.finish());
2989 }
2990 }
2991 whenRenderingDone() {
2992 return new Promise(resolve => {
2993 if (this.players.length) {
2994 return optimizeGroupPlayer(this.players).onDone(() => resolve());
2995 }
2996 else {
2997 resolve();
2998 }
2999 });
3000 }
3001 processLeaveNode(element) {
3002 const details = element[REMOVAL_FLAG];
3003 if (details && details.setForRemoval) {
3004 // this will prevent it from removing it twice
3005 element[REMOVAL_FLAG] = NULL_REMOVAL_STATE;
3006 if (details.namespaceId) {
3007 this.destroyInnerAnimations(element);
3008 const ns = this._fetchNamespace(details.namespaceId);
3009 if (ns) {
3010 ns.clearElementCache(element);
3011 }
3012 }
3013 this._onRemovalComplete(element, details.setForRemoval);
3014 }
3015 if (this.driver.matchesElement(element, DISABLED_SELECTOR)) {
3016 this.markElementAsDisabled(element, false);
3017 }
3018 this.driver.query(element, DISABLED_SELECTOR, true).forEach(node => {
3019 this.markElementAsDisabled(node, false);
3020 });
3021 }
3022 flush(microtaskId = -1) {
3023 let players = [];
3024 if (this.newHostElements.size) {
3025 this.newHostElements.forEach((ns, element) => this._balanceNamespaceList(ns, element));
3026 this.newHostElements.clear();
3027 }
3028 if (this.totalAnimations && this.collectedEnterElements.length) {
3029 for (let i = 0; i < this.collectedEnterElements.length; i++) {
3030 const elm = this.collectedEnterElements[i];
3031 addClass(elm, STAR_CLASSNAME);
3032 }
3033 }
3034 if (this._namespaceList.length &&
3035 (this.totalQueuedPlayers || this.collectedLeaveElements.length)) {
3036 const cleanupFns = [];
3037 try {
3038 players = this._flushAnimations(cleanupFns, microtaskId);
3039 }
3040 finally {
3041 for (let i = 0; i < cleanupFns.length; i++) {
3042 cleanupFns[i]();
3043 }
3044 }
3045 }
3046 else {
3047 for (let i = 0; i < this.collectedLeaveElements.length; i++) {
3048 const element = this.collectedLeaveElements[i];
3049 this.processLeaveNode(element);
3050 }
3051 }
3052 this.totalQueuedPlayers = 0;
3053 this.collectedEnterElements.length = 0;
3054 this.collectedLeaveElements.length = 0;
3055 this._flushFns.forEach(fn => fn());
3056 this._flushFns = [];
3057 if (this._whenQuietFns.length) {
3058 // we move these over to a variable so that
3059 // if any new callbacks are registered in another
3060 // flush they do not populate the existing set
3061 const quietFns = this._whenQuietFns;
3062 this._whenQuietFns = [];
3063 if (players.length) {
3064 optimizeGroupPlayer(players).onDone(() => {
3065 quietFns.forEach(fn => fn());
3066 });
3067 }
3068 else {
3069 quietFns.forEach(fn => fn());
3070 }
3071 }
3072 }
3073 reportError(errors) {
3074 throw new Error(`Unable to process animations due to the following failed trigger transitions\n ${errors.join('\n')}`);
3075 }
3076 _flushAnimations(cleanupFns, microtaskId) {
3077 const subTimelines = new ElementInstructionMap();
3078 const skippedPlayers = [];
3079 const skippedPlayersMap = new Map();
3080 const queuedInstructions = [];
3081 const queriedElements = new Map();
3082 const allPreStyleElements = new Map();
3083 const allPostStyleElements = new Map();
3084 const disabledElementsSet = new Set();
3085 this.disabledNodes.forEach(node => {
3086 disabledElementsSet.add(node);
3087 const nodesThatAreDisabled = this.driver.query(node, QUEUED_SELECTOR, true);
3088 for (let i = 0; i < nodesThatAreDisabled.length; i++) {
3089 disabledElementsSet.add(nodesThatAreDisabled[i]);
3090 }
3091 });
3092 const bodyNode = this.bodyNode;
3093 const allTriggerElements = Array.from(this.statesByElement.keys());
3094 const enterNodeMap = buildRootMap(allTriggerElements, this.collectedEnterElements);
3095 // this must occur before the instructions are built below such that
3096 // the :enter queries match the elements (since the timeline queries
3097 // are fired during instruction building).
3098 const enterNodeMapIds = new Map();
3099 let i = 0;
3100 enterNodeMap.forEach((nodes, root) => {
3101 const className = ENTER_CLASSNAME + i++;
3102 enterNodeMapIds.set(root, className);
3103 nodes.forEach(node => addClass(node, className));
3104 });
3105 const allLeaveNodes = [];
3106 const mergedLeaveNodes = new Set();
3107 const leaveNodesWithoutAnimations = new Set();
3108 for (let i = 0; i < this.collectedLeaveElements.length; i++) {
3109 const element = this.collectedLeaveElements[i];
3110 const details = element[REMOVAL_FLAG];
3111 if (details && details.setForRemoval) {
3112 allLeaveNodes.push(element);
3113 mergedLeaveNodes.add(element);
3114 if (details.hasAnimation) {
3115 this.driver.query(element, STAR_SELECTOR, true).forEach(elm => mergedLeaveNodes.add(elm));
3116 }
3117 else {
3118 leaveNodesWithoutAnimations.add(element);
3119 }
3120 }
3121 }
3122 const leaveNodeMapIds = new Map();
3123 const leaveNodeMap = buildRootMap(allTriggerElements, Array.from(mergedLeaveNodes));
3124 leaveNodeMap.forEach((nodes, root) => {
3125 const className = LEAVE_CLASSNAME + i++;
3126 leaveNodeMapIds.set(root, className);
3127 nodes.forEach(node => addClass(node, className));
3128 });
3129 cleanupFns.push(() => {
3130 enterNodeMap.forEach((nodes, root) => {
3131 const className = enterNodeMapIds.get(root);
3132 nodes.forEach(node => removeClass(node, className));
3133 });
3134 leaveNodeMap.forEach((nodes, root) => {
3135 const className = leaveNodeMapIds.get(root);
3136 nodes.forEach(node => removeClass(node, className));
3137 });
3138 allLeaveNodes.forEach(element => {
3139 this.processLeaveNode(element);
3140 });
3141 });
3142 const allPlayers = [];
3143 const erroneousTransitions = [];
3144 for (let i = this._namespaceList.length - 1; i >= 0; i--) {
3145 const ns = this._namespaceList[i];
3146 ns.drainQueuedTransitions(microtaskId).forEach(entry => {
3147 const player = entry.player;
3148 const element = entry.element;
3149 allPlayers.push(player);
3150 if (this.collectedEnterElements.length) {
3151 const details = element[REMOVAL_FLAG];
3152 // move animations are currently not supported...
3153 if (details && details.setForMove) {
3154 player.destroy();
3155 return;
3156 }
3157 }
3158 const nodeIsOrphaned = !bodyNode || !this.driver.containsElement(bodyNode, element);
3159 const leaveClassName = leaveNodeMapIds.get(element);
3160 const enterClassName = enterNodeMapIds.get(element);
3161 const instruction = this._buildInstruction(entry, subTimelines, enterClassName, leaveClassName, nodeIsOrphaned);
3162 if (instruction.errors && instruction.errors.length) {
3163 erroneousTransitions.push(instruction);
3164 return;
3165 }
3166 // even though the element may not be apart of the DOM, it may
3167 // still be added at a later point (due to the mechanics of content
3168 // projection and/or dynamic component insertion) therefore it's
3169 // important we still style the element.
3170 if (nodeIsOrphaned) {
3171 player.onStart(() => eraseStyles(element, instruction.fromStyles));
3172 player.onDestroy(() => setStyles(element, instruction.toStyles));
3173 skippedPlayers.push(player);
3174 return;
3175 }
3176 // if a unmatched transition is queued to go then it SHOULD NOT render
3177 // an animation and cancel the previously running animations.
3178 if (entry.isFallbackTransition) {
3179 player.onStart(() => eraseStyles(element, instruction.fromStyles));
3180 player.onDestroy(() => setStyles(element, instruction.toStyles));
3181 skippedPlayers.push(player);
3182 return;
3183 }
3184 // this means that if a parent animation uses this animation as a sub trigger
3185 // then it will instruct the timeline builder to not add a player delay, but
3186 // instead stretch the first keyframe gap up until the animation starts. The
3187 // reason this is important is to prevent extra initialization styles from being
3188 // required by the user in the animation.
3189 instruction.timelines.forEach(tl => tl.stretchStartingKeyframe = true);
3190 subTimelines.append(element, instruction.timelines);
3191 const tuple = { instruction, player, element };
3192 queuedInstructions.push(tuple);
3193 instruction.queriedElements.forEach(element => getOrSetAsInMap(queriedElements, element, []).push(player));
3194 instruction.preStyleProps.forEach((stringMap, element) => {
3195 const props = Object.keys(stringMap);
3196 if (props.length) {
3197 let setVal = allPreStyleElements.get(element);
3198 if (!setVal) {
3199 allPreStyleElements.set(element, setVal = new Set());
3200 }
3201 props.forEach(prop => setVal.add(prop));
3202 }
3203 });
3204 instruction.postStyleProps.forEach((stringMap, element) => {
3205 const props = Object.keys(stringMap);
3206 let setVal = allPostStyleElements.get(element);
3207 if (!setVal) {
3208 allPostStyleElements.set(element, setVal = new Set());
3209 }
3210 props.forEach(prop => setVal.add(prop));
3211 });
3212 });
3213 }
3214 if (erroneousTransitions.length) {
3215 const errors = [];
3216 erroneousTransitions.forEach(instruction => {
3217 errors.push(`@${instruction.triggerName} has failed due to:\n`);
3218 instruction.errors.forEach(error => errors.push(`- ${error}\n`));
3219 });
3220 allPlayers.forEach(player => player.destroy());
3221 this.reportError(errors);
3222 }
3223 const allPreviousPlayersMap = new Map();
3224 // this map works to tell which element in the DOM tree is contained by
3225 // which animation. Further down below this map will get populated once
3226 // the players are built and in doing so it can efficiently figure out
3227 // if a sub player is skipped due to a parent player having priority.
3228 const animationElementMap = new Map();
3229 queuedInstructions.forEach(entry => {
3230 const element = entry.element;
3231 if (subTimelines.has(element)) {
3232 animationElementMap.set(element, element);
3233 this._beforeAnimationBuild(entry.player.namespaceId, entry.instruction, allPreviousPlayersMap);
3234 }
3235 });
3236 skippedPlayers.forEach(player => {
3237 const element = player.element;
3238 const previousPlayers = this._getPreviousPlayers(element, false, player.namespaceId, player.triggerName, null);
3239 previousPlayers.forEach(prevPlayer => {
3240 getOrSetAsInMap(allPreviousPlayersMap, element, []).push(prevPlayer);
3241 prevPlayer.destroy();
3242 });
3243 });
3244 // this is a special case for nodes that will be removed (either by)
3245 // having their own leave animations or by being queried in a container
3246 // that will be removed once a parent animation is complete. The idea
3247 // here is that * styles must be identical to ! styles because of
3248 // backwards compatibility (* is also filled in by default in many places).
3249 // Otherwise * styles will return an empty value or auto since the element
3250 // that is being getComputedStyle'd will not be visible (since * = destination)
3251 const replaceNodes = allLeaveNodes.filter(node => {
3252 return replacePostStylesAsPre(node, allPreStyleElements, allPostStyleElements);
3253 });
3254 // POST STAGE: fill the * styles
3255 const postStylesMap = new Map();
3256 const allLeaveQueriedNodes = cloakAndComputeStyles(postStylesMap, this.driver, leaveNodesWithoutAnimations, allPostStyleElements, AUTO_STYLE);
3257 allLeaveQueriedNodes.forEach(node => {
3258 if (replacePostStylesAsPre(node, allPreStyleElements, allPostStyleElements)) {
3259 replaceNodes.push(node);
3260 }
3261 });
3262 // PRE STAGE: fill the ! styles
3263 const preStylesMap = new Map();
3264 enterNodeMap.forEach((nodes, root) => {
3265 cloakAndComputeStyles(preStylesMap, this.driver, new Set(nodes), allPreStyleElements, ɵPRE_STYLE);
3266 });
3267 replaceNodes.forEach(node => {
3268 const post = postStylesMap.get(node);
3269 const pre = preStylesMap.get(node);
3270 postStylesMap.set(node, Object.assign(Object.assign({}, post), pre));
3271 });
3272 const rootPlayers = [];
3273 const subPlayers = [];
3274 const NO_PARENT_ANIMATION_ELEMENT_DETECTED = {};
3275 queuedInstructions.forEach(entry => {
3276 const { element, player, instruction } = entry;
3277 // this means that it was never consumed by a parent animation which
3278 // means that it is independent and therefore should be set for animation
3279 if (subTimelines.has(element)) {
3280 if (disabledElementsSet.has(element)) {
3281 player.onDestroy(() => setStyles(element, instruction.toStyles));
3282 player.disabled = true;
3283 player.overrideTotalTime(instruction.totalTime);
3284 skippedPlayers.push(player);
3285 return;
3286 }
3287 // this will flow up the DOM and query the map to figure out
3288 // if a parent animation has priority over it. In the situation
3289 // that a parent is detected then it will cancel the loop. If
3290 // nothing is detected, or it takes a few hops to find a parent,
3291 // then it will fill in the missing nodes and signal them as having
3292 // a detected parent (or a NO_PARENT value via a special constant).
3293 let parentWithAnimation = NO_PARENT_ANIMATION_ELEMENT_DETECTED;
3294 if (animationElementMap.size > 1) {
3295 let elm = element;
3296 const parentsToAdd = [];
3297 while (elm = elm.parentNode) {
3298 const detectedParent = animationElementMap.get(elm);
3299 if (detectedParent) {
3300 parentWithAnimation = detectedParent;
3301 break;
3302 }
3303 parentsToAdd.push(elm);
3304 }
3305 parentsToAdd.forEach(parent => animationElementMap.set(parent, parentWithAnimation));
3306 }
3307 const innerPlayer = this._buildAnimation(player.namespaceId, instruction, allPreviousPlayersMap, skippedPlayersMap, preStylesMap, postStylesMap);
3308 player.setRealPlayer(innerPlayer);
3309 if (parentWithAnimation === NO_PARENT_ANIMATION_ELEMENT_DETECTED) {
3310 rootPlayers.push(player);
3311 }
3312 else {
3313 const parentPlayers = this.playersByElement.get(parentWithAnimation);
3314 if (parentPlayers && parentPlayers.length) {
3315 player.parentPlayer = optimizeGroupPlayer(parentPlayers);
3316 }
3317 skippedPlayers.push(player);
3318 }
3319 }
3320 else {
3321 eraseStyles(element, instruction.fromStyles);
3322 player.onDestroy(() => setStyles(element, instruction.toStyles));
3323 // there still might be a ancestor player animating this
3324 // element therefore we will still add it as a sub player
3325 // even if its animation may be disabled
3326 subPlayers.push(player);
3327 if (disabledElementsSet.has(element)) {
3328 skippedPlayers.push(player);
3329 }
3330 }
3331 });
3332 // find all of the sub players' corresponding inner animation player
3333 subPlayers.forEach(player => {
3334 // even if any players are not found for a sub animation then it
3335 // will still complete itself after the next tick since it's Noop
3336 const playersForElement = skippedPlayersMap.get(player.element);
3337 if (playersForElement && playersForElement.length) {
3338 const innerPlayer = optimizeGroupPlayer(playersForElement);
3339 player.setRealPlayer(innerPlayer);
3340 }
3341 });
3342 // the reason why we don't actually play the animation is
3343 // because all that a skipped player is designed to do is to
3344 // fire the start/done transition callback events
3345 skippedPlayers.forEach(player => {
3346 if (player.parentPlayer) {
3347 player.syncPlayerEvents(player.parentPlayer);
3348 }
3349 else {
3350 player.destroy();
3351 }
3352 });
3353 // run through all of the queued removals and see if they
3354 // were picked up by a query. If not then perform the removal
3355 // operation right away unless a parent animation is ongoing.
3356 for (let i = 0; i < allLeaveNodes.length; i++) {
3357 const element = allLeaveNodes[i];
3358 const details = element[REMOVAL_FLAG];
3359 removeClass(element, LEAVE_CLASSNAME);
3360 // this means the element has a removal animation that is being
3361 // taken care of and therefore the inner elements will hang around
3362 // until that animation is over (or the parent queried animation)
3363 if (details && details.hasAnimation)
3364 continue;
3365 let players = [];
3366 // if this element is queried or if it contains queried children
3367 // then we want for the element not to be removed from the page
3368 // until the queried animations have finished
3369 if (queriedElements.size) {
3370 let queriedPlayerResults = queriedElements.get(element);
3371 if (queriedPlayerResults && queriedPlayerResults.length) {
3372 players.push(...queriedPlayerResults);
3373 }
3374 let queriedInnerElements = this.driver.query(element, NG_ANIMATING_SELECTOR, true);
3375 for (let j = 0; j < queriedInnerElements.length; j++) {
3376 let queriedPlayers = queriedElements.get(queriedInnerElements[j]);
3377 if (queriedPlayers && queriedPlayers.length) {
3378 players.push(...queriedPlayers);
3379 }
3380 }
3381 }
3382 const activePlayers = players.filter(p => !p.destroyed);
3383 if (activePlayers.length) {
3384 removeNodesAfterAnimationDone(this, element, activePlayers);
3385 }
3386 else {
3387 this.processLeaveNode(element);
3388 }
3389 }
3390 // this is required so the cleanup method doesn't remove them
3391 allLeaveNodes.length = 0;
3392 rootPlayers.forEach(player => {
3393 this.players.push(player);
3394 player.onDone(() => {
3395 player.destroy();
3396 const index = this.players.indexOf(player);
3397 this.players.splice(index, 1);
3398 });
3399 player.play();
3400 });
3401 return rootPlayers;
3402 }
3403 elementContainsData(namespaceId, element) {
3404 let containsData = false;
3405 const details = element[REMOVAL_FLAG];
3406 if (details && details.setForRemoval)
3407 containsData = true;
3408 if (this.playersByElement.has(element))
3409 containsData = true;
3410 if (this.playersByQueriedElement.has(element))
3411 containsData = true;
3412 if (this.statesByElement.has(element))
3413 containsData = true;
3414 return this._fetchNamespace(namespaceId).elementContainsData(element) || containsData;
3415 }
3416 afterFlush(callback) {
3417 this._flushFns.push(callback);
3418 }
3419 afterFlushAnimationsDone(callback) {
3420 this._whenQuietFns.push(callback);
3421 }
3422 _getPreviousPlayers(element, isQueriedElement, namespaceId, triggerName, toStateValue) {
3423 let players = [];
3424 if (isQueriedElement) {
3425 const queriedElementPlayers = this.playersByQueriedElement.get(element);
3426 if (queriedElementPlayers) {
3427 players = queriedElementPlayers;
3428 }
3429 }
3430 else {
3431 const elementPlayers = this.playersByElement.get(element);
3432 if (elementPlayers) {
3433 const isRemovalAnimation = !toStateValue || toStateValue == VOID_VALUE;
3434 elementPlayers.forEach(player => {
3435 if (player.queued)
3436 return;
3437 if (!isRemovalAnimation && player.triggerName != triggerName)
3438 return;
3439 players.push(player);
3440 });
3441 }
3442 }
3443 if (namespaceId || triggerName) {
3444 players = players.filter(player => {
3445 if (namespaceId && namespaceId != player.namespaceId)
3446 return false;
3447 if (triggerName && triggerName != player.triggerName)
3448 return false;
3449 return true;
3450 });
3451 }
3452 return players;
3453 }
3454 _beforeAnimationBuild(namespaceId, instruction, allPreviousPlayersMap) {
3455 const triggerName = instruction.triggerName;
3456 const rootElement = instruction.element;
3457 // when a removal animation occurs, ALL previous players are collected
3458 // and destroyed (even if they are outside of the current namespace)
3459 const targetNameSpaceId = instruction.isRemovalTransition ? undefined : namespaceId;
3460 const targetTriggerName = instruction.isRemovalTransition ? undefined : triggerName;
3461 for (const timelineInstruction of instruction.timelines) {
3462 const element = timelineInstruction.element;
3463 const isQueriedElement = element !== rootElement;
3464 const players = getOrSetAsInMap(allPreviousPlayersMap, element, []);
3465 const previousPlayers = this._getPreviousPlayers(element, isQueriedElement, targetNameSpaceId, targetTriggerName, instruction.toState);
3466 previousPlayers.forEach(player => {
3467 const realPlayer = player.getRealPlayer();
3468 if (realPlayer.beforeDestroy) {
3469 realPlayer.beforeDestroy();
3470 }
3471 player.destroy();
3472 players.push(player);
3473 });
3474 }
3475 // this needs to be done so that the PRE/POST styles can be
3476 // computed properly without interfering with the previous animation
3477 eraseStyles(rootElement, instruction.fromStyles);
3478 }
3479 _buildAnimation(namespaceId, instruction, allPreviousPlayersMap, skippedPlayersMap, preStylesMap, postStylesMap) {
3480 const triggerName = instruction.triggerName;
3481 const rootElement = instruction.element;
3482 // we first run this so that the previous animation player
3483 // data can be passed into the successive animation players
3484 const allQueriedPlayers = [];
3485 const allConsumedElements = new Set();
3486 const allSubElements = new Set();
3487 const allNewPlayers = instruction.timelines.map(timelineInstruction => {
3488 const element = timelineInstruction.element;
3489 allConsumedElements.add(element);
3490 // FIXME (matsko): make sure to-be-removed animations are removed properly
3491 const details = element[REMOVAL_FLAG];
3492 if (details && details.removedBeforeQueried)
3493 return new NoopAnimationPlayer(timelineInstruction.duration, timelineInstruction.delay);
3494 const isQueriedElement = element !== rootElement;
3495 const previousPlayers = flattenGroupPlayers((allPreviousPlayersMap.get(element) || EMPTY_PLAYER_ARRAY)
3496 .map(p => p.getRealPlayer()))
3497 .filter(p => {
3498 // the `element` is not apart of the AnimationPlayer definition, but
3499 // Mock/WebAnimations
3500 // use the element within their implementation. This will be added in Angular5 to
3501 // AnimationPlayer
3502 const pp = p;
3503 return pp.element ? pp.element === element : false;
3504 });
3505 const preStyles = preStylesMap.get(element);
3506 const postStyles = postStylesMap.get(element);
3507 const keyframes = normalizeKeyframes(this.driver, this._normalizer, element, timelineInstruction.keyframes, preStyles, postStyles);
3508 const player = this._buildPlayer(timelineInstruction, keyframes, previousPlayers);
3509 // this means that this particular player belongs to a sub trigger. It is
3510 // important that we match this player up with the corresponding (@trigger.listener)
3511 if (timelineInstruction.subTimeline && skippedPlayersMap) {
3512 allSubElements.add(element);
3513 }
3514 if (isQueriedElement) {
3515 const wrappedPlayer = new TransitionAnimationPlayer(namespaceId, triggerName, element);
3516 wrappedPlayer.setRealPlayer(player);
3517 allQueriedPlayers.push(wrappedPlayer);
3518 }
3519 return player;
3520 });
3521 allQueriedPlayers.forEach(player => {
3522 getOrSetAsInMap(this.playersByQueriedElement, player.element, []).push(player);
3523 player.onDone(() => deleteOrUnsetInMap(this.playersByQueriedElement, player.element, player));
3524 });
3525 allConsumedElements.forEach(element => addClass(element, NG_ANIMATING_CLASSNAME));
3526 const player = optimizeGroupPlayer(allNewPlayers);
3527 player.onDestroy(() => {
3528 allConsumedElements.forEach(element => removeClass(element, NG_ANIMATING_CLASSNAME));
3529 setStyles(rootElement, instruction.toStyles);
3530 });
3531 // this basically makes all of the callbacks for sub element animations
3532 // be dependent on the upper players for when they finish
3533 allSubElements.forEach(element => {
3534 getOrSetAsInMap(skippedPlayersMap, element, []).push(player);
3535 });
3536 return player;
3537 }
3538 _buildPlayer(instruction, keyframes, previousPlayers) {
3539 if (keyframes.length > 0) {
3540 return this.driver.animate(instruction.element, keyframes, instruction.duration, instruction.delay, instruction.easing, previousPlayers);
3541 }
3542 // special case for when an empty transition|definition is provided
3543 // ... there is no point in rendering an empty animation
3544 return new NoopAnimationPlayer(instruction.duration, instruction.delay);
3545 }
3546}
3547class TransitionAnimationPlayer {
3548 constructor(namespaceId, triggerName, element) {
3549 this.namespaceId = namespaceId;
3550 this.triggerName = triggerName;
3551 this.element = element;
3552 this._player = new NoopAnimationPlayer();
3553 this._containsRealPlayer = false;
3554 this._queuedCallbacks = {};
3555 this.destroyed = false;
3556 this.markedForDestroy = false;
3557 this.disabled = false;
3558 this.queued = true;
3559 this.totalTime = 0;
3560 }
3561 setRealPlayer(player) {
3562 if (this._containsRealPlayer)
3563 return;
3564 this._player = player;
3565 Object.keys(this._queuedCallbacks).forEach(phase => {
3566 this._queuedCallbacks[phase].forEach(callback => listenOnPlayer(player, phase, undefined, callback));
3567 });
3568 this._queuedCallbacks = {};
3569 this._containsRealPlayer = true;
3570 this.overrideTotalTime(player.totalTime);
3571 this.queued = false;
3572 }
3573 getRealPlayer() {
3574 return this._player;
3575 }
3576 overrideTotalTime(totalTime) {
3577 this.totalTime = totalTime;
3578 }
3579 syncPlayerEvents(player) {
3580 const p = this._player;
3581 if (p.triggerCallback) {
3582 player.onStart(() => p.triggerCallback('start'));
3583 }
3584 player.onDone(() => this.finish());
3585 player.onDestroy(() => this.destroy());
3586 }
3587 _queueEvent(name, callback) {
3588 getOrSetAsInMap(this._queuedCallbacks, name, []).push(callback);
3589 }
3590 onDone(fn) {
3591 if (this.queued) {
3592 this._queueEvent('done', fn);
3593 }
3594 this._player.onDone(fn);
3595 }
3596 onStart(fn) {
3597 if (this.queued) {
3598 this._queueEvent('start', fn);
3599 }
3600 this._player.onStart(fn);
3601 }
3602 onDestroy(fn) {
3603 if (this.queued) {
3604 this._queueEvent('destroy', fn);
3605 }
3606 this._player.onDestroy(fn);
3607 }
3608 init() {
3609 this._player.init();
3610 }
3611 hasStarted() {
3612 return this.queued ? false : this._player.hasStarted();
3613 }
3614 play() {
3615 !this.queued && this._player.play();
3616 }
3617 pause() {
3618 !this.queued && this._player.pause();
3619 }
3620 restart() {
3621 !this.queued && this._player.restart();
3622 }
3623 finish() {
3624 this._player.finish();
3625 }
3626 destroy() {
3627 this.destroyed = true;
3628 this._player.destroy();
3629 }
3630 reset() {
3631 !this.queued && this._player.reset();
3632 }
3633 setPosition(p) {
3634 if (!this.queued) {
3635 this._player.setPosition(p);
3636 }
3637 }
3638 getPosition() {
3639 return this.queued ? 0 : this._player.getPosition();
3640 }
3641 /** @internal */
3642 triggerCallback(phaseName) {
3643 const p = this._player;
3644 if (p.triggerCallback) {
3645 p.triggerCallback(phaseName);
3646 }
3647 }
3648}
3649function deleteOrUnsetInMap(map, key, value) {
3650 let currentValues;
3651 if (map instanceof Map) {
3652 currentValues = map.get(key);
3653 if (currentValues) {
3654 if (currentValues.length) {
3655 const index = currentValues.indexOf(value);
3656 currentValues.splice(index, 1);
3657 }
3658 if (currentValues.length == 0) {
3659 map.delete(key);
3660 }
3661 }
3662 }
3663 else {
3664 currentValues = map[key];
3665 if (currentValues) {
3666 if (currentValues.length) {
3667 const index = currentValues.indexOf(value);
3668 currentValues.splice(index, 1);
3669 }
3670 if (currentValues.length == 0) {
3671 delete map[key];
3672 }
3673 }
3674 }
3675 return currentValues;
3676}
3677function normalizeTriggerValue(value) {
3678 // we use `!= null` here because it's the most simple
3679 // way to test against a "falsy" value without mixing
3680 // in empty strings or a zero value. DO NOT OPTIMIZE.
3681 return value != null ? value : null;
3682}
3683function isElementNode(node) {
3684 return node && node['nodeType'] === 1;
3685}
3686function isTriggerEventValid(eventName) {
3687 return eventName == 'start' || eventName == 'done';
3688}
3689function cloakElement(element, value) {
3690 const oldValue = element.style.display;
3691 element.style.display = value != null ? value : 'none';
3692 return oldValue;
3693}
3694function cloakAndComputeStyles(valuesMap, driver, elements, elementPropsMap, defaultStyle) {
3695 const cloakVals = [];
3696 elements.forEach(element => cloakVals.push(cloakElement(element)));
3697 const failedElements = [];
3698 elementPropsMap.forEach((props, element) => {
3699 const styles = {};
3700 props.forEach(prop => {
3701 const value = styles[prop] = driver.computeStyle(element, prop, defaultStyle);
3702 // there is no easy way to detect this because a sub element could be removed
3703 // by a parent animation element being detached.
3704 if (!value || value.length == 0) {
3705 element[REMOVAL_FLAG] = NULL_REMOVED_QUERIED_STATE;
3706 failedElements.push(element);
3707 }
3708 });
3709 valuesMap.set(element, styles);
3710 });
3711 // we use a index variable here since Set.forEach(a, i) does not return
3712 // an index value for the closure (but instead just the value)
3713 let i = 0;
3714 elements.forEach(element => cloakElement(element, cloakVals[i++]));
3715 return failedElements;
3716}
3717/*
3718Since the Angular renderer code will return a collection of inserted
3719nodes in all areas of a DOM tree, it's up to this algorithm to figure
3720out which nodes are roots for each animation @trigger.
3721
3722By placing each inserted node into a Set and traversing upwards, it
3723is possible to find the @trigger elements and well any direct *star
3724insertion nodes, if a @trigger root is found then the enter element
3725is placed into the Map[@trigger] spot.
3726 */
3727function buildRootMap(roots, nodes) {
3728 const rootMap = new Map();
3729 roots.forEach(root => rootMap.set(root, []));
3730 if (nodes.length == 0)
3731 return rootMap;
3732 const NULL_NODE = 1;
3733 const nodeSet = new Set(nodes);
3734 const localRootMap = new Map();
3735 function getRoot(node) {
3736 if (!node)
3737 return NULL_NODE;
3738 let root = localRootMap.get(node);
3739 if (root)
3740 return root;
3741 const parent = node.parentNode;
3742 if (rootMap.has(parent)) { // ngIf inside @trigger
3743 root = parent;
3744 }
3745 else if (nodeSet.has(parent)) { // ngIf inside ngIf
3746 root = NULL_NODE;
3747 }
3748 else { // recurse upwards
3749 root = getRoot(parent);
3750 }
3751 localRootMap.set(node, root);
3752 return root;
3753 }
3754 nodes.forEach(node => {
3755 const root = getRoot(node);
3756 if (root !== NULL_NODE) {
3757 rootMap.get(root).push(node);
3758 }
3759 });
3760 return rootMap;
3761}
3762const CLASSES_CACHE_KEY = '$$classes';
3763function containsClass(element, className) {
3764 if (element.classList) {
3765 return element.classList.contains(className);
3766 }
3767 else {
3768 const classes = element[CLASSES_CACHE_KEY];
3769 return classes && classes[className];
3770 }
3771}
3772function addClass(element, className) {
3773 if (element.classList) {
3774 element.classList.add(className);
3775 }
3776 else {
3777 let classes = element[CLASSES_CACHE_KEY];
3778 if (!classes) {
3779 classes = element[CLASSES_CACHE_KEY] = {};
3780 }
3781 classes[className] = true;
3782 }
3783}
3784function removeClass(element, className) {
3785 if (element.classList) {
3786 element.classList.remove(className);
3787 }
3788 else {
3789 let classes = element[CLASSES_CACHE_KEY];
3790 if (classes) {
3791 delete classes[className];
3792 }
3793 }
3794}
3795function removeNodesAfterAnimationDone(engine, element, players) {
3796 optimizeGroupPlayer(players).onDone(() => engine.processLeaveNode(element));
3797}
3798function flattenGroupPlayers(players) {
3799 const finalPlayers = [];
3800 _flattenGroupPlayersRecur(players, finalPlayers);
3801 return finalPlayers;
3802}
3803function _flattenGroupPlayersRecur(players, finalPlayers) {
3804 for (let i = 0; i < players.length; i++) {
3805 const player = players[i];
3806 if (player instanceof ɵAnimationGroupPlayer) {
3807 _flattenGroupPlayersRecur(player.players, finalPlayers);
3808 }
3809 else {
3810 finalPlayers.push(player);
3811 }
3812 }
3813}
3814function objEquals(a, b) {
3815 const k1 = Object.keys(a);
3816 const k2 = Object.keys(b);
3817 if (k1.length != k2.length)
3818 return false;
3819 for (let i = 0; i < k1.length; i++) {
3820 const prop = k1[i];
3821 if (!b.hasOwnProperty(prop) || a[prop] !== b[prop])
3822 return false;
3823 }
3824 return true;
3825}
3826function replacePostStylesAsPre(element, allPreStyleElements, allPostStyleElements) {
3827 const postEntry = allPostStyleElements.get(element);
3828 if (!postEntry)
3829 return false;
3830 let preEntry = allPreStyleElements.get(element);
3831 if (preEntry) {
3832 postEntry.forEach(data => preEntry.add(data));
3833 }
3834 else {
3835 allPreStyleElements.set(element, postEntry);
3836 }
3837 allPostStyleElements.delete(element);
3838 return true;
3839}
3840
3841class AnimationEngine {
3842 constructor(bodyNode, _driver, _normalizer) {
3843 this.bodyNode = bodyNode;
3844 this._driver = _driver;
3845 this._normalizer = _normalizer;
3846 this._triggerCache = {};
3847 // this method is designed to be overridden by the code that uses this engine
3848 this.onRemovalComplete = (element, context) => { };
3849 this._transitionEngine = new TransitionAnimationEngine(bodyNode, _driver, _normalizer);
3850 this._timelineEngine = new TimelineAnimationEngine(bodyNode, _driver, _normalizer);
3851 this._transitionEngine.onRemovalComplete = (element, context) => this.onRemovalComplete(element, context);
3852 }
3853 registerTrigger(componentId, namespaceId, hostElement, name, metadata) {
3854 const cacheKey = componentId + '-' + name;
3855 let trigger = this._triggerCache[cacheKey];
3856 if (!trigger) {
3857 const errors = [];
3858 const ast = buildAnimationAst(this._driver, metadata, errors);
3859 if (errors.length) {
3860 throw new Error(`The animation trigger "${name}" has failed to build due to the following errors:\n - ${errors.join('\n - ')}`);
3861 }
3862 trigger = buildTrigger(name, ast, this._normalizer);
3863 this._triggerCache[cacheKey] = trigger;
3864 }
3865 this._transitionEngine.registerTrigger(namespaceId, name, trigger);
3866 }
3867 register(namespaceId, hostElement) {
3868 this._transitionEngine.register(namespaceId, hostElement);
3869 }
3870 destroy(namespaceId, context) {
3871 this._transitionEngine.destroy(namespaceId, context);
3872 }
3873 onInsert(namespaceId, element, parent, insertBefore) {
3874 this._transitionEngine.insertNode(namespaceId, element, parent, insertBefore);
3875 }
3876 onRemove(namespaceId, element, context, isHostElement) {
3877 this._transitionEngine.removeNode(namespaceId, element, isHostElement || false, context);
3878 }
3879 disableAnimations(element, disable) {
3880 this._transitionEngine.markElementAsDisabled(element, disable);
3881 }
3882 process(namespaceId, element, property, value) {
3883 if (property.charAt(0) == '@') {
3884 const [id, action] = parseTimelineCommand(property);
3885 const args = value;
3886 this._timelineEngine.command(id, element, action, args);
3887 }
3888 else {
3889 this._transitionEngine.trigger(namespaceId, element, property, value);
3890 }
3891 }
3892 listen(namespaceId, element, eventName, eventPhase, callback) {
3893 // @@listen
3894 if (eventName.charAt(0) == '@') {
3895 const [id, action] = parseTimelineCommand(eventName);
3896 return this._timelineEngine.listen(id, element, action, callback);
3897 }
3898 return this._transitionEngine.listen(namespaceId, element, eventName, eventPhase, callback);
3899 }
3900 flush(microtaskId = -1) {
3901 this._transitionEngine.flush(microtaskId);
3902 }
3903 get players() {
3904 return this._transitionEngine.players
3905 .concat(this._timelineEngine.players);
3906 }
3907 whenRenderingDone() {
3908 return this._transitionEngine.whenRenderingDone();
3909 }
3910}
3911
3912/**
3913 * @license
3914 * Copyright Google LLC All Rights Reserved.
3915 *
3916 * Use of this source code is governed by an MIT-style license that can be
3917 * found in the LICENSE file at https://angular.io/license
3918 */
3919/**
3920 * Returns an instance of `SpecialCasedStyles` if and when any special (non animateable) styles are
3921 * detected.
3922 *
3923 * In CSS there exist properties that cannot be animated within a keyframe animation
3924 * (whether it be via CSS keyframes or web-animations) and the animation implementation
3925 * will ignore them. This function is designed to detect those special cased styles and
3926 * return a container that will be executed at the start and end of the animation.
3927 *
3928 * @returns an instance of `SpecialCasedStyles` if any special styles are detected otherwise `null`
3929 */
3930function packageNonAnimatableStyles(element, styles) {
3931 let startStyles = null;
3932 let endStyles = null;
3933 if (Array.isArray(styles) && styles.length) {
3934 startStyles = filterNonAnimatableStyles(styles[0]);
3935 if (styles.length > 1) {
3936 endStyles = filterNonAnimatableStyles(styles[styles.length - 1]);
3937 }
3938 }
3939 else if (styles) {
3940 startStyles = filterNonAnimatableStyles(styles);
3941 }
3942 return (startStyles || endStyles) ? new SpecialCasedStyles(element, startStyles, endStyles) :
3943 null;
3944}
3945/**
3946 * Designed to be executed during a keyframe-based animation to apply any special-cased styles.
3947 *
3948 * When started (when the `start()` method is run) then the provided `startStyles`
3949 * will be applied. When finished (when the `finish()` method is called) the
3950 * `endStyles` will be applied as well any any starting styles. Finally when
3951 * `destroy()` is called then all styles will be removed.
3952 */
3953class SpecialCasedStyles {
3954 constructor(_element, _startStyles, _endStyles) {
3955 this._element = _element;
3956 this._startStyles = _startStyles;
3957 this._endStyles = _endStyles;
3958 this._state = 0 /* Pending */;
3959 let initialStyles = SpecialCasedStyles.initialStylesByElement.get(_element);
3960 if (!initialStyles) {
3961 SpecialCasedStyles.initialStylesByElement.set(_element, initialStyles = {});
3962 }
3963 this._initialStyles = initialStyles;
3964 }
3965 start() {
3966 if (this._state < 1 /* Started */) {
3967 if (this._startStyles) {
3968 setStyles(this._element, this._startStyles, this._initialStyles);
3969 }
3970 this._state = 1 /* Started */;
3971 }
3972 }
3973 finish() {
3974 this.start();
3975 if (this._state < 2 /* Finished */) {
3976 setStyles(this._element, this._initialStyles);
3977 if (this._endStyles) {
3978 setStyles(this._element, this._endStyles);
3979 this._endStyles = null;
3980 }
3981 this._state = 1 /* Started */;
3982 }
3983 }
3984 destroy() {
3985 this.finish();
3986 if (this._state < 3 /* Destroyed */) {
3987 SpecialCasedStyles.initialStylesByElement.delete(this._element);
3988 if (this._startStyles) {
3989 eraseStyles(this._element, this._startStyles);
3990 this._endStyles = null;
3991 }
3992 if (this._endStyles) {
3993 eraseStyles(this._element, this._endStyles);
3994 this._endStyles = null;
3995 }
3996 setStyles(this._element, this._initialStyles);
3997 this._state = 3 /* Destroyed */;
3998 }
3999 }
4000}
4001SpecialCasedStyles.initialStylesByElement = ( /* @__PURE__ */new WeakMap());
4002function filterNonAnimatableStyles(styles) {
4003 let result = null;
4004 const props = Object.keys(styles);
4005 for (let i = 0; i < props.length; i++) {
4006 const prop = props[i];
4007 if (isNonAnimatableStyle(prop)) {
4008 result = result || {};
4009 result[prop] = styles[prop];
4010 }
4011 }
4012 return result;
4013}
4014function isNonAnimatableStyle(prop) {
4015 return prop === 'display' || prop === 'position';
4016}
4017
4018/**
4019 * @license
4020 * Copyright Google LLC All Rights Reserved.
4021 *
4022 * Use of this source code is governed by an MIT-style license that can be
4023 * found in the LICENSE file at https://angular.io/license
4024 */
4025const ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
4026const ANIMATION_PROP = 'animation';
4027const ANIMATIONEND_EVENT = 'animationend';
4028const ONE_SECOND$1 = 1000;
4029class ElementAnimationStyleHandler {
4030 constructor(_element, _name, _duration, _delay, _easing, _fillMode, _onDoneFn) {
4031 this._element = _element;
4032 this._name = _name;
4033 this._duration = _duration;
4034 this._delay = _delay;
4035 this._easing = _easing;
4036 this._fillMode = _fillMode;
4037 this._onDoneFn = _onDoneFn;
4038 this._finished = false;
4039 this._destroyed = false;
4040 this._startTime = 0;
4041 this._position = 0;
4042 this._eventFn = (e) => this._handleCallback(e);
4043 }
4044 apply() {
4045 applyKeyframeAnimation(this._element, `${this._duration}ms ${this._easing} ${this._delay}ms 1 normal ${this._fillMode} ${this._name}`);
4046 addRemoveAnimationEvent(this._element, this._eventFn, false);
4047 this._startTime = Date.now();
4048 }
4049 pause() {
4050 playPauseAnimation(this._element, this._name, 'paused');
4051 }
4052 resume() {
4053 playPauseAnimation(this._element, this._name, 'running');
4054 }
4055 setPosition(position) {
4056 const index = findIndexForAnimation(this._element, this._name);
4057 this._position = position * this._duration;
4058 setAnimationStyle(this._element, 'Delay', `-${this._position}ms`, index);
4059 }
4060 getPosition() {
4061 return this._position;
4062 }
4063 _handleCallback(event) {
4064 const timestamp = event._ngTestManualTimestamp || Date.now();
4065 const elapsedTime = parseFloat(event.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES)) * ONE_SECOND$1;
4066 if (event.animationName == this._name &&
4067 Math.max(timestamp - this._startTime, 0) >= this._delay && elapsedTime >= this._duration) {
4068 this.finish();
4069 }
4070 }
4071 finish() {
4072 if (this._finished)
4073 return;
4074 this._finished = true;
4075 this._onDoneFn();
4076 addRemoveAnimationEvent(this._element, this._eventFn, true);
4077 }
4078 destroy() {
4079 if (this._destroyed)
4080 return;
4081 this._destroyed = true;
4082 this.finish();
4083 removeKeyframeAnimation(this._element, this._name);
4084 }
4085}
4086function playPauseAnimation(element, name, status) {
4087 const index = findIndexForAnimation(element, name);
4088 setAnimationStyle(element, 'PlayState', status, index);
4089}
4090function applyKeyframeAnimation(element, value) {
4091 const anim = getAnimationStyle(element, '').trim();
4092 let index = 0;
4093 if (anim.length) {
4094 index = countChars(anim, ',') + 1;
4095 value = `${anim}, ${value}`;
4096 }
4097 setAnimationStyle(element, '', value);
4098 return index;
4099}
4100function removeKeyframeAnimation(element, name) {
4101 const anim = getAnimationStyle(element, '');
4102 const tokens = anim.split(',');
4103 const index = findMatchingTokenIndex(tokens, name);
4104 if (index >= 0) {
4105 tokens.splice(index, 1);
4106 const newValue = tokens.join(',');
4107 setAnimationStyle(element, '', newValue);
4108 }
4109}
4110function findIndexForAnimation(element, value) {
4111 const anim = getAnimationStyle(element, '');
4112 if (anim.indexOf(',') > 0) {
4113 const tokens = anim.split(',');
4114 return findMatchingTokenIndex(tokens, value);
4115 }
4116 return findMatchingTokenIndex([anim], value);
4117}
4118function findMatchingTokenIndex(tokens, searchToken) {
4119 for (let i = 0; i < tokens.length; i++) {
4120 if (tokens[i].indexOf(searchToken) >= 0) {
4121 return i;
4122 }
4123 }
4124 return -1;
4125}
4126function addRemoveAnimationEvent(element, fn, doRemove) {
4127 doRemove ? element.removeEventListener(ANIMATIONEND_EVENT, fn) :
4128 element.addEventListener(ANIMATIONEND_EVENT, fn);
4129}
4130function setAnimationStyle(element, name, value, index) {
4131 const prop = ANIMATION_PROP + name;
4132 if (index != null) {
4133 const oldValue = element.style[prop];
4134 if (oldValue.length) {
4135 const tokens = oldValue.split(',');
4136 tokens[index] = value;
4137 value = tokens.join(',');
4138 }
4139 }
4140 element.style[prop] = value;
4141}
4142function getAnimationStyle(element, name) {
4143 return element.style[ANIMATION_PROP + name] || '';
4144}
4145function countChars(value, char) {
4146 let count = 0;
4147 for (let i = 0; i < value.length; i++) {
4148 const c = value.charAt(i);
4149 if (c === char)
4150 count++;
4151 }
4152 return count;
4153}
4154
4155const DEFAULT_FILL_MODE = 'forwards';
4156const DEFAULT_EASING = 'linear';
4157class CssKeyframesPlayer {
4158 constructor(element, keyframes, animationName, _duration, _delay, easing, _finalStyles, _specialStyles) {
4159 this.element = element;
4160 this.keyframes = keyframes;
4161 this.animationName = animationName;
4162 this._duration = _duration;
4163 this._delay = _delay;
4164 this._finalStyles = _finalStyles;
4165 this._specialStyles = _specialStyles;
4166 this._onDoneFns = [];
4167 this._onStartFns = [];
4168 this._onDestroyFns = [];
4169 this.currentSnapshot = {};
4170 this._state = 0;
4171 this.easing = easing || DEFAULT_EASING;
4172 this.totalTime = _duration + _delay;
4173 this._buildStyler();
4174 }
4175 onStart(fn) {
4176 this._onStartFns.push(fn);
4177 }
4178 onDone(fn) {
4179 this._onDoneFns.push(fn);
4180 }
4181 onDestroy(fn) {
4182 this._onDestroyFns.push(fn);
4183 }
4184 destroy() {
4185 this.init();
4186 if (this._state >= 4 /* DESTROYED */)
4187 return;
4188 this._state = 4 /* DESTROYED */;
4189 this._styler.destroy();
4190 this._flushStartFns();
4191 this._flushDoneFns();
4192 if (this._specialStyles) {
4193 this._specialStyles.destroy();
4194 }
4195 this._onDestroyFns.forEach(fn => fn());
4196 this._onDestroyFns = [];
4197 }
4198 _flushDoneFns() {
4199 this._onDoneFns.forEach(fn => fn());
4200 this._onDoneFns = [];
4201 }
4202 _flushStartFns() {
4203 this._onStartFns.forEach(fn => fn());
4204 this._onStartFns = [];
4205 }
4206 finish() {
4207 this.init();
4208 if (this._state >= 3 /* FINISHED */)
4209 return;
4210 this._state = 3 /* FINISHED */;
4211 this._styler.finish();
4212 this._flushStartFns();
4213 if (this._specialStyles) {
4214 this._specialStyles.finish();
4215 }
4216 this._flushDoneFns();
4217 }
4218 setPosition(value) {
4219 this._styler.setPosition(value);
4220 }
4221 getPosition() {
4222 return this._styler.getPosition();
4223 }
4224 hasStarted() {
4225 return this._state >= 2 /* STARTED */;
4226 }
4227 init() {
4228 if (this._state >= 1 /* INITIALIZED */)
4229 return;
4230 this._state = 1 /* INITIALIZED */;
4231 const elm = this.element;
4232 this._styler.apply();
4233 if (this._delay) {
4234 this._styler.pause();
4235 }
4236 }
4237 play() {
4238 this.init();
4239 if (!this.hasStarted()) {
4240 this._flushStartFns();
4241 this._state = 2 /* STARTED */;
4242 if (this._specialStyles) {
4243 this._specialStyles.start();
4244 }
4245 }
4246 this._styler.resume();
4247 }
4248 pause() {
4249 this.init();
4250 this._styler.pause();
4251 }
4252 restart() {
4253 this.reset();
4254 this.play();
4255 }
4256 reset() {
4257 this._state = 0 /* RESET */;
4258 this._styler.destroy();
4259 this._buildStyler();
4260 this._styler.apply();
4261 }
4262 _buildStyler() {
4263 this._styler = new ElementAnimationStyleHandler(this.element, this.animationName, this._duration, this._delay, this.easing, DEFAULT_FILL_MODE, () => this.finish());
4264 }
4265 /** @internal */
4266 triggerCallback(phaseName) {
4267 const methods = phaseName == 'start' ? this._onStartFns : this._onDoneFns;
4268 methods.forEach(fn => fn());
4269 methods.length = 0;
4270 }
4271 beforeDestroy() {
4272 this.init();
4273 const styles = {};
4274 if (this.hasStarted()) {
4275 const finished = this._state >= 3 /* FINISHED */;
4276 Object.keys(this._finalStyles).forEach(prop => {
4277 if (prop != 'offset') {
4278 styles[prop] = finished ? this._finalStyles[prop] : computeStyle(this.element, prop);
4279 }
4280 });
4281 }
4282 this.currentSnapshot = styles;
4283 }
4284}
4285
4286/**
4287 * @license
4288 * Copyright Google LLC All Rights Reserved.
4289 *
4290 * Use of this source code is governed by an MIT-style license that can be
4291 * found in the LICENSE file at https://angular.io/license
4292 */
4293class DirectStylePlayer extends NoopAnimationPlayer {
4294 constructor(element, styles) {
4295 super();
4296 this.element = element;
4297 this._startingStyles = {};
4298 this.__initialized = false;
4299 this._styles = hypenatePropsObject(styles);
4300 }
4301 init() {
4302 if (this.__initialized || !this._startingStyles)
4303 return;
4304 this.__initialized = true;
4305 Object.keys(this._styles).forEach(prop => {
4306 this._startingStyles[prop] = this.element.style[prop];
4307 });
4308 super.init();
4309 }
4310 play() {
4311 if (!this._startingStyles)
4312 return;
4313 this.init();
4314 Object.keys(this._styles)
4315 .forEach(prop => this.element.style.setProperty(prop, this._styles[prop]));
4316 super.play();
4317 }
4318 destroy() {
4319 if (!this._startingStyles)
4320 return;
4321 Object.keys(this._startingStyles).forEach(prop => {
4322 const value = this._startingStyles[prop];
4323 if (value) {
4324 this.element.style.setProperty(prop, value);
4325 }
4326 else {
4327 this.element.style.removeProperty(prop);
4328 }
4329 });
4330 this._startingStyles = null;
4331 super.destroy();
4332 }
4333}
4334
4335const KEYFRAMES_NAME_PREFIX = 'gen_css_kf_';
4336const TAB_SPACE = ' ';
4337class CssKeyframesDriver {
4338 constructor() {
4339 this._count = 0;
4340 }
4341 validateStyleProperty(prop) {
4342 return validateStyleProperty(prop);
4343 }
4344 matchesElement(element, selector) {
4345 return matchesElement(element, selector);
4346 }
4347 containsElement(elm1, elm2) {
4348 return containsElement(elm1, elm2);
4349 }
4350 query(element, selector, multi) {
4351 return invokeQuery(element, selector, multi);
4352 }
4353 computeStyle(element, prop, defaultValue) {
4354 return window.getComputedStyle(element)[prop];
4355 }
4356 buildKeyframeElement(element, name, keyframes) {
4357 keyframes = keyframes.map(kf => hypenatePropsObject(kf));
4358 let keyframeStr = `@keyframes ${name} {\n`;
4359 let tab = '';
4360 keyframes.forEach(kf => {
4361 tab = TAB_SPACE;
4362 const offset = parseFloat(kf['offset']);
4363 keyframeStr += `${tab}${offset * 100}% {\n`;
4364 tab += TAB_SPACE;
4365 Object.keys(kf).forEach(prop => {
4366 const value = kf[prop];
4367 switch (prop) {
4368 case 'offset':
4369 return;
4370 case 'easing':
4371 if (value) {
4372 keyframeStr += `${tab}animation-timing-function: ${value};\n`;
4373 }
4374 return;
4375 default:
4376 keyframeStr += `${tab}${prop}: ${value};\n`;
4377 return;
4378 }
4379 });
4380 keyframeStr += `${tab}}\n`;
4381 });
4382 keyframeStr += `}\n`;
4383 const kfElm = document.createElement('style');
4384 kfElm.textContent = keyframeStr;
4385 return kfElm;
4386 }
4387 animate(element, keyframes, duration, delay, easing, previousPlayers = [], scrubberAccessRequested) {
4388 if ((typeof ngDevMode === 'undefined' || ngDevMode) && scrubberAccessRequested) {
4389 notifyFaultyScrubber();
4390 }
4391 const previousCssKeyframePlayers = previousPlayers.filter(player => player instanceof CssKeyframesPlayer);
4392 const previousStyles = {};
4393 if (allowPreviousPlayerStylesMerge(duration, delay)) {
4394 previousCssKeyframePlayers.forEach(player => {
4395 let styles = player.currentSnapshot;
4396 Object.keys(styles).forEach(prop => previousStyles[prop] = styles[prop]);
4397 });
4398 }
4399 keyframes = balancePreviousStylesIntoKeyframes(element, keyframes, previousStyles);
4400 const finalStyles = flattenKeyframesIntoStyles(keyframes);
4401 // if there is no animation then there is no point in applying
4402 // styles and waiting for an event to get fired. This causes lag.
4403 // It's better to just directly apply the styles to the element
4404 // via the direct styling animation player.
4405 if (duration == 0) {
4406 return new DirectStylePlayer(element, finalStyles);
4407 }
4408 const animationName = `${KEYFRAMES_NAME_PREFIX}${this._count++}`;
4409 const kfElm = this.buildKeyframeElement(element, animationName, keyframes);
4410 const nodeToAppendKfElm = findNodeToAppendKeyframeElement(element);
4411 nodeToAppendKfElm.appendChild(kfElm);
4412 const specialStyles = packageNonAnimatableStyles(element, keyframes);
4413 const player = new CssKeyframesPlayer(element, keyframes, animationName, duration, delay, easing, finalStyles, specialStyles);
4414 player.onDestroy(() => removeElement(kfElm));
4415 return player;
4416 }
4417}
4418function findNodeToAppendKeyframeElement(element) {
4419 var _a;
4420 const rootNode = (_a = element.getRootNode) === null || _a === void 0 ? void 0 : _a.call(element);
4421 if (typeof ShadowRoot !== 'undefined' && rootNode instanceof ShadowRoot) {
4422 return rootNode;
4423 }
4424 return document.head;
4425}
4426function flattenKeyframesIntoStyles(keyframes) {
4427 let flatKeyframes = {};
4428 if (keyframes) {
4429 const kfs = Array.isArray(keyframes) ? keyframes : [keyframes];
4430 kfs.forEach(kf => {
4431 Object.keys(kf).forEach(prop => {
4432 if (prop == 'offset' || prop == 'easing')
4433 return;
4434 flatKeyframes[prop] = kf[prop];
4435 });
4436 });
4437 }
4438 return flatKeyframes;
4439}
4440function removeElement(node) {
4441 node.parentNode.removeChild(node);
4442}
4443let warningIssued = false;
4444function notifyFaultyScrubber() {
4445 if (warningIssued)
4446 return;
4447 console.warn('@angular/animations: please load the web-animations.js polyfill to allow programmatic access...\n', ' visit https://bit.ly/IWukam to learn more about using the web-animation-js polyfill.');
4448 warningIssued = true;
4449}
4450
4451class WebAnimationsPlayer {
4452 constructor(element, keyframes, options, _specialStyles) {
4453 this.element = element;
4454 this.keyframes = keyframes;
4455 this.options = options;
4456 this._specialStyles = _specialStyles;
4457 this._onDoneFns = [];
4458 this._onStartFns = [];
4459 this._onDestroyFns = [];
4460 this._initialized = false;
4461 this._finished = false;
4462 this._started = false;
4463 this._destroyed = false;
4464 this.time = 0;
4465 this.parentPlayer = null;
4466 this.currentSnapshot = {};
4467 this._duration = options['duration'];
4468 this._delay = options['delay'] || 0;
4469 this.time = this._duration + this._delay;
4470 }
4471 _onFinish() {
4472 if (!this._finished) {
4473 this._finished = true;
4474 this._onDoneFns.forEach(fn => fn());
4475 this._onDoneFns = [];
4476 }
4477 }
4478 init() {
4479 this._buildPlayer();
4480 this._preparePlayerBeforeStart();
4481 }
4482 _buildPlayer() {
4483 if (this._initialized)
4484 return;
4485 this._initialized = true;
4486 const keyframes = this.keyframes;
4487 this.domPlayer =
4488 this._triggerWebAnimation(this.element, keyframes, this.options);
4489 this._finalKeyframe = keyframes.length ? keyframes[keyframes.length - 1] : {};
4490 this.domPlayer.addEventListener('finish', () => this._onFinish());
4491 }
4492 _preparePlayerBeforeStart() {
4493 // this is required so that the player doesn't start to animate right away
4494 if (this._delay) {
4495 this._resetDomPlayerState();
4496 }
4497 else {
4498 this.domPlayer.pause();
4499 }
4500 }
4501 /** @internal */
4502 _triggerWebAnimation(element, keyframes, options) {
4503 // jscompiler doesn't seem to know animate is a native property because it's not fully
4504 // supported yet across common browsers (we polyfill it for Edge/Safari) [CL #143630929]
4505 return element['animate'](keyframes, options);
4506 }
4507 onStart(fn) {
4508 this._onStartFns.push(fn);
4509 }
4510 onDone(fn) {
4511 this._onDoneFns.push(fn);
4512 }
4513 onDestroy(fn) {
4514 this._onDestroyFns.push(fn);
4515 }
4516 play() {
4517 this._buildPlayer();
4518 if (!this.hasStarted()) {
4519 this._onStartFns.forEach(fn => fn());
4520 this._onStartFns = [];
4521 this._started = true;
4522 if (this._specialStyles) {
4523 this._specialStyles.start();
4524 }
4525 }
4526 this.domPlayer.play();
4527 }
4528 pause() {
4529 this.init();
4530 this.domPlayer.pause();
4531 }
4532 finish() {
4533 this.init();
4534 if (this._specialStyles) {
4535 this._specialStyles.finish();
4536 }
4537 this._onFinish();
4538 this.domPlayer.finish();
4539 }
4540 reset() {
4541 this._resetDomPlayerState();
4542 this._destroyed = false;
4543 this._finished = false;
4544 this._started = false;
4545 }
4546 _resetDomPlayerState() {
4547 if (this.domPlayer) {
4548 this.domPlayer.cancel();
4549 }
4550 }
4551 restart() {
4552 this.reset();
4553 this.play();
4554 }
4555 hasStarted() {
4556 return this._started;
4557 }
4558 destroy() {
4559 if (!this._destroyed) {
4560 this._destroyed = true;
4561 this._resetDomPlayerState();
4562 this._onFinish();
4563 if (this._specialStyles) {
4564 this._specialStyles.destroy();
4565 }
4566 this._onDestroyFns.forEach(fn => fn());
4567 this._onDestroyFns = [];
4568 }
4569 }
4570 setPosition(p) {
4571 if (this.domPlayer === undefined) {
4572 this.init();
4573 }
4574 this.domPlayer.currentTime = p * this.time;
4575 }
4576 getPosition() {
4577 return this.domPlayer.currentTime / this.time;
4578 }
4579 get totalTime() {
4580 return this._delay + this._duration;
4581 }
4582 beforeDestroy() {
4583 const styles = {};
4584 if (this.hasStarted()) {
4585 Object.keys(this._finalKeyframe).forEach(prop => {
4586 if (prop != 'offset') {
4587 styles[prop] =
4588 this._finished ? this._finalKeyframe[prop] : computeStyle(this.element, prop);
4589 }
4590 });
4591 }
4592 this.currentSnapshot = styles;
4593 }
4594 /** @internal */
4595 triggerCallback(phaseName) {
4596 const methods = phaseName == 'start' ? this._onStartFns : this._onDoneFns;
4597 methods.forEach(fn => fn());
4598 methods.length = 0;
4599 }
4600}
4601
4602class WebAnimationsDriver {
4603 constructor() {
4604 this._isNativeImpl = /\{\s*\[native\s+code\]\s*\}/.test(getElementAnimateFn().toString());
4605 this._cssKeyframesDriver = new CssKeyframesDriver();
4606 }
4607 validateStyleProperty(prop) {
4608 return validateStyleProperty(prop);
4609 }
4610 matchesElement(element, selector) {
4611 return matchesElement(element, selector);
4612 }
4613 containsElement(elm1, elm2) {
4614 return containsElement(elm1, elm2);
4615 }
4616 query(element, selector, multi) {
4617 return invokeQuery(element, selector, multi);
4618 }
4619 computeStyle(element, prop, defaultValue) {
4620 return window.getComputedStyle(element)[prop];
4621 }
4622 overrideWebAnimationsSupport(supported) {
4623 this._isNativeImpl = supported;
4624 }
4625 animate(element, keyframes, duration, delay, easing, previousPlayers = [], scrubberAccessRequested) {
4626 const useKeyframes = !scrubberAccessRequested && !this._isNativeImpl;
4627 if (useKeyframes) {
4628 return this._cssKeyframesDriver.animate(element, keyframes, duration, delay, easing, previousPlayers);
4629 }
4630 const fill = delay == 0 ? 'both' : 'forwards';
4631 const playerOptions = { duration, delay, fill };
4632 // we check for this to avoid having a null|undefined value be present
4633 // for the easing (which results in an error for certain browsers #9752)
4634 if (easing) {
4635 playerOptions['easing'] = easing;
4636 }
4637 const previousStyles = {};
4638 const previousWebAnimationPlayers = previousPlayers.filter(player => player instanceof WebAnimationsPlayer);
4639 if (allowPreviousPlayerStylesMerge(duration, delay)) {
4640 previousWebAnimationPlayers.forEach(player => {
4641 let styles = player.currentSnapshot;
4642 Object.keys(styles).forEach(prop => previousStyles[prop] = styles[prop]);
4643 });
4644 }
4645 keyframes = keyframes.map(styles => copyStyles(styles, false));
4646 keyframes = balancePreviousStylesIntoKeyframes(element, keyframes, previousStyles);
4647 const specialStyles = packageNonAnimatableStyles(element, keyframes);
4648 return new WebAnimationsPlayer(element, keyframes, playerOptions, specialStyles);
4649 }
4650}
4651function supportsWebAnimations() {
4652 return typeof getElementAnimateFn() === 'function';
4653}
4654function getElementAnimateFn() {
4655 return (isBrowser() && Element.prototype['animate']) || {};
4656}
4657
4658/**
4659 * @license
4660 * Copyright Google LLC All Rights Reserved.
4661 *
4662 * Use of this source code is governed by an MIT-style license that can be
4663 * found in the LICENSE file at https://angular.io/license
4664 */
4665
4666/**
4667 * @license
4668 * Copyright Google LLC All Rights Reserved.
4669 *
4670 * Use of this source code is governed by an MIT-style license that can be
4671 * found in the LICENSE file at https://angular.io/license
4672 */
4673
4674/**
4675 * @license
4676 * Copyright Google LLC All Rights Reserved.
4677 *
4678 * Use of this source code is governed by an MIT-style license that can be
4679 * found in the LICENSE file at https://angular.io/license
4680 */
4681
4682/**
4683 * @license
4684 * Copyright Google LLC All Rights Reserved.
4685 *
4686 * Use of this source code is governed by an MIT-style license that can be
4687 * found in the LICENSE file at https://angular.io/license
4688 */
4689
4690/**
4691 * Generated bundle index. Do not edit.
4692 */
4693
4694export { AnimationDriver, Animation as ɵAnimation, AnimationEngine as ɵAnimationEngine, AnimationStyleNormalizer as ɵAnimationStyleNormalizer, CssKeyframesDriver as ɵCssKeyframesDriver, CssKeyframesPlayer as ɵCssKeyframesPlayer, NoopAnimationDriver as ɵNoopAnimationDriver, NoopAnimationStyleNormalizer as ɵNoopAnimationStyleNormalizer, WebAnimationsDriver as ɵWebAnimationsDriver, WebAnimationsPlayer as ɵWebAnimationsPlayer, WebAnimationsStyleNormalizer as ɵWebAnimationsStyleNormalizer, allowPreviousPlayerStylesMerge as ɵallowPreviousPlayerStylesMerge, SpecialCasedStyles as ɵangular_packages_animations_browser_browser_a, containsElement as ɵcontainsElement, invokeQuery as ɵinvokeQuery, matchesElement as ɵmatchesElement, supportsWebAnimations as ɵsupportsWebAnimations, validateStyleProperty as ɵvalidateStyleProperty };
4695//# sourceMappingURL=browser.js.map
Note: See TracBrowser for help on using the repository browser.