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

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

initial commit

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