fix(animations): ensure multi-level enter animations work (#19455)
PR Close #19455
This commit is contained in:

committed by
Miško Hevery

parent
8bb42df47e
commit
b2a586cee1
@ -8,7 +8,7 @@
|
||||
import {AnimationMetadata, AnimationMetadataType, AnimationOptions, ɵStyleData} from '@angular/animations';
|
||||
|
||||
import {AnimationDriver} from '../render/animation_driver';
|
||||
import {normalizeStyles} from '../util';
|
||||
import {ENTER_CLASSNAME, normalizeStyles} from '../util';
|
||||
|
||||
import {Ast} from './animation_ast';
|
||||
import {buildAnimationAst} from './animation_ast_builder';
|
||||
@ -39,7 +39,8 @@ export class Animation {
|
||||
const errors: any = [];
|
||||
subInstructions = subInstructions || new ElementInstructionMap();
|
||||
const result = buildAnimationTimelines(
|
||||
this._driver, element, this._animationAst, start, dest, options, subInstructions, errors);
|
||||
this._driver, element, this._animationAst, ENTER_CLASSNAME, start, dest, options,
|
||||
subInstructions, errors);
|
||||
if (errors.length) {
|
||||
const errorMessage = `animation building failed:\n${errors.join("\n")}`;
|
||||
throw new Error(errorMessage);
|
||||
|
@ -62,8 +62,6 @@ export function buildAnimationAst(
|
||||
|
||||
const LEAVE_TOKEN = ':leave';
|
||||
const LEAVE_TOKEN_REGEX = new RegExp(LEAVE_TOKEN, 'g');
|
||||
const ENTER_TOKEN = ':enter';
|
||||
const ENTER_TOKEN_REGEX = new RegExp(ENTER_TOKEN, 'g');
|
||||
const ROOT_SELECTOR = '';
|
||||
|
||||
export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
||||
@ -478,8 +476,7 @@ function normalizeSelector(selector: string): [string, boolean] {
|
||||
selector = selector.replace(SELF_TOKEN_REGEX, '');
|
||||
}
|
||||
|
||||
selector = selector.replace(ENTER_TOKEN_REGEX, ENTER_SELECTOR)
|
||||
.replace(LEAVE_TOKEN_REGEX, LEAVE_SELECTOR)
|
||||
selector = selector.replace(LEAVE_TOKEN_REGEX, LEAVE_SELECTOR)
|
||||
.replace(/@\*/g, NG_TRIGGER_SELECTOR)
|
||||
.replace(/@\w+/g, match => NG_TRIGGER_SELECTOR + '-' + match.substr(1))
|
||||
.replace(/:animating/g, NG_ANIMATING_SELECTOR);
|
||||
|
@ -15,6 +15,8 @@ import {AnimationTimelineInstruction, createTimelineInstruction} from './animati
|
||||
import {ElementInstructionMap} from './element_instruction_map';
|
||||
|
||||
const ONE_FRAME_IN_MILLISECONDS = 1;
|
||||
const ENTER_TOKEN = ':enter';
|
||||
const ENTER_TOKEN_REGEX = new RegExp(ENTER_TOKEN, 'g');
|
||||
|
||||
/*
|
||||
* The code within this file aims to generate web-animations-compatible keyframes from Angular's
|
||||
@ -101,20 +103,22 @@ const ONE_FRAME_IN_MILLISECONDS = 1;
|
||||
* the `AnimationValidatorVisitor` code.
|
||||
*/
|
||||
export function buildAnimationTimelines(
|
||||
driver: AnimationDriver, rootElement: any, ast: Ast<AnimationMetadataType>,
|
||||
driver: AnimationDriver, rootElement: any, ast: Ast<AnimationMetadataType>, enterClassName: string,
|
||||
startingStyles: ɵStyleData = {}, finalStyles: ɵStyleData = {}, options: AnimationOptions,
|
||||
subInstructions?: ElementInstructionMap, errors: any[] = []): AnimationTimelineInstruction[] {
|
||||
return new AnimationTimelineBuilderVisitor().buildKeyframes(
|
||||
driver, rootElement, ast, startingStyles, finalStyles, options, subInstructions, errors);
|
||||
driver, rootElement, ast, enterClassName, startingStyles, finalStyles, options,
|
||||
subInstructions, errors);
|
||||
}
|
||||
|
||||
export class AnimationTimelineBuilderVisitor implements AstVisitor {
|
||||
buildKeyframes(
|
||||
driver: AnimationDriver, rootElement: any, ast: Ast<AnimationMetadataType>,
|
||||
driver: AnimationDriver, rootElement: any, ast: Ast<AnimationMetadataType>, enterClassName: string,
|
||||
startingStyles: ɵStyleData, finalStyles: ɵStyleData, options: AnimationOptions,
|
||||
subInstructions?: ElementInstructionMap, errors: any[] = []): AnimationTimelineInstruction[] {
|
||||
subInstructions = subInstructions || new ElementInstructionMap();
|
||||
const context = new AnimationTimelineContext(driver, rootElement, subInstructions, errors, []);
|
||||
const context = new AnimationTimelineContext(
|
||||
driver, rootElement, subInstructions, enterClassName, errors, []);
|
||||
context.options = options;
|
||||
context.currentTimeline.setStyles([startingStyles], null, context.errors, options);
|
||||
|
||||
@ -445,8 +449,9 @@ export class AnimationTimelineContext {
|
||||
|
||||
constructor(
|
||||
private _driver: AnimationDriver, public element: any,
|
||||
public subInstructions: ElementInstructionMap, public errors: any[],
|
||||
public timelines: TimelineBuilder[], initialTimeline?: TimelineBuilder) {
|
||||
public subInstructions: ElementInstructionMap, private _enterClassName: string,
|
||||
public errors: any[], public timelines: TimelineBuilder[],
|
||||
initialTimeline?: TimelineBuilder) {
|
||||
this.currentTimeline = initialTimeline || new TimelineBuilder(this._driver, element, 0);
|
||||
timelines.push(this.currentTimeline);
|
||||
}
|
||||
@ -499,8 +504,8 @@ export class AnimationTimelineContext {
|
||||
AnimationTimelineContext {
|
||||
const target = element || this.element;
|
||||
const context = new AnimationTimelineContext(
|
||||
this._driver, target, this.subInstructions, this.errors, this.timelines,
|
||||
this.currentTimeline.fork(target, newTime || 0));
|
||||
this._driver, target, this.subInstructions, this._enterClassName, this.errors,
|
||||
this.timelines, this.currentTimeline.fork(target, newTime || 0));
|
||||
context.previousNode = this.previousNode;
|
||||
context.currentAnimateTimings = this.currentAnimateTimings;
|
||||
|
||||
@ -555,6 +560,7 @@ export class AnimationTimelineContext {
|
||||
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);
|
||||
const multi = limit != 1;
|
||||
let elements = this._driver.query(this.element, selector, multi);
|
||||
if (limit !== 0) {
|
||||
|
@ -37,7 +37,7 @@ export class AnimationTransitionFactory {
|
||||
|
||||
build(
|
||||
driver: AnimationDriver, element: any, currentState: any, nextState: any,
|
||||
currentOptions?: AnimationOptions, nextOptions?: AnimationOptions,
|
||||
enterClassName: string, currentOptions?: AnimationOptions, nextOptions?: AnimationOptions,
|
||||
subInstructions?: ElementInstructionMap): AnimationTransitionInstruction {
|
||||
const errors: any[] = [];
|
||||
|
||||
@ -55,8 +55,8 @@ export class AnimationTransitionFactory {
|
||||
const animationOptions = {params: {...transitionAnimationParams, ...nextAnimationParams}};
|
||||
|
||||
const timelines = buildAnimationTimelines(
|
||||
driver, element, this.ast.animation, currentStateStyles, nextStateStyles, animationOptions,
|
||||
subInstructions, errors);
|
||||
driver, element, this.ast.animation, enterClassName, currentStateStyles, nextStateStyles,
|
||||
animationOptions, subInstructions, errors);
|
||||
|
||||
if (errors.length) {
|
||||
return createTransitionInstruction(
|
||||
|
Reference in New Issue
Block a user