Skip to content

Commit

Permalink
fix(animations): ensure multi-level leave animations work
Browse files Browse the repository at this point in the history
  • Loading branch information
matsko committed Oct 17, 2017
1 parent 6074763 commit 9ce40e1
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 46 deletions.
6 changes: 3 additions & 3 deletions packages/animations/browser/src/dsl/animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import {AnimationMetadata, AnimationMetadataType, AnimationOptions, ɵStyleData} from '@angular/animations';

import {AnimationDriver} from '../render/animation_driver';
import {ENTER_CLASSNAME, normalizeStyles} from '../util';
import {ENTER_CLASSNAME, LEAVE_CLASSNAME, normalizeStyles} from '../util';

import {Ast} from './animation_ast';
import {buildAnimationAst} from './animation_ast_builder';
Expand Down Expand Up @@ -39,8 +39,8 @@ export class Animation {
const errors: any = [];
subInstructions = subInstructions || new ElementInstructionMap();
const result = buildAnimationTimelines(
this._driver, element, this._animationAst, ENTER_CLASSNAME, start, dest, options,
subInstructions, errors);
this._driver, element, this._animationAst, ENTER_CLASSNAME, LEAVE_CLASSNAME, start, dest,
options, subInstructions, errors);
if (errors.length) {
const errorMessage = `animation building failed:\n${errors.join("\n")}`;
throw new Error(errorMessage);
Expand Down
1 change: 1 addition & 0 deletions packages/animations/browser/src/dsl/animation_ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface TriggerAst extends Ast<AnimationMetadataType.Trigger> {
transitions: TransitionAst[];
queryCount: number;
depCount: number;
hasLeaveQueries?: boolean;
}

export interface StateAst extends Ast<AnimationMetadataType.State> {
Expand Down
16 changes: 10 additions & 6 deletions packages/animations/browser/src/dsl/animation_ast_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import {AUTO_STYLE, AnimateTimings, AnimationAnimateChildMetadata, AnimationAnim

import {AnimationDriver} from '../render/animation_driver';
import {getOrSetAsInMap} from '../render/shared';
import {ENTER_SELECTOR, LEAVE_SELECTOR, NG_ANIMATING_SELECTOR, NG_TRIGGER_SELECTOR, SUBSTITUTION_EXPR_START, copyObj, extractStyleParams, iteratorToArray, normalizeAnimationEntry, resolveTiming, validateStyleParams, visitDslNode} from '../util';
import {ENTER_SELECTOR, LEAVE_SELECTOR, LEAVE_TOKEN, NG_ANIMATING_SELECTOR, NG_TRIGGER_SELECTOR, SELF_TOKEN, SUBSTITUTION_EXPR_START, copyObj, extractStyleParams, iteratorToArray, normalizeAnimationEntry, resolveTiming, validateStyleParams, visitDslNode} from '../util';

import {AnimateAst, AnimateChildAst, AnimateRefAst, Ast, DynamicTimingAst, GroupAst, KeyframesAst, QueryAst, ReferenceAst, SequenceAst, StaggerAst, StateAst, StyleAst, TimingAst, TransitionAst, TriggerAst} from './animation_ast';
import {AnimationDslVisitor} from './animation_dsl_visitor';
import {parseTransitionExpr} from './animation_transition_expr';

const SELF_TOKEN = ':self';
const SELF_TOKEN_REGEX = new RegExp(`\s*${SELF_TOKEN}\s*,?`, 'g');
const LEAVE_TOKEN_REGEX = new RegExp(`\s*${LEAVE_TOKEN}\s*,?`, 'g');

/*
* [Validation]
Expand Down Expand Up @@ -60,8 +60,6 @@ export function buildAnimationAst(
return new AnimationAstBuilderVisitor(driver).build(metadata, errors);
}

const LEAVE_TOKEN = ':leave';
const LEAVE_TOKEN_REGEX = new RegExp(LEAVE_TOKEN, 'g');
const ROOT_SELECTOR = '';

export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
Expand All @@ -86,6 +84,7 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
TriggerAst {
let queryCount = context.queryCount = 0;
let depCount = context.depCount = 0;
let leaveQueryCount = context.leaveQueryCount = 0;
const states: StateAst[] = [];
const transitions: TransitionAst[] = [];
metadata.definitions.forEach(def => {
Expand All @@ -112,6 +111,7 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
return {
type: AnimationMetadataType.Trigger,
name: metadata.name, states, transitions, queryCount, depCount,
hasLeaveQueries: context.leaveQueryCount > 0,
options: null
};
}
Expand Down Expand Up @@ -435,6 +435,9 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
getOrSetAsInMap(context.collectedStyles, context.currentQuerySelector, {});

const animation = visitDslNode(this, normalizeAnimationEntry(metadata.animation), context);
if (LEAVE_TOKEN_REGEX.test(selector)) {
context.leaveQueryCount++;
}
context.currentQuery = null;
context.currentQuerySelector = parentSelector;

Expand Down Expand Up @@ -471,8 +474,8 @@ function normalizeSelector(selector: string): [string, boolean] {
selector = selector.replace(SELF_TOKEN_REGEX, '');
}

selector = selector.replace(LEAVE_TOKEN_REGEX, LEAVE_SELECTOR)
.replace(/@\*/g, NG_TRIGGER_SELECTOR)
// the :enter and :leave selectors are filled in at runtime during timeline building
selector = selector.replace(/@\*/g, NG_TRIGGER_SELECTOR)
.replace(/@\w+/g, match => NG_TRIGGER_SELECTOR + '-' + match.substr(1))
.replace(/:animating/g, NG_ANIMATING_SELECTOR);

Expand All @@ -490,6 +493,7 @@ export type StyleTimeTuple = {

export class AnimationAstBuilderContext {
public queryCount: number = 0;
public leaveQueryCount: number = 0;
public depCount: number = 0;
public currentTransition: AnimationTransitionMetadata|null = null;
public currentQuery: AnimationQueryMetadata|null = null;
Expand Down
27 changes: 14 additions & 13 deletions packages/animations/browser/src/dsl/animation_timeline_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
import {AUTO_STYLE, AnimateChildOptions, AnimateTimings, AnimationMetadataType, AnimationOptions, AnimationQueryOptions, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations';

import {AnimationDriver} from '../render/animation_driver';
import {copyObj, copyStyles, interpolateParams, iteratorToArray, resolveTiming, resolveTimingValue, visitDslNode} from '../util';
import {ENTER_TOKEN, LEAVE_TOKEN, copyObj, copyStyles, interpolateParams, iteratorToArray, resolveTiming, resolveTimingValue, visitDslNode} from '../util';

import {AnimateAst, AnimateChildAst, AnimateRefAst, Ast, AstVisitor, DynamicTimingAst, GroupAst, KeyframesAst, QueryAst, ReferenceAst, SequenceAst, StaggerAst, StateAst, StyleAst, TimingAst, TransitionAst, TriggerAst} from './animation_ast';
import {AnimationTimelineInstruction, createTimelineInstruction} from './animation_timeline_instruction';
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');
const LEAVE_TOKEN_REGEX = new RegExp(LEAVE_TOKEN, 'g');

/*
* The code within this file aims to generate web-animations-compatible keyframes from Angular's
Expand Down Expand Up @@ -104,23 +104,23 @@ const ENTER_TOKEN_REGEX = new RegExp(ENTER_TOKEN, 'g');
*/
export function buildAnimationTimelines(
driver: AnimationDriver, rootElement: any, ast: Ast<AnimationMetadataType>,
enterClassName: string, startingStyles: ɵStyleData = {}, finalStyles: ɵStyleData = {},
options: AnimationOptions, subInstructions?: ElementInstructionMap,
errors: any[] = []): AnimationTimelineInstruction[] {
enterClassName: string, leaveClassName: string, startingStyles: ɵStyleData = {},
finalStyles: ɵStyleData = {}, options: AnimationOptions,
subInstructions?: ElementInstructionMap, errors: any[] = []): AnimationTimelineInstruction[] {
return new AnimationTimelineBuilderVisitor().buildKeyframes(
driver, rootElement, ast, enterClassName, startingStyles, finalStyles, options,
subInstructions, errors);
driver, rootElement, ast, enterClassName, leaveClassName, startingStyles, finalStyles,
options, subInstructions, errors);
}

export class AnimationTimelineBuilderVisitor implements AstVisitor {
buildKeyframes(
driver: AnimationDriver, rootElement: any, ast: Ast<AnimationMetadataType>,
enterClassName: string, startingStyles: ɵStyleData, finalStyles: ɵStyleData,
options: AnimationOptions, subInstructions?: ElementInstructionMap,
enterClassName: string, leaveClassName: string, startingStyles: ɵStyleData,
finalStyles: ɵStyleData, options: AnimationOptions, subInstructions?: ElementInstructionMap,
errors: any[] = []): AnimationTimelineInstruction[] {
subInstructions = subInstructions || new ElementInstructionMap();
const context = new AnimationTimelineContext(
driver, rootElement, subInstructions, enterClassName, errors, []);
driver, rootElement, subInstructions, enterClassName, leaveClassName, errors, []);
context.options = options;
context.currentTimeline.setStyles([startingStyles], null, context.errors, options);

Expand Down Expand Up @@ -452,7 +452,7 @@ export class AnimationTimelineContext {
constructor(
private _driver: AnimationDriver, public element: any,
public subInstructions: ElementInstructionMap, private _enterClassName: string,
public errors: any[], public timelines: TimelineBuilder[],
private _leaveClassName: string, public errors: any[], public timelines: TimelineBuilder[],
initialTimeline?: TimelineBuilder) {
this.currentTimeline = initialTimeline || new TimelineBuilder(this._driver, element, 0);
timelines.push(this.currentTimeline);
Expand Down Expand Up @@ -506,8 +506,8 @@ export class AnimationTimelineContext {
AnimationTimelineContext {
const target = element || this.element;
const context = new AnimationTimelineContext(
this._driver, target, this.subInstructions, this._enterClassName, this.errors,
this.timelines, this.currentTimeline.fork(target, newTime || 0));
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;

Expand Down Expand Up @@ -563,6 +563,7 @@ export class AnimationTimelineContext {
}
if (selector.length > 0) { // if :self is only used then the selector is empty
selector = selector.replace(ENTER_TOKEN_REGEX, '.' + this._enterClassName);
selector = selector.replace(LEAVE_TOKEN_REGEX, '.' + this._leaveClassName);
const multi = limit != 1;
let elements = this._driver.query(this.element, selector, multi);
if (limit !== 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export class AnimationTransitionFactory {

build(
driver: AnimationDriver, element: any, currentState: any, nextState: any,
enterClassName: string, currentOptions?: AnimationOptions, nextOptions?: AnimationOptions,
enterClassName: string, leaveClassName: string, currentOptions?: AnimationOptions,
nextOptions?: AnimationOptions,
subInstructions?: ElementInstructionMap): AnimationTransitionInstruction {
const errors: any[] = [];

Expand All @@ -55,8 +56,8 @@ export class AnimationTransitionFactory {
const animationOptions = {params: {...transitionAnimationParams, ...nextAnimationParams}};

const timelines = buildAnimationTimelines(
driver, element, this.ast.animation, enterClassName, currentStateStyles, nextStateStyles,
animationOptions, subInstructions, errors);
driver, element, this.ast.animation, enterClassName, leaveClassName, currentStateStyles,
nextStateStyles, animationOptions, subInstructions, errors);

if (errors.length) {
return createTransitionInstruction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {buildAnimationTimelines} from '../dsl/animation_timeline_builder';
import {AnimationTimelineInstruction} from '../dsl/animation_timeline_instruction';
import {ElementInstructionMap} from '../dsl/element_instruction_map';
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
import {ENTER_CLASSNAME} from '../util';
import {ENTER_CLASSNAME, LEAVE_CLASSNAME} from '../util';

import {AnimationDriver} from './animation_driver';
import {getOrSetAsInMap, listenOnPlayer, makeAnimationEvent, normalizeKeyframes, optimizeGroupPlayer} from './shared';
Expand Down Expand Up @@ -56,8 +56,8 @@ export class TimelineAnimationEngine {

if (ast) {
instructions = buildAnimationTimelines(
this._driver, element, ast, ENTER_CLASSNAME, {}, {}, options, EMPTY_INSTRUCTION_MAP,
errors);
this._driver, element, ast, ENTER_CLASSNAME, LEAVE_CLASSNAME, {}, {}, options,
EMPTY_INSTRUCTION_MAP, errors);
instructions.forEach(inst => {
const styles = getOrSetAsInMap(autoStylesMap, inst.element, {});
inst.postStyleProps.forEach(prop => styles[prop] = null);
Expand Down
Loading

0 comments on commit 9ce40e1

Please sign in to comment.