@angular/animations
Version:
Angular - animations integration with web-animations
1,366 lines (1,354 loc) • 125 kB
JavaScript
/**
* @license Angular v21.0.6
* (c) 2010-2025 Google LLC. https://angular.dev/
* License: MIT
*/
import * as i0 from '@angular/core';
import { Injectable } from '@angular/core';
import { validateStyleProperty, containsElement, getParentElement, invokeQuery, dashCaseToCamelCase, invalidCssUnitValue, invalidExpression, invalidTransitionAlias, visitDslNode, invalidTrigger, invalidDefinition, extractStyleParams, invalidState, invalidStyleValue, SUBSTITUTION_EXPR_START, invalidParallelAnimation, validateStyleParams, invalidKeyframes, invalidOffset, keyframeOffsetsOutOfOrder, keyframesMissingOffsets, getOrSetDefaultValue, invalidStagger, resolveTiming, NG_TRIGGER_SELECTOR, NG_ANIMATING_SELECTOR, normalizeAnimationEntry, resolveTimingValue, interpolateParams, invalidQuery, registerFailed, normalizeKeyframes, LEAVE_CLASSNAME, ENTER_CLASSNAME, missingOrDestroyedAnimation, createAnimationFailed, optimizeGroupPlayer, missingPlayer, listenOnPlayer, makeAnimationEvent, triggerTransitionsFailed, eraseStyles, setStyles, transitionFailed, missingTrigger, missingEvent, unsupportedTriggerEvent, NG_TRIGGER_CLASSNAME, unregisteredTrigger, NG_ANIMATING_CLASSNAME, triggerBuildFailed, parseTimelineCommand, computeStyle, camelCaseToDashCase, validateWebAnimatableStyleProperty, allowPreviousPlayerStylesMerge, normalizeKeyframes$1, balancePreviousStylesIntoKeyframes, validationFailed, normalizeStyles, buildingFailed } from './_util-chunk.mjs';
import { NoopAnimationPlayer, AnimationMetadataType, style, AUTO_STYLE, ɵPRE_STYLE as _PRE_STYLE, AnimationGroupPlayer } from './_private_export-chunk.mjs';
class NoopAnimationDriver {
validateStyleProperty(prop) {
return validateStyleProperty(prop);
}
containsElement(elm1, elm2) {
return containsElement(elm1, elm2);
}
getParentElement(element) {
return getParentElement(element);
}
query(element, selector, multi) {
return invokeQuery(element, selector, multi);
}
computeStyle(element, prop, defaultValue) {
return defaultValue || '';
}
animate(element, keyframes, duration, delay, easing, previousPlayers = [], scrubberAccessRequested) {
return new NoopAnimationPlayer(duration, delay);
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: NoopAnimationDriver,
deps: [],
target: i0.ɵɵFactoryTarget.Injectable
});
static ɵprov = i0.ɵɵngDeclareInjectable({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: NoopAnimationDriver
});
}
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "21.0.6",
ngImport: i0,
type: NoopAnimationDriver,
decorators: [{
type: Injectable
}]
});
class AnimationDriver {
static NOOP = new NoopAnimationDriver();
}
class AnimationStyleNormalizer {}
class NoopAnimationStyleNormalizer {
normalizePropertyName(propertyName, errors) {
return propertyName;
}
normalizeStyleValue(userProvidedProperty, normalizedProperty, value, errors) {
return value;
}
}
const DIMENSIONAL_PROP_SET = new Set(['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']);
class WebAnimationsStyleNormalizer extends AnimationStyleNormalizer {
normalizePropertyName(propertyName, errors) {
return dashCaseToCamelCase(propertyName);
}
normalizeStyleValue(userProvidedProperty, normalizedProperty, value, errors) {
let unit = '';
const strVal = value.toString().trim();
if (DIMENSIONAL_PROP_SET.has(normalizedProperty) && value !== 0 && value !== '0') {
if (typeof value === 'number') {
unit = 'px';
} else {
const valAndSuffixMatch = value.match(/^[+-]?[\d\.]+([a-z]*)$/);
if (valAndSuffixMatch && valAndSuffixMatch[1].length == 0) {
errors.push(invalidCssUnitValue(userProvidedProperty, value));
}
}
}
return strVal + unit;
}
}
function createListOfWarnings(warnings) {
const LINE_START = '\n - ';
return `${LINE_START}${warnings.filter(Boolean).map(warning => warning).join(LINE_START)}`;
}
function warnValidation(warnings) {
console.warn(`animation validation warnings:${createListOfWarnings(warnings)}`);
}
function warnTriggerBuild(name, warnings) {
console.warn(`The animation trigger "${name}" has built with the following warnings:${createListOfWarnings(warnings)}`);
}
function warnRegister(warnings) {
console.warn(`Animation built with the following warnings:${createListOfWarnings(warnings)}`);
}
function pushUnrecognizedPropertiesWarning(warnings, props) {
if (props.length) {
warnings.push(`The following provided properties are not recognized: ${props.join(', ')}`);
}
}
const ANY_STATE = '*';
function parseTransitionExpr(transitionValue, errors) {
const expressions = [];
if (typeof transitionValue == 'string') {
transitionValue.split(/\s*,\s*/).forEach(str => parseInnerTransitionStr(str, expressions, errors));
} else {
expressions.push(transitionValue);
}
return expressions;
}
function parseInnerTransitionStr(eventStr, expressions, errors) {
if (eventStr[0] == ':') {
const result = parseAnimationAlias(eventStr, errors);
if (typeof result == 'function') {
expressions.push(result);
return;
}
eventStr = result;
}
const match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/);
if (match == null || match.length < 4) {
errors.push(invalidExpression(eventStr));
return expressions;
}
const fromState = match[1];
const separator = match[2];
const toState = match[3];
expressions.push(makeLambdaFromStates(fromState, toState));
const isFullAnyStateExpr = fromState == ANY_STATE && toState == ANY_STATE;
if (separator[0] == '<' && !isFullAnyStateExpr) {
expressions.push(makeLambdaFromStates(toState, fromState));
}
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(invalidTransitionAlias(alias));
return '* => *';
}
}
const TRUE_BOOLEAN_VALUES = new Set(['true', '1']);
const FALSE_BOOLEAN_VALUES = new Set(['false', '0']);
function makeLambdaFromStates(lhs, rhs) {
const LHS_MATCH_BOOLEAN = TRUE_BOOLEAN_VALUES.has(lhs) || FALSE_BOOLEAN_VALUES.has(lhs);
const RHS_MATCH_BOOLEAN = TRUE_BOOLEAN_VALUES.has(rhs) || FALSE_BOOLEAN_VALUES.has(rhs);
return (fromState, toState) => {
let lhsMatch = lhs == ANY_STATE || lhs == fromState;
let 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;
};
}
const SELF_TOKEN = ':self';
const SELF_TOKEN_REGEX = /* @__PURE__ */new RegExp(`s*${SELF_TOKEN}s*,?`, 'g');
function buildAnimationAst(driver, metadata, errors, warnings) {
return new AnimationAstBuilderVisitor(driver).build(metadata, errors, warnings);
}
const ROOT_SELECTOR = '';
class AnimationAstBuilderVisitor {
_driver;
constructor(_driver) {
this._driver = _driver;
}
build(metadata, errors, warnings) {
const context = new AnimationAstBuilderContext(errors);
this._resetContextStyleTimingState(context);
const ast = visitDslNode(this, normalizeAnimationEntry(metadata), context);
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (context.unsupportedCSSPropertiesFound.size) {
pushUnrecognizedPropertiesWarning(warnings, [...context.unsupportedCSSPropertiesFound.keys()]);
}
}
return ast;
}
_resetContextStyleTimingState(context) {
context.currentQuerySelector = ROOT_SELECTOR;
context.collectedStyles = new Map();
context.collectedStyles.set(ROOT_SELECTOR, new Map());
context.currentTime = 0;
}
visitTrigger(metadata, context) {
let queryCount = context.queryCount = 0;
let depCount = context.depCount = 0;
const states = [];
const transitions = [];
if (metadata.name.charAt(0) == '@') {
context.errors.push(invalidTrigger());
}
metadata.definitions.forEach(def => {
this._resetContextStyleTimingState(context);
if (def.type == AnimationMetadataType.State) {
const stateDef = def;
const name = stateDef.name;
name.toString().split(/\s*,\s*/).forEach(n => {
stateDef.name = n;
states.push(this.visitState(stateDef, context));
});
stateDef.name = name;
} else if (def.type == AnimationMetadataType.Transition) {
const transition = this.visitTransition(def, context);
queryCount += transition.queryCount;
depCount += transition.depCount;
transitions.push(transition);
} else {
context.errors.push(invalidDefinition());
}
});
return {
type: AnimationMetadataType.Trigger,
name: metadata.name,
states,
transitions,
queryCount,
depCount,
options: null
};
}
visitState(metadata, context) {
const styleAst = this.visitStyle(metadata.styles, context);
const astParams = metadata.options && metadata.options.params || null;
if (styleAst.containsDynamicStyles) {
const missingSubs = new Set();
const params = astParams || {};
styleAst.styles.forEach(style => {
if (style instanceof Map) {
style.forEach(value => {
extractStyleParams(value).forEach(sub => {
if (!params.hasOwnProperty(sub)) {
missingSubs.add(sub);
}
});
});
}
});
if (missingSubs.size) {
context.errors.push(invalidState(metadata.name, [...missingSubs.values()]));
}
}
return {
type: AnimationMetadataType.State,
name: metadata.name,
style: styleAst,
options: astParams ? {
params: astParams
} : null
};
}
visitTransition(metadata, context) {
context.queryCount = 0;
context.depCount = 0;
const animation = visitDslNode(this, normalizeAnimationEntry(metadata.animation), context);
const matchers = parseTransitionExpr(metadata.expr, context.errors);
return {
type: AnimationMetadataType.Transition,
matchers,
animation,
queryCount: context.queryCount,
depCount: context.depCount,
options: normalizeAnimationOptions(metadata.options)
};
}
visitSequence(metadata, context) {
return {
type: AnimationMetadataType.Sequence,
steps: metadata.steps.map(s => visitDslNode(this, s, context)),
options: normalizeAnimationOptions(metadata.options)
};
}
visitGroup(metadata, context) {
const currentTime = context.currentTime;
let furthestTime = 0;
const steps = metadata.steps.map(step => {
context.currentTime = currentTime;
const innerAst = visitDslNode(this, step, context);
furthestTime = Math.max(furthestTime, context.currentTime);
return innerAst;
});
context.currentTime = furthestTime;
return {
type: AnimationMetadataType.Group,
steps,
options: normalizeAnimationOptions(metadata.options)
};
}
visitAnimate(metadata, context) {
const timingAst = constructTimingAst(metadata.timings, context.errors);
context.currentAnimateTimings = timingAst;
let styleAst;
let styleMetadata = metadata.styles ? metadata.styles : style({});
if (styleMetadata.type == AnimationMetadataType.Keyframes) {
styleAst = this.visitKeyframes(styleMetadata, context);
} else {
let styleMetadata = metadata.styles;
let isEmpty = false;
if (!styleMetadata) {
isEmpty = true;
const newStyleData = {};
if (timingAst.easing) {
newStyleData['easing'] = timingAst.easing;
}
styleMetadata = style(newStyleData);
}
context.currentTime += timingAst.duration + timingAst.delay;
const _styleAst = this.visitStyle(styleMetadata, context);
_styleAst.isEmptyStep = isEmpty;
styleAst = _styleAst;
}
context.currentAnimateTimings = null;
return {
type: AnimationMetadataType.Animate,
timings: timingAst,
style: styleAst,
options: null
};
}
visitStyle(metadata, context) {
const ast = this._makeStyleAst(metadata, context);
this._validateStyleAst(ast, context);
return ast;
}
_makeStyleAst(metadata, context) {
const styles = [];
const metadataStyles = Array.isArray(metadata.styles) ? metadata.styles : [metadata.styles];
for (let styleTuple of metadataStyles) {
if (typeof styleTuple === 'string') {
if (styleTuple === AUTO_STYLE) {
styles.push(styleTuple);
} else {
context.errors.push(invalidStyleValue(styleTuple));
}
} else {
styles.push(new Map(Object.entries(styleTuple)));
}
}
let containsDynamicStyles = false;
let collectedEasing = null;
styles.forEach(styleData => {
if (styleData instanceof Map) {
if (styleData.has('easing')) {
collectedEasing = styleData.get('easing');
styleData.delete('easing');
}
if (!containsDynamicStyles) {
for (let value of styleData.values()) {
if (value.toString().indexOf(SUBSTITUTION_EXPR_START) >= 0) {
containsDynamicStyles = true;
break;
}
}
}
}
});
return {
type: AnimationMetadataType.Style,
styles,
easing: collectedEasing,
offset: metadata.offset,
containsDynamicStyles,
options: null
};
}
_validateStyleAst(ast, context) {
const timings = context.currentAnimateTimings;
let endTime = context.currentTime;
let startTime = context.currentTime;
if (timings && startTime > 0) {
startTime -= timings.duration + timings.delay;
}
ast.styles.forEach(tuple => {
if (typeof tuple === 'string') return;
tuple.forEach((value, prop) => {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (!this._driver.validateStyleProperty(prop)) {
tuple.delete(prop);
context.unsupportedCSSPropertiesFound.add(prop);
return;
}
}
const collectedStyles = context.collectedStyles.get(context.currentQuerySelector);
const collectedEntry = collectedStyles.get(prop);
let updateCollectedStyle = true;
if (collectedEntry) {
if (startTime != endTime && startTime >= collectedEntry.startTime && endTime <= collectedEntry.endTime) {
context.errors.push(invalidParallelAnimation(prop, collectedEntry.startTime, collectedEntry.endTime, startTime, endTime));
updateCollectedStyle = false;
}
startTime = collectedEntry.startTime;
}
if (updateCollectedStyle) {
collectedStyles.set(prop, {
startTime,
endTime
});
}
if (context.options) {
validateStyleParams(value, context.options, context.errors);
}
});
});
}
visitKeyframes(metadata, context) {
const ast = {
type: AnimationMetadataType.Keyframes,
styles: [],
options: null
};
if (!context.currentAnimateTimings) {
context.errors.push(invalidKeyframes());
return ast;
}
const MAX_KEYFRAME_OFFSET = 1;
let totalKeyframesWithOffsets = 0;
const offsets = [];
let offsetsOutOfOrder = false;
let keyframesOutOfRange = false;
let previousOffset = 0;
const keyframes = metadata.steps.map(styles => {
const style = this._makeStyleAst(styles, context);
let offsetVal = style.offset != null ? style.offset : consumeOffset(style.styles);
let offset = 0;
if (offsetVal != null) {
totalKeyframesWithOffsets++;
offset = style.offset = offsetVal;
}
keyframesOutOfRange = keyframesOutOfRange || offset < 0 || offset > 1;
offsetsOutOfOrder = offsetsOutOfOrder || offset < previousOffset;
previousOffset = offset;
offsets.push(offset);
return style;
});
if (keyframesOutOfRange) {
context.errors.push(invalidOffset());
}
if (offsetsOutOfOrder) {
context.errors.push(keyframeOffsetsOutOfOrder());
}
const length = metadata.steps.length;
let generatedOffset = 0;
if (totalKeyframesWithOffsets > 0 && totalKeyframesWithOffsets < length) {
context.errors.push(keyframesMissingOffsets());
} else if (totalKeyframesWithOffsets == 0) {
generatedOffset = MAX_KEYFRAME_OFFSET / (length - 1);
}
const limit = length - 1;
const currentTime = context.currentTime;
const currentAnimateTimings = context.currentAnimateTimings;
const animateDuration = currentAnimateTimings.duration;
keyframes.forEach((kf, i) => {
const offset = generatedOffset > 0 ? i == limit ? 1 : generatedOffset * i : offsets[i];
const 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;
}
visitReference(metadata, context) {
return {
type: AnimationMetadataType.Reference,
animation: visitDslNode(this, normalizeAnimationEntry(metadata.animation), context),
options: normalizeAnimationOptions(metadata.options)
};
}
visitAnimateChild(metadata, context) {
context.depCount++;
return {
type: AnimationMetadataType.AnimateChild,
options: normalizeAnimationOptions(metadata.options)
};
}
visitAnimateRef(metadata, context) {
return {
type: AnimationMetadataType.AnimateRef,
animation: this.visitReference(metadata.animation, context),
options: normalizeAnimationOptions(metadata.options)
};
}
visitQuery(metadata, context) {
const parentSelector = context.currentQuerySelector;
const options = metadata.options || {};
context.queryCount++;
context.currentQuery = metadata;
const [selector, includeSelf] = normalizeSelector(metadata.selector);
context.currentQuerySelector = parentSelector.length ? parentSelector + ' ' + selector : selector;
getOrSetDefaultValue(context.collectedStyles, context.currentQuerySelector, new Map());
const animation = visitDslNode(this, normalizeAnimationEntry(metadata.animation), context);
context.currentQuery = null;
context.currentQuerySelector = parentSelector;
return {
type: AnimationMetadataType.Query,
selector,
limit: options.limit || 0,
optional: !!options.optional,
includeSelf,
animation,
originalSelector: metadata.selector,
options: normalizeAnimationOptions(metadata.options)
};
}
visitStagger(metadata, context) {
if (!context.currentQuery) {
context.errors.push(invalidStagger());
}
const timings = metadata.timings === 'full' ? {
duration: 0,
delay: 0,
easing: 'full'
} : resolveTiming(metadata.timings, context.errors, true);
return {
type: AnimationMetadataType.Stagger,
animation: visitDslNode(this, normalizeAnimationEntry(metadata.animation), context),
timings,
options: null
};
}
}
function normalizeSelector(selector) {
const hasAmpersand = selector.split(/\s*,\s*/).find(token => token == SELF_TOKEN) ? true : false;
if (hasAmpersand) {
selector = selector.replace(SELF_TOKEN_REGEX, '');
}
selector = selector.replace(/@\*/g, NG_TRIGGER_SELECTOR).replace(/@\w+/g, match => NG_TRIGGER_SELECTOR + '-' + match.slice(1)).replace(/:animating/g, NG_ANIMATING_SELECTOR);
return [selector, hasAmpersand];
}
function normalizeParams(obj) {
return obj ? {
...obj
} : null;
}
class AnimationAstBuilderContext {
errors;
queryCount = 0;
depCount = 0;
currentTransition = null;
currentQuery = null;
currentQuerySelector = null;
currentAnimateTimings = null;
currentTime = 0;
collectedStyles = new Map();
options = null;
unsupportedCSSPropertiesFound = new Set();
constructor(errors) {
this.errors = errors;
}
}
function consumeOffset(styles) {
if (typeof styles == 'string') return null;
let offset = null;
if (Array.isArray(styles)) {
styles.forEach(styleTuple => {
if (styleTuple instanceof Map && styleTuple.has('offset')) {
const obj = styleTuple;
offset = parseFloat(obj.get('offset'));
obj.delete('offset');
}
});
} else if (styles instanceof Map && styles.has('offset')) {
const obj = styles;
offset = parseFloat(obj.get('offset'));
obj.delete('offset');
}
return offset;
}
function constructTimingAst(value, errors) {
if (value.hasOwnProperty('duration')) {
return value;
}
if (typeof value == 'number') {
const duration = resolveTiming(value, errors).duration;
return makeTimingAst(duration, 0, '');
}
const strValue = value;
const isDynamic = strValue.split(/\s+/).some(v => v.charAt(0) == '{' && v.charAt(1) == '{');
if (isDynamic) {
const ast = makeTimingAst(0, 0, '');
ast.dynamic = true;
ast.strValue = strValue;
return ast;
}
const timings = resolveTiming(strValue, errors);
return makeTimingAst(timings.duration, timings.delay, timings.easing);
}
function normalizeAnimationOptions(options) {
if (options) {
options = {
...options
};
if (options['params']) {
options['params'] = normalizeParams(options['params']);
}
} else {
options = {};
}
return options;
}
function makeTimingAst(duration, delay, easing) {
return {
duration,
delay,
easing
};
}
function createTimelineInstruction(element, keyframes, preStyleProps, postStyleProps, duration, delay, easing = null, subTimeline = false) {
return {
type: 1,
element,
keyframes,
preStyleProps,
postStyleProps,
duration,
delay,
totalTime: duration + delay,
easing,
subTimeline
};
}
class ElementInstructionMap {
_map = new Map();
get(element) {
return this._map.get(element) || [];
}
append(element, instructions) {
let existingInstructions = this._map.get(element);
if (!existingInstructions) {
this._map.set(element, existingInstructions = []);
}
existingInstructions.push(...instructions);
}
has(element) {
return this._map.has(element);
}
clear() {
this._map.clear();
}
}
const ONE_FRAME_IN_MILLISECONDS = 1;
const ENTER_TOKEN = ':enter';
const ENTER_TOKEN_REGEX = /* @__PURE__ */new RegExp(ENTER_TOKEN, 'g');
const LEAVE_TOKEN = ':leave';
const LEAVE_TOKEN_REGEX = /* @__PURE__ */new RegExp(LEAVE_TOKEN, 'g');
function buildAnimationTimelines(driver, rootElement, ast, enterClassName, leaveClassName, startingStyles = new Map(), finalStyles = new Map(), options, subInstructions, errors = []) {
return new AnimationTimelineBuilderVisitor().buildKeyframes(driver, rootElement, ast, enterClassName, leaveClassName, startingStyles, finalStyles, options, subInstructions, errors);
}
class AnimationTimelineBuilderVisitor {
buildKeyframes(driver, rootElement, ast, enterClassName, leaveClassName, startingStyles, finalStyles, options, subInstructions, errors = []) {
subInstructions = subInstructions || new ElementInstructionMap();
const context = new AnimationTimelineContext(driver, rootElement, subInstructions, enterClassName, leaveClassName, errors, []);
context.options = options;
const delay = options.delay ? resolveTimingValue(options.delay) : 0;
context.currentTimeline.delayNextStep(delay);
context.currentTimeline.setStyles([startingStyles], null, context.errors, options);
visitDslNode(this, ast, context);
const timelines = context.timelines.filter(timeline => timeline.containsAnimation());
if (timelines.length && finalStyles.size) {
let lastRootTimeline;
for (let i = timelines.length - 1; i >= 0; i--) {
const timeline = timelines[i];
if (timeline.element === rootElement) {
lastRootTimeline = timeline;
break;
}
}
if (lastRootTimeline && !lastRootTimeline.allowOnlyTimelineStyles()) {
lastRootTimeline.setStyles([finalStyles], null, context.errors, options);
}
}
return timelines.length ? timelines.map(timeline => timeline.buildKeyframes()) : [createTimelineInstruction(rootElement, [], [], [], 0, delay, '', false)];
}
visitTrigger(ast, context) {}
visitState(ast, context) {}
visitTransition(ast, context) {}
visitAnimateChild(ast, context) {
const elementInstructions = context.subInstructions.get(context.element);
if (elementInstructions) {
const innerContext = context.createSubContext(ast.options);
const startTime = context.currentTimeline.currentTime;
const endTime = this._visitSubInstructions(elementInstructions, innerContext, innerContext.options);
if (startTime != endTime) {
context.transformIntoNewTimeline(endTime);
}
}
context.previousNode = ast;
}
visitAnimateRef(ast, context) {
const innerContext = context.createSubContext(ast.options);
innerContext.transformIntoNewTimeline();
this._applyAnimationRefDelays([ast.options, ast.animation.options], context, innerContext);
this.visitReference(ast.animation, innerContext);
context.transformIntoNewTimeline(innerContext.currentTimeline.currentTime);
context.previousNode = ast;
}
_applyAnimationRefDelays(animationsRefsOptions, context, innerContext) {
for (const animationRefOptions of animationsRefsOptions) {
const animationDelay = animationRefOptions?.delay;
if (animationDelay) {
const animationDelayValue = typeof animationDelay === 'number' ? animationDelay : resolveTimingValue(interpolateParams(animationDelay, animationRefOptions?.params ?? {}, context.errors));
innerContext.delayNextStep(animationDelayValue);
}
}
}
_visitSubInstructions(instructions, context, options) {
const startTime = context.currentTimeline.currentTime;
let furthestTime = startTime;
const duration = options.duration != null ? resolveTimingValue(options.duration) : null;
const delay = options.delay != null ? resolveTimingValue(options.delay) : null;
if (duration !== 0) {
instructions.forEach(instruction => {
const instructionTimings = context.appendInstructionToTimeline(instruction, duration, delay);
furthestTime = Math.max(furthestTime, instructionTimings.duration + instructionTimings.delay);
});
}
return furthestTime;
}
visitReference(ast, context) {
context.updateOptions(ast.options, true);
visitDslNode(this, ast.animation, context);
context.previousNode = ast;
}
visitSequence(ast, context) {
const subContextCount = context.subContextCount;
let ctx = context;
const options = ast.options;
if (options && (options.params || options.delay)) {
ctx = context.createSubContext(options);
ctx.transformIntoNewTimeline();
if (options.delay != null) {
if (ctx.previousNode.type == AnimationMetadataType.Style) {
ctx.currentTimeline.snapshotCurrentStyles();
ctx.previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
}
const delay = resolveTimingValue(options.delay);
ctx.delayNextStep(delay);
}
}
if (ast.steps.length) {
ast.steps.forEach(s => visitDslNode(this, s, ctx));
ctx.currentTimeline.applyStylesToKeyframe();
if (ctx.subContextCount > subContextCount) {
ctx.transformIntoNewTimeline();
}
}
context.previousNode = ast;
}
visitGroup(ast, context) {
const innerTimelines = [];
let furthestTime = context.currentTimeline.currentTime;
const delay = ast.options && ast.options.delay ? resolveTimingValue(ast.options.delay) : 0;
ast.steps.forEach(s => {
const 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);
});
innerTimelines.forEach(timeline => context.currentTimeline.mergeTimelineCollectedStyles(timeline));
context.transformIntoNewTimeline(furthestTime);
context.previousNode = ast;
}
_visitTiming(ast, context) {
if (ast.dynamic) {
const strValue = ast.strValue;
const 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
};
}
}
visitAnimate(ast, context) {
const timings = context.currentAnimateTimings = this._visitTiming(ast.timings, context);
const timeline = context.currentTimeline;
if (timings.delay) {
context.incrementTime(timings.delay);
timeline.snapshotCurrentStyles();
}
const style = ast.style;
if (style.type == AnimationMetadataType.Keyframes) {
this.visitKeyframes(style, context);
} else {
context.incrementTime(timings.duration);
this.visitStyle(style, context);
timeline.applyStylesToKeyframe();
}
context.currentAnimateTimings = null;
context.previousNode = ast;
}
visitStyle(ast, context) {
const timeline = context.currentTimeline;
const timings = context.currentAnimateTimings;
if (!timings && timeline.hasCurrentStyleProperties()) {
timeline.forwardFrame();
}
const 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;
}
visitKeyframes(ast, context) {
const currentAnimateTimings = context.currentAnimateTimings;
const startTime = context.currentTimeline.duration;
const duration = currentAnimateTimings.duration;
const innerContext = context.createSubContext();
const innerTimeline = innerContext.currentTimeline;
innerTimeline.easing = currentAnimateTimings.easing;
ast.styles.forEach(step => {
const offset = step.offset || 0;
innerTimeline.forwardTime(offset * duration);
innerTimeline.setStyles(step.styles, step.easing, context.errors, context.options);
innerTimeline.applyStylesToKeyframe();
});
context.currentTimeline.mergeTimelineCollectedStyles(innerTimeline);
context.transformIntoNewTimeline(startTime + duration);
context.previousNode = ast;
}
visitQuery(ast, context) {
const startTime = context.currentTimeline.currentTime;
const options = ast.options || {};
const delay = options.delay ? resolveTimingValue(options.delay) : 0;
if (delay && (context.previousNode.type === AnimationMetadataType.Style || startTime == 0 && context.currentTimeline.hasCurrentStyleProperties())) {
context.currentTimeline.snapshotCurrentStyles();
context.previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
}
let furthestTime = startTime;
const elms = context.invokeQuery(ast.selector, ast.originalSelector, ast.limit, ast.includeSelf, options.optional ? true : false, context.errors);
context.currentQueryTotal = elms.length;
let sameElementTimeline = null;
elms.forEach((element, i) => {
context.currentQueryIndex = i;
const innerContext = context.createSubContext(ast.options, element);
if (delay) {
innerContext.delayNextStep(delay);
}
if (element === context.element) {
sameElementTimeline = innerContext.currentTimeline;
}
visitDslNode(this, ast.animation, innerContext);
innerContext.currentTimeline.applyStylesToKeyframe();
const 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;
}
visitStagger(ast, context) {
const parentContext = context.parentContext;
const tl = context.currentTimeline;
const timings = ast.timings;
const duration = Math.abs(timings.duration);
const maxTime = duration * (context.currentQueryTotal - 1);
let delay = duration * context.currentQueryIndex;
let staggerTransformer = timings.duration < 0 ? 'reverse' : timings.easing;
switch (staggerTransformer) {
case 'reverse':
delay = maxTime - delay;
break;
case 'full':
delay = parentContext.currentStaggerTime;
break;
}
const timeline = context.currentTimeline;
if (delay) {
timeline.delayNextStep(delay);
}
const startingTime = timeline.currentTime;
visitDslNode(this, ast.animation, context);
context.previousNode = ast;
parentContext.currentStaggerTime = tl.currentTime - startingTime + (tl.startTime - parentContext.currentTimeline.startTime);
}
}
const DEFAULT_NOOP_PREVIOUS_NODE = {};
class AnimationTimelineContext {
_driver;
element;
subInstructions;
_enterClassName;
_leaveClassName;
errors;
timelines;
parentContext = null;
currentTimeline;
currentAnimateTimings = null;
previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
subContextCount = 0;
options = {};
currentQueryIndex = 0;
currentQueryTotal = 0;
currentStaggerTime = 0;
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.currentTimeline = initialTimeline || new TimelineBuilder(this._driver, element, 0);
timelines.push(this.currentTimeline);
}
get params() {
return this.options.params;
}
updateOptions(options, skipIfExists) {
if (!options) return;
const newOptions = options;
let optionsToUpdate = this.options;
if (newOptions.duration != null) {
optionsToUpdate.duration = resolveTimingValue(newOptions.duration);
}
if (newOptions.delay != null) {
optionsToUpdate.delay = resolveTimingValue(newOptions.delay);
}
const newParams = newOptions.params;
if (newParams) {
let paramsToUpdate = 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);
}
});
}
}
_copyOptions() {
const options = {};
if (this.options) {
const oldParams = this.options.params;
if (oldParams) {
const params = options['params'] = {};
Object.keys(oldParams).forEach(name => {
params[name] = oldParams[name];
});
}
}
return options;
}
createSubContext(options = null, element, newTime) {
const target = element || this.element;
const 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;
}
transformIntoNewTimeline(newTime) {
this.previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
this.currentTimeline = this.currentTimeline.fork(this.element, newTime);
this.timelines.push(this.currentTimeline);
return this.currentTimeline;
}
appendInstructionToTimeline(instruction, duration, delay) {
const updatedTimings = {
duration: duration != null ? duration : instruction.duration,
delay: this.currentTimeline.currentTime + (delay != null ? delay : 0) + instruction.delay,
easing: ''
};
const builder = new SubTimelineBuilder(this._driver, instruction.element, instruction.keyframes, instruction.preStyleProps, instruction.postStyleProps, updatedTimings, instruction.stretchStartingKeyframe);
this.timelines.push(builder);
return updatedTimings;
}
incrementTime(time) {
this.currentTimeline.forwardTime(this.currentTimeline.duration + time);
}
delayNextStep(delay) {
if (delay > 0) {
this.currentTimeline.delayNextStep(delay);
}
}
invokeQuery(selector, originalSelector, limit, includeSelf, optional, errors) {
let results = [];
if (includeSelf) {
results.push(this.element);
}
if (selector.length > 0) {
selector = selector.replace(ENTER_TOKEN_REGEX, '.' + this._enterClassName);
selector = selector.replace(LEAVE_TOKEN_REGEX, '.' + this._leaveClassName);
const multi = limit != 1;
let 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(invalidQuery(originalSelector));
}
return results;
}
}
class TimelineBuilder {
_driver;
element;
startTime;
_elementTimelineStylesLookup;
duration = 0;
easing = null;
_previousKeyframe = new Map();
_currentKeyframe = new Map();
_keyframes = new Map();
_styleSummary = new Map();
_localTimelineStyles = new Map();
_globalTimelineStyles;
_pendingStyles = new Map();
_backFill = new Map();
_currentEmptyStepKeyframe = null;
constructor(_driver, element, startTime, _elementTimelineStylesLookup) {
this._driver = _driver;
this.element = element;
this.startTime = startTime;
this._elementTimelineStylesLookup = _elementTimelineStylesLookup;
if (!this._elementTimelineStylesLookup) {
this._elementTimelineStylesLookup = new Map();
}
this._globalTimelineStyles = this._elementTimelineStylesLookup.get(element);
if (!this._globalTimelineStyles) {
this._globalTimelineStyles = this._localTimelineStyles;
this._elementTimelineStylesLookup.set(element, this._localTimelineStyles);
}
this._loadKeyframe();
}
containsAnimation() {
switch (this._keyframes.size) {
case 0:
return false;
case 1:
return this.hasCurrentStyleProperties();
default:
return true;
}
}
hasCurrentStyleProperties() {
return this._currentKeyframe.size > 0;
}
get currentTime() {
return this.startTime + this.duration;
}
delayNextStep(delay) {
const hasPreStyleStep = this._keyframes.size === 1 && this._pendingStyles.size;
if (this.duration || hasPreStyleStep) {
this.forwardTime(this.currentTime + delay);
if (hasPreStyleStep) {
this.snapshotCurrentStyles();
}
} else {
this.startTime += delay;
}
}
fork(element, currentTime) {
this.applyStylesToKeyframe();
return new TimelineBuilder(this._driver, element, currentTime || this.currentTime, this._elementTimelineStylesLookup);
}
_loadKeyframe() {
if (this._currentKeyframe) {
this._previousKeyframe = this._currentKeyframe;
}
this._currentKeyframe = this._keyframes.get(this.duration);
if (!this._currentKeyframe) {
this._currentKeyframe = new Map();
this._keyframes.set(this.duration, this._currentKeyframe);
}
}
forwardFrame() {
this.duration += ONE_FRAME_IN_MILLISECONDS;
this._loadKeyframe();
}
forwardTime(time) {
this.applyStylesToKeyframe();
this.duration = time;
this._loadKeyframe();
}
_updateStyle(prop, value) {
this._localTimelineStyles.set(prop, value);
this._globalTimelineStyles.set(prop, value);
this._styleSummary.set(prop, {
time: this.currentTime,
value
});
}
allowOnlyTimelineStyles() {
return this._currentEmptyStepKeyframe !== this._currentKeyframe;
}
applyEmptyStep(easing) {
if (easing) {
this._previousKeyframe.set('easing', easing);
}
for (let [prop, value] of this._globalTimelineStyles) {
this._backFill.set(prop, value || AUTO_STYLE);
this._currentKeyframe.set(prop, AUTO_STYLE);
}
this._currentEmptyStepKeyframe = this._currentKeyframe;
}
setStyles(input, easing, errors, options) {
if (easing) {
this._previousKeyframe.set('easing', easing);
}
const params = options && options.params || {};
const styles = flattenStyles(input, this._globalTimelineStyles);
for (let [prop, value] of styles) {
const val = interpolateParams(value, params, errors);
this._pendingStyles.set(prop, val);
if (!this._localTimelineStyles.has(prop)) {
this._backFill.set(prop, this._globalTimelineStyles.get(prop) ?? AUTO_STYLE);
}
this._updateStyle(prop, val);
}
}
applyStylesToKeyframe() {
if (this._pendingStyles.size == 0) return;
this._pendingStyles.forEach((val, prop) => {
this._currentKeyframe.set(prop, val);
});
this._pendingStyles.clear();
this._localTimelineStyles.forEach((val, prop) => {
if (!this._currentKeyframe.has(prop)) {
this._currentKeyframe.set(prop, val);
}
});
}
snapshotCurrentStyles() {
for (let [prop, val] of this._localTimelineStyles) {
this._pendingStyles.set(prop, val);
this._updateStyle(prop, val);
}
}
getFinalKeyframe() {
return this._keyframes.get(this.duration);
}
get properties() {
const properties = [];
for (let prop in this._currentKeyframe) {
properties.push(prop);
}
return properties;
}
mergeTimelineCollectedStyles(timeline) {
timeline._styleSummary.forEach((details1, prop) => {
const details0 = this._styleSummary.get(prop);
if (!details0 || details1.time > details0.time) {
this._updateStyle(prop, details1.value);
}
});
}
buildKeyframes() {
this.applyStylesToKeyframe();
const preStyleProps = new Set();
const postStyleProps = new Set();
const isEmpty = this._keyframes.size === 1 && this.duration === 0;
let finalKeyframes = [];
this._keyframes.forEach((keyframe, time) => {
const finalKeyframe = new Map([...this._backFill, ...keyframe]);
finalKeyframe.forEach((value, prop) => {
if (value === _PRE_STYLE) {
preStyleProps.add(prop);
} else if (value === AUTO_STYLE) {
postStyleProps.add(prop);
}
});
if (!isEmpty) {
finalKeyframe.set('offset', time / this.duration);
}
finalKeyframes.push(finalKeyframe);
});
const preProps = [...preStyleProps.values()];
const postProps = [...postStyleProps.values()];
if (isEmpty) {
const kf0 = finalKeyframes[0];
const kf1 = new Map(kf0);
kf0.set('offset', 0);
kf1.set('offset', 1);
finalKeyframes = [kf0, kf1];
}
return createTimelineInstruction(this.element, finalKeyframes, preProps, postProps, this.duration, this.startTime, this.easing, false);
}
}
class SubTimelineBuilder extends TimelineBuilder {
keyframes;
preStyleProps;
postStyleProps;
_stretchStartingKeyframe;
timings;
constructor(driver, element, keyframes, preStyleProps, postStyleProps, timings, _stretchStartingKeyframe = false) {
super(driver, element, timings.delay);
this.keyframes = keyframes;
this.preStyleProps = preStyleProps;
this.postStyleProps = postStyleProps;
this._stretchStartingKeyframe = _stretchStartingKeyframe;
this.timings = {
duration: timings.duration,
delay: timings.delay,
easing: timings.easing
};
}
containsAnimation() {
return this.keyframes.length > 1;
}
buildKeyframes() {
let keyframes = this.keyframes;
let {
delay,
duration,
easing
} = this.timings;
if (this._stretchStartingKeyframe && delay) {
const newKeyframes = [];
const totalTime = duration + delay;
const startingGap = delay / totalTime;
const newFirstKeyframe = new Map(keyframes[0]);
newFirstKeyframe.set('offset', 0);
newKeyframes.push(newFirstKeyframe);
const oldFirstKeyframe = new Map(keyframes[0]);
oldFirstKeyframe.set('offset', roundOffset(startingGap));
newKeyframes.push(oldFirstKeyframe);
const limit = keyframes.length - 1;
for (let i = 1; i <= limit; i++) {
let kf = new Map(keyframes[i]);
const oldOffset = kf.get('offset');
const timeAtKeyframe = delay + oldOffset * duration;
kf.set('offset', roundOffset(timeAtKeyframe / totalTime));
newKeyframes.push(kf);
}
duration = totalTime;
delay = 0;
easing = '';
keyframes = newKeyframes;
}
return createTimelineInstruction(this.element, keyframes, this.preStyleProps, this.postStyleProps, duration, delay, easing, true);
}
}
function roundOffset(offset, decimalPoints = 3) {
const mult = Math.pow(10, decimalPoints - 1);
return Math.round(offset * mult) / mult;
}
function flattenStyles(input, allStyles) {
const styles = new Map();
let allProperties;
input.forEach(token => {
if (token === '*') {
allProperties ??= allStyles.keys();
for (let prop of allProperties) {
styles.set(prop, AUTO_STYLE);
}
} else {
for (let [prop, val] of token) {
styles.set(prop, val);
}
}
});
return styles;
}
function createTransitionInstruction(element, triggerName, fromState, toState, isRemovalTransition, fromStyles, toStyles, timelines, queriedElements, preStyleProps, postStyleProps, totalTime, errors) {
return {
type: 0,
element,
triggerName,
isRemovalTransition,
fromState,
fromStyles,
toState,
toStyles,
timelines,
queriedElements,
preStyleProps,
postStyleProps,
totalTime,
errors
};
}
const EMPTY_OBJECT = {};
class AnimationTransitionFactory {
_triggerName;
ast;
_stateStyles;
constructor(_triggerName, ast, _stateStyles) {
this._triggerName = _triggerName;
this.ast = ast;
this._stateStyles = _stateStyles;
}
match(currentState, nextState, element, params) {
return oneOrMoreTransitionsMatch(this.ast.matchers, currentState, nextState, element, params);
}
buildStyles(stateName, params, errors) {
let styler = this._stateStyles.get('*');
if (stateName !== undefined) {
styler = this._stateStyles.get(stateName?.toString()) || styler;
}
return styler ? styler.buildStyles(params, errors) : new Map();
}
build(driver, element, currentState, nextState, enterClassName, leaveClassName, currentOptions, nextOptions, subInstructions, skipAstBuild) {
const errors = [];
const transitionAnimationParams = this.ast.options && this.ast.options.params || EMPTY_OBJECT;
const currentAnimationParams = currentOptions && currentOptions.params || EMPTY_OBJECT;
const currentStateStyles = this.buildStyles(currentState, currentAnimationParams, errors);
const nextAnimationParams = nextOptions && nextOptions.params || EMPTY_OBJECT;
const nextStateStyles = this.buildStyles(nextState, nextAnimationParams, errors);
const queriedElements = new Set();
const preStyleMap = new Map();
const postStyleMap = new Map();
const isRemoval = nextState === 'void';
const animationOptions = {
params: applyParamDefaults(nextAnimationParams, transitionAnimationParams),
delay: this.ast.options?.delay
};
const timelines = skipAstBuild ? [] : buildAnimationTimelines(driver, element, this.ast.animation, enterClassName, leaveClassName, currentStateStyles, nextStateStyles, animationOptions, subInstructions, errors);
let totalTime = 0;
timelines.forEach(tl => {
totalTime = Math.max(tl.duration + tl.delay, totalTime);
});
if (errors.length) {
return createTransitionInstruction(element, this._triggerName, currentState, nextState, isRemoval, currentStateStyles, nextStateStyles, [], [], preStyleMap, postStyleMap, totalTime, errors);
}
timelines.forEach(tl => {
const elm = tl.element;
const preProps = getOrSetDefaultValue(preStyleMap, elm, new Set());
tl.preStyleProps.forEach(prop => preProps.add(prop));
const postProps = getOrSetDefaultValue(postStyleMap, elm, new S