parent
6c6b316bd9
commit
5e0f8cf3f0
@ -58,3 +58,25 @@ export var pureProxy10: typeof t.pureProxy10 = r.pureProxy10;
|
||||
export var castByValue: typeof t.castByValue = r.castByValue;
|
||||
export type Console = t.Console;
|
||||
export var Console: typeof t.Console = r.Console;
|
||||
|
||||
export type NoOpAnimationPlayer = t.NoOpAnimationPlayer;
|
||||
export var NoOpAnimationPlayer: typeof t.NoOpAnimationPlayer = r.NoOpAnimationPlayer;
|
||||
export type AnimationPlayer = t.AnimationPlayer;
|
||||
export var AnimationPlayer: typeof t.AnimationPlayer = r.AnimationPlayer;
|
||||
export type NoOpAnimationDriver = t.NoOpAnimationDriver;
|
||||
export var NoOpAnimationDriver: typeof t.NoOpAnimationDriver = r.NoOpAnimationDriver;
|
||||
export type AnimationDriver = t.AnimationDriver;
|
||||
export var AnimationDriver: typeof t.AnimationDriver = r.AnimationDriver;
|
||||
export type AnimationSequencePlayer = t.AnimationSequencePlayer;
|
||||
export var AnimationSequencePlayer: typeof t.AnimationSequencePlayer = r.AnimationSequencePlayer;
|
||||
export type AnimationGroupPlayer = t.AnimationGroupPlayer;
|
||||
export var AnimationGroupPlayer: typeof t.AnimationGroupPlayer = r.AnimationGroupPlayer;
|
||||
export type AnimationKeyframe = t.AnimationKeyframe;
|
||||
export var AnimationKeyframe: typeof t.AnimationKeyframe = r.AnimationKeyframe;
|
||||
export type AnimationStyleUtil = t.AnimationStyleUtil;
|
||||
export var AnimationStyleUtil: typeof t.AnimationStyleUtil = r.AnimationStyleUtil;
|
||||
export type AnimationStylrs = t.AnimationStyles;
|
||||
export var AnimationStyles: typeof t.AnimationStyles = r.AnimationStyles;
|
||||
export var ANY_STATE = r.ANY_STATE;
|
||||
export var EMPTY_STATE = r.EMPTY_STATE;
|
||||
export var FILL_STYLE_FLAG = r.FILL_STYLE_FLAG;
|
||||
|
98
modules/@angular/compiler/src/animation/animation_ast.ts
Normal file
98
modules/@angular/compiler/src/animation/animation_ast.ts
Normal file
@ -0,0 +1,98 @@
|
||||
export abstract class AnimationAst {
|
||||
public startTime: number = 0;
|
||||
public playTime: number = 0;
|
||||
abstract visit(visitor: AnimationAstVisitor, context: any): any;
|
||||
}
|
||||
|
||||
export abstract class AnimationStateAst extends AnimationAst {
|
||||
abstract visit(visitor: AnimationAstVisitor, context: any): any;
|
||||
}
|
||||
|
||||
export interface AnimationAstVisitor {
|
||||
visitAnimationEntry(ast: AnimationEntryAst, context: any): any;
|
||||
visitAnimationStateDeclaration(ast: AnimationStateDeclarationAst, context: any): any;
|
||||
visitAnimationStateTransition(ast: AnimationStateTransitionAst, context: any): any;
|
||||
visitAnimationStep(ast: AnimationStepAst, context: any): any;
|
||||
visitAnimationSequence(ast: AnimationSequenceAst, context: any): any;
|
||||
visitAnimationGroup(ast: AnimationGroupAst, context: any): any;
|
||||
visitAnimationKeyframe(ast: AnimationKeyframeAst, context: any): any;
|
||||
visitAnimationStyles(ast: AnimationStylesAst, context: any): any;
|
||||
}
|
||||
|
||||
export class AnimationEntryAst extends AnimationAst {
|
||||
constructor(public name: string,
|
||||
public stateDeclarations: AnimationStateDeclarationAst[],
|
||||
public stateTransitions: AnimationStateTransitionAst[]) {
|
||||
super();
|
||||
}
|
||||
visit(visitor: AnimationAstVisitor, context: any): any {
|
||||
return visitor.visitAnimationEntry(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class AnimationStateDeclarationAst extends AnimationStateAst {
|
||||
constructor(public stateName: string, public styles: AnimationStylesAst) {
|
||||
super();
|
||||
}
|
||||
visit(visitor: AnimationAstVisitor, context: any): any {
|
||||
return visitor.visitAnimationStateDeclaration(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class AnimationStateTransitionExpression {
|
||||
constructor(public fromState: string, public toState: string) {}
|
||||
}
|
||||
|
||||
export class AnimationStateTransitionAst extends AnimationStateAst {
|
||||
constructor(public stateChanges: AnimationStateTransitionExpression[], public animation: AnimationSequenceAst) {
|
||||
super();
|
||||
}
|
||||
visit(visitor: AnimationAstVisitor, context: any): any {
|
||||
return visitor.visitAnimationStateTransition(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class AnimationStepAst extends AnimationAst {
|
||||
constructor(public startingStyles: AnimationStylesAst,
|
||||
public keyframes: AnimationKeyframeAst[],
|
||||
public duration: number,
|
||||
public delay: number,
|
||||
public easing: string) {
|
||||
super();
|
||||
}
|
||||
visit(visitor: AnimationAstVisitor, context: any): any {
|
||||
return visitor.visitAnimationStep(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class AnimationStylesAst extends AnimationAst {
|
||||
constructor(public styles: Array<{[key: string]: string | number}>) { super(); }
|
||||
visit(visitor: AnimationAstVisitor, context: any): any {
|
||||
return visitor.visitAnimationStyles(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class AnimationKeyframeAst extends AnimationAst {
|
||||
constructor(public offset: number, public styles: AnimationStylesAst) { super(); }
|
||||
visit(visitor: AnimationAstVisitor, context: any): any {
|
||||
return visitor.visitAnimationKeyframe(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class AnimationWithStepsAst extends AnimationAst {
|
||||
constructor(public steps: AnimationAst[]) { super(); }
|
||||
}
|
||||
|
||||
export class AnimationGroupAst extends AnimationWithStepsAst {
|
||||
constructor(steps: AnimationAst[]) { super(steps); }
|
||||
visit(visitor: AnimationAstVisitor, context: any): any {
|
||||
return visitor.visitAnimationGroup(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
export class AnimationSequenceAst extends AnimationWithStepsAst {
|
||||
constructor(steps: AnimationAst[]) { super(steps); }
|
||||
visit(visitor: AnimationAstVisitor, context: any): any {
|
||||
return visitor.visitAnimationSequence(this, context);
|
||||
}
|
||||
}
|
368
modules/@angular/compiler/src/animation/animation_compiler.ts
Normal file
368
modules/@angular/compiler/src/animation/animation_compiler.ts
Normal file
@ -0,0 +1,368 @@
|
||||
import {BaseException} from '../facade/exceptions';
|
||||
import {ListWrapper, Map, StringMapWrapper} from '../facade/collection';
|
||||
import {isPresent, isBlank, isArray} from '../facade/lang';
|
||||
|
||||
import {Identifiers} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
import {AUTO_STYLE} from '@angular/core';
|
||||
import {ANY_STATE, EMPTY_STATE} from '../../core_private';
|
||||
|
||||
import {
|
||||
AnimationParseError,
|
||||
ParsedAnimationResult,
|
||||
parseAnimationEntry
|
||||
} from './animation_parser';
|
||||
|
||||
import {CompileDirectiveMetadata} from "../compile_metadata";
|
||||
|
||||
import {
|
||||
AnimationAst,
|
||||
AnimationEntryAst,
|
||||
AnimationStateAst,
|
||||
AnimationStateDeclarationAst,
|
||||
AnimationStateTransitionAst,
|
||||
AnimationKeyframeAst,
|
||||
AnimationStylesAst,
|
||||
AnimationSequenceAst,
|
||||
AnimationGroupAst,
|
||||
AnimationStepAst,
|
||||
AnimationAstVisitor
|
||||
} from './animation_ast';
|
||||
|
||||
export class CompiledAnimation {
|
||||
constructor(public name: string,
|
||||
public statesMapStatement: o.Statement,
|
||||
public statesVariableName: string,
|
||||
public fnStatement: o.Statement,
|
||||
public fnVariable: o.Expression) {}
|
||||
}
|
||||
|
||||
export class AnimationCompiler {
|
||||
compileComponent(component: CompileDirectiveMetadata): CompiledAnimation[] {
|
||||
var compiledAnimations: CompiledAnimation[] = [];
|
||||
var index = 0;
|
||||
component.template.animations.forEach(entry => {
|
||||
var result = parseAnimationEntry(entry);
|
||||
if (result.errors.length > 0) {
|
||||
var errorMessage = '';
|
||||
result.errors.forEach((error: AnimationParseError) => { errorMessage += "\n- " + error.msg; });
|
||||
// todo (matsko): include the component name when throwing
|
||||
throw new BaseException(
|
||||
`Unable to parse the animation sequence for "${entry.name}" due to the following errors: ` +
|
||||
errorMessage);
|
||||
}
|
||||
|
||||
var factoryName = `${component.type.name}_${entry.name}_${index}`;
|
||||
index++;
|
||||
|
||||
var visitor = new _AnimationBuilder(entry.name, factoryName);
|
||||
compiledAnimations.push(visitor.build(result.ast));
|
||||
});
|
||||
return compiledAnimations;
|
||||
}
|
||||
}
|
||||
|
||||
var _ANIMATION_FACTORY_ELEMENT_VAR = o.variable('element');
|
||||
var _ANIMATION_FACTORY_VIEW_VAR = o.variable('view');
|
||||
var _ANIMATION_FACTORY_RENDERER_VAR = _ANIMATION_FACTORY_VIEW_VAR.prop('renderer');
|
||||
var _ANIMATION_CURRENT_STATE_VAR = o.variable('currentState');
|
||||
var _ANIMATION_NEXT_STATE_VAR = o.variable('nextState');
|
||||
var _ANIMATION_PLAYER_VAR = o.variable('player');
|
||||
var _ANIMATION_START_STATE_STYLES_VAR = o.variable('startStateStyles');
|
||||
var _ANIMATION_END_STATE_STYLES_VAR = o.variable('endStateStyles');
|
||||
var _ANIMATION_COLLECTED_STYLES = o.variable('collectedStyles');
|
||||
var EMPTY_MAP = o.literalMap([]);
|
||||
|
||||
class _AnimationBuilder implements AnimationAstVisitor {
|
||||
private _fnVarName: string;
|
||||
private _statesMapVarName: string;
|
||||
private _statesMapVar: any;
|
||||
|
||||
constructor(public animationName: string, factoryName: string) {
|
||||
this._fnVarName = factoryName + '_factory';
|
||||
this._statesMapVarName = factoryName + '_states';
|
||||
this._statesMapVar = o.variable(this._statesMapVarName);
|
||||
}
|
||||
|
||||
visitAnimationStyles(ast: AnimationStylesAst,
|
||||
context: _AnimationBuilderContext): o.Expression {
|
||||
var stylesArr = [];
|
||||
if (context.isExpectingFirstStyleStep) {
|
||||
stylesArr.push(_ANIMATION_START_STATE_STYLES_VAR);
|
||||
context.isExpectingFirstStyleStep = false;
|
||||
}
|
||||
|
||||
ast.styles.forEach(entry => {
|
||||
stylesArr.push(o.literalMap(StringMapWrapper.keys(entry).map(key => [key, o.literal(entry[key])])));
|
||||
});
|
||||
|
||||
return o.importExpr(Identifiers.AnimationStyles).instantiate([
|
||||
o.importExpr(Identifiers.collectAndResolveStyles).callFn([
|
||||
_ANIMATION_COLLECTED_STYLES,
|
||||
o.literalArr(stylesArr)
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
visitAnimationKeyframe(ast: AnimationKeyframeAst,
|
||||
context: _AnimationBuilderContext): o.Expression {
|
||||
return o.importExpr(Identifiers.AnimationKeyframe).instantiate([
|
||||
o.literal(ast.offset),
|
||||
ast.styles.visit(this, context)
|
||||
]);
|
||||
}
|
||||
|
||||
visitAnimationStep(ast: AnimationStepAst, context: _AnimationBuilderContext): o.Expression {
|
||||
if (context.endStateAnimateStep === ast) {
|
||||
return this._visitEndStateAnimation(ast, context);
|
||||
}
|
||||
|
||||
var startingStylesExpr = ast.startingStyles.visit(this, context);
|
||||
var keyframeExpressions = ast.keyframes.map(keyframeEntry => keyframeEntry.visit(this, context));
|
||||
return this._callAnimateMethod(ast, startingStylesExpr, o.literalArr(keyframeExpressions));
|
||||
}
|
||||
|
||||
_visitEndStateAnimation(ast: AnimationStepAst,
|
||||
context: _AnimationBuilderContext): o.Expression {
|
||||
var startingStylesExpr = ast.startingStyles.visit(this, context);
|
||||
var keyframeExpressions = ast.keyframes.map(keyframe => keyframe.visit(this, context));
|
||||
var keyframesExpr = o.importExpr(Identifiers.balanceAnimationKeyframes).callFn([
|
||||
_ANIMATION_COLLECTED_STYLES,
|
||||
_ANIMATION_END_STATE_STYLES_VAR,
|
||||
o.literalArr(keyframeExpressions)
|
||||
]);
|
||||
|
||||
return this._callAnimateMethod(ast, startingStylesExpr, keyframesExpr);
|
||||
}
|
||||
|
||||
_callAnimateMethod(ast: AnimationStepAst, startingStylesExpr, keyframesExpr) {
|
||||
return _ANIMATION_FACTORY_RENDERER_VAR.callMethod('animate', [
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR,
|
||||
startingStylesExpr,
|
||||
keyframesExpr,
|
||||
o.literal(ast.duration),
|
||||
o.literal(ast.delay),
|
||||
o.literal(ast.easing)
|
||||
]);
|
||||
}
|
||||
|
||||
visitAnimationSequence(ast: AnimationSequenceAst,
|
||||
context: _AnimationBuilderContext): o.Expression {
|
||||
var playerExprs = ast.steps.map(step => step.visit(this, context));
|
||||
return o.importExpr(Identifiers.AnimationSequencePlayer).instantiate([
|
||||
o.literalArr(playerExprs)]);
|
||||
}
|
||||
|
||||
visitAnimationGroup(ast: AnimationGroupAst, context: _AnimationBuilderContext): o.Expression {
|
||||
var playerExprs = ast.steps.map(step => step.visit(this, context));
|
||||
return o.importExpr(Identifiers.AnimationGroupPlayer).instantiate([
|
||||
o.literalArr(playerExprs)]);
|
||||
}
|
||||
|
||||
visitAnimationStateDeclaration(ast: AnimationStateDeclarationAst, context: _AnimationBuilderContext): void {
|
||||
var flatStyles: {[key: string]: string|number} = {};
|
||||
_getStylesArray(ast).forEach(entry => {
|
||||
StringMapWrapper.forEach(entry, (value, key) => {
|
||||
flatStyles[key] = value;
|
||||
});
|
||||
});
|
||||
context.stateMap.registerState(ast.stateName, flatStyles);
|
||||
}
|
||||
|
||||
visitAnimationStateTransition(ast: AnimationStateTransitionAst, context: _AnimationBuilderContext): any {
|
||||
var steps = ast.animation.steps;
|
||||
var lastStep = steps[steps.length - 1];
|
||||
if (_isEndStateAnimateStep(lastStep)) {
|
||||
context.endStateAnimateStep = <AnimationStepAst>lastStep;
|
||||
}
|
||||
|
||||
context.isExpectingFirstStyleStep = true;
|
||||
|
||||
var stateChangePreconditions = [];
|
||||
|
||||
ast.stateChanges.forEach(stateChange => {
|
||||
stateChangePreconditions.push(
|
||||
_compareToAnimationStateExpr(_ANIMATION_CURRENT_STATE_VAR, stateChange.fromState)
|
||||
.and(_compareToAnimationStateExpr(_ANIMATION_NEXT_STATE_VAR, stateChange.toState))
|
||||
);
|
||||
|
||||
if (stateChange.fromState != ANY_STATE) {
|
||||
context.stateMap.registerState(stateChange.fromState);
|
||||
}
|
||||
|
||||
if (stateChange.toState != ANY_STATE) {
|
||||
context.stateMap.registerState(stateChange.toState);
|
||||
}
|
||||
});
|
||||
|
||||
var animationPlayerExpr = ast.animation.visit(this, context);
|
||||
|
||||
var reducedStateChangesPrecondition = stateChangePreconditions.reduce((a,b) => a.or(b));
|
||||
var precondition = _ANIMATION_PLAYER_VAR.equals(o.NULL_EXPR).and(reducedStateChangesPrecondition);
|
||||
|
||||
return new o.IfStmt(precondition, [
|
||||
_ANIMATION_PLAYER_VAR.set(animationPlayerExpr).toStmt()
|
||||
]);
|
||||
}
|
||||
|
||||
visitAnimationEntry(ast: AnimationEntryAst, context: _AnimationBuilderContext): any {
|
||||
//visit each of the declarations first to build the context state map
|
||||
ast.stateDeclarations.forEach(def => def.visit(this, context));
|
||||
|
||||
var statements = [];
|
||||
statements.push(
|
||||
_ANIMATION_FACTORY_VIEW_VAR.callMethod('cancelActiveAnimation', [
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR,
|
||||
o.literal(this.animationName),
|
||||
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
|
||||
]).toStmt());
|
||||
|
||||
statements.push(_ANIMATION_COLLECTED_STYLES.set(EMPTY_MAP).toDeclStmt());
|
||||
statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt());
|
||||
|
||||
statements.push(
|
||||
_ANIMATION_START_STATE_STYLES_VAR.set(
|
||||
this._statesMapVar.key(_ANIMATION_CURRENT_STATE_VAR)
|
||||
).toDeclStmt());
|
||||
|
||||
statements.push(
|
||||
new o.IfStmt(_ANIMATION_START_STATE_STYLES_VAR.equals(o.NULL_EXPR), [
|
||||
_ANIMATION_START_STATE_STYLES_VAR.set(EMPTY_MAP).toStmt()
|
||||
]));
|
||||
|
||||
statements.push(
|
||||
_ANIMATION_END_STATE_STYLES_VAR.set(
|
||||
this._statesMapVar.key(_ANIMATION_NEXT_STATE_VAR)
|
||||
).toDeclStmt());
|
||||
|
||||
statements.push(
|
||||
new o.IfStmt(_ANIMATION_END_STATE_STYLES_VAR.equals(o.NULL_EXPR), [
|
||||
_ANIMATION_END_STATE_STYLES_VAR.set(EMPTY_MAP).toStmt()
|
||||
]));
|
||||
|
||||
// before we start any animation we want to clear out the starting
|
||||
// styles from the element's style property (since they were placed
|
||||
// there at the end of the last animation
|
||||
statements.push(
|
||||
_ANIMATION_FACTORY_RENDERER_VAR.callMethod('setElementStyles', [
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR,
|
||||
o.importExpr(Identifiers.clearAnimationStyles).callFn([_ANIMATION_START_STATE_STYLES_VAR])
|
||||
]).toStmt());
|
||||
|
||||
ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context)));
|
||||
|
||||
// this check ensures that the animation factory always returns a player
|
||||
// so that the onDone callback can be used for tracking
|
||||
statements.push(
|
||||
new o.IfStmt(_ANIMATION_PLAYER_VAR.equals(o.NULL_EXPR), [
|
||||
_ANIMATION_PLAYER_VAR.set(
|
||||
o.importExpr(Identifiers.NoOpAnimationPlayer).instantiate([])
|
||||
).toStmt()
|
||||
]));
|
||||
|
||||
// once complete we want to apply the styles on the element
|
||||
// since the destination state's values should persist once
|
||||
// the animation sequence has completed.
|
||||
statements.push(
|
||||
_ANIMATION_PLAYER_VAR.callMethod('onDone', [
|
||||
o.fn([], [
|
||||
_ANIMATION_FACTORY_RENDERER_VAR.callMethod('setElementStyles', [
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR,
|
||||
o.importExpr(Identifiers.balanceAnimationStyles).callFn([
|
||||
_ANIMATION_START_STATE_STYLES_VAR,
|
||||
_ANIMATION_END_STATE_STYLES_VAR
|
||||
])
|
||||
]).toStmt()
|
||||
])
|
||||
]).toStmt());
|
||||
|
||||
statements.push(
|
||||
_ANIMATION_FACTORY_VIEW_VAR.callMethod('registerAndStartAnimation', [
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR,
|
||||
o.literal(this.animationName),
|
||||
_ANIMATION_PLAYER_VAR
|
||||
]).toStmt());
|
||||
|
||||
return o.fn([
|
||||
new o.FnParam(_ANIMATION_FACTORY_VIEW_VAR.name, o.importType(Identifiers.AppView)),
|
||||
new o.FnParam(_ANIMATION_FACTORY_ELEMENT_VAR.name, o.DYNAMIC_TYPE),
|
||||
new o.FnParam(_ANIMATION_CURRENT_STATE_VAR.name, o.DYNAMIC_TYPE),
|
||||
new o.FnParam(_ANIMATION_NEXT_STATE_VAR.name, o.DYNAMIC_TYPE)
|
||||
], statements);
|
||||
}
|
||||
|
||||
build(ast: AnimationAst): CompiledAnimation {
|
||||
var context = new _AnimationBuilderContext();
|
||||
var fnStatement = ast.visit(this, context).toDeclStmt(this._fnVarName);
|
||||
var fnVariable = o.variable(this._fnVarName);
|
||||
|
||||
var lookupMap = [];
|
||||
StringMapWrapper.forEach(context.stateMap.states, (value, stateName) => {
|
||||
var variableValue = EMPTY_MAP;
|
||||
if (isPresent(value)) {
|
||||
let styleMap = [];
|
||||
StringMapWrapper.forEach(value, (value, key) => {
|
||||
styleMap.push([key, o.literal(value)]);
|
||||
});
|
||||
variableValue = o.literalMap(styleMap);
|
||||
}
|
||||
lookupMap.push([stateName, variableValue]);
|
||||
});
|
||||
|
||||
var compiledStatesMapExpr = this._statesMapVar.set(o.literalMap(lookupMap)).toDeclStmt();
|
||||
return new CompiledAnimation(this.animationName,
|
||||
compiledStatesMapExpr,
|
||||
this._statesMapVarName,
|
||||
fnStatement,
|
||||
fnVariable);
|
||||
}
|
||||
}
|
||||
|
||||
class _AnimationBuilderContext {
|
||||
stateMap = new _AnimationBuilderStateMap();
|
||||
endStateAnimateStep: AnimationStepAst = null;
|
||||
isExpectingFirstStyleStep = false;
|
||||
}
|
||||
|
||||
class _AnimationBuilderStateMap {
|
||||
private _states: {[key: string]: {[prop: string]: string|number}} = {};
|
||||
get states() { return this._states; }
|
||||
registerState(name: string, value: {[prop: string]: string|number} = null): void {
|
||||
var existingEntry = this._states[name];
|
||||
if (isBlank(existingEntry)) {
|
||||
this._states[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _compareToAnimationStateExpr(value: o.Expression, animationState: string): o.Expression {
|
||||
var emptyStateLiteral = o.literal(EMPTY_STATE);
|
||||
switch (animationState) {
|
||||
case EMPTY_STATE:
|
||||
return value.equals(emptyStateLiteral);
|
||||
|
||||
case ANY_STATE:
|
||||
return o.literal(true);
|
||||
|
||||
default:
|
||||
return value.equals(o.literal(animationState));
|
||||
}
|
||||
}
|
||||
|
||||
function _isEndStateAnimateStep(step: AnimationAst): boolean {
|
||||
// the final animation step is characterized by having only TWO
|
||||
// keyframe values and it must have zero styles for both keyframes
|
||||
if (step instanceof AnimationStepAst
|
||||
&& step.duration > 0
|
||||
&& step.keyframes.length == 2) {
|
||||
var styles1 = _getStylesArray(step.keyframes[0])[0];
|
||||
var styles2 = _getStylesArray(step.keyframes[1])[0];
|
||||
return StringMapWrapper.isEmpty(styles1) && StringMapWrapper.isEmpty(styles2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function _getStylesArray(obj: any) {
|
||||
return obj.styles.styles;
|
||||
}
|
561
modules/@angular/compiler/src/animation/animation_parser.ts
Normal file
561
modules/@angular/compiler/src/animation/animation_parser.ts
Normal file
@ -0,0 +1,561 @@
|
||||
import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
||||
import {Math} from '../facade/math';
|
||||
import {ANY_STATE, EMPTY_STATE} from '../../core_private';
|
||||
import {
|
||||
IS_DART,
|
||||
RegExpWrapper,
|
||||
isArray,
|
||||
isPresent,
|
||||
isBlank,
|
||||
isNumber,
|
||||
isString,
|
||||
isStringMap,
|
||||
NumberWrapper
|
||||
} from '../facade/lang';
|
||||
|
||||
import {FILL_STYLE_FLAG} from '../../core_private';
|
||||
|
||||
import {
|
||||
CompileAnimationEntryMetadata,
|
||||
CompileAnimationStateMetadata,
|
||||
CompileAnimationStateDeclarationMetadata,
|
||||
CompileAnimationStateTransitionMetadata,
|
||||
CompileAnimationMetadata,
|
||||
CompileAnimationWithStepsMetadata,
|
||||
CompileAnimationStyleMetadata,
|
||||
CompileAnimationAnimateMetadata,
|
||||
CompileAnimationGroupMetadata,
|
||||
CompileAnimationSequenceMetadata,
|
||||
CompileAnimationKeyframesSequenceMetadata
|
||||
} from '../compile_metadata';
|
||||
|
||||
import {
|
||||
AnimationAst,
|
||||
AnimationEntryAst,
|
||||
AnimationStateAst,
|
||||
AnimationStateTransitionAst,
|
||||
AnimationStateDeclarationAst,
|
||||
AnimationKeyframeAst,
|
||||
AnimationStylesAst,
|
||||
AnimationWithStepsAst,
|
||||
AnimationSequenceAst,
|
||||
AnimationGroupAst,
|
||||
AnimationStepAst,
|
||||
AnimationStateTransitionExpression
|
||||
} from './animation_ast';
|
||||
|
||||
import {StylesCollection} from './styles_collection';
|
||||
import {ParseError} from '../parse_util';
|
||||
|
||||
const _INITIAL_KEYFRAME = 0;
|
||||
const _TERMINAL_KEYFRAME = 1;
|
||||
const _ONE_SECOND = 1000;
|
||||
|
||||
export class AnimationParseError extends ParseError {
|
||||
constructor(message) { super(null, message); }
|
||||
toString(): string { return `${this.msg}`; }
|
||||
}
|
||||
|
||||
export class ParsedAnimationResult {
|
||||
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
|
||||
}
|
||||
|
||||
export function parseAnimationEntry(entry: CompileAnimationEntryMetadata): ParsedAnimationResult {
|
||||
var errors: AnimationParseError[] = [];
|
||||
var stateStyles: {[key: string]: AnimationStylesAst} = {};
|
||||
var transitions: CompileAnimationStateTransitionMetadata[] = [];
|
||||
|
||||
var stateDeclarationAsts = [];
|
||||
entry.definitions.forEach(def => {
|
||||
if (def instanceof CompileAnimationStateDeclarationMetadata) {
|
||||
_parseAnimationDeclarationStates(def, errors).forEach(ast => {
|
||||
stateDeclarationAsts.push(ast);
|
||||
stateStyles[ast.stateName] = ast.styles;
|
||||
});
|
||||
} else {
|
||||
transitions.push(<CompileAnimationStateTransitionMetadata>def);
|
||||
}
|
||||
});
|
||||
|
||||
var stateTransitionAsts = transitions.map(transDef =>
|
||||
_parseAnimationStateTransition(transDef, stateStyles, errors));
|
||||
|
||||
var ast = new AnimationEntryAst(entry.name, stateDeclarationAsts, stateTransitionAsts);
|
||||
return new ParsedAnimationResult(ast, errors);
|
||||
}
|
||||
|
||||
function _parseAnimationDeclarationStates(stateMetadata: CompileAnimationStateDeclarationMetadata, errors: AnimationParseError[]): AnimationStateDeclarationAst[] {
|
||||
var styleValues: {[key: string]: string|number}[] = [];
|
||||
stateMetadata.styles.styles.forEach(stylesEntry => {
|
||||
// TODO (matsko): change this when we get CSS class integration support
|
||||
if (isStringMap(stylesEntry)) {
|
||||
styleValues.push(<{[key: string]: string|number}>stylesEntry);
|
||||
} else {
|
||||
errors.push(new AnimationParseError(`State based animations cannot contain references to other states`));
|
||||
}
|
||||
});
|
||||
var defStyles = new AnimationStylesAst(styleValues);
|
||||
|
||||
var states = stateMetadata.stateNameExpr.split(/\s*,\s*/);
|
||||
return states.map(state => new AnimationStateDeclarationAst(state, defStyles));
|
||||
}
|
||||
|
||||
function _parseAnimationStateTransition(transitionStateMetadata: CompileAnimationStateTransitionMetadata,
|
||||
stateStyles: {[key: string]: AnimationStylesAst},
|
||||
errors: AnimationParseError[]): AnimationStateTransitionAst {
|
||||
var styles = new StylesCollection();
|
||||
var transitionExprs = [];
|
||||
var transitionStates = transitionStateMetadata.stateChangeExpr.split(/\s*,\s*/);
|
||||
transitionStates.forEach(expr => {
|
||||
_parseAnimationTransitionExpr(expr, errors).forEach(transExpr => {
|
||||
transitionExprs.push(transExpr);
|
||||
});
|
||||
});
|
||||
var entry = _normalizeAnimationEntry(transitionStateMetadata.animation);
|
||||
var animation = _normalizeStyleSteps(entry, stateStyles, errors);
|
||||
var animationAst = _parseTransitionAnimation(animation, 0, styles, stateStyles, errors);
|
||||
if (errors.length == 0) {
|
||||
_fillAnimationAstStartingKeyframes(animationAst, styles, errors);
|
||||
}
|
||||
|
||||
var sequenceAst = (animationAst instanceof AnimationSequenceAst)
|
||||
? <AnimationSequenceAst>animationAst
|
||||
: new AnimationSequenceAst([animationAst]);
|
||||
|
||||
return new AnimationStateTransitionAst(transitionExprs, sequenceAst);
|
||||
}
|
||||
|
||||
function _parseAnimationTransitionExpr(eventStr: string, errors: AnimationParseError[]): AnimationStateTransitionExpression[] {
|
||||
var expressions = [];
|
||||
var match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/);
|
||||
if (!isPresent(match) || match.length < 4) {
|
||||
errors.push(new AnimationParseError(`the provided ${eventStr} is not of a supported format`));
|
||||
return expressions;
|
||||
}
|
||||
|
||||
var fromState = match[1];
|
||||
var separator = match[2];
|
||||
var toState = match[3];
|
||||
expressions.push(
|
||||
new AnimationStateTransitionExpression(fromState, toState));
|
||||
|
||||
var isFullAnyStateExpr = fromState == ANY_STATE && toState == ANY_STATE;
|
||||
if (separator[0] == '<' && !isFullAnyStateExpr) {
|
||||
expressions.push(
|
||||
new AnimationStateTransitionExpression(toState, fromState));
|
||||
}
|
||||
return expressions;
|
||||
}
|
||||
|
||||
function _fetchSylesFromState(stateName: string,
|
||||
stateStyles: {[key: string]: AnimationStylesAst}): CompileAnimationStyleMetadata {
|
||||
var entry = stateStyles[stateName];
|
||||
if (isPresent(entry)) {
|
||||
var styles = <{[key: string]: string | number}[]>entry.styles;
|
||||
return new CompileAnimationStyleMetadata(0, styles);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function _normalizeAnimationEntry(entry: CompileAnimationMetadata | CompileAnimationMetadata[])
|
||||
:CompileAnimationMetadata {
|
||||
return isArray(entry)
|
||||
? new CompileAnimationSequenceMetadata(<CompileAnimationMetadata[]>entry)
|
||||
: <CompileAnimationMetadata>entry;
|
||||
}
|
||||
|
||||
function _normalizeStyleMetadata(entry: CompileAnimationStyleMetadata,
|
||||
stateStyles: {[key: string]: AnimationStylesAst},
|
||||
errors: AnimationParseError[]): Array<{[key: string]: string|number}> {
|
||||
var normalizedStyles = [];
|
||||
entry.styles.forEach(styleEntry => {
|
||||
if (isString(styleEntry)) {
|
||||
ListWrapper.addAll(normalizedStyles, _resolveStylesFromState(<string>styleEntry, stateStyles, errors));
|
||||
} else {
|
||||
normalizedStyles.push(<{[key: string]: string | number}>styleEntry);
|
||||
}
|
||||
});
|
||||
return normalizedStyles;
|
||||
}
|
||||
|
||||
function _normalizeStyleSteps(entry: CompileAnimationMetadata,
|
||||
stateStyles: {[key: string]: AnimationStylesAst},
|
||||
errors: AnimationParseError[]): CompileAnimationMetadata {
|
||||
var steps = _normalizeStyleStepEntry(entry, stateStyles, errors);
|
||||
return new CompileAnimationSequenceMetadata(steps);
|
||||
}
|
||||
|
||||
function _mergeAnimationStyles(stylesList: any[], newItem: {[key: string]: string|number}|string) {
|
||||
if (isStringMap(newItem) && stylesList.length > 0) {
|
||||
var lastIndex = stylesList.length - 1;
|
||||
var lastItem = stylesList[lastIndex];
|
||||
if (isStringMap(lastItem)) {
|
||||
stylesList[lastIndex] = StringMapWrapper.merge(
|
||||
<{[key: string]: string|number}>lastItem,
|
||||
<{[key: string]: string|number}>newItem
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
stylesList.push(newItem);
|
||||
}
|
||||
|
||||
function _normalizeStyleStepEntry(entry: CompileAnimationMetadata,
|
||||
stateStyles: {[key: string]: AnimationStylesAst},
|
||||
errors: AnimationParseError[]): CompileAnimationMetadata[] {
|
||||
var steps: CompileAnimationMetadata[];
|
||||
if (entry instanceof CompileAnimationWithStepsMetadata) {
|
||||
steps = entry.steps;
|
||||
} else {
|
||||
return [entry];
|
||||
}
|
||||
|
||||
var newSteps: CompileAnimationMetadata[] = [];
|
||||
var combinedStyles: {[key: string]: string | number}[];
|
||||
steps.forEach(step => {
|
||||
if (step instanceof CompileAnimationStyleMetadata) {
|
||||
// this occurs when a style step is followed by a previous style step
|
||||
// or when the first style step is run. We want to concatenate all subsequent
|
||||
// style steps together into a single style step such that we have the correct
|
||||
// starting keyframe data to pass into the animation player.
|
||||
if (!isPresent(combinedStyles)) {
|
||||
combinedStyles = [];
|
||||
}
|
||||
_normalizeStyleMetadata(<CompileAnimationStyleMetadata>step, stateStyles, errors).forEach(entry => {
|
||||
_mergeAnimationStyles(combinedStyles, entry);
|
||||
});
|
||||
} else {
|
||||
// it is important that we create a metadata entry of the combined styles
|
||||
// before we go on an process the animate, sequence or group metadata steps.
|
||||
// This will ensure that the AST will have the previous styles painted on
|
||||
// screen before any further animations that use the styles take place.
|
||||
if (isPresent(combinedStyles)) {
|
||||
newSteps.push(new CompileAnimationStyleMetadata(0, combinedStyles));
|
||||
combinedStyles = null;
|
||||
}
|
||||
|
||||
if (step instanceof CompileAnimationAnimateMetadata) {
|
||||
// we do not recurse into CompileAnimationAnimateMetadata since
|
||||
// those style steps are not going to be squashed
|
||||
var animateStyleValue = (<CompileAnimationAnimateMetadata>step).styles;
|
||||
if (animateStyleValue instanceof CompileAnimationStyleMetadata) {
|
||||
animateStyleValue.styles = _normalizeStyleMetadata(animateStyleValue, stateStyles, errors);
|
||||
} else if (animateStyleValue instanceof CompileAnimationKeyframesSequenceMetadata) {
|
||||
animateStyleValue.steps.forEach(step => {
|
||||
step.styles = _normalizeStyleMetadata(step, stateStyles, errors);
|
||||
});
|
||||
}
|
||||
} else if (step instanceof CompileAnimationWithStepsMetadata) {
|
||||
let innerSteps = _normalizeStyleStepEntry(step, stateStyles, errors);
|
||||
step = step instanceof CompileAnimationGroupMetadata
|
||||
? new CompileAnimationGroupMetadata(innerSteps)
|
||||
: new CompileAnimationSequenceMetadata(innerSteps);
|
||||
}
|
||||
|
||||
newSteps.push(step);
|
||||
}
|
||||
});
|
||||
|
||||
// this happens when only styles were animated within the sequence
|
||||
if (isPresent(combinedStyles)) {
|
||||
newSteps.push(new CompileAnimationStyleMetadata(0, combinedStyles));
|
||||
}
|
||||
|
||||
return newSteps;
|
||||
}
|
||||
|
||||
|
||||
function _resolveStylesFromState(stateName: string, stateStyles: {[key: string]: AnimationStylesAst}, errors: AnimationParseError[]) {
|
||||
var styles: {[key: string]: string|number}[] = [];
|
||||
if (stateName[0] != ':') {
|
||||
errors.push(new AnimationParseError(`Animation states via styles must be prefixed with a ":"`));
|
||||
} else {
|
||||
var normalizedStateName = stateName.substring(1);
|
||||
var value = stateStyles[normalizedStateName];
|
||||
if (!isPresent(value)) {
|
||||
errors.push(new AnimationParseError(`Unable to apply styles due to missing a state: "${normalizedStateName}"`));
|
||||
} else {
|
||||
value.styles.forEach(stylesEntry => {
|
||||
if (isStringMap(stylesEntry)) {
|
||||
styles.push(<{[key: string]: string | number}>stylesEntry);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return styles;
|
||||
}
|
||||
|
||||
class _AnimationTimings {
|
||||
constructor(public duration: number, public delay: number, public easing: string) {}
|
||||
}
|
||||
|
||||
function _parseAnimationKeyframes(keyframeSequence: CompileAnimationKeyframesSequenceMetadata,
|
||||
currentTime: number,
|
||||
collectedStyles: StylesCollection,
|
||||
stateStyles: {[key: string]: AnimationStylesAst},
|
||||
errors: AnimationParseError[]): AnimationKeyframeAst[] {
|
||||
var totalEntries = keyframeSequence.steps.length;
|
||||
var totalOffsets = 0;
|
||||
keyframeSequence.steps.forEach(step => totalOffsets += (isPresent(step.offset) ? 1 : 0));
|
||||
|
||||
if (totalOffsets > 0 && totalOffsets < totalEntries) {
|
||||
errors.push(new AnimationParseError(`Not all style() entries contain an offset for the provided keyframe()`));
|
||||
totalOffsets = totalEntries;
|
||||
}
|
||||
|
||||
var limit = totalEntries - 1;
|
||||
var margin = totalOffsets == 0 ? (1 / limit) : 0;
|
||||
var rawKeyframes = [];
|
||||
var index = 0;
|
||||
var doSortKeyframes = false;
|
||||
var lastOffset = 0;
|
||||
keyframeSequence.steps.forEach(styleMetadata => {
|
||||
var offset = styleMetadata.offset;
|
||||
var keyframeStyles: {[key: string]: string|number} = {};
|
||||
styleMetadata.styles.forEach(entry => {
|
||||
StringMapWrapper.forEach(<{[key: string]: string|number}>entry, (value, prop) => {
|
||||
if (prop != 'offset') {
|
||||
keyframeStyles[prop] = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (isPresent(offset)) {
|
||||
doSortKeyframes = doSortKeyframes || (offset < lastOffset);
|
||||
} else {
|
||||
offset = index == limit ? _TERMINAL_KEYFRAME : (margin * index);
|
||||
}
|
||||
|
||||
rawKeyframes.push([offset, keyframeStyles]);
|
||||
lastOffset = offset;
|
||||
index++;
|
||||
});
|
||||
|
||||
if (doSortKeyframes) {
|
||||
ListWrapper.sort(rawKeyframes, (a,b) => a[0] <= b[0] ? -1 : 1);
|
||||
}
|
||||
|
||||
var i;
|
||||
var firstKeyframe = rawKeyframes[0];
|
||||
if (firstKeyframe[0] != _INITIAL_KEYFRAME) {
|
||||
ListWrapper.insert(rawKeyframes, 0, firstKeyframe = [_INITIAL_KEYFRAME, {}]);
|
||||
}
|
||||
|
||||
var firstKeyframeStyles = firstKeyframe[1];
|
||||
var limit = rawKeyframes.length - 1;
|
||||
var lastKeyframe = rawKeyframes[limit];
|
||||
if (lastKeyframe[0] != _TERMINAL_KEYFRAME) {
|
||||
rawKeyframes.push(lastKeyframe = [_TERMINAL_KEYFRAME, {}]);
|
||||
limit++;
|
||||
}
|
||||
|
||||
var lastKeyframeStyles = lastKeyframe[1];
|
||||
for (i = 1; i <= limit; i++) {
|
||||
let entry = rawKeyframes[i];
|
||||
let styles = entry[1];
|
||||
|
||||
StringMapWrapper.forEach(styles, (value, prop) => {
|
||||
if (!isPresent(firstKeyframeStyles[prop])) {
|
||||
firstKeyframeStyles[prop] = FILL_STYLE_FLAG;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (i = limit - 1; i >= 0; i--) {
|
||||
let entry = rawKeyframes[i];
|
||||
let styles = entry[1];
|
||||
|
||||
StringMapWrapper.forEach(styles, (value, prop) => {
|
||||
if (!isPresent(lastKeyframeStyles[prop])) {
|
||||
lastKeyframeStyles[prop] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return rawKeyframes.map(entry => new AnimationKeyframeAst(entry[0], new AnimationStylesAst([entry[1]])));
|
||||
}
|
||||
|
||||
function _parseTransitionAnimation(entry: CompileAnimationMetadata,
|
||||
currentTime: number,
|
||||
collectedStyles: StylesCollection,
|
||||
stateStyles: {[key: string]: AnimationStylesAst},
|
||||
errors: AnimationParseError[]): AnimationAst {
|
||||
var ast;
|
||||
var playTime = 0;
|
||||
var startingTime = currentTime;
|
||||
if (entry instanceof CompileAnimationWithStepsMetadata) {
|
||||
var maxDuration = 0;
|
||||
var steps = [];
|
||||
var isGroup = entry instanceof CompileAnimationGroupMetadata;
|
||||
var previousStyles;
|
||||
entry.steps.forEach(entry => {
|
||||
// these will get picked up by the next step...
|
||||
var time = isGroup ? startingTime : currentTime;
|
||||
if (entry instanceof CompileAnimationStyleMetadata) {
|
||||
entry.styles.forEach(stylesEntry => {
|
||||
// by this point we know that we only have stringmap values
|
||||
var map = <{[key: string]: string|number}>stylesEntry;
|
||||
StringMapWrapper.forEach(map, (value, prop) => {
|
||||
collectedStyles.insertAtTime(prop, time, value);
|
||||
});
|
||||
});
|
||||
previousStyles = entry.styles;
|
||||
return;
|
||||
}
|
||||
|
||||
var innerAst = _parseTransitionAnimation(entry, time, collectedStyles, stateStyles, errors);
|
||||
if (isPresent(previousStyles)) {
|
||||
if (entry instanceof CompileAnimationWithStepsMetadata) {
|
||||
let startingStyles = new AnimationStylesAst(previousStyles);
|
||||
steps.push(new AnimationStepAst(startingStyles, [], 0, 0, ''));
|
||||
} else {
|
||||
var innerStep = <AnimationStepAst>innerAst;
|
||||
ListWrapper.addAll(innerStep.startingStyles.styles, previousStyles);
|
||||
}
|
||||
previousStyles = null;
|
||||
}
|
||||
|
||||
var astDuration = innerAst.playTime;
|
||||
currentTime += astDuration;
|
||||
playTime += astDuration;
|
||||
maxDuration = Math.max(astDuration, maxDuration);
|
||||
steps.push(innerAst);
|
||||
});
|
||||
if (isPresent(previousStyles)) {
|
||||
let startingStyles = new AnimationStylesAst(previousStyles);
|
||||
steps.push(new AnimationStepAst(startingStyles, [], 0, 0, ''));
|
||||
}
|
||||
if (isGroup) {
|
||||
ast = new AnimationGroupAst(steps);
|
||||
playTime = maxDuration;
|
||||
currentTime = startingTime + playTime;
|
||||
} else {
|
||||
ast = new AnimationSequenceAst(steps);
|
||||
}
|
||||
} else if (entry instanceof CompileAnimationAnimateMetadata) {
|
||||
var timings = _parseTimeExpression(entry.timings, errors);
|
||||
var styles = entry.styles;
|
||||
|
||||
var keyframes;
|
||||
if (styles instanceof CompileAnimationKeyframesSequenceMetadata) {
|
||||
keyframes = _parseAnimationKeyframes(styles, currentTime, collectedStyles, stateStyles, errors);
|
||||
} else {
|
||||
let styleData = <CompileAnimationStyleMetadata>styles;
|
||||
let offset = _TERMINAL_KEYFRAME;
|
||||
let styleAst = new AnimationStylesAst(<{[key: string]: string|number}[]>styleData.styles);
|
||||
var keyframe = new AnimationKeyframeAst(offset, styleAst);
|
||||
keyframes = [keyframe];
|
||||
}
|
||||
|
||||
ast = new AnimationStepAst(new AnimationStylesAst([]), keyframes, timings.duration, timings.delay, timings.easing);
|
||||
playTime = timings.duration + timings.delay;
|
||||
currentTime += playTime;
|
||||
|
||||
keyframes.forEach(keyframe =>
|
||||
keyframe.styles.styles.forEach(entry =>
|
||||
StringMapWrapper.forEach(entry, (value, prop) =>
|
||||
collectedStyles.insertAtTime(prop, currentTime, value))
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// if the code reaches this stage then an error
|
||||
// has already been populated within the _normalizeStyleSteps()
|
||||
// operation...
|
||||
ast = new AnimationStepAst(null, [], 0, 0, '');
|
||||
}
|
||||
|
||||
ast.playTime = playTime;
|
||||
ast.startTime = startingTime;
|
||||
return ast;
|
||||
}
|
||||
|
||||
function _fillAnimationAstStartingKeyframes(ast: AnimationAst, collectedStyles: StylesCollection,
|
||||
errors: AnimationParseError[]): void {
|
||||
// steps that only contain style will not be filled
|
||||
if ((ast instanceof AnimationStepAst) && ast.keyframes.length > 0) {
|
||||
var keyframes = ast.keyframes;
|
||||
if (keyframes.length == 1) {
|
||||
var endKeyframe = keyframes[0];
|
||||
var startKeyframe = _createStartKeyframeFromEndKeyframe(endKeyframe, ast.startTime,
|
||||
ast.playTime, collectedStyles, errors);
|
||||
ast.keyframes = [startKeyframe, endKeyframe];
|
||||
}
|
||||
} else if (ast instanceof AnimationWithStepsAst) {
|
||||
ast.steps.forEach(entry => _fillAnimationAstStartingKeyframes(entry, collectedStyles, errors));
|
||||
}
|
||||
}
|
||||
|
||||
function _parseTimeExpression(exp: string | number,
|
||||
errors: AnimationParseError[]): _AnimationTimings {
|
||||
var regex = /^([\.\d]+)(m?s)(?:\s+([\.\d]+)(m?s))?(?:\s+([-a-z]+(?:\(.+?\))?))?/gi;
|
||||
var duration: number;
|
||||
var delay: number = 0;
|
||||
var easing: string = null;
|
||||
if (isString(exp)) {
|
||||
var matches = RegExpWrapper.firstMatch(regex, <string>exp);
|
||||
if (!isPresent(matches)) {
|
||||
errors.push(new AnimationParseError(`The provided timing value "${exp}" is invalid.`));
|
||||
return new _AnimationTimings(0, 0, null);
|
||||
}
|
||||
|
||||
var durationMatch = NumberWrapper.parseFloat(matches[1]);
|
||||
var durationUnit = matches[2];
|
||||
if (durationUnit == 's') {
|
||||
durationMatch *= _ONE_SECOND;
|
||||
}
|
||||
duration = Math.floor(durationMatch);
|
||||
|
||||
var delayMatch = matches[3];
|
||||
var delayUnit = matches[4];
|
||||
if (isPresent(delayMatch)) {
|
||||
var delayVal: number = NumberWrapper.parseFloat(delayMatch);
|
||||
if (isPresent(delayUnit) && delayUnit == 's') {
|
||||
delayVal *= _ONE_SECOND;
|
||||
}
|
||||
delay = Math.floor(delayVal);
|
||||
}
|
||||
|
||||
var easingVal = matches[5];
|
||||
if (!isBlank(easingVal)) {
|
||||
easing = easingVal;
|
||||
}
|
||||
} else {
|
||||
duration = <number>exp;
|
||||
}
|
||||
|
||||
return new _AnimationTimings(duration, delay, easing);
|
||||
}
|
||||
|
||||
function _createStartKeyframeFromEndKeyframe(endKeyframe: AnimationKeyframeAst, startTime: number,
|
||||
duration: number, collectedStyles: StylesCollection,
|
||||
errors: AnimationParseError[]): AnimationKeyframeAst {
|
||||
var values: {[key: string]: string | number} = {};
|
||||
var endTime = startTime + duration;
|
||||
endKeyframe.styles.styles.forEach((styleData: {[key: string]: string|number}) => {
|
||||
StringMapWrapper.forEach(styleData, (val, prop) => {
|
||||
if (prop == 'offset') return;
|
||||
|
||||
var resultIndex = collectedStyles.indexOfAtOrBeforeTime(prop, startTime);
|
||||
var resultEntry, nextEntry, value;
|
||||
if (isPresent(resultIndex)) {
|
||||
resultEntry = collectedStyles.getByIndex(prop, resultIndex);
|
||||
value = resultEntry.value;
|
||||
nextEntry = collectedStyles.getByIndex(prop, resultIndex + 1);
|
||||
} else {
|
||||
// this is a flag that the runtime code uses to pass
|
||||
// in a value either from the state declaration styles
|
||||
// or using the AUTO_STYLE value (e.g. getComputedStyle)
|
||||
value = FILL_STYLE_FLAG;
|
||||
}
|
||||
|
||||
if (isPresent(nextEntry) && !nextEntry.matches(endTime, val)) {
|
||||
errors.push(new AnimationParseError(
|
||||
`The animated CSS property "${prop}" unexpectedly changes between steps "${resultEntry.time}ms" and "${endTime}ms" at "${nextEntry.time}ms"`));
|
||||
}
|
||||
|
||||
values[prop] = value;
|
||||
});
|
||||
});
|
||||
|
||||
return new AnimationKeyframeAst(_INITIAL_KEYFRAME, new AnimationStylesAst([values]));
|
||||
}
|
52
modules/@angular/compiler/src/animation/styles_collection.ts
Normal file
52
modules/@angular/compiler/src/animation/styles_collection.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
|
||||
export class StylesCollectionEntry {
|
||||
constructor(public time: number, public value: string | number) {}
|
||||
|
||||
matches(time: number, value: string | number): boolean {
|
||||
return time == this.time && value == this.value;
|
||||
}
|
||||
}
|
||||
|
||||
export class StylesCollection {
|
||||
styles: {[key: string]: StylesCollectionEntry[]} = {};
|
||||
|
||||
insertAtTime(property: string, time: number, value: string | number) {
|
||||
var tuple = new StylesCollectionEntry(time, value);
|
||||
var entries = this.styles[property];
|
||||
if (!isPresent(entries)) {
|
||||
entries = this.styles[property] = [];
|
||||
}
|
||||
|
||||
// insert this at the right stop in the array
|
||||
// this way we can keep it sorted
|
||||
var insertionIndex = 0;
|
||||
for (var i = entries.length - 1; i >= 0; i--) {
|
||||
if (entries[i].time <= time) {
|
||||
insertionIndex = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ListWrapper.insert(entries, insertionIndex, tuple);
|
||||
}
|
||||
|
||||
getByIndex(property: string, index: number): StylesCollectionEntry {
|
||||
var items = this.styles[property];
|
||||
if (isPresent(items)) {
|
||||
return index >= items.length ? null : items[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
indexOfAtOrBeforeTime(property: string, time: number): number {
|
||||
var entries = this.styles[property];
|
||||
if (isPresent(entries)) {
|
||||
for (var i = entries.length - 1; i >= 0; i--) {
|
||||
if (entries[i].time <= time) return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -17,17 +17,16 @@ import {
|
||||
Type,
|
||||
isString,
|
||||
RegExpWrapper,
|
||||
StringWrapper,
|
||||
NumberWrapper,
|
||||
isArray
|
||||
} from '../src/facade/lang';
|
||||
import {unimplemented, BaseException} from '../src/facade/exceptions';
|
||||
import {
|
||||
StringMapWrapper,
|
||||
} from '../src/facade/collection';
|
||||
import {StringMapWrapper, ListWrapper} from '../src/facade/collection';
|
||||
import {CssSelector} from './selector';
|
||||
import {splitAtColon, sanitizeIdentifier} from './util';
|
||||
import {getUrlScheme} from './url_resolver';
|
||||
|
||||
// group 1: "property" from "[property]"
|
||||
// group 2: "event" from "(event)"
|
||||
var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g;
|
||||
|
||||
@ -49,6 +48,174 @@ export function metadataFromJson(data: {[key: string]: any}): any {
|
||||
return _COMPILE_METADATA_FROM_JSON[data['class']](data);
|
||||
}
|
||||
|
||||
export class CompileAnimationEntryMetadata {
|
||||
static fromJson(data: {[key: string]: any}): CompileAnimationEntryMetadata {
|
||||
var value = data['value'];
|
||||
var defs = _arrayFromJson(value['definitions'], metadataFromJson);
|
||||
return new CompileAnimationEntryMetadata(value['name'], defs);
|
||||
}
|
||||
|
||||
constructor(public name: string = null, public definitions: CompileAnimationStateMetadata[] = null) {}
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
'class': 'AnimationEntryMetadata',
|
||||
'value': {
|
||||
'name' : this.name,
|
||||
'definitions': _arrayToJson(this.definitions)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class CompileAnimationStateMetadata {}
|
||||
|
||||
export class CompileAnimationStateDeclarationMetadata extends CompileAnimationStateMetadata {
|
||||
static fromJson(data: {[key: string]: any}): CompileAnimationStateDeclarationMetadata {
|
||||
var value = data['value'];
|
||||
var styles = _objFromJson(value['styles'], metadataFromJson);
|
||||
return new CompileAnimationStateDeclarationMetadata(value['stateNameExpr'], styles);
|
||||
}
|
||||
|
||||
constructor(public stateNameExpr: string, public styles: CompileAnimationStyleMetadata) { super(); }
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
'class': 'AnimationStateDeclarationMetadata',
|
||||
'value': {
|
||||
'stateNameExpr': this.stateNameExpr,
|
||||
'styles': this.styles.toJson()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CompileAnimationStateTransitionMetadata extends CompileAnimationStateMetadata {
|
||||
static fromJson(data: {[key: string]: any}): CompileAnimationStateTransitionMetadata {
|
||||
var value = data['value'];
|
||||
var animation = _objFromJson(value['animation'], metadataFromJson);
|
||||
return new CompileAnimationStateTransitionMetadata(value['stateChangeExpr'], animation);
|
||||
}
|
||||
|
||||
constructor(public stateChangeExpr: string, public animation: CompileAnimationMetadata) { super(); }
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
'class': 'AnimationStateTransitionMetadata',
|
||||
'value': {
|
||||
'stateChangeExpr': this.stateChangeExpr,
|
||||
'animation': this.animation.toJson()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class CompileAnimationMetadata {
|
||||
abstract toJson(): {[key: string]: any};
|
||||
}
|
||||
|
||||
export class CompileAnimationKeyframesSequenceMetadata extends CompileAnimationMetadata {
|
||||
static fromJson(data: {[key: string]: any}): CompileAnimationKeyframesSequenceMetadata {
|
||||
var steps = _arrayFromJson(data['value'], metadataFromJson);
|
||||
return new CompileAnimationKeyframesSequenceMetadata(<CompileAnimationStyleMetadata[]>steps);
|
||||
}
|
||||
|
||||
constructor(public steps: CompileAnimationStyleMetadata[] = []) {
|
||||
super();
|
||||
}
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
'class': 'AnimationKeyframesSequenceMetadata',
|
||||
'value': _arrayToJson(this.steps)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CompileAnimationStyleMetadata extends CompileAnimationMetadata {
|
||||
static fromJson(data: {[key: string]: any}): CompileAnimationStyleMetadata {
|
||||
var value = data['value'];
|
||||
var offsetVal = value['offset'];
|
||||
var offset = isPresent(offsetVal) ? NumberWrapper.parseFloat(offsetVal) : null;
|
||||
var styles = <Array<string|{[key: string]: string | number}>>value['styles'];
|
||||
return new CompileAnimationStyleMetadata(offset, styles);
|
||||
}
|
||||
|
||||
constructor(public offset: number, public styles: Array<string|{[key: string]: string | number}> = null) { super(); }
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
'class': 'AnimationStyleMetadata',
|
||||
'value': {
|
||||
'offset': this.offset,
|
||||
'styles': this.styles
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CompileAnimationAnimateMetadata extends CompileAnimationMetadata {
|
||||
static fromJson(data: {[key: string]: any}): CompileAnimationAnimateMetadata {
|
||||
var value = data['value'];
|
||||
var timings = <string|number>value['timings'];
|
||||
var styles = _objFromJson(value['styles'], metadataFromJson);
|
||||
return new CompileAnimationAnimateMetadata(timings, styles);
|
||||
}
|
||||
|
||||
constructor(public timings: string|number = 0,
|
||||
public styles: CompileAnimationStyleMetadata|CompileAnimationKeyframesSequenceMetadata = null) { super(); }
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
'class': 'AnimationAnimateMetadata',
|
||||
'value': {
|
||||
'timings': this.timings,
|
||||
'styles': _objToJson(this.styles)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class CompileAnimationWithStepsMetadata extends CompileAnimationMetadata {
|
||||
constructor(public steps: CompileAnimationMetadata[] = null) { super(); }
|
||||
}
|
||||
|
||||
export class CompileAnimationSequenceMetadata extends CompileAnimationWithStepsMetadata {
|
||||
static fromJson(data: {[key: string]: any}): CompileAnimationSequenceMetadata {
|
||||
var steps = _arrayFromJson(data['value'], metadataFromJson);
|
||||
return new CompileAnimationSequenceMetadata(steps);
|
||||
}
|
||||
|
||||
constructor(steps: CompileAnimationMetadata[] = null) {
|
||||
super(steps);
|
||||
}
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
'class': 'AnimationSequenceMetadata',
|
||||
'value': _arrayToJson(this.steps)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CompileAnimationGroupMetadata extends CompileAnimationWithStepsMetadata {
|
||||
static fromJson(data: {[key: string]: any}): CompileAnimationGroupMetadata {
|
||||
var steps = _arrayFromJson(data["value"], metadataFromJson);
|
||||
return new CompileAnimationGroupMetadata(steps);
|
||||
}
|
||||
|
||||
constructor(steps: CompileAnimationMetadata[] = null) {
|
||||
super(steps);
|
||||
}
|
||||
|
||||
toJson(): {[key: string]: any} {
|
||||
return {
|
||||
'class': 'AnimationGroupMetadata',
|
||||
'value': _arrayToJson(this.steps)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier {
|
||||
runtime: any;
|
||||
name: string;
|
||||
@ -477,24 +644,28 @@ export class CompileTemplateMetadata {
|
||||
templateUrl: string;
|
||||
styles: string[];
|
||||
styleUrls: string[];
|
||||
animations: CompileAnimationEntryMetadata[];
|
||||
ngContentSelectors: string[];
|
||||
constructor({encapsulation, template, templateUrl, styles, styleUrls, ngContentSelectors}: {
|
||||
constructor({encapsulation, template, templateUrl, styles, styleUrls, animations, ngContentSelectors}: {
|
||||
encapsulation?: ViewEncapsulation,
|
||||
template?: string,
|
||||
templateUrl?: string,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
ngContentSelectors?: string[]
|
||||
ngContentSelectors?: string[],
|
||||
animations?: CompileAnimationEntryMetadata[]
|
||||
} = {}) {
|
||||
this.encapsulation = isPresent(encapsulation) ? encapsulation : ViewEncapsulation.Emulated;
|
||||
this.template = template;
|
||||
this.templateUrl = templateUrl;
|
||||
this.styles = isPresent(styles) ? styles : [];
|
||||
this.styleUrls = isPresent(styleUrls) ? styleUrls : [];
|
||||
this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : [];
|
||||
this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : [];
|
||||
}
|
||||
|
||||
static fromJson(data: {[key: string]: any}): CompileTemplateMetadata {
|
||||
var animations = <CompileAnimationEntryMetadata[]>_arrayFromJson(data['animations'], metadataFromJson);
|
||||
return new CompileTemplateMetadata({
|
||||
encapsulation: isPresent(data['encapsulation']) ?
|
||||
VIEW_ENCAPSULATION_VALUES[data['encapsulation']] :
|
||||
@ -503,6 +674,7 @@ export class CompileTemplateMetadata {
|
||||
templateUrl: data['templateUrl'],
|
||||
styles: data['styles'],
|
||||
styleUrls: data['styleUrls'],
|
||||
animations: animations,
|
||||
ngContentSelectors: data['ngContentSelectors']
|
||||
});
|
||||
}
|
||||
@ -515,6 +687,7 @@ export class CompileTemplateMetadata {
|
||||
'templateUrl': this.templateUrl,
|
||||
'styles': this.styles,
|
||||
'styleUrls': this.styleUrls,
|
||||
'animations': _objToJson(this.animations),
|
||||
'ngContentSelectors': this.ngContentSelectors
|
||||
};
|
||||
}
|
||||
@ -718,7 +891,7 @@ export function createHostComponentMeta(componentType: CompileTypeMetadata,
|
||||
isHost: true
|
||||
}),
|
||||
template: new CompileTemplateMetadata(
|
||||
{template: template, templateUrl: '', styles: [], styleUrls: [], ngContentSelectors: []}),
|
||||
{template: template, templateUrl: '', styles: [], styleUrls: [], ngContentSelectors: [], animations:[]}),
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
@ -777,7 +950,15 @@ var _COMPILE_METADATA_FROM_JSON = {
|
||||
'Type': CompileTypeMetadata.fromJson,
|
||||
'Provider': CompileProviderMetadata.fromJson,
|
||||
'Identifier': CompileIdentifierMetadata.fromJson,
|
||||
'Factory': CompileFactoryMetadata.fromJson
|
||||
'Factory': CompileFactoryMetadata.fromJson,
|
||||
'AnimationEntryMetadata': CompileAnimationEntryMetadata.fromJson,
|
||||
'AnimationStateDeclarationMetadata': CompileAnimationStateDeclarationMetadata.fromJson,
|
||||
'AnimationStateTransitionMetadata': CompileAnimationStateTransitionMetadata.fromJson,
|
||||
'AnimationSequenceMetadata': CompileAnimationSequenceMetadata.fromJson,
|
||||
'AnimationGroupMetadata': CompileAnimationGroupMetadata.fromJson,
|
||||
'AnimationAnimateMetadata': CompileAnimationAnimateMetadata.fromJson,
|
||||
'AnimationStyleMetadata': CompileAnimationStyleMetadata.fromJson,
|
||||
'AnimationKeyframesSequenceMetadata': CompileAnimationKeyframesSequenceMetadata.fromJson
|
||||
};
|
||||
|
||||
function _arrayFromJson(obj: any[], fn: (a: {[key: string]: any}) => any): any {
|
||||
|
@ -109,7 +109,8 @@ export class DirectiveNormalizer {
|
||||
templateUrl: templateAbsUrl,
|
||||
styles: allResolvedStyles,
|
||||
styleUrls: allStyleAbsUrls,
|
||||
ngContentSelectors: visitor.ngContentSelectors
|
||||
ngContentSelectors: visitor.ngContentSelectors,
|
||||
animations: templateMeta.animations
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,13 @@ import {
|
||||
pureProxy7,
|
||||
pureProxy8,
|
||||
pureProxy9,
|
||||
pureProxy10
|
||||
pureProxy10,
|
||||
AnimationKeyframe as AnimationKeyframe_,
|
||||
AnimationStyles as AnimationStyles_,
|
||||
NoOpAnimationPlayer as NoOpAnimationPlayer_,
|
||||
AnimationGroupPlayer as AnimationGroupPlayer_,
|
||||
AnimationSequencePlayer as AnimationSequencePlayer_,
|
||||
AnimationStyleUtil
|
||||
} from '../core_private';
|
||||
|
||||
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
|
||||
@ -83,6 +89,13 @@ var impCheckBinding = checkBinding;
|
||||
var impCastByValue = castByValue;
|
||||
var impEMPTY_ARRAY = EMPTY_ARRAY;
|
||||
var impEMPTY_MAP = EMPTY_MAP;
|
||||
var impAnimationGroupPlayer = AnimationGroupPlayer_;
|
||||
var impAnimationSequencePlayer = AnimationSequencePlayer_;
|
||||
var impAnimationKeyframe = AnimationKeyframe_;
|
||||
var impAnimationStyles = AnimationStyles_;
|
||||
var impNoOpAnimationPlayer = NoOpAnimationPlayer_;
|
||||
|
||||
var ANIMATION_STYLE_UTIL_ASSET_URL = assetUrl('core','animation/animation_style_util');
|
||||
|
||||
export class Identifiers {
|
||||
static ViewUtils = new CompileIdentifierMetadata(
|
||||
@ -205,6 +218,51 @@ export class Identifiers {
|
||||
moduleUrl: assetUrl('core', 'security'),
|
||||
runtime: SecurityContext,
|
||||
});
|
||||
static AnimationKeyframe = new CompileIdentifierMetadata({
|
||||
name: 'AnimationKeyframe',
|
||||
moduleUrl: assetUrl('core','animation/animation_keyframe'),
|
||||
runtime: impAnimationKeyframe
|
||||
});
|
||||
static AnimationStyles = new CompileIdentifierMetadata({
|
||||
name: 'AnimationStyles',
|
||||
moduleUrl: assetUrl('core','animation/animation_styles'),
|
||||
runtime: impAnimationStyles
|
||||
});
|
||||
static NoOpAnimationPlayer = new CompileIdentifierMetadata({
|
||||
name: 'NoOpAnimationPlayer',
|
||||
moduleUrl: assetUrl('core','animation/animation_player'),
|
||||
runtime: impNoOpAnimationPlayer
|
||||
});
|
||||
static AnimationGroupPlayer = new CompileIdentifierMetadata({
|
||||
name: 'AnimationGroupPlayer',
|
||||
moduleUrl: assetUrl('core','animation/animation_group_player'),
|
||||
runtime: impAnimationGroupPlayer
|
||||
});
|
||||
static AnimationSequencePlayer = new CompileIdentifierMetadata({
|
||||
name: 'AnimationSequencePlayer',
|
||||
moduleUrl: assetUrl('core','animation/animation_sequence_player'),
|
||||
runtime: impAnimationSequencePlayer
|
||||
});
|
||||
static balanceAnimationStyles = new CompileIdentifierMetadata({
|
||||
name: 'balanceAnimationStyles',
|
||||
moduleUrl: ANIMATION_STYLE_UTIL_ASSET_URL,
|
||||
runtime: AnimationStyleUtil.balanceStyles
|
||||
});
|
||||
static balanceAnimationKeyframes = new CompileIdentifierMetadata({
|
||||
name: 'balanceAnimationKeyframes',
|
||||
moduleUrl: ANIMATION_STYLE_UTIL_ASSET_URL,
|
||||
runtime: AnimationStyleUtil.balanceKeyframes
|
||||
});
|
||||
static clearAnimationStyles = new CompileIdentifierMetadata({
|
||||
name: 'clearAnimationStyles',
|
||||
moduleUrl: ANIMATION_STYLE_UTIL_ASSET_URL,
|
||||
runtime: AnimationStyleUtil.clearStyles
|
||||
});
|
||||
static collectAndResolveStyles = new CompileIdentifierMetadata({
|
||||
name: 'collectAndResolveStyles',
|
||||
moduleUrl: ANIMATION_STYLE_UTIL_ASSET_URL,
|
||||
runtime: AnimationStyleUtil.collectAndResolveStyles
|
||||
});
|
||||
}
|
||||
|
||||
export function identifierToken(identifier: CompileIdentifierMetadata): CompileTokenMetadata {
|
||||
|
@ -33,6 +33,7 @@ import {
|
||||
import {StringMapWrapper} from '../src/facade/collection';
|
||||
import {BaseException} from '../src/facade/exceptions';
|
||||
import * as cpl from './compile_metadata';
|
||||
import * as anmd from '@angular/core';
|
||||
import {DirectiveResolver} from './directive_resolver';
|
||||
import {PipeResolver} from './pipe_resolver';
|
||||
import {ViewResolver} from './view_resolver';
|
||||
@ -77,6 +78,44 @@ export class CompileMetadataResolver {
|
||||
return sanitizeIdentifier(identifier);
|
||||
}
|
||||
|
||||
getAnimationEntryMetadata(entry: anmd.AnimationEntryMetadata): cpl.CompileAnimationEntryMetadata {
|
||||
var defs = entry.definitions.map(def => this.getAnimationStateMetadata(def));
|
||||
return new cpl.CompileAnimationEntryMetadata(entry.name, defs);
|
||||
}
|
||||
|
||||
getAnimationStateMetadata(value: anmd.AnimationStateMetadata): cpl.CompileAnimationStateMetadata {
|
||||
if (value instanceof anmd.AnimationStateDeclarationMetadata) {
|
||||
var styles = this.getAnimationStyleMetadata(value.styles);
|
||||
return new cpl.CompileAnimationStateDeclarationMetadata(value.stateNameExpr, styles);
|
||||
} else if (value instanceof anmd.AnimationStateTransitionMetadata) {
|
||||
return new cpl.CompileAnimationStateTransitionMetadata(value.stateChangeExpr, this.getAnimationMetadata(value.animation));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getAnimationStyleMetadata(value: anmd.AnimationStyleMetadata): cpl.CompileAnimationStyleMetadata {
|
||||
return new cpl.CompileAnimationStyleMetadata(value.offset, value.styles);
|
||||
}
|
||||
|
||||
getAnimationMetadata(value: anmd.AnimationMetadata): cpl.CompileAnimationMetadata {
|
||||
if (value instanceof anmd.AnimationStyleMetadata) {
|
||||
return this.getAnimationStyleMetadata(value);
|
||||
} else if (value instanceof anmd.AnimationKeyframesSequenceMetadata) {
|
||||
return new cpl.CompileAnimationKeyframesSequenceMetadata(value.steps.map(entry => this.getAnimationStyleMetadata(entry)));
|
||||
} else if (value instanceof anmd.AnimationAnimateMetadata) {
|
||||
let animateData = <cpl.CompileAnimationStyleMetadata|cpl.CompileAnimationKeyframesSequenceMetadata>this.getAnimationMetadata(value.styles);
|
||||
return new cpl.CompileAnimationAnimateMetadata(value.timings, animateData);
|
||||
} else if (value instanceof anmd.AnimationWithStepsMetadata) {
|
||||
var steps = value.steps.map(step => this.getAnimationMetadata(step));
|
||||
if (value instanceof anmd.AnimationGroupMetadata) {
|
||||
return new cpl.CompileAnimationGroupMetadata(steps);
|
||||
} else {
|
||||
return new cpl.CompileAnimationSequenceMetadata(steps);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getDirectiveMetadata(directiveType: Type): cpl.CompileDirectiveMetadata {
|
||||
var meta = this._directiveCache.get(directiveType);
|
||||
if (isBlank(meta)) {
|
||||
@ -90,12 +129,17 @@ export class CompileMetadataResolver {
|
||||
var cmpMeta = <ComponentMetadata>dirMeta;
|
||||
var viewMeta = this._viewResolver.resolve(directiveType);
|
||||
assertArrayOfStrings('styles', viewMeta.styles);
|
||||
var animations = isPresent(viewMeta.animations)
|
||||
? viewMeta.animations.map(e => this.getAnimationEntryMetadata(e))
|
||||
: null;
|
||||
|
||||
templateMeta = new cpl.CompileTemplateMetadata({
|
||||
encapsulation: viewMeta.encapsulation,
|
||||
template: viewMeta.template,
|
||||
templateUrl: viewMeta.templateUrl,
|
||||
styles: viewMeta.styles,
|
||||
styleUrls: viewMeta.styleUrls
|
||||
styleUrls: viewMeta.styleUrls,
|
||||
animations: animations
|
||||
});
|
||||
changeDetectionStrategy = cmpMeta.changeDetection;
|
||||
if (isPresent(dirMeta.viewProviders)) {
|
||||
|
@ -168,4 +168,4 @@ function _splitSuffix(path: string): string[] {
|
||||
} else {
|
||||
return [path, ''];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,14 @@ class _InterpretiveAppView extends DebugAppView<any> implements DynamicInstance
|
||||
return super.injectorGet(token, nodeIndex, notFoundResult);
|
||||
}
|
||||
}
|
||||
detachInternal(): void {
|
||||
var m = this.methods.get('detachInternal');
|
||||
if (isPresent(m)) {
|
||||
return m();
|
||||
} else {
|
||||
return super.detachInternal();
|
||||
}
|
||||
}
|
||||
destroyInternal(): void {
|
||||
var m = this.methods.get('destroyInternal');
|
||||
if (isPresent(m)) {
|
||||
|
@ -103,8 +103,7 @@ export class RuntimeCompiler implements ComponentResolver {
|
||||
|
||||
var childPromises = [];
|
||||
compiledTemplate.init(this._compileComponent(compMeta, parsedTemplate, styles,
|
||||
pipes, compilingComponentsPath,
|
||||
childPromises));
|
||||
pipes, compilingComponentsPath, childPromises));
|
||||
return PromiseWrapper.all(childPromises).then((_) => { return compiledTemplate; });
|
||||
});
|
||||
this._compiledTemplateDone.set(cacheKey, done);
|
||||
@ -113,7 +112,8 @@ export class RuntimeCompiler implements ComponentResolver {
|
||||
}
|
||||
|
||||
private _compileComponent(compMeta: CompileDirectiveMetadata, parsedTemplate: TemplateAst[],
|
||||
styles: string[], pipes: CompilePipeMetadata[],
|
||||
styles: string[],
|
||||
pipes: CompilePipeMetadata[],
|
||||
compilingComponentsPath: any[],
|
||||
childPromises: Promise<any>[]): Function {
|
||||
var compileResult = this._viewCompiler.compileComponent(
|
||||
|
@ -213,7 +213,12 @@ export enum PropertyBindingType {
|
||||
/**
|
||||
* A binding to a style rule (e.g. `[style.rule]="expression"`).
|
||||
*/
|
||||
Style
|
||||
Style,
|
||||
|
||||
/**
|
||||
* A binding to an animation reference (e.g. `[animate.key]="expression"`).
|
||||
*/
|
||||
Animation
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,12 +75,13 @@ import {ProviderElementContext, ProviderViewContext} from './provider_parser';
|
||||
// Group 4 = "ref-/#"
|
||||
// Group 5 = "on-"
|
||||
// Group 6 = "bindon-"
|
||||
// Group 7 = the identifier after "bind-", "var-/#", or "on-"
|
||||
// Group 8 = identifier inside [()]
|
||||
// Group 9 = identifier inside []
|
||||
// Group 10 = identifier inside ()
|
||||
// Group 7 = "animate-/@"
|
||||
// Group 8 = the identifier after "bind-", "var-/#", or "on-"
|
||||
// Group 9 = identifier inside [()]
|
||||
// Group 10 = identifier inside []
|
||||
// Group 11 = identifier inside ()
|
||||
var BIND_NAME_REGEXP =
|
||||
/^(?:(?:(?:(bind-)|(var-)|(let-)|(ref-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g;
|
||||
/^(?:(?:(?:(bind-)|(var-)|(let-)|(ref-|#)|(on-)|(bindon-)|(animate-|@))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g;
|
||||
|
||||
const TEMPLATE_ELEMENT = 'template';
|
||||
const TEMPLATE_ATTR = 'template';
|
||||
@ -303,6 +304,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
var elementOrDirectiveProps: BoundElementOrDirectiveProperty[] = [];
|
||||
var elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
|
||||
var elementVars: VariableAst[] = [];
|
||||
var animationProps: BoundElementPropertyAst[] = [];
|
||||
var events: BoundEventAst[] = [];
|
||||
|
||||
var templateElementOrDirectiveProps: BoundElementOrDirectiveProperty[] = [];
|
||||
@ -316,7 +318,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
|
||||
element.attrs.forEach(attr => {
|
||||
var hasBinding =
|
||||
this._parseAttr(isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
|
||||
this._parseAttr(isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, animationProps, events,
|
||||
elementOrDirectiveRefs, elementVars);
|
||||
var hasTemplateBinding = this._parseInlineTemplateBinding(
|
||||
attr, templateMatchableAttrs, templateElementOrDirectiveProps, templateElementVars);
|
||||
@ -337,7 +339,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
elementOrDirectiveProps, elementOrDirectiveRefs,
|
||||
element.sourceSpan, references);
|
||||
var elementProps: BoundElementPropertyAst[] =
|
||||
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
|
||||
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts).concat(animationProps);
|
||||
var isViewRoot = parent.isTemplateElement || hasInlineTemplates;
|
||||
var providerContext =
|
||||
new ProviderElementContext(this.providerViewContext, parent.providerContext, isViewRoot,
|
||||
@ -439,7 +441,9 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
|
||||
private _parseAttr(isTemplateElement: boolean, attr: HtmlAttrAst,
|
||||
targetMatchableAttrs: string[][],
|
||||
targetProps: BoundElementOrDirectiveProperty[], targetEvents: BoundEventAst[],
|
||||
targetProps: BoundElementOrDirectiveProperty[],
|
||||
targetAnimationProps: BoundElementPropertyAst[],
|
||||
targetEvents: BoundEventAst[],
|
||||
targetRefs: ElementOrDirectiveRef[], targetVars: VariableAst[]): boolean {
|
||||
var attrName = this._normalizeAttributeName(attr.name);
|
||||
var attrValue = attr.value;
|
||||
@ -448,11 +452,11 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
if (isPresent(bindParts)) {
|
||||
hasBinding = true;
|
||||
if (isPresent(bindParts[1])) { // match: bind-prop
|
||||
this._parseProperty(bindParts[7], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
this._parseProperty(bindParts[8], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
targetProps);
|
||||
|
||||
} else if (isPresent(bindParts[2])) { // match: var-name / var-name="iden"
|
||||
var identifier = bindParts[7];
|
||||
var identifier = bindParts[8];
|
||||
if (isTemplateElement) {
|
||||
this._reportError(`"var-" on <template> elements is deprecated. Use "let-" instead!`,
|
||||
attr.sourceSpan, ParseErrorLevel.WARNING);
|
||||
@ -465,38 +469,41 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
|
||||
} else if (isPresent(bindParts[3])) { // match: let-name
|
||||
if (isTemplateElement) {
|
||||
var identifier = bindParts[7];
|
||||
var identifier = bindParts[8];
|
||||
this._parseVariable(identifier, attrValue, attr.sourceSpan, targetVars);
|
||||
} else {
|
||||
this._reportError(`"let-" is only supported on template elements.`, attr.sourceSpan);
|
||||
}
|
||||
|
||||
} else if (isPresent(bindParts[4])) { // match: ref- / #iden
|
||||
var identifier = bindParts[7];
|
||||
var identifier = bindParts[8];
|
||||
this._parseReference(identifier, attrValue, attr.sourceSpan, targetRefs);
|
||||
|
||||
} else if (isPresent(bindParts[5])) { // match: on-event
|
||||
this._parseEvent(bindParts[7], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
this._parseEvent(bindParts[8], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
targetEvents);
|
||||
|
||||
} else if (isPresent(bindParts[6])) { // match: bindon-prop
|
||||
this._parseProperty(bindParts[7], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
targetProps);
|
||||
this._parseAssignmentEvent(bindParts[7], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
targetEvents);
|
||||
|
||||
} else if (isPresent(bindParts[8])) { // match: [(expr)]
|
||||
this._parseProperty(bindParts[8], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
targetProps);
|
||||
this._parseAssignmentEvent(bindParts[8], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
targetEvents);
|
||||
|
||||
} else if (isPresent(bindParts[9])) { // match: [expr]
|
||||
} else if (isPresent(bindParts[7])) { // match: animate-name
|
||||
this._parseAnimation(bindParts[8], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
targetAnimationProps);
|
||||
} else if (isPresent(bindParts[9])) { // match: [(expr)]
|
||||
this._parseProperty(bindParts[9], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
targetProps);
|
||||
this._parseAssignmentEvent(bindParts[9], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
targetEvents);
|
||||
|
||||
} else if (isPresent(bindParts[10])) { // match: (event)
|
||||
this._parseEvent(bindParts[10], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
} else if (isPresent(bindParts[10])) { // match: [expr]
|
||||
this._parseProperty(bindParts[10], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
targetProps);
|
||||
|
||||
} else if (isPresent(bindParts[11])) { // match: (event)
|
||||
this._parseEvent(bindParts[11], attrValue, attr.sourceSpan, targetMatchableAttrs,
|
||||
targetEvents);
|
||||
}
|
||||
} else {
|
||||
@ -536,6 +543,14 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
targetMatchableAttrs, targetProps);
|
||||
}
|
||||
|
||||
private _parseAnimation(name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][],
|
||||
targetAnimationProps: BoundElementPropertyAst[]) {
|
||||
var ast = this._parseBinding(expression, sourceSpan);
|
||||
targetMatchableAttrs.push([name, ast.source]);
|
||||
targetAnimationProps.push(new BoundElementPropertyAst(name, PropertyBindingType.Animation, SecurityContext.NONE, ast, null, sourceSpan));
|
||||
}
|
||||
|
||||
private _parsePropertyInterpolation(name: string, value: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][],
|
||||
targetProps: BoundElementOrDirectiveProperty[]): boolean {
|
||||
@ -716,8 +731,8 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
bindingType = PropertyBindingType.Property;
|
||||
if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName)) {
|
||||
this._reportError(
|
||||
`Can't bind to '${boundPropertyName}' since it isn't a known native property`,
|
||||
sourceSpan);
|
||||
`Can't bind to '${boundPropertyName}' since it isn't a known native property`,
|
||||
sourceSpan);
|
||||
}
|
||||
} else {
|
||||
if (parts[0] == ATTRIBUTE_PREFIX) {
|
||||
|
@ -27,6 +27,8 @@ import {CompilerConfig} from '../config';
|
||||
import {CompileBinding} from './compile_binding';
|
||||
import {Identifiers} from '../identifiers';
|
||||
|
||||
import {CompiledAnimation} from '../animation/animation_compiler';
|
||||
|
||||
export class CompileView implements NameResolver {
|
||||
public viewType: ViewType;
|
||||
public viewQueries: CompileTokenMap<CompileQuery[]>;
|
||||
@ -48,6 +50,7 @@ export class CompileView implements NameResolver {
|
||||
public afterContentLifecycleCallbacksMethod: CompileMethod;
|
||||
public afterViewLifecycleCallbacksMethod: CompileMethod;
|
||||
public destroyMethod: CompileMethod;
|
||||
public detachMethod: CompileMethod;
|
||||
public eventHandlerMethods: o.ClassMethod[] = [];
|
||||
|
||||
public fields: o.ClassField[] = [];
|
||||
@ -66,13 +69,17 @@ export class CompileView implements NameResolver {
|
||||
public literalArrayCount = 0;
|
||||
public literalMapCount = 0;
|
||||
public pipeCount = 0;
|
||||
public animations = new Map<string, CompiledAnimation>();
|
||||
|
||||
public componentContext: o.Expression;
|
||||
|
||||
constructor(public component: CompileDirectiveMetadata, public genConfig: CompilerConfig,
|
||||
public pipeMetas: CompilePipeMetadata[], public styles: o.Expression,
|
||||
public pipeMetas: CompilePipeMetadata[],
|
||||
public styles: o.Expression,
|
||||
animations: CompiledAnimation[],
|
||||
public viewIndex: number, public declarationElement: CompileElement,
|
||||
public templateVariableBindings: string[][]) {
|
||||
animations.forEach(entry => this.animations.set(entry.name, entry));
|
||||
this.createMethod = new CompileMethod(this);
|
||||
this.injectorGetMethod = new CompileMethod(this);
|
||||
this.updateContentQueriesMethod = new CompileMethod(this);
|
||||
@ -84,6 +91,7 @@ export class CompileView implements NameResolver {
|
||||
this.afterContentLifecycleCallbacksMethod = new CompileMethod(this);
|
||||
this.afterViewLifecycleCallbacksMethod = new CompileMethod(this);
|
||||
this.destroyMethod = new CompileMethod(this);
|
||||
this.detachMethod = new CompileMethod(this);
|
||||
|
||||
this.viewType = getViewType(component, viewIndex);
|
||||
this.className = `_View_${component.type.name}${viewIndex}`;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {SecurityContext} from '../../core_private';
|
||||
import {LifecycleHooks, isDefaultChangeDetectionStrategy} from '../../core_private';
|
||||
import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../../core_private';
|
||||
|
||||
import {isBlank, isPresent} from '../../src/facade/lang';
|
||||
|
||||
@ -24,6 +24,7 @@ import {camelCaseToDashCase} from '../util';
|
||||
import {convertCdExpressionToIr} from './expression_converter';
|
||||
|
||||
import {CompileBinding} from './compile_binding';
|
||||
import {BaseException} from '@angular/core';
|
||||
|
||||
|
||||
function createBindFieldExpr(exprIndex: number): o.ReadPropExpr {
|
||||
@ -95,36 +96,92 @@ function bindAndWriteToRenderer(boundProps: BoundElementPropertyAst[], context:
|
||||
var fieldExpr = createBindFieldExpr(bindingIndex);
|
||||
var currValExpr = createCurrValueExpr(bindingIndex);
|
||||
var renderMethod: string;
|
||||
var oldRenderValue: o.Expression = sanitizedValue(boundProp, fieldExpr);
|
||||
var renderValue: o.Expression = sanitizedValue(boundProp, currValExpr);
|
||||
var updateStmts = [];
|
||||
switch (boundProp.type) {
|
||||
case PropertyBindingType.Property:
|
||||
renderMethod = 'setElementProperty';
|
||||
if (view.genConfig.logBindingUpdate) {
|
||||
updateStmts.push(logBindingUpdateStmt(renderNode, boundProp.name, currValExpr));
|
||||
updateStmts.push(logBindingUpdateStmt(renderNode, boundProp.name, renderValue));
|
||||
}
|
||||
updateStmts.push(
|
||||
o.THIS_EXPR.prop('renderer')
|
||||
.callMethod('setElementProperty', [renderNode, o.literal(boundProp.name), renderValue])
|
||||
.toStmt()
|
||||
);
|
||||
break;
|
||||
case PropertyBindingType.Attribute:
|
||||
renderMethod = 'setElementAttribute';
|
||||
renderValue =
|
||||
renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', []));
|
||||
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', []));
|
||||
updateStmts.push(
|
||||
o.THIS_EXPR.prop('renderer')
|
||||
.callMethod('setElementAttribute', [renderNode, o.literal(boundProp.name), renderValue])
|
||||
.toStmt()
|
||||
);
|
||||
break;
|
||||
case PropertyBindingType.Class:
|
||||
renderMethod = 'setElementClass';
|
||||
updateStmts.push(
|
||||
o.THIS_EXPR.prop('renderer')
|
||||
.callMethod('setElementClass', [renderNode, o.literal(boundProp.name), renderValue])
|
||||
.toStmt()
|
||||
);
|
||||
break;
|
||||
case PropertyBindingType.Style:
|
||||
renderMethod = 'setElementStyle';
|
||||
var strValue: o.Expression = renderValue.callMethod('toString', []);
|
||||
if (isPresent(boundProp.unit)) {
|
||||
strValue = strValue.plus(o.literal(boundProp.unit));
|
||||
}
|
||||
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
|
||||
updateStmts.push(
|
||||
o.THIS_EXPR.prop('renderer')
|
||||
.callMethod('setElementStyle', [renderNode, o.literal(boundProp.name), renderValue])
|
||||
.toStmt()
|
||||
);
|
||||
break;
|
||||
case PropertyBindingType.Animation:
|
||||
var animationName = boundProp.name;
|
||||
var animation = view.componentView.animations.get(animationName);
|
||||
if (!isPresent(animation)) {
|
||||
throw new BaseException(`Internal Error: couldn't find an animation entry for ${boundProp.name}`);
|
||||
}
|
||||
|
||||
// it's important to normalize the void value as `void` explicitly
|
||||
// so that the styles data can be obtained from the stringmap
|
||||
var emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
|
||||
|
||||
// void => ...
|
||||
var oldRenderVar = o.variable('oldRenderVar');
|
||||
updateStmts.push(oldRenderVar.set(oldRenderValue).toDeclStmt());
|
||||
updateStmts.push(
|
||||
new o.IfStmt(oldRenderVar.equals(o.importExpr(Identifiers.uninitialized)), [
|
||||
oldRenderVar.set(emptyStateValue).toStmt()
|
||||
]));
|
||||
|
||||
// ... => void
|
||||
var newRenderVar = o.variable('newRenderVar');
|
||||
updateStmts.push(newRenderVar.set(renderValue).toDeclStmt());
|
||||
updateStmts.push(
|
||||
new o.IfStmt(newRenderVar.equals(o.importExpr(Identifiers.uninitialized)), [
|
||||
newRenderVar.set(emptyStateValue).toStmt()
|
||||
]));
|
||||
|
||||
updateStmts.push(
|
||||
animation.fnVariable.callFn([
|
||||
o.THIS_EXPR,
|
||||
renderNode,
|
||||
oldRenderVar,
|
||||
newRenderVar
|
||||
]).toStmt());
|
||||
|
||||
view.detachMethod.addStmt(
|
||||
animation.fnVariable.callFn([
|
||||
o.THIS_EXPR,
|
||||
renderNode,
|
||||
oldRenderValue,
|
||||
emptyStateValue
|
||||
]).toStmt());
|
||||
|
||||
break;
|
||||
}
|
||||
updateStmts.push(
|
||||
o.THIS_EXPR.prop('renderer')
|
||||
.callMethod(renderMethod, [renderNode, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
|
||||
bind(view, currValExpr, fieldExpr, boundProp.value, context, updateStmts,
|
||||
view.detectChangesRenderPropertiesMethod);
|
||||
|
@ -45,6 +45,8 @@ import {
|
||||
CompileTokenMetadata
|
||||
} from '../compile_metadata';
|
||||
|
||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||
|
||||
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
|
||||
const CLASS_ATTR = 'class';
|
||||
const STYLE_ATTR = 'style';
|
||||
@ -79,6 +81,8 @@ export function finishView(view: CompileView, targetStatements: o.Statement[]) {
|
||||
class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||
nestedViewCount: number = 0;
|
||||
|
||||
private _animationCompiler = new AnimationCompiler();
|
||||
|
||||
constructor(public view: CompileView, public targetDependencies: ViewCompileDependency[]) {}
|
||||
|
||||
private _isRootNode(parent: CompileElement): boolean { return parent.view !== this.view; }
|
||||
@ -270,9 +274,11 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||
ast.providers, ast.hasViewContainer, true, ast.references);
|
||||
this.view.nodes.push(compileElement);
|
||||
|
||||
var compiledAnimations = this._animationCompiler.compileComponent(this.view.component);
|
||||
|
||||
this.nestedViewCount++;
|
||||
var embeddedView = new CompileView(
|
||||
this.view.component, this.view.genConfig, this.view.pipeMetas, o.NULL_EXPR,
|
||||
this.view.component, this.view.genConfig, this.view.pipeMetas, o.NULL_EXPR, compiledAnimations,
|
||||
this.view.viewIndex + this.nestedViewCount, compileElement, templateVariableBindings);
|
||||
this.nestedViewCount += buildView(embeddedView, ast.children, this.targetDependencies);
|
||||
|
||||
@ -423,7 +429,8 @@ function createViewClass(view: CompileView, renderCompTypeVar: o.ReadVarExpr,
|
||||
[new o.FnParam(DetectChangesVars.throwOnChange.name, o.BOOL_TYPE)],
|
||||
generateDetectChangesMethod(view)),
|
||||
new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()),
|
||||
new o.ClassMethod('destroyInternal', [], view.destroyMethod.finish())
|
||||
new o.ClassMethod('destroyInternal', [], view.destroyMethod.finish()),
|
||||
new o.ClassMethod('detachInternal', [], view.detachMethod.finish())
|
||||
].concat(view.eventHandlerMethods);
|
||||
var superClass = view.genConfig.genDebugInfo ? Identifiers.DebugAppView : Identifiers.AppView;
|
||||
var viewClass = new o.ClassStmt(view.className, o.importExpr(superClass, [getContextType(view)]),
|
||||
|
@ -8,6 +8,8 @@ import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata
|
||||
import {TemplateAst} from '../template_ast';
|
||||
import {CompilerConfig} from '../config';
|
||||
|
||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||
|
||||
export class ViewCompileResult {
|
||||
constructor(public statements: o.Statement[], public viewFactoryVar: string,
|
||||
public dependencies: ViewCompileDependency[]) {}
|
||||
@ -15,13 +17,19 @@ export class ViewCompileResult {
|
||||
|
||||
@Injectable()
|
||||
export class ViewCompiler {
|
||||
private _animationCompiler = new AnimationCompiler();
|
||||
constructor(private _genConfig: CompilerConfig) {}
|
||||
|
||||
compileComponent(component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
styles: o.Expression, pipes: CompilePipeMetadata[]): ViewCompileResult {
|
||||
var statements = [];
|
||||
var dependencies = [];
|
||||
var view = new CompileView(component, this._genConfig, pipes, styles, 0,
|
||||
var compiledAnimations = this._animationCompiler.compileComponent(component);
|
||||
var statements = [];
|
||||
compiledAnimations.map(entry => {
|
||||
statements.push(entry.statesMapStatement);
|
||||
statements.push(entry.fnStatement);
|
||||
});
|
||||
var view = new CompileView(component, this._genConfig, pipes, styles, compiledAnimations, 0,
|
||||
CompileElement.createNull(), []);
|
||||
buildView(view, template, dependencies);
|
||||
// Need to separate binding from creation to be able to refer to
|
||||
|
@ -91,7 +91,8 @@ export class ViewResolver {
|
||||
pipes: compMeta.pipes,
|
||||
encapsulation: compMeta.encapsulation,
|
||||
styles: compMeta.styles,
|
||||
styleUrls: compMeta.styleUrls
|
||||
styleUrls: compMeta.styleUrls,
|
||||
animations: compMeta.animations
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -0,0 +1,82 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
beforeEachProviders
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {trigger, style, animate, group, sequence, transition, AnimationMetadata} from '@angular/core';
|
||||
|
||||
import {AnimationCompiler, CompiledAnimation} from '../../src/animation/animation_compiler';
|
||||
import {
|
||||
CompileTemplateMetadata,
|
||||
CompileDirectiveMetadata,
|
||||
CompileTypeMetadata
|
||||
} from '../../src/compile_metadata';
|
||||
|
||||
import {CompileMetadataResolver} from '../../src/metadata_resolver';
|
||||
|
||||
export function main() {
|
||||
describe('RuntimeAnimationCompiler', () => {
|
||||
var resolver;
|
||||
beforeEach(inject([CompileMetadataResolver], (res: CompileMetadataResolver) => {
|
||||
resolver = res;
|
||||
}));
|
||||
|
||||
var compiler = new AnimationCompiler();
|
||||
|
||||
var compileAnimations = (component: CompileDirectiveMetadata): CompiledAnimation => {
|
||||
return compiler.compileComponent(component)[0];
|
||||
};
|
||||
|
||||
var compile = (seq: AnimationMetadata) => {
|
||||
var entry = trigger('myAnimation', [
|
||||
transition('state1 => state2', seq)
|
||||
]);
|
||||
|
||||
var compiledAnimationEntry = resolver.getAnimationEntryMetadata(entry);
|
||||
var component = CompileDirectiveMetadata.create({
|
||||
type: new CompileTypeMetadata({
|
||||
name: 'something'
|
||||
}),
|
||||
template: new CompileTemplateMetadata({
|
||||
animations: [compiledAnimationEntry]
|
||||
})
|
||||
});
|
||||
|
||||
return compileAnimations(component);
|
||||
};
|
||||
|
||||
it('should throw an exception containing all the inner animation parser errors', () => {
|
||||
var animation = sequence([
|
||||
style({"color": "red"}),
|
||||
animate(1000, style({"font-size": "100px"})),
|
||||
style({"color": "blue"}),
|
||||
animate(1000, style(":missing_state")),
|
||||
style({"color": "gold"}),
|
||||
animate(1000, style("broken_state"))
|
||||
]);
|
||||
|
||||
var capturedErrorMessage: string;
|
||||
try {
|
||||
compile(animation);
|
||||
} catch (e) {
|
||||
capturedErrorMessage = e.message;
|
||||
}
|
||||
|
||||
expect(capturedErrorMessage)
|
||||
.toMatchPattern(
|
||||
/Unable to apply styles due to missing a state: "missing_state"/g);
|
||||
|
||||
expect(capturedErrorMessage)
|
||||
.toMatchPattern(/Animation states via styles must be prefixed with a ":"/);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,469 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
beforeEachProviders
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {parseAnimationEntry} from '../../src/animation/animation_parser';
|
||||
|
||||
import {CompileMetadataResolver} from '../../src/metadata_resolver';
|
||||
|
||||
import {
|
||||
style,
|
||||
animate,
|
||||
group,
|
||||
sequence,
|
||||
trigger,
|
||||
keyframes,
|
||||
transition,
|
||||
state,
|
||||
AnimationMetadata,
|
||||
AnimationWithStepsMetadata,
|
||||
AnimationStyleMetadata,
|
||||
AnimationAnimateMetadata,
|
||||
AnimationGroupMetadata,
|
||||
AnimationSequenceMetadata
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
AnimationAst,
|
||||
AnimationStateTransitionAst,
|
||||
AnimationEntryAst,
|
||||
AnimationKeyframeAst,
|
||||
AnimationStylesAst,
|
||||
AnimationSequenceAst,
|
||||
AnimationGroupAst,
|
||||
AnimationStepAst
|
||||
} from '../../src/animation/animation_ast';
|
||||
|
||||
import {FILL_STYLE_FLAG, AnimationStyleUtil} from '../../core_private';
|
||||
|
||||
import {StringMapWrapper} from '../../src/facade/collection';
|
||||
|
||||
export function main() {
|
||||
describe('parseAnimationEntry', () => {
|
||||
var combineStyles = (styles: AnimationStylesAst): {[key: string]: string | number} => {
|
||||
var flatStyles: {[key: string]: string | number} = {};
|
||||
styles.styles.forEach(entry => StringMapWrapper.forEach(entry, (val, prop) => { flatStyles[prop] = val; }));
|
||||
return flatStyles;
|
||||
};
|
||||
|
||||
var collectKeyframeStyles = (keyframe: AnimationKeyframeAst): {[key: string]: string | number} => {
|
||||
return combineStyles(keyframe.styles);
|
||||
};
|
||||
|
||||
var collectStepStyles = (step: AnimationStepAst): Array<{[key: string]: string | number}> => {
|
||||
var keyframes = step.keyframes;
|
||||
var styles = [];
|
||||
if (step.startingStyles.styles.length > 0) {
|
||||
styles.push(combineStyles(step.startingStyles));
|
||||
}
|
||||
keyframes.forEach(keyframe => styles.push(collectKeyframeStyles(keyframe)));
|
||||
return styles;
|
||||
};
|
||||
|
||||
var resolver;
|
||||
beforeEach(inject([CompileMetadataResolver], (res: CompileMetadataResolver) => {
|
||||
resolver = res;
|
||||
}));
|
||||
|
||||
var parseAnimation = (data: AnimationMetadata[]) => {
|
||||
var entry = trigger('myAnimation', [
|
||||
transition('state1 => state2', sequence(data))
|
||||
]);
|
||||
var compiledAnimationEntry = resolver.getAnimationEntryMetadata(entry);
|
||||
return parseAnimationEntry(compiledAnimationEntry);
|
||||
};
|
||||
|
||||
var getAnimationAstFromEntryAst = (ast: AnimationEntryAst) => {
|
||||
return ast.stateTransitions[0].animation;
|
||||
}
|
||||
|
||||
var parseAnimationAst = (data: AnimationMetadata[]) => {
|
||||
return getAnimationAstFromEntryAst(parseAnimation(data).ast);
|
||||
};
|
||||
|
||||
var parseAnimationAndGetErrors = (data: AnimationMetadata[]) => parseAnimation(data).errors;
|
||||
|
||||
it('should merge repeated style steps into a single style ast step entry', () => {
|
||||
var ast = parseAnimationAst([
|
||||
style({"color": 'black'}),
|
||||
style({"background": 'red'}),
|
||||
style({"opacity": 0}),
|
||||
animate(1000, style({"color": 'white', "background": 'black', "opacity": 1}))
|
||||
]);
|
||||
|
||||
expect(ast.steps.length).toEqual(1);
|
||||
|
||||
var step = <AnimationStepAst>ast.steps[0];
|
||||
expect(step.startingStyles.styles[0])
|
||||
.toEqual({"color": 'black', "background": 'red', "opacity": 0});
|
||||
|
||||
expect(step.keyframes[0].styles.styles[0])
|
||||
.toEqual({"color": 'black', "background": 'red', "opacity": 0});
|
||||
|
||||
expect(step.keyframes[1].styles.styles[0])
|
||||
.toEqual({"color": 'white', "background": 'black', "opacity": 1});
|
||||
});
|
||||
|
||||
it('should animate only the styles requested within an animation step', () => {
|
||||
var ast = parseAnimationAst([
|
||||
style({"color": 'black', "background": 'blue'}),
|
||||
animate(1000, style({"background": 'orange'}))
|
||||
]);
|
||||
|
||||
expect(ast.steps.length).toEqual(1);
|
||||
|
||||
var animateStep = <AnimationStepAst>ast.steps[0];
|
||||
var fromKeyframe = animateStep.keyframes[0].styles.styles[0];
|
||||
var toKeyframe = animateStep.keyframes[1].styles.styles[0];
|
||||
expect(fromKeyframe).toEqual({"background": 'blue'});
|
||||
expect(toKeyframe).toEqual({"background": 'orange'});
|
||||
});
|
||||
|
||||
it('should populate the starting and duration times propertly', () => {
|
||||
var ast = parseAnimationAst([
|
||||
style({"color": 'black', "opacity": 1}),
|
||||
animate(1000, style({"color": 'red'})),
|
||||
animate(4000, style({"color": 'yellow'})),
|
||||
sequence([animate(1000, style({"color": 'blue'})), animate(1000, style({"color": 'grey'}))]),
|
||||
group([animate(500, style({"color": 'pink'})), animate(1000, style({"opacity": '0.5'}))]),
|
||||
animate(300, style({"color": 'black'})),
|
||||
]);
|
||||
|
||||
expect(ast.steps.length).toEqual(5);
|
||||
|
||||
var step1 = <AnimationStepAst>ast.steps[0];
|
||||
expect(step1.playTime).toEqual(1000);
|
||||
expect(step1.startTime).toEqual(0);
|
||||
|
||||
var step2 = <AnimationStepAst>ast.steps[1];
|
||||
expect(step2.playTime).toEqual(4000);
|
||||
expect(step2.startTime).toEqual(1000);
|
||||
|
||||
var seq = <AnimationSequenceAst>ast.steps[2];
|
||||
expect(seq.playTime).toEqual(2000);
|
||||
expect(seq.startTime).toEqual(5000);
|
||||
|
||||
var step4 = <AnimationStepAst>seq.steps[0];
|
||||
expect(step4.playTime).toEqual(1000);
|
||||
expect(step4.startTime).toEqual(5000);
|
||||
|
||||
var step5 = <AnimationStepAst>seq.steps[1];
|
||||
expect(step5.playTime).toEqual(1000);
|
||||
expect(step5.startTime).toEqual(6000);
|
||||
|
||||
var grp = <AnimationGroupAst>ast.steps[3];
|
||||
expect(grp.playTime).toEqual(1000);
|
||||
expect(grp.startTime).toEqual(7000);
|
||||
|
||||
var step6 = <AnimationStepAst>grp.steps[0];
|
||||
expect(step6.playTime).toEqual(500);
|
||||
expect(step6.startTime).toEqual(7000);
|
||||
|
||||
var step7 = <AnimationStepAst>grp.steps[1];
|
||||
expect(step7.playTime).toEqual(1000);
|
||||
expect(step7.startTime).toEqual(7000);
|
||||
|
||||
var step8 = <AnimationStepAst>ast.steps[4];
|
||||
expect(step8.playTime).toEqual(300);
|
||||
expect(step8.startTime).toEqual(8000);
|
||||
});
|
||||
|
||||
it('should apply the correct animate() styles when parallel animations are active and use the same properties',
|
||||
() => {
|
||||
var details = parseAnimation([
|
||||
style({"opacity": 0, "color": 'red'}),
|
||||
group([
|
||||
sequence([
|
||||
animate(2000, style({"color": "black"})),
|
||||
animate(2000, style({"opacity": 0.5})),
|
||||
]),
|
||||
sequence([animate(2000, style({"opacity": 0.8})), animate(2000, style({"color": "blue"}))])
|
||||
])
|
||||
]);
|
||||
|
||||
var errors = details.errors;
|
||||
expect(errors.length).toEqual(0);
|
||||
|
||||
var ast = <AnimationSequenceAst>getAnimationAstFromEntryAst(details.ast);
|
||||
var g1 = <AnimationGroupAst>ast.steps[1];
|
||||
|
||||
var sq1 = <AnimationSequenceAst>g1.steps[0];
|
||||
var sq2 = <AnimationSequenceAst>g1.steps[1];
|
||||
|
||||
var sq1a1 = <AnimationStepAst>sq1.steps[0];
|
||||
expect(collectStepStyles(sq1a1)).toEqual([{"color": 'red'}, {"color": 'black'}]);
|
||||
|
||||
var sq1a2 = <AnimationStepAst>sq1.steps[1];
|
||||
expect(collectStepStyles(sq1a2)).toEqual([{"opacity": 0.8}, {"opacity": 0.5}]);
|
||||
|
||||
var sq2a1 = <AnimationStepAst>sq2.steps[0];
|
||||
expect(collectStepStyles(sq2a1)).toEqual([{"opacity": 0}, {"opacity": 0.8}]);
|
||||
|
||||
var sq2a2 = <AnimationStepAst>sq2.steps[1];
|
||||
expect(collectStepStyles(sq2a2)).toEqual([{"color": "black"}, {"color": "blue"}]);
|
||||
});
|
||||
|
||||
it('should throw errors when animations animate a CSS property at the same time', () => {
|
||||
var animation1 = parseAnimation([
|
||||
style({"opacity": 0}),
|
||||
group([animate(1000, style({"opacity": 1})), animate(2000, style({"opacity": 0.5}))])
|
||||
]);
|
||||
|
||||
var errors1 = animation1.errors;
|
||||
expect(errors1.length).toEqual(1);
|
||||
expect(errors1[0].msg)
|
||||
.toContainError(
|
||||
'The animated CSS property "opacity" unexpectedly changes between steps "0ms" and "2000ms" at "1000ms"');
|
||||
|
||||
var animation2 = parseAnimation([
|
||||
style({"color": "red"}),
|
||||
group([
|
||||
animate(5000, style({"color": "blue"})),
|
||||
animate(2500, style({"color": "black"}))
|
||||
])
|
||||
]);
|
||||
|
||||
var errors2 = animation2.errors;
|
||||
expect(errors2.length).toEqual(1);
|
||||
expect(errors2[0].msg)
|
||||
.toContainError(
|
||||
'The animated CSS property "color" unexpectedly changes between steps "0ms" and "5000ms" at "2500ms"');
|
||||
});
|
||||
|
||||
it('should return an error when an animation style contains an invalid timing value', () => {
|
||||
var errors = parseAnimationAndGetErrors(
|
||||
[style({"opacity": 0}), animate('one second', style({"opacity": 1}))]);
|
||||
expect(errors[0].msg).toContainError(`The provided timing value "one second" is invalid.`);
|
||||
});
|
||||
|
||||
it('should collect and return any errors collected when parsing the metadata', () => {
|
||||
var errors = parseAnimationAndGetErrors([
|
||||
style({"opacity": 0}),
|
||||
animate('one second', style({"opacity": 1})),
|
||||
style({"opacity": 0}),
|
||||
animate('one second', null),
|
||||
style({"background": 'red'})
|
||||
]);
|
||||
expect(errors.length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it('should normalize a series of keyframe styles into a list of offset steps', () => {
|
||||
var ast = parseAnimationAst([
|
||||
animate(1000, keyframes([
|
||||
style({"width": 0}),
|
||||
style({"width": 25}),
|
||||
style({"width": 50}),
|
||||
style({"width": 75})
|
||||
]))
|
||||
]);
|
||||
|
||||
var step = <AnimationStepAst>ast.steps[0];
|
||||
expect(step.keyframes.length).toEqual(4);
|
||||
|
||||
expect(step.keyframes[0].offset).toEqual(0);
|
||||
expect(step.keyframes[1].offset).toMatchPattern(/^0\.33/);
|
||||
expect(step.keyframes[2].offset).toMatchPattern(/^0\.66/);
|
||||
expect(step.keyframes[3].offset).toEqual(1);
|
||||
});
|
||||
|
||||
it('should use an existing collection of offset steps if provided', () => {
|
||||
var ast = parseAnimationAst([
|
||||
animate(1000, keyframes([
|
||||
style({"height": 0, "offset": 0}),
|
||||
style({"height": 25, "offset": 0.6}),
|
||||
style({"height": 50, "offset": 0.7}),
|
||||
style({"height": 75, "offset": 1})
|
||||
]))
|
||||
]);
|
||||
|
||||
var step = <AnimationStepAst>ast.steps[0];
|
||||
expect(step.keyframes.length).toEqual(4);
|
||||
|
||||
expect(step.keyframes[0].offset).toEqual(0);
|
||||
expect(step.keyframes[1].offset).toEqual(0.6);
|
||||
expect(step.keyframes[2].offset).toEqual(0.7);
|
||||
expect(step.keyframes[3].offset).toEqual(1);
|
||||
});
|
||||
|
||||
it('should sort the provided collection of steps that contain offsets', () => {
|
||||
var ast = parseAnimationAst([
|
||||
animate(1000, keyframes([
|
||||
style({"opacity": 0, "offset": 0.9}),
|
||||
style({"opacity": .25, "offset": 0}),
|
||||
style({"opacity": .50, "offset": 1}),
|
||||
style({"opacity": .75, "offset": 0.91})
|
||||
]))
|
||||
]);
|
||||
|
||||
var step = <AnimationStepAst>ast.steps[0];
|
||||
expect(step.keyframes.length).toEqual(4);
|
||||
|
||||
expect(step.keyframes[0].offset).toEqual(0);
|
||||
expect(step.keyframes[0].styles.styles[0]['opacity']).toEqual(.25);
|
||||
|
||||
expect(step.keyframes[1].offset).toEqual(0.9);
|
||||
expect(step.keyframes[1].styles.styles[0]['opacity']).toEqual(0);
|
||||
|
||||
expect(step.keyframes[2].offset).toEqual(0.91);
|
||||
expect(step.keyframes[2].styles.styles[0]['opacity']).toEqual(.75);
|
||||
|
||||
expect(step.keyframes[3].offset).toEqual(1);
|
||||
expect(step.keyframes[3].styles.styles[0]['opacity']).toEqual(.50);
|
||||
});
|
||||
|
||||
it('should throw an error if a partial amount of keyframes contain an offset', () => {
|
||||
var errors = parseAnimationAndGetErrors([
|
||||
animate(1000, keyframes([
|
||||
style({"z-index": 0, "offset": 0}),
|
||||
style({"z-index": 1}),
|
||||
style({"z-index": 2, "offset": 1})
|
||||
]))
|
||||
]);
|
||||
|
||||
expect(errors.length).toEqual(1);
|
||||
var error = errors[0];
|
||||
|
||||
expect(error.msg).toMatchPattern(/Not all style\(\) entries contain an offset/);
|
||||
});
|
||||
|
||||
it('should use an existing style used earlier in the animation sequence if not defined in the first keyframe', () => {
|
||||
var ast = parseAnimationAst([
|
||||
animate(1000, keyframes([
|
||||
style({"color": "red"}),
|
||||
style({"background": "blue", "color": "white"})
|
||||
]))
|
||||
]);
|
||||
|
||||
var keyframesStep = <AnimationStepAst>ast.steps[0];
|
||||
var kf1 = keyframesStep.keyframes[0];
|
||||
var kf2 = keyframesStep.keyframes[1];
|
||||
|
||||
expect(AnimationStyleUtil.flattenStyles(kf1.styles.styles)).toEqual({
|
||||
"color": "red",
|
||||
"background": FILL_STYLE_FLAG
|
||||
});
|
||||
});
|
||||
|
||||
it('should copy over any missing styles to the final keyframe if not already defined', () => {
|
||||
var ast = parseAnimationAst([
|
||||
animate(1000, keyframes([
|
||||
style({"color": "white", "border-color":"white"}),
|
||||
style({"color": "red", "background": "blue"}),
|
||||
style({"background": "blue" })
|
||||
]))
|
||||
]);
|
||||
|
||||
var keyframesStep = <AnimationStepAst>ast.steps[0];
|
||||
var kf1 = keyframesStep.keyframes[0];
|
||||
var kf2 = keyframesStep.keyframes[1];
|
||||
var kf3 = keyframesStep.keyframes[2];
|
||||
|
||||
expect(AnimationStyleUtil.flattenStyles(kf3.styles.styles)).toEqual({
|
||||
"background": "blue",
|
||||
"color": "red",
|
||||
"border-color": "white"
|
||||
});
|
||||
});
|
||||
|
||||
it('should create an initial keyframe if not detected and place all keyframes styles there', () => {
|
||||
var ast = parseAnimationAst([
|
||||
animate(1000, keyframes([
|
||||
style({"color": "white", "background": "black", "offset": 0.5}),
|
||||
style({"color": "orange", "background": "red", "font-size": "100px", "offset": 1})
|
||||
]))
|
||||
]);
|
||||
|
||||
var keyframesStep = <AnimationStepAst>ast.steps[0];
|
||||
expect(keyframesStep.keyframes.length).toEqual(3);
|
||||
var kf1 = keyframesStep.keyframes[0];
|
||||
var kf2 = keyframesStep.keyframes[1];
|
||||
var kf3 = keyframesStep.keyframes[2];
|
||||
|
||||
expect(kf1.offset).toEqual(0);
|
||||
expect(AnimationStyleUtil.flattenStyles(kf1.styles.styles)).toEqual({
|
||||
"font-size": FILL_STYLE_FLAG,
|
||||
"background": FILL_STYLE_FLAG,
|
||||
"color": FILL_STYLE_FLAG
|
||||
});
|
||||
});
|
||||
|
||||
it('should create an destination keyframe if not detected and place all keyframes styles there', () => {
|
||||
var ast = parseAnimationAst([
|
||||
animate(1000, keyframes([
|
||||
style({"color": "white", "background": "black", "transform": "rotate(360deg)", "offset": 0}),
|
||||
style({"color": "orange", "background": "red", "font-size": "100px", "offset": 0.5})
|
||||
]))
|
||||
]);
|
||||
|
||||
var keyframesStep = <AnimationStepAst>ast.steps[0];
|
||||
expect(keyframesStep.keyframes.length).toEqual(3);
|
||||
var kf1 = keyframesStep.keyframes[0];
|
||||
var kf2 = keyframesStep.keyframes[1];
|
||||
var kf3 = keyframesStep.keyframes[2];
|
||||
|
||||
expect(kf3.offset).toEqual(1);
|
||||
expect(AnimationStyleUtil.flattenStyles(kf3.styles.styles)).toEqual({
|
||||
"color": "orange",
|
||||
"background": "red",
|
||||
"transform": "rotate(360deg)",
|
||||
"font-size": "100px"
|
||||
});
|
||||
});
|
||||
|
||||
describe('easing / duration / delay', () => {
|
||||
it('should parse simple string-based values', () => {
|
||||
var ast = parseAnimationAst([
|
||||
animate("1s .5s ease-out", style({ "opacity": 1 }))
|
||||
]);
|
||||
|
||||
var step = <AnimationStepAst>ast.steps[0];
|
||||
expect(step.duration).toEqual(1000);
|
||||
expect(step.delay).toEqual(500);
|
||||
expect(step.easing).toEqual("ease-out");
|
||||
});
|
||||
|
||||
it('should parse a numeric duration value', () => {
|
||||
var ast = parseAnimationAst([
|
||||
animate(666, style({ "opacity": 1 }))
|
||||
]);
|
||||
|
||||
var step = <AnimationStepAst>ast.steps[0];
|
||||
expect(step.duration).toEqual(666);
|
||||
expect(step.delay).toEqual(0);
|
||||
expect(step.easing).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should parse an easing value without a delay', () => {
|
||||
var ast = parseAnimationAst([
|
||||
animate("5s linear", style({ "opacity": 1 }))
|
||||
]);
|
||||
|
||||
var step = <AnimationStepAst>ast.steps[0];
|
||||
expect(step.duration).toEqual(5000);
|
||||
expect(step.delay).toEqual(0);
|
||||
expect(step.easing).toEqual("linear");
|
||||
});
|
||||
|
||||
it('should parse a complex easing value', () => {
|
||||
var ast = parseAnimationAst([
|
||||
animate("30ms cubic-bezier(0, 0,0, .69)", style({ "opacity": 1 }))
|
||||
]);
|
||||
|
||||
var step = <AnimationStepAst>ast.steps[0];
|
||||
expect(step.duration).toEqual(30);
|
||||
expect(step.delay).toEqual(0);
|
||||
expect(step.easing).toEqual("cubic-bezier(0, 0,0, .69)");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -18,14 +18,21 @@ import {
|
||||
CompileQueryMetadata,
|
||||
CompileIdentifierMetadata,
|
||||
CompileFactoryMetadata,
|
||||
CompileTokenMetadata
|
||||
CompileTokenMetadata,
|
||||
CompileAnimationEntryMetadata,
|
||||
CompileAnimationStyleMetadata,
|
||||
CompileAnimationAnimateMetadata,
|
||||
CompileAnimationSequenceMetadata,
|
||||
CompileAnimationStateTransitionMetadata,
|
||||
CompileAnimationKeyframesSequenceMetadata,
|
||||
CompileAnimationGroupMetadata
|
||||
} from '@angular/compiler/src/compile_metadata';
|
||||
import {ViewEncapsulation} from '@angular/core/src/metadata/view';
|
||||
import {ChangeDetectionStrategy} from '@angular/core/src/change_detection';
|
||||
import {LifecycleHooks} from '@angular/core/src/metadata/lifecycle_hooks';
|
||||
|
||||
export function main() {
|
||||
describe('CompileMetadata', () => {
|
||||
describe('CompileMetadata', () => {
|
||||
var fullTypeMeta: CompileTypeMetadata;
|
||||
var fullTemplateMeta: CompileTemplateMetadata;
|
||||
var fullDirectiveMeta: CompileDirectiveMetadata;
|
||||
@ -60,6 +67,17 @@ export function main() {
|
||||
templateUrl: 'someTemplateUrl',
|
||||
styles: ['someStyle'],
|
||||
styleUrls: ['someStyleUrl'],
|
||||
animations: [
|
||||
new CompileAnimationEntryMetadata('animation', [
|
||||
new CompileAnimationStateTransitionMetadata('* => *',
|
||||
new CompileAnimationSequenceMetadata([
|
||||
new CompileAnimationStyleMetadata(0, [{ 'opacity': 0 }]),
|
||||
new CompileAnimationAnimateMetadata(1000,
|
||||
new CompileAnimationStyleMetadata(0, [{ 'opacity': 1 }]))
|
||||
])
|
||||
)
|
||||
])
|
||||
],
|
||||
ngContentSelectors: ['*']
|
||||
});
|
||||
fullDirectiveMeta = CompileDirectiveMetadata.create({
|
||||
@ -169,5 +187,98 @@ export function main() {
|
||||
expect(CompileTemplateMetadata.fromJson(empty.toJson())).toEqual(empty);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CompileAnimationStyleMetadata', () => {
|
||||
it('should serialize with full data', () => {
|
||||
let full = new CompileAnimationStyleMetadata(0, [{ "opacity": 0, "color": "red" }]);
|
||||
expect(CompileAnimationStyleMetadata.fromJson(full.toJson())).toEqual(full);
|
||||
});
|
||||
|
||||
it('should serialize with no data', () => {
|
||||
let empty = new CompileAnimationStyleMetadata(0, []);
|
||||
expect(CompileAnimationStyleMetadata.fromJson(empty.toJson())).toEqual(empty);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CompileAnimationAnimateMetadata', () => {
|
||||
it('should serialize with full data', () => {
|
||||
let full = new CompileAnimationAnimateMetadata("1s linear",
|
||||
new CompileAnimationStyleMetadata(0, [{ "opacity": 0.5, "color": "blue" }]))
|
||||
expect(CompileAnimationAnimateMetadata.fromJson(full.toJson())).toEqual(full);
|
||||
});
|
||||
|
||||
it('should serialize with no data', () => {
|
||||
let empty = new CompileAnimationAnimateMetadata();
|
||||
expect(CompileAnimationAnimateMetadata.fromJson(empty.toJson())).toEqual(empty);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CompileAnimationSequenceMetadata', () => {
|
||||
it('should serialize with full data', () => {
|
||||
let full = new CompileAnimationSequenceMetadata([
|
||||
new CompileAnimationStyleMetadata(0, [{ "opacity": 0.5, "width": 100 }]),
|
||||
new CompileAnimationAnimateMetadata(1000,
|
||||
new CompileAnimationStyleMetadata(0, [{ "opacity": 1, "width": 0 }]))
|
||||
]);
|
||||
expect(CompileAnimationSequenceMetadata.fromJson(full.toJson())).toEqual(full);
|
||||
});
|
||||
|
||||
it('should serialize with no data', () => {
|
||||
let empty = new CompileAnimationSequenceMetadata();
|
||||
expect(CompileAnimationSequenceMetadata.fromJson(empty.toJson())).toEqual(empty);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CompileAnimationGroupMetadata', () => {
|
||||
it('should serialize with full data', () => {
|
||||
let full = new CompileAnimationGroupMetadata([
|
||||
new CompileAnimationStyleMetadata(0, [{ "width": 100, "border": "1px solid red" }]),
|
||||
new CompileAnimationAnimateMetadata(1000,
|
||||
new CompileAnimationStyleMetadata(0, [{ "width": 900, "border": "10px solid blue" }]))
|
||||
]);
|
||||
expect(CompileAnimationGroupMetadata.fromJson(full.toJson())).toEqual(full);
|
||||
});
|
||||
|
||||
it('should serialize with no data', () => {
|
||||
let empty = new CompileAnimationGroupMetadata();
|
||||
expect(CompileAnimationGroupMetadata.fromJson(empty.toJson())).toEqual(empty);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CompileAnimationKeyframesSequenceMetadata', () => {
|
||||
it('should serialize with full data', () => {
|
||||
let full = new CompileAnimationKeyframesSequenceMetadata([
|
||||
new CompileAnimationStyleMetadata(0, [{ "width": 0 }]),
|
||||
new CompileAnimationStyleMetadata(0.5, [{ "width": 100 }]),
|
||||
new CompileAnimationStyleMetadata(1, [{ "width": 200 }]),
|
||||
]);
|
||||
expect(CompileAnimationKeyframesSequenceMetadata.fromJson(full.toJson())).toEqual(full);
|
||||
});
|
||||
|
||||
it('should serialize with no data', () => {
|
||||
let empty = new CompileAnimationKeyframesSequenceMetadata();
|
||||
expect(CompileAnimationKeyframesSequenceMetadata.fromJson(empty.toJson())).toEqual(empty);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CompileAnimationEntryMetadata', () => {
|
||||
it('should serialize with full data', () => {
|
||||
let full = new CompileAnimationEntryMetadata('name', [
|
||||
new CompileAnimationStateTransitionMetadata('key => value',
|
||||
new CompileAnimationSequenceMetadata([
|
||||
new CompileAnimationStyleMetadata(0, [{ "color": "red" }]),
|
||||
new CompileAnimationAnimateMetadata(1000,
|
||||
new CompileAnimationStyleMetadata(0, [{ "color": "blue" }]))
|
||||
])
|
||||
)
|
||||
]);
|
||||
expect(CompileAnimationEntryMetadata.fromJson(full.toJson())).toEqual(full);
|
||||
});
|
||||
|
||||
it('should serialize with no data', () => {
|
||||
let empty = new CompileAnimationEntryMetadata();
|
||||
expect(CompileAnimationEntryMetadata.fromJson(empty.toJson())).toEqual(empty);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -252,6 +252,13 @@ export function main() {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse bound properties via animate- and not report them as attributes', () => {
|
||||
expect(humanizeTplAst(parse('<div animate-something="value2">', [])))
|
||||
.toEqual([
|
||||
[ElementAst, 'div'],
|
||||
[BoundElementPropertyAst, PropertyBindingType.Animation, 'something', 'value2', null]
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('events', () => {
|
||||
|
@ -30,7 +30,6 @@ export function main() {
|
||||
var view = viewResolver.resolve(SomeComponent);
|
||||
expect(view.template).toEqual('overridden template');
|
||||
expect(isBlank(view.directives)).toBe(true);
|
||||
|
||||
});
|
||||
|
||||
it('should not allow overriding a view after it has been resolved', () => {
|
||||
|
@ -28,6 +28,8 @@ export class TestComponentRenderer {
|
||||
insertRootElement(rootElementId: string) {}
|
||||
}
|
||||
|
||||
import {AnimationEntryMetadata} from '@angular/core';
|
||||
|
||||
export var ComponentFixtureAutoDetect = new OpaqueToken("ComponentFixtureAutoDetect");
|
||||
export var ComponentFixtureNoNgZone = new OpaqueToken("ComponentFixtureNoNgZone");
|
||||
|
||||
@ -219,6 +221,8 @@ export class TestComponentBuilder {
|
||||
/** @internal */
|
||||
_templateOverrides = new Map<Type, string>();
|
||||
/** @internal */
|
||||
_animationOverrides = new Map<Type, AnimationEntryMetadata[]>();
|
||||
/** @internal */
|
||||
_viewBindingsOverrides = new Map<Type, any[]>();
|
||||
/** @internal */
|
||||
_viewOverrides = new Map<Type, ViewMetadata>();
|
||||
@ -247,6 +251,12 @@ export class TestComponentBuilder {
|
||||
return clone;
|
||||
}
|
||||
|
||||
overrideAnimations(componentType: Type, animations: AnimationEntryMetadata[]): TestComponentBuilder {
|
||||
var clone = this._clone();
|
||||
clone._animationOverrides.set(componentType, animations);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides a component's {@link ViewMetadata}.
|
||||
*/
|
||||
@ -339,6 +349,8 @@ export class TestComponentBuilder {
|
||||
this._viewOverrides.forEach((view, type) => mockViewResolver.setView(type, view));
|
||||
this._templateOverrides.forEach((template, type) =>
|
||||
mockViewResolver.setInlineTemplate(type, template));
|
||||
this._animationOverrides.forEach((animationsEntry, type) =>
|
||||
mockViewResolver.setAnimations(type, animationsEntry));
|
||||
this._directiveOverrides.forEach((overrides, component) => {
|
||||
overrides.forEach(
|
||||
(to, from) => { mockViewResolver.overrideViewDirective(component, from, to); });
|
||||
|
@ -2,7 +2,7 @@ import {Injectable, ViewMetadata, Type, BaseException} from '@angular/core';
|
||||
import {ViewResolver} from '../index';
|
||||
import {Map} from '../src/facade/collection';
|
||||
import {isPresent, stringify, isBlank, isArray} from '../src/facade/lang';
|
||||
import {resolveForwardRef} from '@angular/core';
|
||||
import {AnimationEntryMetadata, resolveForwardRef} from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class MockViewResolver extends ViewResolver {
|
||||
@ -11,6 +11,8 @@ export class MockViewResolver extends ViewResolver {
|
||||
/** @internal */
|
||||
_inlineTemplates = new Map<Type, string>();
|
||||
/** @internal */
|
||||
_animations = new Map<Type, AnimationEntryMetadata[]>();
|
||||
/** @internal */
|
||||
_viewCache = new Map<Type, ViewMetadata>();
|
||||
/** @internal */
|
||||
_directiveOverrides = new Map<Type, Map<Type, Type>>();
|
||||
@ -24,7 +26,6 @@ export class MockViewResolver extends ViewResolver {
|
||||
this._checkOverrideable(component);
|
||||
this._views.set(component, view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the inline template for a component - other configuration remains unchanged.
|
||||
*/
|
||||
@ -33,6 +34,11 @@ export class MockViewResolver extends ViewResolver {
|
||||
this._inlineTemplates.set(component, template);
|
||||
}
|
||||
|
||||
setAnimations(component: Type, animations: AnimationEntryMetadata[]): void {
|
||||
this._checkOverrideable(component);
|
||||
this._animations.set(component, animations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides a directive from the component {@link ViewMetadata}.
|
||||
*/
|
||||
@ -67,10 +73,26 @@ export class MockViewResolver extends ViewResolver {
|
||||
}
|
||||
|
||||
var directives = [];
|
||||
if (isPresent(view.directives)) {
|
||||
flattenArray(view.directives, directives);
|
||||
}
|
||||
var animations = view.animations;
|
||||
var templateUrl = view.templateUrl;
|
||||
var overrides = this._directiveOverrides.get(component);
|
||||
|
||||
var inlineAnimations = this._animations.get(component);
|
||||
if (isPresent(inlineAnimations)) {
|
||||
animations = inlineAnimations;
|
||||
}
|
||||
|
||||
var inlineTemplate = this._inlineTemplates.get(component);
|
||||
if (isPresent(inlineTemplate)) {
|
||||
templateUrl = null;
|
||||
} else {
|
||||
inlineTemplate = view.template;
|
||||
}
|
||||
|
||||
if (isPresent(overrides) && isPresent(view.directives)) {
|
||||
flattenArray(view.directives, directives);
|
||||
overrides.forEach((to, from) => {
|
||||
var srcIndex = directives.indexOf(from);
|
||||
if (srcIndex == -1) {
|
||||
@ -79,15 +101,18 @@ export class MockViewResolver extends ViewResolver {
|
||||
}
|
||||
directives[srcIndex] = to;
|
||||
});
|
||||
view = new ViewMetadata(
|
||||
{template: view.template, templateUrl: view.templateUrl, directives: directives});
|
||||
}
|
||||
|
||||
var inlineTemplate = this._inlineTemplates.get(component);
|
||||
if (isPresent(inlineTemplate)) {
|
||||
view = new ViewMetadata(
|
||||
{template: inlineTemplate, templateUrl: null, directives: view.directives});
|
||||
}
|
||||
view = new ViewMetadata({
|
||||
template: inlineTemplate,
|
||||
templateUrl: templateUrl,
|
||||
directives: directives.length > 0 ? directives : null,
|
||||
animations: animations,
|
||||
styles: view.styles,
|
||||
styleUrls: view.styleUrls,
|
||||
pipes: view.pipes,
|
||||
encapsulation: view.encapsulation
|
||||
});
|
||||
|
||||
this._viewCache.set(component, view);
|
||||
return view;
|
||||
@ -112,6 +137,7 @@ export class MockViewResolver extends ViewResolver {
|
||||
}
|
||||
|
||||
function flattenArray(tree: any[], out: Array<Type | any[]>): void {
|
||||
if (!isPresent(tree)) return;
|
||||
for (var i = 0; i < tree.length; i++) {
|
||||
var item = resolveForwardRef(tree[i]);
|
||||
if (isArray(item)) {
|
||||
|
@ -40,7 +40,11 @@ export {
|
||||
wtfEndTimeRange,
|
||||
WtfScopeFn
|
||||
} from './src/profile/profile';
|
||||
|
||||
export {Type, enableProdMode} from "./src/facade/lang";
|
||||
export {EventEmitter} from "./src/facade/async";
|
||||
export {ExceptionHandler, WrappedException, BaseException} from "./src/facade/exceptions";
|
||||
export * from './private_export';
|
||||
|
||||
export * from './src/animation/metadata';
|
||||
export {AnimationPlayer} from './src/animation/animation_player';
|
||||
|
@ -21,6 +21,27 @@ import * as provider_util from './src/di/provider_util';
|
||||
import * as console from './src/console';
|
||||
import {Provider} from './index';
|
||||
|
||||
import {
|
||||
NoOpAnimationPlayer as NoOpAnimationPlayer_,
|
||||
AnimationPlayer as AnimationPlayer_
|
||||
} from './src/animation/animation_player';
|
||||
import {
|
||||
NoOpAnimationDriver as NoOpAnimationDriver_,
|
||||
AnimationDriver as AnimationDriver_
|
||||
} from './src/animation/animation_driver';
|
||||
import {AnimationSequencePlayer as AnimationSequencePlayer_} from './src/animation/animation_sequence_player';
|
||||
import {AnimationGroupPlayer as AnimationGroupPlayer_} from './src/animation/animation_group_player';
|
||||
import {AnimationKeyframe as AnimationKeyframe_} from './src/animation/animation_keyframe';
|
||||
import {AnimationStyleUtil as AnimationStyleUtil_} from './src/animation/animation_style_util';
|
||||
import {AnimationStyles as AnimationStyles_} from './src/animation/animation_styles';
|
||||
import {
|
||||
ANY_STATE as ANY_STATE_,
|
||||
EMPTY_STATE as EMPTY_STATE_,
|
||||
FILL_STYLE_FLAG as FILL_STYLE_FLAG_
|
||||
} from './src/animation/animation_constants';
|
||||
import {MockAnimationPlayer as MockAnimationPlayer_} from './testing/animation/mock_animation_player';
|
||||
import {MockAnimationDriver as MockAnimationDriver_} from './testing/animation/mock_animation_driver';
|
||||
|
||||
export declare namespace __core_private_types__ {
|
||||
export var isDefaultChangeDetectionStrategy: typeof constants.isDefaultChangeDetectionStrategy;
|
||||
export type ChangeDetectorState = constants.ChangeDetectorState;
|
||||
@ -82,6 +103,31 @@ export declare namespace __core_private_types__ {
|
||||
export var castByValue: typeof view_utils.castByValue;
|
||||
export type Console = console.Console;
|
||||
export var Console: typeof console.Console;
|
||||
export type NoOpAnimationPlayer = NoOpAnimationPlayer_;
|
||||
export var NoOpAnimationPlayer: typeof NoOpAnimationPlayer_;
|
||||
export type AnimationPlayer = AnimationPlayer_;
|
||||
export var AnimationPlayer: typeof AnimationPlayer_;
|
||||
export type NoOpAnimationDriver = NoOpAnimationDriver_;
|
||||
export var NoOpAnimationDriver: typeof NoOpAnimationDriver_;
|
||||
export type AnimationDriver = AnimationDriver_;
|
||||
export var AnimationDriver: typeof AnimationDriver_;
|
||||
export type AnimationSequencePlayer = AnimationSequencePlayer_;
|
||||
export var AnimationSequencePlayer: typeof AnimationSequencePlayer_;
|
||||
export type AnimationGroupPlayer = AnimationGroupPlayer_;
|
||||
export var AnimationGroupPlayer: typeof AnimationGroupPlayer_;
|
||||
export type AnimationKeyframe = AnimationKeyframe_;
|
||||
export var AnimationKeyframe: typeof AnimationKeyframe_;
|
||||
export type AnimationStyleUtil = AnimationStyleUtil_;
|
||||
export var AnimationStyleUtil: typeof AnimationStyleUtil_;
|
||||
export type AnimationStyles = AnimationStyles_;
|
||||
export var AnimationStyles: typeof AnimationStyles_;
|
||||
export var ANY_STATE: typeof ANY_STATE_;
|
||||
export var EMPTY_STATE: typeof EMPTY_STATE_;
|
||||
export var FILL_STYLE_FLAG: typeof FILL_STYLE_FLAG_;
|
||||
export type MockAnimationPlayer = MockAnimationPlayer_;
|
||||
export var MockAnimationPlayer: typeof MockAnimationPlayer_;
|
||||
export type MockAnimationDriver = MockAnimationDriver_;
|
||||
export var MockAnimationDriver: typeof MockAnimationDriver_;
|
||||
}
|
||||
|
||||
export var __core_private__ = {
|
||||
@ -132,4 +178,18 @@ export var __core_private__ = {
|
||||
pureProxy10: view_utils.pureProxy10,
|
||||
castByValue: view_utils.castByValue,
|
||||
Console: console.Console,
|
||||
NoOpAnimationPlayer: NoOpAnimationPlayer_,
|
||||
AnimationPlayer: AnimationPlayer_,
|
||||
NoOpAnimationDriver: NoOpAnimationDriver_,
|
||||
AnimationDriver: AnimationDriver_,
|
||||
AnimationSequencePlayer: AnimationSequencePlayer_,
|
||||
AnimationGroupPlayer: AnimationGroupPlayer_,
|
||||
AnimationKeyframe: AnimationKeyframe_,
|
||||
AnimationStyleUtil: AnimationStyleUtil_,
|
||||
AnimationStyles: AnimationStyles_,
|
||||
MockAnimationPlayer: MockAnimationPlayer_,
|
||||
MockAnimationDriver: MockAnimationDriver_,
|
||||
ANY_STATE: ANY_STATE_,
|
||||
EMPTY_STATE: EMPTY_STATE_,
|
||||
FILL_STYLE_FLAG: FILL_STYLE_FLAG_
|
||||
};
|
||||
|
@ -0,0 +1,57 @@
|
||||
import {AnimationPlayer} from './animation_player';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {ListWrapper, StringMapWrapper, Map} from '../facade/collection';
|
||||
|
||||
export class ActiveAnimationPlayersMap {
|
||||
private _map = new Map<any, {[key: string]: AnimationPlayer}>();
|
||||
private _allPlayers: AnimationPlayer[] = [];
|
||||
|
||||
get length(): number {
|
||||
return this.getAllPlayers().length;
|
||||
}
|
||||
|
||||
find(element: any, animationName: string): AnimationPlayer {
|
||||
var playersByAnimation = this._map.get(element);
|
||||
if (isPresent(playersByAnimation)) {
|
||||
return playersByAnimation[animationName];
|
||||
}
|
||||
}
|
||||
|
||||
findAllPlayersByElement(element: any): AnimationPlayer[] {
|
||||
var players = [];
|
||||
StringMapWrapper.forEach(this._map.get(element), player => players.push(player));
|
||||
return players;
|
||||
}
|
||||
|
||||
set(element: any, animationName: string, player: AnimationPlayer): void {
|
||||
var playersByAnimation = this._map.get(element);
|
||||
if (!isPresent(playersByAnimation)) {
|
||||
playersByAnimation = {};
|
||||
}
|
||||
var existingEntry = playersByAnimation[animationName];
|
||||
if (isPresent(existingEntry)) {
|
||||
this.remove(element, animationName);
|
||||
}
|
||||
playersByAnimation[animationName] = player;
|
||||
this._allPlayers.push(player);
|
||||
this._map.set(element, playersByAnimation);
|
||||
}
|
||||
|
||||
getAllPlayers(): AnimationPlayer[] {
|
||||
return this._allPlayers;
|
||||
}
|
||||
|
||||
remove(element: any, animationName: string): void {
|
||||
var playersByAnimation = this._map.get(element);
|
||||
if (isPresent(playersByAnimation)) {
|
||||
var player = playersByAnimation[animationName];
|
||||
delete playersByAnimation[animationName];
|
||||
var index = this._allPlayers.indexOf(player);
|
||||
ListWrapper.removeAt(this._allPlayers, index);
|
||||
|
||||
if (StringMapWrapper.isEmpty(playersByAnimation)) {
|
||||
this._map.delete(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export const FILL_STYLE_FLAG = 'true'; // TODO (matsko): change to boolean
|
||||
export const ANY_STATE = '*';
|
||||
export const EMPTY_STATE = 'void';
|
15
modules/@angular/core/src/animation/animation_driver.ts
Normal file
15
modules/@angular/core/src/animation/animation_driver.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import {NoOpAnimationPlayer, AnimationPlayer} from './animation_player';
|
||||
import {AnimationKeyframe} from './animation_keyframe';
|
||||
import {AnimationStyles} from './animation_styles';
|
||||
|
||||
export abstract class AnimationDriver {
|
||||
abstract animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number,
|
||||
easing: string): AnimationPlayer;
|
||||
}
|
||||
|
||||
export class NoOpAnimationDriver extends AnimationDriver {
|
||||
animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number,
|
||||
easing: string): AnimationPlayer {
|
||||
return new NoOpAnimationPlayer();
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
import {AnimationPlayer} from './animation_player';
|
||||
import {isPresent, scheduleMicroTask} from '../facade/lang';
|
||||
import {Math} from '../facade/math';
|
||||
|
||||
export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
private _subscriptions: Function[] = [];
|
||||
private _finished = false;
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
|
||||
constructor(private _players: AnimationPlayer[]) {
|
||||
var count = 0;
|
||||
var total = this._players.length;
|
||||
if (total == 0) {
|
||||
scheduleMicroTask(() => this._onFinish());
|
||||
} else {
|
||||
this._players.forEach(player => {
|
||||
player.parentPlayer = this;
|
||||
player.onDone(() => {
|
||||
if (++count >= total) {
|
||||
this._onFinish();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _onFinish() {
|
||||
if (!this._finished) {
|
||||
this._finished = true;
|
||||
if (!isPresent(this.parentPlayer)) {
|
||||
this.destroy();
|
||||
}
|
||||
this._subscriptions.forEach(subscription => subscription());
|
||||
this._subscriptions = [];
|
||||
}
|
||||
}
|
||||
|
||||
onDone(fn: Function): void { this._subscriptions.push(fn); }
|
||||
|
||||
play() { this._players.forEach(player => player.play()); }
|
||||
|
||||
pause(): void { this._players.forEach(player => player.pause()); }
|
||||
|
||||
restart(): void { this._players.forEach(player => player.restart()); }
|
||||
|
||||
finish(): void {
|
||||
this._onFinish();
|
||||
this._players.forEach(player => player.finish());
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this._onFinish();
|
||||
this._players.forEach(player => player.destroy());
|
||||
}
|
||||
|
||||
reset(): void { this._players.forEach(player => player.reset()); }
|
||||
|
||||
setPosition(p): void {
|
||||
this._players.forEach(player => {
|
||||
player.setPosition(p);
|
||||
});
|
||||
}
|
||||
|
||||
getPosition(): number {
|
||||
var min = 0;
|
||||
this._players.forEach(player => {
|
||||
var p = player.getPosition();
|
||||
min = Math.min(p, min);
|
||||
});
|
||||
return min;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import {AnimationStyles} from './animation_styles';
|
||||
|
||||
export class AnimationKeyframe {
|
||||
constructor(public offset: number, public styles: AnimationStyles) {}
|
||||
}
|
39
modules/@angular/core/src/animation/animation_player.ts
Normal file
39
modules/@angular/core/src/animation/animation_player.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import {scheduleMicroTask} from '../facade/lang';
|
||||
import {BaseException} from '../facade/exceptions';
|
||||
|
||||
export abstract class AnimationPlayer {
|
||||
abstract onDone(fn: Function): void;
|
||||
abstract play(): void;
|
||||
abstract pause(): void;
|
||||
abstract restart(): void;
|
||||
abstract finish(): void;
|
||||
abstract destroy(): void;
|
||||
abstract reset(): void;
|
||||
abstract setPosition(p): void;
|
||||
abstract getPosition(): number;
|
||||
get parentPlayer(): AnimationPlayer { throw new BaseException('NOT IMPLEMENTED: Base Class'); }
|
||||
set parentPlayer(player: AnimationPlayer) { throw new BaseException('NOT IMPLEMENTED: Base Class'); }
|
||||
}
|
||||
|
||||
export class NoOpAnimationPlayer implements AnimationPlayer {
|
||||
private _subscriptions = [];
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
constructor() {
|
||||
scheduleMicroTask(() => this._onFinish());
|
||||
}
|
||||
_onFinish() {
|
||||
this._subscriptions.forEach(entry => { entry(); });
|
||||
this._subscriptions = [];
|
||||
}
|
||||
onDone(fn: Function): void { this._subscriptions.push(fn); }
|
||||
play(): void {}
|
||||
pause(): void {}
|
||||
restart(): void {}
|
||||
finish(): void {
|
||||
this._onFinish();
|
||||
}
|
||||
destroy(): void {}
|
||||
reset(): void {}
|
||||
setPosition(p): void {}
|
||||
getPosition(): number { return 0; }
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {NoOpAnimationPlayer, AnimationPlayer} from './animation_player';
|
||||
import {scheduleMicroTask} from '../facade/lang';
|
||||
|
||||
export class AnimationSequencePlayer implements AnimationPlayer {
|
||||
private _currentIndex: number = 0;
|
||||
private _activePlayer: AnimationPlayer;
|
||||
private _subscriptions: Function[] = [];
|
||||
private _finished = false;
|
||||
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
|
||||
constructor(private _players: AnimationPlayer[]) {
|
||||
this._players.forEach(player => {
|
||||
player.parentPlayer = this;
|
||||
});
|
||||
this._onNext(false);
|
||||
}
|
||||
|
||||
private _onNext(start: boolean) {
|
||||
if (this._finished) return;
|
||||
|
||||
if (this._players.length == 0) {
|
||||
this._activePlayer = new NoOpAnimationPlayer();
|
||||
scheduleMicroTask(() => this._onFinish());
|
||||
} else if (this._currentIndex >= this._players.length) {
|
||||
this._activePlayer = new NoOpAnimationPlayer();
|
||||
this._onFinish();
|
||||
} else {
|
||||
var player = this._players[this._currentIndex++];
|
||||
player.onDone(() => this._onNext(true));
|
||||
|
||||
this._activePlayer = player;
|
||||
if (start) {
|
||||
player.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _onFinish() {
|
||||
if (!this._finished) {
|
||||
this._finished = true;
|
||||
if (!isPresent(this.parentPlayer)) {
|
||||
this.destroy();
|
||||
}
|
||||
this._subscriptions.forEach(subscription => subscription());
|
||||
this._subscriptions = [];
|
||||
}
|
||||
}
|
||||
|
||||
onDone(fn: Function): void { this._subscriptions.push(fn); }
|
||||
|
||||
play(): void { this._activePlayer.play(); }
|
||||
|
||||
pause(): void { this._activePlayer.pause(); }
|
||||
|
||||
restart(): void {
|
||||
if (this._players.length > 0) {
|
||||
this.reset();
|
||||
this._players[0].restart();
|
||||
}
|
||||
}
|
||||
|
||||
reset(): void { this._players.forEach(player => player.reset()); }
|
||||
|
||||
finish(): void {
|
||||
this._onFinish();
|
||||
this._players.forEach(player => player.finish());
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this._onFinish();
|
||||
this._players.forEach(player => player.destroy());
|
||||
}
|
||||
|
||||
setPosition(p): void {
|
||||
this._players[0].setPosition(p);
|
||||
}
|
||||
|
||||
getPosition(): number {
|
||||
return this._players[0].getPosition();
|
||||
}
|
||||
}
|
113
modules/@angular/core/src/animation/animation_style_util.ts
Normal file
113
modules/@angular/core/src/animation/animation_style_util.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import {isPresent, isArray} from '../facade/lang';
|
||||
import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
||||
import {AUTO_STYLE} from './metadata';
|
||||
import {FILL_STYLE_FLAG} from './animation_constants';
|
||||
|
||||
export class AnimationStyleUtil {
|
||||
static balanceStyles(previousStyles: {[key: string]: string|number},
|
||||
newStyles: {[key: string]: string|number},
|
||||
nullValue = null): {[key: string]: string|number} {
|
||||
var finalStyles: {[key: string]: string|number} = {};
|
||||
|
||||
StringMapWrapper.forEach(newStyles, (value, prop) => {
|
||||
finalStyles[prop] = value;
|
||||
});
|
||||
|
||||
StringMapWrapper.forEach(previousStyles, (value, prop) => {
|
||||
if (!isPresent(finalStyles[prop])) {
|
||||
finalStyles[prop] = nullValue;
|
||||
}
|
||||
});
|
||||
|
||||
return finalStyles;
|
||||
}
|
||||
static balanceKeyframes(collectedStyles: {[key: string]: string|number},
|
||||
finalStateStyles: {[key: string]: string|number},
|
||||
keyframes: any[]): any[] {
|
||||
var limit = keyframes.length - 1;
|
||||
var firstKeyframe = keyframes[0];
|
||||
|
||||
// phase 1: copy all the styles from the first keyframe into the lookup map
|
||||
var flatenedFirstKeyframeStyles = AnimationStyleUtil.flattenStyles(firstKeyframe.styles.styles);
|
||||
|
||||
var extraFirstKeyframeStyles = {};
|
||||
var hasExtraFirstStyles = false;
|
||||
StringMapWrapper.forEach(collectedStyles, (value, prop) => {
|
||||
// if the style is already defined in the first keyframe then
|
||||
// we do not replace it.
|
||||
if (!flatenedFirstKeyframeStyles[prop]) {
|
||||
flatenedFirstKeyframeStyles[prop] = value;
|
||||
extraFirstKeyframeStyles[prop] = value;
|
||||
hasExtraFirstStyles = true;
|
||||
}
|
||||
});
|
||||
|
||||
var keyframeCollectedStyles = StringMapWrapper.merge({}, flatenedFirstKeyframeStyles);
|
||||
|
||||
// phase 2: normalize the final keyframe
|
||||
var finalKeyframe = keyframes[limit];
|
||||
ListWrapper.insert(finalKeyframe.styles.styles, 0, finalStateStyles);
|
||||
|
||||
var flatenedFinalKeyframeStyles = AnimationStyleUtil.flattenStyles(finalKeyframe.styles.styles);
|
||||
var extraFinalKeyframeStyles = {};
|
||||
var hasExtraFinalStyles = false;
|
||||
StringMapWrapper.forEach(keyframeCollectedStyles, (value, prop) => {
|
||||
if (!isPresent(flatenedFinalKeyframeStyles[prop])) {
|
||||
extraFinalKeyframeStyles[prop] = AUTO_STYLE;
|
||||
hasExtraFinalStyles = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasExtraFinalStyles) {
|
||||
finalKeyframe.styles.styles.push(extraFinalKeyframeStyles);
|
||||
}
|
||||
|
||||
StringMapWrapper.forEach(flatenedFinalKeyframeStyles, (value, prop) => {
|
||||
if (!isPresent(flatenedFirstKeyframeStyles[prop])) {
|
||||
extraFirstKeyframeStyles[prop] = AUTO_STYLE;
|
||||
hasExtraFirstStyles = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (hasExtraFirstStyles) {
|
||||
firstKeyframe.styles.styles.push(extraFirstKeyframeStyles);
|
||||
}
|
||||
|
||||
return keyframes;
|
||||
}
|
||||
|
||||
static clearStyles(styles: {[key: string]: string|number}): {[key: string]: string|number} {
|
||||
var finalStyles: {[key: string]: string|number} = {};
|
||||
StringMapWrapper.keys(styles).forEach(key => {
|
||||
finalStyles[key] = null;
|
||||
});
|
||||
return finalStyles;
|
||||
}
|
||||
|
||||
static collectAndResolveStyles(collection: {[key: string]: string|number}, styles: {[key: string]: string|number}[]) {
|
||||
return styles.map(entry => {
|
||||
var stylesObj = {};
|
||||
StringMapWrapper.forEach(entry, (value, prop) => {
|
||||
if (value == FILL_STYLE_FLAG) {
|
||||
value = collection[prop];
|
||||
if (!isPresent(value)) {
|
||||
value = AUTO_STYLE;
|
||||
}
|
||||
}
|
||||
collection[prop] = value;
|
||||
stylesObj[prop] = value;
|
||||
});
|
||||
return stylesObj;
|
||||
});
|
||||
}
|
||||
|
||||
static flattenStyles(styles: {[key: string]: string|number}[]) {
|
||||
var finalStyles = {};
|
||||
styles.forEach(entry => {
|
||||
StringMapWrapper.forEach(entry, (value, prop) => {
|
||||
finalStyles[prop] = value;
|
||||
});
|
||||
});
|
||||
return finalStyles;
|
||||
}
|
||||
}
|
3
modules/@angular/core/src/animation/animation_styles.ts
Normal file
3
modules/@angular/core/src/animation/animation_styles.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export class AnimationStyles {
|
||||
constructor(public styles: {[key: string]: string | number}[]) {}
|
||||
}
|
116
modules/@angular/core/src/animation/metadata.ts
Normal file
116
modules/@angular/core/src/animation/metadata.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import {isPresent, isArray, isString, isStringMap, NumberWrapper} from '../facade/lang';
|
||||
import {BaseException} from '../facade/exceptions';
|
||||
|
||||
export const AUTO_STYLE = "*";
|
||||
|
||||
export class AnimationEntryMetadata {
|
||||
constructor(public name: string, public definitions: AnimationStateMetadata[]) {}
|
||||
}
|
||||
|
||||
export abstract class AnimationStateMetadata {}
|
||||
|
||||
export class AnimationStateDeclarationMetadata extends AnimationStateMetadata {
|
||||
constructor(public stateNameExpr: string, public styles: AnimationStyleMetadata) { super(); }
|
||||
}
|
||||
|
||||
export class AnimationStateTransitionMetadata extends AnimationStateMetadata {
|
||||
constructor(public stateChangeExpr: string, public animation: AnimationMetadata) { super(); }
|
||||
}
|
||||
|
||||
export abstract class AnimationMetadata {}
|
||||
|
||||
export class AnimationKeyframesSequenceMetadata extends AnimationMetadata {
|
||||
constructor(public steps: AnimationStyleMetadata[]) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
export class AnimationStyleMetadata extends AnimationMetadata {
|
||||
constructor(public styles: Array<string|{[key: string]: string | number}>, public offset: number = null) { super(); }
|
||||
}
|
||||
|
||||
export class AnimationAnimateMetadata extends AnimationMetadata {
|
||||
constructor(public timings: string | number,
|
||||
public styles: AnimationStyleMetadata|AnimationKeyframesSequenceMetadata) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class AnimationWithStepsMetadata extends AnimationMetadata {
|
||||
constructor() { super(); }
|
||||
get steps(): AnimationMetadata[] { throw new BaseException('NOT IMPLEMENTED: Base Class'); }
|
||||
}
|
||||
|
||||
export class AnimationSequenceMetadata extends AnimationWithStepsMetadata {
|
||||
constructor(private _steps: AnimationMetadata[]) { super(); }
|
||||
get steps(): AnimationMetadata[] { return this._steps; }
|
||||
}
|
||||
|
||||
export class AnimationGroupMetadata extends AnimationWithStepsMetadata {
|
||||
constructor(private _steps: AnimationMetadata[]) { super(); }
|
||||
get steps(): AnimationMetadata[] { return this._steps; }
|
||||
}
|
||||
|
||||
export function animate(timing: string | number,
|
||||
styles: AnimationStyleMetadata|AnimationKeyframesSequenceMetadata = null): AnimationAnimateMetadata {
|
||||
var stylesEntry = styles;
|
||||
if (!isPresent(stylesEntry)) {
|
||||
var EMPTY_STYLE: {[key: string]: string|number} = {};
|
||||
stylesEntry = new AnimationStyleMetadata([EMPTY_STYLE], 1);
|
||||
}
|
||||
return new AnimationAnimateMetadata(timing, stylesEntry);
|
||||
}
|
||||
|
||||
export function group(steps: AnimationMetadata[]): AnimationGroupMetadata {
|
||||
return new AnimationGroupMetadata(steps);
|
||||
}
|
||||
|
||||
export function sequence(steps: AnimationMetadata[]): AnimationSequenceMetadata {
|
||||
return new AnimationSequenceMetadata(steps);
|
||||
}
|
||||
|
||||
export function style(tokens: string|{[key: string]: string | number}|Array<string|{[key: string]: string | number}>): AnimationStyleMetadata {
|
||||
var input: Array<{[key: string]: string | number}|string>;
|
||||
var offset: number = null;
|
||||
if (isString(tokens)) {
|
||||
input = [<string>tokens];
|
||||
} else {
|
||||
if (isArray(tokens)) {
|
||||
input = <Array<{[key: string]: string | number}>>tokens;
|
||||
} else {
|
||||
input = [<{[key: string]: string | number}>tokens];
|
||||
}
|
||||
input.forEach(entry => {
|
||||
var entryOffset = entry['offset'];
|
||||
if (isPresent(entryOffset)) {
|
||||
offset = offset == null ? NumberWrapper.parseFloat(entryOffset) : offset;
|
||||
}
|
||||
});
|
||||
}
|
||||
return new AnimationStyleMetadata(input, offset);
|
||||
}
|
||||
|
||||
export function state(stateNameExpr: string, styles: AnimationStyleMetadata): AnimationStateDeclarationMetadata {
|
||||
return new AnimationStateDeclarationMetadata(stateNameExpr, styles);
|
||||
}
|
||||
|
||||
export function keyframes(steps: AnimationStyleMetadata|AnimationStyleMetadata[]): AnimationKeyframesSequenceMetadata {
|
||||
var stepData = isArray(steps)
|
||||
? <AnimationStyleMetadata[]>steps
|
||||
: [<AnimationStyleMetadata>steps];
|
||||
return new AnimationKeyframesSequenceMetadata(stepData);
|
||||
}
|
||||
|
||||
export function transition(stateChangeExpr: string, animationData: AnimationMetadata|AnimationMetadata[]): AnimationStateTransitionMetadata {
|
||||
var animation = isArray(animationData)
|
||||
? new AnimationSequenceMetadata(<AnimationMetadata[]>animationData)
|
||||
: <AnimationMetadata>animationData;
|
||||
return new AnimationStateTransitionMetadata(stateChangeExpr, animation);
|
||||
}
|
||||
|
||||
export function trigger(name: string, animation: AnimationMetadata|AnimationMetadata[]): AnimationEntryMetadata {
|
||||
var entry = isArray(animation)
|
||||
? <AnimationMetadata[]>animation
|
||||
: [<AnimationMetadata>animation];
|
||||
return new AnimationEntryMetadata(name, entry);
|
||||
}
|
@ -9,6 +9,10 @@ import {
|
||||
removeDebugNodeFromIndex
|
||||
} from './debug_node';
|
||||
|
||||
import {AnimationKeyframe} from '../animation/animation_keyframe';
|
||||
import {AnimationStyles} from '../animation/animation_styles';
|
||||
import {AnimationPlayer} from '../animation/animation_player';
|
||||
|
||||
export class DebugDomRootRenderer implements RootRenderer {
|
||||
constructor(private _delegate: RootRenderer) {}
|
||||
|
||||
@ -137,4 +141,8 @@ export class DebugDomRenderer implements Renderer {
|
||||
}
|
||||
|
||||
setText(renderNode: any, text: string) { this._delegate.setText(renderNode, text); }
|
||||
|
||||
animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string): AnimationPlayer {
|
||||
return this._delegate.animate(element, startingStyles, keyframes, duration, delay, easing);
|
||||
}
|
||||
}
|
||||
|
@ -81,8 +81,7 @@ export class AppElement {
|
||||
if (view.type === ViewType.COMPONENT) {
|
||||
throw new BaseException(`Component views can't be moved!`);
|
||||
}
|
||||
|
||||
view.renderer.detachView(view.flatRootNodes);
|
||||
view.detach();
|
||||
|
||||
view.removeFromContentChildren(this);
|
||||
return view;
|
||||
|
@ -1,7 +1,9 @@
|
||||
import {
|
||||
ListWrapper,
|
||||
StringMapWrapper,
|
||||
} from '../../src/facade/collection';
|
||||
Map,
|
||||
MapWrapper
|
||||
} from '../facade/collection';
|
||||
|
||||
import {AppElement} from './element';
|
||||
import {
|
||||
@ -14,9 +16,9 @@ import {
|
||||
stringify,
|
||||
isPrimitive,
|
||||
isString
|
||||
} from '../../src/facade/lang';
|
||||
} from '../facade/lang';
|
||||
|
||||
import {ObservableWrapper} from '../../src/facade/async';
|
||||
import {ObservableWrapper} from '../facade/async';
|
||||
import {Renderer, RootRenderer, RenderComponentType, RenderDebugInfo} from '../render/api';
|
||||
import {ViewRef_} from './view_ref';
|
||||
|
||||
@ -43,6 +45,14 @@ import {StaticNodeDebugInfo, DebugContext} from './debug_context';
|
||||
import {ElementInjector} from './element_injector';
|
||||
import {Injector} from '../di/injector';
|
||||
|
||||
import {AUTO_STYLE} from '../animation/metadata';
|
||||
import {AnimationPlayer} from '../animation/animation_player';
|
||||
import {AnimationGroupPlayer} from '../animation/animation_group_player';
|
||||
import {AnimationKeyframe} from '../animation/animation_keyframe';
|
||||
import {AnimationStyles} from '../animation/animation_styles';
|
||||
import {AnimationDriver} from '../animation/animation_driver';
|
||||
import {ActiveAnimationPlayersMap} from '../animation/active_animation_players_map';
|
||||
|
||||
var _scope_check: WtfScopeFn = wtfCreateScope(`AppView#check(ascii id)`);
|
||||
|
||||
/**
|
||||
@ -71,6 +81,8 @@ export abstract class AppView<T> {
|
||||
|
||||
private _hasExternalHostElement: boolean;
|
||||
|
||||
public activeAnimationPlayers = new ActiveAnimationPlayersMap();
|
||||
|
||||
public context: T;
|
||||
|
||||
constructor(public clazz: any, public componentType: RenderComponentType, public type: ViewType,
|
||||
@ -84,6 +96,25 @@ export abstract class AppView<T> {
|
||||
}
|
||||
}
|
||||
|
||||
cancelActiveAnimation(element: any, animationName: string, removeAllAnimations: boolean = false) {
|
||||
if (removeAllAnimations) {
|
||||
this.activeAnimationPlayers.findAllPlayersByElement(element).forEach(player => player.destroy());
|
||||
} else {
|
||||
var player = this.activeAnimationPlayers.find(element, animationName);
|
||||
if (isPresent(player)) {
|
||||
player.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerAndStartAnimation(element: any, animationName: string, player: AnimationPlayer): void {
|
||||
this.activeAnimationPlayers.set(element, animationName, player);
|
||||
player.onDone(() => {
|
||||
this.activeAnimationPlayers.remove(element, animationName);
|
||||
});
|
||||
player.play();
|
||||
}
|
||||
|
||||
create(context: T, givenProjectableNodes: Array<any | any[]>,
|
||||
rootSelectorOrNode: string | any): AppElement {
|
||||
this.context = context;
|
||||
@ -193,7 +224,15 @@ export abstract class AppView<T> {
|
||||
}
|
||||
this.destroyInternal();
|
||||
this.dirtyParentQueriesInternal();
|
||||
this.renderer.destroyView(hostElement, this.allNodes);
|
||||
|
||||
if (this.activeAnimationPlayers.length == 0) {
|
||||
this.renderer.destroyView(hostElement, this.allNodes);
|
||||
} else {
|
||||
var player = new AnimationGroupPlayer(this.activeAnimationPlayers.getAllPlayers());
|
||||
player.onDone(() => {
|
||||
this.renderer.destroyView(hostElement, this.allNodes);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -201,6 +240,23 @@ export abstract class AppView<T> {
|
||||
*/
|
||||
destroyInternal(): void {}
|
||||
|
||||
/**
|
||||
* Overwritten by implementations
|
||||
*/
|
||||
detachInternal(): void {}
|
||||
|
||||
detach(): void {
|
||||
this.detachInternal();
|
||||
if (this.activeAnimationPlayers.length == 0) {
|
||||
this.renderer.detachView(this.flatRootNodes);
|
||||
} else {
|
||||
var player = new AnimationGroupPlayer(this.activeAnimationPlayers.getAllPlayers());
|
||||
player.onDone(() => {
|
||||
this.renderer.detachView(this.flatRootNodes);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get changeDetectorRef(): ChangeDetectorRef { return this.ref; }
|
||||
|
||||
get parent(): AppView<any> {
|
||||
@ -319,6 +375,16 @@ export class DebugAppView<T> extends AppView<T> {
|
||||
}
|
||||
}
|
||||
|
||||
detach(): void {
|
||||
this._resetDebug();
|
||||
try {
|
||||
super.detach();
|
||||
} catch (e) {
|
||||
this._rethrowWithContext(e, e.stack);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
destroyLocal() {
|
||||
this._resetDebug();
|
||||
try {
|
||||
|
@ -5,6 +5,7 @@ import 'package:angular2/src/core/change_detection/change_detection.dart';
|
||||
import './metadata/di.dart';
|
||||
import './metadata/directives.dart';
|
||||
import './metadata/view.dart';
|
||||
import './metadata/animations.dart' show AnimationEntryMetadata;
|
||||
|
||||
export './metadata/di.dart';
|
||||
export './metadata/directives.dart';
|
||||
@ -72,7 +73,8 @@ class Component extends ComponentMetadata {
|
||||
dynamic pipes,
|
||||
ViewEncapsulation encapsulation,
|
||||
List<String> styles,
|
||||
List<String> styleUrls})
|
||||
List<String> styleUrls,
|
||||
List<AnimationEntryMetadata> animations})
|
||||
: super(
|
||||
selector: selector,
|
||||
inputs: inputs,
|
||||
@ -92,7 +94,8 @@ class Component extends ComponentMetadata {
|
||||
pipes: pipes,
|
||||
encapsulation: encapsulation,
|
||||
styles: styles,
|
||||
styleUrls: styleUrls);
|
||||
styleUrls: styleUrls,
|
||||
animations: animations);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,7 +109,8 @@ class View extends ViewMetadata {
|
||||
dynamic pipes,
|
||||
ViewEncapsulation encapsulation,
|
||||
List<String> styles,
|
||||
List<String> styleUrls})
|
||||
List<String> styleUrls,
|
||||
List<AnimationEntryMetadata> animations})
|
||||
: super(
|
||||
templateUrl: templateUrl,
|
||||
template: template,
|
||||
@ -114,7 +118,8 @@ class View extends ViewMetadata {
|
||||
pipes: pipes,
|
||||
encapsulation: encapsulation,
|
||||
styles: styles,
|
||||
styleUrls: styleUrls);
|
||||
styleUrls: styleUrls,
|
||||
animations: animations);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,7 +57,8 @@ import {
|
||||
} from './metadata/directives';
|
||||
|
||||
import {ViewMetadata, ViewEncapsulation} from './metadata/view';
|
||||
import {ChangeDetectionStrategy} from './change_detection/change_detection';
|
||||
import {AnimationEntryMetadata} from './animation/metadata';
|
||||
import {ChangeDetectionStrategy} from '../src/change_detection/change_detection';
|
||||
|
||||
import {
|
||||
makeDecorator,
|
||||
@ -91,6 +92,7 @@ export interface ComponentDecorator extends TypeDecorator {
|
||||
renderer?: string,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
animations?: AnimationEntryMetadata[]
|
||||
}): ViewDecorator;
|
||||
}
|
||||
|
||||
@ -111,6 +113,7 @@ export interface ViewDecorator extends TypeDecorator {
|
||||
renderer?: string,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
animations?: AnimationEntryMetadata[]
|
||||
}): ViewDecorator;
|
||||
}
|
||||
|
||||
@ -219,6 +222,7 @@ export interface ComponentMetadataFactory {
|
||||
template?: string,
|
||||
styleUrls?: string[],
|
||||
styles?: string[],
|
||||
animations?: AnimationEntryMetadata[],
|
||||
directives?: Array<Type | any[]>,
|
||||
pipes?: Array<Type | any[]>,
|
||||
encapsulation?: ViewEncapsulation
|
||||
@ -240,6 +244,7 @@ export interface ComponentMetadataFactory {
|
||||
template?: string,
|
||||
styleUrls?: string[],
|
||||
styles?: string[],
|
||||
animations?: AnimationEntryMetadata[],
|
||||
directives?: Array<Type | any[]>,
|
||||
pipes?: Array<Type | any[]>,
|
||||
encapsulation?: ViewEncapsulation
|
||||
@ -297,6 +302,7 @@ export interface ViewMetadataFactory {
|
||||
encapsulation?: ViewEncapsulation,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
animations?: AnimationEntryMetadata[]
|
||||
}): ViewDecorator;
|
||||
new (obj: {
|
||||
templateUrl?: string,
|
||||
@ -306,6 +312,7 @@ export interface ViewMetadataFactory {
|
||||
encapsulation?: ViewEncapsulation,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
animations?: AnimationEntryMetadata[]
|
||||
}): ViewMetadata;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import {isPresent, Type} from '../../src/facade/lang';
|
||||
import {InjectableMetadata} from '../di/metadata';
|
||||
import {ViewEncapsulation} from './view';
|
||||
import {ChangeDetectionStrategy} from '../change_detection/constants';
|
||||
import {AnimationEntryMetadata} from '../animation/metadata';
|
||||
|
||||
/**
|
||||
* Directives allow you to attach behavior to elements in the DOM.
|
||||
@ -872,6 +873,8 @@ export class ComponentMetadata extends DirectiveMetadata {
|
||||
|
||||
styles: string[];
|
||||
|
||||
animations: AnimationEntryMetadata[];
|
||||
|
||||
directives: Array<Type | any[]>;
|
||||
|
||||
pipes: Array<Type | any[]>;
|
||||
@ -881,7 +884,7 @@ export class ComponentMetadata extends DirectiveMetadata {
|
||||
constructor({selector, inputs, outputs, properties, events, host, exportAs, moduleId,
|
||||
providers, viewProviders,
|
||||
changeDetection = ChangeDetectionStrategy.Default, queries, templateUrl, template,
|
||||
styleUrls, styles, directives, pipes, encapsulation}: {
|
||||
styleUrls, styles, animations, directives, pipes, encapsulation}: {
|
||||
selector?: string,
|
||||
inputs?: string[],
|
||||
outputs?: string[],
|
||||
@ -898,6 +901,7 @@ export class ComponentMetadata extends DirectiveMetadata {
|
||||
template?: string,
|
||||
styleUrls?: string[],
|
||||
styles?: string[],
|
||||
animations?: AnimationEntryMetadata[],
|
||||
directives?: Array<Type | any[]>,
|
||||
pipes?: Array<Type | any[]>,
|
||||
encapsulation?: ViewEncapsulation
|
||||
@ -924,6 +928,7 @@ export class ComponentMetadata extends DirectiveMetadata {
|
||||
this.pipes = pipes;
|
||||
this.encapsulation = encapsulation;
|
||||
this.moduleId = moduleId;
|
||||
this.animations = animations;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {Type} from '../../src/facade/lang';
|
||||
import {AnimationEntryMetadata} from '../animation/metadata';
|
||||
|
||||
/**
|
||||
* Defines template and style encapsulation options available for Component's {@link View}.
|
||||
@ -123,7 +124,10 @@ export class ViewMetadata {
|
||||
*/
|
||||
encapsulation: ViewEncapsulation;
|
||||
|
||||
constructor({templateUrl, template, directives, pipes, encapsulation, styles, styleUrls}: {
|
||||
animations: AnimationEntryMetadata[];
|
||||
|
||||
constructor({templateUrl, template, directives, pipes, encapsulation, styles, styleUrls,
|
||||
animations}: {
|
||||
templateUrl?: string,
|
||||
template?: string,
|
||||
directives?: Array<Type | any[]>,
|
||||
@ -131,6 +135,7 @@ export class ViewMetadata {
|
||||
encapsulation?: ViewEncapsulation,
|
||||
styles?: string[],
|
||||
styleUrls?: string[],
|
||||
animations?: AnimationEntryMetadata[]
|
||||
} = {}) {
|
||||
this.templateUrl = templateUrl;
|
||||
this.template = template;
|
||||
@ -139,5 +144,6 @@ export class ViewMetadata {
|
||||
this.directives = directives;
|
||||
this.pipes = pipes;
|
||||
this.encapsulation = encapsulation;
|
||||
this.animations = animations;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
import {unimplemented} from '../../src/facade/exceptions';
|
||||
import {ViewEncapsulation} from '../metadata/view';
|
||||
import {Injector} from '../di/injector';
|
||||
import {AnimationKeyframe} from '../../src/animation/animation_keyframe';
|
||||
import {AnimationPlayer} from '../../src/animation/animation_player';
|
||||
import {AnimationStyles} from '../../src/animation/animation_styles';
|
||||
|
||||
export class RenderComponentType {
|
||||
constructor(public id: string, public templateUrl: string, public slotCount: number,
|
||||
@ -59,6 +62,8 @@ export abstract class Renderer {
|
||||
abstract invokeElementMethod(renderElement: any, methodName: string, args: any[]);
|
||||
|
||||
abstract setText(renderNode: any, text: string);
|
||||
|
||||
abstract animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string): AnimationPlayer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,85 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit
|
||||
} from '../../testing/testing_internal';
|
||||
|
||||
import {
|
||||
fakeAsync,
|
||||
flushMicrotasks
|
||||
} from '../../testing';
|
||||
|
||||
import {el} from '@angular/platform-browser/testing';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
||||
import {isPresent} from '../../src/facade/lang';
|
||||
import {MockAnimationPlayer} from '../../testing/animation/mock_animation_player';
|
||||
import {ActiveAnimationPlayersMap} from '../../src/animation/active_animation_players_map';
|
||||
|
||||
export function main() {
|
||||
describe('ActiveAnimationsPlayersMap', function() {
|
||||
var playersMap;
|
||||
var elementNode;
|
||||
var animationName = 'animationName';
|
||||
|
||||
beforeEach(() => {
|
||||
playersMap = new ActiveAnimationPlayersMap();
|
||||
elementNode = el('<div></div>');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
getDOM().remove(elementNode);
|
||||
elementNode = null;
|
||||
});
|
||||
|
||||
it('should register a player an allow it to be accessed', () => {
|
||||
var player = new MockAnimationPlayer();
|
||||
playersMap.set(elementNode, animationName, player);
|
||||
|
||||
expect(playersMap.find(elementNode, animationName)).toBe(player);
|
||||
expect(playersMap.findAllPlayersByElement(elementNode)).toEqual([player]);
|
||||
expect(playersMap.getAllPlayers()).toEqual([player]);
|
||||
expect(playersMap.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should remove a registered player when remove() is called', () => {
|
||||
var player = new MockAnimationPlayer();
|
||||
playersMap.set(elementNode, animationName, player);
|
||||
expect(playersMap.find(elementNode, animationName)).toBe(player);
|
||||
expect(playersMap.length).toEqual(1);
|
||||
playersMap.remove(elementNode, animationName);
|
||||
expect(playersMap.find(elementNode, animationName)).not.toBe(player);
|
||||
expect(playersMap.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should allow multiple players to be registered on the same element', () => {
|
||||
var player1 = new MockAnimationPlayer();
|
||||
var player2 = new MockAnimationPlayer();
|
||||
playersMap.set(elementNode, 'myAnimation1', player1);
|
||||
playersMap.set(elementNode, 'myAnimation2', player2);
|
||||
expect(playersMap.length).toEqual(2);
|
||||
expect(playersMap.findAllPlayersByElement(elementNode)).toEqual([
|
||||
player1,
|
||||
player2
|
||||
]);
|
||||
});
|
||||
|
||||
it('should only allow one player to be set for a given element/animationName pair', () => {
|
||||
var player1 = new MockAnimationPlayer();
|
||||
var player2 = new MockAnimationPlayer();
|
||||
playersMap.set(elementNode, animationName, player1);
|
||||
expect(playersMap.find(elementNode, animationName)).toBe(player1);
|
||||
expect(playersMap.length).toEqual(1);
|
||||
playersMap.set(elementNode, animationName, player2);
|
||||
expect(playersMap.find(elementNode, animationName)).toBe(player2);
|
||||
expect(playersMap.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit
|
||||
} from '../../testing/testing_internal';
|
||||
|
||||
import {
|
||||
fakeAsync,
|
||||
flushMicrotasks
|
||||
} from '../../testing';
|
||||
|
||||
import {isPresent} from '../../src/facade/lang';
|
||||
import {AnimationGroupPlayer} from '../../src/animation/animation_group_player';
|
||||
import {MockAnimationPlayer} from '../../testing/animation/mock_animation_player';
|
||||
|
||||
export function main() {
|
||||
describe('AnimationGroupPlayer', function() {
|
||||
var players;
|
||||
beforeEach(() => {
|
||||
players = [
|
||||
new MockAnimationPlayer(),
|
||||
new MockAnimationPlayer(),
|
||||
new MockAnimationPlayer(),
|
||||
];
|
||||
});
|
||||
|
||||
var assertLastStatus =
|
||||
(player: MockAnimationPlayer, status: string, match: boolean, iOffset: number = 0) => {
|
||||
var index = player.log.length - 1 + iOffset;
|
||||
var actual = player.log.length > 0 ? player.log[index] : null;
|
||||
if (match) {
|
||||
expect(actual).toEqual(status);
|
||||
} else {
|
||||
expect(actual).not.toEqual(status);
|
||||
}
|
||||
}
|
||||
|
||||
var assertPlaying = (player: MockAnimationPlayer, isPlaying: boolean) => {
|
||||
assertLastStatus(player, 'play', isPlaying);
|
||||
};
|
||||
|
||||
it('should play and pause all players in parallel', () => {
|
||||
var group = new AnimationGroupPlayer(players);
|
||||
|
||||
assertPlaying(players[0], false);
|
||||
assertPlaying(players[1], false);
|
||||
assertPlaying(players[2], false);
|
||||
|
||||
group.play();
|
||||
|
||||
assertPlaying(players[0], true);
|
||||
assertPlaying(players[1], true);
|
||||
assertPlaying(players[2], true);
|
||||
|
||||
group.pause();
|
||||
|
||||
assertPlaying(players[0], false);
|
||||
assertPlaying(players[1], false);
|
||||
assertPlaying(players[2], false);
|
||||
});
|
||||
|
||||
it('should finish when all players have finished', () => {
|
||||
var group = new AnimationGroupPlayer(players);
|
||||
var completed = false;
|
||||
group.onDone(() => completed = true);
|
||||
|
||||
group.play();
|
||||
|
||||
expect(completed).toBeFalsy();
|
||||
|
||||
players[0].finish();
|
||||
|
||||
expect(completed).toBeFalsy();
|
||||
|
||||
players[1].finish();
|
||||
|
||||
expect(completed).toBeFalsy();
|
||||
|
||||
players[2].finish();
|
||||
|
||||
expect(completed).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should restart all the players', () => {
|
||||
var group = new AnimationGroupPlayer(players);
|
||||
|
||||
group.play();
|
||||
|
||||
assertLastStatus(players[0], 'restart', false);
|
||||
assertLastStatus(players[1], 'restart', false);
|
||||
assertLastStatus(players[2], 'restart', false);
|
||||
|
||||
group.restart();
|
||||
|
||||
assertLastStatus(players[0], 'restart', true);
|
||||
assertLastStatus(players[1], 'restart', true);
|
||||
assertLastStatus(players[2], 'restart', true);
|
||||
});
|
||||
|
||||
it('should finish all the players', () => {
|
||||
var group = new AnimationGroupPlayer(players);
|
||||
|
||||
var completed = false;
|
||||
group.onDone(() => completed = true);
|
||||
|
||||
expect(completed).toBeFalsy();
|
||||
|
||||
group.play();
|
||||
|
||||
assertLastStatus(players[0], 'finish', false);
|
||||
assertLastStatus(players[1], 'finish', false);
|
||||
assertLastStatus(players[2], 'finish', false);
|
||||
|
||||
expect(completed).toBeFalsy();
|
||||
|
||||
group.finish();
|
||||
|
||||
assertLastStatus(players[0], 'finish', true, -1);
|
||||
assertLastStatus(players[1], 'finish', true, -1);
|
||||
assertLastStatus(players[2], 'finish', true, -1);
|
||||
|
||||
assertLastStatus(players[0], 'destroy', true);
|
||||
assertLastStatus(players[1], 'destroy', true);
|
||||
assertLastStatus(players[2], 'destroy', true);
|
||||
|
||||
expect(completed).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call destroy automatically when finished if no parent player is present', () => {
|
||||
var group = new AnimationGroupPlayer(players);
|
||||
|
||||
group.play();
|
||||
|
||||
assertLastStatus(players[0], 'destroy', false);
|
||||
assertLastStatus(players[1], 'destroy', false);
|
||||
assertLastStatus(players[2], 'destroy', false);
|
||||
|
||||
group.finish();
|
||||
|
||||
assertLastStatus(players[0], 'destroy', true);
|
||||
assertLastStatus(players[1], 'destroy', true);
|
||||
assertLastStatus(players[2], 'destroy', true);
|
||||
});
|
||||
|
||||
it('should not call destroy automatically when finished if a parent player is present', () => {
|
||||
var group = new AnimationGroupPlayer(players);
|
||||
var parent = new AnimationGroupPlayer([group, new MockAnimationPlayer()]);
|
||||
|
||||
group.play();
|
||||
|
||||
assertLastStatus(players[0], 'destroy', false);
|
||||
assertLastStatus(players[1], 'destroy', false);
|
||||
assertLastStatus(players[2], 'destroy', false);
|
||||
|
||||
group.finish();
|
||||
|
||||
assertLastStatus(players[0], 'destroy', false);
|
||||
assertLastStatus(players[1], 'destroy', false);
|
||||
assertLastStatus(players[2], 'destroy', false);
|
||||
|
||||
parent.finish();
|
||||
|
||||
assertLastStatus(players[0], 'destroy', true);
|
||||
assertLastStatus(players[1], 'destroy', true);
|
||||
assertLastStatus(players[2], 'destroy', true);
|
||||
});
|
||||
|
||||
it('should function without any players', () => {
|
||||
var group = new AnimationGroupPlayer([]);
|
||||
group.onDone(() => {});
|
||||
group.pause();
|
||||
group.play();
|
||||
group.finish();
|
||||
group.restart();
|
||||
group.destroy();
|
||||
});
|
||||
|
||||
it('should call onDone after the next microtask if no players are provided', fakeAsync(() => {
|
||||
var group = new AnimationGroupPlayer([]);
|
||||
var completed = false;
|
||||
group.onDone(() => completed = true);
|
||||
expect(completed).toEqual(false);
|
||||
flushMicrotasks();
|
||||
expect(completed).toEqual(true);
|
||||
}));
|
||||
});
|
||||
}
|
@ -0,0 +1,899 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
beforeEachProviders
|
||||
} from '../../testing/testing_internal';
|
||||
|
||||
import {TestComponentBuilder} from '@angular/compiler/testing';
|
||||
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
||||
import {
|
||||
fakeAsync,
|
||||
flushMicrotasks,
|
||||
tick
|
||||
} from '../../testing';
|
||||
|
||||
import {isPresent, isArray, IS_DART} from '../../src/facade/lang';
|
||||
|
||||
import {provide, Component} from '../../index';
|
||||
|
||||
import {NgIf, NgFor, AsyncPipe} from '@angular/common';
|
||||
|
||||
import {CompilerConfig} from '@angular/compiler';
|
||||
import {AnimationDriver} from '../../src/animation/animation_driver';
|
||||
import {MockAnimationDriver} from '../../testing/animation/mock_animation_driver';
|
||||
import {trigger, state, transition, keyframes, style, animate, group, sequence, AnimationEntryMetadata} from '../../src/animation/metadata';
|
||||
|
||||
import {AnimationStyleUtil} from '../../src/animation/animation_style_util';
|
||||
|
||||
import {AUTO_STYLE} from '../../src/animation/metadata';
|
||||
|
||||
export function main() {
|
||||
if (IS_DART) {
|
||||
declareTests();
|
||||
} else {
|
||||
describe('jit', () => {
|
||||
beforeEachProviders(
|
||||
() => [provide(CompilerConfig, {useValue: new CompilerConfig(true, false, true)})]);
|
||||
declareTests();
|
||||
});
|
||||
|
||||
describe('no jit', () => {
|
||||
beforeEachProviders(
|
||||
() => [provide(CompilerConfig, {useValue: new CompilerConfig(true, false, false)})]);
|
||||
declareTests();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function declareTests() {
|
||||
describe('animation tests', function() {
|
||||
beforeEachProviders(() => [provide(AnimationDriver, {useClass: MockAnimationDriver})]);
|
||||
|
||||
var makeAnimationCmp = (tcb: TestComponentBuilder, tpl: string, animationEntry: AnimationEntryMetadata|AnimationEntryMetadata[], callback = null) => {
|
||||
var entries = isArray(animationEntry)
|
||||
? <AnimationEntryMetadata[]>animationEntry
|
||||
: [<AnimationEntryMetadata>animationEntry];
|
||||
tcb = tcb.overrideTemplate(DummyIfCmp, tpl);
|
||||
tcb = tcb.overrideAnimations(DummyIfCmp, entries);
|
||||
tcb.createAsync(DummyIfCmp).then((root) => { callback(root); });
|
||||
tick();
|
||||
};
|
||||
|
||||
describe('animation triggers', () => {
|
||||
it('should trigger a state change animation from void => state',
|
||||
inject([TestComponentBuilder, AnimationDriver],
|
||||
fakeAsync(
|
||||
(tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
makeAnimationCmp(tcb, '<div *ngIf="exp" @myAnimation="exp"></div>',
|
||||
trigger('myAnimation', [
|
||||
transition('void => *', [
|
||||
style({'opacity': 0}),
|
||||
animate(500,
|
||||
style({'opacity': 1}))
|
||||
])
|
||||
]),
|
||||
(fixture) => {
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(1);
|
||||
|
||||
var keyframes2 = driver.log[0]['keyframeLookup'];
|
||||
expect(keyframes2.length).toEqual(2);
|
||||
expect(keyframes2[0]).toEqual([0, {'opacity': 0}]);
|
||||
expect(keyframes2[1]).toEqual([1, {'opacity': 1}]);
|
||||
});
|
||||
})));
|
||||
|
||||
it('should trigger a state change animation from state => void',
|
||||
inject([TestComponentBuilder, AnimationDriver],
|
||||
fakeAsync(
|
||||
(tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
makeAnimationCmp(tcb, '<div *ngIf="exp" @myAnimation="exp"></div>',
|
||||
trigger('myAnimation', [
|
||||
transition('* => void', [
|
||||
style({'opacity': 1}),
|
||||
animate(500,
|
||||
style({'opacity': 0}))
|
||||
])
|
||||
]),
|
||||
(fixture) => {
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
cmp.exp = false;
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(1);
|
||||
|
||||
var keyframes2 = driver.log[0]['keyframeLookup'];
|
||||
expect(keyframes2.length).toEqual(2);
|
||||
expect(keyframes2[0]).toEqual([0, {'opacity': 1}]);
|
||||
expect(keyframes2[1]).toEqual([1, {'opacity': 0}]);
|
||||
});
|
||||
})));
|
||||
|
||||
it('should animate the element when the expression changes between states',
|
||||
inject([TestComponentBuilder, AnimationDriver],
|
||||
fakeAsync(
|
||||
(tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
tcb.overrideAnimations(DummyIfCmp, [
|
||||
trigger("myAnimation", [
|
||||
transition("* => state1", [
|
||||
style({'background': 'red'}),
|
||||
animate('0.5s 1s ease-out',
|
||||
style({'background': 'blue'}))
|
||||
])
|
||||
])
|
||||
]).createAsync(DummyIfCmp)
|
||||
.then((fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = 'state1';
|
||||
fixture.detectChanges();
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(1);
|
||||
|
||||
var animation1 = driver.log[0];
|
||||
expect(animation1['duration']).toEqual(500);
|
||||
expect(animation1['delay']).toEqual(1000);
|
||||
expect(animation1['easing']).toEqual('ease-out');
|
||||
|
||||
var startingStyles = animation1['startingStyles'];
|
||||
expect(startingStyles).toEqual({'background': 'red'});
|
||||
|
||||
var keyframes = animation1['keyframeLookup'];
|
||||
expect(keyframes[0]).toEqual([0, {'background': 'red'}]);
|
||||
expect(keyframes[1]).toEqual([1, {'background': 'blue'}]);
|
||||
});
|
||||
})));
|
||||
|
||||
it('should combine repeated style steps into a single step',
|
||||
inject(
|
||||
[TestComponentBuilder, AnimationDriver],
|
||||
fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
tcb.overrideAnimations(DummyIfCmp, [
|
||||
trigger("myAnimation", [
|
||||
transition("void => *", [
|
||||
style({'background': 'red'}),
|
||||
style({'width': '100px'}),
|
||||
style({'background': 'gold'}),
|
||||
style({'height': 111}),
|
||||
animate('999ms', style({'width': '200px', 'background': 'blue'})),
|
||||
style({'opacity': '1'}),
|
||||
style({'border-width': '100px'}),
|
||||
animate('999ms', style({'opacity': '0', 'height': '200px', 'border-width': '10px'}))
|
||||
])
|
||||
])
|
||||
])
|
||||
.createAsync(DummyIfCmp)
|
||||
.then((fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(2);
|
||||
|
||||
var animation1 = driver.log[0];
|
||||
expect(animation1['duration']).toEqual(999);
|
||||
expect(animation1['delay']).toEqual(0);
|
||||
expect(animation1['easing']).toEqual(null);
|
||||
expect(animation1['startingStyles'])
|
||||
.toEqual({'background': 'gold', 'width': '100px', 'height': 111});
|
||||
|
||||
var keyframes1 = animation1['keyframeLookup'];
|
||||
expect(keyframes1[0]).toEqual([0, {'background': 'gold', 'width': '100px'}]);
|
||||
expect(keyframes1[1]).toEqual([1, {'background': 'blue', 'width': '200px'}]);
|
||||
|
||||
var animation2 = driver.log[1];
|
||||
expect(animation2['duration']).toEqual(999);
|
||||
expect(animation2['delay']).toEqual(0);
|
||||
expect(animation2['easing']).toEqual(null);
|
||||
expect(animation2['startingStyles'])
|
||||
.toEqual({'opacity': '1', 'border-width': '100px'});
|
||||
|
||||
var keyframes2 = animation2['keyframeLookup'];
|
||||
expect(keyframes2[0])
|
||||
.toEqual([0, {'opacity': '1', 'height': 111, 'border-width': '100px'}]);
|
||||
expect(keyframes2[1])
|
||||
.toEqual([1, {'opacity': '0', 'height': '200px', 'border-width': '10px'}]);
|
||||
});
|
||||
})));
|
||||
|
||||
describe('groups/sequences', () => {
|
||||
var assertPlaying =
|
||||
(player: MockAnimationDriver, isPlaying) => {
|
||||
var method = 'play';
|
||||
var lastEntry = player.log.length > 0 ? player.log[player.log.length - 1] : null;
|
||||
if (isPresent(lastEntry)) {
|
||||
if (isPlaying) {
|
||||
expect(lastEntry).toEqual(method);
|
||||
} else {
|
||||
expect(lastEntry).not.toEqual(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it('should run animations in sequence one by one if a top-level array is used',
|
||||
inject([TestComponentBuilder, AnimationDriver],
|
||||
fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
tcb.overrideAnimations(DummyIfCmp, [
|
||||
trigger("myAnimation", [
|
||||
transition("void => *", [
|
||||
style({"opacity": '0'}),
|
||||
animate(1000, style({'opacity': '0.5'})),
|
||||
animate('1000ms', style({'opacity': '0.8'})),
|
||||
animate('1s', style({'opacity': '1'})),
|
||||
])
|
||||
])
|
||||
])
|
||||
.createAsync(DummyIfCmp)
|
||||
.then((fixture) => {
|
||||
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(3);
|
||||
|
||||
var player1 = driver.log[0]['player'];
|
||||
var player2 = driver.log[1]['player'];
|
||||
var player3 = driver.log[2]['player'];
|
||||
|
||||
assertPlaying(player1, true);
|
||||
assertPlaying(player2, false);
|
||||
assertPlaying(player3, false);
|
||||
|
||||
player1.finish();
|
||||
|
||||
assertPlaying(player1, false);
|
||||
assertPlaying(player2, true);
|
||||
assertPlaying(player3, false);
|
||||
|
||||
player2.finish();
|
||||
|
||||
assertPlaying(player1, false);
|
||||
assertPlaying(player2, false);
|
||||
assertPlaying(player3, true);
|
||||
|
||||
player3.finish();
|
||||
|
||||
assertPlaying(player1, false);
|
||||
assertPlaying(player2, false);
|
||||
assertPlaying(player3, false);
|
||||
});
|
||||
})));
|
||||
|
||||
it('should run animations in parallel if a group is used',
|
||||
inject(
|
||||
[TestComponentBuilder, AnimationDriver],
|
||||
fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
tcb.overrideAnimations(DummyIfCmp, [
|
||||
trigger("myAnimation", [
|
||||
transition("void => *", [
|
||||
style({'width': 0, 'height': 0}),
|
||||
group([animate(1000, style({'width': 100})), animate(5000, style({'height': 500}))]),
|
||||
group([animate(1000, style({'width': 0})), animate(5000, style({'height': 0}))])
|
||||
])
|
||||
])
|
||||
])
|
||||
.createAsync(DummyIfCmp)
|
||||
.then((fixture) => {
|
||||
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(5);
|
||||
|
||||
var player1 = driver.log[0]['player'];
|
||||
var player2 = driver.log[1]['player'];
|
||||
var player3 = driver.log[2]['player'];
|
||||
var player4 = driver.log[3]['player'];
|
||||
var player5 = driver.log[4]['player'];
|
||||
|
||||
assertPlaying(player1, true);
|
||||
assertPlaying(player2, false);
|
||||
assertPlaying(player3, false);
|
||||
assertPlaying(player4, false);
|
||||
assertPlaying(player5, false);
|
||||
|
||||
player1.finish();
|
||||
|
||||
assertPlaying(player1, false);
|
||||
assertPlaying(player2, true);
|
||||
assertPlaying(player3, true);
|
||||
assertPlaying(player4, false);
|
||||
assertPlaying(player5, false);
|
||||
|
||||
player2.finish();
|
||||
|
||||
assertPlaying(player1, false);
|
||||
assertPlaying(player2, false);
|
||||
assertPlaying(player3, true);
|
||||
assertPlaying(player4, false);
|
||||
assertPlaying(player5, false);
|
||||
|
||||
player3.finish();
|
||||
|
||||
assertPlaying(player1, false);
|
||||
assertPlaying(player2, false);
|
||||
assertPlaying(player3, false);
|
||||
assertPlaying(player4, true);
|
||||
assertPlaying(player5, true);
|
||||
});
|
||||
})));
|
||||
});
|
||||
|
||||
describe('keyframes', () => {
|
||||
it('should create an animation step with multiple keyframes',
|
||||
inject(
|
||||
[TestComponentBuilder, AnimationDriver],
|
||||
fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
tcb.overrideAnimations(DummyIfCmp, [
|
||||
trigger("myAnimation", [
|
||||
transition("void => *", [
|
||||
animate(1000, keyframes([
|
||||
style([{ "width": 0, offset: 0 }]),
|
||||
style([{ "width": 100, offset: 0.25 }]),
|
||||
style([{ "width": 200, offset: 0.75 }]),
|
||||
style([{ "width": 300, offset: 1 }])
|
||||
]))
|
||||
])
|
||||
])
|
||||
])
|
||||
.createAsync(DummyIfCmp)
|
||||
.then((fixture) => {
|
||||
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
var keyframes = driver.log[0]['keyframeLookup'];
|
||||
expect(keyframes.length).toEqual(4);
|
||||
expect(keyframes[0]).toEqual([0, {'width': 0}]);
|
||||
expect(keyframes[1]).toEqual([0.25, {'width': 100}]);
|
||||
expect(keyframes[2]).toEqual([0.75, {'width': 200}]);
|
||||
expect(keyframes[3]).toEqual([1, {'width': 300}]);
|
||||
});
|
||||
})));
|
||||
|
||||
it('should fetch any keyframe styles that are not defined in the first keyframe from the previous entries or getCompuedStyle',
|
||||
inject(
|
||||
[TestComponentBuilder, AnimationDriver],
|
||||
fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
tcb.overrideAnimations(DummyIfCmp, [
|
||||
trigger("myAnimation", [
|
||||
transition("void => *", [
|
||||
style({ "color": "white" }),
|
||||
animate(1000, style({ "color": "silver" })),
|
||||
animate(1000, keyframes([
|
||||
style([{ "color": "gold", offset: 0.25 }]),
|
||||
style([{ "color": "bronze", "background-color":"teal", offset: 0.50 }]),
|
||||
style([{ "color": "platinum", offset: 0.75 }]),
|
||||
style([{ "color": "diamond", offset: 1 }])
|
||||
]))
|
||||
])
|
||||
])
|
||||
])
|
||||
.createAsync(DummyIfCmp)
|
||||
.then((fixture) => {
|
||||
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
var keyframes = driver.log[1]['keyframeLookup'];
|
||||
expect(keyframes.length).toEqual(5);
|
||||
expect(keyframes[0]).toEqual([0, {"color": "silver", "background-color":AUTO_STYLE }]);
|
||||
expect(keyframes[1]).toEqual([0.25, {"color": "gold"}]);
|
||||
expect(keyframes[2]).toEqual([0.50, {"color": "bronze", "background-color":"teal"}]);
|
||||
expect(keyframes[3]).toEqual([0.75, {"color": "platinum"}]);
|
||||
expect(keyframes[4]).toEqual([1, {"color": "diamond", "background-color":"teal"}]);
|
||||
});
|
||||
})));
|
||||
});
|
||||
|
||||
it('should cancel the previously running animation active with the same element/animationName pair',
|
||||
inject([TestComponentBuilder, AnimationDriver],
|
||||
fakeAsync(
|
||||
(tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
tcb.overrideAnimations(DummyIfCmp, [
|
||||
trigger("myAnimation", [
|
||||
transition("* => *", [
|
||||
style({ "opacity":0 }),
|
||||
animate(500, style({ "opacity":1 }))
|
||||
])
|
||||
])
|
||||
])
|
||||
.createAsync(DummyIfCmp)
|
||||
.then((fixture) => {
|
||||
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
|
||||
cmp.exp = "state1";
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
var enterCompleted = false;
|
||||
var enterPlayer = driver.log[0]['player'];
|
||||
enterPlayer.onDone(() => enterCompleted = true);
|
||||
|
||||
expect(enterCompleted).toEqual(false);
|
||||
|
||||
cmp.exp = "state2";
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(enterCompleted).toEqual(true);
|
||||
});
|
||||
})));
|
||||
|
||||
it('should destroy all animation players once the animation is complete',
|
||||
inject([TestComponentBuilder, AnimationDriver],
|
||||
fakeAsync(
|
||||
(tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
tcb.overrideAnimations(DummyIfCmp, [
|
||||
trigger("myAnimation", [
|
||||
transition("void => *", [
|
||||
style({'background': 'red', 'opacity': 0.5}),
|
||||
animate(500, style({'background': 'black'})),
|
||||
group([
|
||||
animate(500, style({'background': 'black'})),
|
||||
animate(1000, style({'opacity': '0.2'})),
|
||||
]),
|
||||
sequence([
|
||||
animate(500, style({'opacity': '1'})),
|
||||
animate(1000, style({'background': 'white'}))
|
||||
])
|
||||
])
|
||||
])
|
||||
]).createAsync(DummyIfCmp)
|
||||
.then((fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(5);
|
||||
|
||||
driver.log.forEach(entry => entry['player'].finish());
|
||||
driver.log.forEach(entry => {
|
||||
var player = <MockAnimationDriver>entry['player'];
|
||||
expect(player.log[player.log.length - 2]).toEqual('finish');
|
||||
expect(player.log[player.log.length - 1]).toEqual('destroy');
|
||||
});
|
||||
});
|
||||
})));
|
||||
|
||||
it('should use first matched animation when multiple animations are registered',
|
||||
inject(
|
||||
[TestComponentBuilder, AnimationDriver],
|
||||
fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
tcb = tcb.overrideTemplate(DummyIfCmp, `
|
||||
<div @rotate="exp"></div>
|
||||
<div @rotate="exp2"></div>
|
||||
`);
|
||||
tcb.overrideAnimations(DummyIfCmp, [
|
||||
trigger("rotate", [
|
||||
transition("start => *", [
|
||||
style({'color': 'white'}),
|
||||
animate(500,
|
||||
style({'color': 'red'}))
|
||||
]),
|
||||
transition("start => end", [
|
||||
style({'color': 'white'}),
|
||||
animate(500,
|
||||
style({'color': 'pink'}))
|
||||
])
|
||||
]),
|
||||
]).createAsync(DummyIfCmp)
|
||||
.then((fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = 'start';
|
||||
cmp.exp2 = 'start';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(0);
|
||||
|
||||
cmp.exp = 'something';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(1);
|
||||
|
||||
var animation1 = driver.log[0];
|
||||
var keyframes1 = animation1['keyframeLookup'];
|
||||
var toStyles1 = keyframes1[1][1];
|
||||
expect(toStyles1['color']).toEqual('red');
|
||||
|
||||
cmp.exp2 = 'end';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(2);
|
||||
|
||||
var animation2 = driver.log[1];
|
||||
var keyframes2 = animation2['keyframeLookup'];
|
||||
var toStyles2 = keyframes2[1][1];
|
||||
expect(toStyles2['color']).toEqual('red');
|
||||
});
|
||||
})));
|
||||
|
||||
it('should not remove the element until the void transition animation is complete',
|
||||
inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
makeAnimationCmp(tcb, '<div class="my-if" *ngIf="exp" @myAnimation></div>',
|
||||
trigger('myAnimation', [
|
||||
transition('* => void', [
|
||||
animate(1000, style({'opacity': 0}))
|
||||
])
|
||||
]), (fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
cmp.exp = false;
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
var player = driver.log[0]['player'];
|
||||
var container = fixture.debugElement.nativeElement;
|
||||
var ifElm = getDOM().querySelector(container, '.my-if');
|
||||
expect(ifElm).toBeTruthy();
|
||||
|
||||
player.finish();
|
||||
ifElm = getDOM().querySelector(container,'.my-if');
|
||||
expect(ifElm).toBeFalsy();
|
||||
});
|
||||
})));
|
||||
|
||||
it('should fill an animation with the missing style values if not defined within an earlier style step',
|
||||
inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
makeAnimationCmp(tcb, '<div @myAnimation="exp"></div>',
|
||||
trigger('myAnimation', [
|
||||
transition('* => *', [
|
||||
animate(1000, style({'opacity': 0})),
|
||||
animate(1000, style({'opacity': 1}))
|
||||
])
|
||||
]), (fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = 'state1';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
var animation1 = driver.log[0];
|
||||
var keyframes1 = animation1['keyframeLookup'];
|
||||
expect(keyframes1[0]).toEqual([0, {'opacity': AUTO_STYLE}]);
|
||||
expect(keyframes1[1]).toEqual([1, {'opacity': 0}]);
|
||||
|
||||
var animation2 = driver.log[1];
|
||||
var keyframes2 = animation2['keyframeLookup'];
|
||||
expect(keyframes2[0]).toEqual([0, {'opacity': 0}]);
|
||||
expect(keyframes2[1]).toEqual([1, {'opacity': 1}]);
|
||||
});
|
||||
})));
|
||||
|
||||
it('should perform two transitions in parallel if defined in different state triggers',
|
||||
inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
makeAnimationCmp(tcb, '<div @one="exp" @two="exp2"></div>', [
|
||||
trigger('one', [
|
||||
transition('state1 => state2', [
|
||||
style({'opacity': 0}),
|
||||
animate(1000, style({'opacity': 1}))
|
||||
])
|
||||
]),
|
||||
trigger('two', [
|
||||
transition('state1 => state2', [
|
||||
style({'width': 100}),
|
||||
animate(1000, style({'width': 1000}))
|
||||
])
|
||||
])
|
||||
], (fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
cmp.exp = 'state1';
|
||||
cmp.exp2 = 'state1';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
cmp.exp = 'state2';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(1);
|
||||
|
||||
var count = 0;
|
||||
var animation1 = driver.log[0];
|
||||
var player1 = animation1['player'];
|
||||
player1.onDone(() => count++);
|
||||
|
||||
expect(count).toEqual(0);
|
||||
|
||||
cmp.exp2 = 'state2';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(2);
|
||||
expect(count).toEqual(0);
|
||||
|
||||
var animation2 = driver.log[1];
|
||||
var player2 = animation2['player'];
|
||||
player2.onDone(() => count++);
|
||||
|
||||
expect(count).toEqual(0);
|
||||
player1.finish();
|
||||
expect(count).toEqual(1);
|
||||
player2.finish();
|
||||
expect(count).toEqual(2);
|
||||
});
|
||||
})));
|
||||
});
|
||||
|
||||
describe('animation states', () => {
|
||||
it('should retain the destination animation state styles once the animation is complete',
|
||||
inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
makeAnimationCmp(tcb, '<div class="target" @status="exp"></div>', [
|
||||
trigger('status', [
|
||||
state('final', style({ "top": '100px' })),
|
||||
transition('* => final', [ animate(1000) ])
|
||||
])
|
||||
], (fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
var node = getDOM().querySelector(fixture.debugElement.nativeElement, '.target');
|
||||
cmp.exp = 'final';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
var animation = driver.log[0];
|
||||
var player = animation['player'];
|
||||
player.finish();
|
||||
|
||||
expect(getDOM().getStyle(node, 'top')).toEqual('100px');
|
||||
});
|
||||
})));
|
||||
|
||||
it('should seed in the origin animation state styles into the first animation step',
|
||||
inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
makeAnimationCmp(tcb, '<div class="target" @status="exp"></div>', [
|
||||
trigger('status', [
|
||||
state('void', style({ "height": '100px' })),
|
||||
transition('* => *', [ animate(1000) ])
|
||||
])
|
||||
], (fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
var node = getDOM().querySelector(fixture.debugElement.nativeElement, '.target');
|
||||
cmp.exp = 'final';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
var animation = driver.log[0];
|
||||
expect(animation['startingStyles']).toEqual({
|
||||
"height": "100px"
|
||||
});
|
||||
});
|
||||
})));
|
||||
|
||||
it('should perform a state change even if there is no transition that is found',
|
||||
inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
makeAnimationCmp(tcb, '<div class="target" @status="exp"></div>', [
|
||||
trigger('status', [
|
||||
state('void', style({ "width": '0px' })),
|
||||
state('final', style({ "width": '100px' })),
|
||||
])
|
||||
], (fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
var node = getDOM().querySelector(fixture.debugElement.nativeElement, '.target');
|
||||
cmp.exp = 'final';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.length).toEqual(0);
|
||||
flushMicrotasks();
|
||||
|
||||
expect(getDOM().getStyle(node, 'width')).toEqual('100px');
|
||||
});
|
||||
})));
|
||||
|
||||
it('should allow multiple states to be defined with the same styles',
|
||||
inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
makeAnimationCmp(tcb, '<div class="target" @status="exp"></div>', [
|
||||
trigger('status', [
|
||||
state('a, c', style({ "height": '100px' })),
|
||||
state('b, d', style({ "width": '100px' })),
|
||||
])
|
||||
], (fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
var node = getDOM().querySelector(fixture.debugElement.nativeElement, '.target');
|
||||
|
||||
cmp.exp = 'a';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(getDOM().getStyle(node, 'height')).toEqual('100px');
|
||||
expect(getDOM().getStyle(node, 'width')).not.toEqual('100px');
|
||||
|
||||
cmp.exp = 'b';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(getDOM().getStyle(node, 'height')).not.toEqual('100px');
|
||||
expect(getDOM().getStyle(node, 'width')).toEqual('100px');
|
||||
|
||||
cmp.exp = 'c';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(getDOM().getStyle(node, 'height')).toEqual('100px');
|
||||
expect(getDOM().getStyle(node, 'width')).not.toEqual('100px');
|
||||
|
||||
cmp.exp = 'd';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(getDOM().getStyle(node, 'height')).not.toEqual('100px');
|
||||
expect(getDOM().getStyle(node, 'width')).toEqual('100px');
|
||||
|
||||
cmp.exp = 'e';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(getDOM().getStyle(node, 'height')).not.toEqual('100px');
|
||||
expect(getDOM().getStyle(node, 'width')).not.toEqual('100px');
|
||||
});
|
||||
})));
|
||||
|
||||
it('should allow multiple transitions to be defined with the same sequence',
|
||||
inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
makeAnimationCmp(tcb, '<div class="target" @status="exp"></div>', [
|
||||
trigger('status', [
|
||||
transition('a => b, b => c', [
|
||||
animate(1000)
|
||||
]),
|
||||
transition('* => *', [
|
||||
animate(300)
|
||||
])
|
||||
])
|
||||
], (fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
var node = getDOM().querySelector(fixture.debugElement.nativeElement, '.target');
|
||||
|
||||
cmp.exp = 'a';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.pop()['duration']).toEqual(300);
|
||||
|
||||
cmp.exp = 'b';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.pop()['duration']).toEqual(1000);
|
||||
|
||||
cmp.exp = 'c';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.pop()['duration']).toEqual(1000);
|
||||
|
||||
cmp.exp = 'd';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
expect(driver.log.pop()['duration']).toEqual(300);
|
||||
});
|
||||
})));
|
||||
|
||||
it('should balance the animation with the origin/destination styles as keyframe animation properties',
|
||||
inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
makeAnimationCmp(tcb, '<div class="target" @status="exp"></div>', [
|
||||
trigger('status', [
|
||||
state('void', style({ "height": "100px", "opacity":0 })),
|
||||
state('final', style({ "height": "333px", "width":"200px" })),
|
||||
transition('void => final', [
|
||||
animate(1000)
|
||||
])
|
||||
])
|
||||
], (fixture) => {
|
||||
tick();
|
||||
|
||||
var cmp = fixture.debugElement.componentInstance;
|
||||
var node = getDOM().querySelector(fixture.debugElement.nativeElement, '.target');
|
||||
|
||||
cmp.exp = 'final';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
var animation = driver.log.pop();
|
||||
var keyframes = animation['keyframeLookup'];
|
||||
|
||||
expect(keyframes[0]).toEqual(
|
||||
[0, { "height": "100px",
|
||||
"opacity": 0,
|
||||
"width": AUTO_STYLE }]);
|
||||
|
||||
expect(keyframes[1]).toEqual(
|
||||
[1, { "height": "333px",
|
||||
"opacity": AUTO_STYLE,
|
||||
"width": "200px" }]);
|
||||
});
|
||||
})));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'if-cmp',
|
||||
directives: [NgIf],
|
||||
template: `
|
||||
<div *ngIf="exp" @myAnimation="exp"></div>
|
||||
`
|
||||
})
|
||||
class DummyIfCmp {
|
||||
exp = false;
|
||||
exp2 = false;
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit
|
||||
} from '../../testing/testing_internal';
|
||||
|
||||
import {
|
||||
fakeAsync,
|
||||
flushMicrotasks
|
||||
} from '../../testing';
|
||||
|
||||
import {NoOpAnimationPlayer, AnimationPlayer} from '../../src/animation/animation_player';
|
||||
|
||||
export function main() {
|
||||
describe('NoOpAnimationPlayer', function() {
|
||||
it('should call onDone after the next microtask when constructed', fakeAsync(() => {
|
||||
var player = new NoOpAnimationPlayer();
|
||||
var completed = false;
|
||||
player.onDone(() => completed = true);
|
||||
expect(completed).toEqual(false);
|
||||
flushMicrotasks();
|
||||
expect(completed).toEqual(true);
|
||||
}));
|
||||
|
||||
it('should be able to run each of the player methods', fakeAsync(() => {
|
||||
var player = new NoOpAnimationPlayer();
|
||||
player.pause();
|
||||
player.play();
|
||||
player.finish();
|
||||
player.restart();
|
||||
player.destroy();
|
||||
}));
|
||||
});
|
||||
}
|
@ -0,0 +1,216 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit
|
||||
} from '../../testing/testing_internal';
|
||||
|
||||
import {
|
||||
fakeAsync,
|
||||
flushMicrotasks
|
||||
} from '../../testing';
|
||||
|
||||
import {isPresent} from '../../src/facade/lang';
|
||||
import {AnimationSequencePlayer} from '../../src/animation/animation_sequence_player';
|
||||
import {MockAnimationPlayer} from '../../testing/animation/mock_animation_player';
|
||||
|
||||
export function main() {
|
||||
describe('AnimationSequencePlayer', function() {
|
||||
var players;
|
||||
beforeEach(() => {
|
||||
players = [
|
||||
new MockAnimationPlayer(),
|
||||
new MockAnimationPlayer(),
|
||||
new MockAnimationPlayer(),
|
||||
];
|
||||
});
|
||||
|
||||
var assertLastStatus =
|
||||
(player: MockAnimationPlayer, status: string, match: boolean, iOffset: number = 0) => {
|
||||
var index = player.log.length - 1 + iOffset;
|
||||
var actual = player.log.length > 0 ? player.log[index] : null;
|
||||
if (match) {
|
||||
expect(actual).toEqual(status);
|
||||
} else {
|
||||
expect(actual).not.toEqual(status);
|
||||
}
|
||||
}
|
||||
|
||||
var assertPlaying = (player: MockAnimationPlayer, isPlaying: boolean) => {
|
||||
assertLastStatus(player, 'play', isPlaying);
|
||||
};
|
||||
|
||||
it('should pause/play the active player', () => {
|
||||
var sequence = new AnimationSequencePlayer(players);
|
||||
|
||||
assertPlaying(players[0], false);
|
||||
assertPlaying(players[1], false);
|
||||
assertPlaying(players[2], false);
|
||||
|
||||
sequence.play();
|
||||
|
||||
assertPlaying(players[0], true);
|
||||
assertPlaying(players[1], false);
|
||||
assertPlaying(players[2], false);
|
||||
|
||||
sequence.pause();
|
||||
|
||||
assertPlaying(players[0], false);
|
||||
assertPlaying(players[1], false);
|
||||
assertPlaying(players[2], false);
|
||||
|
||||
sequence.play();
|
||||
players[0].finish();
|
||||
|
||||
assertPlaying(players[0], false);
|
||||
assertPlaying(players[1], true);
|
||||
assertPlaying(players[2], false);
|
||||
|
||||
players[1].finish();
|
||||
|
||||
assertPlaying(players[0], false);
|
||||
assertPlaying(players[1], false);
|
||||
assertPlaying(players[2], true);
|
||||
|
||||
players[2].finish();
|
||||
sequence.pause();
|
||||
|
||||
assertPlaying(players[0], false);
|
||||
assertPlaying(players[1], false);
|
||||
assertPlaying(players[2], false);
|
||||
});
|
||||
|
||||
it('should finish when all players have finished', () => {
|
||||
var sequence = new AnimationSequencePlayer(players);
|
||||
|
||||
var completed = false;
|
||||
sequence.onDone(() => completed = true);
|
||||
sequence.play();
|
||||
|
||||
expect(completed).toBeFalsy();
|
||||
|
||||
players[0].finish();
|
||||
|
||||
expect(completed).toBeFalsy();
|
||||
|
||||
players[1].finish();
|
||||
|
||||
expect(completed).toBeFalsy();
|
||||
|
||||
players[2].finish();
|
||||
|
||||
expect(completed).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should restart all the players', () => {
|
||||
var sequence = new AnimationSequencePlayer(players);
|
||||
|
||||
sequence.play();
|
||||
|
||||
assertPlaying(players[0], true);
|
||||
assertPlaying(players[1], false);
|
||||
assertPlaying(players[2], false);
|
||||
|
||||
players[0].finish();
|
||||
|
||||
assertPlaying(players[0], false);
|
||||
assertPlaying(players[1], true);
|
||||
assertPlaying(players[2], false);
|
||||
|
||||
sequence.restart();
|
||||
|
||||
assertLastStatus(players[0], 'restart', true);
|
||||
assertLastStatus(players[1], 'reset', true);
|
||||
assertLastStatus(players[2], 'reset', true);
|
||||
});
|
||||
|
||||
it('should finish all the players', () => {
|
||||
var sequence = new AnimationSequencePlayer(players);
|
||||
|
||||
var completed = false;
|
||||
sequence.onDone(() => completed = true);
|
||||
|
||||
sequence.play();
|
||||
|
||||
assertLastStatus(players[0], 'finish', false);
|
||||
assertLastStatus(players[1], 'finish', false);
|
||||
assertLastStatus(players[2], 'finish', false);
|
||||
|
||||
sequence.finish();
|
||||
|
||||
assertLastStatus(players[0], 'finish', true, -1);
|
||||
assertLastStatus(players[1], 'finish', true, -1);
|
||||
assertLastStatus(players[2], 'finish', true, -1);
|
||||
|
||||
assertLastStatus(players[0], 'destroy', true);
|
||||
assertLastStatus(players[1], 'destroy', true);
|
||||
assertLastStatus(players[2], 'destroy', true);
|
||||
|
||||
expect(completed).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call destroy automatically when finished if no parent player is present', () => {
|
||||
var sequence = new AnimationSequencePlayer(players);
|
||||
|
||||
sequence.play();
|
||||
|
||||
assertLastStatus(players[0], 'destroy', false);
|
||||
assertLastStatus(players[1], 'destroy', false);
|
||||
assertLastStatus(players[2], 'destroy', false);
|
||||
|
||||
sequence.finish();
|
||||
|
||||
assertLastStatus(players[0], 'destroy', true);
|
||||
assertLastStatus(players[1], 'destroy', true);
|
||||
assertLastStatus(players[2], 'destroy', true);
|
||||
});
|
||||
|
||||
it('should not call destroy automatically when finished if a parent player is present', () => {
|
||||
var sequence = new AnimationSequencePlayer(players);
|
||||
var parent = new AnimationSequencePlayer([sequence, new MockAnimationPlayer()]);
|
||||
|
||||
sequence.play();
|
||||
|
||||
assertLastStatus(players[0], 'destroy', false);
|
||||
assertLastStatus(players[1], 'destroy', false);
|
||||
assertLastStatus(players[2], 'destroy', false);
|
||||
|
||||
sequence.finish();
|
||||
|
||||
assertLastStatus(players[0], 'destroy', false);
|
||||
assertLastStatus(players[1], 'destroy', false);
|
||||
assertLastStatus(players[2], 'destroy', false);
|
||||
|
||||
parent.finish();
|
||||
|
||||
assertLastStatus(players[0], 'destroy', true);
|
||||
assertLastStatus(players[1], 'destroy', true);
|
||||
assertLastStatus(players[2], 'destroy', true);
|
||||
});
|
||||
|
||||
it('should function without any players', () => {
|
||||
var sequence = new AnimationSequencePlayer([]);
|
||||
sequence.onDone(() => {});
|
||||
sequence.pause();
|
||||
sequence.play();
|
||||
sequence.finish();
|
||||
sequence.restart();
|
||||
sequence.destroy();
|
||||
});
|
||||
|
||||
it('should call onDone after the next microtask if no players are provided', fakeAsync(() => {
|
||||
var sequence = new AnimationSequencePlayer([]);
|
||||
var completed = false;
|
||||
sequence.onDone(() => completed = true);
|
||||
expect(completed).toEqual(false);
|
||||
flushMicrotasks();
|
||||
expect(completed).toEqual(true);
|
||||
}));
|
||||
});
|
||||
}
|
@ -0,0 +1,195 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit
|
||||
} from '../../testing/testing_internal';
|
||||
|
||||
import {
|
||||
fakeAsync,
|
||||
flushMicrotasks
|
||||
} from '../../testing';
|
||||
|
||||
import {el} from '@angular/platform-browser/testing';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
||||
import {isPresent} from '../../src/facade/lang';
|
||||
import {MockAnimationPlayer} from '../../testing/animation/mock_animation_player';
|
||||
import {AnimationStyleUtil} from '../../src/animation/animation_style_util';
|
||||
import {AnimationKeyframe} from '../../src/animation/animation_keyframe';
|
||||
import {AnimationStyles} from '../../src/animation/animation_styles';
|
||||
|
||||
import {FILL_STYLE_FLAG} from '../../src/animation/animation_constants';
|
||||
import {AUTO_STYLE} from '../../src/animation/metadata';
|
||||
|
||||
export function main() {
|
||||
describe('AnimationStyleUtil', function() {
|
||||
|
||||
describe('balanceStyles', () => {
|
||||
it('should set all non-shared styles to the provided null value between the two sets of styles', () => {
|
||||
var styles = { opacity: 0, color: 'red' };
|
||||
var newStyles = { background: 'red' };
|
||||
var flag = '*';
|
||||
var result = AnimationStyleUtil.balanceStyles(styles, newStyles, flag);
|
||||
expect(result).toEqual({
|
||||
opacity:flag,
|
||||
color:flag,
|
||||
background:'red'
|
||||
})
|
||||
});
|
||||
|
||||
it('should handle an empty set of styles', () => {
|
||||
var value = '*';
|
||||
|
||||
expect(AnimationStyleUtil.balanceStyles({}, { opacity: 0 }, value)).toEqual({
|
||||
opacity: 0
|
||||
});
|
||||
|
||||
expect(AnimationStyleUtil.balanceStyles({ opacity: 0 }, {}, value)).toEqual({
|
||||
opacity: value
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('balanceKeyframes', () => {
|
||||
it('should balance both the starting and final keyframes with thep provided styles', () => {
|
||||
var collectedStyles = {
|
||||
width: 100,
|
||||
height: 200
|
||||
};
|
||||
|
||||
var finalStyles = {
|
||||
background: 'red',
|
||||
border: '1px solid black'
|
||||
};
|
||||
|
||||
var keyframes = [
|
||||
new AnimationKeyframe(0, new AnimationStyles([{ height: 100, opacity: 1 }])),
|
||||
new AnimationKeyframe(1, new AnimationStyles([{ background: 'blue', left: '100px', top: '100px' }]))
|
||||
];
|
||||
|
||||
var result = AnimationStyleUtil.balanceKeyframes(collectedStyles, finalStyles, keyframes);
|
||||
|
||||
expect(AnimationStyleUtil.flattenStyles(result[0].styles.styles)).toEqual({
|
||||
"width": 100,
|
||||
"height": 100,
|
||||
"opacity": 1,
|
||||
"background": '*',
|
||||
"border": '*',
|
||||
"left": '*',
|
||||
"top": '*'
|
||||
});
|
||||
|
||||
expect(AnimationStyleUtil.flattenStyles(result[1].styles.styles)).toEqual({
|
||||
"width": '*',
|
||||
"height": '*',
|
||||
"opacity": '*',
|
||||
"background": 'blue',
|
||||
"border": '1px solid black',
|
||||
"left": '100px',
|
||||
"top": '100px'
|
||||
});
|
||||
});
|
||||
|
||||
it('should perform balancing when no collected and final styles are provided', () => {
|
||||
var keyframes = [
|
||||
new AnimationKeyframe(0, new AnimationStyles([{ height: 100, opacity: 1 }])),
|
||||
new AnimationKeyframe(1, new AnimationStyles([{ width: 100 }]))
|
||||
];
|
||||
|
||||
var result = AnimationStyleUtil.balanceKeyframes({}, {}, keyframes);
|
||||
|
||||
expect(AnimationStyleUtil.flattenStyles(result[0].styles.styles)).toEqual({
|
||||
"height": 100,
|
||||
"opacity": 1,
|
||||
"width": "*"
|
||||
});
|
||||
|
||||
expect(AnimationStyleUtil.flattenStyles(result[1].styles.styles)).toEqual({
|
||||
"width": 100,
|
||||
"height": "*",
|
||||
"opacity": "*"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearStyles', () => {
|
||||
it('should set all the style values to "null"', () => {
|
||||
var styles = {
|
||||
"opacity": 0,
|
||||
"width": 100,
|
||||
"color": "red"
|
||||
};
|
||||
var expectedResult = {
|
||||
"opacity": null,
|
||||
"width": null,
|
||||
"color": null
|
||||
};
|
||||
expect(AnimationStyleUtil.clearStyles(styles)).toEqual(expectedResult);
|
||||
});
|
||||
|
||||
it('should handle an empty set of styles', () => {
|
||||
expect(AnimationStyleUtil.clearStyles({})).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('collectAndResolveStyles', () => {
|
||||
it('should keep a record of the styles as they are called', () => {
|
||||
var styles1 = [{
|
||||
"opacity": 0,
|
||||
"width": 100
|
||||
}];
|
||||
|
||||
var styles2 = [{
|
||||
"height": 999,
|
||||
"opacity": 1
|
||||
}];
|
||||
|
||||
var collection: {[key: string]: string|number} = {};
|
||||
|
||||
expect(AnimationStyleUtil.collectAndResolveStyles(collection, styles1)).toEqual(styles1);
|
||||
expect(collection).toEqual({
|
||||
"opacity": 0,
|
||||
"width": 100
|
||||
});
|
||||
|
||||
expect(AnimationStyleUtil.collectAndResolveStyles(collection, styles2)).toEqual(styles2);
|
||||
expect(collection).toEqual({
|
||||
"opacity": 1,
|
||||
"width": 100,
|
||||
"height": 999
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve styles if they contain a FILL_STYLE_FLAG value', () => {
|
||||
var styles1 = [{
|
||||
"opacity": 0,
|
||||
"width": FILL_STYLE_FLAG
|
||||
}];
|
||||
|
||||
var styles2 = [{
|
||||
"height": 999,
|
||||
"opacity": FILL_STYLE_FLAG
|
||||
}];
|
||||
|
||||
var collection = {};
|
||||
|
||||
expect(AnimationStyleUtil.collectAndResolveStyles(collection, styles1)).toEqual([{
|
||||
"opacity": 0,
|
||||
"width": AUTO_STYLE
|
||||
}]);
|
||||
|
||||
expect(AnimationStyleUtil.collectAndResolveStyles(collection, styles2)).toEqual([{
|
||||
"opacity": 0,
|
||||
"height": 999
|
||||
}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import {AnimationDriver} from '../../src/animation/animation_driver';
|
||||
import {AnimationKeyframe} from '../../src/animation/animation_keyframe';
|
||||
import {AnimationPlayer} from '../../src/animation/animation_player';
|
||||
import {StringMapWrapper} from '../../src/facade/collection';
|
||||
import {AnimationStyles} from '../../src/animation/animation_styles';
|
||||
import {MockAnimationPlayer} from '../../testing/animation/mock_animation_player';
|
||||
|
||||
export class MockAnimationDriver extends AnimationDriver {
|
||||
log = [];
|
||||
animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number,
|
||||
easing: string): AnimationPlayer {
|
||||
var player = new MockAnimationPlayer();
|
||||
this.log.push({
|
||||
'element': element,
|
||||
'startingStyles': _serializeStyles(startingStyles),
|
||||
'keyframes': keyframes,
|
||||
'keyframeLookup': _serializeKeyframes(keyframes),
|
||||
'duration': duration,
|
||||
'delay': delay,
|
||||
'easing': easing,
|
||||
'player': player
|
||||
});
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
function _serializeKeyframes(keyframes: AnimationKeyframe[]): any[] {
|
||||
return keyframes.map(keyframe => [keyframe.offset, _serializeStyles(keyframe.styles)]);
|
||||
}
|
||||
|
||||
function _serializeStyles(styles: AnimationStyles): {[key: string]: any} {
|
||||
var flatStyles = {};
|
||||
styles.styles.forEach(entry => StringMapWrapper.forEach(entry, (val, prop) => { flatStyles[prop] = val; }));
|
||||
return flatStyles;
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
import {isPresent} from '../../src/facade/lang';
|
||||
import {AnimationPlayer} from '../../src/animation/animation_player';
|
||||
|
||||
export class MockAnimationPlayer implements AnimationPlayer {
|
||||
private _subscriptions = [];
|
||||
private _finished = false;
|
||||
private _destroyed = false;
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
|
||||
public log = [];
|
||||
|
||||
private _onfinish(): void {
|
||||
if (!this._finished) {
|
||||
this._finished = true;
|
||||
this.log.push('finish');
|
||||
|
||||
this._subscriptions.forEach((entry) => { entry(); });
|
||||
this._subscriptions = [];
|
||||
if (!isPresent(this.parentPlayer)) {
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDone(fn: Function): void { this._subscriptions.push(fn); }
|
||||
|
||||
play(): void { this.log.push('play'); }
|
||||
|
||||
pause(): void { this.log.push('pause'); }
|
||||
|
||||
restart(): void { this.log.push('restart'); }
|
||||
|
||||
finish(): void { this._onfinish(); }
|
||||
|
||||
reset(): void { this.log.push('reset'); }
|
||||
|
||||
destroy(): void {
|
||||
if (!this._destroyed) {
|
||||
this._destroyed = true;
|
||||
this.finish();
|
||||
this.log.push('destroy');
|
||||
}
|
||||
}
|
||||
|
||||
setPosition(p): void {}
|
||||
getPosition(): number { return 0; }
|
||||
}
|
@ -178,6 +178,27 @@ var CORE: string[] = [
|
||||
'APP_ID',
|
||||
'AngularEntrypoint:dart',
|
||||
'AbstractProviderError',
|
||||
'AUTO_STYLE',
|
||||
'AnimationAnimateMetadata',
|
||||
'AnimationEntryMetadata',
|
||||
'AnimationGroupMetadata',
|
||||
'AnimationMetadata',
|
||||
'AnimationSequenceMetadata',
|
||||
'AnimationStateDeclarationMetadata',
|
||||
'AnimationStateMetadata',
|
||||
'AnimationStateTransitionMetadata',
|
||||
'AnimationStyleMetadata',
|
||||
'AnimationWithStepsMetadata',
|
||||
'AnimationKeyframesSequenceMetadata',
|
||||
'AnimationPlayer',
|
||||
'animate',
|
||||
'group',
|
||||
'sequence',
|
||||
'state',
|
||||
'style',
|
||||
'keyframes',
|
||||
'trigger',
|
||||
'transition',
|
||||
'ApplicationRef',
|
||||
'APPLICATION_COMMON_PROVIDERS',
|
||||
'Attribute',
|
||||
|
@ -12,3 +12,27 @@ export var SecurityContext: typeof t.SecurityContext = r.SecurityContext;
|
||||
export type SecurityContext = t.SecurityContext;
|
||||
export var SanitizationService: typeof t.SanitizationService = r.SanitizationService;
|
||||
export type SanitizationService = t.SanitizationService;
|
||||
|
||||
export type NoOpAnimationPlayer = t.NoOpAnimationPlayer;
|
||||
export var NoOpAnimationPlayer: typeof t.NoOpAnimationPlayer = r.NoOpAnimationPlayer;
|
||||
export type AnimationPlayer = t.AnimationPlayer;
|
||||
export var AnimationPlayer: typeof t.AnimationPlayer = r.AnimationPlayer;
|
||||
export type NoOpAnimationDriver = t.NoOpAnimationDriver;
|
||||
export var NoOpAnimationDriver: typeof t.NoOpAnimationDriver = r.NoOpAnimationDriver;
|
||||
export type AnimationDriver = t.AnimationDriver;
|
||||
export var AnimationDriver: typeof t.AnimationDriver = r.AnimationDriver;
|
||||
export type AnimationSequencePlayer = t.AnimationSequencePlayer;
|
||||
export var AnimationSequencePlayer: typeof t.AnimationSequencePlayer = r.AnimationSequencePlayer;
|
||||
export type AnimationGroupPlayer = t.AnimationGroupPlayer;
|
||||
export var AnimationGroupPlayer: typeof t.AnimationGroupPlayer = r.AnimationGroupPlayer;
|
||||
export type AnimationKeyframe = t.AnimationKeyframe;
|
||||
export var AnimationKeyframe: typeof t.AnimationKeyframe = r.AnimationKeyframe;
|
||||
export type AnimationStyleUtil = t.AnimationStyleUtil;
|
||||
export var AnimationStyleUtil: typeof t.AnimationStyleUtil = r.AnimationStyleUtil;
|
||||
export type AnimationStyles = t.AnimationStyles;
|
||||
export var AnimationStyles: typeof t.AnimationStyles = r.AnimationStyles;
|
||||
|
||||
export type MockAnimationPlayer = t.MockAnimationPlayer;
|
||||
export var MockAnimationPlayer: typeof t.MockAnimationPlayer = r.MockAnimationPlayer;
|
||||
export type MockAnimationDriver = t.MockAnimationDriver;
|
||||
export var MockAnimationDriver: typeof t.MockAnimationDriver = r.MockAnimationDriver;
|
||||
|
@ -1,203 +0,0 @@
|
||||
import {
|
||||
DateWrapper,
|
||||
StringWrapper,
|
||||
RegExpWrapper,
|
||||
NumberWrapper,
|
||||
isPresent
|
||||
} from '../../src/facade/lang';
|
||||
import {Math} from '../../src/facade/math';
|
||||
import {StringMapWrapper} from '../../src/facade/collection';
|
||||
import {camelCaseToDashCase} from '../dom/util';
|
||||
import {getDOM} from '../dom/dom_adapter';
|
||||
|
||||
import {BrowserDetails} from './browser_details';
|
||||
import {CssAnimationOptions} from './css_animation_options';
|
||||
|
||||
export class Animation {
|
||||
/** functions to be called upon completion */
|
||||
callbacks: Function[] = [];
|
||||
|
||||
/** the duration (ms) of the animation (whether from CSS or manually set) */
|
||||
computedDuration: number;
|
||||
|
||||
/** the animation delay (ms) (whether from CSS or manually set) */
|
||||
computedDelay: number;
|
||||
|
||||
/** timestamp of when the animation started */
|
||||
startTime: number;
|
||||
|
||||
/** functions for removing event listeners */
|
||||
eventClearFunctions: Function[] = [];
|
||||
|
||||
/** flag used to track whether or not the animation has finished */
|
||||
completed: boolean = false;
|
||||
|
||||
private _stringPrefix: string = '';
|
||||
|
||||
/** total amount of time that the animation should take including delay */
|
||||
get totalTime(): number {
|
||||
let delay = this.computedDelay != null ? this.computedDelay : 0;
|
||||
let duration = this.computedDuration != null ? this.computedDuration : 0;
|
||||
return delay + duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the start time and starts the animation
|
||||
* @param element
|
||||
* @param data
|
||||
* @param browserDetails
|
||||
*/
|
||||
constructor(public element: HTMLElement, public data: CssAnimationOptions,
|
||||
public browserDetails: BrowserDetails) {
|
||||
this.startTime = DateWrapper.toMillis(DateWrapper.now());
|
||||
this._stringPrefix = getDOM().getAnimationPrefix();
|
||||
this.setup();
|
||||
this.wait((timestamp: any) => this.start());
|
||||
}
|
||||
|
||||
wait(callback: Function) {
|
||||
// Firefox requires 2 frames for some reason
|
||||
this.browserDetails.raf(callback, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the initial styles before the animation is started
|
||||
*/
|
||||
setup(): void {
|
||||
if (this.data.fromStyles != null) this.applyStyles(this.data.fromStyles);
|
||||
if (this.data.duration != null)
|
||||
this.applyStyles({'transitionDuration': this.data.duration.toString() + 'ms'});
|
||||
if (this.data.delay != null)
|
||||
this.applyStyles({'transitionDelay': this.data.delay.toString() + 'ms'});
|
||||
}
|
||||
|
||||
/**
|
||||
* After the initial setup has occurred, this method adds the animation styles
|
||||
*/
|
||||
start(): void {
|
||||
this.addClasses(this.data.classesToAdd);
|
||||
this.addClasses(this.data.animationClasses);
|
||||
this.removeClasses(this.data.classesToRemove);
|
||||
if (this.data.toStyles != null) this.applyStyles(this.data.toStyles);
|
||||
var computedStyles = getDOM().getComputedStyle(this.element);
|
||||
this.computedDelay =
|
||||
Math.max(this.parseDurationString(
|
||||
computedStyles.getPropertyValue(this._stringPrefix + 'transition-delay')),
|
||||
this.parseDurationString(
|
||||
this.element.style.getPropertyValue(this._stringPrefix + 'transition-delay')));
|
||||
this.computedDuration = Math.max(this.parseDurationString(computedStyles.getPropertyValue(
|
||||
this._stringPrefix + 'transition-duration')),
|
||||
this.parseDurationString(this.element.style.getPropertyValue(
|
||||
this._stringPrefix + 'transition-duration')));
|
||||
this.addEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the provided styles to the element
|
||||
* @param styles
|
||||
*/
|
||||
applyStyles(styles: {[key: string]: any}): void {
|
||||
StringMapWrapper.forEach(styles, (value: any, key: string) => {
|
||||
var dashCaseKey = camelCaseToDashCase(key);
|
||||
if (isPresent(getDOM().getStyle(this.element, dashCaseKey))) {
|
||||
getDOM().setStyle(this.element, dashCaseKey, value.toString());
|
||||
} else {
|
||||
getDOM().setStyle(this.element, this._stringPrefix + dashCaseKey, value.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided classes to the element
|
||||
* @param classes
|
||||
*/
|
||||
addClasses(classes: string[]): void {
|
||||
for (let i = 0, len = classes.length; i < len; i++) getDOM().addClass(this.element, classes[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the provided classes from the element
|
||||
* @param classes
|
||||
*/
|
||||
removeClasses(classes: string[]): void {
|
||||
for (let i = 0, len = classes.length; i < len; i++)
|
||||
getDOM().removeClass(this.element, classes[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds events to track when animations have finished
|
||||
*/
|
||||
addEvents(): void {
|
||||
if (this.totalTime > 0) {
|
||||
this.eventClearFunctions.push(
|
||||
getDOM().onAndCancel(this.element, getDOM().getTransitionEnd(),
|
||||
(event: any) => this.handleAnimationEvent(event)));
|
||||
} else {
|
||||
this.handleAnimationCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
handleAnimationEvent(event: any): void {
|
||||
let elapsedTime = Math.round(event.elapsedTime * 1000);
|
||||
if (!this.browserDetails.elapsedTimeIncludesDelay) elapsedTime += this.computedDelay;
|
||||
event.stopPropagation();
|
||||
if (elapsedTime >= this.totalTime) this.handleAnimationCompleted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all animation callbacks and removes temporary classes
|
||||
*/
|
||||
handleAnimationCompleted(): void {
|
||||
this.removeClasses(this.data.animationClasses);
|
||||
this.callbacks.forEach(callback => callback());
|
||||
this.callbacks = [];
|
||||
this.eventClearFunctions.forEach(fn => fn());
|
||||
this.eventClearFunctions = [];
|
||||
this.completed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds animation callbacks to be called upon completion
|
||||
* @param callback
|
||||
* @returns {Animation}
|
||||
*/
|
||||
onComplete(callback: Function): Animation {
|
||||
if (this.completed) {
|
||||
callback();
|
||||
} else {
|
||||
this.callbacks.push(callback);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the duration string to the number of milliseconds
|
||||
* @param duration
|
||||
* @returns {number}
|
||||
*/
|
||||
parseDurationString(duration: string): number {
|
||||
var maxValue = 0;
|
||||
// duration must have at least 2 characters to be valid. (number + type)
|
||||
if (duration == null || duration.length < 2) {
|
||||
return maxValue;
|
||||
} else if (duration.substring(duration.length - 2) == 'ms') {
|
||||
let value = NumberWrapper.parseInt(this.stripLetters(duration), 10);
|
||||
if (value > maxValue) maxValue = value;
|
||||
} else if (duration.substring(duration.length - 1) == 's') {
|
||||
duration = StringWrapper.replace(duration, ',', '.');
|
||||
let ms = NumberWrapper.parseFloat(this.stripLetters(duration)) * 1000;
|
||||
let value = Math.floor(ms);
|
||||
if (value > maxValue) maxValue = value;
|
||||
}
|
||||
return maxValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the letters from the duration string
|
||||
* @param str
|
||||
* @returns {string}
|
||||
*/
|
||||
stripLetters(str: string): string {
|
||||
return StringWrapper.replaceAll(str, RegExpWrapper.create('[^0-9]+$', ''), '');
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {CssAnimationBuilder} from './css_animation_builder';
|
||||
import {BrowserDetails} from './browser_details';
|
||||
|
||||
@Injectable()
|
||||
export class AnimationBuilder {
|
||||
/**
|
||||
* Used for DI
|
||||
* @param browserDetails
|
||||
*/
|
||||
constructor(public browserDetails: BrowserDetails) {}
|
||||
|
||||
/**
|
||||
* Creates a new CSS Animation
|
||||
* @returns {CssAnimationBuilder}
|
||||
*/
|
||||
css(): CssAnimationBuilder { return new CssAnimationBuilder(this.browserDetails); }
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Math} from '../../src/facade/math';
|
||||
import {getDOM} from '../dom/dom_adapter';
|
||||
|
||||
@Injectable()
|
||||
export class BrowserDetails {
|
||||
elapsedTimeIncludesDelay = false;
|
||||
|
||||
constructor() { this.doesElapsedTimeIncludesDelay(); }
|
||||
|
||||
/**
|
||||
* Determines if `event.elapsedTime` includes transition delay in the current browser. At this
|
||||
* time, Chrome and Opera seem to be the only browsers that include this.
|
||||
*/
|
||||
doesElapsedTimeIncludesDelay(): void {
|
||||
var div = getDOM().createElement('div');
|
||||
getDOM().setAttribute(div, 'style',
|
||||
`position: absolute; top: -9999px; left: -9999px; width: 1px;
|
||||
height: 1px; transition: all 1ms linear 1ms;`);
|
||||
// Firefox requires that we wait for 2 frames for some reason
|
||||
this.raf((timestamp: any) => {
|
||||
getDOM().on(div, 'transitionend', (event: any) => {
|
||||
var elapsed = Math.round(event.elapsedTime * 1000);
|
||||
this.elapsedTimeIncludesDelay = elapsed == 2;
|
||||
getDOM().remove(div);
|
||||
});
|
||||
getDOM().setStyle(div, 'width', '2px');
|
||||
}, 2);
|
||||
}
|
||||
|
||||
raf(callback: Function, frames: number = 1): Function {
|
||||
var queue: RafQueue = new RafQueue(callback, frames);
|
||||
return () => queue.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
class RafQueue {
|
||||
currentFrameId: number;
|
||||
constructor(public callback: Function, public frames: number) { this._raf(); }
|
||||
private _raf() {
|
||||
this.currentFrameId =
|
||||
getDOM().requestAnimationFrame((timestamp: number) => this._nextFrame(timestamp));
|
||||
}
|
||||
private _nextFrame(timestamp: number) {
|
||||
this.frames--;
|
||||
if (this.frames > 0) {
|
||||
this._raf();
|
||||
} else {
|
||||
this.callback(timestamp);
|
||||
}
|
||||
}
|
||||
cancel() {
|
||||
getDOM().cancelAnimationFrame(this.currentFrameId);
|
||||
this.currentFrameId = null;
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
import {CssAnimationOptions} from './css_animation_options';
|
||||
import {Animation} from './animation';
|
||||
import {BrowserDetails} from './browser_details';
|
||||
|
||||
export class CssAnimationBuilder {
|
||||
/** @type {CssAnimationOptions} */
|
||||
data: CssAnimationOptions = new CssAnimationOptions();
|
||||
|
||||
/**
|
||||
* Accepts public properties for CssAnimationBuilder
|
||||
*/
|
||||
constructor(public browserDetails: BrowserDetails) {}
|
||||
|
||||
/**
|
||||
* Adds a temporary class that will be removed at the end of the animation
|
||||
* @param className
|
||||
*/
|
||||
addAnimationClass(className: string): CssAnimationBuilder {
|
||||
this.data.animationClasses.push(className);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a class that will remain on the element after the animation has finished
|
||||
* @param className
|
||||
*/
|
||||
addClass(className: string): CssAnimationBuilder {
|
||||
this.data.classesToAdd.push(className);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a class from the element
|
||||
* @param className
|
||||
*/
|
||||
removeClass(className: string): CssAnimationBuilder {
|
||||
this.data.classesToRemove.push(className);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the animation duration (and overrides any defined through CSS)
|
||||
* @param duration
|
||||
*/
|
||||
setDuration(duration: number): CssAnimationBuilder {
|
||||
this.data.duration = duration;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the animation delay (and overrides any defined through CSS)
|
||||
* @param delay
|
||||
*/
|
||||
setDelay(delay: number): CssAnimationBuilder {
|
||||
this.data.delay = delay;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets styles for both the initial state and the destination state
|
||||
* @param from
|
||||
* @param to
|
||||
*/
|
||||
setStyles(from: {[key: string]: any}, to: {[key: string]: any}): CssAnimationBuilder {
|
||||
return this.setFromStyles(from).setToStyles(to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial styles for the animation
|
||||
* @param from
|
||||
*/
|
||||
setFromStyles(from: {[key: string]: any}): CssAnimationBuilder {
|
||||
this.data.fromStyles = from;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the destination styles for the animation
|
||||
* @param to
|
||||
*/
|
||||
setToStyles(to: {[key: string]: any}): CssAnimationBuilder {
|
||||
this.data.toStyles = to;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the animation and returns a promise
|
||||
* @param element
|
||||
*/
|
||||
start(element: HTMLElement): Animation {
|
||||
return new Animation(element, this.data, this.browserDetails);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
export class CssAnimationOptions {
|
||||
/** initial styles for the element */
|
||||
fromStyles: {[key: string]: any};
|
||||
|
||||
/** destination styles for the element */
|
||||
toStyles: {[key: string]: any};
|
||||
|
||||
/** classes to be added to the element */
|
||||
classesToAdd: string[] = [];
|
||||
|
||||
/** classes to be removed from the element */
|
||||
classesToRemove: string[] = [];
|
||||
|
||||
/** classes to be added for the duration of the animation */
|
||||
animationClasses: string[] = [];
|
||||
|
||||
/** override the duration of the animation (in milliseconds) */
|
||||
duration: number;
|
||||
|
||||
/** override the transition delay (in milliseconds) */
|
||||
delay: number;
|
||||
}
|
@ -19,7 +19,8 @@ import {
|
||||
ComponentRef
|
||||
} from "@angular/core";
|
||||
import {isBlank, isPresent} from "./facade/lang";
|
||||
import {wtfInit, SanitizationService, ReflectionCapabilities} from "../core_private";
|
||||
import {wtfInit, SanitizationService, ReflectionCapabilities, AnimationDriver, NoOpAnimationDriver} from '../core_private';
|
||||
import {WebAnimationsDriver} from '../src/dom/web_animations_driver';
|
||||
import {COMMON_DIRECTIVES, COMMON_PIPES, FORM_PROVIDERS, PlatformLocation} from "@angular/common";
|
||||
import {DomSanitizationService, DomSanitizationServiceImpl} from "./security/dom_sanitization_service";
|
||||
import {BrowserDomAdapter} from "./browser/browser_adapter";
|
||||
@ -33,8 +34,6 @@ import {KeyEventsPlugin} from "./dom/events/key_events";
|
||||
import {ELEMENT_PROBE_PROVIDERS} from "./dom/debug/ng_probe";
|
||||
import {DomEventsPlugin} from "./dom/events/dom_events";
|
||||
import {HAMMER_GESTURE_CONFIG, HammerGestureConfig, HammerGesturesPlugin} from "./dom/events/hammer_gestures";
|
||||
import {AnimationBuilder} from "./animate/animation_builder";
|
||||
import {BrowserDetails} from "./animate/browser_details";
|
||||
import {BrowserPlatformLocation} from "./browser/location/browser_platform_location";
|
||||
import {COMPILER_PROVIDERS, XHR} from "@angular/compiler";
|
||||
import {CachedXHR} from "./xhr/xhr_cache";
|
||||
@ -83,10 +82,9 @@ export const BROWSER_APP_PROVIDERS: Array<any /*Type | Provider | any[]*/> =
|
||||
{provide: DomRootRenderer, useClass: DomRootRenderer_},
|
||||
{provide: RootRenderer, useExisting: DomRootRenderer},
|
||||
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
|
||||
{provide: AnimationDriver, useFactory: _resolveDefaultAnimationDriver},
|
||||
DomSharedStylesHost,
|
||||
Testability,
|
||||
BrowserDetails,
|
||||
AnimationBuilder,
|
||||
EventManager,
|
||||
ELEMENT_PROBE_PROVIDERS
|
||||
];
|
||||
@ -198,3 +196,10 @@ function _exceptionHandler(): ExceptionHandler {
|
||||
function _document(): any {
|
||||
return getDOM().defaultDoc();
|
||||
}
|
||||
|
||||
function _resolveDefaultAnimationDriver(): AnimationDriver {
|
||||
if (getDOM().supportsWebAnimation()) {
|
||||
return new WebAnimationsDriver();
|
||||
}
|
||||
return new NoOpAnimationDriver();
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {ListWrapper} from '../../src/facade/collection';
|
||||
import {isBlank, isPresent, global, setValueOnPath, DateWrapper} from '../../src/facade/lang';
|
||||
|
||||
import {isBlank, isPresent, isFunction, global, setValueOnPath, DateWrapper} from '../../src/facade/lang';
|
||||
import {GenericBrowserDomAdapter} from './generic_browser_adapter';
|
||||
import {setRootDomAdapter} from '../dom/dom_adapter';
|
||||
|
||||
@ -341,6 +340,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
|
||||
setGlobalVar(path: string, value: any) { setValueOnPath(global, path, value); }
|
||||
requestAnimationFrame(callback): number { return window.requestAnimationFrame(callback); }
|
||||
cancelAnimationFrame(id: number) { window.cancelAnimationFrame(id); }
|
||||
supportsWebAnimation(): boolean { return isFunction(document.body['animate']); }
|
||||
performanceNow(): number {
|
||||
// performance.now() is not available in all browsers, see
|
||||
// http://caniuse.com/#search=performance.now
|
||||
|
@ -147,6 +147,7 @@ export abstract class DomAdapter {
|
||||
abstract setGlobalVar(name: string, value: any);
|
||||
abstract requestAnimationFrame(callback): number;
|
||||
abstract cancelAnimationFrame(id);
|
||||
abstract supportsWebAnimation(): boolean;
|
||||
abstract performanceNow(): number;
|
||||
abstract getAnimationPrefix(): string;
|
||||
abstract getTransitionEnd(): string;
|
||||
|
@ -0,0 +1,9 @@
|
||||
export interface DomAnimatePlayer {
|
||||
cancel(): void;
|
||||
play(): void;
|
||||
pause(): void;
|
||||
finish(): void;
|
||||
onfinish: Function;
|
||||
position: number;
|
||||
currentTime: number;
|
||||
}
|
@ -8,7 +8,6 @@ import {
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import {RenderDebugInfo} from '../../core_private';
|
||||
import {AnimationBuilder} from '../animate/animation_builder';
|
||||
import {
|
||||
isPresent,
|
||||
isBlank,
|
||||
@ -24,6 +23,14 @@ import {StringMapWrapper} from '../../src/facade/collection';
|
||||
|
||||
import {BaseException} from '../../src/facade/exceptions';
|
||||
import {DomSharedStylesHost} from './shared_styles_host';
|
||||
|
||||
import {
|
||||
AnimationKeyframe,
|
||||
AnimationStyles,
|
||||
AnimationPlayer,
|
||||
AnimationDriver
|
||||
} from '../../core_private';
|
||||
|
||||
import {EventManager} from './events/event_manager';
|
||||
import {DOCUMENT} from './dom_tokens';
|
||||
import {getDOM} from './dom_adapter';
|
||||
@ -38,12 +45,13 @@ export abstract class DomRootRenderer implements RootRenderer {
|
||||
protected registeredComponents: Map<string, DomRenderer> = new Map<string, DomRenderer>();
|
||||
|
||||
constructor(public document: any, public eventManager: EventManager,
|
||||
public sharedStylesHost: DomSharedStylesHost, public animate: AnimationBuilder) {}
|
||||
public sharedStylesHost: DomSharedStylesHost,
|
||||
public animationDriver: AnimationDriver) {}
|
||||
|
||||
renderComponent(componentProto: RenderComponentType): Renderer {
|
||||
var renderer = this.registeredComponents.get(componentProto.id);
|
||||
if (isBlank(renderer)) {
|
||||
renderer = new DomRenderer(this, componentProto);
|
||||
renderer = new DomRenderer(this, componentProto, this.animationDriver);
|
||||
this.registeredComponents.set(componentProto.id, renderer);
|
||||
}
|
||||
return renderer;
|
||||
@ -53,8 +61,9 @@ export abstract class DomRootRenderer implements RootRenderer {
|
||||
@Injectable()
|
||||
export class DomRootRenderer_ extends DomRootRenderer {
|
||||
constructor(@Inject(DOCUMENT) _document: any, _eventManager: EventManager,
|
||||
sharedStylesHost: DomSharedStylesHost, animate: AnimationBuilder) {
|
||||
super(_document, _eventManager, sharedStylesHost, animate);
|
||||
sharedStylesHost: DomSharedStylesHost,
|
||||
animationDriver: AnimationDriver) {
|
||||
super(_document, _eventManager, sharedStylesHost, animationDriver);
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,7 +72,8 @@ export class DomRenderer implements Renderer {
|
||||
private _hostAttr: string;
|
||||
private _styles: string[];
|
||||
|
||||
constructor(private _rootRenderer: DomRootRenderer, private componentProto: RenderComponentType) {
|
||||
constructor(private _rootRenderer: DomRootRenderer, private componentProto: RenderComponentType,
|
||||
private _animationDriver: AnimationDriver) {
|
||||
this._styles = _flattenStyles(componentProto.id, componentProto.styles, []);
|
||||
if (componentProto.encapsulation !== ViewEncapsulation.Native) {
|
||||
this._rootRenderer.sharedStylesHost.addStyles(this._styles);
|
||||
@ -145,14 +155,11 @@ export class DomRenderer implements Renderer {
|
||||
|
||||
attachViewAfter(node: any, viewRootNodes: any[]) {
|
||||
moveNodesAfterSibling(node, viewRootNodes);
|
||||
for (let i = 0; i < viewRootNodes.length; i++) this.animateNodeEnter(viewRootNodes[i]);
|
||||
}
|
||||
|
||||
detachView(viewRootNodes: any[]) {
|
||||
for (var i = 0; i < viewRootNodes.length; i++) {
|
||||
var node = viewRootNodes[i];
|
||||
getDOM().remove(node);
|
||||
this.animateNodeLeave(node);
|
||||
getDOM().remove(viewRootNodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,39 +247,11 @@ export class DomRenderer implements Renderer {
|
||||
|
||||
setText(renderNode: any, text: string): void { getDOM().setText(renderNode, text); }
|
||||
|
||||
/**
|
||||
* Performs animations if necessary
|
||||
* @param node
|
||||
*/
|
||||
animateNodeEnter(node: Node) {
|
||||
if (getDOM().isElementNode(node) && getDOM().hasClass(node, 'ng-animate')) {
|
||||
getDOM().addClass(node, 'ng-enter');
|
||||
this._rootRenderer.animate.css()
|
||||
.addAnimationClass('ng-enter-active')
|
||||
.start(<HTMLElement>node)
|
||||
.onComplete(() => { getDOM().removeClass(node, 'ng-enter'); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If animations are necessary, performs animations then removes the element; otherwise, it just
|
||||
* removes the element.
|
||||
* @param node
|
||||
*/
|
||||
animateNodeLeave(node: Node) {
|
||||
if (getDOM().isElementNode(node) && getDOM().hasClass(node, 'ng-animate')) {
|
||||
getDOM().addClass(node, 'ng-leave');
|
||||
this._rootRenderer.animate.css()
|
||||
.addAnimationClass('ng-leave-active')
|
||||
.start(<HTMLElement>node)
|
||||
.onComplete(() => {
|
||||
getDOM().removeClass(node, 'ng-leave');
|
||||
getDOM().remove(node);
|
||||
});
|
||||
} else {
|
||||
getDOM().remove(node);
|
||||
}
|
||||
animate(element: any,
|
||||
startingStyles: AnimationStyles,
|
||||
keyframes: AnimationKeyframe[], duration: number, delay: number,
|
||||
easing: string): AnimationPlayer {
|
||||
return this._animationDriver.animate(element, startingStyles, keyframes, duration, delay, easing);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,136 @@
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isPresent, isNumber, StringWrapper} from '../facade/lang';
|
||||
import {BaseException, AUTO_STYLE} from '@angular/core';
|
||||
|
||||
import {
|
||||
AnimationDriver,
|
||||
AnimationPlayer,
|
||||
NoOpAnimationPlayer,
|
||||
AnimationKeyframe,
|
||||
AnimationStyles
|
||||
} from '../../core_private';
|
||||
|
||||
import {WebAnimationsPlayer} from './web_animations_player';
|
||||
|
||||
import {getDOM} from './dom_adapter';
|
||||
|
||||
export class WebAnimationsDriver implements AnimationDriver {
|
||||
animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number,
|
||||
easing: string): AnimationPlayer {
|
||||
|
||||
var anyElm = <any>element;
|
||||
|
||||
var formattedSteps = [];
|
||||
var startingStyleLookup: {[key: string]: string|number}= {};
|
||||
if (isPresent(startingStyles) && startingStyles.styles.length > 0) {
|
||||
startingStyleLookup = _populateStyles(anyElm, startingStyles, {});
|
||||
startingStyleLookup['offset'] = 0;
|
||||
formattedSteps.push(startingStyleLookup);
|
||||
}
|
||||
|
||||
keyframes.forEach((keyframe: AnimationKeyframe) => {
|
||||
let data = _populateStyles(anyElm, keyframe.styles, startingStyleLookup);
|
||||
data['offset'] = keyframe.offset;
|
||||
formattedSteps.push(data);
|
||||
});
|
||||
|
||||
// this is a special case when only styles are applied as an
|
||||
// animation. When this occurs we want to animate from start to
|
||||
// end with the same values. Removing the offset and having only
|
||||
// start/end values is suitable enough for the web-animations API
|
||||
if (formattedSteps.length == 1) {
|
||||
var start = formattedSteps[0];
|
||||
start.offset = null;
|
||||
formattedSteps = [start, start];
|
||||
}
|
||||
|
||||
var player = anyElm.animate(
|
||||
formattedSteps,
|
||||
{'duration': duration, 'delay': delay, 'easing': easing, 'fill': 'forwards'});
|
||||
|
||||
return new WebAnimationsPlayer(player, duration);
|
||||
}
|
||||
}
|
||||
|
||||
function _populateStyles(element: any, styles: AnimationStyles, defaultStyles: {[key: string]: string|number}) {
|
||||
var data = {};
|
||||
styles.styles.forEach((entry) => {
|
||||
StringMapWrapper.forEach(entry, (val, prop) => {
|
||||
data[prop] = val == AUTO_STYLE
|
||||
? _computeStyle(element, prop)
|
||||
: val.toString() + _resolveStyleUnit(val, prop);
|
||||
});
|
||||
});
|
||||
StringMapWrapper.forEach(defaultStyles, (value, prop) => {
|
||||
if (!isPresent(data[prop])) {
|
||||
data[prop] = value;
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function _resolveStyleUnit(val: string | number, prop: string): string {
|
||||
var unit = '';
|
||||
if (_isPixelDimensionStyle(prop) && val != 0 && val != '0') {
|
||||
if (isNumber(val)) {
|
||||
unit = 'px';
|
||||
} else if (_findDimensionalSuffix(val.toString()).length == 0) {
|
||||
throw new BaseException('Please provide a CSS unit value for ' + prop + ':' + val);
|
||||
}
|
||||
}
|
||||
return unit;
|
||||
}
|
||||
|
||||
const _$0 = 48;
|
||||
const _$9 = 57;
|
||||
const _$PERIOD = 46;
|
||||
|
||||
function _findDimensionalSuffix(value: string): string {
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
var c = StringWrapper.charCodeAt(value, i);
|
||||
if ((c >= _$0 && c <= _$9) || c == _$PERIOD) continue;
|
||||
return value.substring(i, value.length);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function _isPixelDimensionStyle(prop: string): boolean {
|
||||
switch (prop) {
|
||||
case 'width':
|
||||
case 'height':
|
||||
case 'min-width':
|
||||
case 'min-height':
|
||||
case 'max-width':
|
||||
case 'max-height':
|
||||
case 'left':
|
||||
case 'top':
|
||||
case 'bottom':
|
||||
case 'right':
|
||||
case 'font-size':
|
||||
case 'outline-width':
|
||||
case 'outline-offset':
|
||||
case 'padding-top':
|
||||
case 'padding-left':
|
||||
case 'padding-bottom':
|
||||
case 'padding-right':
|
||||
case 'margin-top':
|
||||
case 'margin-left':
|
||||
case 'margin-bottom':
|
||||
case 'margin-right':
|
||||
case 'border-radius':
|
||||
case 'border-width':
|
||||
case 'border-top-width':
|
||||
case 'border-left-width':
|
||||
case 'border-right-width':
|
||||
case 'border-bottom-width':
|
||||
case 'text-indent':
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function _computeStyle(element: any, prop: string): string {
|
||||
return getDOM().getComputedStyle(element)[prop];
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {AnimationPlayer} from '../../core_private';
|
||||
import {DomAnimatePlayer} from './dom_animate_player';
|
||||
|
||||
export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
private _subscriptions: Function[] = [];
|
||||
private _finished = false;
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
|
||||
constructor(private _player: DomAnimatePlayer, public totalTime: number) {
|
||||
// this is required to make the player startable at a later time
|
||||
this.reset();
|
||||
this._player.onfinish = () => this._onFinish();
|
||||
}
|
||||
|
||||
private _onFinish() {
|
||||
if (!this._finished) {
|
||||
this._finished = true;
|
||||
if (!isPresent(this.parentPlayer)) {
|
||||
this.destroy();
|
||||
}
|
||||
this._subscriptions.forEach(fn => fn());
|
||||
this._subscriptions = [];
|
||||
}
|
||||
}
|
||||
|
||||
onDone(fn: Function): void { this._subscriptions.push(fn); }
|
||||
|
||||
play(): void { this._player.play(); }
|
||||
|
||||
pause(): void { this._player.pause(); }
|
||||
|
||||
finish(): void {
|
||||
this._onFinish();
|
||||
this._player.finish();
|
||||
}
|
||||
|
||||
reset(): void { this._player.cancel(); }
|
||||
|
||||
restart(): void {
|
||||
this.reset();
|
||||
this.play();
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.reset();
|
||||
this._onFinish();
|
||||
}
|
||||
|
||||
setPosition(p): void {
|
||||
this._player.currentTime = p * this.totalTime;
|
||||
}
|
||||
|
||||
getPosition(): number {
|
||||
return this._player.currentTime / this.totalTime;
|
||||
}
|
||||
}
|
@ -17,6 +17,8 @@ import {MessageBus} from '../shared/message_bus';
|
||||
import {ObservableWrapper} from '../../../src/facade/async';
|
||||
import {deserializeGenericEvent} from './event_deserializer';
|
||||
|
||||
import {AnimationKeyframe, AnimationPlayer, AnimationStyles} from '../../../core_private';
|
||||
|
||||
@Injectable()
|
||||
export class WebWorkerRootRenderer implements RootRenderer {
|
||||
private _messageBroker;
|
||||
@ -241,6 +243,12 @@ export class WebWorkerRenderer implements Renderer, RenderStoreObject {
|
||||
this._runOnService('listenDone', [new FnArg(unlistenCallbackId, null)]);
|
||||
};
|
||||
}
|
||||
|
||||
animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number,
|
||||
easing: string): AnimationPlayer {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class NamedEventEmitter {
|
||||
|
@ -152,4 +152,5 @@ export class WorkerDomAdapter extends DomAdapter {
|
||||
getAnimationPrefix(): string { throw "not implemented"; }
|
||||
getTransitionEnd(): string { throw "not implemented"; }
|
||||
supportsAnimation(): boolean { throw "not implemented"; }
|
||||
}
|
||||
supportsWebAnimation(): boolean { throw "not implemented"; }
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
APP_INITIALIZER,
|
||||
ApplicationRef
|
||||
} from "@angular/core";
|
||||
import {wtfInit} from "../core_private";
|
||||
import {wtfInit, AnimationDriver, NoOpAnimationDriver} from '../core_private';
|
||||
import {getDOM} from "./dom/dom_adapter";
|
||||
import {DomEventsPlugin} from "./dom/events/dom_events";
|
||||
import {KeyEventsPlugin} from "./dom/events/key_events";
|
||||
@ -27,8 +27,6 @@ import {HammerGesturesPlugin, HAMMER_GESTURE_CONFIG, HammerGestureConfig} from "
|
||||
import {DOCUMENT} from "./dom/dom_tokens";
|
||||
import {DomRootRenderer, DomRootRenderer_} from "./dom/dom_renderer";
|
||||
import {DomSharedStylesHost, SharedStylesHost} from "./dom/shared_styles_host";
|
||||
import {BrowserDetails} from "./animate/browser_details";
|
||||
import {AnimationBuilder} from "./animate/animation_builder";
|
||||
import {BrowserGetTestability} from "./browser/testability";
|
||||
import {BrowserDomAdapter} from "./browser/browser_adapter";
|
||||
import {MessageBasedRenderer} from "./web_workers/ui/renderer";
|
||||
@ -96,13 +94,12 @@ export const WORKER_RENDER_APPLICATION_PROVIDERS: Array<any /*Type | Provider |
|
||||
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
|
||||
{provide: ServiceMessageBrokerFactory, useClass: ServiceMessageBrokerFactory_},
|
||||
{provide: ClientMessageBrokerFactory, useClass: ClientMessageBrokerFactory_},
|
||||
{provide: AnimationDriver, useFactory: _resolveDefaultAnimationDriver},
|
||||
Serializer,
|
||||
{provide: ON_WEB_WORKER, useValue: false},
|
||||
RenderStore,
|
||||
DomSharedStylesHost,
|
||||
Testability,
|
||||
BrowserDetails,
|
||||
AnimationBuilder,
|
||||
EventManager,
|
||||
WebWorkerInstance,
|
||||
{
|
||||
@ -192,3 +189,9 @@ function spawnWebWorker(uri: string, instance: WebWorkerInstance): void {
|
||||
|
||||
instance.init(webWorker, bus);
|
||||
}
|
||||
|
||||
function _resolveDefaultAnimationDriver(): AnimationDriver {
|
||||
// web workers have not been tested or configured to
|
||||
// work with animations just yet...
|
||||
return new NoOpAnimationDriver();
|
||||
}
|
||||
|
@ -1,146 +0,0 @@
|
||||
import {describe, it, iit, expect, inject} from '@angular/core/testing';
|
||||
import {AnimationBuilder} from '../../src/animate/animation_builder';
|
||||
import {getDOM} from '../../src/dom/dom_adapter';
|
||||
import {el} from '../../testing/browser_util';
|
||||
import {SpyObject} from '@angular/core/testing/testing_internal';
|
||||
|
||||
export function main() {
|
||||
describe("AnimationBuilder", () => {
|
||||
|
||||
it('should have data object', inject([AnimationBuilder], animate => {
|
||||
var animateCss = animate.css();
|
||||
expect(animateCss.data).toBeDefined();
|
||||
}));
|
||||
|
||||
it('should allow you to add classes', inject([AnimationBuilder], animate => {
|
||||
var animateCss = animate.css();
|
||||
animateCss.addClass('some-class');
|
||||
expect(animateCss.data.classesToAdd).toEqual(['some-class']);
|
||||
animateCss.addClass('another-class');
|
||||
expect(animateCss.data.classesToAdd).toEqual(['some-class', 'another-class']);
|
||||
}));
|
||||
|
||||
it('should allow you to add temporary classes', inject([AnimationBuilder], animate => {
|
||||
var animateCss = animate.css();
|
||||
animateCss.addAnimationClass('some-class');
|
||||
expect(animateCss.data.animationClasses).toEqual(['some-class']);
|
||||
animateCss.addAnimationClass('another-class');
|
||||
expect(animateCss.data.animationClasses).toEqual(['some-class', 'another-class']);
|
||||
}));
|
||||
|
||||
it('should allow you to remove classes', inject([AnimationBuilder], animate => {
|
||||
var animateCss = animate.css();
|
||||
animateCss.removeClass('some-class');
|
||||
expect(animateCss.data.classesToRemove).toEqual(['some-class']);
|
||||
animateCss.removeClass('another-class');
|
||||
expect(animateCss.data.classesToRemove).toEqual(['some-class', 'another-class']);
|
||||
}));
|
||||
|
||||
it('should support chaining', inject([AnimationBuilder], animate => {
|
||||
var animateCss = animate.css()
|
||||
.addClass('added-class')
|
||||
.removeClass('removed-class')
|
||||
.addAnimationClass('temp-class')
|
||||
.addClass('another-added-class');
|
||||
expect(animateCss.data.classesToAdd).toEqual(['added-class', 'another-added-class']);
|
||||
expect(animateCss.data.classesToRemove).toEqual(['removed-class']);
|
||||
expect(animateCss.data.animationClasses).toEqual(['temp-class']);
|
||||
}));
|
||||
|
||||
it('should support duration and delay', inject([AnimationBuilder], (animate) => {
|
||||
var animateCss = animate.css();
|
||||
animateCss.setDelay(100).setDuration(200);
|
||||
expect(animateCss.data.duration).toBe(200);
|
||||
expect(animateCss.data.delay).toBe(100);
|
||||
|
||||
var element = el('<div></div>');
|
||||
var runner = animateCss.start(element);
|
||||
runner.flush();
|
||||
|
||||
if (getDOM().supportsAnimation()) {
|
||||
expect(runner.computedDelay).toBe(100);
|
||||
expect(runner.computedDuration).toBe(200);
|
||||
} else {
|
||||
expect(runner.computedDelay).toBe(0);
|
||||
expect(runner.computedDuration).toBe(0);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should support from styles', inject([AnimationBuilder], animate => {
|
||||
var animateCss = animate.css();
|
||||
animateCss.setFromStyles({'backgroundColor': 'blue'});
|
||||
expect(animateCss.data.fromStyles).toBeDefined();
|
||||
|
||||
var element = el('<div></div>');
|
||||
animateCss.start(element);
|
||||
|
||||
expect(element.style.getPropertyValue('background-color')).toEqual('blue');
|
||||
}));
|
||||
|
||||
it('should support duration and delay defined in CSS', inject([AnimationBuilder], (animate) => {
|
||||
var animateCss = animate.css();
|
||||
var element =
|
||||
el(`<div style="${getDOM().getAnimationPrefix()}transition: 0.5s ease 250ms;"></div>`);
|
||||
var runner = animateCss.start(element);
|
||||
runner.flush();
|
||||
|
||||
if (getDOM().supportsAnimation()) {
|
||||
expect(runner.computedDelay).toBe(250);
|
||||
expect(runner.computedDuration).toBe(500);
|
||||
} else {
|
||||
expect(runner.computedDelay).toEqual(0);
|
||||
expect(runner.computedDuration).toEqual(0);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should support parsing when commas are used as decimal separator due to regional settings',
|
||||
inject([AnimationBuilder], (animate) => {
|
||||
var animateCss = animate.css();
|
||||
var element = el(`<div></div>`);
|
||||
var runner = animateCss.start(element);
|
||||
expect(runner.parseDurationString('0,5s')).toBe(500);
|
||||
expect(runner.parseDurationString('0.5s')).toBe(500);
|
||||
}));
|
||||
|
||||
it('should add classes', inject([AnimationBuilder], (animate) => {
|
||||
var animateCss = animate.css().addClass('one').addClass('two');
|
||||
var element = el('<div></div>');
|
||||
var runner = animateCss.start(element);
|
||||
|
||||
expect(element).not.toHaveCssClass('one');
|
||||
expect(element).not.toHaveCssClass('two');
|
||||
|
||||
runner.flush();
|
||||
|
||||
expect(element).toHaveCssClass('one');
|
||||
expect(element).toHaveCssClass('two');
|
||||
}));
|
||||
|
||||
it('should call `onComplete` method after animations have finished',
|
||||
inject([AnimationBuilder], (animate) => {
|
||||
var spyObject = new SpyObject();
|
||||
var callback = spyObject.spy('animationFinished');
|
||||
var runner = animate.css()
|
||||
.addClass('one')
|
||||
.addClass('two')
|
||||
.setDuration(100)
|
||||
.start(el('<div></div>'))
|
||||
.onComplete(callback);
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
runner.flush();
|
||||
|
||||
if (getDOM().supportsAnimation()) {
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
runner.handleAnimationCompleted();
|
||||
|
||||
expect(callback).toHaveBeenCalled();
|
||||
} else {
|
||||
expect(callback).toHaveBeenCalled();
|
||||
}
|
||||
}));
|
||||
|
||||
});
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
xdescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
beforeEachProviders
|
||||
} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {isPresent} from "../../src/facade/lang";
|
||||
import {WebAnimationsPlayer} from '../../src/dom/web_animations_player';
|
||||
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
|
||||
import {MockAnimationPlayer} from '../../core_private';
|
||||
|
||||
export class MockDomAnimatePlayer implements DomAnimatePlayer {
|
||||
public captures: {[key: string]: any[]} = {};
|
||||
private _position: number = 0;
|
||||
private _onfinish = () => {};
|
||||
public currentTime: number;
|
||||
|
||||
_capture(method: string, data: any) {
|
||||
if (!isPresent(this.captures[method])) {
|
||||
this.captures[method] = [];
|
||||
}
|
||||
this.captures[method].push(data);
|
||||
}
|
||||
|
||||
cancel() { this._capture('cancel', null); }
|
||||
play() { this._capture('play', null); }
|
||||
pause() { this._capture('pause', null); }
|
||||
finish() {
|
||||
this._capture('finish', null);
|
||||
this._onfinish();
|
||||
}
|
||||
set onfinish(fn) {
|
||||
this._capture('onfinish', fn);
|
||||
this._onfinish = fn;
|
||||
}
|
||||
get onfinish() { return this._onfinish; }
|
||||
set position(val) {
|
||||
this._capture('position', val);
|
||||
this._position = val;
|
||||
}
|
||||
get position() { return this._position; }
|
||||
}
|
||||
|
||||
export function main() {
|
||||
function makePlayer(): {[key: string]: any} {
|
||||
var mockPlayer = new MockDomAnimatePlayer();
|
||||
var c = mockPlayer.captures;
|
||||
var p = new WebAnimationsPlayer(mockPlayer, 0);
|
||||
return {'captures': c, 'player': p};
|
||||
}
|
||||
|
||||
describe('WebAnimationsPlayer', () => {
|
||||
var player, captures;
|
||||
beforeEach(() => {
|
||||
var newPlayer = makePlayer();
|
||||
captures = <{[key: string]: any}>newPlayer['captures'];
|
||||
player = <WebAnimationsPlayer>newPlayer['player'];
|
||||
});
|
||||
|
||||
it('should pause the animation', () => {
|
||||
expect(captures['pause']).toBeFalsy();
|
||||
player.pause();
|
||||
expect(captures['pause'].length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should play the animation', () => {
|
||||
expect(captures['play']).toBeFalsy();
|
||||
player.play();
|
||||
expect(captures['play'].length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should finish the animation', () => {
|
||||
expect(captures['finish']).toBeFalsy();
|
||||
player.finish();
|
||||
expect(captures['finish'].length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should make use of the onfinish function',
|
||||
() => { expect(captures['onfinish'].length).toEqual(1); });
|
||||
|
||||
it('should trigger the subscribe functions when complete', () => {
|
||||
var count = 0;
|
||||
var method = () => { count++; };
|
||||
|
||||
player.onDone(method);
|
||||
player.onDone(method);
|
||||
player.onDone(method);
|
||||
|
||||
expect(count).toEqual(0);
|
||||
captures['onfinish'][0]();
|
||||
expect(count).toEqual(3);
|
||||
});
|
||||
|
||||
it('should finish right away when finish is called directly', () => {
|
||||
var completed = false;
|
||||
player.onDone(() => completed = true);
|
||||
expect(completed).toEqual(false);
|
||||
|
||||
player.finish();
|
||||
expect(completed).toEqual(true);
|
||||
|
||||
completed = false;
|
||||
player.finish();
|
||||
expect(completed).toEqual(false);
|
||||
});
|
||||
|
||||
it('should trigger finish when destroy is called if the animation has not finished already',
|
||||
() => {
|
||||
var count = 0;
|
||||
var method = () => { count++; };
|
||||
|
||||
player.onDone(method);
|
||||
expect(count).toEqual(0);
|
||||
player.destroy();
|
||||
expect(count).toEqual(1);
|
||||
|
||||
var player2 = makePlayer()['player'];
|
||||
player2.onDone(method);
|
||||
expect(count).toEqual(1);
|
||||
player2.finish();
|
||||
expect(count).toEqual(2);
|
||||
player2.destroy();
|
||||
expect(count).toEqual(2);
|
||||
});
|
||||
|
||||
it('should destroy itself automatically if a parent player is not present',
|
||||
() => {
|
||||
captures['cancel'] = [];
|
||||
player.finish();
|
||||
|
||||
expect(captures['finish'].length).toEqual(1);
|
||||
expect(captures['cancel'].length).toEqual(1);
|
||||
|
||||
var next = makePlayer();
|
||||
var player2 = next['player'];
|
||||
player2.parentPlayer = new MockAnimationPlayer();
|
||||
|
||||
var captures2 = next['captures'];
|
||||
captures2['cancel'] = [];
|
||||
|
||||
player2.finish();
|
||||
expect(captures2['finish'].length).toEqual(1);
|
||||
expect(captures2['cancel'].length).toEqual(0);
|
||||
});
|
||||
});
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Animation} from '../src/animate/animation';
|
||||
import {CssAnimationOptions} from '../src/animate/css_animation_options';
|
||||
import {BrowserDetails} from '../src/animate/browser_details';
|
||||
import {AnimationBuilder} from '../src/animate/animation_builder';
|
||||
import {CssAnimationBuilder} from '../src/animate/css_animation_builder';
|
||||
|
||||
@Injectable()
|
||||
export class MockAnimationBuilder extends AnimationBuilder {
|
||||
constructor() { super(null); }
|
||||
css(): CssAnimationBuilder { return new MockCssAnimationBuilder(); }
|
||||
}
|
||||
|
||||
class MockCssAnimationBuilder extends CssAnimationBuilder {
|
||||
constructor() { super(null); }
|
||||
start(element: HTMLElement): Animation { return new MockAnimation(element, this.data); }
|
||||
}
|
||||
|
||||
class MockBrowserAbstraction extends BrowserDetails {
|
||||
doesElapsedTimeIncludesDelay(): void { this.elapsedTimeIncludesDelay = false; }
|
||||
}
|
||||
|
||||
class MockAnimation extends Animation {
|
||||
private _callback: Function;
|
||||
constructor(element: HTMLElement, data: CssAnimationOptions) {
|
||||
super(element, data, new MockBrowserAbstraction());
|
||||
}
|
||||
wait(callback: Function) { this._callback = callback; }
|
||||
flush() {
|
||||
this._callback(0);
|
||||
this._callback = null;
|
||||
}
|
||||
}
|
@ -1,13 +1,12 @@
|
||||
import {APP_ID, NgZone, PLATFORM_COMMON_PROVIDERS, PLATFORM_INITIALIZER} from '@angular/core';
|
||||
import {BROWSER_APP_PROVIDERS} from '../src/browser';
|
||||
import {BrowserDomAdapter} from '../src/browser/browser_adapter';
|
||||
import {AnimationBuilder} from '../src/animate/animation_builder';
|
||||
import {MockAnimationBuilder} from './animation_builder_mock';
|
||||
import {MockLocationStrategy} from '@angular/common/testing';
|
||||
import {LocationStrategy} from '@angular/common';
|
||||
import {BrowserDetection} from './browser_util';
|
||||
import {Log} from '@angular/core/testing';
|
||||
import {ELEMENT_PROBE_PROVIDERS} from '../src/dom/debug/ng_probe';
|
||||
import {AnimationDriver, NoOpAnimationDriver} from '../core_private';
|
||||
|
||||
/**
|
||||
* Default platform providers for testing without a compiler.
|
||||
@ -25,7 +24,7 @@ export const ADDITIONAL_TEST_BROWSER_STATIC_PROVIDERS: Array<any /*Type | Provid
|
||||
Log,
|
||||
{provide: NgZone, useFactory: createNgZone},
|
||||
{provide: LocationStrategy, useClass: MockLocationStrategy},
|
||||
{provide: AnimationBuilder, useClass: MockAnimationBuilder}
|
||||
{provide: AnimationDriver, useClass: NoOpAnimationDriver}
|
||||
];
|
||||
|
||||
/**
|
||||
|
10
modules/@angular/platform-server/core_private.ts
Normal file
10
modules/@angular/platform-server/core_private.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import {__core_private__ as r, __core_private_types__ as t} from '@angular/core';
|
||||
|
||||
export type NoOpAnimationPlayer = t.NoOpAnimationPlayer;
|
||||
export var NoOpAnimationPlayer: typeof t.NoOpAnimationPlayer = r.NoOpAnimationPlayer;
|
||||
export type AnimationPlayer = t.AnimationPlayer;
|
||||
export var AnimationPlayer: typeof t.AnimationPlayer = r.AnimationPlayer;
|
||||
export type NoOpAnimationDriver = t.NoOpAnimationDriver;
|
||||
export var NoOpAnimationDriver: typeof t.NoOpAnimationDriver = r.NoOpAnimationDriver;
|
||||
export type AnimationDriver = t.AnimationDriver;
|
||||
export var AnimationDriver: typeof t.AnimationDriver = r.AnimationDriver;
|
@ -7,6 +7,7 @@ import 'package:angular2/platform/common_dom.dart';
|
||||
import 'package:angular2/src/compiler/xhr.dart';
|
||||
|
||||
import 'package:angular2/src/facade/lang.dart' show isBlank, isPresent;
|
||||
import 'package:angular2/src/platform/dom/animation/dom_animate_player.dart' show DomAnimatePlayer;
|
||||
|
||||
const _attrToPropMap = const {
|
||||
'innerHtml': 'innerHTML',
|
||||
@ -385,6 +386,10 @@ abstract class AbstractHtml5LibAdapter implements DomAdapter {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool supportsWebAnimation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
getHistory() {
|
||||
throw 'not implemented';
|
||||
}
|
||||
@ -445,4 +450,8 @@ abstract class AbstractHtml5LibAdapter implements DomAdapter {
|
||||
supportsAnimation() {
|
||||
throw 'not implemented';
|
||||
}
|
||||
|
||||
DomAnimatePlayer animate(element, keyframes, options) {
|
||||
throw 'not implemented';
|
||||
}
|
||||
}
|
||||
|
@ -553,6 +553,7 @@ export class Parse5DomAdapter extends DomAdapter {
|
||||
setGlobalVar(path: string, value: any) { setValueOnPath(global, path, value); }
|
||||
requestAnimationFrame(callback): number { return setTimeout(callback, 0); }
|
||||
cancelAnimationFrame(id: number) { clearTimeout(id); }
|
||||
supportsWebAnimation(): boolean { return true; }
|
||||
performanceNow(): number { return DateWrapper.toMillis(DateWrapper.now()); }
|
||||
getAnimationPrefix(): string { return ''; }
|
||||
getTransitionEnd(): string { return 'transitionend'; }
|
||||
|
@ -14,10 +14,9 @@ import {
|
||||
TestComponentRenderer
|
||||
} from '@angular/compiler/testing';
|
||||
import {Parse5DomAdapter} from '../index';
|
||||
import {AnimationBuilder} from '../../platform-browser/src/animate/animation_builder';
|
||||
import {MockAnimationBuilder} from '../../platform-browser/testing/animation_builder_mock';
|
||||
import {MockLocationStrategy} from '../../common/testing/mock_location_strategy';
|
||||
import {BrowserDetection, DOMTestComponentRenderer} from '@angular/platform-browser/testing';
|
||||
import {AnimationDriver, NoOpAnimationDriver} from '../core_private';
|
||||
import {
|
||||
DOCUMENT,
|
||||
BROWSER_SANITIZATION_PROVIDERS,
|
||||
@ -73,6 +72,7 @@ export const TEST_SERVER_APPLICATION_PROVIDERS: Array<any /*Type | Provider | an
|
||||
/* @ts2dart_Provider */ {provide: DOCUMENT, useFactory: appDoc},
|
||||
/* @ts2dart_Provider */ {provide: DomRootRenderer, useClass: DomRootRenderer_},
|
||||
/* @ts2dart_Provider */ {provide: RootRenderer, useExisting: DomRootRenderer},
|
||||
/* @ts2dart_Provider */ {provide: AnimationDriver, useClass: NoOpAnimationDriver},
|
||||
EventManager,
|
||||
/* @ts2dart_Provider */ {provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},
|
||||
/* @ts2dart_Provider */ {provide: XHR, useClass: XHR},
|
||||
@ -86,6 +86,5 @@ export const TEST_SERVER_APPLICATION_PROVIDERS: Array<any /*Type | Provider | an
|
||||
/* @ts2dart_Provider */ {provide: TestComponentRenderer, useClass: DOMTestComponentRenderer},
|
||||
TestComponentBuilder,
|
||||
/* @ts2dart_Provider */ {provide: NgZone, useFactory: createNgZone},
|
||||
/* @ts2dart_Provider */ {provide: LocationStrategy, useClass: MockLocationStrategy},
|
||||
/* @ts2dart_Provider */ {provide: AnimationBuilder, useClass: MockAnimationBuilder},
|
||||
/* @ts2dart_Provider */ {provide: LocationStrategy, useClass: MockLocationStrategy}
|
||||
];
|
||||
|
@ -1,13 +1,79 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
trigger,
|
||||
state,
|
||||
transition,
|
||||
keyframes,
|
||||
group,
|
||||
animate,
|
||||
style,
|
||||
sequence
|
||||
} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'animate-app',
|
||||
styleUrls: ['css/animate-app.css'],
|
||||
template: `
|
||||
<h1>The box is {{visible ? 'visible' : 'hidden'}}</h1>
|
||||
<div class="ng-animate box" *ngIf="visible"></div>
|
||||
<button (click)="visible = !visible">Animate</button>
|
||||
`
|
||||
<div @backgroundAnimation="bgStatus">
|
||||
<button (click)="state='start'">Start State</button>
|
||||
<button (click)="state='active'">Active State</button>
|
||||
<button (click)="state='void'">Void State</button>
|
||||
<button style="float:right" (click)="bgStatus='blur'">Blur Page</button>
|
||||
<hr />
|
||||
<div *ngFor="let item of items" class="box" @boxAnimation="state">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [
|
||||
trigger("backgroundAnimation", [
|
||||
state("focus", style({ "background-color":"white" })),
|
||||
state("blur", style({ "background-color":"grey" })),
|
||||
transition("* => *", [
|
||||
animate(500)
|
||||
])
|
||||
]),
|
||||
trigger("boxAnimation", [
|
||||
state("void, hidden", style({ "height": 0, "opacity": 0 })),
|
||||
state("start", style({ "background-color": "red", "height": "*" })),
|
||||
state("active", style({ "background-color": "orange", "color": "white", "font-size":"100px" })),
|
||||
|
||||
transition("active <=> start", [
|
||||
animate(500, style({ "transform": "scale(2)" })),
|
||||
animate(500)
|
||||
]),
|
||||
|
||||
transition("* => *", [
|
||||
animate(1000, style({ "opacity": 1, "height": 300 })),
|
||||
animate(1000, style({ "background-color": "blue" })),
|
||||
animate(1000, keyframes([
|
||||
style({ "background-color": "blue", "color": "black", "offset": 0.2 }),
|
||||
style({ "background-color": "brown", "color": "black", "offset": 0.5 }),
|
||||
style({ "background-color": "black", "color": "white", "offset": 1 })
|
||||
])),
|
||||
animate(2000)
|
||||
])
|
||||
])
|
||||
]
|
||||
})
|
||||
export class AnimateApp {
|
||||
visible: boolean = false;
|
||||
public items = [];
|
||||
public _state;
|
||||
|
||||
public bgStatus = 'focus';
|
||||
|
||||
get state() { return this._state; }
|
||||
set state(s) {
|
||||
this._state = s;
|
||||
if (s == 'start' || s == 'active') {
|
||||
this.items = [
|
||||
1,2,3,4,5,
|
||||
6,7,8,9,10,
|
||||
11,12,13,14,15,
|
||||
16,17,18,19,20
|
||||
];
|
||||
} else {
|
||||
this.items = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
modules/playground/src/animate/css/animate-app.css
Normal file
21
modules/playground/src/animate/css/animate-app.css
Normal file
@ -0,0 +1,21 @@
|
||||
button {
|
||||
background:red;
|
||||
font-size:20px;
|
||||
color:white;
|
||||
border:0;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.box {
|
||||
font-size:50px;
|
||||
border:10px solid black;
|
||||
width:200px;
|
||||
font-size:80px;
|
||||
line-height:100px;
|
||||
height:100px;
|
||||
display:inline-block;
|
||||
vertical-align:top;
|
||||
text-align:center;
|
||||
margin:5px;
|
||||
overflow:hidden;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: red;
|
||||
transition: all 0.35s ease-in-out;
|
||||
}
|
||||
.box.blue {
|
||||
background-color: blue;
|
||||
}
|
||||
.box.ng-enter {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
}
|
||||
.box.ng-enter-active {
|
||||
opacity: 1;
|
||||
height: 100px;
|
||||
}
|
||||
.box.ng-leave-active {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
}
|
@ -56,6 +56,41 @@ const CORE = [
|
||||
'Binding.toClass:any',
|
||||
'Binding.toFactory:any',
|
||||
'Binding.toValue:any',
|
||||
'AnimationAnimateMetadata',
|
||||
'AnimationAnimateMetadata.constructor(timings:string|number, styles:AnimationStyleMetadata|AnimationKeyframesSequenceMetadata)',
|
||||
'AnimationEntryMetadata',
|
||||
'AnimationEntryMetadata.constructor(name:string, definitions:AnimationStateMetadata[])',
|
||||
'AnimationGroupMetadata',
|
||||
'AnimationGroupMetadata.constructor(_steps:AnimationMetadata[])',
|
||||
'AnimationGroupMetadata.steps:AnimationMetadata[]',
|
||||
'AnimationKeyframesSequenceMetadata',
|
||||
'AnimationKeyframesSequenceMetadata.constructor(steps:AnimationStyleMetadata[])',
|
||||
'AnimationMetadata',
|
||||
'AnimationPlayer',
|
||||
'AnimationPlayer.destroy():void',
|
||||
'AnimationPlayer.finish():void',
|
||||
'AnimationPlayer.getPosition():number',
|
||||
'AnimationPlayer.onDone(fn:Function):void',
|
||||
'AnimationPlayer.parentPlayer:AnimationPlayer',
|
||||
'AnimationPlayer.parentPlayer=(player:AnimationPlayer)',
|
||||
'AnimationPlayer.pause():void',
|
||||
'AnimationPlayer.play():void',
|
||||
'AnimationPlayer.reset():void',
|
||||
'AnimationPlayer.restart():void',
|
||||
'AnimationPlayer.setPosition(p:any):void',
|
||||
'AnimationSequenceMetadata',
|
||||
'AnimationSequenceMetadata.constructor(_steps:AnimationMetadata[])',
|
||||
'AnimationSequenceMetadata.steps:AnimationMetadata[]',
|
||||
'AnimationStateDeclarationMetadata',
|
||||
'AnimationStateDeclarationMetadata.constructor(stateNameExpr:string, styles:AnimationStyleMetadata)',
|
||||
'AnimationStateMetadata',
|
||||
'AnimationStateTransitionMetadata',
|
||||
'AnimationStateTransitionMetadata.constructor(stateChangeExpr:string, animation:AnimationMetadata)',
|
||||
'AnimationStyleMetadata',
|
||||
'AnimationStyleMetadata.constructor(styles:Array<string|{[key:string]:string|number}>, offset:number)',
|
||||
'AnimationWithStepsMetadata',
|
||||
'AnimationWithStepsMetadata.constructor()',
|
||||
'AnimationWithStepsMetadata.steps:AnimationMetadata[]',
|
||||
'ChangeDetectionStrategy',
|
||||
'ChangeDetectionStrategy.CheckAlways',
|
||||
'ChangeDetectionStrategy.Checked',
|
||||
@ -79,14 +114,15 @@ const CORE = [
|
||||
'CollectionChangeRecord.previousIndex:number',
|
||||
'CollectionChangeRecord.toString():string',
|
||||
'ComponentDecorator',
|
||||
'ComponentDecorator.View(obj:{templateUrl?:string, template?:string, directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, renderer?:string, styles?:string[], styleUrls?:string[]}):ViewDecorator',
|
||||
'ComponentDecorator.View(obj:{templateUrl?:string, template?:string, directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, renderer?:string, styles?:string[], styleUrls?:string[], animations?:AnimationEntryMetadata[]}):ViewDecorator',
|
||||
'ComponentFactory.componentType:Type',
|
||||
'ComponentFactory.constructor(selector:string, _viewFactory:Function, _componentType:Type)',
|
||||
'ComponentFactory.create(injector:Injector, projectableNodes:any[][], rootSelectorOrNode:string|any):ComponentRef<C>',
|
||||
'ComponentFactory<C>',
|
||||
'ComponentMetadata',
|
||||
'ComponentMetadata.animations:AnimationEntryMetadata[]',
|
||||
'ComponentMetadata.changeDetection:ChangeDetectionStrategy',
|
||||
'ComponentMetadata.constructor({selector,inputs,outputs,properties,events,host,exportAs,moduleId,providers,viewProviders,changeDetection=ChangeDetectionStrategy.Default,queries,templateUrl,template,styleUrls,styles,directives,pipes,encapsulation}:{selector?:string, inputs?:string[], outputs?:string[], properties?:string[], events?:string[], host?:{[key:string]:string}, providers?:any[], exportAs?:string, moduleId?:string, viewProviders?:any[], queries?:{[key:string]:any}, changeDetection?:ChangeDetectionStrategy, templateUrl?:string, template?:string, styleUrls?:string[], styles?:string[], directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, encapsulation?:ViewEncapsulation})',
|
||||
'ComponentMetadata.constructor({selector,inputs,outputs,properties,events,host,exportAs,moduleId,providers,viewProviders,changeDetection=ChangeDetectionStrategy.Default,queries,templateUrl,template,styleUrls,styles,animations,directives,pipes,encapsulation}:{selector?:string, inputs?:string[], outputs?:string[], properties?:string[], events?:string[], host?:{[key:string]:string}, providers?:any[], exportAs?:string, moduleId?:string, viewProviders?:any[], queries?:{[key:string]:any}, changeDetection?:ChangeDetectionStrategy, templateUrl?:string, template?:string, styleUrls?:string[], styles?:string[], animations?:AnimationEntryMetadata[], directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, encapsulation?:ViewEncapsulation})',
|
||||
'ComponentMetadata.directives:Array<Type|any[]>',
|
||||
'ComponentMetadata.encapsulation:ViewEncapsulation',
|
||||
'ComponentMetadata.moduleId:string',
|
||||
@ -416,6 +452,7 @@ const CORE = [
|
||||
'RenderComponentType',
|
||||
'RenderComponentType.constructor(id:string, templateUrl:string, slotCount:number, encapsulation:ViewEncapsulation, styles:Array<string|any[]>)',
|
||||
'Renderer',
|
||||
'Renderer.animate(element:any, startingStyles:AnimationStyles, keyframes:AnimationKeyframe[], duration:number, delay:number, easing:string):AnimationPlayer',
|
||||
'Renderer.attachViewAfter(node:any, viewRootNodes:any[]):void',
|
||||
'Renderer.createElement(parentElement:any, name:string, debugInfo:RenderDebugInfo):any',
|
||||
'Renderer.createTemplateAnchor(parentElement:any, debugInfo:RenderDebugInfo):any',
|
||||
@ -530,13 +567,14 @@ const CORE = [
|
||||
'ViewContainerRef.parentInjector:Injector',
|
||||
'ViewContainerRef.remove(index:number):void',
|
||||
'ViewDecorator',
|
||||
'ViewDecorator.View(obj:{templateUrl?:string, template?:string, directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, renderer?:string, styles?:string[], styleUrls?:string[]}):ViewDecorator',
|
||||
'ViewDecorator.View(obj:{templateUrl?:string, template?:string, directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, renderer?:string, styles?:string[], styleUrls?:string[], animations?:AnimationEntryMetadata[]}):ViewDecorator',
|
||||
'ViewEncapsulation',
|
||||
'ViewEncapsulation.Emulated',
|
||||
'ViewEncapsulation.Native',
|
||||
'ViewEncapsulation.None',
|
||||
'ViewMetadata',
|
||||
'ViewMetadata.constructor({templateUrl,template,directives,pipes,encapsulation,styles,styleUrls}:{templateUrl?:string, template?:string, directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, encapsulation?:ViewEncapsulation, styles?:string[], styleUrls?:string[]})',
|
||||
'ViewMetadata.animations:AnimationEntryMetadata[]',
|
||||
'ViewMetadata.constructor({templateUrl,template,directives,pipes,encapsulation,styles,styleUrls,animations}:{templateUrl?:string, template?:string, directives?:Array<Type|any[]>, pipes?:Array<Type|any[]>, encapsulation?:ViewEncapsulation, styles?:string[], styleUrls?:string[], animations?:AnimationEntryMetadata[]})',
|
||||
'ViewMetadata.directives:Array<Type|any[]>',
|
||||
'ViewMetadata.encapsulation:ViewEncapsulation',
|
||||
'ViewMetadata.pipes:Array<Type|any[]>',
|
||||
@ -564,7 +602,16 @@ const CORE = [
|
||||
'WrappedValue',
|
||||
'WrappedValue.constructor(wrapped:any)',
|
||||
'WrappedValue.wrap(value:any):WrappedValue',
|
||||
'WtfScopeFn'
|
||||
'WtfScopeFn',
|
||||
'animate(timing:string|number, styles:AnimationStyleMetadata|AnimationKeyframesSequenceMetadata):AnimationAnimateMetadata',
|
||||
'const AUTO_STYLE:any',
|
||||
'group(steps:AnimationMetadata[]):AnimationGroupMetadata',
|
||||
'keyframes(steps:AnimationStyleMetadata|AnimationStyleMetadata[]):AnimationKeyframesSequenceMetadata',
|
||||
'sequence(steps:AnimationMetadata[]):AnimationSequenceMetadata',
|
||||
'state(stateNameExpr:string, styles:AnimationStyleMetadata):AnimationStateDeclarationMetadata',
|
||||
'style(tokens:string|{[key:string]:string|number}|Array<string|{[key:string]:string|number}>):AnimationStyleMetadata',
|
||||
'transition(stateChangeExpr:string, animationData:AnimationMetadata|AnimationMetadata[]):AnimationStateTransitionMetadata',
|
||||
'trigger(name:string, animation:AnimationMetadata|AnimationMetadata[]):AnimationEntryMetadata'
|
||||
];
|
||||
|
||||
const COMMON = [
|
||||
@ -1030,7 +1077,8 @@ const COMPILER = [
|
||||
'CompilerConfig.constructor(genDebugInfo:boolean, logBindingUpdate:boolean, useJit:boolean, renderTypes:RenderTypes)',
|
||||
'CompilerConfig.renderTypes:RenderTypes',
|
||||
'CompileTemplateMetadata',
|
||||
'CompileTemplateMetadata.constructor({encapsulation,template,templateUrl,styles,styleUrls,ngContentSelectors}:{encapsulation?:ViewEncapsulation, template?:string, templateUrl?:string, styles?:string[], styleUrls?:string[], ngContentSelectors?:string[]})',
|
||||
'CompileTemplateMetadata.animations:CompileAnimationEntryMetadata[]',
|
||||
'CompileTemplateMetadata.constructor({encapsulation,template,templateUrl,styles,styleUrls,animations,ngContentSelectors}:{encapsulation?:ViewEncapsulation, template?:string, templateUrl?:string, styles?:string[], styleUrls?:string[], ngContentSelectors?:string[], animations?:CompileAnimationEntryMetadata[]})',
|
||||
'CompileTemplateMetadata.encapsulation:ViewEncapsulation',
|
||||
'CompileTemplateMetadata.fromJson(data:{[key:string]:any}):CompileTemplateMetadata',
|
||||
'CompileTemplateMetadata.ngContentSelectors:string[]',
|
||||
@ -1096,6 +1144,7 @@ const COMPILER = [
|
||||
'PipeResolver.constructor(_reflector:ReflectorReader)',
|
||||
'PipeResolver.resolve(type:Type):PipeMetadata',
|
||||
'PropertyBindingType',
|
||||
'PropertyBindingType.Animation',
|
||||
'PropertyBindingType.Attribute',
|
||||
'PropertyBindingType.Class',
|
||||
'PropertyBindingType.Property',
|
||||
|
Loading…
x
Reference in New Issue
Block a user