UNPKG

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