| /** |
| * @license Angular v5.2.0 |
| * (c) 2010-2018 Google, Inc. https://angular.io/ |
| * License: MIT |
| */ |
| import { AUTO_STYLE, NoopAnimationPlayer, sequence, style, ɵAnimationGroupPlayer, ɵPRE_STYLE } from '@angular/animations'; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| /** |
| * @param {?} players |
| * @return {?} |
| */ |
| function optimizeGroupPlayer(players) { |
| switch (players.length) { |
| case 0: |
| return new NoopAnimationPlayer(); |
| case 1: |
| return players[0]; |
| default: |
| return new ɵAnimationGroupPlayer(players); |
| } |
| } |
| /** |
| * @param {?} driver |
| * @param {?} normalizer |
| * @param {?} element |
| * @param {?} keyframes |
| * @param {?=} preStyles |
| * @param {?=} postStyles |
| * @return {?} |
| */ |
| function normalizeKeyframes(driver, normalizer, element, keyframes, preStyles = {}, postStyles = {}) { |
| const /** @type {?} */ errors = []; |
| const /** @type {?} */ normalizedKeyframes = []; |
| let /** @type {?} */ previousOffset = -1; |
| let /** @type {?} */ previousKeyframe = null; |
| keyframes.forEach(kf => { |
| const /** @type {?} */ offset = /** @type {?} */ (kf['offset']); |
| const /** @type {?} */ isSameOffset = offset == previousOffset; |
| const /** @type {?} */ normalizedKeyframe = (isSameOffset && previousKeyframe) || {}; |
| Object.keys(kf).forEach(prop => { |
| let /** @type {?} */ normalizedProp = prop; |
| let /** @type {?} */ normalizedValue = kf[prop]; |
| if (prop !== 'offset') { |
| normalizedProp = normalizer.normalizePropertyName(normalizedProp, errors); |
| switch (normalizedValue) { |
| case ɵPRE_STYLE: |
| normalizedValue = preStyles[prop]; |
| break; |
| case AUTO_STYLE: |
| normalizedValue = postStyles[prop]; |
| break; |
| default: |
| normalizedValue = |
| normalizer.normalizeStyleValue(prop, normalizedProp, normalizedValue, errors); |
| break; |
| } |
| } |
| normalizedKeyframe[normalizedProp] = normalizedValue; |
| }); |
| if (!isSameOffset) { |
| normalizedKeyframes.push(normalizedKeyframe); |
| } |
| previousKeyframe = normalizedKeyframe; |
| previousOffset = offset; |
| }); |
| if (errors.length) { |
| const /** @type {?} */ LINE_START = '\n - '; |
| throw new Error(`Unable to animate due to the following errors:${LINE_START}${errors.join(LINE_START)}`); |
| } |
| return normalizedKeyframes; |
| } |
| /** |
| * @param {?} player |
| * @param {?} eventName |
| * @param {?} event |
| * @param {?} callback |
| * @return {?} |
| */ |
| function listenOnPlayer(player, eventName, event, callback) { |
| switch (eventName) { |
| case 'start': |
| player.onStart(() => callback(event && copyAnimationEvent(event, 'start', player.totalTime))); |
| break; |
| case 'done': |
| player.onDone(() => callback(event && copyAnimationEvent(event, 'done', player.totalTime))); |
| break; |
| case 'destroy': |
| player.onDestroy(() => callback(event && copyAnimationEvent(event, 'destroy', player.totalTime))); |
| break; |
| } |
| } |
| /** |
| * @param {?} e |
| * @param {?=} phaseName |
| * @param {?=} totalTime |
| * @return {?} |
| */ |
| function copyAnimationEvent(e, phaseName, totalTime) { |
| const /** @type {?} */ event = makeAnimationEvent(e.element, e.triggerName, e.fromState, e.toState, phaseName || e.phaseName, totalTime == undefined ? e.totalTime : totalTime); |
| const /** @type {?} */ data = (/** @type {?} */ (e))['_data']; |
| if (data != null) { |
| (/** @type {?} */ (event))['_data'] = data; |
| } |
| return event; |
| } |
| /** |
| * @param {?} element |
| * @param {?} triggerName |
| * @param {?} fromState |
| * @param {?} toState |
| * @param {?=} phaseName |
| * @param {?=} totalTime |
| * @return {?} |
| */ |
| function makeAnimationEvent(element, triggerName, fromState, toState, phaseName = '', totalTime = 0) { |
| return { element, triggerName, fromState, toState, phaseName, totalTime }; |
| } |
| /** |
| * @param {?} map |
| * @param {?} key |
| * @param {?} defaultValue |
| * @return {?} |
| */ |
| function getOrSetAsInMap(map, key, defaultValue) { |
| let /** @type {?} */ value; |
| if (map instanceof Map) { |
| value = map.get(key); |
| if (!value) { |
| map.set(key, value = defaultValue); |
| } |
| } |
| else { |
| value = map[key]; |
| if (!value) { |
| value = map[key] = defaultValue; |
| } |
| } |
| return value; |
| } |
| /** |
| * @param {?} command |
| * @return {?} |
| */ |
| function parseTimelineCommand(command) { |
| const /** @type {?} */ separatorPos = command.indexOf(':'); |
| const /** @type {?} */ id = command.substring(1, separatorPos); |
| const /** @type {?} */ action = command.substr(separatorPos + 1); |
| return [id, action]; |
| } |
| let _contains = (elm1, elm2) => false; |
| let _matches = (element, selector) => false; |
| let _query = (element, selector, multi) => { |
| return []; |
| }; |
| if (typeof Element != 'undefined') { |
| // this is well supported in all browsers |
| _contains = (elm1, elm2) => { return /** @type {?} */ (elm1.contains(elm2)); }; |
| if (Element.prototype.matches) { |
| _matches = (element, selector) => element.matches(selector); |
| } |
| else { |
| const /** @type {?} */ proto = /** @type {?} */ (Element.prototype); |
| const /** @type {?} */ fn = proto.matchesSelector || proto.mozMatchesSelector || proto.msMatchesSelector || |
| proto.oMatchesSelector || proto.webkitMatchesSelector; |
| if (fn) { |
| _matches = (element, selector) => fn.apply(element, [selector]); |
| } |
| } |
| _query = (element, selector, multi) => { |
| let /** @type {?} */ results = []; |
| if (multi) { |
| results.push(...element.querySelectorAll(selector)); |
| } |
| else { |
| const /** @type {?} */ elm = element.querySelector(selector); |
| if (elm) { |
| results.push(elm); |
| } |
| } |
| return results; |
| }; |
| } |
| /** |
| * @param {?} prop |
| * @return {?} |
| */ |
| function containsVendorPrefix(prop) { |
| // Webkit is the only real popular vendor prefix nowadays |
| // cc: http://shouldiprefix.com/ |
| return prop.substring(1, 6) == 'ebkit'; // webkit or Webkit |
| } |
| let _CACHED_BODY = null; |
| let _IS_WEBKIT = false; |
| /** |
| * @param {?} prop |
| * @return {?} |
| */ |
| function validateStyleProperty(prop) { |
| if (!_CACHED_BODY) { |
| _CACHED_BODY = getBodyNode() || {}; |
| _IS_WEBKIT = /** @type {?} */ ((_CACHED_BODY)).style ? ('WebkitAppearance' in /** @type {?} */ ((_CACHED_BODY)).style) : false; |
| } |
| let /** @type {?} */ result = true; |
| if (/** @type {?} */ ((_CACHED_BODY)).style && !containsVendorPrefix(prop)) { |
| result = prop in /** @type {?} */ ((_CACHED_BODY)).style; |
| if (!result && _IS_WEBKIT) { |
| const /** @type {?} */ camelProp = 'Webkit' + prop.charAt(0).toUpperCase() + prop.substr(1); |
| result = camelProp in /** @type {?} */ ((_CACHED_BODY)).style; |
| } |
| } |
| return result; |
| } |
| /** |
| * @return {?} |
| */ |
| function getBodyNode() { |
| if (typeof document != 'undefined') { |
| return document.body; |
| } |
| return null; |
| } |
| const matchesElement = _matches; |
| const containsElement = _contains; |
| const invokeQuery = _query; |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| /** |
| * \@experimental |
| */ |
| class NoopAnimationDriver { |
| /** |
| * @param {?} prop |
| * @return {?} |
| */ |
| validateStyleProperty(prop) { return validateStyleProperty(prop); } |
| /** |
| * @param {?} element |
| * @param {?} selector |
| * @return {?} |
| */ |
| matchesElement(element, selector) { |
| return matchesElement(element, selector); |
| } |
| /** |
| * @param {?} elm1 |
| * @param {?} elm2 |
| * @return {?} |
| */ |
| containsElement(elm1, elm2) { return containsElement(elm1, elm2); } |
| /** |
| * @param {?} element |
| * @param {?} selector |
| * @param {?} multi |
| * @return {?} |
| */ |
| query(element, selector, multi) { |
| return invokeQuery(element, selector, multi); |
| } |
| /** |
| * @param {?} element |
| * @param {?} prop |
| * @param {?=} defaultValue |
| * @return {?} |
| */ |
| computeStyle(element, prop, defaultValue) { |
| return defaultValue || ''; |
| } |
| /** |
| * @param {?} element |
| * @param {?} keyframes |
| * @param {?} duration |
| * @param {?} delay |
| * @param {?} easing |
| * @param {?=} previousPlayers |
| * @return {?} |
| */ |
| animate(element, keyframes, duration, delay, easing, previousPlayers = []) { |
| return new NoopAnimationPlayer(); |
| } |
| } |
| /** |
| * \@experimental |
| * @abstract |
| */ |
| class AnimationDriver { |
| } |
| AnimationDriver.NOOP = new NoopAnimationDriver(); |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| const ONE_SECOND = 1000; |
| const SUBSTITUTION_EXPR_START = '{{'; |
| const SUBSTITUTION_EXPR_END = '}}'; |
| const ENTER_CLASSNAME = 'ng-enter'; |
| const LEAVE_CLASSNAME = 'ng-leave'; |
| |
| |
| const NG_TRIGGER_CLASSNAME = 'ng-trigger'; |
| const NG_TRIGGER_SELECTOR = '.ng-trigger'; |
| const NG_ANIMATING_CLASSNAME = 'ng-animating'; |
| const NG_ANIMATING_SELECTOR = '.ng-animating'; |
| /** |
| * @param {?} value |
| * @return {?} |
| */ |
| function resolveTimingValue(value) { |
| if (typeof value == 'number') |
| return value; |
| const /** @type {?} */ matches = (/** @type {?} */ (value)).match(/^(-?[\.\d]+)(m?s)/); |
| if (!matches || matches.length < 2) |
| return 0; |
| return _convertTimeValueToMS(parseFloat(matches[1]), matches[2]); |
| } |
| /** |
| * @param {?} value |
| * @param {?} unit |
| * @return {?} |
| */ |
| function _convertTimeValueToMS(value, unit) { |
| switch (unit) { |
| case 's': |
| return value * ONE_SECOND; |
| default: |
| // ms or something else |
| return value; |
| } |
| } |
| /** |
| * @param {?} timings |
| * @param {?} errors |
| * @param {?=} allowNegativeValues |
| * @return {?} |
| */ |
| function resolveTiming(timings, errors, allowNegativeValues) { |
| return timings.hasOwnProperty('duration') ? /** @type {?} */ (timings) : |
| parseTimeExpression(/** @type {?} */ (timings), errors, allowNegativeValues); |
| } |
| /** |
| * @param {?} exp |
| * @param {?} errors |
| * @param {?=} allowNegativeValues |
| * @return {?} |
| */ |
| function parseTimeExpression(exp, errors, allowNegativeValues) { |
| const /** @type {?} */ regex = /^(-?[\.\d]+)(m?s)(?:\s+(-?[\.\d]+)(m?s))?(?:\s+([-a-z]+(?:\(.+?\))?))?$/i; |
| let /** @type {?} */ duration; |
| let /** @type {?} */ delay = 0; |
| let /** @type {?} */ easing = ''; |
| if (typeof exp === 'string') { |
| const /** @type {?} */ matches = exp.match(regex); |
| if (matches === null) { |
| errors.push(`The provided timing value "${exp}" is invalid.`); |
| return { duration: 0, delay: 0, easing: '' }; |
| } |
| duration = _convertTimeValueToMS(parseFloat(matches[1]), matches[2]); |
| const /** @type {?} */ delayMatch = matches[3]; |
| if (delayMatch != null) { |
| delay = _convertTimeValueToMS(Math.floor(parseFloat(delayMatch)), matches[4]); |
| } |
| const /** @type {?} */ easingVal = matches[5]; |
| if (easingVal) { |
| easing = easingVal; |
| } |
| } |
| else { |
| duration = /** @type {?} */ (exp); |
| } |
| if (!allowNegativeValues) { |
| let /** @type {?} */ containsErrors = false; |
| let /** @type {?} */ startIndex = errors.length; |
| if (duration < 0) { |
| errors.push(`Duration values below 0 are not allowed for this animation step.`); |
| containsErrors = true; |
| } |
| if (delay < 0) { |
| errors.push(`Delay values below 0 are not allowed for this animation step.`); |
| containsErrors = true; |
| } |
| if (containsErrors) { |
| errors.splice(startIndex, 0, `The provided timing value "${exp}" is invalid.`); |
| } |
| } |
| return { duration, delay, easing }; |
| } |
| /** |
| * @param {?} obj |
| * @param {?=} destination |
| * @return {?} |
| */ |
| function copyObj(obj, destination = {}) { |
| Object.keys(obj).forEach(prop => { destination[prop] = obj[prop]; }); |
| return destination; |
| } |
| /** |
| * @param {?} styles |
| * @return {?} |
| */ |
| function normalizeStyles(styles) { |
| const /** @type {?} */ normalizedStyles = {}; |
| if (Array.isArray(styles)) { |
| styles.forEach(data => copyStyles(data, false, normalizedStyles)); |
| } |
| else { |
| copyStyles(styles, false, normalizedStyles); |
| } |
| return normalizedStyles; |
| } |
| /** |
| * @param {?} styles |
| * @param {?} readPrototype |
| * @param {?=} destination |
| * @return {?} |
| */ |
| function copyStyles(styles, readPrototype, destination = {}) { |
| if (readPrototype) { |
| // we make use of a for-in loop so that the |
| // prototypically inherited properties are |
| // revealed from the backFill map |
| for (let /** @type {?} */ prop in styles) { |
| destination[prop] = styles[prop]; |
| } |
| } |
| else { |
| copyObj(styles, destination); |
| } |
| return destination; |
| } |
| /** |
| * @param {?} element |
| * @param {?} styles |
| * @return {?} |
| */ |
| function setStyles(element, styles) { |
| if (element['style']) { |
| Object.keys(styles).forEach(prop => { |
| const /** @type {?} */ camelProp = dashCaseToCamelCase(prop); |
| element.style[camelProp] = styles[prop]; |
| }); |
| } |
| } |
| /** |
| * @param {?} element |
| * @param {?} styles |
| * @return {?} |
| */ |
| function eraseStyles(element, styles) { |
| if (element['style']) { |
| Object.keys(styles).forEach(prop => { |
| const /** @type {?} */ camelProp = dashCaseToCamelCase(prop); |
| element.style[camelProp] = ''; |
| }); |
| } |
| } |
| /** |
| * @param {?} steps |
| * @return {?} |
| */ |
| function normalizeAnimationEntry(steps) { |
| if (Array.isArray(steps)) { |
| if (steps.length == 1) |
| return steps[0]; |
| return sequence(steps); |
| } |
| return /** @type {?} */ (steps); |
| } |
| /** |
| * @param {?} value |
| * @param {?} options |
| * @param {?} errors |
| * @return {?} |
| */ |
| function validateStyleParams(value, options, errors) { |
| const /** @type {?} */ params = options.params || {}; |
| const /** @type {?} */ matches = extractStyleParams(value); |
| if (matches.length) { |
| matches.forEach(varName => { |
| if (!params.hasOwnProperty(varName)) { |
| errors.push(`Unable to resolve the local animation param ${varName} in the given list of values`); |
| } |
| }); |
| } |
| } |
| const PARAM_REGEX = new RegExp(`${SUBSTITUTION_EXPR_START}\\s*(.+?)\\s*${SUBSTITUTION_EXPR_END}`, 'g'); |
| /** |
| * @param {?} value |
| * @return {?} |
| */ |
| function extractStyleParams(value) { |
| let /** @type {?} */ params = []; |
| if (typeof value === 'string') { |
| const /** @type {?} */ val = value.toString(); |
| let /** @type {?} */ match; |
| while (match = PARAM_REGEX.exec(val)) { |
| params.push(/** @type {?} */ (match[1])); |
| } |
| PARAM_REGEX.lastIndex = 0; |
| } |
| return params; |
| } |
| /** |
| * @param {?} value |
| * @param {?} params |
| * @param {?} errors |
| * @return {?} |
| */ |
| function interpolateParams(value, params, errors) { |
| const /** @type {?} */ original = value.toString(); |
| const /** @type {?} */ str = original.replace(PARAM_REGEX, (_, varName) => { |
| let /** @type {?} */ localVal = params[varName]; |
| // this means that the value was never overidden by the data passed in by the user |
| if (!params.hasOwnProperty(varName)) { |
| errors.push(`Please provide a value for the animation param ${varName}`); |
| localVal = ''; |
| } |
| return localVal.toString(); |
| }); |
| // we do this to assert that numeric values stay as they are |
| return str == original ? value : str; |
| } |
| /** |
| * @param {?} iterator |
| * @return {?} |
| */ |
| function iteratorToArray(iterator) { |
| const /** @type {?} */ arr = []; |
| let /** @type {?} */ item = iterator.next(); |
| while (!item.done) { |
| arr.push(item.value); |
| item = iterator.next(); |
| } |
| return arr; |
| } |
| /** |
| * @param {?} source |
| * @param {?} destination |
| * @return {?} |
| */ |
| |
| const DASH_CASE_REGEXP = /-+([a-z0-9])/g; |
| /** |
| * @param {?} input |
| * @return {?} |
| */ |
| function dashCaseToCamelCase(input) { |
| return input.replace(DASH_CASE_REGEXP, (...m) => m[1].toUpperCase()); |
| } |
| /** |
| * @param {?} duration |
| * @param {?} delay |
| * @return {?} |
| */ |
| function allowPreviousPlayerStylesMerge(duration, delay) { |
| return duration === 0 || delay === 0; |
| } |
| /** |
| * @param {?} visitor |
| * @param {?} node |
| * @param {?} context |
| * @return {?} |
| */ |
| function visitDslNode(visitor, node, context) { |
| switch (node.type) { |
| case 7 /* Trigger */: |
| return visitor.visitTrigger(node, context); |
| case 0 /* State */: |
| return visitor.visitState(node, context); |
| case 1 /* Transition */: |
| return visitor.visitTransition(node, context); |
| case 2 /* Sequence */: |
| return visitor.visitSequence(node, context); |
| case 3 /* Group */: |
| return visitor.visitGroup(node, context); |
| case 4 /* Animate */: |
| return visitor.visitAnimate(node, context); |
| case 5 /* Keyframes */: |
| return visitor.visitKeyframes(node, context); |
| case 6 /* Style */: |
| return visitor.visitStyle(node, context); |
| case 8 /* Reference */: |
| return visitor.visitReference(node, context); |
| case 9 /* AnimateChild */: |
| return visitor.visitAnimateChild(node, context); |
| case 10 /* AnimateRef */: |
| return visitor.visitAnimateRef(node, context); |
| case 11 /* Query */: |
| return visitor.visitQuery(node, context); |
| case 12 /* Stagger */: |
| return visitor.visitStagger(node, context); |
| default: |
| throw new Error(`Unable to resolve animation metadata node #${node.type}`); |
| } |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| /** |
| * @license |
| * Copyright Google Inc. All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| const ANY_STATE = '*'; |
| /** |
| * @param {?} transitionValue |
| * @param {?} errors |
| * @return {?} |
| */ |
| function parseTransitionExpr(transitionValue, errors) { |
| const /** @type {?} */ expressions = []; |
| if (typeof transitionValue == 'string') { |
| (/** @type {?} */ (transitionValue)) |
| .split(/\s*,\s*/) |
| .forEach(str => parseInnerTransitionStr(str, expressions, errors)); |
| } |
| else { |
| expressions.push(/** @type {?} */ (transitionValue)); |
| } |
| return expressions; |
| } |
| /** |
| * @param {?} eventStr |
| * @param {?} expressions |
| * @param {?} errors |
| * @return {?} |
| */ |
| function parseInnerTransitionStr(eventStr, expressions, errors) { |
| if (eventStr[0] == ':') { |
| const /** @type {?} */ result = parseAnimationAlias(eventStr, errors); |
| if (typeof result == 'function') { |
| expressions.push(result); |
| return; |
| } |
| eventStr = /** @type {?} */ (result); |
| } |
| const /** @type {?} */ match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/); |
| if (match == null || match.length < 4) { |
| errors.push(`The provided transition expression "${eventStr}" is not supported`); |
| return expressions; |
| } |
| const /** @type {?} */ fromState = match[1]; |
| const /** @type {?} */ separator = match[2]; |
| const /** @type {?} */ toState = match[3]; |
| expressions.push(makeLambdaFromStates(fromState, toState)); |
| const /** @type {?} */ isFullAnyStateExpr = fromState == ANY_STATE && toState == ANY_STATE; |
| if (separator[0] == '<' && !isFullAnyStateExpr) { |
| expressions.push(makeLambdaFromStates(toState, fromState)); |
| } |
| } |
| /** |
| * @param {?} alias |
| * @param {?} errors |
| * @return {?} |
| */ |
| function parseAnimationAlias(alias, errors) { |
| switch (alias) { |
| case ':enter': |
| return 'void => *'; |
| case ':leave': |
| return '* => void'; |
| case ':increment': |
| return (fromState, toState) => parseFloat(toState) > parseFloat(fromState); |
| case ':decrement': |
| return (fromState, toState) => parseFloat(toState) < parseFloat(fromState); |
| default: |
| errors.push(`The transition alias value "${alias}" is not supported`); |
| return '* => *'; |
| } |
| } |
| // DO NOT REFACTOR ... keep the follow set instantiations |
| // with the values intact (closure compiler for some reason |
| // removes follow-up lines that add the values outside of |
| // the constructor... |
| const TRUE_BOOLEAN_VALUES = new Set(['true', '1']); |
| const FALSE_BOOLEAN_VALUES = new Set(['false', '0']); |
| /** |
| * @param {?} lhs |
| * @param {?} rhs |
| * @return {?} |
| */ |
| function makeLambdaFromStates(lhs, rhs) { |
| const /** @type {?} */ LHS_MATCH_BOOLEAN = TRUE_BOOLEAN_VALUES.has(lhs) || FALSE_BOOLEAN_VALUES.has(lhs); |
| const /** @type {?} */ RHS_MATCH_BOOLEAN = TRUE_BOOLEAN_VALUES.has(rhs) || FALSE_BOOLEAN_VALUES.has(rhs); |
| return (fromState, toState) => { |
| let /** @type {?} */ lhsMatch = lhs == ANY_STATE || lhs == fromState; |
| let /** @type {?} */ rhsMatch = rhs == ANY_STATE || rhs == toState; |
| if (!lhsMatch && LHS_MATCH_BOOLEAN && typeof fromState === 'boolean') { |
| lhsMatch = fromState ? TRUE_BOOLEAN_VALUES.has(lhs) : FALSE_BOOLEAN_VALUES.has(lhs); |
| } |
| if (!rhsMatch && RHS_MATCH_BOOLEAN && typeof toState === 'boolean') { |
| rhsMatch = toState ? TRUE_BOOLEAN_VALUES.has(rhs) : FALSE_BOOLEAN_VALUES.has(rhs); |
| } |
| return lhsMatch && rhsMatch; |
| }; |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| const SELF_TOKEN = ':self'; |
| const SELF_TOKEN_REGEX = new RegExp(`\s*${SELF_TOKEN}\s*,?`, 'g'); |
| /** |
| * @param {?} driver |
| * @param {?} metadata |
| * @param {?} errors |
| * @return {?} |
| */ |
| function buildAnimationAst(driver, metadata, errors) { |
| return new AnimationAstBuilderVisitor(driver).build(metadata, errors); |
| } |
| const ROOT_SELECTOR = ''; |
| class AnimationAstBuilderVisitor { |
| /** |
| * @param {?} _driver |
| */ |
| constructor(_driver) { |
| this._driver = _driver; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} errors |
| * @return {?} |
| */ |
| build(metadata, errors) { |
| const /** @type {?} */ context = new AnimationAstBuilderContext(errors); |
| this._resetContextStyleTimingState(context); |
| return /** @type {?} */ (visitDslNode(this, normalizeAnimationEntry(metadata), context)); |
| } |
| /** |
| * @param {?} context |
| * @return {?} |
| */ |
| _resetContextStyleTimingState(context) { |
| context.currentQuerySelector = ROOT_SELECTOR; |
| context.collectedStyles = {}; |
| context.collectedStyles[ROOT_SELECTOR] = {}; |
| context.currentTime = 0; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitTrigger(metadata, context) { |
| let /** @type {?} */ queryCount = context.queryCount = 0; |
| let /** @type {?} */ depCount = context.depCount = 0; |
| const /** @type {?} */ states = []; |
| const /** @type {?} */ transitions = []; |
| if (metadata.name.charAt(0) == '@') { |
| context.errors.push('animation triggers cannot be prefixed with an `@` sign (e.g. trigger(\'@foo\', [...]))'); |
| } |
| metadata.definitions.forEach(def => { |
| this._resetContextStyleTimingState(context); |
| if (def.type == 0 /* State */) { |
| const /** @type {?} */ stateDef = /** @type {?} */ (def); |
| const /** @type {?} */ name = stateDef.name; |
| name.split(/\s*,\s*/).forEach(n => { |
| stateDef.name = n; |
| states.push(this.visitState(stateDef, context)); |
| }); |
| stateDef.name = name; |
| } |
| else if (def.type == 1 /* Transition */) { |
| const /** @type {?} */ transition = this.visitTransition(/** @type {?} */ (def), context); |
| queryCount += transition.queryCount; |
| depCount += transition.depCount; |
| transitions.push(transition); |
| } |
| else { |
| context.errors.push('only state() and transition() definitions can sit inside of a trigger()'); |
| } |
| }); |
| return { |
| type: 7 /* Trigger */, |
| name: metadata.name, states, transitions, queryCount, depCount, |
| options: null |
| }; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitState(metadata, context) { |
| const /** @type {?} */ styleAst = this.visitStyle(metadata.styles, context); |
| const /** @type {?} */ astParams = (metadata.options && metadata.options.params) || null; |
| if (styleAst.containsDynamicStyles) { |
| const /** @type {?} */ missingSubs = new Set(); |
| const /** @type {?} */ params = astParams || {}; |
| styleAst.styles.forEach(value => { |
| if (isObject(value)) { |
| const /** @type {?} */ stylesObj = /** @type {?} */ (value); |
| Object.keys(stylesObj).forEach(prop => { |
| extractStyleParams(stylesObj[prop]).forEach(sub => { |
| if (!params.hasOwnProperty(sub)) { |
| missingSubs.add(sub); |
| } |
| }); |
| }); |
| } |
| }); |
| if (missingSubs.size) { |
| const /** @type {?} */ missingSubsArr = iteratorToArray(missingSubs.values()); |
| context.errors.push(`state("${metadata.name}", ...) must define default values for all the following style substitutions: ${missingSubsArr.join(', ')}`); |
| } |
| } |
| return { |
| type: 0 /* State */, |
| name: metadata.name, |
| style: styleAst, |
| options: astParams ? { params: astParams } : null |
| }; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitTransition(metadata, context) { |
| context.queryCount = 0; |
| context.depCount = 0; |
| const /** @type {?} */ animation = visitDslNode(this, normalizeAnimationEntry(metadata.animation), context); |
| const /** @type {?} */ matchers = parseTransitionExpr(metadata.expr, context.errors); |
| return { |
| type: 1 /* Transition */, |
| matchers, |
| animation, |
| queryCount: context.queryCount, |
| depCount: context.depCount, |
| options: normalizeAnimationOptions(metadata.options) |
| }; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitSequence(metadata, context) { |
| return { |
| type: 2 /* Sequence */, |
| steps: metadata.steps.map(s => visitDslNode(this, s, context)), |
| options: normalizeAnimationOptions(metadata.options) |
| }; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitGroup(metadata, context) { |
| const /** @type {?} */ currentTime = context.currentTime; |
| let /** @type {?} */ furthestTime = 0; |
| const /** @type {?} */ steps = metadata.steps.map(step => { |
| context.currentTime = currentTime; |
| const /** @type {?} */ innerAst = visitDslNode(this, step, context); |
| furthestTime = Math.max(furthestTime, context.currentTime); |
| return innerAst; |
| }); |
| context.currentTime = furthestTime; |
| return { |
| type: 3 /* Group */, |
| steps, |
| options: normalizeAnimationOptions(metadata.options) |
| }; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitAnimate(metadata, context) { |
| const /** @type {?} */ timingAst = constructTimingAst(metadata.timings, context.errors); |
| context.currentAnimateTimings = timingAst; |
| let /** @type {?} */ styleAst; |
| let /** @type {?} */ styleMetadata = metadata.styles ? metadata.styles : style({}); |
| if (styleMetadata.type == 5 /* Keyframes */) { |
| styleAst = this.visitKeyframes(/** @type {?} */ (styleMetadata), context); |
| } |
| else { |
| let /** @type {?} */ styleMetadata = /** @type {?} */ (metadata.styles); |
| let /** @type {?} */ isEmpty = false; |
| if (!styleMetadata) { |
| isEmpty = true; |
| const /** @type {?} */ newStyleData = {}; |
| if (timingAst.easing) { |
| newStyleData['easing'] = timingAst.easing; |
| } |
| styleMetadata = style(newStyleData); |
| } |
| context.currentTime += timingAst.duration + timingAst.delay; |
| const /** @type {?} */ _styleAst = this.visitStyle(styleMetadata, context); |
| _styleAst.isEmptyStep = isEmpty; |
| styleAst = _styleAst; |
| } |
| context.currentAnimateTimings = null; |
| return { |
| type: 4 /* Animate */, |
| timings: timingAst, |
| style: styleAst, |
| options: null |
| }; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitStyle(metadata, context) { |
| const /** @type {?} */ ast = this._makeStyleAst(metadata, context); |
| this._validateStyleAst(ast, context); |
| return ast; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| _makeStyleAst(metadata, context) { |
| const /** @type {?} */ styles = []; |
| if (Array.isArray(metadata.styles)) { |
| (/** @type {?} */ (metadata.styles)).forEach(styleTuple => { |
| if (typeof styleTuple == 'string') { |
| if (styleTuple == AUTO_STYLE) { |
| styles.push(/** @type {?} */ (styleTuple)); |
| } |
| else { |
| context.errors.push(`The provided style string value ${styleTuple} is not allowed.`); |
| } |
| } |
| else { |
| styles.push(/** @type {?} */ (styleTuple)); |
| } |
| }); |
| } |
| else { |
| styles.push(metadata.styles); |
| } |
| let /** @type {?} */ containsDynamicStyles = false; |
| let /** @type {?} */ collectedEasing = null; |
| styles.forEach(styleData => { |
| if (isObject(styleData)) { |
| const /** @type {?} */ styleMap = /** @type {?} */ (styleData); |
| const /** @type {?} */ easing = styleMap['easing']; |
| if (easing) { |
| collectedEasing = /** @type {?} */ (easing); |
| delete styleMap['easing']; |
| } |
| if (!containsDynamicStyles) { |
| for (let /** @type {?} */ prop in styleMap) { |
| const /** @type {?} */ value = styleMap[prop]; |
| if (value.toString().indexOf(SUBSTITUTION_EXPR_START) >= 0) { |
| containsDynamicStyles = true; |
| break; |
| } |
| } |
| } |
| } |
| }); |
| return { |
| type: 6 /* Style */, |
| styles, |
| easing: collectedEasing, |
| offset: metadata.offset, containsDynamicStyles, |
| options: null |
| }; |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| _validateStyleAst(ast, context) { |
| const /** @type {?} */ timings = context.currentAnimateTimings; |
| let /** @type {?} */ endTime = context.currentTime; |
| let /** @type {?} */ startTime = context.currentTime; |
| if (timings && startTime > 0) { |
| startTime -= timings.duration + timings.delay; |
| } |
| ast.styles.forEach(tuple => { |
| if (typeof tuple == 'string') |
| return; |
| Object.keys(tuple).forEach(prop => { |
| if (!this._driver.validateStyleProperty(prop)) { |
| context.errors.push(`The provided animation property "${prop}" is not a supported CSS property for animations`); |
| return; |
| } |
| const /** @type {?} */ collectedStyles = context.collectedStyles[/** @type {?} */ ((context.currentQuerySelector))]; |
| const /** @type {?} */ collectedEntry = collectedStyles[prop]; |
| let /** @type {?} */ updateCollectedStyle = true; |
| if (collectedEntry) { |
| if (startTime != endTime && startTime >= collectedEntry.startTime && |
| endTime <= collectedEntry.endTime) { |
| context.errors.push(`The CSS property "${prop}" that exists between the times of "${collectedEntry.startTime}ms" and "${collectedEntry.endTime}ms" is also being animated in a parallel animation between the times of "${startTime}ms" and "${endTime}ms"`); |
| updateCollectedStyle = false; |
| } |
| // we always choose the smaller start time value since we |
| // want to have a record of the entire animation window where |
| // the style property is being animated in between |
| startTime = collectedEntry.startTime; |
| } |
| if (updateCollectedStyle) { |
| collectedStyles[prop] = { startTime, endTime }; |
| } |
| if (context.options) { |
| validateStyleParams(tuple[prop], context.options, context.errors); |
| } |
| }); |
| }); |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitKeyframes(metadata, context) { |
| const /** @type {?} */ ast = { type: 5 /* Keyframes */, styles: [], options: null }; |
| if (!context.currentAnimateTimings) { |
| context.errors.push(`keyframes() must be placed inside of a call to animate()`); |
| return ast; |
| } |
| const /** @type {?} */ MAX_KEYFRAME_OFFSET = 1; |
| let /** @type {?} */ totalKeyframesWithOffsets = 0; |
| const /** @type {?} */ offsets = []; |
| let /** @type {?} */ offsetsOutOfOrder = false; |
| let /** @type {?} */ keyframesOutOfRange = false; |
| let /** @type {?} */ previousOffset = 0; |
| const /** @type {?} */ keyframes = metadata.steps.map(styles => { |
| const /** @type {?} */ style$$1 = this._makeStyleAst(styles, context); |
| let /** @type {?} */ offsetVal = style$$1.offset != null ? style$$1.offset : consumeOffset(style$$1.styles); |
| let /** @type {?} */ offset = 0; |
| if (offsetVal != null) { |
| totalKeyframesWithOffsets++; |
| offset = style$$1.offset = offsetVal; |
| } |
| keyframesOutOfRange = keyframesOutOfRange || offset < 0 || offset > 1; |
| offsetsOutOfOrder = offsetsOutOfOrder || offset < previousOffset; |
| previousOffset = offset; |
| offsets.push(offset); |
| return style$$1; |
| }); |
| if (keyframesOutOfRange) { |
| context.errors.push(`Please ensure that all keyframe offsets are between 0 and 1`); |
| } |
| if (offsetsOutOfOrder) { |
| context.errors.push(`Please ensure that all keyframe offsets are in order`); |
| } |
| const /** @type {?} */ length = metadata.steps.length; |
| let /** @type {?} */ generatedOffset = 0; |
| if (totalKeyframesWithOffsets > 0 && totalKeyframesWithOffsets < length) { |
| context.errors.push(`Not all style() steps within the declared keyframes() contain offsets`); |
| } |
| else if (totalKeyframesWithOffsets == 0) { |
| generatedOffset = MAX_KEYFRAME_OFFSET / (length - 1); |
| } |
| const /** @type {?} */ limit = length - 1; |
| const /** @type {?} */ currentTime = context.currentTime; |
| const /** @type {?} */ currentAnimateTimings = /** @type {?} */ ((context.currentAnimateTimings)); |
| const /** @type {?} */ animateDuration = currentAnimateTimings.duration; |
| keyframes.forEach((kf, i) => { |
| const /** @type {?} */ offset = generatedOffset > 0 ? (i == limit ? 1 : (generatedOffset * i)) : offsets[i]; |
| const /** @type {?} */ durationUpToThisFrame = offset * animateDuration; |
| context.currentTime = currentTime + currentAnimateTimings.delay + durationUpToThisFrame; |
| currentAnimateTimings.duration = durationUpToThisFrame; |
| this._validateStyleAst(kf, context); |
| kf.offset = offset; |
| ast.styles.push(kf); |
| }); |
| return ast; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitReference(metadata, context) { |
| return { |
| type: 8 /* Reference */, |
| animation: visitDslNode(this, normalizeAnimationEntry(metadata.animation), context), |
| options: normalizeAnimationOptions(metadata.options) |
| }; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitAnimateChild(metadata, context) { |
| context.depCount++; |
| return { |
| type: 9 /* AnimateChild */, |
| options: normalizeAnimationOptions(metadata.options) |
| }; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitAnimateRef(metadata, context) { |
| return { |
| type: 10 /* AnimateRef */, |
| animation: this.visitReference(metadata.animation, context), |
| options: normalizeAnimationOptions(metadata.options) |
| }; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitQuery(metadata, context) { |
| const /** @type {?} */ parentSelector = /** @type {?} */ ((context.currentQuerySelector)); |
| const /** @type {?} */ options = /** @type {?} */ ((metadata.options || {})); |
| context.queryCount++; |
| context.currentQuery = metadata; |
| const [selector, includeSelf] = normalizeSelector(metadata.selector); |
| context.currentQuerySelector = |
| parentSelector.length ? (parentSelector + ' ' + selector) : selector; |
| getOrSetAsInMap(context.collectedStyles, context.currentQuerySelector, {}); |
| const /** @type {?} */ animation = visitDslNode(this, normalizeAnimationEntry(metadata.animation), context); |
| context.currentQuery = null; |
| context.currentQuerySelector = parentSelector; |
| return { |
| type: 11 /* Query */, |
| selector, |
| limit: options.limit || 0, |
| optional: !!options.optional, includeSelf, animation, |
| originalSelector: metadata.selector, |
| options: normalizeAnimationOptions(metadata.options) |
| }; |
| } |
| /** |
| * @param {?} metadata |
| * @param {?} context |
| * @return {?} |
| */ |
| visitStagger(metadata, context) { |
| if (!context.currentQuery) { |
| context.errors.push(`stagger() can only be used inside of query()`); |
| } |
| const /** @type {?} */ timings = metadata.timings === 'full' ? |
| { duration: 0, delay: 0, easing: 'full' } : |
| resolveTiming(metadata.timings, context.errors, true); |
| return { |
| type: 12 /* Stagger */, |
| animation: visitDslNode(this, normalizeAnimationEntry(metadata.animation), context), timings, |
| options: null |
| }; |
| } |
| } |
| /** |
| * @param {?} selector |
| * @return {?} |
| */ |
| function normalizeSelector(selector) { |
| const /** @type {?} */ hasAmpersand = selector.split(/\s*,\s*/).find(token => token == SELF_TOKEN) ? true : false; |
| if (hasAmpersand) { |
| selector = selector.replace(SELF_TOKEN_REGEX, ''); |
| } |
| // the :enter and :leave selectors are filled in at runtime during timeline building |
| selector = selector.replace(/@\*/g, NG_TRIGGER_SELECTOR) |
| .replace(/@\w+/g, match => NG_TRIGGER_SELECTOR + '-' + match.substr(1)) |
| .replace(/:animating/g, NG_ANIMATING_SELECTOR); |
| return [selector, hasAmpersand]; |
| } |
| /** |
| * @param {?} obj |
| * @return {?} |
| */ |
| function normalizeParams(obj) { |
| return obj ? copyObj(obj) : null; |
| } |
| class AnimationAstBuilderContext { |
| /** |
| * @param {?} errors |
| */ |
| constructor(errors) { |
| this.errors = errors; |
| this.queryCount = 0; |
| this.depCount = 0; |
| this.currentTransition = null; |
| this.currentQuery = null; |
| this.currentQuerySelector = null; |
| this.currentAnimateTimings = null; |
| this.currentTime = 0; |
| this.collectedStyles = {}; |
| this.options = null; |
| } |
| } |
| /** |
| * @param {?} styles |
| * @return {?} |
| */ |
| function consumeOffset(styles) { |
| if (typeof styles == 'string') |
| return null; |
| let /** @type {?} */ offset = null; |
| if (Array.isArray(styles)) { |
| styles.forEach(styleTuple => { |
| if (isObject(styleTuple) && styleTuple.hasOwnProperty('offset')) { |
| const /** @type {?} */ obj = /** @type {?} */ (styleTuple); |
| offset = parseFloat(/** @type {?} */ (obj['offset'])); |
| delete obj['offset']; |
| } |
| }); |
| } |
| else if (isObject(styles) && styles.hasOwnProperty('offset')) { |
| const /** @type {?} */ obj = /** @type {?} */ (styles); |
| offset = parseFloat(/** @type {?} */ (obj['offset'])); |
| delete obj['offset']; |
| } |
| return offset; |
| } |
| /** |
| * @param {?} value |
| * @return {?} |
| */ |
| function isObject(value) { |
| return !Array.isArray(value) && typeof value == 'object'; |
| } |
| /** |
| * @param {?} value |
| * @param {?} errors |
| * @return {?} |
| */ |
| function constructTimingAst(value, errors) { |
| let /** @type {?} */ timings = null; |
| if (value.hasOwnProperty('duration')) { |
| timings = /** @type {?} */ (value); |
| } |
| else if (typeof value == 'number') { |
| const /** @type {?} */ duration = resolveTiming(/** @type {?} */ (value), errors).duration; |
| return makeTimingAst(/** @type {?} */ (duration), 0, ''); |
| } |
| const /** @type {?} */ strValue = /** @type {?} */ (value); |
| const /** @type {?} */ isDynamic = strValue.split(/\s+/).some(v => v.charAt(0) == '{' && v.charAt(1) == '{'); |
| if (isDynamic) { |
| const /** @type {?} */ ast = /** @type {?} */ (makeTimingAst(0, 0, '')); |
| ast.dynamic = true; |
| ast.strValue = strValue; |
| return /** @type {?} */ (ast); |
| } |
| timings = timings || resolveTiming(strValue, errors); |
| return makeTimingAst(timings.duration, timings.delay, timings.easing); |
| } |
| /** |
| * @param {?} options |
| * @return {?} |
| */ |
| function normalizeAnimationOptions(options) { |
| if (options) { |
| options = copyObj(options); |
| if (options['params']) { |
| options['params'] = /** @type {?} */ ((normalizeParams(options['params']))); |
| } |
| } |
| else { |
| options = {}; |
| } |
| return options; |
| } |
| /** |
| * @param {?} duration |
| * @param {?} delay |
| * @param {?} easing |
| * @return {?} |
| */ |
| function makeTimingAst(duration, delay, easing) { |
| return { duration, delay, easing }; |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| /** |
| * @record |
| */ |
| |
| /** |
| * @param {?} element |
| * @param {?} keyframes |
| * @param {?} preStyleProps |
| * @param {?} postStyleProps |
| * @param {?} duration |
| * @param {?} delay |
| * @param {?=} easing |
| * @param {?=} subTimeline |
| * @return {?} |
| */ |
| function createTimelineInstruction(element, keyframes, preStyleProps, postStyleProps, duration, delay, easing = null, subTimeline = false) { |
| return { |
| type: 1 /* TimelineAnimation */, |
| element, |
| keyframes, |
| preStyleProps, |
| postStyleProps, |
| duration, |
| delay, |
| totalTime: duration + delay, easing, subTimeline |
| }; |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| class ElementInstructionMap { |
| constructor() { |
| this._map = new Map(); |
| } |
| /** |
| * @param {?} element |
| * @return {?} |
| */ |
| consume(element) { |
| let /** @type {?} */ instructions = this._map.get(element); |
| if (instructions) { |
| this._map.delete(element); |
| } |
| else { |
| instructions = []; |
| } |
| return instructions; |
| } |
| /** |
| * @param {?} element |
| * @param {?} instructions |
| * @return {?} |
| */ |
| append(element, instructions) { |
| let /** @type {?} */ existingInstructions = this._map.get(element); |
| if (!existingInstructions) { |
| this._map.set(element, existingInstructions = []); |
| } |
| existingInstructions.push(...instructions); |
| } |
| /** |
| * @param {?} element |
| * @return {?} |
| */ |
| has(element) { return this._map.has(element); } |
| /** |
| * @return {?} |
| */ |
| clear() { this._map.clear(); } |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| const ONE_FRAME_IN_MILLISECONDS = 1; |
| const ENTER_TOKEN = ':enter'; |
| const ENTER_TOKEN_REGEX = new RegExp(ENTER_TOKEN, 'g'); |
| const LEAVE_TOKEN = ':leave'; |
| const LEAVE_TOKEN_REGEX = new RegExp(LEAVE_TOKEN, 'g'); |
| /** |
| * @param {?} driver |
| * @param {?} rootElement |
| * @param {?} ast |
| * @param {?} enterClassName |
| * @param {?} leaveClassName |
| * @param {?=} startingStyles |
| * @param {?=} finalStyles |
| * @param {?=} options |
| * @param {?=} subInstructions |
| * @param {?=} errors |
| * @return {?} |
| */ |
| function buildAnimationTimelines(driver, rootElement, ast, enterClassName, leaveClassName, startingStyles = {}, finalStyles = {}, options, subInstructions, errors = []) { |
| return new AnimationTimelineBuilderVisitor().buildKeyframes(driver, rootElement, ast, enterClassName, leaveClassName, startingStyles, finalStyles, options, subInstructions, errors); |
| } |
| class AnimationTimelineBuilderVisitor { |
| /** |
| * @param {?} driver |
| * @param {?} rootElement |
| * @param {?} ast |
| * @param {?} enterClassName |
| * @param {?} leaveClassName |
| * @param {?} startingStyles |
| * @param {?} finalStyles |
| * @param {?} options |
| * @param {?=} subInstructions |
| * @param {?=} errors |
| * @return {?} |
| */ |
| buildKeyframes(driver, rootElement, ast, enterClassName, leaveClassName, startingStyles, finalStyles, options, subInstructions, errors = []) { |
| subInstructions = subInstructions || new ElementInstructionMap(); |
| const /** @type {?} */ context = new AnimationTimelineContext(driver, rootElement, subInstructions, enterClassName, leaveClassName, errors, []); |
| context.options = options; |
| context.currentTimeline.setStyles([startingStyles], null, context.errors, options); |
| visitDslNode(this, ast, context); |
| // this checks to see if an actual animation happened |
| const /** @type {?} */ timelines = context.timelines.filter(timeline => timeline.containsAnimation()); |
| if (timelines.length && Object.keys(finalStyles).length) { |
| const /** @type {?} */ tl = timelines[timelines.length - 1]; |
| if (!tl.allowOnlyTimelineStyles()) { |
| tl.setStyles([finalStyles], null, context.errors, options); |
| } |
| } |
| return timelines.length ? timelines.map(timeline => timeline.buildKeyframes()) : |
| [createTimelineInstruction(rootElement, [], [], [], 0, 0, '', false)]; |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitTrigger(ast, context) { |
| // these values are not visited in this AST |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitState(ast, context) { |
| // these values are not visited in this AST |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitTransition(ast, context) { |
| // these values are not visited in this AST |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitAnimateChild(ast, context) { |
| const /** @type {?} */ elementInstructions = context.subInstructions.consume(context.element); |
| if (elementInstructions) { |
| const /** @type {?} */ innerContext = context.createSubContext(ast.options); |
| const /** @type {?} */ startTime = context.currentTimeline.currentTime; |
| const /** @type {?} */ endTime = this._visitSubInstructions(elementInstructions, innerContext, /** @type {?} */ (innerContext.options)); |
| if (startTime != endTime) { |
| // we do this on the upper context because we created a sub context for |
| // the sub child animations |
| context.transformIntoNewTimeline(endTime); |
| } |
| } |
| context.previousNode = ast; |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitAnimateRef(ast, context) { |
| const /** @type {?} */ innerContext = context.createSubContext(ast.options); |
| innerContext.transformIntoNewTimeline(); |
| this.visitReference(ast.animation, innerContext); |
| context.transformIntoNewTimeline(innerContext.currentTimeline.currentTime); |
| context.previousNode = ast; |
| } |
| /** |
| * @param {?} instructions |
| * @param {?} context |
| * @param {?} options |
| * @return {?} |
| */ |
| _visitSubInstructions(instructions, context, options) { |
| const /** @type {?} */ startTime = context.currentTimeline.currentTime; |
| let /** @type {?} */ furthestTime = startTime; |
| // this is a special-case for when a user wants to skip a sub |
| // animation from being fired entirely. |
| const /** @type {?} */ duration = options.duration != null ? resolveTimingValue(options.duration) : null; |
| const /** @type {?} */ delay = options.delay != null ? resolveTimingValue(options.delay) : null; |
| if (duration !== 0) { |
| instructions.forEach(instruction => { |
| const /** @type {?} */ instructionTimings = context.appendInstructionToTimeline(instruction, duration, delay); |
| furthestTime = |
| Math.max(furthestTime, instructionTimings.duration + instructionTimings.delay); |
| }); |
| } |
| return furthestTime; |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitReference(ast, context) { |
| context.updateOptions(ast.options, true); |
| visitDslNode(this, ast.animation, context); |
| context.previousNode = ast; |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitSequence(ast, context) { |
| const /** @type {?} */ subContextCount = context.subContextCount; |
| let /** @type {?} */ ctx = context; |
| const /** @type {?} */ options = ast.options; |
| if (options && (options.params || options.delay)) { |
| ctx = context.createSubContext(options); |
| ctx.transformIntoNewTimeline(); |
| if (options.delay != null) { |
| if (ctx.previousNode.type == 6 /* Style */) { |
| ctx.currentTimeline.snapshotCurrentStyles(); |
| ctx.previousNode = DEFAULT_NOOP_PREVIOUS_NODE; |
| } |
| const /** @type {?} */ delay = resolveTimingValue(options.delay); |
| ctx.delayNextStep(delay); |
| } |
| } |
| if (ast.steps.length) { |
| ast.steps.forEach(s => visitDslNode(this, s, ctx)); |
| // this is here just incase the inner steps only contain or end with a style() call |
| ctx.currentTimeline.applyStylesToKeyframe(); |
| // this means that some animation function within the sequence |
| // ended up creating a sub timeline (which means the current |
| // timeline cannot overlap with the contents of the sequence) |
| if (ctx.subContextCount > subContextCount) { |
| ctx.transformIntoNewTimeline(); |
| } |
| } |
| context.previousNode = ast; |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitGroup(ast, context) { |
| const /** @type {?} */ innerTimelines = []; |
| let /** @type {?} */ furthestTime = context.currentTimeline.currentTime; |
| const /** @type {?} */ delay = ast.options && ast.options.delay ? resolveTimingValue(ast.options.delay) : 0; |
| ast.steps.forEach(s => { |
| const /** @type {?} */ innerContext = context.createSubContext(ast.options); |
| if (delay) { |
| innerContext.delayNextStep(delay); |
| } |
| visitDslNode(this, s, innerContext); |
| furthestTime = Math.max(furthestTime, innerContext.currentTimeline.currentTime); |
| innerTimelines.push(innerContext.currentTimeline); |
| }); |
| // this operation is run after the AST loop because otherwise |
| // if the parent timeline's collected styles were updated then |
| // it would pass in invalid data into the new-to-be forked items |
| innerTimelines.forEach(timeline => context.currentTimeline.mergeTimelineCollectedStyles(timeline)); |
| context.transformIntoNewTimeline(furthestTime); |
| context.previousNode = ast; |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| _visitTiming(ast, context) { |
| if ((/** @type {?} */ (ast)).dynamic) { |
| const /** @type {?} */ strValue = (/** @type {?} */ (ast)).strValue; |
| const /** @type {?} */ timingValue = context.params ? interpolateParams(strValue, context.params, context.errors) : strValue; |
| return resolveTiming(timingValue, context.errors); |
| } |
| else { |
| return { duration: ast.duration, delay: ast.delay, easing: ast.easing }; |
| } |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitAnimate(ast, context) { |
| const /** @type {?} */ timings = context.currentAnimateTimings = this._visitTiming(ast.timings, context); |
| const /** @type {?} */ timeline = context.currentTimeline; |
| if (timings.delay) { |
| context.incrementTime(timings.delay); |
| timeline.snapshotCurrentStyles(); |
| } |
| const /** @type {?} */ style$$1 = ast.style; |
| if (style$$1.type == 5 /* Keyframes */) { |
| this.visitKeyframes(style$$1, context); |
| } |
| else { |
| context.incrementTime(timings.duration); |
| this.visitStyle(/** @type {?} */ (style$$1), context); |
| timeline.applyStylesToKeyframe(); |
| } |
| context.currentAnimateTimings = null; |
| context.previousNode = ast; |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitStyle(ast, context) { |
| const /** @type {?} */ timeline = context.currentTimeline; |
| const /** @type {?} */ timings = /** @type {?} */ ((context.currentAnimateTimings)); |
| // this is a special case for when a style() call |
| // directly follows an animate() call (but not inside of an animate() call) |
| if (!timings && timeline.getCurrentStyleProperties().length) { |
| timeline.forwardFrame(); |
| } |
| const /** @type {?} */ easing = (timings && timings.easing) || ast.easing; |
| if (ast.isEmptyStep) { |
| timeline.applyEmptyStep(easing); |
| } |
| else { |
| timeline.setStyles(ast.styles, easing, context.errors, context.options); |
| } |
| context.previousNode = ast; |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitKeyframes(ast, context) { |
| const /** @type {?} */ currentAnimateTimings = /** @type {?} */ ((context.currentAnimateTimings)); |
| const /** @type {?} */ startTime = (/** @type {?} */ ((context.currentTimeline))).duration; |
| const /** @type {?} */ duration = currentAnimateTimings.duration; |
| const /** @type {?} */ innerContext = context.createSubContext(); |
| const /** @type {?} */ innerTimeline = innerContext.currentTimeline; |
| innerTimeline.easing = currentAnimateTimings.easing; |
| ast.styles.forEach(step => { |
| const /** @type {?} */ offset = step.offset || 0; |
| innerTimeline.forwardTime(offset * duration); |
| innerTimeline.setStyles(step.styles, step.easing, context.errors, context.options); |
| innerTimeline.applyStylesToKeyframe(); |
| }); |
| // this will ensure that the parent timeline gets all the styles from |
| // the child even if the new timeline below is not used |
| context.currentTimeline.mergeTimelineCollectedStyles(innerTimeline); |
| // we do this because the window between this timeline and the sub timeline |
| // should ensure that the styles within are exactly the same as they were before |
| context.transformIntoNewTimeline(startTime + duration); |
| context.previousNode = ast; |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitQuery(ast, context) { |
| // in the event that the first step before this is a style step we need |
| // to ensure the styles are applied before the children are animated |
| const /** @type {?} */ startTime = context.currentTimeline.currentTime; |
| const /** @type {?} */ options = /** @type {?} */ ((ast.options || {})); |
| const /** @type {?} */ delay = options.delay ? resolveTimingValue(options.delay) : 0; |
| if (delay && (context.previousNode.type === 6 /* Style */ || |
| (startTime == 0 && context.currentTimeline.getCurrentStyleProperties().length))) { |
| context.currentTimeline.snapshotCurrentStyles(); |
| context.previousNode = DEFAULT_NOOP_PREVIOUS_NODE; |
| } |
| let /** @type {?} */ furthestTime = startTime; |
| const /** @type {?} */ elms = context.invokeQuery(ast.selector, ast.originalSelector, ast.limit, ast.includeSelf, options.optional ? true : false, context.errors); |
| context.currentQueryTotal = elms.length; |
| let /** @type {?} */ sameElementTimeline = null; |
| elms.forEach((element, i) => { |
| context.currentQueryIndex = i; |
| const /** @type {?} */ innerContext = context.createSubContext(ast.options, element); |
| if (delay) { |
| innerContext.delayNextStep(delay); |
| } |
| if (element === context.element) { |
| sameElementTimeline = innerContext.currentTimeline; |
| } |
| visitDslNode(this, ast.animation, innerContext); |
| // this is here just incase the inner steps only contain or end |
| // with a style() call (which is here to signal that this is a preparatory |
| // call to style an element before it is animated again) |
| innerContext.currentTimeline.applyStylesToKeyframe(); |
| const /** @type {?} */ endTime = innerContext.currentTimeline.currentTime; |
| furthestTime = Math.max(furthestTime, endTime); |
| }); |
| context.currentQueryIndex = 0; |
| context.currentQueryTotal = 0; |
| context.transformIntoNewTimeline(furthestTime); |
| if (sameElementTimeline) { |
| context.currentTimeline.mergeTimelineCollectedStyles(sameElementTimeline); |
| context.currentTimeline.snapshotCurrentStyles(); |
| } |
| context.previousNode = ast; |
| } |
| /** |
| * @param {?} ast |
| * @param {?} context |
| * @return {?} |
| */ |
| visitStagger(ast, context) { |
| const /** @type {?} */ parentContext = /** @type {?} */ ((context.parentContext)); |
| const /** @type {?} */ tl = context.currentTimeline; |
| const /** @type {?} */ timings = ast.timings; |
| const /** @type {?} */ duration = Math.abs(timings.duration); |
| const /** @type {?} */ maxTime = duration * (context.currentQueryTotal - 1); |
| let /** @type {?} */ delay = duration * context.currentQueryIndex; |
| let /** @type {?} */ staggerTransformer = timings.duration < 0 ? 'reverse' : timings.easing; |
| switch (staggerTransformer) { |
| case 'reverse': |
| delay = maxTime - delay; |
| break; |
| case 'full': |
| delay = parentContext.currentStaggerTime; |
| break; |
| } |
| const /** @type {?} */ timeline = context.currentTimeline; |
| if (delay) { |
| timeline.delayNextStep(delay); |
| } |
| const /** @type {?} */ startingTime = timeline.currentTime; |
| visitDslNode(this, ast.animation, context); |
| context.previousNode = ast; |
| // time = duration + delay |
| // the reason why this computation is so complex is because |
| // the inner timeline may either have a delay value or a stretched |
| // keyframe depending on if a subtimeline is not used or is used. |
| parentContext.currentStaggerTime = |
| (tl.currentTime - startingTime) + (tl.startTime - parentContext.currentTimeline.startTime); |
| } |
| } |
| const DEFAULT_NOOP_PREVIOUS_NODE = /** @type {?} */ ({}); |
| class AnimationTimelineContext { |
| /** |
| * @param {?} _driver |
| * @param {?} element |
| * @param {?} subInstructions |
| * @param {?} _enterClassName |
| * @param {?} _leaveClassName |
| * @param {?} errors |
| * @param {?} timelines |
| * @param {?=} initialTimeline |
| */ |
| constructor(_driver, element, subInstructions, _enterClassName, _leaveClassName, errors, timelines, initialTimeline) { |
| this._driver = _driver; |
| this.element = element; |
| this.subInstructions = subInstructions; |
| this._enterClassName = _enterClassName; |
| this._leaveClassName = _leaveClassName; |
| this.errors = errors; |
| this.timelines = timelines; |
| this.parentContext = null; |
| this.currentAnimateTimings = null; |
| this.previousNode = DEFAULT_NOOP_PREVIOUS_NODE; |
| this.subContextCount = 0; |
| this.options = {}; |
| this.currentQueryIndex = 0; |
| this.currentQueryTotal = 0; |
| this.currentStaggerTime = 0; |
| this.currentTimeline = initialTimeline || new TimelineBuilder(this._driver, element, 0); |
| timelines.push(this.currentTimeline); |
| } |
| /** |
| * @return {?} |
| */ |
| get params() { return this.options.params; } |
| /** |
| * @param {?} options |
| * @param {?=} skipIfExists |
| * @return {?} |
| */ |
| updateOptions(options, skipIfExists) { |
| if (!options) |
| return; |
| const /** @type {?} */ newOptions = /** @type {?} */ (options); |
| let /** @type {?} */ optionsToUpdate = this.options; |
| // NOTE: this will get patched up when other animation methods support duration overrides |
| if (newOptions.duration != null) { |
| (/** @type {?} */ (optionsToUpdate)).duration = resolveTimingValue(newOptions.duration); |
| } |
| if (newOptions.delay != null) { |
| optionsToUpdate.delay = resolveTimingValue(newOptions.delay); |
| } |
| const /** @type {?} */ newParams = newOptions.params; |
| if (newParams) { |
| let /** @type {?} */ paramsToUpdate = /** @type {?} */ ((optionsToUpdate.params)); |
| if (!paramsToUpdate) { |
| paramsToUpdate = this.options.params = {}; |
| } |
| Object.keys(newParams).forEach(name => { |
| if (!skipIfExists || !paramsToUpdate.hasOwnProperty(name)) { |
| paramsToUpdate[name] = interpolateParams(newParams[name], paramsToUpdate, this.errors); |
| } |
| }); |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| _copyOptions() { |
| const /** @type {?} */ options = {}; |
| if (this.options) { |
| const /** @type {?} */ oldParams = this.options.params; |
| if (oldParams) { |
| const /** @type {?} */ params = options['params'] = {}; |
| Object.keys(oldParams).forEach(name => { params[name] = oldParams[name]; }); |
| } |
| } |
| return options; |
| } |
| /** |
| * @param {?=} options |
| * @param {?=} element |
| * @param {?=} newTime |
| * @return {?} |
| */ |
| createSubContext(options = null, element, newTime) { |
| const /** @type {?} */ target = element || this.element; |
| const /** @type {?} */ context = new AnimationTimelineContext(this._driver, target, this.subInstructions, this._enterClassName, this._leaveClassName, this.errors, this.timelines, this.currentTimeline.fork(target, newTime || 0)); |
| context.previousNode = this.previousNode; |
| context.currentAnimateTimings = this.currentAnimateTimings; |
| context.options = this._copyOptions(); |
| context.updateOptions(options); |
| context.currentQueryIndex = this.currentQueryIndex; |
| context.currentQueryTotal = this.currentQueryTotal; |
| context.parentContext = this; |
| this.subContextCount++; |
| return context; |
| } |
| /** |
| * @param {?=} newTime |
| * @return {?} |
| */ |
| transformIntoNewTimeline(newTime) { |
| this.previousNode = DEFAULT_NOOP_PREVIOUS_NODE; |
| this.currentTimeline = this.currentTimeline.fork(this.element, newTime); |
| this.timelines.push(this.currentTimeline); |
| return this.currentTimeline; |
| } |
| /** |
| * @param {?} instruction |
| * @param {?} duration |
| * @param {?} delay |
| * @return {?} |
| */ |
| appendInstructionToTimeline(instruction, duration, delay) { |
| const /** @type {?} */ updatedTimings = { |
| duration: duration != null ? duration : instruction.duration, |
| delay: this.currentTimeline.currentTime + (delay != null ? delay : 0) + instruction.delay, |
| easing: '' |
| }; |
| const /** @type {?} */ builder = new SubTimelineBuilder(this._driver, instruction.element, instruction.keyframes, instruction.preStyleProps, instruction.postStyleProps, updatedTimings, instruction.stretchStartingKeyframe); |
| this.timelines.push(builder); |
| return updatedTimings; |
| } |
| /** |
| * @param {?} time |
| * @return {?} |
| */ |
| incrementTime(time) { |
| this.currentTimeline.forwardTime(this.currentTimeline.duration + time); |
| } |
| /** |
| * @param {?} delay |
| * @return {?} |
| */ |
| delayNextStep(delay) { |
| // negative delays are not yet supported |
| if (delay > 0) { |
| this.currentTimeline.delayNextStep(delay); |
| } |
| } |
| /** |
| * @param {?} selector |
| * @param {?} originalSelector |
| * @param {?} limit |
| * @param {?} includeSelf |
| * @param {?} optional |
| * @param {?} errors |
| * @return {?} |
| */ |
| invokeQuery(selector, originalSelector, limit, includeSelf, optional, errors) { |
| let /** @type {?} */ results = []; |
| if (includeSelf) { |
| results.push(this.element); |
| } |
| if (selector.length > 0) { |
| // if :self is only used then the selector is empty |
| selector = selector.replace(ENTER_TOKEN_REGEX, '.' + this._enterClassName); |
| selector = selector.replace(LEAVE_TOKEN_REGEX, '.' + this._leaveClassName); |
| const /** @type {?} */ multi = limit != 1; |
| let /** @type {?} */ elements = this._driver.query(this.element, selector, multi); |
| if (limit !== 0) { |
| elements = limit < 0 ? elements.slice(elements.length + limit, elements.length) : |
| elements.slice(0, limit); |
| } |
| results.push(...elements); |
| } |
| if (!optional && results.length == 0) { |
| errors.push(`\`query("${originalSelector}")\` returned zero elements. (Use \`query("${originalSelector}", { optional: true })\` if you wish to allow this.)`); |
| } |
| return results; |
| } |
| } |
| class TimelineBuilder { |
| /** |
| * @param {?} _driver |
| * @param {?} element |
| * @param {?} startTime |
| * @param {?=} _elementTimelineStylesLookup |
| */ |
| constructor(_driver, element, startTime, _elementTimelineStylesLookup) { |
| this._driver = _driver; |
| this.element = element; |
| this.startTime = startTime; |
| this._elementTimelineStylesLookup = _elementTimelineStylesLookup; |
| this.duration = 0; |
| this._previousKeyframe = {}; |
| this._currentKeyframe = {}; |
| this._keyframes = new Map(); |
| this._styleSummary = {}; |
| this._pendingStyles = {}; |
| this._backFill = {}; |
| this._currentEmptyStepKeyframe = null; |
| if (!this._elementTimelineStylesLookup) { |
| this._elementTimelineStylesLookup = new Map(); |
| } |
| this._localTimelineStyles = Object.create(this._backFill, {}); |
| this._globalTimelineStyles = /** @type {?} */ ((this._elementTimelineStylesLookup.get(element))); |
| if (!this._globalTimelineStyles) { |
| this._globalTimelineStyles = this._localTimelineStyles; |
| this._elementTimelineStylesLookup.set(element, this._localTimelineStyles); |
| } |
| this._loadKeyframe(); |
| } |
| /** |
| * @return {?} |
| */ |
| containsAnimation() { |
| switch (this._keyframes.size) { |
| case 0: |
| return false; |
| case 1: |
| return this.getCurrentStyleProperties().length > 0; |
| default: |
| return true; |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| getCurrentStyleProperties() { return Object.keys(this._currentKeyframe); } |
| /** |
| * @return {?} |
| */ |
| get currentTime() { return this.startTime + this.duration; } |
| /** |
| * @param {?} delay |
| * @return {?} |
| */ |
| delayNextStep(delay) { |
| // in the event that a style() step is placed right before a stagger() |
| // and that style() step is the very first style() value in the animation |
| // then we need to make a copy of the keyframe [0, copy, 1] so that the delay |
| // properly applies the style() values to work with the stagger... |
| const /** @type {?} */ hasPreStyleStep = this._keyframes.size == 1 && Object.keys(this._pendingStyles).length; |
| if (this.duration || hasPreStyleStep) { |
| this.forwardTime(this.currentTime + delay); |
| if (hasPreStyleStep) { |
| this.snapshotCurrentStyles(); |
| } |
| } |
| else { |
| this.startTime += delay; |
| } |
| } |
| /** |
| * @param {?} element |
| * @param {?=} currentTime |
| * @return {?} |
| */ |
| fork(element, currentTime) { |
| this.applyStylesToKeyframe(); |
| return new TimelineBuilder(this._driver, element, currentTime || this.currentTime, this._elementTimelineStylesLookup); |
| } |
| /** |
| * @return {?} |
| */ |
| _loadKeyframe() { |
| if (this._currentKeyframe) { |
| this._previousKeyframe = this._currentKeyframe; |
| } |
| this._currentKeyframe = /** @type {?} */ ((this._keyframes.get(this.duration))); |
| if (!this._currentKeyframe) { |
| this._currentKeyframe = Object.create(this._backFill, {}); |
| this._keyframes.set(this.duration, this._currentKeyframe); |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| forwardFrame() { |
| this.duration += ONE_FRAME_IN_MILLISECONDS; |
| this._loadKeyframe(); |
| } |
| /** |
| * @param {?} time |
| * @return {?} |
| */ |
| forwardTime(time) { |
| this.applyStylesToKeyframe(); |
| this.duration = time; |
| this._loadKeyframe(); |
| } |
| /** |
| * @param {?} prop |
| * @param {?} value |
| * @return {?} |
| */ |
| _updateStyle(prop, value) { |
| this._localTimelineStyles[prop] = value; |
| this._globalTimelineStyles[prop] = value; |
| this._styleSummary[prop] = { time: this.currentTime, value }; |
| } |
| /** |
| * @return {?} |
| */ |
| allowOnlyTimelineStyles() { return this._currentEmptyStepKeyframe !== this._currentKeyframe; } |
| /** |
| * @param {?} easing |
| * @return {?} |
| */ |
| applyEmptyStep(easing) { |
| if (easing) { |
| this._previousKeyframe['easing'] = easing; |
| } |
| // special case for animate(duration): |
| // all missing styles are filled with a `*` value then |
| // if any destination styles are filled in later on the same |
| // keyframe then they will override the overridden styles |
| // We use `_globalTimelineStyles` here because there may be |
| // styles in previous keyframes that are not present in this timeline |
| Object.keys(this._globalTimelineStyles).forEach(prop => { |
| this._backFill[prop] = this._globalTimelineStyles[prop] || AUTO_STYLE; |
| this._currentKeyframe[prop] = AUTO_STYLE; |
| }); |
| this._currentEmptyStepKeyframe = this._currentKeyframe; |
| } |
| /** |
| * @param {?} input |
| * @param {?} easing |
| * @param {?} errors |
| * @param {?=} options |
| * @return {?} |
| */ |
| setStyles(input, easing, errors, options) { |
| if (easing) { |
| this._previousKeyframe['easing'] = easing; |
| } |
| const /** @type {?} */ params = (options && options.params) || {}; |
| const /** @type {?} */ styles = flattenStyles(input, this._globalTimelineStyles); |
| Object.keys(styles).forEach(prop => { |
| const /** @type {?} */ val = interpolateParams(styles[prop], params, errors); |
| this._pendingStyles[prop] = val; |
| if (!this._localTimelineStyles.hasOwnProperty(prop)) { |
| this._backFill[prop] = this._globalTimelineStyles.hasOwnProperty(prop) ? |
| this._globalTimelineStyles[prop] : |
| AUTO_STYLE; |
| } |
| this._updateStyle(prop, val); |
| }); |
| } |
| /** |
| * @return {?} |
| */ |
| applyStylesToKeyframe() { |
| const /** @type {?} */ styles = this._pendingStyles; |
| const /** @type {?} */ props = Object.keys(styles); |
| if (props.length == 0) |
| return; |
| this._pendingStyles = {}; |
| props.forEach(prop => { |
| const /** @type {?} */ val = styles[prop]; |
| this._currentKeyframe[prop] = val; |
| }); |
| Object.keys(this._localTimelineStyles).forEach(prop => { |
| if (!this._currentKeyframe.hasOwnProperty(prop)) { |
| this._currentKeyframe[prop] = this._localTimelineStyles[prop]; |
| } |
| }); |
| } |
| /** |
| * @return {?} |
| */ |
| snapshotCurrentStyles() { |
| Object.keys(this._localTimelineStyles).forEach(prop => { |
| const /** @type {?} */ val = this._localTimelineStyles[prop]; |
| this._pendingStyles[prop] = val; |
| this._updateStyle(prop, val); |
| }); |
| } |
| /** |
| * @return {?} |
| */ |
| getFinalKeyframe() { return this._keyframes.get(this.duration); } |
| /** |
| * @return {?} |
| */ |
| get properties() { |
| const /** @type {?} */ properties = []; |
| for (let /** @type {?} */ prop in this._currentKeyframe) { |
| properties.push(prop); |
| } |
| return properties; |
| } |
| /** |
| * @param {?} timeline |
| * @return {?} |
| */ |
| mergeTimelineCollectedStyles(timeline) { |
| Object.keys(timeline._styleSummary).forEach(prop => { |
| const /** @type {?} */ details0 = this._styleSummary[prop]; |
| const /** @type {?} */ details1 = timeline._styleSummary[prop]; |
| if (!details0 || details1.time > details0.time) { |
| this._updateStyle(prop, details1.value); |
| } |
| }); |
| } |
| /** |
| * @return {?} |
| */ |
| buildKeyframes() { |
| this.applyStylesToKeyframe(); |
| const /** @type {?} */ preStyleProps = new Set(); |
| const /** @type {?} */ postStyleProps = new Set(); |
| const /** @type {?} */ isEmpty = this._keyframes.size === 1 && this.duration === 0; |
| let /** @type {?} */ finalKeyframes = []; |
| this._keyframes.forEach((keyframe, time) => { |
| const /** @type {?} */ finalKeyframe = copyStyles(keyframe, true); |
| Object.keys(finalKeyframe).forEach(prop => { |
| const /** @type {?} */ value = finalKeyframe[prop]; |
| if (value == ɵPRE_STYLE) { |
| preStyleProps.add(prop); |
| } |
| else if (value == AUTO_STYLE) { |
| postStyleProps.add(prop); |
| } |
| }); |
| if (!isEmpty) { |
| finalKeyframe['offset'] = time / this.duration; |
| } |
| finalKeyframes.push(finalKeyframe); |
| }); |
| const /** @type {?} */ preProps = preStyleProps.size ? iteratorToArray(preStyleProps.values()) : []; |
| const /** @type {?} */ postProps = postStyleProps.size ? iteratorToArray(postStyleProps.values()) : []; |
| // special case for a 0-second animation (which is designed just to place styles onscreen) |
| if (isEmpty) { |
| const /** @type {?} */ kf0 = finalKeyframes[0]; |
| const /** @type {?} */ kf1 = copyObj(kf0); |
| kf0['offset'] = 0; |
| kf1['offset'] = 1; |
| finalKeyframes = [kf0, kf1]; |
| } |
| return createTimelineInstruction(this.element, finalKeyframes, preProps, postProps, this.duration, this.startTime, this.easing, false); |
| } |
| } |
| class SubTimelineBuilder extends TimelineBuilder { |
| /** |
| * @param {?} driver |
| * @param {?} element |
| * @param {?} keyframes |
| * @param {?} preStyleProps |
| * @param {?} postStyleProps |
| * @param {?} timings |
| * @param {?=} _stretchStartingKeyframe |
| */ |
| constructor(driver, element, keyframes, preStyleProps, postStyleProps, timings, _stretchStartingKeyframe = false) { |
| super(driver, element, timings.delay); |
| this.element = element; |
| this.keyframes = keyframes; |
| this.preStyleProps = preStyleProps; |
| this.postStyleProps = postStyleProps; |
| this._stretchStartingKeyframe = _stretchStartingKeyframe; |
| this.timings = { duration: timings.duration, delay: timings.delay, easing: timings.easing }; |
| } |
| /** |
| * @return {?} |
| */ |
| containsAnimation() { return this.keyframes.length > 1; } |
| /** |
| * @return {?} |
| */ |
| buildKeyframes() { |
| let /** @type {?} */ keyframes = this.keyframes; |
| let { delay, duration, easing } = this.timings; |
| if (this._stretchStartingKeyframe && delay) { |
| const /** @type {?} */ newKeyframes = []; |
| const /** @type {?} */ totalTime = duration + delay; |
| const /** @type {?} */ startingGap = delay / totalTime; |
| // the original starting keyframe now starts once the delay is done |
| const /** @type {?} */ newFirstKeyframe = copyStyles(keyframes[0], false); |
| newFirstKeyframe['offset'] = 0; |
| newKeyframes.push(newFirstKeyframe); |
| const /** @type {?} */ oldFirstKeyframe = copyStyles(keyframes[0], false); |
| oldFirstKeyframe['offset'] = roundOffset(startingGap); |
| newKeyframes.push(oldFirstKeyframe); |
| /* |
| When the keyframe is stretched then it means that the delay before the animation |
| starts is gone. Instead the first keyframe is placed at the start of the animation |
| and it is then copied to where it starts when the original delay is over. This basically |
| means nothing animates during that delay, but the styles are still renderered. For this |
| to work the original offset values that exist in the original keyframes must be "warped" |
| so that they can take the new keyframe + delay into account. |
| |
| delay=1000, duration=1000, keyframes = 0 .5 1 |
| |
| turns into |
| |
| delay=0, duration=2000, keyframes = 0 .33 .66 1 |
| */ |
| // offsets between 1 ... n -1 are all warped by the keyframe stretch |
| const /** @type {?} */ limit = keyframes.length - 1; |
| for (let /** @type {?} */ i = 1; i <= limit; i++) { |
| let /** @type {?} */ kf = copyStyles(keyframes[i], false); |
| const /** @type {?} */ oldOffset = /** @type {?} */ (kf['offset']); |
| const /** @type {?} */ timeAtKeyframe = delay + oldOffset * duration; |
| kf['offset'] = roundOffset(timeAtKeyframe / totalTime); |
| newKeyframes.push(kf); |
| } |
| // the new starting keyframe should be added at the start |
| duration = totalTime; |
| delay = 0; |
| easing = ''; |
| keyframes = newKeyframes; |
| } |
| return createTimelineInstruction(this.element, keyframes, this.preStyleProps, this.postStyleProps, duration, delay, easing, true); |
| } |
| } |
| /** |
| * @param {?} offset |
| * @param {?=} decimalPoints |
| * @return {?} |
| */ |
| function roundOffset(offset, decimalPoints = 3) { |
| const /** @type {?} */ mult = Math.pow(10, decimalPoints - 1); |
| return Math.round(offset * mult) / mult; |
| } |
| /** |
| * @param {?} input |
| * @param {?} allStyles |
| * @return {?} |
| */ |
| function flattenStyles(input, allStyles) { |
| const /** @type {?} */ styles = {}; |
| let /** @type {?} */ allProperties; |
| input.forEach(token => { |
| if (token === '*') { |
| allProperties = allProperties || Object.keys(allStyles); |
| allProperties.forEach(prop => { styles[prop] = AUTO_STYLE; }); |
| } |
| else { |
| copyStyles(/** @type {?} */ (token), false, styles); |
| } |
| }); |
| return styles; |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| class Animation { |
| /** |
| * @param {?} _driver |
| * @param {?} input |
| */ |
| constructor(_driver, input) { |
| this._driver = _driver; |
| const /** @type {?} */ errors = []; |
| const /** @type {?} */ ast = buildAnimationAst(_driver, input, errors); |
| if (errors.length) { |
| const /** @type {?} */ errorMessage = `animation validation failed:\n${errors.join("\n")}`; |
| throw new Error(errorMessage); |
| } |
| this._animationAst = ast; |
| } |
| /** |
| * @param {?} element |
| * @param {?} startingStyles |
| * @param {?} destinationStyles |
| * @param {?} options |
| * @param {?=} subInstructions |
| * @return {?} |
| */ |
| buildTimelines(element, startingStyles, destinationStyles, options, subInstructions) { |
| const /** @type {?} */ start = Array.isArray(startingStyles) ? normalizeStyles(startingStyles) : /** @type {?} */ (startingStyles); |
| const /** @type {?} */ dest = Array.isArray(destinationStyles) ? normalizeStyles(destinationStyles) : /** @type {?} */ (destinationStyles); |
| const /** @type {?} */ errors = []; |
| subInstructions = subInstructions || new ElementInstructionMap(); |
| const /** @type {?} */ result = buildAnimationTimelines(this._driver, element, this._animationAst, ENTER_CLASSNAME, LEAVE_CLASSNAME, start, dest, options, subInstructions, errors); |
| if (errors.length) { |
| const /** @type {?} */ errorMessage = `animation building failed:\n${errors.join("\n")}`; |
| throw new Error(errorMessage); |
| } |
| return result; |
| } |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| /** |
| * @license |
| * Copyright Google Inc. All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * \@experimental Animation support is experimental. |
| * @abstract |
| */ |
| class AnimationStyleNormalizer { |
| } |
| /** |
| * \@experimental Animation support is experimental. |
| */ |
| class NoopAnimationStyleNormalizer { |
| /** |
| * @param {?} propertyName |
| * @param {?} errors |
| * @return {?} |
| */ |
| normalizePropertyName(propertyName, errors) { return propertyName; } |
| /** |
| * @param {?} userProvidedProperty |
| * @param {?} normalizedProperty |
| * @param {?} value |
| * @param {?} errors |
| * @return {?} |
| */ |
| normalizeStyleValue(userProvidedProperty, normalizedProperty, value, errors) { |
| return /** @type {?} */ (value); |
| } |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| class WebAnimationsStyleNormalizer extends AnimationStyleNormalizer { |
| /** |
| * @param {?} propertyName |
| * @param {?} errors |
| * @return {?} |
| */ |
| normalizePropertyName(propertyName, errors) { |
| return dashCaseToCamelCase(propertyName); |
| } |
| /** |
| * @param {?} userProvidedProperty |
| * @param {?} normalizedProperty |
| * @param {?} value |
| * @param {?} errors |
| * @return {?} |
| */ |
| normalizeStyleValue(userProvidedProperty, normalizedProperty, value, errors) { |
| let /** @type {?} */ unit = ''; |
| const /** @type {?} */ strVal = value.toString().trim(); |
| if (DIMENSIONAL_PROP_MAP[normalizedProperty] && value !== 0 && value !== '0') { |
| if (typeof value === 'number') { |
| unit = 'px'; |
| } |
| else { |
| const /** @type {?} */ valAndSuffixMatch = value.match(/^[+-]?[\d\.]+([a-z]*)$/); |
| if (valAndSuffixMatch && valAndSuffixMatch[1].length == 0) { |
| errors.push(`Please provide a CSS unit value for ${userProvidedProperty}:${value}`); |
| } |
| } |
| } |
| return strVal + unit; |
| } |
| } |
| const DIMENSIONAL_PROP_MAP = makeBooleanMap('width,height,minWidth,minHeight,maxWidth,maxHeight,left,top,bottom,right,fontSize,outlineWidth,outlineOffset,paddingTop,paddingLeft,paddingBottom,paddingRight,marginTop,marginLeft,marginBottom,marginRight,borderRadius,borderWidth,borderTopWidth,borderLeftWidth,borderRightWidth,borderBottomWidth,textIndent,perspective' |
| .split(',')); |
| /** |
| * @param {?} keys |
| * @return {?} |
| */ |
| function makeBooleanMap(keys) { |
| const /** @type {?} */ map = {}; |
| keys.forEach(key => map[key] = true); |
| return map; |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| /** |
| * @record |
| */ |
| |
| /** |
| * @param {?} element |
| * @param {?} triggerName |
| * @param {?} fromState |
| * @param {?} toState |
| * @param {?} isRemovalTransition |
| * @param {?} fromStyles |
| * @param {?} toStyles |
| * @param {?} timelines |
| * @param {?} queriedElements |
| * @param {?} preStyleProps |
| * @param {?} postStyleProps |
| * @param {?=} errors |
| * @return {?} |
| */ |
| function createTransitionInstruction(element, triggerName, fromState, toState, isRemovalTransition, fromStyles, toStyles, timelines, queriedElements, preStyleProps, postStyleProps, errors) { |
| return { |
| type: 0 /* TransitionAnimation */, |
| element, |
| triggerName, |
| isRemovalTransition, |
| fromState, |
| fromStyles, |
| toState, |
| toStyles, |
| timelines, |
| queriedElements, |
| preStyleProps, |
| postStyleProps, |
| errors |
| }; |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| const EMPTY_OBJECT = {}; |
| class AnimationTransitionFactory { |
| /** |
| * @param {?} _triggerName |
| * @param {?} ast |
| * @param {?} _stateStyles |
| */ |
| constructor(_triggerName, ast, _stateStyles) { |
| this._triggerName = _triggerName; |
| this.ast = ast; |
| this._stateStyles = _stateStyles; |
| } |
| /** |
| * @param {?} currentState |
| * @param {?} nextState |
| * @return {?} |
| */ |
| match(currentState, nextState) { |
| return oneOrMoreTransitionsMatch(this.ast.matchers, currentState, nextState); |
| } |
| /** |
| * @param {?} stateName |
| * @param {?} params |
| * @param {?} errors |
| * @return {?} |
| */ |
| buildStyles(stateName, params, errors) { |
| const /** @type {?} */ backupStateStyler = this._stateStyles['*']; |
| const /** @type {?} */ stateStyler = this._stateStyles[stateName]; |
| const /** @type {?} */ backupStyles = backupStateStyler ? backupStateStyler.buildStyles(params, errors) : {}; |
| return stateStyler ? stateStyler.buildStyles(params, errors) : backupStyles; |
| } |
| /** |
| * @param {?} driver |
| * @param {?} element |
| * @param {?} currentState |
| * @param {?} nextState |
| * @param {?} enterClassName |
| * @param {?} leaveClassName |
| * @param {?=} currentOptions |
| * @param {?=} nextOptions |
| * @param {?=} subInstructions |
| * @return {?} |
| */ |
| build(driver, element, currentState, nextState, enterClassName, leaveClassName, currentOptions, nextOptions, subInstructions) { |
| const /** @type {?} */ errors = []; |
| const /** @type {?} */ transitionAnimationParams = this.ast.options && this.ast.options.params || EMPTY_OBJECT; |
| const /** @type {?} */ currentAnimationParams = currentOptions && currentOptions.params || EMPTY_OBJECT; |
| const /** @type {?} */ currentStateStyles = this.buildStyles(currentState, currentAnimationParams, errors); |
| const /** @type {?} */ nextAnimationParams = nextOptions && nextOptions.params || EMPTY_OBJECT; |
| const /** @type {?} */ nextStateStyles = this.buildStyles(nextState, nextAnimationParams, errors); |
| const /** @type {?} */ queriedElements = new Set(); |
| const /** @type {?} */ preStyleMap = new Map(); |
| const /** @type {?} */ postStyleMap = new Map(); |
| const /** @type {?} */ isRemoval = nextState === 'void'; |
| const /** @type {?} */ animationOptions = { params: Object.assign({}, transitionAnimationParams, nextAnimationParams) }; |
| const /** @type {?} */ timelines = buildAnimationTimelines(driver, element, this.ast.animation, enterClassName, leaveClassName, currentStateStyles, nextStateStyles, animationOptions, subInstructions, errors); |
| if (errors.length) { |
| return createTransitionInstruction(element, this._triggerName, currentState, nextState, isRemoval, currentStateStyles, nextStateStyles, [], [], preStyleMap, postStyleMap, errors); |
| } |
| timelines.forEach(tl => { |
| const /** @type {?} */ elm = tl.element; |
| const /** @type {?} */ preProps = getOrSetAsInMap(preStyleMap, elm, {}); |
| tl.preStyleProps.forEach(prop => preProps[prop] = true); |
| const /** @type {?} */ postProps = getOrSetAsInMap(postStyleMap, elm, {}); |
| tl.postStyleProps.forEach(prop => postProps[prop] = true); |
| if (elm !== element) { |
| queriedElements.add(elm); |
| } |
| }); |
| const /** @type {?} */ queriedElementsList = iteratorToArray(queriedElements.values()); |
| return createTransitionInstruction(element, this._triggerName, currentState, nextState, isRemoval, currentStateStyles, nextStateStyles, timelines, queriedElementsList, preStyleMap, postStyleMap); |
| } |
| } |
| /** |
| * @param {?} matchFns |
| * @param {?} currentState |
| * @param {?} nextState |
| * @return {?} |
| */ |
| function oneOrMoreTransitionsMatch(matchFns, currentState, nextState) { |
| return matchFns.some(fn => fn(currentState, nextState)); |
| } |
| class AnimationStateStyles { |
| /** |
| * @param {?} styles |
| * @param {?} defaultParams |
| */ |
| constructor(styles, defaultParams) { |
| this.styles = styles; |
| this.defaultParams = defaultParams; |
| } |
| /** |
| * @param {?} params |
| * @param {?} errors |
| * @return {?} |
| */ |
| buildStyles(params, errors) { |
| const /** @type {?} */ finalStyles = {}; |
| const /** @type {?} */ combinedParams = copyObj(this.defaultParams); |
| Object.keys(params).forEach(key => { |
| const /** @type {?} */ value = params[key]; |
| if (value != null) { |
| combinedParams[key] = value; |
| } |
| }); |
| this.styles.styles.forEach(value => { |
| if (typeof value !== 'string') { |
| const /** @type {?} */ styleObj = /** @type {?} */ (value); |
| Object.keys(styleObj).forEach(prop => { |
| let /** @type {?} */ val = styleObj[prop]; |
| if (val.length > 1) { |
| val = interpolateParams(val, combinedParams, errors); |
| } |
| finalStyles[prop] = val; |
| }); |
| } |
| }); |
| return finalStyles; |
| } |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| /** |
| * \@experimental Animation support is experimental. |
| * @param {?} name |
| * @param {?} ast |
| * @return {?} |
| */ |
| function buildTrigger(name, ast) { |
| return new AnimationTrigger(name, ast); |
| } |
| /** |
| * \@experimental Animation support is experimental. |
| */ |
| class AnimationTrigger { |
| /** |
| * @param {?} name |
| * @param {?} ast |
| */ |
| constructor(name, ast) { |
| this.name = name; |
| this.ast = ast; |
| this.transitionFactories = []; |
| this.states = {}; |
| ast.states.forEach(ast => { |
| const /** @type {?} */ defaultParams = (ast.options && ast.options.params) || {}; |
| this.states[ast.name] = new AnimationStateStyles(ast.style, defaultParams); |
| }); |
| balanceProperties(this.states, 'true', '1'); |
| balanceProperties(this.states, 'false', '0'); |
| ast.transitions.forEach(ast => { |
| this.transitionFactories.push(new AnimationTransitionFactory(name, ast, this.states)); |
| }); |
| this.fallbackTransition = createFallbackTransition(name, this.states); |
| } |
| /** |
| * @return {?} |
| */ |
| get containsQueries() { return this.ast.queryCount > 0; } |
| /** |
| * @param {?} currentState |
| * @param {?} nextState |
| * @return {?} |
| */ |
| matchTransition(currentState, nextState) { |
| const /** @type {?} */ entry = this.transitionFactories.find(f => f.match(currentState, nextState)); |
| return entry || null; |
| } |
| /** |
| * @param {?} currentState |
| * @param {?} params |
| * @param {?} errors |
| * @return {?} |
| */ |
| matchStyles(currentState, params, errors) { |
| return this.fallbackTransition.buildStyles(currentState, params, errors); |
| } |
| } |
| /** |
| * @param {?} triggerName |
| * @param {?} states |
| * @return {?} |
| */ |
| function createFallbackTransition(triggerName, states) { |
| const /** @type {?} */ matchers = [(fromState, toState) => true]; |
| const /** @type {?} */ animation = { type: 2 /* Sequence */, steps: [], options: null }; |
| const /** @type {?} */ transition = { |
| type: 1 /* Transition */, |
| animation, |
| matchers, |
| options: null, |
| queryCount: 0, |
| depCount: 0 |
| }; |
| return new AnimationTransitionFactory(triggerName, transition, states); |
| } |
| /** |
| * @param {?} obj |
| * @param {?} key1 |
| * @param {?} key2 |
| * @return {?} |
| */ |
| function balanceProperties(obj, key1, key2) { |
| if (obj.hasOwnProperty(key1)) { |
| if (!obj.hasOwnProperty(key2)) { |
| obj[key2] = obj[key1]; |
| } |
| } |
| else if (obj.hasOwnProperty(key2)) { |
| obj[key1] = obj[key2]; |
| } |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| const EMPTY_INSTRUCTION_MAP = new ElementInstructionMap(); |
| class TimelineAnimationEngine { |
| /** |
| * @param {?} _driver |
| * @param {?} _normalizer |
| */ |
| constructor(_driver, _normalizer) { |
| this._driver = _driver; |
| this._normalizer = _normalizer; |
| this._animations = {}; |
| this._playersById = {}; |
| this.players = []; |
| } |
| /** |
| * @param {?} id |
| * @param {?} metadata |
| * @return {?} |
| */ |
| register(id, metadata) { |
| const /** @type {?} */ errors = []; |
| const /** @type {?} */ ast = buildAnimationAst(this._driver, metadata, errors); |
| if (errors.length) { |
| throw new Error(`Unable to build the animation due to the following errors: ${errors.join("\n")}`); |
| } |
| else { |
| this._animations[id] = ast; |
| } |
| } |
| /** |
| * @param {?} i |
| * @param {?} preStyles |
| * @param {?=} postStyles |
| * @return {?} |
| */ |
| _buildPlayer(i, preStyles, postStyles) { |
| const /** @type {?} */ element = i.element; |
| const /** @type {?} */ keyframes = normalizeKeyframes(this._driver, this._normalizer, element, i.keyframes, preStyles, postStyles); |
| return this._driver.animate(element, keyframes, i.duration, i.delay, i.easing, []); |
| } |
| /** |
| * @param {?} id |
| * @param {?} element |
| * @param {?=} options |
| * @return {?} |
| */ |
| create(id, element, options = {}) { |
| const /** @type {?} */ errors = []; |
| const /** @type {?} */ ast = this._animations[id]; |
| let /** @type {?} */ instructions; |
| const /** @type {?} */ autoStylesMap = new Map(); |
| if (ast) { |
| instructions = buildAnimationTimelines(this._driver, element, ast, ENTER_CLASSNAME, LEAVE_CLASSNAME, {}, {}, options, EMPTY_INSTRUCTION_MAP, errors); |
| instructions.forEach(inst => { |
| const /** @type {?} */ styles = getOrSetAsInMap(autoStylesMap, inst.element, {}); |
| inst.postStyleProps.forEach(prop => styles[prop] = null); |
| }); |
| } |
| else { |
| errors.push('The requested animation doesn\'t exist or has already been destroyed'); |
| instructions = []; |
| } |
| if (errors.length) { |
| throw new Error(`Unable to create the animation due to the following errors: ${errors.join("\n")}`); |
| } |
| autoStylesMap.forEach((styles, element) => { |
| Object.keys(styles).forEach(prop => { styles[prop] = this._driver.computeStyle(element, prop, AUTO_STYLE); }); |
| }); |
| const /** @type {?} */ players = instructions.map(i => { |
| const /** @type {?} */ styles = autoStylesMap.get(i.element); |
| return this._buildPlayer(i, {}, styles); |
| }); |
| const /** @type {?} */ player = optimizeGroupPlayer(players); |
| this._playersById[id] = player; |
| player.onDestroy(() => this.destroy(id)); |
| this.players.push(player); |
| return player; |
| } |
| /** |
| * @param {?} id |
| * @return {?} |
| */ |
| destroy(id) { |
| const /** @type {?} */ player = this._getPlayer(id); |
| player.destroy(); |
| delete this._playersById[id]; |
| const /** @type {?} */ index = this.players.indexOf(player); |
| if (index >= 0) { |
| this.players.splice(index, 1); |
| } |
| } |
| /** |
| * @param {?} id |
| * @return {?} |
| */ |
| _getPlayer(id) { |
| const /** @type {?} */ player = this._playersById[id]; |
| if (!player) { |
| throw new Error(`Unable to find the timeline player referenced by ${id}`); |
| } |
| return player; |
| } |
| /** |
| * @param {?} id |
| * @param {?} element |
| * @param {?} eventName |
| * @param {?} callback |
| * @return {?} |
| */ |
| listen(id, element, eventName, callback) { |
| // triggerName, fromState, toState are all ignored for timeline animations |
| const /** @type {?} */ baseEvent = makeAnimationEvent(element, '', '', ''); |
| listenOnPlayer(this._getPlayer(id), eventName, baseEvent, callback); |
| return () => { }; |
| } |
| /** |
| * @param {?} id |
| * @param {?} element |
| * @param {?} command |
| * @param {?} args |
| * @return {?} |
| */ |
| command(id, element, command, args) { |
| if (command == 'register') { |
| this.register(id, /** @type {?} */ (args[0])); |
| return; |
| } |
| if (command == 'create') { |
| const /** @type {?} */ options = /** @type {?} */ ((args[0] || {})); |
| this.create(id, element, options); |
| return; |
| } |
| const /** @type {?} */ player = this._getPlayer(id); |
| switch (command) { |
| case 'play': |
| player.play(); |
| break; |
| case 'pause': |
| player.pause(); |
| break; |
| case 'reset': |
| player.reset(); |
| break; |
| case 'restart': |
| player.restart(); |
| break; |
| case 'finish': |
| player.finish(); |
| break; |
| case 'init': |
| player.init(); |
| break; |
| case 'setPosition': |
| player.setPosition(parseFloat(/** @type {?} */ (args[0]))); |
| break; |
| case 'destroy': |
| this.destroy(id); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| const QUEUED_CLASSNAME = 'ng-animate-queued'; |
| const QUEUED_SELECTOR = '.ng-animate-queued'; |
| const DISABLED_CLASSNAME = 'ng-animate-disabled'; |
| const DISABLED_SELECTOR = '.ng-animate-disabled'; |
| const STAR_CLASSNAME = 'ng-star-inserted'; |
| const STAR_SELECTOR = '.ng-star-inserted'; |
| const EMPTY_PLAYER_ARRAY = []; |
| const NULL_REMOVAL_STATE = { |
| namespaceId: '', |
| setForRemoval: null, |
| hasAnimation: false, |
| removedBeforeQueried: false |
| }; |
| const NULL_REMOVED_QUERIED_STATE = { |
| namespaceId: '', |
| setForRemoval: null, |
| hasAnimation: false, |
| removedBeforeQueried: true |
| }; |
| /** |
| * @record |
| */ |
| |
| const REMOVAL_FLAG = '__ng_removed'; |
| /** |
| * @record |
| */ |
| |
| class StateValue { |
| /** |
| * @param {?} input |
| * @param {?=} namespaceId |
| */ |
| constructor(input, namespaceId = '') { |
| this.namespaceId = namespaceId; |
| const /** @type {?} */ isObj = input && input.hasOwnProperty('value'); |
| const /** @type {?} */ value = isObj ? input['value'] : input; |
| this.value = normalizeTriggerValue(value); |
| if (isObj) { |
| const /** @type {?} */ options = copyObj(/** @type {?} */ (input)); |
| delete options['value']; |
| this.options = /** @type {?} */ (options); |
| } |
| else { |
| this.options = {}; |
| } |
| if (!this.options.params) { |
| this.options.params = {}; |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| get params() { return /** @type {?} */ (this.options.params); } |
| /** |
| * @param {?} options |
| * @return {?} |
| */ |
| absorbOptions(options) { |
| const /** @type {?} */ newParams = options.params; |
| if (newParams) { |
| const /** @type {?} */ oldParams = /** @type {?} */ ((this.options.params)); |
| Object.keys(newParams).forEach(prop => { |
| if (oldParams[prop] == null) { |
| oldParams[prop] = newParams[prop]; |
| } |
| }); |
| } |
| } |
| } |
| const VOID_VALUE = 'void'; |
| const DEFAULT_STATE_VALUE = new StateValue(VOID_VALUE); |
| const DELETED_STATE_VALUE = new StateValue('DELETED'); |
| class AnimationTransitionNamespace { |
| /** |
| * @param {?} id |
| * @param {?} hostElement |
| * @param {?} _engine |
| */ |
| constructor(id, hostElement, _engine) { |
| this.id = id; |
| this.hostElement = hostElement; |
| this._engine = _engine; |
| this.players = []; |
| this._triggers = {}; |
| this._queue = []; |
| this._elementListeners = new Map(); |
| this._hostClassName = 'ng-tns-' + id; |
| addClass(hostElement, this._hostClassName); |
| } |
| /** |
| * @param {?} element |
| * @param {?} name |
| * @param {?} phase |
| * @param {?} callback |
| * @return {?} |
| */ |
| listen(element, name, phase, callback) { |
| if (!this._triggers.hasOwnProperty(name)) { |
| throw new Error(`Unable to listen on the animation trigger event "${phase}" because the animation trigger "${name}" doesn\'t exist!`); |
| } |
| if (phase == null || phase.length == 0) { |
| throw new Error(`Unable to listen on the animation trigger "${name}" because the provided event is undefined!`); |
| } |
| if (!isTriggerEventValid(phase)) { |
| throw new Error(`The provided animation trigger event "${phase}" for the animation trigger "${name}" is not supported!`); |
| } |
| const /** @type {?} */ listeners = getOrSetAsInMap(this._elementListeners, element, []); |
| const /** @type {?} */ data = { name, phase, callback }; |
| listeners.push(data); |
| const /** @type {?} */ triggersWithStates = getOrSetAsInMap(this._engine.statesByElement, element, {}); |
| if (!triggersWithStates.hasOwnProperty(name)) { |
| addClass(element, NG_TRIGGER_CLASSNAME); |
| addClass(element, NG_TRIGGER_CLASSNAME + '-' + name); |
| triggersWithStates[name] = DEFAULT_STATE_VALUE; |
| } |
| return () => { |
| // the event listener is removed AFTER the flush has occurred such |
| // that leave animations callbacks can fire (otherwise if the node |
| // is removed in between then the listeners would be deregistered) |
| this._engine.afterFlush(() => { |
| const /** @type {?} */ index = listeners.indexOf(data); |
| if (index >= 0) { |
| listeners.splice(index, 1); |
| } |
| if (!this._triggers[name]) { |
| delete triggersWithStates[name]; |
| } |
| }); |
| }; |
| } |
| /** |
| * @param {?} name |
| * @param {?} ast |
| * @return {?} |
| */ |
| register(name, ast) { |
| if (this._triggers[name]) { |
| // throw |
| return false; |
| } |
| else { |
| this._triggers[name] = ast; |
| return true; |
| } |
| } |
| /** |
| * @param {?} name |
| * @return {?} |
| */ |
| _getTrigger(name) { |
| const /** @type {?} */ trigger = this._triggers[name]; |
| if (!trigger) { |
| throw new Error(`The provided animation trigger "${name}" has not been registered!`); |
| } |
| return trigger; |
| } |
| /** |
| * @param {?} element |
| * @param {?} triggerName |
| * @param {?} value |
| * @param {?=} defaultToFallback |
| * @return {?} |
| */ |
| trigger(element, triggerName, value, defaultToFallback = true) { |
| const /** @type {?} */ trigger = this._getTrigger(triggerName); |
| const /** @type {?} */ player = new TransitionAnimationPlayer(this.id, triggerName, element); |
| let /** @type {?} */ triggersWithStates = this._engine.statesByElement.get(element); |
| if (!triggersWithStates) { |
| addClass(element, NG_TRIGGER_CLASSNAME); |
| addClass(element, NG_TRIGGER_CLASSNAME + '-' + triggerName); |
| this._engine.statesByElement.set(element, triggersWithStates = {}); |
| } |
| let /** @type {?} */ fromState = triggersWithStates[triggerName]; |
| const /** @type {?} */ toState = new StateValue(value, this.id); |
| const /** @type {?} */ isObj = value && value.hasOwnProperty('value'); |
| if (!isObj && fromState) { |
| toState.absorbOptions(fromState.options); |
| } |
| triggersWithStates[triggerName] = toState; |
| if (!fromState) { |
| fromState = DEFAULT_STATE_VALUE; |
| } |
| else if (fromState === DELETED_STATE_VALUE) { |
| return player; |
| } |
| const /** @type {?} */ isRemoval = toState.value === VOID_VALUE; |
| // normally this isn't reached by here, however, if an object expression |
| // is passed in then it may be a new object each time. Comparing the value |
| // is important since that will stay the same despite there being a new object. |
| // The removal arc here is special cased because the same element is triggered |
| // twice in the event that it contains animations on the outer/inner portions |
| // of the host container |
| if (!isRemoval && fromState.value === toState.value) { |
| // this means that despite the value not changing, some inner params |
| // have changed which means that the animation final styles need to be applied |
| if (!objEquals(fromState.params, toState.params)) { |
| const /** @type {?} */ errors = []; |
| const /** @type {?} */ fromStyles = trigger.matchStyles(fromState.value, fromState.params, errors); |
| const /** @type {?} */ toStyles = trigger.matchStyles(toState.value, toState.params, errors); |
| if (errors.length) { |
| this._engine.reportError(errors); |
| } |
| else { |
| this._engine.afterFlush(() => { |
| eraseStyles(element, fromStyles); |
| setStyles(element, toStyles); |
| }); |
| } |
| } |
| return; |
| } |
| const /** @type {?} */ playersOnElement = getOrSetAsInMap(this._engine.playersByElement, element, []); |
| playersOnElement.forEach(player => { |
| // only remove the player if it is queued on the EXACT same trigger/namespace |
| // we only also deal with queued players here because if the animation has |
| // started then we want to keep the player alive until the flush happens |
| // (which is where the previousPlayers are passed into the new palyer) |
| if (player.namespaceId == this.id && player.triggerName == triggerName && player.queued) { |
| player.destroy(); |
| } |
| }); |
| let /** @type {?} */ transition = trigger.matchTransition(fromState.value, toState.value); |
| let /** @type {?} */ isFallbackTransition = false; |
| if (!transition) { |
| if (!defaultToFallback) |
| return; |
| transition = trigger.fallbackTransition; |
| isFallbackTransition = true; |
| } |
| this._engine.totalQueuedPlayers++; |
| this._queue.push({ element, triggerName, transition, fromState, toState, player, isFallbackTransition }); |
| if (!isFallbackTransition) { |
| addClass(element, QUEUED_CLASSNAME); |
| player.onStart(() => { removeClass(element, QUEUED_CLASSNAME); }); |
| } |
| player.onDone(() => { |
| let /** @type {?} */ index = this.players.indexOf(player); |
| if (index >= 0) { |
| this.players.splice(index, 1); |
| } |
| const /** @type {?} */ players = this._engine.playersByElement.get(element); |
| if (players) { |
| let /** @type {?} */ index = players.indexOf(player); |
| if (index >= 0) { |
| players.splice(index, 1); |
| } |
| } |
| }); |
| this.players.push(player); |
| playersOnElement.push(player); |
| return player; |
| } |
| /** |
| * @param {?} name |
| * @return {?} |
| */ |
| deregister(name) { |
| delete this._triggers[name]; |
| this._engine.statesByElement.forEach((stateMap, element) => { delete stateMap[name]; }); |
| this._elementListeners.forEach((listeners, element) => { |
| this._elementListeners.set(element, listeners.filter(entry => { return entry.name != name; })); |
| }); |
| } |
| /** |
| * @param {?} element |
| * @return {?} |
| */ |
| clearElementCache(element) { |
| this._engine.statesByElement.delete(element); |
| this._elementListeners.delete(element); |
| const /** @type {?} */ elementPlayers = this._engine.playersByElement.get(element); |
| if (elementPlayers) { |
| elementPlayers.forEach(player => player.destroy()); |
| this._engine.playersByElement.delete(element); |
| } |
| } |
| /** |
| * @param {?} rootElement |
| * @param {?} context |
| * @param {?=} animate |
| * @return {?} |
| */ |
| _signalRemovalForInnerTriggers(rootElement, context, animate = false) { |
| // emulate a leave animation for all inner nodes within this node. |
| // If there are no animations found for any of the nodes then clear the cache |
| // for the element. |
| this._engine.driver.query(rootElement, NG_TRIGGER_SELECTOR, true).forEach(elm => { |
| // this means that an inner remove() operation has already kicked off |
| // the animation on this element... |
| if (elm[REMOVAL_FLAG]) |
| return; |
| const /** @type {?} */ namespaces = this._engine.fetchNamespacesByElement(elm); |
| if (namespaces.size) { |
| namespaces.forEach(ns => ns.triggerLeaveAnimation(elm, context, false, true)); |
| } |
| else { |
| this.clearElementCache(elm); |
| } |
| }); |
| } |
| /** |
| * @param {?} element |
| * @param {?} context |
| * @param {?=} destroyAfterComplete |
| * @param {?=} defaultToFallback |
| * @return {?} |
| */ |
| triggerLeaveAnimation(element, context, destroyAfterComplete, defaultToFallback) { |
| const /** @type {?} */ triggerStates = this._engine.statesByElement.get(element); |
| if (triggerStates) { |
| const /** @type {?} */ players = []; |
| Object.keys(triggerStates).forEach(triggerName => { |
| // this check is here in the event that an element is removed |
| // twice (both on the host level and the component level) |
| if (this._triggers[triggerName]) { |
| const /** @type {?} */ player = this.trigger(element, triggerName, VOID_VALUE, defaultToFallback); |
| if (player) { |
| players.push(player); |
| } |
| } |
| }); |
| if (players.length) { |
| this._engine.markElementAsRemoved(this.id, element, true, context); |
| if (destroyAfterComplete) { |
| optimizeGroupPlayer(players).onDone(() => this._engine.processLeaveNode(element)); |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| /** |
| * @param {?} element |
| * @return {?} |
| */ |
| prepareLeaveAnimationListeners(element) { |
| const /** @type {?} */ listeners = this._elementListeners.get(element); |
| if (listeners) { |
| const /** @type {?} */ visitedTriggers = new Set(); |
| listeners.forEach(listener => { |
| const /** @type {?} */ triggerName = listener.name; |
| if (visitedTriggers.has(triggerName)) |
| return; |
| visitedTriggers.add(triggerName); |
| const /** @type {?} */ trigger = this._triggers[triggerName]; |
| const /** @type {?} */ transition = trigger.fallbackTransition; |
| const /** @type {?} */ elementStates = /** @type {?} */ ((this._engine.statesByElement.get(element))); |
| const /** @type {?} */ fromState = elementStates[triggerName] || DEFAULT_STATE_VALUE; |
| const /** @type {?} */ toState = new StateValue(VOID_VALUE); |
| const /** @type {?} */ player = new TransitionAnimationPlayer(this.id, triggerName, element); |
| this._engine.totalQueuedPlayers++; |
| this._queue.push({ |
| element, |
| triggerName, |
| transition, |
| fromState, |
| toState, |
| player, |
| isFallbackTransition: true |
| }); |
| }); |
| } |
| } |
| /** |
| * @param {?} element |
| * @param {?} context |
| * @return {?} |
| */ |
| removeNode(element, context) { |
| const /** @type {?} */ engine = this._engine; |
| if (element.childElementCount) { |
| this._signalRemovalForInnerTriggers(element, context, true); |
| } |
| // this means that a * => VOID animation was detected and kicked off |
| if (this.triggerLeaveAnimation(element, context, true)) |
| return; |
| // find the player that is animating and make sure that the |
| // removal is delayed until that player has completed |
| let /** @type {?} */ containsPotentialParentTransition = false; |
| if (engine.totalAnimations) { |
| const /** @type {?} */ currentPlayers = engine.players.length ? engine.playersByQueriedElement.get(element) : []; |
| // when this `if statement` does not continue forward it means that |
| // a previous animation query has selected the current element and |
| // is animating it. In this situation want to continue fowards and |
| // allow the element to be queued up for animation later. |
| if (currentPlayers && currentPlayers.length) { |
| containsPotentialParentTransition = true; |
| } |
| else { |
| let /** @type {?} */ parent = element; |
| while (parent = parent.parentNode) { |
| const /** @type {?} */ triggers = engine.statesByElement.get(parent); |
| if (triggers) { |
| containsPotentialParentTransition = true; |
| break; |
| } |
| } |
| } |
| } |
| // at this stage we know that the element will either get removed |
| // during flush or will be picked up by a parent query. Either way |
| // we need to fire the listeners for this element when it DOES get |
| // removed (once the query parent animation is done or after flush) |
| this.prepareLeaveAnimationListeners(element); |
| // whether or not a parent has an animation we need to delay the deferral of the leave |
| // operation until we have more information (which we do after flush() has been called) |
| if (containsPotentialParentTransition) { |
| engine.markElementAsRemoved(this.id, element, false, context); |
| } |
| else { |
| // we do this after the flush has occurred such |
| // that the callbacks can be fired |
| engine.afterFlush(() => this.clearElementCache(element)); |
| engine.destroyInnerAnimations(element); |
| engine._onRemovalComplete(element, context); |
| } |
| } |
| /** |
| * @param {?} element |
| * @param {?} parent |
| * @return {?} |
| */ |
| insertNode(element, parent) { addClass(element, this._hostClassName); } |
| /** |
| * @param {?} microtaskId |
| * @return {?} |
| */ |
| drainQueuedTransitions(microtaskId) { |
| const /** @type {?} */ instructions = []; |
| this._queue.forEach(entry => { |
| const /** @type {?} */ player = entry.player; |
| if (player.destroyed) |
| return; |
| const /** @type {?} */ element = entry.element; |
| const /** @type {?} */ listeners = this._elementListeners.get(element); |
| if (listeners) { |
| listeners.forEach((listener) => { |
| if (listener.name == entry.triggerName) { |
| const /** @type {?} */ baseEvent = makeAnimationEvent(element, entry.triggerName, entry.fromState.value, entry.toState.value); |
| (/** @type {?} */ (baseEvent))['_data'] = microtaskId; |
| listenOnPlayer(entry.player, listener.phase, baseEvent, listener.callback); |
| } |
| }); |
| } |
| if (player.markedForDestroy) { |
| this._engine.afterFlush(() => { |
| // now we can destroy the element properly since the event listeners have |
| // been bound to the player |
| player.destroy(); |
| }); |
| } |
| else { |
| instructions.push(entry); |
| } |
| }); |
| this._queue = []; |
| return instructions.sort((a, b) => { |
| // if depCount == 0 them move to front |
| // otherwise if a contains b then move back |
| const /** @type {?} */ d0 = a.transition.ast.depCount; |
| const /** @type {?} */ d1 = b.transition.ast.depCount; |
| if (d0 == 0 || d1 == 0) { |
| return d0 - d1; |
| } |
| return this._engine.driver.containsElement(a.element, b.element) ? 1 : -1; |
| }); |
| } |
| /** |
| * @param {?} context |
| * @return {?} |
| */ |
| destroy(context) { |
| this.players.forEach(p => p.destroy()); |
| this._signalRemovalForInnerTriggers(this.hostElement, context); |
| } |
| /** |
| * @param {?} element |
| * @return {?} |
| */ |
| elementContainsData(element) { |
| let /** @type {?} */ containsData = false; |
| if (this._elementListeners.has(element)) |
| containsData = true; |
| containsData = |
| (this._queue.find(entry => entry.element === element) ? true : false) || containsData; |
| return containsData; |
| } |
| } |
| /** |
| * @record |
| */ |
| |
| class TransitionAnimationEngine { |
| /** |
| * @param {?} driver |
| * @param {?} _normalizer |
| */ |
| constructor(driver, _normalizer) { |
| this.driver = driver; |
| this._normalizer = _normalizer; |
| this.players = []; |
| this.newHostElements = new Map(); |
| this.playersByElement = new Map(); |
| this.playersByQueriedElement = new Map(); |
| this.statesByElement = new Map(); |
| this.disabledNodes = new Set(); |
| this.totalAnimations = 0; |
| this.totalQueuedPlayers = 0; |
| this._namespaceLookup = {}; |
| this._namespaceList = []; |
| this._flushFns = []; |
| this._whenQuietFns = []; |
| this.namespacesByHostElement = new Map(); |
| this.collectedEnterElements = []; |
| this.collectedLeaveElements = []; |
| this.onRemovalComplete = (element, context) => { }; |
| } |
| /** |
| * \@internal |
| * @param {?} element |
| * @param {?} context |
| * @return {?} |
| */ |
| _onRemovalComplete(element, context) { this.onRemovalComplete(element, context); } |
| /** |
| * @return {?} |
| */ |
| get queuedPlayers() { |
| const /** @type {?} */ players = []; |
| this._namespaceList.forEach(ns => { |
| ns.players.forEach(player => { |
| if (player.queued) { |
| players.push(player); |
| } |
| }); |
| }); |
| return players; |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} hostElement |
| * @return {?} |
| */ |
| createNamespace(namespaceId, hostElement) { |
| const /** @type {?} */ ns = new AnimationTransitionNamespace(namespaceId, hostElement, this); |
| if (hostElement.parentNode) { |
| this._balanceNamespaceList(ns, hostElement); |
| } |
| else { |
| // defer this later until flush during when the host element has |
| // been inserted so that we know exactly where to place it in |
| // the namespace list |
| this.newHostElements.set(hostElement, ns); |
| // given that this host element is apart of the animation code, it |
| // may or may not be inserted by a parent node that is an of an |
| // animation renderer type. If this happens then we can still have |
| // access to this item when we query for :enter nodes. If the parent |
| // is a renderer then the set data-structure will normalize the entry |
| this.collectEnterElement(hostElement); |
| } |
| return this._namespaceLookup[namespaceId] = ns; |
| } |
| /** |
| * @param {?} ns |
| * @param {?} hostElement |
| * @return {?} |
| */ |
| _balanceNamespaceList(ns, hostElement) { |
| const /** @type {?} */ limit = this._namespaceList.length - 1; |
| if (limit >= 0) { |
| let /** @type {?} */ found = false; |
| for (let /** @type {?} */ i = limit; i >= 0; i--) { |
| const /** @type {?} */ nextNamespace = this._namespaceList[i]; |
| if (this.driver.containsElement(nextNamespace.hostElement, hostElement)) { |
| this._namespaceList.splice(i + 1, 0, ns); |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| this._namespaceList.splice(0, 0, ns); |
| } |
| } |
| else { |
| this._namespaceList.push(ns); |
| } |
| this.namespacesByHostElement.set(hostElement, ns); |
| return ns; |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} hostElement |
| * @return {?} |
| */ |
| register(namespaceId, hostElement) { |
| let /** @type {?} */ ns = this._namespaceLookup[namespaceId]; |
| if (!ns) { |
| ns = this.createNamespace(namespaceId, hostElement); |
| } |
| return ns; |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} name |
| * @param {?} trigger |
| * @return {?} |
| */ |
| registerTrigger(namespaceId, name, trigger) { |
| let /** @type {?} */ ns = this._namespaceLookup[namespaceId]; |
| if (ns && ns.register(name, trigger)) { |
| this.totalAnimations++; |
| } |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} context |
| * @return {?} |
| */ |
| destroy(namespaceId, context) { |
| if (!namespaceId) |
| return; |
| const /** @type {?} */ ns = this._fetchNamespace(namespaceId); |
| this.afterFlush(() => { |
| this.namespacesByHostElement.delete(ns.hostElement); |
| delete this._namespaceLookup[namespaceId]; |
| const /** @type {?} */ index = this._namespaceList.indexOf(ns); |
| if (index >= 0) { |
| this._namespaceList.splice(index, 1); |
| } |
| }); |
| this.afterFlushAnimationsDone(() => ns.destroy(context)); |
| } |
| /** |
| * @param {?} id |
| * @return {?} |
| */ |
| _fetchNamespace(id) { return this._namespaceLookup[id]; } |
| /** |
| * @param {?} element |
| * @return {?} |
| */ |
| fetchNamespacesByElement(element) { |
| // normally there should only be one namespace per element, however |
| // if @triggers are placed on both the component element and then |
| // its host element (within the component code) then there will be |
| // two namespaces returned. We use a set here to simply the dedupe |
| // of namespaces incase there are multiple triggers both the elm and host |
| const /** @type {?} */ namespaces = new Set(); |
| const /** @type {?} */ elementStates = this.statesByElement.get(element); |
| if (elementStates) { |
| const /** @type {?} */ keys = Object.keys(elementStates); |
| for (let /** @type {?} */ i = 0; i < keys.length; i++) { |
| const /** @type {?} */ nsId = elementStates[keys[i]].namespaceId; |
| if (nsId) { |
| const /** @type {?} */ ns = this._fetchNamespace(nsId); |
| if (ns) { |
| namespaces.add(ns); |
| } |
| } |
| } |
| } |
| return namespaces; |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} element |
| * @param {?} name |
| * @param {?} value |
| * @return {?} |
| */ |
| trigger(namespaceId, element, name, value) { |
| if (isElementNode(element)) { |
| this._fetchNamespace(namespaceId).trigger(element, name, value); |
| return true; |
| } |
| return false; |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} element |
| * @param {?} parent |
| * @param {?} insertBefore |
| * @return {?} |
| */ |
| insertNode(namespaceId, element, parent, insertBefore) { |
| if (!isElementNode(element)) |
| return; |
| // special case for when an element is removed and reinserted (move operation) |
| // when this occurs we do not want to use the element for deletion later |
| const /** @type {?} */ details = /** @type {?} */ (element[REMOVAL_FLAG]); |
| if (details && details.setForRemoval) { |
| details.setForRemoval = false; |
| } |
| // in the event that the namespaceId is blank then the caller |
| // code does not contain any animation code in it, but it is |
| // just being called so that the node is marked as being inserted |
| if (namespaceId) { |
| this._fetchNamespace(namespaceId).insertNode(element, parent); |
| } |
| // only *directives and host elements are inserted before |
| if (insertBefore) { |
| this.collectEnterElement(element); |
| } |
| } |
| /** |
| * @param {?} element |
| * @return {?} |
| */ |
| collectEnterElement(element) { this.collectedEnterElements.push(element); } |
| /** |
| * @param {?} element |
| * @param {?} value |
| * @return {?} |
| */ |
| markElementAsDisabled(element, value) { |
| if (value) { |
| if (!this.disabledNodes.has(element)) { |
| this.disabledNodes.add(element); |
| addClass(element, DISABLED_CLASSNAME); |
| } |
| } |
| else if (this.disabledNodes.has(element)) { |
| this.disabledNodes.delete(element); |
| removeClass(element, DISABLED_CLASSNAME); |
| } |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} element |
| * @param {?} context |
| * @return {?} |
| */ |
| removeNode(namespaceId, element, context) { |
| if (!isElementNode(element)) { |
| this._onRemovalComplete(element, context); |
| return; |
| } |
| const /** @type {?} */ ns = namespaceId ? this._fetchNamespace(namespaceId) : null; |
| if (ns) { |
| ns.removeNode(element, context); |
| } |
| else { |
| this.markElementAsRemoved(namespaceId, element, false, context); |
| } |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} element |
| * @param {?=} hasAnimation |
| * @param {?=} context |
| * @return {?} |
| */ |
| markElementAsRemoved(namespaceId, element, hasAnimation, context) { |
| this.collectedLeaveElements.push(element); |
| element[REMOVAL_FLAG] = { |
| namespaceId, |
| setForRemoval: context, hasAnimation, |
| removedBeforeQueried: false |
| }; |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} element |
| * @param {?} name |
| * @param {?} phase |
| * @param {?} callback |
| * @return {?} |
| */ |
| listen(namespaceId, element, name, phase, callback) { |
| if (isElementNode(element)) { |
| return this._fetchNamespace(namespaceId).listen(element, name, phase, callback); |
| } |
| return () => { }; |
| } |
| /** |
| * @param {?} entry |
| * @param {?} subTimelines |
| * @param {?} enterClassName |
| * @param {?} leaveClassName |
| * @return {?} |
| */ |
| _buildInstruction(entry, subTimelines, enterClassName, leaveClassName) { |
| return entry.transition.build(this.driver, entry.element, entry.fromState.value, entry.toState.value, enterClassName, leaveClassName, entry.fromState.options, entry.toState.options, subTimelines); |
| } |
| /** |
| * @param {?} containerElement |
| * @return {?} |
| */ |
| destroyInnerAnimations(containerElement) { |
| let /** @type {?} */ elements = this.driver.query(containerElement, NG_TRIGGER_SELECTOR, true); |
| elements.forEach(element => this.destroyActiveAnimationsForElement(element)); |
| if (this.playersByQueriedElement.size == 0) |
| return; |
| elements = this.driver.query(containerElement, NG_ANIMATING_SELECTOR, true); |
| elements.forEach(element => this.finishActiveQueriedAnimationOnElement(element)); |
| } |
| /** |
| * @param {?} element |
| * @return {?} |
| */ |
| destroyActiveAnimationsForElement(element) { |
| const /** @type {?} */ players = this.playersByElement.get(element); |
| if (players) { |
| players.forEach(player => { |
| // special case for when an element is set for destruction, but hasn't started. |
| // in this situation we want to delay the destruction until the flush occurs |
| // so that any event listeners attached to the player are triggered. |
| if (player.queued) { |
| player.markedForDestroy = true; |
| } |
| else { |
| player.destroy(); |
| } |
| }); |
| } |
| const /** @type {?} */ stateMap = this.statesByElement.get(element); |
| if (stateMap) { |
| Object.keys(stateMap).forEach(triggerName => stateMap[triggerName] = DELETED_STATE_VALUE); |
| } |
| } |
| /** |
| * @param {?} element |
| * @return {?} |
| */ |
| finishActiveQueriedAnimationOnElement(element) { |
| const /** @type {?} */ players = this.playersByQueriedElement.get(element); |
| if (players) { |
| players.forEach(player => player.finish()); |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| whenRenderingDone() { |
| return new Promise(resolve => { |
| if (this.players.length) { |
| return optimizeGroupPlayer(this.players).onDone(() => resolve()); |
| } |
| else { |
| resolve(); |
| } |
| }); |
| } |
| /** |
| * @param {?} element |
| * @return {?} |
| */ |
| processLeaveNode(element) { |
| const /** @type {?} */ details = /** @type {?} */ (element[REMOVAL_FLAG]); |
| if (details && details.setForRemoval) { |
| // this will prevent it from removing it twice |
| element[REMOVAL_FLAG] = NULL_REMOVAL_STATE; |
| if (details.namespaceId) { |
| this.destroyInnerAnimations(element); |
| const /** @type {?} */ ns = this._fetchNamespace(details.namespaceId); |
| if (ns) { |
| ns.clearElementCache(element); |
| } |
| } |
| this._onRemovalComplete(element, details.setForRemoval); |
| } |
| if (this.driver.matchesElement(element, DISABLED_SELECTOR)) { |
| this.markElementAsDisabled(element, false); |
| } |
| this.driver.query(element, DISABLED_SELECTOR, true).forEach(node => { |
| this.markElementAsDisabled(element, false); |
| }); |
| } |
| /** |
| * @param {?=} microtaskId |
| * @return {?} |
| */ |
| flush(microtaskId = -1) { |
| let /** @type {?} */ players = []; |
| if (this.newHostElements.size) { |
| this.newHostElements.forEach((ns, element) => this._balanceNamespaceList(ns, element)); |
| this.newHostElements.clear(); |
| } |
| if (this.totalAnimations && this.collectedEnterElements.length) { |
| for (let /** @type {?} */ i = 0; i < this.collectedEnterElements.length; i++) { |
| const /** @type {?} */ elm = this.collectedEnterElements[i]; |
| addClass(elm, STAR_CLASSNAME); |
| } |
| } |
| if (this._namespaceList.length && |
| (this.totalQueuedPlayers || this.collectedLeaveElements.length)) { |
| const /** @type {?} */ cleanupFns = []; |
| try { |
| players = this._flushAnimations(cleanupFns, microtaskId); |
| } |
| finally { |
| for (let /** @type {?} */ i = 0; i < cleanupFns.length; i++) { |
| cleanupFns[i](); |
| } |
| } |
| } |
| else { |
| for (let /** @type {?} */ i = 0; i < this.collectedLeaveElements.length; i++) { |
| const /** @type {?} */ element = this.collectedLeaveElements[i]; |
| this.processLeaveNode(element); |
| } |
| } |
| this.totalQueuedPlayers = 0; |
| this.collectedEnterElements.length = 0; |
| this.collectedLeaveElements.length = 0; |
| this._flushFns.forEach(fn => fn()); |
| this._flushFns = []; |
| if (this._whenQuietFns.length) { |
| // we move these over to a variable so that |
| // if any new callbacks are registered in another |
| // flush they do not populate the existing set |
| const /** @type {?} */ quietFns = this._whenQuietFns; |
| this._whenQuietFns = []; |
| if (players.length) { |
| optimizeGroupPlayer(players).onDone(() => { quietFns.forEach(fn => fn()); }); |
| } |
| else { |
| quietFns.forEach(fn => fn()); |
| } |
| } |
| } |
| /** |
| * @param {?} errors |
| * @return {?} |
| */ |
| reportError(errors) { |
| throw new Error(`Unable to process animations due to the following failed trigger transitions\n ${errors.join('\n')}`); |
| } |
| /** |
| * @param {?} cleanupFns |
| * @param {?} microtaskId |
| * @return {?} |
| */ |
| _flushAnimations(cleanupFns, microtaskId) { |
| const /** @type {?} */ subTimelines = new ElementInstructionMap(); |
| const /** @type {?} */ skippedPlayers = []; |
| const /** @type {?} */ skippedPlayersMap = new Map(); |
| const /** @type {?} */ queuedInstructions = []; |
| const /** @type {?} */ queriedElements = new Map(); |
| const /** @type {?} */ allPreStyleElements = new Map(); |
| const /** @type {?} */ allPostStyleElements = new Map(); |
| const /** @type {?} */ disabledElementsSet = new Set(); |
| this.disabledNodes.forEach(node => { |
| disabledElementsSet.add(node); |
| const /** @type {?} */ nodesThatAreDisabled = this.driver.query(node, QUEUED_SELECTOR, true); |
| for (let /** @type {?} */ i = 0; i < nodesThatAreDisabled.length; i++) { |
| disabledElementsSet.add(nodesThatAreDisabled[i]); |
| } |
| }); |
| const /** @type {?} */ bodyNode = getBodyNode(); |
| const /** @type {?} */ allTriggerElements = Array.from(this.statesByElement.keys()); |
| const /** @type {?} */ enterNodeMap = buildRootMap(allTriggerElements, this.collectedEnterElements); |
| // this must occur before the instructions are built below such that |
| // the :enter queries match the elements (since the timeline queries |
| // are fired during instruction building). |
| const /** @type {?} */ enterNodeMapIds = new Map(); |
| let /** @type {?} */ i = 0; |
| enterNodeMap.forEach((nodes, root) => { |
| const /** @type {?} */ className = ENTER_CLASSNAME + i++; |
| enterNodeMapIds.set(root, className); |
| nodes.forEach(node => addClass(node, className)); |
| }); |
| const /** @type {?} */ allLeaveNodes = []; |
| const /** @type {?} */ mergedLeaveNodes = new Set(); |
| const /** @type {?} */ leaveNodesWithoutAnimations = new Set(); |
| for (let /** @type {?} */ i = 0; i < this.collectedLeaveElements.length; i++) { |
| const /** @type {?} */ element = this.collectedLeaveElements[i]; |
| const /** @type {?} */ details = /** @type {?} */ (element[REMOVAL_FLAG]); |
| if (details && details.setForRemoval) { |
| allLeaveNodes.push(element); |
| mergedLeaveNodes.add(element); |
| if (details.hasAnimation) { |
| this.driver.query(element, STAR_SELECTOR, true).forEach(elm => mergedLeaveNodes.add(elm)); |
| } |
| else { |
| leaveNodesWithoutAnimations.add(element); |
| } |
| } |
| } |
| const /** @type {?} */ leaveNodeMapIds = new Map(); |
| const /** @type {?} */ leaveNodeMap = buildRootMap(allTriggerElements, Array.from(mergedLeaveNodes)); |
| leaveNodeMap.forEach((nodes, root) => { |
| const /** @type {?} */ className = LEAVE_CLASSNAME + i++; |
| leaveNodeMapIds.set(root, className); |
| nodes.forEach(node => addClass(node, className)); |
| }); |
| cleanupFns.push(() => { |
| enterNodeMap.forEach((nodes, root) => { |
| const /** @type {?} */ className = /** @type {?} */ ((enterNodeMapIds.get(root))); |
| nodes.forEach(node => removeClass(node, className)); |
| }); |
| leaveNodeMap.forEach((nodes, root) => { |
| const /** @type {?} */ className = /** @type {?} */ ((leaveNodeMapIds.get(root))); |
| nodes.forEach(node => removeClass(node, className)); |
| }); |
| allLeaveNodes.forEach(element => { this.processLeaveNode(element); }); |
| }); |
| const /** @type {?} */ allPlayers = []; |
| const /** @type {?} */ erroneousTransitions = []; |
| for (let /** @type {?} */ i = this._namespaceList.length - 1; i >= 0; i--) { |
| const /** @type {?} */ ns = this._namespaceList[i]; |
| ns.drainQueuedTransitions(microtaskId).forEach(entry => { |
| const /** @type {?} */ player = entry.player; |
| allPlayers.push(player); |
| const /** @type {?} */ element = entry.element; |
| if (!bodyNode || !this.driver.containsElement(bodyNode, element)) { |
| player.destroy(); |
| return; |
| } |
| const /** @type {?} */ leaveClassName = /** @type {?} */ ((leaveNodeMapIds.get(element))); |
| const /** @type {?} */ enterClassName = /** @type {?} */ ((enterNodeMapIds.get(element))); |
| const /** @type {?} */ instruction = /** @type {?} */ ((this._buildInstruction(entry, subTimelines, enterClassName, leaveClassName))); |
| if (instruction.errors && instruction.errors.length) { |
| erroneousTransitions.push(instruction); |
| return; |
| } |
| // if a unmatched transition is queued to go then it SHOULD NOT render |
| // an animation and cancel the previously running animations. |
| if (entry.isFallbackTransition) { |
| player.onStart(() => eraseStyles(element, instruction.fromStyles)); |
| player.onDestroy(() => setStyles(element, instruction.toStyles)); |
| skippedPlayers.push(player); |
| return; |
| } |
| // this means that if a parent animation uses this animation as a sub trigger |
| // then it will instruct the timeline builder to not add a player delay, but |
| // instead stretch the first keyframe gap up until the animation starts. The |
| // reason this is important is to prevent extra initialization styles from being |
| // required by the user in the animation. |
| instruction.timelines.forEach(tl => tl.stretchStartingKeyframe = true); |
| subTimelines.append(element, instruction.timelines); |
| const /** @type {?} */ tuple = { instruction, player, element }; |
| queuedInstructions.push(tuple); |
| instruction.queriedElements.forEach(element => getOrSetAsInMap(queriedElements, element, []).push(player)); |
| instruction.preStyleProps.forEach((stringMap, element) => { |
| const /** @type {?} */ props = Object.keys(stringMap); |
| if (props.length) { |
| let /** @type {?} */ setVal = /** @type {?} */ ((allPreStyleElements.get(element))); |
| if (!setVal) { |
| allPreStyleElements.set(element, setVal = new Set()); |
| } |
| props.forEach(prop => setVal.add(prop)); |
| } |
| }); |
| instruction.postStyleProps.forEach((stringMap, element) => { |
| const /** @type {?} */ props = Object.keys(stringMap); |
| let /** @type {?} */ setVal = /** @type {?} */ ((allPostStyleElements.get(element))); |
| if (!setVal) { |
| allPostStyleElements.set(element, setVal = new Set()); |
| } |
| props.forEach(prop => setVal.add(prop)); |
| }); |
| }); |
| } |
| if (erroneousTransitions.length) { |
| const /** @type {?} */ errors = []; |
| erroneousTransitions.forEach(instruction => { |
| errors.push(`@${instruction.triggerName} has failed due to:\n`); /** @type {?} */ |
| ((instruction.errors)).forEach(error => errors.push(`- ${error}\n`)); |
| }); |
| allPlayers.forEach(player => player.destroy()); |
| this.reportError(errors); |
| } |
| const /** @type {?} */ allPreviousPlayersMap = new Map(); |
| // this map works to tell which element in the DOM tree is contained by |
| // which animation. Further down below this map will get populated once |
| // the players are built and in doing so it can efficiently figure out |
| // if a sub player is skipped due to a parent player having priority. |
| const /** @type {?} */ animationElementMap = new Map(); |
| queuedInstructions.forEach(entry => { |
| const /** @type {?} */ element = entry.element; |
| if (subTimelines.has(element)) { |
| animationElementMap.set(element, element); |
| this._beforeAnimationBuild(entry.player.namespaceId, entry.instruction, allPreviousPlayersMap); |
| } |
| }); |
| skippedPlayers.forEach(player => { |
| const /** @type {?} */ element = player.element; |
| const /** @type {?} */ previousPlayers = this._getPreviousPlayers(element, false, player.namespaceId, player.triggerName, null); |
| previousPlayers.forEach(prevPlayer => { |
| getOrSetAsInMap(allPreviousPlayersMap, element, []).push(prevPlayer); |
| prevPlayer.destroy(); |
| }); |
| }); |
| // this is a special case for nodes that will be removed (either by) |
| // having their own leave animations or by being queried in a container |
| // that will be removed once a parent animation is complete. The idea |
| // here is that * styles must be identical to ! styles because of |
| // backwards compatibility (* is also filled in by default in many places). |
| // Otherwise * styles will return an empty value or auto since the element |
| // that is being getComputedStyle'd will not be visible (since * = destination) |
| const /** @type {?} */ replaceNodes = allLeaveNodes.filter(node => { |
| return replacePostStylesAsPre(node, allPreStyleElements, allPostStyleElements); |
| }); |
| // POST STAGE: fill the * styles |
| const /** @type {?} */ postStylesMap = new Map(); |
| const /** @type {?} */ allLeaveQueriedNodes = cloakAndComputeStyles(postStylesMap, this.driver, leaveNodesWithoutAnimations, allPostStyleElements, AUTO_STYLE); |
| allLeaveQueriedNodes.forEach(node => { |
| if (replacePostStylesAsPre(node, allPreStyleElements, allPostStyleElements)) { |
| replaceNodes.push(node); |
| } |
| }); |
| // PRE STAGE: fill the ! styles |
| const /** @type {?} */ preStylesMap = new Map(); |
| enterNodeMap.forEach((nodes, root) => { |
| cloakAndComputeStyles(preStylesMap, this.driver, new Set(nodes), allPreStyleElements, ɵPRE_STYLE); |
| }); |
| replaceNodes.forEach(node => { |
| const /** @type {?} */ post = postStylesMap.get(node); |
| const /** @type {?} */ pre = preStylesMap.get(node); |
| postStylesMap.set(node, /** @type {?} */ (Object.assign({}, post, pre))); |
| }); |
| const /** @type {?} */ rootPlayers = []; |
| const /** @type {?} */ subPlayers = []; |
| const /** @type {?} */ NO_PARENT_ANIMATION_ELEMENT_DETECTED = {}; |
| queuedInstructions.forEach(entry => { |
| const { element, player, instruction } = entry; |
| // this means that it was never consumed by a parent animation which |
| // means that it is independent and therefore should be set for animation |
| if (subTimelines.has(element)) { |
| if (disabledElementsSet.has(element)) { |
| player.onDestroy(() => setStyles(element, instruction.toStyles)); |
| skippedPlayers.push(player); |
| return; |
| } |
| // this will flow up the DOM and query the map to figure out |
| // if a parent animation has priority over it. In the situation |
| // that a parent is detected then it will cancel the loop. If |
| // nothing is detected, or it takes a few hops to find a parent, |
| // then it will fill in the missing nodes and signal them as having |
| // a detected parent (or a NO_PARENT value via a special constant). |
| let /** @type {?} */ parentWithAnimation = NO_PARENT_ANIMATION_ELEMENT_DETECTED; |
| if (animationElementMap.size > 1) { |
| let /** @type {?} */ elm = element; |
| const /** @type {?} */ parentsToAdd = []; |
| while (elm = elm.parentNode) { |
| const /** @type {?} */ detectedParent = animationElementMap.get(elm); |
| if (detectedParent) { |
| parentWithAnimation = detectedParent; |
| break; |
| } |
| parentsToAdd.push(elm); |
| } |
| parentsToAdd.forEach(parent => animationElementMap.set(parent, parentWithAnimation)); |
| } |
| const /** @type {?} */ innerPlayer = this._buildAnimation(player.namespaceId, instruction, allPreviousPlayersMap, skippedPlayersMap, preStylesMap, postStylesMap); |
| player.setRealPlayer(innerPlayer); |
| if (parentWithAnimation === NO_PARENT_ANIMATION_ELEMENT_DETECTED) { |
| rootPlayers.push(player); |
| } |
| else { |
| const /** @type {?} */ parentPlayers = this.playersByElement.get(parentWithAnimation); |
| if (parentPlayers && parentPlayers.length) { |
| player.parentPlayer = optimizeGroupPlayer(parentPlayers); |
| } |
| skippedPlayers.push(player); |
| } |
| } |
| else { |
| eraseStyles(element, instruction.fromStyles); |
| player.onDestroy(() => setStyles(element, instruction.toStyles)); |
| // there still might be a ancestor player animating this |
| // element therefore we will still add it as a sub player |
| // even if its animation may be disabled |
| subPlayers.push(player); |
| if (disabledElementsSet.has(element)) { |
| skippedPlayers.push(player); |
| } |
| } |
| }); |
| // find all of the sub players' corresponding inner animation player |
| subPlayers.forEach(player => { |
| // even if any players are not found for a sub animation then it |
| // will still complete itself after the next tick since it's Noop |
| const /** @type {?} */ playersForElement = skippedPlayersMap.get(player.element); |
| if (playersForElement && playersForElement.length) { |
| const /** @type {?} */ innerPlayer = optimizeGroupPlayer(playersForElement); |
| player.setRealPlayer(innerPlayer); |
| } |
| }); |
| // the reason why we don't actually play the animation is |
| // because all that a skipped player is designed to do is to |
| // fire the start/done transition callback events |
| skippedPlayers.forEach(player => { |
| if (player.parentPlayer) { |
| player.syncPlayerEvents(player.parentPlayer); |
| } |
| else { |
| player.destroy(); |
| } |
| }); |
| // run through all of the queued removals and see if they |
| // were picked up by a query. If not then perform the removal |
| // operation right away unless a parent animation is ongoing. |
| for (let /** @type {?} */ i = 0; i < allLeaveNodes.length; i++) { |
| const /** @type {?} */ element = allLeaveNodes[i]; |
| const /** @type {?} */ details = /** @type {?} */ (element[REMOVAL_FLAG]); |
| removeClass(element, LEAVE_CLASSNAME); |
| // this means the element has a removal animation that is being |
| // taken care of and therefore the inner elements will hang around |
| // until that animation is over (or the parent queried animation) |
| if (details && details.hasAnimation) |
| continue; |
| let /** @type {?} */ players = []; |
| // if this element is queried or if it contains queried children |
| // then we want for the element not to be removed from the page |
| // until the queried animations have finished |
| if (queriedElements.size) { |
| let /** @type {?} */ queriedPlayerResults = queriedElements.get(element); |
| if (queriedPlayerResults && queriedPlayerResults.length) { |
| players.push(...queriedPlayerResults); |
| } |
| let /** @type {?} */ queriedInnerElements = this.driver.query(element, NG_ANIMATING_SELECTOR, true); |
| for (let /** @type {?} */ j = 0; j < queriedInnerElements.length; j++) { |
| let /** @type {?} */ queriedPlayers = queriedElements.get(queriedInnerElements[j]); |
| if (queriedPlayers && queriedPlayers.length) { |
| players.push(...queriedPlayers); |
| } |
| } |
| } |
| const /** @type {?} */ activePlayers = players.filter(p => !p.destroyed); |
| if (activePlayers.length) { |
| removeNodesAfterAnimationDone(this, element, activePlayers); |
| } |
| else { |
| this.processLeaveNode(element); |
| } |
| } |
| // this is required so the cleanup method doesn't remove them |
| allLeaveNodes.length = 0; |
| rootPlayers.forEach(player => { |
| this.players.push(player); |
| player.onDone(() => { |
| player.destroy(); |
| const /** @type {?} */ index = this.players.indexOf(player); |
| this.players.splice(index, 1); |
| }); |
| player.play(); |
| }); |
| return rootPlayers; |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} element |
| * @return {?} |
| */ |
| elementContainsData(namespaceId, element) { |
| let /** @type {?} */ containsData = false; |
| const /** @type {?} */ details = /** @type {?} */ (element[REMOVAL_FLAG]); |
| if (details && details.setForRemoval) |
| containsData = true; |
| if (this.playersByElement.has(element)) |
| containsData = true; |
| if (this.playersByQueriedElement.has(element)) |
| containsData = true; |
| if (this.statesByElement.has(element)) |
| containsData = true; |
| return this._fetchNamespace(namespaceId).elementContainsData(element) || containsData; |
| } |
| /** |
| * @param {?} callback |
| * @return {?} |
| */ |
| afterFlush(callback) { this._flushFns.push(callback); } |
| /** |
| * @param {?} callback |
| * @return {?} |
| */ |
| afterFlushAnimationsDone(callback) { this._whenQuietFns.push(callback); } |
| /** |
| * @param {?} element |
| * @param {?} isQueriedElement |
| * @param {?=} namespaceId |
| * @param {?=} triggerName |
| * @param {?=} toStateValue |
| * @return {?} |
| */ |
| _getPreviousPlayers(element, isQueriedElement, namespaceId, triggerName, toStateValue) { |
| let /** @type {?} */ players = []; |
| if (isQueriedElement) { |
| const /** @type {?} */ queriedElementPlayers = this.playersByQueriedElement.get(element); |
| if (queriedElementPlayers) { |
| players = queriedElementPlayers; |
| } |
| } |
| else { |
| const /** @type {?} */ elementPlayers = this.playersByElement.get(element); |
| if (elementPlayers) { |
| const /** @type {?} */ isRemovalAnimation = !toStateValue || toStateValue == VOID_VALUE; |
| elementPlayers.forEach(player => { |
| if (player.queued) |
| return; |
| if (!isRemovalAnimation && player.triggerName != triggerName) |
| return; |
| players.push(player); |
| }); |
| } |
| } |
| if (namespaceId || triggerName) { |
| players = players.filter(player => { |
| if (namespaceId && namespaceId != player.namespaceId) |
| return false; |
| if (triggerName && triggerName != player.triggerName) |
| return false; |
| return true; |
| }); |
| } |
| return players; |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} instruction |
| * @param {?} allPreviousPlayersMap |
| * @return {?} |
| */ |
| _beforeAnimationBuild(namespaceId, instruction, allPreviousPlayersMap) { |
| const /** @type {?} */ triggerName = instruction.triggerName; |
| const /** @type {?} */ rootElement = instruction.element; |
| // when a removal animation occurs, ALL previous players are collected |
| // and destroyed (even if they are outside of the current namespace) |
| const /** @type {?} */ targetNameSpaceId = instruction.isRemovalTransition ? undefined : namespaceId; |
| const /** @type {?} */ targetTriggerName = instruction.isRemovalTransition ? undefined : triggerName; |
| for (const /** @type {?} */ timelineInstruction of instruction.timelines) { |
| const /** @type {?} */ element = timelineInstruction.element; |
| const /** @type {?} */ isQueriedElement = element !== rootElement; |
| const /** @type {?} */ players = getOrSetAsInMap(allPreviousPlayersMap, element, []); |
| const /** @type {?} */ previousPlayers = this._getPreviousPlayers(element, isQueriedElement, targetNameSpaceId, targetTriggerName, instruction.toState); |
| previousPlayers.forEach(player => { |
| const /** @type {?} */ realPlayer = /** @type {?} */ (player.getRealPlayer()); |
| if (realPlayer.beforeDestroy) { |
| realPlayer.beforeDestroy(); |
| } |
| player.destroy(); |
| players.push(player); |
| }); |
| } |
| // this needs to be done so that the PRE/POST styles can be |
| // computed properly without interfering with the previous animation |
| eraseStyles(rootElement, instruction.fromStyles); |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} instruction |
| * @param {?} allPreviousPlayersMap |
| * @param {?} skippedPlayersMap |
| * @param {?} preStylesMap |
| * @param {?} postStylesMap |
| * @return {?} |
| */ |
| _buildAnimation(namespaceId, instruction, allPreviousPlayersMap, skippedPlayersMap, preStylesMap, postStylesMap) { |
| const /** @type {?} */ triggerName = instruction.triggerName; |
| const /** @type {?} */ rootElement = instruction.element; |
| // we first run this so that the previous animation player |
| // data can be passed into the successive animation players |
| const /** @type {?} */ allQueriedPlayers = []; |
| const /** @type {?} */ allConsumedElements = new Set(); |
| const /** @type {?} */ allSubElements = new Set(); |
| const /** @type {?} */ allNewPlayers = instruction.timelines.map(timelineInstruction => { |
| const /** @type {?} */ element = timelineInstruction.element; |
| allConsumedElements.add(element); |
| // FIXME (matsko): make sure to-be-removed animations are removed properly |
| const /** @type {?} */ details = element[REMOVAL_FLAG]; |
| if (details && details.removedBeforeQueried) |
| return new NoopAnimationPlayer(); |
| const /** @type {?} */ isQueriedElement = element !== rootElement; |
| const /** @type {?} */ previousPlayers = flattenGroupPlayers((allPreviousPlayersMap.get(element) || EMPTY_PLAYER_ARRAY) |
| .map(p => p.getRealPlayer())) |
| .filter(p => { |
| // the `element` is not apart of the AnimationPlayer definition, but |
| // Mock/WebAnimations |
| // use the element within their implementation. This will be added in Angular5 to |
| // AnimationPlayer |
| const /** @type {?} */ pp = /** @type {?} */ (p); |
| return pp.element ? pp.element === element : false; |
| }); |
| const /** @type {?} */ preStyles = preStylesMap.get(element); |
| const /** @type {?} */ postStyles = postStylesMap.get(element); |
| const /** @type {?} */ keyframes = normalizeKeyframes(this.driver, this._normalizer, element, timelineInstruction.keyframes, preStyles, postStyles); |
| const /** @type {?} */ player = this._buildPlayer(timelineInstruction, keyframes, previousPlayers); |
| // this means that this particular player belongs to a sub trigger. It is |
| // important that we match this player up with the corresponding (@trigger.listener) |
| if (timelineInstruction.subTimeline && skippedPlayersMap) { |
| allSubElements.add(element); |
| } |
| if (isQueriedElement) { |
| const /** @type {?} */ wrappedPlayer = new TransitionAnimationPlayer(namespaceId, triggerName, element); |
| wrappedPlayer.setRealPlayer(player); |
| allQueriedPlayers.push(wrappedPlayer); |
| } |
| return player; |
| }); |
| allQueriedPlayers.forEach(player => { |
| getOrSetAsInMap(this.playersByQueriedElement, player.element, []).push(player); |
| player.onDone(() => deleteOrUnsetInMap(this.playersByQueriedElement, player.element, player)); |
| }); |
| allConsumedElements.forEach(element => addClass(element, NG_ANIMATING_CLASSNAME)); |
| const /** @type {?} */ player = optimizeGroupPlayer(allNewPlayers); |
| player.onDestroy(() => { |
| allConsumedElements.forEach(element => removeClass(element, NG_ANIMATING_CLASSNAME)); |
| setStyles(rootElement, instruction.toStyles); |
| }); |
| // this basically makes all of the callbacks for sub element animations |
| // be dependent on the upper players for when they finish |
| allSubElements.forEach(element => { getOrSetAsInMap(skippedPlayersMap, element, []).push(player); }); |
| return player; |
| } |
| /** |
| * @param {?} instruction |
| * @param {?} keyframes |
| * @param {?} previousPlayers |
| * @return {?} |
| */ |
| _buildPlayer(instruction, keyframes, previousPlayers) { |
| if (keyframes.length > 0) { |
| return this.driver.animate(instruction.element, keyframes, instruction.duration, instruction.delay, instruction.easing, previousPlayers); |
| } |
| // special case for when an empty transition|definition is provided |
| // ... there is no point in rendering an empty animation |
| return new NoopAnimationPlayer(); |
| } |
| } |
| class TransitionAnimationPlayer { |
| /** |
| * @param {?} namespaceId |
| * @param {?} triggerName |
| * @param {?} element |
| */ |
| constructor(namespaceId, triggerName, element) { |
| this.namespaceId = namespaceId; |
| this.triggerName = triggerName; |
| this.element = element; |
| this._player = new NoopAnimationPlayer(); |
| this._containsRealPlayer = false; |
| this._queuedCallbacks = {}; |
| this.destroyed = false; |
| this.markedForDestroy = false; |
| this.queued = true; |
| } |
| /** |
| * @param {?} player |
| * @return {?} |
| */ |
| setRealPlayer(player) { |
| if (this._containsRealPlayer) |
| return; |
| this._player = player; |
| Object.keys(this._queuedCallbacks).forEach(phase => { |
| this._queuedCallbacks[phase].forEach(callback => listenOnPlayer(player, phase, undefined, callback)); |
| }); |
| this._queuedCallbacks = {}; |
| this._containsRealPlayer = true; |
| (/** @type {?} */ (this)).queued = false; |
| } |
| /** |
| * @return {?} |
| */ |
| getRealPlayer() { return this._player; } |
| /** |
| * @param {?} player |
| * @return {?} |
| */ |
| syncPlayerEvents(player) { |
| const /** @type {?} */ p = /** @type {?} */ (this._player); |
| if (p.triggerCallback) { |
| player.onStart(() => p.triggerCallback('start')); |
| } |
| player.onDone(() => this.finish()); |
| player.onDestroy(() => this.destroy()); |
| } |
| /** |
| * @param {?} name |
| * @param {?} callback |
| * @return {?} |
| */ |
| _queueEvent(name, callback) { |
| getOrSetAsInMap(this._queuedCallbacks, name, []).push(callback); |
| } |
| /** |
| * @param {?} fn |
| * @return {?} |
| */ |
| onDone(fn) { |
| if (this.queued) { |
| this._queueEvent('done', fn); |
| } |
| this._player.onDone(fn); |
| } |
| /** |
| * @param {?} fn |
| * @return {?} |
| */ |
| onStart(fn) { |
| if (this.queued) { |
| this._queueEvent('start', fn); |
| } |
| this._player.onStart(fn); |
| } |
| /** |
| * @param {?} fn |
| * @return {?} |
| */ |
| onDestroy(fn) { |
| if (this.queued) { |
| this._queueEvent('destroy', fn); |
| } |
| this._player.onDestroy(fn); |
| } |
| /** |
| * @return {?} |
| */ |
| init() { this._player.init(); } |
| /** |
| * @return {?} |
| */ |
| hasStarted() { return this.queued ? false : this._player.hasStarted(); } |
| /** |
| * @return {?} |
| */ |
| play() { !this.queued && this._player.play(); } |
| /** |
| * @return {?} |
| */ |
| pause() { !this.queued && this._player.pause(); } |
| /** |
| * @return {?} |
| */ |
| restart() { !this.queued && this._player.restart(); } |
| /** |
| * @return {?} |
| */ |
| finish() { this._player.finish(); } |
| /** |
| * @return {?} |
| */ |
| destroy() { |
| (/** @type {?} */ (this)).destroyed = true; |
| this._player.destroy(); |
| } |
| /** |
| * @return {?} |
| */ |
| reset() { !this.queued && this._player.reset(); } |
| /** |
| * @param {?} p |
| * @return {?} |
| */ |
| setPosition(p) { |
| if (!this.queued) { |
| this._player.setPosition(p); |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| getPosition() { return this.queued ? 0 : this._player.getPosition(); } |
| /** |
| * @return {?} |
| */ |
| get totalTime() { return this._player.totalTime; } |
| /** |
| * @param {?} phaseName |
| * @return {?} |
| */ |
| triggerCallback(phaseName) { |
| const /** @type {?} */ p = /** @type {?} */ (this._player); |
| if (p.triggerCallback) { |
| p.triggerCallback(phaseName); |
| } |
| } |
| } |
| /** |
| * @param {?} map |
| * @param {?} key |
| * @param {?} value |
| * @return {?} |
| */ |
| function deleteOrUnsetInMap(map, key, value) { |
| let /** @type {?} */ currentValues; |
| if (map instanceof Map) { |
| currentValues = map.get(key); |
| if (currentValues) { |
| if (currentValues.length) { |
| const /** @type {?} */ index = currentValues.indexOf(value); |
| currentValues.splice(index, 1); |
| } |
| if (currentValues.length == 0) { |
| map.delete(key); |
| } |
| } |
| } |
| else { |
| currentValues = map[key]; |
| if (currentValues) { |
| if (currentValues.length) { |
| const /** @type {?} */ index = currentValues.indexOf(value); |
| currentValues.splice(index, 1); |
| } |
| if (currentValues.length == 0) { |
| delete map[key]; |
| } |
| } |
| } |
| return currentValues; |
| } |
| /** |
| * @param {?} value |
| * @return {?} |
| */ |
| function normalizeTriggerValue(value) { |
| // we use `!= null` here because it's the most simple |
| // way to test against a "falsy" value without mixing |
| // in empty strings or a zero value. DO NOT OPTIMIZE. |
| return value != null ? value : null; |
| } |
| /** |
| * @param {?} node |
| * @return {?} |
| */ |
| function isElementNode(node) { |
| return node && node['nodeType'] === 1; |
| } |
| /** |
| * @param {?} eventName |
| * @return {?} |
| */ |
| function isTriggerEventValid(eventName) { |
| return eventName == 'start' || eventName == 'done'; |
| } |
| /** |
| * @param {?} element |
| * @param {?=} value |
| * @return {?} |
| */ |
| function cloakElement(element, value) { |
| const /** @type {?} */ oldValue = element.style.display; |
| element.style.display = value != null ? value : 'none'; |
| return oldValue; |
| } |
| /** |
| * @param {?} valuesMap |
| * @param {?} driver |
| * @param {?} elements |
| * @param {?} elementPropsMap |
| * @param {?} defaultStyle |
| * @return {?} |
| */ |
| function cloakAndComputeStyles(valuesMap, driver, elements, elementPropsMap, defaultStyle) { |
| const /** @type {?} */ cloakVals = []; |
| elements.forEach(element => cloakVals.push(cloakElement(element))); |
| const /** @type {?} */ failedElements = []; |
| elementPropsMap.forEach((props, element) => { |
| const /** @type {?} */ styles = {}; |
| props.forEach(prop => { |
| const /** @type {?} */ value = styles[prop] = driver.computeStyle(element, prop, defaultStyle); |
| // there is no easy way to detect this because a sub element could be removed |
| // by a parent animation element being detached. |
| if (!value || value.length == 0) { |
| element[REMOVAL_FLAG] = NULL_REMOVED_QUERIED_STATE; |
| failedElements.push(element); |
| } |
| }); |
| valuesMap.set(element, styles); |
| }); |
| // we use a index variable here since Set.forEach(a, i) does not return |
| // an index value for the closure (but instead just the value) |
| let /** @type {?} */ i = 0; |
| elements.forEach(element => cloakElement(element, cloakVals[i++])); |
| return failedElements; |
| } |
| /** |
| * @param {?} roots |
| * @param {?} nodes |
| * @return {?} |
| */ |
| function buildRootMap(roots, nodes) { |
| const /** @type {?} */ rootMap = new Map(); |
| roots.forEach(root => rootMap.set(root, [])); |
| if (nodes.length == 0) |
| return rootMap; |
| const /** @type {?} */ NULL_NODE = 1; |
| const /** @type {?} */ nodeSet = new Set(nodes); |
| const /** @type {?} */ localRootMap = new Map(); |
| /** |
| * @param {?} node |
| * @return {?} |
| */ |
| function getRoot(node) { |
| if (!node) |
| return NULL_NODE; |
| let /** @type {?} */ root = localRootMap.get(node); |
| if (root) |
| return root; |
| const /** @type {?} */ parent = node.parentNode; |
| if (rootMap.has(parent)) { |
| // ngIf inside @trigger |
| root = parent; |
| } |
| else if (nodeSet.has(parent)) { |
| // ngIf inside ngIf |
| root = NULL_NODE; |
| } |
| else { |
| // recurse upwards |
| root = getRoot(parent); |
| } |
| localRootMap.set(node, root); |
| return root; |
| } |
| nodes.forEach(node => { |
| const /** @type {?} */ root = getRoot(node); |
| if (root !== NULL_NODE) { |
| /** @type {?} */ ((rootMap.get(root))).push(node); |
| } |
| }); |
| return rootMap; |
| } |
| const CLASSES_CACHE_KEY = '$$classes'; |
| /** |
| * @param {?} element |
| * @param {?} className |
| * @return {?} |
| */ |
| function addClass(element, className) { |
| if (element.classList) { |
| element.classList.add(className); |
| } |
| else { |
| let /** @type {?} */ classes = element[CLASSES_CACHE_KEY]; |
| if (!classes) { |
| classes = element[CLASSES_CACHE_KEY] = {}; |
| } |
| classes[className] = true; |
| } |
| } |
| /** |
| * @param {?} element |
| * @param {?} className |
| * @return {?} |
| */ |
| function removeClass(element, className) { |
| if (element.classList) { |
| element.classList.remove(className); |
| } |
| else { |
| let /** @type {?} */ classes = element[CLASSES_CACHE_KEY]; |
| if (classes) { |
| delete classes[className]; |
| } |
| } |
| } |
| /** |
| * @param {?} engine |
| * @param {?} element |
| * @param {?} players |
| * @return {?} |
| */ |
| function removeNodesAfterAnimationDone(engine, element, players) { |
| optimizeGroupPlayer(players).onDone(() => engine.processLeaveNode(element)); |
| } |
| /** |
| * @param {?} players |
| * @return {?} |
| */ |
| function flattenGroupPlayers(players) { |
| const /** @type {?} */ finalPlayers = []; |
| _flattenGroupPlayersRecur(players, finalPlayers); |
| return finalPlayers; |
| } |
| /** |
| * @param {?} players |
| * @param {?} finalPlayers |
| * @return {?} |
| */ |
| function _flattenGroupPlayersRecur(players, finalPlayers) { |
| for (let /** @type {?} */ i = 0; i < players.length; i++) { |
| const /** @type {?} */ player = players[i]; |
| if (player instanceof ɵAnimationGroupPlayer) { |
| _flattenGroupPlayersRecur(player.players, finalPlayers); |
| } |
| else { |
| finalPlayers.push(/** @type {?} */ (player)); |
| } |
| } |
| } |
| /** |
| * @param {?} a |
| * @param {?} b |
| * @return {?} |
| */ |
| function objEquals(a, b) { |
| const /** @type {?} */ k1 = Object.keys(a); |
| const /** @type {?} */ k2 = Object.keys(b); |
| if (k1.length != k2.length) |
| return false; |
| for (let /** @type {?} */ i = 0; i < k1.length; i++) { |
| const /** @type {?} */ prop = k1[i]; |
| if (!b.hasOwnProperty(prop) || a[prop] !== b[prop]) |
| return false; |
| } |
| return true; |
| } |
| /** |
| * @param {?} element |
| * @param {?} allPreStyleElements |
| * @param {?} allPostStyleElements |
| * @return {?} |
| */ |
| function replacePostStylesAsPre(element, allPreStyleElements, allPostStyleElements) { |
| const /** @type {?} */ postEntry = allPostStyleElements.get(element); |
| if (!postEntry) |
| return false; |
| let /** @type {?} */ preEntry = allPreStyleElements.get(element); |
| if (preEntry) { |
| postEntry.forEach(data => /** @type {?} */ ((preEntry)).add(data)); |
| } |
| else { |
| allPreStyleElements.set(element, postEntry); |
| } |
| allPostStyleElements.delete(element); |
| return true; |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| class AnimationEngine { |
| /** |
| * @param {?} _driver |
| * @param {?} normalizer |
| */ |
| constructor(_driver, normalizer) { |
| this._driver = _driver; |
| this._triggerCache = {}; |
| this.onRemovalComplete = (element, context) => { }; |
| this._transitionEngine = new TransitionAnimationEngine(_driver, normalizer); |
| this._timelineEngine = new TimelineAnimationEngine(_driver, normalizer); |
| this._transitionEngine.onRemovalComplete = (element, context) => this.onRemovalComplete(element, context); |
| } |
| /** |
| * @param {?} componentId |
| * @param {?} namespaceId |
| * @param {?} hostElement |
| * @param {?} name |
| * @param {?} metadata |
| * @return {?} |
| */ |
| registerTrigger(componentId, namespaceId, hostElement, name, metadata) { |
| const /** @type {?} */ cacheKey = componentId + '-' + name; |
| let /** @type {?} */ trigger = this._triggerCache[cacheKey]; |
| if (!trigger) { |
| const /** @type {?} */ errors = []; |
| const /** @type {?} */ ast = /** @type {?} */ (buildAnimationAst(this._driver, /** @type {?} */ (metadata), errors)); |
| if (errors.length) { |
| throw new Error(`The animation trigger "${name}" has failed to build due to the following errors:\n - ${errors.join("\n - ")}`); |
| } |
| trigger = buildTrigger(name, ast); |
| this._triggerCache[cacheKey] = trigger; |
| } |
| this._transitionEngine.registerTrigger(namespaceId, name, trigger); |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} hostElement |
| * @return {?} |
| */ |
| register(namespaceId, hostElement) { |
| this._transitionEngine.register(namespaceId, hostElement); |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} context |
| * @return {?} |
| */ |
| destroy(namespaceId, context) { |
| this._transitionEngine.destroy(namespaceId, context); |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} element |
| * @param {?} parent |
| * @param {?} insertBefore |
| * @return {?} |
| */ |
| onInsert(namespaceId, element, parent, insertBefore) { |
| this._transitionEngine.insertNode(namespaceId, element, parent, insertBefore); |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} element |
| * @param {?} context |
| * @return {?} |
| */ |
| onRemove(namespaceId, element, context) { |
| this._transitionEngine.removeNode(namespaceId, element, context); |
| } |
| /** |
| * @param {?} element |
| * @param {?} disable |
| * @return {?} |
| */ |
| disableAnimations(element, disable) { |
| this._transitionEngine.markElementAsDisabled(element, disable); |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} element |
| * @param {?} property |
| * @param {?} value |
| * @return {?} |
| */ |
| process(namespaceId, element, property, value) { |
| if (property.charAt(0) == '@') { |
| const [id, action] = parseTimelineCommand(property); |
| const /** @type {?} */ args = /** @type {?} */ (value); |
| this._timelineEngine.command(id, element, action, args); |
| } |
| else { |
| this._transitionEngine.trigger(namespaceId, element, property, value); |
| } |
| } |
| /** |
| * @param {?} namespaceId |
| * @param {?} element |
| * @param {?} eventName |
| * @param {?} eventPhase |
| * @param {?} callback |
| * @return {?} |
| */ |
| listen(namespaceId, element, eventName, eventPhase, callback) { |
| // @@listen |
| if (eventName.charAt(0) == '@') { |
| const [id, action] = parseTimelineCommand(eventName); |
| return this._timelineEngine.listen(id, element, action, callback); |
| } |
| return this._transitionEngine.listen(namespaceId, element, eventName, eventPhase, callback); |
| } |
| /** |
| * @param {?=} microtaskId |
| * @return {?} |
| */ |
| flush(microtaskId = -1) { this._transitionEngine.flush(microtaskId); } |
| /** |
| * @return {?} |
| */ |
| get players() { |
| return (/** @type {?} */ (this._transitionEngine.players)) |
| .concat(/** @type {?} */ (this._timelineEngine.players)); |
| } |
| /** |
| * @return {?} |
| */ |
| whenRenderingDone() { return this._transitionEngine.whenRenderingDone(); } |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| class WebAnimationsPlayer { |
| /** |
| * @param {?} element |
| * @param {?} keyframes |
| * @param {?} options |
| * @param {?=} previousPlayers |
| */ |
| constructor(element, keyframes, options, previousPlayers = []) { |
| this.element = element; |
| this.keyframes = keyframes; |
| this.options = options; |
| this.previousPlayers = previousPlayers; |
| this._onDoneFns = []; |
| this._onStartFns = []; |
| this._onDestroyFns = []; |
| this._initialized = false; |
| this._finished = false; |
| this._started = false; |
| this._destroyed = false; |
| this.time = 0; |
| this.parentPlayer = null; |
| this.previousStyles = {}; |
| this.currentSnapshot = {}; |
| this._duration = /** @type {?} */ (options['duration']); |
| this._delay = /** @type {?} */ (options['delay']) || 0; |
| this.time = this._duration + this._delay; |
| if (allowPreviousPlayerStylesMerge(this._duration, this._delay)) { |
| previousPlayers.forEach(player => { |
| let /** @type {?} */ styles = player.currentSnapshot; |
| Object.keys(styles).forEach(prop => this.previousStyles[prop] = styles[prop]); |
| }); |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| _onFinish() { |
| if (!this._finished) { |
| this._finished = true; |
| this._onDoneFns.forEach(fn => fn()); |
| this._onDoneFns = []; |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| init() { |
| this._buildPlayer(); |
| this._preparePlayerBeforeStart(); |
| } |
| /** |
| * @return {?} |
| */ |
| _buildPlayer() { |
| if (this._initialized) |
| return; |
| this._initialized = true; |
| const /** @type {?} */ keyframes = this.keyframes.map(styles => copyStyles(styles, false)); |
| const /** @type {?} */ previousStyleProps = Object.keys(this.previousStyles); |
| if (previousStyleProps.length && keyframes.length) { |
| let /** @type {?} */ startingKeyframe = keyframes[0]; |
| let /** @type {?} */ missingStyleProps = []; |
| previousStyleProps.forEach(prop => { |
| if (!startingKeyframe.hasOwnProperty(prop)) { |
| missingStyleProps.push(prop); |
| } |
| startingKeyframe[prop] = this.previousStyles[prop]; |
| }); |
| if (missingStyleProps.length) { |
| const /** @type {?} */ self = this; |
| // tslint:disable-next-line |
| for (var /** @type {?} */ i = 1; i < keyframes.length; i++) { |
| let /** @type {?} */ kf = keyframes[i]; |
| missingStyleProps.forEach(function (prop) { |
| kf[prop] = _computeStyle(self.element, prop); |
| }); |
| } |
| } |
| } |
| (/** @type {?} */ (this)).domPlayer = |
| this._triggerWebAnimation(this.element, keyframes, this.options); |
| this._finalKeyframe = keyframes.length ? keyframes[keyframes.length - 1] : {}; |
| this.domPlayer.addEventListener('finish', () => this._onFinish()); |
| } |
| /** |
| * @return {?} |
| */ |
| _preparePlayerBeforeStart() { |
| // this is required so that the player doesn't start to animate right away |
| if (this._delay) { |
| this._resetDomPlayerState(); |
| } |
| else { |
| this.domPlayer.pause(); |
| } |
| } |
| /** |
| * \@internal |
| * @param {?} element |
| * @param {?} keyframes |
| * @param {?} options |
| * @return {?} |
| */ |
| _triggerWebAnimation(element, keyframes, options) { |
| // jscompiler doesn't seem to know animate is a native property because it's not fully |
| // supported yet across common browsers (we polyfill it for Edge/Safari) [CL #143630929] |
| return /** @type {?} */ (element['animate'](keyframes, options)); |
| } |
| /** |
| * @param {?} fn |
| * @return {?} |
| */ |
| onStart(fn) { this._onStartFns.push(fn); } |
| /** |
| * @param {?} fn |
| * @return {?} |
| */ |
| onDone(fn) { this._onDoneFns.push(fn); } |
| /** |
| * @param {?} fn |
| * @return {?} |
| */ |
| onDestroy(fn) { this._onDestroyFns.push(fn); } |
| /** |
| * @return {?} |
| */ |
| play() { |
| this._buildPlayer(); |
| if (!this.hasStarted()) { |
| this._onStartFns.forEach(fn => fn()); |
| this._onStartFns = []; |
| this._started = true; |
| } |
| this.domPlayer.play(); |
| } |
| /** |
| * @return {?} |
| */ |
| pause() { |
| this.init(); |
| this.domPlayer.pause(); |
| } |
| /** |
| * @return {?} |
| */ |
| finish() { |
| this.init(); |
| this._onFinish(); |
| this.domPlayer.finish(); |
| } |
| /** |
| * @return {?} |
| */ |
| reset() { |
| this._resetDomPlayerState(); |
| this._destroyed = false; |
| this._finished = false; |
| this._started = false; |
| } |
| /** |
| * @return {?} |
| */ |
| _resetDomPlayerState() { |
| if (this.domPlayer) { |
| this.domPlayer.cancel(); |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| restart() { |
| this.reset(); |
| this.play(); |
| } |
| /** |
| * @return {?} |
| */ |
| hasStarted() { return this._started; } |
| /** |
| * @return {?} |
| */ |
| destroy() { |
| if (!this._destroyed) { |
| this._destroyed = true; |
| this._resetDomPlayerState(); |
| this._onFinish(); |
| this._onDestroyFns.forEach(fn => fn()); |
| this._onDestroyFns = []; |
| } |
| } |
| /** |
| * @param {?} p |
| * @return {?} |
| */ |
| setPosition(p) { this.domPlayer.currentTime = p * this.time; } |
| /** |
| * @return {?} |
| */ |
| getPosition() { return this.domPlayer.currentTime / this.time; } |
| /** |
| * @return {?} |
| */ |
| get totalTime() { return this._delay + this._duration; } |
| /** |
| * @return {?} |
| */ |
| beforeDestroy() { |
| const /** @type {?} */ styles = {}; |
| if (this.hasStarted()) { |
| Object.keys(this._finalKeyframe).forEach(prop => { |
| if (prop != 'offset') { |
| styles[prop] = |
| this._finished ? this._finalKeyframe[prop] : _computeStyle(this.element, prop); |
| } |
| }); |
| } |
| this.currentSnapshot = styles; |
| } |
| /** |
| * @param {?} phaseName |
| * @return {?} |
| */ |
| triggerCallback(phaseName) { |
| const /** @type {?} */ methods = phaseName == 'start' ? this._onStartFns : this._onDoneFns; |
| methods.forEach(fn => fn()); |
| methods.length = 0; |
| } |
| } |
| /** |
| * @param {?} element |
| * @param {?} prop |
| * @return {?} |
| */ |
| function _computeStyle(element, prop) { |
| return (/** @type {?} */ (window.getComputedStyle(element)))[prop]; |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| class WebAnimationsDriver { |
| /** |
| * @param {?} prop |
| * @return {?} |
| */ |
| validateStyleProperty(prop) { return validateStyleProperty(prop); } |
| /** |
| * @param {?} element |
| * @param {?} selector |
| * @return {?} |
| */ |
| matchesElement(element, selector) { |
| return matchesElement(element, selector); |
| } |
| /** |
| * @param {?} elm1 |
| * @param {?} elm2 |
| * @return {?} |
| */ |
| containsElement(elm1, elm2) { return containsElement(elm1, elm2); } |
| /** |
| * @param {?} element |
| * @param {?} selector |
| * @param {?} multi |
| * @return {?} |
| */ |
| query(element, selector, multi) { |
| return invokeQuery(element, selector, multi); |
| } |
| /** |
| * @param {?} element |
| * @param {?} prop |
| * @param {?=} defaultValue |
| * @return {?} |
| */ |
| computeStyle(element, prop, defaultValue) { |
| return /** @type {?} */ ((/** @type {?} */ (window.getComputedStyle(element)))[prop]); |
| } |
| /** |
| * @param {?} element |
| * @param {?} keyframes |
| * @param {?} duration |
| * @param {?} delay |
| * @param {?} easing |
| * @param {?=} previousPlayers |
| * @return {?} |
| */ |
| animate(element, keyframes, duration, delay, easing, previousPlayers = []) { |
| const /** @type {?} */ fill = delay == 0 ? 'both' : 'forwards'; |
| const /** @type {?} */ playerOptions = { duration, delay, fill }; |
| // we check for this to avoid having a null|undefined value be present |
| // for the easing (which results in an error for certain browsers #9752) |
| if (easing) { |
| playerOptions['easing'] = easing; |
| } |
| const /** @type {?} */ previousWebAnimationPlayers = /** @type {?} */ (previousPlayers.filter(player => { return player instanceof WebAnimationsPlayer; })); |
| return new WebAnimationsPlayer(element, keyframes, playerOptions, previousWebAnimationPlayers); |
| } |
| } |
| /** |
| * @return {?} |
| */ |
| function supportsWebAnimations() { |
| return typeof Element !== 'undefined' && typeof (/** @type {?} */ (Element)).prototype['animate'] === 'function'; |
| } |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| /** |
| * @license |
| * Copyright Google Inc. All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| /** |
| * @license |
| * Copyright Google Inc. All Rights Reserved. |
| * |
| * Use of this source code is governed by an MIT-style license that can be |
| * found in the LICENSE file at https://angular.io/license |
| */ |
| /** |
| * @module |
| * @description |
| * Entry point for all public APIs of this package. |
| */ |
| |
| /** |
| * @fileoverview added by tsickle |
| * @suppress {checkTypes} checked by tsc |
| */ |
| /** |
| * Generated bundle index. Do not edit. |
| */ |
| |
| export { AnimationDriver, Animation as ɵAnimation, AnimationStyleNormalizer as ɵAnimationStyleNormalizer, NoopAnimationStyleNormalizer as ɵNoopAnimationStyleNormalizer, WebAnimationsStyleNormalizer as ɵWebAnimationsStyleNormalizer, NoopAnimationDriver as ɵNoopAnimationDriver, AnimationEngine as ɵAnimationEngine, WebAnimationsDriver as ɵWebAnimationsDriver, supportsWebAnimations as ɵsupportsWebAnimations, WebAnimationsPlayer as ɵWebAnimationsPlayer }; |
| //# sourceMappingURL=browser.js.map |