refactor(animations): deport TCB away from animation-land forever (#10892)
* feat(animations): support animation trigger template callbacks * refactor(animations): deport TCB away from animation-land forever
This commit is contained in:
parent
ca41b4f5ff
commit
45e8e73670
|
@ -78,6 +78,8 @@ export type AnimationKeyframe = t.AnimationKeyframe;
|
|||
export var AnimationKeyframe: typeof t.AnimationKeyframe = r.AnimationKeyframe;
|
||||
export type AnimationStyles = t.AnimationStyles;
|
||||
export var AnimationStyles: typeof t.AnimationStyles = r.AnimationStyles;
|
||||
export type AnimationOutput = t.AnimationOutput;
|
||||
export var AnimationOutput: typeof t.AnimationOutput = r.AnimationOutput;
|
||||
export var ANY_STATE = r.ANY_STATE;
|
||||
export var DEFAULT_STATE = r.DEFAULT_STATE;
|
||||
export var EMPTY_STATE = r.EMPTY_STATE;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import {AUTO_STYLE, BaseException} from '@angular/core';
|
||||
|
||||
import {ANY_STATE, DEFAULT_STATE, EMPTY_STATE} from '../../core_private';
|
||||
import {ANY_STATE, AnimationOutput, DEFAULT_STATE, EMPTY_STATE} from '../../core_private';
|
||||
import {CompileDirectiveMetadata} from '../compile_metadata';
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
|
@ -17,23 +17,29 @@ import * as o from '../output/output_ast';
|
|||
import * as t from '../template_parser/template_ast';
|
||||
|
||||
import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_ast';
|
||||
import {AnimationParseError, ParsedAnimationResult, parseAnimationEntry} from './animation_parser';
|
||||
import {AnimationParseError, ParsedAnimationResult, parseAnimationEntry, parseAnimationOutputName} from './animation_parser';
|
||||
|
||||
const animationCompilationCache = new Map<CompileDirectiveMetadata, CompiledAnimation[]>();
|
||||
const animationCompilationCache =
|
||||
new Map<CompileDirectiveMetadata, CompiledAnimationTriggerResult[]>();
|
||||
|
||||
export class CompiledAnimation {
|
||||
export class CompiledAnimationTriggerResult {
|
||||
constructor(
|
||||
public name: string, public statesMapStatement: o.Statement,
|
||||
public statesVariableName: string, public fnStatement: o.Statement,
|
||||
public fnVariable: o.Expression) {}
|
||||
}
|
||||
|
||||
export class CompiledComponentAnimationResult {
|
||||
constructor(
|
||||
public outputs: AnimationOutput[], public triggers: CompiledAnimationTriggerResult[]) {}
|
||||
}
|
||||
|
||||
export class AnimationCompiler {
|
||||
compileComponent(component: CompileDirectiveMetadata, template: t.TemplateAst[]):
|
||||
CompiledAnimation[] {
|
||||
var compiledAnimations: CompiledAnimation[] = [];
|
||||
CompiledComponentAnimationResult {
|
||||
var compiledAnimations: CompiledAnimationTriggerResult[] = [];
|
||||
var groupedErrors: string[] = [];
|
||||
var triggerLookup: {[key: string]: CompiledAnimation} = {};
|
||||
var triggerLookup: {[key: string]: CompiledAnimationTriggerResult} = {};
|
||||
var componentName = component.type.name;
|
||||
|
||||
component.template.animations.forEach(entry => {
|
||||
|
@ -59,9 +65,8 @@ export class AnimationCompiler {
|
|||
}
|
||||
});
|
||||
|
||||
_validateAnimationProperties(compiledAnimations, template).forEach(entry => {
|
||||
groupedErrors.push(entry.msg);
|
||||
});
|
||||
var validatedProperties = _validateAnimationProperties(compiledAnimations, template);
|
||||
validatedProperties.errors.forEach(error => { groupedErrors.push(error.msg); });
|
||||
|
||||
if (groupedErrors.length > 0) {
|
||||
var errorMessageStr =
|
||||
|
@ -71,7 +76,7 @@ export class AnimationCompiler {
|
|||
}
|
||||
|
||||
animationCompilationCache.set(component, compiledAnimations);
|
||||
return compiledAnimations;
|
||||
return new CompiledComponentAnimationResult(validatedProperties.outputs, compiledAnimations);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,14 +297,15 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||
.toStmt()])])
|
||||
.toStmt());
|
||||
|
||||
statements.push(_ANIMATION_FACTORY_VIEW_VAR
|
||||
.callMethod(
|
||||
'queueAnimation',
|
||||
[
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
|
||||
_ANIMATION_PLAYER_VAR
|
||||
])
|
||||
.toStmt());
|
||||
statements.push(
|
||||
_ANIMATION_FACTORY_VIEW_VAR
|
||||
.callMethod(
|
||||
'queueAnimation',
|
||||
[
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
|
||||
_ANIMATION_PLAYER_VAR, _ANIMATION_CURRENT_STATE_VAR, _ANIMATION_NEXT_STATE_VAR
|
||||
])
|
||||
.toStmt());
|
||||
|
||||
return o.fn(
|
||||
[
|
||||
|
@ -313,7 +319,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||
statements);
|
||||
}
|
||||
|
||||
build(ast: AnimationAst): CompiledAnimation {
|
||||
build(ast: AnimationAst): CompiledAnimationTriggerResult {
|
||||
var context = new _AnimationBuilderContext();
|
||||
var fnStatement = ast.visit(this, context).toDeclStmt(this._fnVarName);
|
||||
var fnVariable = o.variable(this._fnVarName);
|
||||
|
@ -333,7 +339,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||
});
|
||||
|
||||
var compiledStatesMapExpr = this._statesMapVar.set(o.literalMap(lookupMap)).toDeclStmt();
|
||||
return new CompiledAnimation(
|
||||
return new CompiledAnimationTriggerResult(
|
||||
this.animationName, compiledStatesMapExpr, this._statesMapVarName, fnStatement, fnVariable);
|
||||
}
|
||||
}
|
||||
|
@ -385,60 +391,92 @@ function _getStylesArray(obj: any): {[key: string]: any}[] {
|
|||
}
|
||||
|
||||
function _validateAnimationProperties(
|
||||
compiledAnimations: CompiledAnimation[], template: t.TemplateAst[]): AnimationParseError[] {
|
||||
compiledAnimations: CompiledAnimationTriggerResult[],
|
||||
template: t.TemplateAst[]): AnimationPropertyValidationOutput {
|
||||
var visitor = new _AnimationTemplatePropertyVisitor(compiledAnimations);
|
||||
t.templateVisitAll(visitor, template);
|
||||
return visitor.errors;
|
||||
return new AnimationPropertyValidationOutput(visitor.outputs, visitor.errors);
|
||||
}
|
||||
|
||||
export class AnimationPropertyValidationOutput {
|
||||
constructor(public outputs: AnimationOutput[], public errors: AnimationParseError[]) {}
|
||||
}
|
||||
|
||||
class _AnimationTemplatePropertyVisitor implements t.TemplateAstVisitor {
|
||||
private _animationRegistry: {[key: string]: boolean};
|
||||
public errors: AnimationParseError[] = [];
|
||||
public outputs: AnimationOutput[] = [];
|
||||
|
||||
constructor(animations: CompiledAnimation[]) {
|
||||
constructor(animations: CompiledAnimationTriggerResult[]) {
|
||||
this._animationRegistry = this._buildCompileAnimationLookup(animations);
|
||||
}
|
||||
|
||||
private _buildCompileAnimationLookup(animations: CompiledAnimation[]): {[key: string]: boolean} {
|
||||
private _buildCompileAnimationLookup(animations: CompiledAnimationTriggerResult[]):
|
||||
{[key: string]: boolean} {
|
||||
var map: {[key: string]: boolean} = {};
|
||||
animations.forEach(entry => { map[entry.name] = true; });
|
||||
return map;
|
||||
}
|
||||
|
||||
visitElement(ast: t.ElementAst, ctx: any): any {
|
||||
var inputAsts: t.BoundElementPropertyAst[] = ast.inputs;
|
||||
var componentAnimationRegistry = this._animationRegistry;
|
||||
|
||||
var componentOnElement: t.DirectiveAst =
|
||||
ast.directives.find(directive => directive.directive.isComponent);
|
||||
if (componentOnElement) {
|
||||
inputAsts = componentOnElement.hostProperties;
|
||||
let cachedComponentAnimations = animationCompilationCache.get(componentOnElement.directive);
|
||||
if (cachedComponentAnimations) {
|
||||
componentAnimationRegistry = this._buildCompileAnimationLookup(cachedComponentAnimations);
|
||||
}
|
||||
}
|
||||
|
||||
private _validateAnimationInputOutputPairs(
|
||||
inputAsts: t.BoundElementPropertyAst[], outputAsts: t.BoundEventAst[],
|
||||
animationRegistry: {[key: string]: any}, isHostLevel: boolean): void {
|
||||
var detectedAnimationInputs: {[key: string]: boolean} = {};
|
||||
inputAsts.forEach(input => {
|
||||
if (input.type == t.PropertyBindingType.Animation) {
|
||||
var animationName = input.name;
|
||||
if (!isPresent(componentAnimationRegistry[animationName])) {
|
||||
var triggerName = input.name;
|
||||
if (isPresent(animationRegistry[triggerName])) {
|
||||
detectedAnimationInputs[triggerName] = true;
|
||||
} else {
|
||||
this.errors.push(
|
||||
new AnimationParseError(`couldn't find an animation entry for ${animationName}`));
|
||||
new AnimationParseError(`Couldn't find an animation entry for ${triggerName}`));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
outputAsts.forEach(output => {
|
||||
if (output.name[0] == '@') {
|
||||
var normalizedOutputData = parseAnimationOutputName(output.name.substr(1), this.errors);
|
||||
let triggerName = normalizedOutputData.name;
|
||||
let triggerEventPhase = normalizedOutputData.phase;
|
||||
if (!animationRegistry[triggerName]) {
|
||||
this.errors.push(new AnimationParseError(
|
||||
`Couldn't find the corresponding ${isHostLevel ? 'host-level ' : '' }animation trigger definition for (@${triggerName})`));
|
||||
} else if (!detectedAnimationInputs[triggerName]) {
|
||||
this.errors.push(new AnimationParseError(
|
||||
`Unable to listen on (@${triggerName}.${triggerEventPhase}) because the animation trigger [@${triggerName}] isn't being used on the same element`));
|
||||
} else {
|
||||
this.outputs.push(normalizedOutputData);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
visitElement(ast: t.ElementAst, ctx: any): any {
|
||||
this._validateAnimationInputOutputPairs(
|
||||
ast.inputs, ast.outputs, this._animationRegistry, false);
|
||||
|
||||
var componentOnElement: t.DirectiveAst =
|
||||
ast.directives.find(directive => directive.directive.isComponent);
|
||||
if (componentOnElement) {
|
||||
let cachedComponentAnimations = animationCompilationCache.get(componentOnElement.directive);
|
||||
if (cachedComponentAnimations) {
|
||||
this._validateAnimationInputOutputPairs(
|
||||
componentOnElement.hostProperties, componentOnElement.hostEvents,
|
||||
this._buildCompileAnimationLookup(cachedComponentAnimations), true);
|
||||
}
|
||||
}
|
||||
|
||||
t.templateVisitAll(this, ast.children);
|
||||
}
|
||||
|
||||
visitEvent(ast: t.BoundEventAst, ctx: any): any {}
|
||||
visitBoundText(ast: t.BoundTextAst, ctx: any): any {}
|
||||
visitText(ast: t.TextAst, ctx: any): any {}
|
||||
visitEmbeddedTemplate(ast: t.EmbeddedTemplateAst, ctx: any): any {}
|
||||
visitNgContent(ast: t.NgContentAst, ctx: any): any {}
|
||||
visitAttr(ast: t.AttrAst, ctx: any): any {}
|
||||
visitDirective(ast: t.DirectiveAst, ctx: any): any {}
|
||||
visitEvent(ast: t.BoundEventAst, ctx: any): any {}
|
||||
visitReference(ast: t.ReferenceAst, ctx: any): any {}
|
||||
visitVariable(ast: t.VariableAst, ctx: any): any {}
|
||||
visitDirectiveProperty(ast: t.BoundDirectivePropertyAst, ctx: any): any {}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ANY_STATE, FILL_STYLE_FLAG} from '../../core_private';
|
||||
import {ANY_STATE, AnimationOutput, FILL_STYLE_FLAG} from '../../core_private';
|
||||
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata} from '../compile_metadata';
|
||||
import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
||||
import {NumberWrapper, isArray, isBlank, isPresent, isString, isStringMap} from '../facade/lang';
|
||||
|
@ -53,6 +53,32 @@ export function parseAnimationEntry(entry: CompileAnimationEntryMetadata): Parse
|
|||
return new ParsedAnimationResult(ast, errors);
|
||||
}
|
||||
|
||||
export function parseAnimationOutputName(
|
||||
outputName: string, errors: AnimationParseError[]): AnimationOutput {
|
||||
var values = outputName.split('.');
|
||||
var name: string;
|
||||
var phase: string = '';
|
||||
if (values.length > 1) {
|
||||
name = values[0];
|
||||
let parsedPhase = values[1];
|
||||
switch (parsedPhase) {
|
||||
case 'start':
|
||||
case 'done':
|
||||
phase = parsedPhase;
|
||||
break;
|
||||
|
||||
default:
|
||||
errors.push(new AnimationParseError(
|
||||
`The provided animation output phase value "${parsedPhase}" for "@${name}" is not supported (use start or done)`));
|
||||
}
|
||||
} else {
|
||||
name = outputName;
|
||||
errors.push(new AnimationParseError(
|
||||
`The animation trigger output event (@${name}) is missing its phase value name (start or done are currently supported)`));
|
||||
}
|
||||
return new AnimationOutput(name, phase, outputName);
|
||||
}
|
||||
|
||||
function _parseAnimationDeclarationStates(
|
||||
stateMetadata: CompileAnimationStateDeclarationMetadata,
|
||||
errors: AnimationParseError[]): AnimationStateDeclarationAst[] {
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
*/
|
||||
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ElementRef, Injector, LOCALE_ID as LOCALE_ID_, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT as TRANSLATIONS_FORMAT_, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||
import {AnimationGroupPlayer as AnimationGroupPlayer_, AnimationKeyframe as AnimationKeyframe_, AnimationSequencePlayer as AnimationSequencePlayer_, AnimationStyles as AnimationStyles_, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NgModuleInjector, NoOpAnimationPlayer as NoOpAnimationPlayer_, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes as impBalanceAnimationKeyframes, castByValue, checkBinding, clearStyles as impClearStyles, collectAndResolveStyles as impCollectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles as impBalanceAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, renderStyles as impRenderStyles} from '../core_private';
|
||||
|
||||
import {AnimationGroupPlayer as AnimationGroupPlayer_, AnimationKeyframe as AnimationKeyframe_, AnimationOutput as AnimationOutput_, AnimationSequencePlayer as AnimationSequencePlayer_, AnimationStyles as AnimationStyles_, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NgModuleInjector, NoOpAnimationPlayer as NoOpAnimationPlayer_, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes as impBalanceAnimationKeyframes, castByValue, checkBinding, clearStyles as impClearStyles, collectAndResolveStyles as impCollectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles as impBalanceAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, renderStyles as impRenderStyles} from '../core_private';
|
||||
|
||||
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
|
||||
import {assetUrl} from './util';
|
||||
|
@ -53,6 +54,7 @@ var impAnimationSequencePlayer = AnimationSequencePlayer_;
|
|||
var impAnimationKeyframe = AnimationKeyframe_;
|
||||
var impAnimationStyles = AnimationStyles_;
|
||||
var impNoOpAnimationPlayer = NoOpAnimationPlayer_;
|
||||
var impAnimationOutput = AnimationOutput_;
|
||||
|
||||
var ANIMATION_STYLE_UTIL_ASSET_URL = assetUrl('core', 'animation/animation_style_util');
|
||||
|
||||
|
@ -258,6 +260,11 @@ export class Identifiers {
|
|||
moduleUrl: assetUrl('core', 'i18n/tokens'),
|
||||
runtime: TRANSLATIONS_FORMAT_
|
||||
});
|
||||
static AnimationOutput = new CompileIdentifierMetadata({
|
||||
name: 'AnimationOutput',
|
||||
moduleUrl: assetUrl('core', 'animation/animation_output'),
|
||||
runtime: impAnimationOutput
|
||||
});
|
||||
}
|
||||
|
||||
export function identifierToken(identifier: CompileIdentifierMetadata): CompileTokenMetadata {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {ViewType} from '../../core_private';
|
||||
import {CompiledAnimation} from '../animation/animation_compiler';
|
||||
import {CompiledAnimationTriggerResult} from '../animation/animation_compiler';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMap, CompileIdentifierMetadata, CompilePipeMetadata, CompileTokenMetadata} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
|
@ -71,7 +71,7 @@ export class CompileView implements NameResolver {
|
|||
constructor(
|
||||
public component: CompileDirectiveMetadata, public genConfig: CompilerConfig,
|
||||
public pipeMetas: CompilePipeMetadata[], public styles: o.Expression,
|
||||
public animations: CompiledAnimation[], public viewIndex: number,
|
||||
public animations: CompiledAnimationTriggerResult[], public viewIndex: number,
|
||||
public declarationElement: CompileElement, public templateVariableBindings: string[][]) {
|
||||
this.createMethod = new CompileMethod(this);
|
||||
this.injectorGetMethod = new CompileMethod(this);
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AnimationOutput} from '../../core_private';
|
||||
import {CompileDirectiveMetadata} from '../compile_metadata';
|
||||
import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
||||
import {StringWrapper, isBlank, isPresent} from '../facade/lang';
|
||||
import {identifierToken} from '../identifiers';
|
||||
import {Identifiers, identifierToken} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
|
||||
|
||||
|
@ -19,6 +20,10 @@ import {CompileMethod} from './compile_method';
|
|||
import {EventHandlerVars, ViewProperties} from './constants';
|
||||
import {convertCdStatementToIr} from './expression_converter';
|
||||
|
||||
export class CompileElementAnimationOutput {
|
||||
constructor(public listener: CompileEventListener, public output: AnimationOutput) {}
|
||||
}
|
||||
|
||||
export class CompileEventListener {
|
||||
private _method: CompileMethod;
|
||||
private _hasComponentHostListener: boolean = false;
|
||||
|
@ -39,6 +44,8 @@ export class CompileEventListener {
|
|||
return listener;
|
||||
}
|
||||
|
||||
get methodName() { return this._methodName; }
|
||||
|
||||
constructor(
|
||||
public compileElement: CompileElement, public eventTarget: string, public eventName: string,
|
||||
listenerIndex: number) {
|
||||
|
@ -112,6 +119,26 @@ export class CompileEventListener {
|
|||
disposable.set(listenExpr).toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private]));
|
||||
}
|
||||
|
||||
listenToAnimation(output: AnimationOutput) {
|
||||
var outputListener = o.THIS_EXPR.callMethod(
|
||||
'eventHandler',
|
||||
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
|
||||
|
||||
// tie the property callback method to the view animations map
|
||||
var stmt = o.THIS_EXPR
|
||||
.callMethod(
|
||||
'registerAnimationOutput',
|
||||
[
|
||||
this.compileElement.renderNode,
|
||||
o.importExpr(Identifiers.AnimationOutput).instantiate([
|
||||
o.literal(output.name), o.literal(output.phase)
|
||||
]),
|
||||
outputListener
|
||||
])
|
||||
.toStmt();
|
||||
this.compileElement.view.createMethod.addStmt(stmt);
|
||||
}
|
||||
|
||||
listenToDirective(directiveInstance: o.Expression, observablePropName: string) {
|
||||
var subscription = o.variable(`subscription_${this.compileElement.view.subscriptions.length}`);
|
||||
this.compileElement.view.subscriptions.push(subscription);
|
||||
|
@ -166,6 +193,10 @@ export function bindRenderOutputs(eventListeners: CompileEventListener[]) {
|
|||
eventListeners.forEach(listener => listener.listenToRenderer());
|
||||
}
|
||||
|
||||
export function bindAnimationOutputs(eventListeners: CompileElementAnimationOutput[]) {
|
||||
eventListeners.forEach(entry => { entry.listener.listenToAnimation(entry.output); });
|
||||
}
|
||||
|
||||
function convertStmtIntoExpression(stmt: o.Statement): o.Expression {
|
||||
if (stmt instanceof o.ExpressionStatement) {
|
||||
return stmt.expr;
|
||||
|
|
|
@ -5,19 +5,20 @@
|
|||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AnimationOutput} from '../../core_private';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {identifierToken} from '../identifiers';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||
|
||||
import {CompileElement, CompileNode} from './compile_element';
|
||||
import {CompileView} from './compile_view';
|
||||
import {bindDirectiveOutputs, bindRenderOutputs, collectEventListeners} from './event_binder';
|
||||
import {CompileElementAnimationOutput, CompileEventListener, bindAnimationOutputs, bindDirectiveOutputs, bindRenderOutputs, collectEventListeners} from './event_binder';
|
||||
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveDetectChangesLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
|
||||
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
|
||||
|
||||
export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void {
|
||||
var visitor = new ViewBinderVisitor(view);
|
||||
export function bindView(
|
||||
view: CompileView, parsedTemplate: TemplateAst[], animationOutputs: AnimationOutput[]): void {
|
||||
var visitor = new ViewBinderVisitor(view, animationOutputs);
|
||||
templateVisitAll(visitor, parsedTemplate);
|
||||
view.pipes.forEach(
|
||||
(pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); });
|
||||
|
@ -25,8 +26,12 @@ export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void
|
|||
|
||||
class ViewBinderVisitor implements TemplateAstVisitor {
|
||||
private _nodeIndex: number = 0;
|
||||
private _animationOutputsMap: {[key: string]: AnimationOutput} = {};
|
||||
|
||||
constructor(public view: CompileView) {}
|
||||
constructor(public view: CompileView, animationOutputs: AnimationOutput[]) {
|
||||
animationOutputs.forEach(
|
||||
entry => { this._animationOutputsMap[entry.fullPropertyName] = entry; });
|
||||
}
|
||||
|
||||
visitBoundText(ast: BoundTextAst, parent: CompileElement): any {
|
||||
var node = this.view.nodes[this._nodeIndex++];
|
||||
|
@ -42,7 +47,23 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
|||
|
||||
visitElement(ast: ElementAst, parent: CompileElement): any {
|
||||
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||
var eventListeners = collectEventListeners(ast.outputs, ast.directives, compileElement);
|
||||
var eventListeners: CompileEventListener[] = [];
|
||||
var animationEventListeners: CompileElementAnimationOutput[] = [];
|
||||
collectEventListeners(ast.outputs, ast.directives, compileElement).forEach(entry => {
|
||||
// TODO: figure out how to abstract this `if` statement elsewhere
|
||||
if (entry.eventName[0] == '@') {
|
||||
let animationOutputName = entry.eventName.substr(1);
|
||||
let output = this._animationOutputsMap[animationOutputName];
|
||||
// no need to report an error here since the parser will
|
||||
// have caught the missing animation trigger definition
|
||||
if (output) {
|
||||
animationEventListeners.push(new CompileElementAnimationOutput(entry, output));
|
||||
}
|
||||
} else {
|
||||
eventListeners.push(entry);
|
||||
}
|
||||
});
|
||||
bindAnimationOutputs(animationEventListeners);
|
||||
bindRenderInputs(ast.inputs, compileElement);
|
||||
bindRenderOutputs(eventListeners);
|
||||
ast.directives.forEach((directiveAst) => {
|
||||
|
@ -90,7 +111,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
|||
var providerInstance = compileElement.instances.get(providerAst.token);
|
||||
bindInjectableDestroyLifecycleCallbacks(providerAst, providerInstance, compileElement);
|
||||
});
|
||||
bindView(compileElement.embeddedView, ast.children);
|
||||
bindView(compileElement.embeddedView, ast.children, []);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -283,7 +283,7 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
|
|||
this.nestedViewCount++;
|
||||
var embeddedView = new CompileView(
|
||||
this.view.component, this.view.genConfig, this.view.pipeMetas, o.NULL_EXPR,
|
||||
compiledAnimations, this.view.viewIndex + this.nestedViewCount, compileElement,
|
||||
compiledAnimations.triggers, this.view.viewIndex + this.nestedViewCount, compileElement,
|
||||
templateVariableBindings);
|
||||
this.nestedViewCount += buildView(embeddedView, ast.children, this.targetDependencies);
|
||||
|
||||
|
|
|
@ -38,17 +38,18 @@ export class ViewCompiler {
|
|||
var dependencies: Array<ViewFactoryDependency|ComponentFactoryDependency> = [];
|
||||
var compiledAnimations = this._animationCompiler.compileComponent(component, template);
|
||||
var statements: o.Statement[] = [];
|
||||
compiledAnimations.map(entry => {
|
||||
var animationTriggers = compiledAnimations.triggers;
|
||||
animationTriggers.forEach(entry => {
|
||||
statements.push(entry.statesMapStatement);
|
||||
statements.push(entry.fnStatement);
|
||||
});
|
||||
var view = new CompileView(
|
||||
component, this._genConfig, pipes, styles, compiledAnimations, 0,
|
||||
component, this._genConfig, pipes, styles, animationTriggers, 0,
|
||||
CompileElement.createNull(), []);
|
||||
buildView(view, template, dependencies);
|
||||
// Need to separate binding from creation to be able to refer to
|
||||
// variables that have been declared after usage.
|
||||
bindView(view, template);
|
||||
bindView(view, template, compiledAnimations.outputs);
|
||||
finishView(view, statements);
|
||||
|
||||
return new ViewCompileResult(statements, view.viewFactory.name, dependencies);
|
||||
|
|
|
@ -10,7 +10,7 @@ import {AnimationMetadata, animate, group, sequence, style, transition, trigger}
|
|||
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {StringMapWrapper} from '../../../platform-browser-dynamic/src/facade/collection';
|
||||
import {AnimationCompiler, CompiledAnimation} from '../../src/animation/animation_compiler';
|
||||
import {AnimationCompiler, CompiledAnimationTriggerResult} from '../../src/animation/animation_compiler';
|
||||
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '../../src/compile_metadata';
|
||||
import {CompileMetadataResolver} from '../../src/metadata_resolver';
|
||||
|
||||
|
@ -22,9 +22,11 @@ export function main() {
|
|||
|
||||
var compiler = new AnimationCompiler();
|
||||
|
||||
var compileAnimations = (component: CompileDirectiveMetadata): CompiledAnimation => {
|
||||
return compiler.compileComponent(component, [])[0];
|
||||
};
|
||||
var compileAnimations =
|
||||
(component: CompileDirectiveMetadata): CompiledAnimationTriggerResult => {
|
||||
var result = compiler.compileComponent(component, []);
|
||||
return result.triggers[0];
|
||||
};
|
||||
|
||||
var compileTriggers = (input: any[]) => {
|
||||
var entries: CompileAnimationEntryMetadata[] = input.map(entry => {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import {ANY_STATE as ANY_STATE_, DEFAULT_STATE as DEFAULT_STATE_, EMPTY_STATE as EMPTY_STATE_, FILL_STYLE_FLAG as FILL_STYLE_FLAG_} from './src/animation/animation_constants';
|
||||
import {AnimationGroupPlayer as AnimationGroupPlayer_} from './src/animation/animation_group_player';
|
||||
import {AnimationKeyframe as AnimationKeyframe_} from './src/animation/animation_keyframe';
|
||||
import {AnimationOutput as AnimationOutput_} from './src/animation/animation_output';
|
||||
import {AnimationPlayer as AnimationPlayer_, NoOpAnimationPlayer as NoOpAnimationPlayer_} from './src/animation/animation_player';
|
||||
import {AnimationSequencePlayer as AnimationSequencePlayer_} from './src/animation/animation_sequence_player';
|
||||
import * as animationUtils from './src/animation/animation_style_util';
|
||||
|
@ -119,6 +120,8 @@ export declare namespace __core_private_types__ {
|
|||
export var collectAndResolveStyles: typeof animationUtils.collectAndResolveStyles;
|
||||
export type AnimationStyles = AnimationStyles_;
|
||||
export var AnimationStyles: typeof AnimationStyles_;
|
||||
export type AnimationOutput = AnimationOutput_;
|
||||
export var AnimationOutput: typeof AnimationOutput_;
|
||||
export var ANY_STATE: typeof ANY_STATE_;
|
||||
export var DEFAULT_STATE: typeof DEFAULT_STATE_;
|
||||
export var EMPTY_STATE: typeof EMPTY_STATE_;
|
||||
|
@ -185,6 +188,7 @@ export var __core_private__ = {
|
|||
renderStyles: animationUtils.renderStyles,
|
||||
collectAndResolveStyles: animationUtils.collectAndResolveStyles,
|
||||
AnimationStyles: AnimationStyles_,
|
||||
AnimationOutput: AnimationOutput_,
|
||||
ANY_STATE: ANY_STATE_,
|
||||
DEFAULT_STATE: DEFAULT_STATE_,
|
||||
EMPTY_STATE: EMPTY_STATE_,
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
export class AnimationOutput {
|
||||
constructor(public name: string, public phase: string, public fullPropertyName: string) {}
|
||||
}
|
|
@ -7,7 +7,8 @@
|
|||
*/
|
||||
|
||||
import {AnimationGroupPlayer} from '../animation/animation_group_player';
|
||||
import {AnimationPlayer} from '../animation/animation_player';
|
||||
import {AnimationOutput} from '../animation/animation_output';
|
||||
import {AnimationPlayer, NoOpAnimationPlayer} from '../animation/animation_player';
|
||||
import {ViewAnimationMap} from '../animation/view_animation_map';
|
||||
import {ChangeDetectorRef, ChangeDetectorStatus} from '../change_detection/change_detection';
|
||||
import {Injector} from '../di/injector';
|
||||
|
@ -50,6 +51,8 @@ export abstract class AppView<T> {
|
|||
|
||||
public animationPlayers = new ViewAnimationMap();
|
||||
|
||||
private _animationListeners = new Map<any, _AnimationOutputWithHandler[]>();
|
||||
|
||||
public context: T;
|
||||
|
||||
constructor(
|
||||
|
@ -77,9 +80,23 @@ export abstract class AppView<T> {
|
|||
}
|
||||
}
|
||||
|
||||
queueAnimation(element: any, animationName: string, player: AnimationPlayer): void {
|
||||
queueAnimation(
|
||||
element: any, animationName: string, player: AnimationPlayer, fromState: string,
|
||||
toState: string): void {
|
||||
var actualAnimationDetected = !(player instanceof NoOpAnimationPlayer);
|
||||
var animationData = {
|
||||
'fromState': fromState,
|
||||
'toState': toState,
|
||||
'running': actualAnimationDetected
|
||||
};
|
||||
this.animationPlayers.set(element, animationName, player);
|
||||
player.onDone(() => { this.animationPlayers.remove(element, animationName); });
|
||||
player.onDone(() => {
|
||||
// TODO: make this into a datastructure for done|start
|
||||
this.triggerAnimationOutput(element, animationName, 'done', animationData);
|
||||
this.animationPlayers.remove(element, animationName);
|
||||
});
|
||||
player.onStart(
|
||||
() => { this.triggerAnimationOutput(element, animationName, 'start', animationData); });
|
||||
}
|
||||
|
||||
triggerQueuedAnimations() {
|
||||
|
@ -90,6 +107,32 @@ export abstract class AppView<T> {
|
|||
});
|
||||
}
|
||||
|
||||
triggerAnimationOutput(
|
||||
element: any, animationName: string, phase: string, animationData: {[key: string]: any}) {
|
||||
var listeners = this._animationListeners.get(element);
|
||||
if (isPresent(listeners) && listeners.length) {
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
let listener = listeners[i];
|
||||
// we check for both the name in addition to the phase in the event
|
||||
// that there may be more than one @trigger on the same element
|
||||
if (listener.output.name == animationName && listener.output.phase == phase) {
|
||||
listener.handler(animationData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerAnimationOutput(element: any, outputEvent: AnimationOutput, eventHandler: Function):
|
||||
void {
|
||||
var entry = new _AnimationOutputWithHandler(outputEvent, eventHandler);
|
||||
var animations = this._animationListeners.get(element);
|
||||
if (!isPresent(animations)) {
|
||||
this._animationListeners.set(element, animations = []);
|
||||
}
|
||||
animations.push(entry);
|
||||
}
|
||||
|
||||
create(context: T, givenProjectableNodes: Array<any|any[]>, rootSelectorOrNode: string|any):
|
||||
AppElement {
|
||||
this.context = context;
|
||||
|
@ -433,3 +476,7 @@ function _findLastRenderNode(node: any): any {
|
|||
}
|
||||
return lastNode;
|
||||
}
|
||||
|
||||
class _AnimationOutputWithHandler {
|
||||
constructor(public output: AnimationOutput, public handler: Function) {}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,7 +10,9 @@ import {Component, animate, group, keyframes, sequence, state, style, transition
|
|||
|
||||
@Component({
|
||||
host: {
|
||||
'[@backgroundAnimation]': 'bgStatus'
|
||||
'[@backgroundAnimation]': 'bgStatus',
|
||||
'(@backgroundAnimation.start)': 'bgStatusChanged($event, "started")',
|
||||
'(@backgroundAnimation.done)': 'bgStatusChanged($event, "completed")'
|
||||
},
|
||||
selector: 'animate-app',
|
||||
styleUrls: ['css/animate-app.css'],
|
||||
|
@ -80,6 +82,10 @@ export class AnimateApp {
|
|||
this.items[Math.floor(Math.random() * this.items.length)] = 99;
|
||||
}
|
||||
|
||||
bgStatusChanged(data: {[key: string]: any}, phase: string) {
|
||||
alert(`backgroundAnimation has ${phase} from ${data['fromState']} to ${data['toState']}`);
|
||||
}
|
||||
|
||||
get state() { return this._state; }
|
||||
set state(s) {
|
||||
this._state = s;
|
||||
|
|
Loading…
Reference in New Issue