UNPKG

@angular/animations

Version:

Angular - animations integration with web-animations

1 lines 355 kB
{"version":3,"file":"browser.mjs","sources":["../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/render/animation_driver.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/dsl/style_normalization/animation_style_normalizer.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/dsl/style_normalization/web_animations_style_normalizer.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/warning_helpers.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/dsl/animation_transition_expr.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/dsl/animation_ast_builder.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/dsl/animation_timeline_instruction.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/dsl/element_instruction_map.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/dsl/animation_timeline_builder.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/dsl/animation_transition_instruction.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/dsl/animation_transition_factory.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/dsl/animation_trigger.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/render/timeline_animation_engine.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/render/transition_animation_engine.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/render/animation_engine_next.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/render/special_cased_styles.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/render/web_animations/web_animations_player.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/render/web_animations/web_animations_driver.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/create_engine.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/dsl/animation.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/render/renderer.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/animations/browser/src/render/animation_renderer.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nimport {AnimationPlayer, NoopAnimationPlayer} from '../../../src/animations';\nimport {Injectable} from '@angular/core';\n\nimport {containsElement, getParentElement, invokeQuery, validateStyleProperty} from './shared';\n\n/**\n * @publicApi\n *\n * @deprecated 20.2 Use `animate.enter` or `animate.leave` instead. Intent to remove in v23\n *\n * `AnimationDriver` implentation for Noop animations\n */\n@Injectable()\nexport class NoopAnimationDriver implements AnimationDriver {\n /**\n * @returns Whether `prop` is a valid CSS property\n */\n validateStyleProperty(prop: string): boolean {\n return validateStyleProperty(prop);\n }\n\n /**\n *\n * @returns Whether elm1 contains elm2.\n */\n containsElement(elm1: any, elm2: any): boolean {\n return containsElement(elm1, elm2);\n }\n\n /**\n * @returns Rhe parent of the given element or `null` if the element is the `document`\n */\n getParentElement(element: unknown): unknown {\n return getParentElement(element);\n }\n\n /**\n * @returns The result of the query selector on the element. The array will contain up to 1 item\n * if `multi` is `false`.\n */\n query(element: any, selector: string, multi: boolean): any[] {\n return invokeQuery(element, selector, multi);\n }\n\n /**\n * @returns The `defaultValue` or empty string\n */\n computeStyle(element: any, prop: string, defaultValue?: string): string {\n return defaultValue || '';\n }\n\n /**\n * @returns An `NoopAnimationPlayer`\n */\n animate(\n element: any,\n keyframes: Array<Map<string, string | number>>,\n duration: number,\n delay: number,\n easing: string,\n previousPlayers: any[] = [],\n scrubberAccessRequested?: boolean,\n ): AnimationPlayer {\n return new NoopAnimationPlayer(duration, delay);\n }\n}\n\n/**\n * @publicApi\n *\n * @deprecated 20.2 Use `animate.enter` or `animate.leave` instead. Intent to remove in v23\n */\nexport abstract class AnimationDriver {\n /**\n * @deprecated Use the NoopAnimationDriver class.\n */\n static NOOP: AnimationDriver = /* @__PURE__ */ new NoopAnimationDriver();\n\n abstract validateStyleProperty(prop: string): boolean;\n\n abstract validateAnimatableStyleProperty?: (prop: string) => boolean;\n\n abstract containsElement(elm1: any, elm2: any): boolean;\n\n /**\n * Obtains the parent element, if any. `null` is returned if the element does not have a parent.\n */\n abstract getParentElement(element: unknown): unknown;\n\n abstract query(element: any, selector: string, multi: boolean): any[];\n\n abstract computeStyle(element: any, prop: string, defaultValue?: string): string;\n\n abstract animate(\n element: any,\n keyframes: Array<Map<string, string | number>>,\n duration: number,\n delay: number,\n easing?: string | null,\n previousPlayers?: any[],\n scrubberAccessRequested?: boolean,\n ): any;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nexport abstract class AnimationStyleNormalizer {\n abstract normalizePropertyName(propertyName: string, errors: Error[]): string;\n abstract normalizeStyleValue(\n userProvidedProperty: string,\n normalizedProperty: string,\n value: string | number,\n errors: Error[],\n ): string;\n}\n\nexport class NoopAnimationStyleNormalizer {\n normalizePropertyName(propertyName: string, errors: Error[]): string {\n return propertyName;\n }\n\n normalizeStyleValue(\n userProvidedProperty: string,\n normalizedProperty: string,\n value: string | number,\n errors: Error[],\n ): string {\n return <any>value;\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nimport {invalidCssUnitValue} from '../../error_helpers';\nimport {dashCaseToCamelCase} from '../../util';\n\nimport {AnimationStyleNormalizer} from './animation_style_normalizer';\n\nconst DIMENSIONAL_PROP_SET = new Set([\n 'width',\n 'height',\n 'minWidth',\n 'minHeight',\n 'maxWidth',\n 'maxHeight',\n 'left',\n 'top',\n 'bottom',\n 'right',\n 'fontSize',\n 'outlineWidth',\n 'outlineOffset',\n 'paddingTop',\n 'paddingLeft',\n 'paddingBottom',\n 'paddingRight',\n 'marginTop',\n 'marginLeft',\n 'marginBottom',\n 'marginRight',\n 'borderRadius',\n 'borderWidth',\n 'borderTopWidth',\n 'borderLeftWidth',\n 'borderRightWidth',\n 'borderBottomWidth',\n 'textIndent',\n 'perspective',\n]);\n\nexport class WebAnimationsStyleNormalizer extends AnimationStyleNormalizer {\n override normalizePropertyName(propertyName: string, errors: Error[]): string {\n return dashCaseToCamelCase(propertyName);\n }\n\n override normalizeStyleValue(\n userProvidedProperty: string,\n normalizedProperty: string,\n value: string | number,\n errors: Error[],\n ): string {\n let unit: string = '';\n const strVal = value.toString().trim();\n\n if (DIMENSIONAL_PROP_SET.has(normalizedProperty) && value !== 0 && value !== '0') {\n if (typeof value === 'number') {\n unit = 'px';\n } else {\n const valAndSuffixMatch = value.match(/^[+-]?[\\d\\.]+([a-z]*)$/);\n if (valAndSuffixMatch && valAndSuffixMatch[1].length == 0) {\n errors.push(invalidCssUnitValue(userProvidedProperty, value));\n }\n }\n }\n return strVal + unit;\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nfunction createListOfWarnings(warnings: string[]): string {\n const LINE_START = '\\n - ';\n return `${LINE_START}${warnings\n .filter(Boolean)\n .map((warning) => warning)\n .join(LINE_START)}`;\n}\n\nexport function warnValidation(warnings: string[]): void {\n console.warn(`animation validation warnings:${createListOfWarnings(warnings)}`);\n}\n\nexport function warnTriggerBuild(name: string, warnings: string[]): void {\n console.warn(\n `The animation trigger \"${name}\" has built with the following warnings:${createListOfWarnings(\n warnings,\n )}`,\n );\n}\n\nexport function warnRegister(warnings: string[]): void {\n console.warn(`Animation built with the following warnings:${createListOfWarnings(warnings)}`);\n}\n\nexport function triggerParsingWarnings(name: string, warnings: string[]): void {\n console.warn(\n `Animation parsing for the ${name} trigger presents the following warnings:${createListOfWarnings(\n warnings,\n )}`,\n );\n}\n\nexport function pushUnrecognizedPropertiesWarning(warnings: string[], props: string[]): void {\n if (props.length) {\n warnings.push(`The following provided properties are not recognized: ${props.join(', ')}`);\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {invalidExpression, invalidTransitionAlias} from '../error_helpers';\n\nexport const ANY_STATE = '*';\nexport declare type TransitionMatcherFn = (\n fromState: any,\n toState: any,\n element: any,\n params: {[key: string]: any},\n) => boolean;\n\nexport function parseTransitionExpr(\n transitionValue: string | TransitionMatcherFn,\n errors: Error[],\n): TransitionMatcherFn[] {\n const expressions: TransitionMatcherFn[] = [];\n if (typeof transitionValue == 'string') {\n transitionValue\n .split(/\\s*,\\s*/)\n .forEach((str) => parseInnerTransitionStr(str, expressions, errors));\n } else {\n expressions.push(<TransitionMatcherFn>transitionValue);\n }\n return expressions;\n}\n\nfunction parseInnerTransitionStr(\n eventStr: string,\n expressions: TransitionMatcherFn[],\n errors: Error[],\n) {\n if (eventStr[0] == ':') {\n const result = parseAnimationAlias(eventStr, errors);\n if (typeof result == 'function') {\n expressions.push(result);\n return;\n }\n eventStr = result;\n }\n\n const match = eventStr.match(/^(\\*|[-\\w]+)\\s*(<?[=-]>)\\s*(\\*|[-\\w]+)$/);\n if (match == null || match.length < 4) {\n errors.push(invalidExpression(eventStr));\n return expressions;\n }\n\n const fromState = match[1];\n const separator = match[2];\n const toState = match[3];\n expressions.push(makeLambdaFromStates(fromState, toState));\n\n const isFullAnyStateExpr = fromState == ANY_STATE && toState == ANY_STATE;\n if (separator[0] == '<' && !isFullAnyStateExpr) {\n expressions.push(makeLambdaFromStates(toState, fromState));\n }\n return;\n}\n\nfunction parseAnimationAlias(alias: string, errors: Error[]): string | TransitionMatcherFn {\n switch (alias) {\n case ':enter':\n return 'void => *';\n case ':leave':\n return '* => void';\n case ':increment':\n return (fromState: any, toState: any): boolean => parseFloat(toState) > parseFloat(fromState);\n case ':decrement':\n return (fromState: any, toState: any): boolean => parseFloat(toState) < parseFloat(fromState);\n default:\n errors.push(invalidTransitionAlias(alias));\n return '* => *';\n }\n}\n\n// DO NOT REFACTOR ... keep the follow set instantiations\n// with the values intact (closure compiler for some reason\n// removes follow-up lines that add the values outside of\n// the constructor...\nconst TRUE_BOOLEAN_VALUES = new Set<string>(['true', '1']);\nconst FALSE_BOOLEAN_VALUES = new Set<string>(['false', '0']);\n\nfunction makeLambdaFromStates(lhs: string, rhs: string): TransitionMatcherFn {\n const LHS_MATCH_BOOLEAN = TRUE_BOOLEAN_VALUES.has(lhs) || FALSE_BOOLEAN_VALUES.has(lhs);\n const RHS_MATCH_BOOLEAN = TRUE_BOOLEAN_VALUES.has(rhs) || FALSE_BOOLEAN_VALUES.has(rhs);\n\n return (fromState: any, toState: any): boolean => {\n let lhsMatch = lhs == ANY_STATE || lhs == fromState;\n let rhsMatch = rhs == ANY_STATE || rhs == toState;\n\n if (!lhsMatch && LHS_MATCH_BOOLEAN && typeof fromState === 'boolean') {\n lhsMatch = fromState ? TRUE_BOOLEAN_VALUES.has(lhs) : FALSE_BOOLEAN_VALUES.has(lhs);\n }\n if (!rhsMatch && RHS_MATCH_BOOLEAN && typeof toState === 'boolean') {\n rhsMatch = toState ? TRUE_BOOLEAN_VALUES.has(rhs) : FALSE_BOOLEAN_VALUES.has(rhs);\n }\n\n return lhsMatch && rhsMatch;\n };\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nimport {\n AnimateTimings,\n AnimationAnimateChildMetadata,\n AnimationAnimateMetadata,\n AnimationAnimateRefMetadata,\n AnimationGroupMetadata,\n AnimationKeyframesSequenceMetadata,\n AnimationMetadata,\n AnimationMetadataType,\n AnimationOptions,\n AnimationQueryMetadata,\n AnimationQueryOptions,\n AnimationReferenceMetadata,\n AnimationSequenceMetadata,\n AnimationStaggerMetadata,\n AnimationStateMetadata,\n AnimationStyleMetadata,\n AnimationTransitionMetadata,\n AnimationTriggerMetadata,\n AUTO_STYLE,\n style,\n ɵStyleDataMap,\n} from '../../../src/animations';\n\nimport {\n invalidDefinition,\n invalidKeyframes,\n invalidOffset,\n invalidParallelAnimation,\n invalidProperty,\n invalidStagger,\n invalidState,\n invalidStyleValue,\n invalidTrigger,\n keyframeOffsetsOutOfOrder,\n keyframesMissingOffsets,\n} from '../error_helpers';\nimport {AnimationDriver} from '../render/animation_driver';\nimport {getOrSetDefaultValue} from '../render/shared';\nimport {\n extractStyleParams,\n NG_ANIMATING_SELECTOR,\n NG_TRIGGER_SELECTOR,\n normalizeAnimationEntry,\n resolveTiming,\n SUBSTITUTION_EXPR_START,\n validateStyleParams,\n visitDslNode,\n} from '../util';\nimport {pushUnrecognizedPropertiesWarning} from '../warning_helpers';\n\nimport {\n AnimateAst,\n AnimateChildAst,\n AnimateRefAst,\n Ast,\n DynamicTimingAst,\n GroupAst,\n KeyframesAst,\n QueryAst,\n ReferenceAst,\n SequenceAst,\n StaggerAst,\n StateAst,\n StyleAst,\n TimingAst,\n TransitionAst,\n TriggerAst,\n} from './animation_ast';\nimport {AnimationDslVisitor} from './animation_dsl_visitor';\nimport {parseTransitionExpr} from './animation_transition_expr';\n\nconst SELF_TOKEN = ':self';\nconst SELF_TOKEN_REGEX = /* @__PURE__ */ new RegExp(`s*${SELF_TOKEN}s*,?`, 'g');\n\n/*\n * [Validation]\n * The visitor code below will traverse the animation AST generated by the animation verb functions\n * (the output is a tree of objects) and attempt to perform a series of validations on the data. The\n * following corner-cases will be validated:\n *\n * 1. Overlap of animations\n * Given that a CSS property cannot be animated in more than one place at the same time, it's\n * important that this behavior is detected and validated. The way in which this occurs is that\n * each time a style property is examined, a string-map containing the property will be updated with\n * the start and end times for when the property is used within an animation step.\n *\n * If there are two or more parallel animations that are currently running (these are invoked by the\n * group()) on the same element then the validator will throw an error. Since the start/end timing\n * values are collected for each property then if the current animation step is animating the same\n * property and its timing values fall anywhere into the window of time that the property is\n * currently being animated within then this is what causes an error.\n *\n * 2. Timing values\n * The validator will validate to see if a timing value of `duration delay easing` or\n * `durationNumber` is valid or not.\n *\n * (note that upon validation the code below will replace the timing data with an object containing\n * {duration,delay,easing}.\n *\n * 3. Offset Validation\n * Each of the style() calls are allowed to have an offset value when placed inside of keyframes().\n * Offsets within keyframes() are considered valid when:\n *\n * - No offsets are used at all\n * - Each style() entry contains an offset value\n * - Each offset is between 0 and 1\n * - Each offset is greater to or equal than the previous one\n *\n * Otherwise an error will be thrown.\n */\nexport function buildAnimationAst(\n driver: AnimationDriver,\n metadata: AnimationMetadata | AnimationMetadata[],\n errors: Error[],\n warnings: string[],\n): Ast<AnimationMetadataType> {\n return new AnimationAstBuilderVisitor(driver).build(metadata, errors, warnings);\n}\n\nconst ROOT_SELECTOR = '';\n\nexport class AnimationAstBuilderVisitor implements AnimationDslVisitor {\n constructor(private _driver: AnimationDriver) {}\n\n build(\n metadata: AnimationMetadata | AnimationMetadata[],\n errors: Error[],\n warnings: string[],\n ): Ast<AnimationMetadataType> {\n const context = new AnimationAstBuilderContext(errors);\n this._resetContextStyleTimingState(context);\n const ast = <Ast<AnimationMetadataType>>(\n visitDslNode(this, normalizeAnimationEntry(metadata), context)\n );\n\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (context.unsupportedCSSPropertiesFound.size) {\n pushUnrecognizedPropertiesWarning(warnings, [\n ...context.unsupportedCSSPropertiesFound.keys(),\n ]);\n }\n }\n\n return ast;\n }\n\n private _resetContextStyleTimingState(context: AnimationAstBuilderContext) {\n context.currentQuerySelector = ROOT_SELECTOR;\n context.collectedStyles = new Map<string, Map<string, StyleTimeTuple>>();\n context.collectedStyles.set(ROOT_SELECTOR, new Map());\n context.currentTime = 0;\n }\n\n visitTrigger(\n metadata: AnimationTriggerMetadata,\n context: AnimationAstBuilderContext,\n ): TriggerAst {\n let queryCount = (context.queryCount = 0);\n let depCount = (context.depCount = 0);\n const states: StateAst[] = [];\n const transitions: TransitionAst[] = [];\n if (metadata.name.charAt(0) == '@') {\n context.errors.push(invalidTrigger());\n }\n\n metadata.definitions.forEach((def) => {\n this._resetContextStyleTimingState(context);\n if (def.type == AnimationMetadataType.State) {\n const stateDef = def as AnimationStateMetadata;\n const name = stateDef.name;\n name\n .toString()\n .split(/\\s*,\\s*/)\n .forEach((n) => {\n stateDef.name = n;\n states.push(this.visitState(stateDef, context));\n });\n stateDef.name = name;\n } else if (def.type == AnimationMetadataType.Transition) {\n const transition = this.visitTransition(def as AnimationTransitionMetadata, context);\n queryCount += transition.queryCount;\n depCount += transition.depCount;\n transitions.push(transition);\n } else {\n context.errors.push(invalidDefinition());\n }\n });\n\n return {\n type: AnimationMetadataType.Trigger,\n name: metadata.name,\n states,\n transitions,\n queryCount,\n depCount,\n options: null,\n };\n }\n\n visitState(metadata: AnimationStateMetadata, context: AnimationAstBuilderContext): StateAst {\n const styleAst = this.visitStyle(metadata.styles, context);\n const astParams = (metadata.options && metadata.options.params) || null;\n if (styleAst.containsDynamicStyles) {\n const missingSubs = new Set<string>();\n const params = astParams || {};\n styleAst.styles.forEach((style) => {\n if (style instanceof Map) {\n style.forEach((value) => {\n extractStyleParams(value).forEach((sub) => {\n if (!params.hasOwnProperty(sub)) {\n missingSubs.add(sub);\n }\n });\n });\n }\n });\n if (missingSubs.size) {\n context.errors.push(invalidState(metadata.name, [...missingSubs.values()]));\n }\n }\n\n return {\n type: AnimationMetadataType.State,\n name: metadata.name,\n style: styleAst,\n options: astParams ? {params: astParams} : null,\n };\n }\n\n visitTransition(\n metadata: AnimationTransitionMetadata,\n context: AnimationAstBuilderContext,\n ): TransitionAst {\n context.queryCount = 0;\n context.depCount = 0;\n const animation = visitDslNode(this, normalizeAnimationEntry(metadata.animation), context);\n const matchers = parseTransitionExpr(metadata.expr, context.errors);\n\n return {\n type: AnimationMetadataType.Transition,\n matchers,\n animation,\n queryCount: context.queryCount,\n depCount: context.depCount,\n options: normalizeAnimationOptions(metadata.options),\n };\n }\n\n visitSequence(\n metadata: AnimationSequenceMetadata,\n context: AnimationAstBuilderContext,\n ): SequenceAst {\n return {\n type: AnimationMetadataType.Sequence,\n steps: metadata.steps.map((s) => visitDslNode(this, s, context)),\n options: normalizeAnimationOptions(metadata.options),\n };\n }\n\n visitGroup(metadata: AnimationGroupMetadata, context: AnimationAstBuilderContext): GroupAst {\n const currentTime = context.currentTime;\n let furthestTime = 0;\n const steps = metadata.steps.map((step) => {\n context.currentTime = currentTime;\n const innerAst = visitDslNode(this, step, context);\n furthestTime = Math.max(furthestTime, context.currentTime);\n return innerAst;\n });\n\n context.currentTime = furthestTime;\n return {\n type: AnimationMetadataType.Group,\n steps,\n options: normalizeAnimationOptions(metadata.options),\n };\n }\n\n visitAnimate(\n metadata: AnimationAnimateMetadata,\n context: AnimationAstBuilderContext,\n ): AnimateAst {\n const timingAst = constructTimingAst(metadata.timings, context.errors);\n context.currentAnimateTimings = timingAst;\n let styleAst: StyleAst | KeyframesAst;\n let styleMetadata: AnimationStyleMetadata | AnimationKeyframesSequenceMetadata = metadata.styles\n ? metadata.styles\n : style({});\n if (styleMetadata.type == AnimationMetadataType.Keyframes) {\n styleAst = this.visitKeyframes(styleMetadata as AnimationKeyframesSequenceMetadata, context);\n } else {\n let styleMetadata = metadata.styles as AnimationStyleMetadata;\n let isEmpty = false;\n if (!styleMetadata) {\n isEmpty = true;\n const newStyleData: {[prop: string]: string | number} = {};\n if (timingAst.easing) {\n newStyleData['easing'] = timingAst.easing;\n }\n styleMetadata = style(newStyleData);\n }\n context.currentTime += timingAst.duration + timingAst.delay;\n const _styleAst = this.visitStyle(styleMetadata, context);\n _styleAst.isEmptyStep = isEmpty;\n styleAst = _styleAst;\n }\n\n context.currentAnimateTimings = null;\n return {\n type: AnimationMetadataType.Animate,\n timings: timingAst,\n style: styleAst,\n options: null,\n };\n }\n\n visitStyle(metadata: AnimationStyleMetadata, context: AnimationAstBuilderContext): StyleAst {\n const ast = this._makeStyleAst(metadata, context);\n this._validateStyleAst(ast, context);\n return ast;\n }\n\n private _makeStyleAst(\n metadata: AnimationStyleMetadata,\n context: AnimationAstBuilderContext,\n ): StyleAst {\n const styles: Array<ɵStyleDataMap | string> = [];\n const metadataStyles = Array.isArray(metadata.styles) ? metadata.styles : [metadata.styles];\n\n for (let styleTuple of metadataStyles) {\n if (typeof styleTuple === 'string') {\n if (styleTuple === AUTO_STYLE) {\n styles.push(styleTuple);\n } else {\n context.errors.push(invalidStyleValue(styleTuple));\n }\n } else {\n styles.push(new Map(Object.entries(styleTuple)));\n }\n }\n\n let containsDynamicStyles = false;\n let collectedEasing: string | null = null;\n styles.forEach((styleData) => {\n if (styleData instanceof Map) {\n if (styleData.has('easing')) {\n collectedEasing = styleData.get('easing') as string;\n styleData.delete('easing');\n }\n if (!containsDynamicStyles) {\n for (let value of styleData.values()) {\n if (value!.toString().indexOf(SUBSTITUTION_EXPR_START) >= 0) {\n containsDynamicStyles = true;\n break;\n }\n }\n }\n }\n });\n\n return {\n type: AnimationMetadataType.Style,\n styles,\n easing: collectedEasing,\n offset: metadata.offset,\n containsDynamicStyles,\n options: null,\n };\n }\n\n private _validateStyleAst(ast: StyleAst, context: AnimationAstBuilderContext): void {\n const timings = context.currentAnimateTimings;\n let endTime = context.currentTime;\n let startTime = context.currentTime;\n if (timings && startTime > 0) {\n startTime -= timings.duration + timings.delay;\n }\n\n ast.styles.forEach((tuple) => {\n if (typeof tuple === 'string') return;\n\n tuple.forEach((value, prop) => {\n if (typeof ngDevMode === 'undefined' || ngDevMode) {\n if (!this._driver.validateStyleProperty(prop)) {\n tuple.delete(prop);\n context.unsupportedCSSPropertiesFound.add(prop);\n return;\n }\n }\n\n // This is guaranteed to have a defined Map at this querySelector location making it\n // safe to add the assertion here. It is set as a default empty map in prior methods.\n const collectedStyles = context.collectedStyles.get(context.currentQuerySelector!)!;\n const collectedEntry = collectedStyles.get(prop);\n let updateCollectedStyle = true;\n if (collectedEntry) {\n if (\n startTime != endTime &&\n startTime >= collectedEntry.startTime &&\n endTime <= collectedEntry.endTime\n ) {\n context.errors.push(\n invalidParallelAnimation(\n prop,\n collectedEntry.startTime,\n collectedEntry.endTime,\n startTime,\n endTime,\n ),\n );\n updateCollectedStyle = false;\n }\n\n // we always choose the smaller start time value since we\n // want to have a record of the entire animation window where\n // the style property is being animated in between\n startTime = collectedEntry.startTime;\n }\n\n if (updateCollectedStyle) {\n collectedStyles.set(prop, {startTime, endTime});\n }\n\n if (context.options) {\n validateStyleParams(value, context.options, context.errors);\n }\n });\n });\n }\n\n visitKeyframes(\n metadata: AnimationKeyframesSequenceMetadata,\n context: AnimationAstBuilderContext,\n ): KeyframesAst {\n const ast: KeyframesAst = {type: AnimationMetadataType.Keyframes, styles: [], options: null};\n if (!context.currentAnimateTimings) {\n context.errors.push(invalidKeyframes());\n return ast;\n }\n\n const MAX_KEYFRAME_OFFSET = 1;\n\n let totalKeyframesWithOffsets = 0;\n const offsets: number[] = [];\n let offsetsOutOfOrder = false;\n let keyframesOutOfRange = false;\n let previousOffset: number = 0;\n\n const keyframes: StyleAst[] = metadata.steps.map((styles) => {\n const style = this._makeStyleAst(styles, context);\n let offsetVal: number | null =\n style.offset != null ? style.offset : consumeOffset(style.styles);\n let offset: number = 0;\n if (offsetVal != null) {\n totalKeyframesWithOffsets++;\n offset = style.offset = offsetVal;\n }\n keyframesOutOfRange = keyframesOutOfRange || offset < 0 || offset > 1;\n offsetsOutOfOrder = offsetsOutOfOrder || offset < previousOffset;\n previousOffset = offset;\n offsets.push(offset);\n return style;\n });\n\n if (keyframesOutOfRange) {\n context.errors.push(invalidOffset());\n }\n\n if (offsetsOutOfOrder) {\n context.errors.push(keyframeOffsetsOutOfOrder());\n }\n\n const length = metadata.steps.length;\n let generatedOffset = 0;\n if (totalKeyframesWithOffsets > 0 && totalKeyframesWithOffsets < length) {\n context.errors.push(keyframesMissingOffsets());\n } else if (totalKeyframesWithOffsets == 0) {\n generatedOffset = MAX_KEYFRAME_OFFSET / (length - 1);\n }\n\n const limit = length - 1;\n const currentTime = context.currentTime;\n const currentAnimateTimings = context.currentAnimateTimings!;\n const animateDuration = currentAnimateTimings.duration;\n keyframes.forEach((kf, i) => {\n const offset = generatedOffset > 0 ? (i == limit ? 1 : generatedOffset * i) : offsets[i];\n const durationUpToThisFrame = offset * animateDuration;\n context.currentTime = currentTime + currentAnimateTimings.delay + durationUpToThisFrame;\n currentAnimateTimings.duration = durationUpToThisFrame;\n this._validateStyleAst(kf, context);\n kf.offset = offset;\n\n ast.styles.push(kf);\n });\n\n return ast;\n }\n\n visitReference(\n metadata: AnimationReferenceMetadata,\n context: AnimationAstBuilderContext,\n ): ReferenceAst {\n return {\n type: AnimationMetadataType.Reference,\n animation: visitDslNode(this, normalizeAnimationEntry(metadata.animation), context),\n options: normalizeAnimationOptions(metadata.options),\n };\n }\n\n visitAnimateChild(\n metadata: AnimationAnimateChildMetadata,\n context: AnimationAstBuilderContext,\n ): AnimateChildAst {\n context.depCount++;\n return {\n type: AnimationMetadataType.AnimateChild,\n options: normalizeAnimationOptions(metadata.options),\n };\n }\n\n visitAnimateRef(\n metadata: AnimationAnimateRefMetadata,\n context: AnimationAstBuilderContext,\n ): AnimateRefAst {\n return {\n type: AnimationMetadataType.AnimateRef,\n animation: this.visitReference(metadata.animation, context),\n options: normalizeAnimationOptions(metadata.options),\n };\n }\n\n visitQuery(metadata: AnimationQueryMetadata, context: AnimationAstBuilderContext): QueryAst {\n const parentSelector = context.currentQuerySelector!;\n const options = (metadata.options || {}) as AnimationQueryOptions;\n\n context.queryCount++;\n context.currentQuery = metadata;\n const [selector, includeSelf] = normalizeSelector(metadata.selector);\n context.currentQuerySelector = parentSelector.length\n ? parentSelector + ' ' + selector\n : selector;\n getOrSetDefaultValue(context.collectedStyles, context.currentQuerySelector, new Map());\n\n const animation = visitDslNode(this, normalizeAnimationEntry(metadata.animation), context);\n context.currentQuery = null;\n context.currentQuerySelector = parentSelector;\n\n return {\n type: AnimationMetadataType.Query,\n selector,\n limit: options.limit || 0,\n optional: !!options.optional,\n includeSelf,\n animation,\n originalSelector: metadata.selector,\n options: normalizeAnimationOptions(metadata.options),\n };\n }\n\n visitStagger(\n metadata: AnimationStaggerMetadata,\n context: AnimationAstBuilderContext,\n ): StaggerAst {\n if (!context.currentQuery) {\n context.errors.push(invalidStagger());\n }\n const timings =\n metadata.timings === 'full'\n ? {duration: 0, delay: 0, easing: 'full'}\n : resolveTiming(metadata.timings, context.errors, true);\n\n return {\n type: AnimationMetadataType.Stagger,\n animation: visitDslNode(this, normalizeAnimationEntry(metadata.animation), context),\n timings,\n options: null,\n };\n }\n}\n\nfunction normalizeSelector(selector: string): [string, boolean] {\n const hasAmpersand = selector.split(/\\s*,\\s*/).find((token) => token == SELF_TOKEN)\n ? true\n : false;\n if (hasAmpersand) {\n selector = selector.replace(SELF_TOKEN_REGEX, '');\n }\n\n // Note: the :enter and :leave aren't normalized here since those\n // selectors are filled in at runtime during timeline building\n selector = selector\n .replace(/@\\*/g, NG_TRIGGER_SELECTOR)\n .replace(/@\\w+/g, (match) => NG_TRIGGER_SELECTOR + '-' + match.slice(1))\n .replace(/:animating/g, NG_ANIMATING_SELECTOR);\n\n return [selector, hasAmpersand];\n}\n\nfunction normalizeParams(obj: {[key: string]: any} | any): {[key: string]: any} | null {\n return obj ? {...obj} : null;\n}\n\nexport type StyleTimeTuple = {\n startTime: number;\n endTime: number;\n};\n\nexport class AnimationAstBuilderContext {\n public queryCount: number = 0;\n public depCount: number = 0;\n public currentTransition: AnimationTransitionMetadata | null = null;\n public currentQuery: AnimationQueryMetadata | null = null;\n public currentQuerySelector: string | null = null;\n public currentAnimateTimings: TimingAst | null = null;\n public currentTime: number = 0;\n public collectedStyles = new Map<string, Map<string, StyleTimeTuple>>();\n public options: AnimationOptions | null = null;\n public unsupportedCSSPropertiesFound: Set<string> = new Set<string>();\n constructor(public errors: Error[]) {}\n}\n\ntype OffsetStyles = string | ɵStyleDataMap;\n\nfunction consumeOffset(styles: OffsetStyles | Array<OffsetStyles>): number | null {\n if (typeof styles == 'string') return null;\n\n let offset: number | null = null;\n\n if (Array.isArray(styles)) {\n styles.forEach((styleTuple) => {\n if (styleTuple instanceof Map && styleTuple.has('offset')) {\n const obj = styleTuple as ɵStyleDataMap;\n offset = parseFloat(obj.get('offset') as string);\n obj.delete('offset');\n }\n });\n } else if (styles instanceof Map && styles.has('offset')) {\n const obj = styles;\n offset = parseFloat(obj.get('offset') as string);\n obj.delete('offset');\n }\n return offset;\n}\n\nfunction constructTimingAst(value: string | number | AnimateTimings, errors: Error[]) {\n if (value.hasOwnProperty('duration')) {\n return value as AnimateTimings;\n }\n\n if (typeof value == 'number') {\n const duration = resolveTiming(value, errors).duration;\n return makeTimingAst(duration, 0, '');\n }\n\n const strValue = value as string;\n const isDynamic = strValue.split(/\\s+/).some((v) => v.charAt(0) == '{' && v.charAt(1) == '{');\n if (isDynamic) {\n const ast = makeTimingAst(0, 0, '') as any;\n ast.dynamic = true;\n ast.strValue = strValue;\n return ast as DynamicTimingAst;\n }\n\n const timings = resolveTiming(strValue, errors);\n return makeTimingAst(timings.duration, timings.delay, timings.easing);\n}\n\nfunction normalizeAnimationOptions(options: AnimationOptions | null): AnimationOptions {\n if (options) {\n options = {...options};\n if (options['params']) {\n options['params'] = normalizeParams(options['params'])!;\n }\n } else {\n options = {};\n }\n return options;\n}\n\nfunction makeTimingAst(duration: number, delay: number, easing: string | null): TimingAst {\n return {duration, delay, easing};\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nimport {ɵStyleDataMap} from '../../../src/animations';\n\nimport {\n AnimationEngineInstruction,\n AnimationTransitionInstructionType,\n} from '../render/animation_engine_instruction';\n\nexport interface AnimationTimelineInstruction extends AnimationEngineInstruction {\n element: any;\n keyframes: Array<ɵStyleDataMap>;\n preStyleProps: string[];\n postStyleProps: string[];\n duration: number;\n delay: number;\n totalTime: number;\n easing: string | null;\n stretchStartingKeyframe?: boolean;\n subTimeline: boolean;\n}\n\nexport function createTimelineInstruction(\n element: any,\n keyframes: Array<ɵStyleDataMap>,\n preStyleProps: string[],\n postStyleProps: string[],\n duration: number,\n delay: number,\n easing: string | null = null,\n subTimeline: boolean = false,\n): AnimationTimelineInstruction {\n return {\n type: AnimationTransitionInstructionType.TimelineAnimation,\n element,\n keyframes,\n preStyleProps,\n postStyleProps,\n duration,\n delay,\n totalTime: duration + delay,\n easing,\n subTimeline,\n };\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nimport {AnimationTimelineInstruction} from './animation_timeline_instruction';\n\nexport class ElementInstructionMap {\n private _map = new Map<any, AnimationTimelineInstruction[]>();\n\n get(element: any): AnimationTimelineInstruction[] {\n return this._map.get(element) || [];\n }\n\n append(element: any, instructions: AnimationTimelineInstruction[]) {\n let existingInstructions = this._map.get(element);\n if (!existingInstructions) {\n this._map.set(element, (existingInstructions = []));\n }\n existingInstructions.push(...instructions);\n }\n\n has(element: any): boolean {\n return this._map.has(element);\n }\n\n clear() {\n this._map.clear();\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\nimport {\n AnimateChildOptions,\n AnimateTimings,\n AnimationMetadataType,\n AnimationOptions,\n AnimationQueryOptions,\n AUTO_STYLE,\n ɵPRE_STYLE as PRE_STYLE,\n ɵStyleDataMap,\n} from '../../../src/animations';\n\nimport {invalidQuery} from '../error_helpers';\nimport {AnimationDriver} from '../render/animation_driver';\nimport {interpolateParams, resolveTiming, resolveTimingValue, visitDslNode} from '../util';\n\nimport {\n AnimateAst,\n AnimateChildAst,\n AnimateRefAst,\n Ast,\n AstVisitor,\n DynamicTimingAst,\n GroupAst,\n KeyframesAst,\n QueryAst,\n ReferenceAst,\n SequenceAst,\n StaggerAst,\n StateAst,\n StyleAst,\n TimingAst,\n TransitionAst,\n TriggerAst,\n} from './animation_ast';\nimport {\n AnimationTimelineInstruction,\n createTimelineInstruction,\n} from './animation_timeline_instruction';\nimport {ElementInstructionMap} from './element_instruction_map';\n\nconst ONE_FRAME_IN_MILLISECONDS = 1;\nconst ENTER_TOKEN = ':enter';\nconst ENTER_TOKEN_REGEX = /* @__PURE__ */ new RegExp(ENTER_TOKEN, 'g');\nconst LEAVE_TOKEN = ':leave';\nconst LEAVE_TOKEN_REGEX = /* @__PURE__ */ new RegExp(LEAVE_TOKEN, 'g');\n\n/*\n * The code within this file aims to generate web-animations-compatible keyframes from Angular's\n * animation DSL code.\n *\n * The code below will be converted from:\n *\n * ```ts\n * sequence([\n * style({ opacity: 0 }),\n * animate(1000, style({ opacity: 0 }))\n * ])\n * ```\n *\n * To:\n * ```ts\n * keyframes = [{ opacity: 0, offset: 0 }, { opacity: 1, offset: 1 }]\n * duration = 1000\n * delay = 0\n * easing = ''\n * ```\n *\n * For this operation to cover the combination of animation verbs (style, animate, group, etc...) a\n * combination of AST traversal and merge-sort-like algorithms are used.\n *\n * [AST Traversal]\n * Each of the animation verbs, when executed, will return an string-map object representing what\n * type of action it is (style, animate, group, etc...) and the data associated with it. This means\n * that when functional composition mix of these functions is evaluated (like in the example above)\n * then it will end up producing a tree of objects representing the animation itself.\n *\n * When this animation object tree is processed by the visitor code below it will visit each of the\n * verb statements within the visitor. And during each visit it will build the context of the\n * animation keyframes by interacting with the `TimelineBuilder`.\n *\n * [TimelineBuilder]\n * This class is responsible for tracking the styles and building a series of keyframe objects for a\n * timeline between a start and end time. The builder starts off with an initial timeline and each\n * time the AST comes across a `group()`, `keyframes()` or a combination of the two within a\n * `sequence()` then it will generate a sub timeline for each step as well as a new one after\n * they are complete.\n *\n * As the AST is traversed, the timing state on each of the timelines will be incremented. If a sub\n * timeline was created (based on one of the cases above) then the parent timeline will attempt to\n * merge the styles used within the sub timelines into itself (only with group() this will happen).\n * This happens with a merge operation (much like how the merge works in mergeSort) and it will only\n * copy the most recently used styles from the sub timelines into the parent timeline. This ensures\n * that if the styles are used later on in another phase of the animation then they will be the most\n * up-to-date values.\n *\n * [How Missing Styles Are Updated]\n * Each timeline has a `backFill` property which is responsible for filling in new styles into\n * already processed keyframes if a new style shows up later within the animation sequence.\n *\n * ```ts\n * sequence([\n * style({ width: 0 }),\n * animate(1000, style({ width: 100 })),\n * animate(1000, style({ width: 200 })),\n * animate(1000, style({ width: 300 }))\n * animate(1000, style({ width: 400, height: 400 })) // notice how `height` doesn't exist anywhere\n * else\n * ])\n * ```\n *\n * What is happening here is that the `height` value is added later in the sequence, but is missing\n * from all previous animation steps. Therefore when a keyframe is created it would also be missing\n * from all previous keyframes up until where it is first used. For the timeline keyframe generation\n * to properly fill in the style it will place the previous value (the value from the parent\n * timeline) or a default value of `*` into the backFill map.\n *\n * When a sub-timeline is created it will have its own backFill property. This is done so that\n * styles present within the sub-timeline do not accidentally seep into the previous/future timeline\n * keyframes\n *\n * [Validation]\n * The code in this file is not responsible for validation. That functionality happens with within\n * the `AnimationValidatorVisitor` code.\n */\nexport function buildAnimationTimelines(\n driver: AnimationDriver,\n rootElement: any,\n ast: Ast<AnimationMetadataType>,\n enterClassName: string,\n leaveClassName: string,\n startingStyles: ɵStyleDataMap = new Map(),\n finalStyles: ɵStyleDataMap = new Map(),\n options: AnimationOptions,\n subInstructions?: ElementInstructionMap,\n errors: Error[] = [],\n): AnimationTimelineInstruction[] {\n return new AnimationTimelineBuilderVisitor().buildKeyframes(\n driver,\n rootElement,\n ast,\n enterClassName,\n leaveClassName,\n startingStyles,\n finalStyles,\n options,\n subInstructions,\n errors,\n );\n}\n\nexport class AnimationTimelineBuilderVisitor implements AstVisitor {\n buildKeyframes(\n driver: AnimationDriver,\n rootElement: any,\n ast: Ast<AnimationMetadataType>,\n enterClassName: string,\n leaveClassName: string,\n startingStyles: ɵStyleDataMap,\n finalStyles: ɵStyleDataMap,\n options: AnimationOptions,\n subInstructions?: ElementInstructionMap,\n errors: Error[] = [],\n ): AnimationTimelineInstruction[] {\n subInstructions = subInstructions || new ElementInstructionMap();\n const context = new AnimationTimelineContext(\n driver,\n rootElement,\n subInstructions,\n enterClassName,\n leaveClassName,\n errors,\n [],\n );\n context.options = options;\n const delay = options.delay ? resolveTimingValue(options.delay) : 0;\n context.currentTimeline.delayNextStep(delay);\n context.currentTimeline.setStyles([startingStyles], null, context.errors, options);\n\n visitDslNode(this, ast, context);\n\n // this checks to see if an actual animation happened\n const timelines = context.timelines.filter((timeline) => timeline.containsAnimation());\n\n // note: we just want to apply the final styles for the rootElement, so we do not\n // just apply the styles to the last timeline but the last timeline which\n // element is the root one (basically `*`-styles are replaced with the actual\n // state style values only for the root element)\n if (timelines.length && finalStyles.size) {\n let lastRootTimeline: TimelineBuilder | undefined;\n for (let i = timelines.length - 1; i >= 0; i--) {\n const timeline = timelines[i];\n if (timeline.element === rootElement) {\n lastRootTimeline = timeline;\n break;\n }\n }\n if (lastRootTimeline && !lastRootTimeline.allowOnlyTimelineStyles()) {\n lastRootTimeline.setStyles([finalStyles], null, context.errors, options);\n }\n }\n return timelines.length\n ? timelines.map((timeline) => timeline.buildKeyframes())\n : [createTimelineInstruction(rootElement, [], [], [], 0, delay, '', false)];\n }\n\n visitTrigger(ast: TriggerAst, context: AnimationTimelineContext): any {\n // these values are not visited in this AST\n }\n\n visitState(ast: StateAst, context: AnimationTimelineContext): any {\n // these values are not visited in this AST\n }\n\n visitTransition(ast: TransitionAst, context: AnimationTimelineContext): any {\n // these values are not visited in this AST\n }\n\n visitAnimateChild(ast: AnimateChildAst, context: AnimationTimelineContext): any {\n const elementInstructions = context.subInstructions.get(context.element);\n if (elementInstructions) {\n const innerContext = context.createSubContext(ast.options);\n const startTime = context.currentTimeline.currentTime;\n const endTime = this._visitSubInstructions(\n elementInstructions,\n innerContext,\n innerContext.options as AnimateChildOptions,\n );\n if (startTime != endTime) {\n // we do this on the upper context because we created a sub context for\n // the sub child animations\n context.transformIntoNewTimeline(endTime);\n }\n }\n context.previousNode = ast;\n }\n\n visitAnimateRef(ast: AnimateRefAst, context: AnimationTimelineContext): any {\n const innerContext = context.createSubContext(ast.options);\n innerContext.transformIntoNewTimeline();\n this._applyAnimationRefDelays([ast.options, ast.animation.options], context, innerContext);\n this.visitReference(ast.animation, innerContext);\n context.transformIntoNewTimeline(innerContext.currentTimeline.currentTime);\n context.previousNode = ast;\n }\n\n private _applyAnimationRefDelays(\n animationsRefsOptions: (AnimationOptions | null)[],\n context: AnimationTimelineContext,\n innerContext: AnimationTimelineContext,\n ) {\n for (const animationRefOptions of animationsRefsOptions) {\n const animationDelay = animationRefOptions?.delay;\n if (animationDelay) {\n const animationDelayValue =\n typeof animationDelay === 'number'\n ? animationDelay\n : resolveTimingValue(\n interpolateParams(\n animationDelay,\n animationRefOptions?.params ?? {},\n context.errors,\n ),\n );\n innerContext.delayNextStep(animationDelayValue);\n }\n }\n }\n\n private _visitSubInstructions(\n instructions: AnimationTimelineInstruction[],\n context: AnimationTimelineContext,\n options: AnimateChildOptions,\n ): number {\n const startTime = context.currentTimeline.currentTime;\n let furthestTime = startTime;\n\n // this is a special-case for when a user wants to skip a sub\n // animation from being fired entirely.\n const duration = options.duration != null ? resolveTimingValue(options.duration) : null;\n const delay = options.delay != null ? resolveTimingValue(options.delay) : null;\n if (duration !== 0) {\n instructions.forEach((instruction) => {\n const instructionTimings = context.appendInstructionToTimeline(\n instruction,\n duration,\n delay,\n );\n furthestTime = Math.max(\n furthestTime,\n instructionTimings.duration + instructionTimings.delay,\n );\n });\n }\n\n return furthestTime;\n }\n\n visitReference(ast: ReferenceAst, context: AnimationTimelineContext) {\n co