feat(animations): allow animation integration support into host params
Closes #9044 Closes #9933
This commit is contained in:
parent
5af1e891cd
commit
806a25413c
|
@ -15,7 +15,7 @@ import {BaseException} from '../facade/exceptions';
|
||||||
import {isArray, isBlank, isPresent} from '../facade/lang';
|
import {isArray, isBlank, isPresent} from '../facade/lang';
|
||||||
import {Identifiers} from '../identifiers';
|
import {Identifiers} from '../identifiers';
|
||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
import {PropertyBindingType, TemplateAst, TemplateAstVisitor, NgContentAst, EmbeddedTemplateAst, ElementAst, ReferenceAst, VariableAst, BoundEventAst, BoundElementPropertyAst, AttrAst, BoundTextAst, TextAst, DirectiveAst, BoundDirectivePropertyAst, templateVisitAll,} from '../template_ast';
|
import * as t from '../template_ast';
|
||||||
|
|
||||||
import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_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} from './animation_parser';
|
||||||
|
@ -28,10 +28,9 @@ export class CompiledAnimation {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AnimationCompiler {
|
export class AnimationCompiler {
|
||||||
compileComponent(component: CompileDirectiveMetadata, template: TemplateAst[]):
|
compileComponent(component: CompileDirectiveMetadata, template: t.TemplateAst[]):
|
||||||
CompiledAnimation[] {
|
CompiledAnimation[] {
|
||||||
var compiledAnimations: CompiledAnimation[] = [];
|
var compiledAnimations: CompiledAnimation[] = [];
|
||||||
var index = 0;
|
|
||||||
var groupedErrors: string[] = [];
|
var groupedErrors: string[] = [];
|
||||||
var triggerLookup: {[key: string]: CompiledAnimation} = {};
|
var triggerLookup: {[key: string]: CompiledAnimation} = {};
|
||||||
var componentName = component.type.name;
|
var componentName = component.type.name;
|
||||||
|
@ -44,7 +43,6 @@ export class AnimationCompiler {
|
||||||
`Unable to parse the animation sequence for "${triggerName}" due to the following errors:`;
|
`Unable to parse the animation sequence for "${triggerName}" due to the following errors:`;
|
||||||
result.errors.forEach(
|
result.errors.forEach(
|
||||||
(error: AnimationParseError) => { errorMessage += '\n-- ' + error.msg; });
|
(error: AnimationParseError) => { errorMessage += '\n-- ' + error.msg; });
|
||||||
// todo (matsko): include the component name when throwing
|
|
||||||
groupedErrors.push(errorMessage);
|
groupedErrors.push(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,11 +50,9 @@ export class AnimationCompiler {
|
||||||
groupedErrors.push(
|
groupedErrors.push(
|
||||||
`The animation trigger "${triggerName}" has already been registered on "${componentName}"`);
|
`The animation trigger "${triggerName}" has already been registered on "${componentName}"`);
|
||||||
} else {
|
} else {
|
||||||
var factoryName = `${component.type.name}_${entry.name}_${index}`;
|
var factoryName = `${componentName}_${entry.name}`;
|
||||||
index++;
|
|
||||||
|
|
||||||
var visitor = new _AnimationBuilder(triggerName, factoryName);
|
var visitor = new _AnimationBuilder(triggerName, factoryName);
|
||||||
var compileResult = visitor.build(result.ast)
|
var compileResult = visitor.build(result.ast);
|
||||||
compiledAnimations.push(compileResult);
|
compiledAnimations.push(compileResult);
|
||||||
triggerLookup[entry.name] = compileResult;
|
triggerLookup[entry.name] = compileResult;
|
||||||
}
|
}
|
||||||
|
@ -387,14 +383,13 @@ function _getStylesArray(obj: any): {[key: string]: any}[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _validateAnimationProperties(
|
function _validateAnimationProperties(
|
||||||
compiledAnimations: CompiledAnimation[], template: TemplateAst[]): AnimationParseError[] {
|
compiledAnimations: CompiledAnimation[], template: t.TemplateAst[]): AnimationParseError[] {
|
||||||
var visitor = new _AnimationTemplatePropertyVisitor(compiledAnimations);
|
var visitor = new _AnimationTemplatePropertyVisitor(compiledAnimations);
|
||||||
templateVisitAll(visitor, template);
|
t.templateVisitAll(visitor, template);
|
||||||
return visitor.errors;
|
return visitor.errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AnimationTemplatePropertyVisitor implements TemplateAstVisitor {
|
class _AnimationTemplatePropertyVisitor implements t.TemplateAstVisitor {
|
||||||
private _nodeIndex: number = 0;
|
|
||||||
private _animationRegistry: {[key: string]: boolean} = {};
|
private _animationRegistry: {[key: string]: boolean} = {};
|
||||||
|
|
||||||
public errors: AnimationParseError[] = [];
|
public errors: AnimationParseError[] = [];
|
||||||
|
@ -403,9 +398,9 @@ class _AnimationTemplatePropertyVisitor implements TemplateAstVisitor {
|
||||||
animations.forEach(entry => { this._animationRegistry[entry.name] = true; });
|
animations.forEach(entry => { this._animationRegistry[entry.name] = true; });
|
||||||
}
|
}
|
||||||
|
|
||||||
visitElement(ast: ElementAst, ctx: any): any {
|
visitElement(ast: t.ElementAst, ctx: any): any {
|
||||||
ast.inputs.forEach(input => {
|
ast.inputs.forEach(input => {
|
||||||
if (input.type == PropertyBindingType.Animation) {
|
if (input.type == t.PropertyBindingType.Animation) {
|
||||||
var animationName = input.name;
|
var animationName = input.name;
|
||||||
if (!isPresent(this._animationRegistry[animationName])) {
|
if (!isPresent(this._animationRegistry[animationName])) {
|
||||||
this.errors.push(
|
this.errors.push(
|
||||||
|
@ -413,21 +408,18 @@ class _AnimationTemplatePropertyVisitor implements TemplateAstVisitor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
templateVisitAll(this, ast.children);
|
t.templateVisitAll(this, ast.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitBoundText(ast: BoundTextAst, ctx: any): any { this._nodeIndex++; }
|
visitBoundText(ast: t.BoundTextAst, ctx: any): any {}
|
||||||
|
visitText(ast: t.TextAst, ctx: any): any {}
|
||||||
visitText(ast: TextAst, ctx: any): any { this._nodeIndex++; }
|
visitEmbeddedTemplate(ast: t.EmbeddedTemplateAst, ctx: any): any {}
|
||||||
|
visitNgContent(ast: t.NgContentAst, ctx: any): any {}
|
||||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, ctx: any): any { this._nodeIndex++; }
|
visitAttr(ast: t.AttrAst, ctx: any): any {}
|
||||||
|
visitDirective(ast: t.DirectiveAst, ctx: any): any {}
|
||||||
visitNgContent(ast: NgContentAst, ctx: any): any {}
|
visitEvent(ast: t.BoundEventAst, ctx: any): any {}
|
||||||
visitAttr(ast: AttrAst, ctx: any): any {}
|
visitReference(ast: t.ReferenceAst, ctx: any): any {}
|
||||||
visitDirective(ast: DirectiveAst, ctx: any): any {}
|
visitVariable(ast: t.VariableAst, ctx: any): any {}
|
||||||
visitEvent(ast: BoundEventAst, ctx: any): any {}
|
visitDirectiveProperty(ast: t.BoundDirectivePropertyAst, ctx: any): any {}
|
||||||
visitReference(ast: ReferenceAst, ctx: any): any {}
|
visitElementProperty(ast: t.BoundElementPropertyAst, ctx: any): any {}
|
||||||
visitVariable(ast: VariableAst, ctx: any): any {}
|
|
||||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, ctx: any): any {}
|
|
||||||
visitElementProperty(ast: BoundElementPropertyAst, ctx: any): any {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,11 @@ import {getUrlScheme} from './url_resolver';
|
||||||
import {sanitizeIdentifier, splitAtColon} from './util';
|
import {sanitizeIdentifier, splitAtColon} from './util';
|
||||||
|
|
||||||
|
|
||||||
|
// group 0: "[prop] or (event) or @trigger"
|
||||||
|
// group 1: "prop" from "[prop]"
|
||||||
// group 2: "event" from "(event)"
|
// group 2: "event" from "(event)"
|
||||||
var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g;
|
// group 3: "@trigger" from "@trigger"
|
||||||
|
var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/g;
|
||||||
|
|
||||||
export abstract class CompileMetadataWithIdentifier {
|
export abstract class CompileMetadataWithIdentifier {
|
||||||
abstract toJson(): {[key: string]: any};
|
abstract toJson(): {[key: string]: any};
|
||||||
|
@ -741,6 +743,8 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType {
|
||||||
hostProperties[matches[1]] = value;
|
hostProperties[matches[1]] = value;
|
||||||
} else if (isPresent(matches[2])) {
|
} else if (isPresent(matches[2])) {
|
||||||
hostListeners[matches[2]] = value;
|
hostListeners[matches[2]] = value;
|
||||||
|
} else if (isPresent(matches[3])) {
|
||||||
|
hostProperties[matches[3]] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -774,13 +774,23 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||||
const parts = name.split(PROPERTY_PARTS_SEPARATOR);
|
const parts = name.split(PROPERTY_PARTS_SEPARATOR);
|
||||||
let securityContext: SecurityContext;
|
let securityContext: SecurityContext;
|
||||||
if (parts.length === 1) {
|
if (parts.length === 1) {
|
||||||
boundPropertyName = this._schemaRegistry.getMappedPropName(parts[0]);
|
var partValue = parts[0];
|
||||||
securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName);
|
if (partValue[0] == '@') {
|
||||||
bindingType = PropertyBindingType.Property;
|
boundPropertyName = partValue.substr(1);
|
||||||
if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName)) {
|
bindingType = PropertyBindingType.Animation;
|
||||||
|
securityContext = SecurityContext.NONE;
|
||||||
this._reportError(
|
this._reportError(
|
||||||
`Can't bind to '${boundPropertyName}' since it isn't a known native property`,
|
`Assigning animation triggers within host data as attributes such as "@prop": "exp" is deprecated. Use "[@prop]": "exp" instead!`,
|
||||||
sourceSpan);
|
sourceSpan, ParseErrorLevel.WARNING);
|
||||||
|
} else {
|
||||||
|
boundPropertyName = this._schemaRegistry.getMappedPropName(partValue);
|
||||||
|
securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName);
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (parts[0] == ATTRIBUTE_PREFIX) {
|
if (parts[0] == ATTRIBUTE_PREFIX) {
|
||||||
|
|
|
@ -65,16 +65,14 @@ export class CompileView implements NameResolver {
|
||||||
public literalArrayCount = 0;
|
public literalArrayCount = 0;
|
||||||
public literalMapCount = 0;
|
public literalMapCount = 0;
|
||||||
public pipeCount = 0;
|
public pipeCount = 0;
|
||||||
public animations = new Map<string, CompiledAnimation>();
|
|
||||||
|
|
||||||
public componentContext: o.Expression;
|
public componentContext: o.Expression;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public component: CompileDirectiveMetadata, public genConfig: CompilerConfig,
|
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 animations: CompiledAnimation[], public viewIndex: number,
|
||||||
public declarationElement: CompileElement, public templateVariableBindings: string[][]) {
|
public declarationElement: CompileElement, public templateVariableBindings: string[][]) {
|
||||||
animations.forEach(entry => this.animations.set(entry.name, entry));
|
|
||||||
this.createMethod = new CompileMethod(this);
|
this.createMethod = new CompileMethod(this);
|
||||||
this.injectorGetMethod = new CompileMethod(this);
|
this.injectorGetMethod = new CompileMethod(this);
|
||||||
this.updateContentQueriesMethod = new CompileMethod(this);
|
this.updateContentQueriesMethod = new CompileMethod(this);
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseException, SecurityContext} from '@angular/core';
|
|
||||||
|
|
||||||
import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../../core_private';
|
import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../../core_private';
|
||||||
import * as cdAst from '../expression_parser/ast';
|
import * as cdAst from '../expression_parser/ast';
|
||||||
import {isBlank, isPresent} from '../facade/lang';
|
import {isBlank, isPresent} from '../facade/lang';
|
||||||
|
@ -21,8 +19,7 @@ import {CompileMethod} from './compile_method';
|
||||||
import {camelCaseToDashCase} from '../util';
|
import {camelCaseToDashCase} from '../util';
|
||||||
import {convertCdExpressionToIr} from './expression_converter';
|
import {convertCdExpressionToIr} from './expression_converter';
|
||||||
import {CompileBinding} from './compile_binding';
|
import {CompileBinding} from './compile_binding';
|
||||||
import {BaseException, SecurityContext} from '@angular/core';
|
import {SecurityContext} from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
function createBindFieldExpr(exprIndex: number): o.ReadPropExpr {
|
function createBindFieldExpr(exprIndex: number): o.ReadPropExpr {
|
||||||
return o.THIS_EXPR.prop(`_expr_${exprIndex}`);
|
return o.THIS_EXPR.prop(`_expr_${exprIndex}`);
|
||||||
|
@ -85,7 +82,8 @@ export function bindRenderText(
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindAndWriteToRenderer(
|
function bindAndWriteToRenderer(
|
||||||
boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement) {
|
boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement,
|
||||||
|
isHostProp: boolean) {
|
||||||
var view = compileElement.view;
|
var view = compileElement.view;
|
||||||
var renderNode = compileElement.renderNode;
|
var renderNode = compileElement.renderNode;
|
||||||
boundProps.forEach((boundProp) => {
|
boundProps.forEach((boundProp) => {
|
||||||
|
@ -129,6 +127,7 @@ function bindAndWriteToRenderer(
|
||||||
if (isPresent(boundProp.unit)) {
|
if (isPresent(boundProp.unit)) {
|
||||||
strValue = strValue.plus(o.literal(boundProp.unit));
|
strValue = strValue.plus(o.literal(boundProp.unit));
|
||||||
}
|
}
|
||||||
|
|
||||||
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
|
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
|
||||||
updateStmts.push(
|
updateStmts.push(
|
||||||
o.THIS_EXPR.prop('renderer')
|
o.THIS_EXPR.prop('renderer')
|
||||||
|
@ -137,7 +136,13 @@ function bindAndWriteToRenderer(
|
||||||
break;
|
break;
|
||||||
case PropertyBindingType.Animation:
|
case PropertyBindingType.Animation:
|
||||||
var animationName = boundProp.name;
|
var animationName = boundProp.name;
|
||||||
var animation = view.componentView.animations.get(animationName);
|
var targetViewExpr: o.Expression = o.THIS_EXPR;
|
||||||
|
if (isHostProp) {
|
||||||
|
targetViewExpr = compileElement.appElement.prop('componentView');
|
||||||
|
}
|
||||||
|
|
||||||
|
var animationFnExpr =
|
||||||
|
targetViewExpr.prop('componentType').prop('animations').key(o.literal(animationName));
|
||||||
|
|
||||||
// it's important to normalize the void value as `void` explicitly
|
// it's important to normalize the void value as `void` explicitly
|
||||||
// so that the styles data can be obtained from the stringmap
|
// so that the styles data can be obtained from the stringmap
|
||||||
|
@ -158,11 +163,10 @@ function bindAndWriteToRenderer(
|
||||||
[newRenderVar.set(emptyStateValue).toStmt()]));
|
[newRenderVar.set(emptyStateValue).toStmt()]));
|
||||||
|
|
||||||
updateStmts.push(
|
updateStmts.push(
|
||||||
animation.fnVariable.callFn([o.THIS_EXPR, renderNode, oldRenderVar, newRenderVar])
|
animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderVar, newRenderVar]).toStmt());
|
||||||
.toStmt());
|
|
||||||
|
|
||||||
view.detachMethod.addStmt(
|
view.detachMethod.addStmt(
|
||||||
animation.fnVariable.callFn([o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue])
|
animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue])
|
||||||
.toStmt());
|
.toStmt());
|
||||||
|
|
||||||
if (!_animationViewCheckedFlagMap.get(view)) {
|
if (!_animationViewCheckedFlagMap.get(view)) {
|
||||||
|
@ -212,13 +216,13 @@ function sanitizedValue(
|
||||||
|
|
||||||
export function bindRenderInputs(
|
export function bindRenderInputs(
|
||||||
boundProps: BoundElementPropertyAst[], compileElement: CompileElement): void {
|
boundProps: BoundElementPropertyAst[], compileElement: CompileElement): void {
|
||||||
bindAndWriteToRenderer(boundProps, compileElement.view.componentContext, compileElement);
|
bindAndWriteToRenderer(boundProps, compileElement.view.componentContext, compileElement, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bindDirectiveHostProps(
|
export function bindDirectiveHostProps(
|
||||||
directiveAst: DirectiveAst, directiveInstance: o.Expression,
|
directiveAst: DirectiveAst, directiveInstance: o.Expression,
|
||||||
compileElement: CompileElement): void {
|
compileElement: CompileElement): void {
|
||||||
bindAndWriteToRenderer(directiveAst.hostProperties, directiveInstance, compileElement);
|
bindAndWriteToRenderer(directiveAst.hostProperties, directiveInstance, compileElement, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bindDirectiveInputs(
|
export function bindDirectiveInputs(
|
||||||
|
|
|
@ -500,6 +500,7 @@ function createViewFactory(
|
||||||
templateUrlInfo = view.component.template.templateUrl;
|
templateUrlInfo = view.component.template.templateUrl;
|
||||||
}
|
}
|
||||||
if (view.viewIndex === 0) {
|
if (view.viewIndex === 0) {
|
||||||
|
var animationsExpr = o.literalMap(view.animations.map(entry => [entry.name, entry.fnVariable]));
|
||||||
initRenderCompTypeStmts = [new o.IfStmt(renderCompTypeVar.identical(o.NULL_EXPR), [
|
initRenderCompTypeStmts = [new o.IfStmt(renderCompTypeVar.identical(o.NULL_EXPR), [
|
||||||
renderCompTypeVar
|
renderCompTypeVar
|
||||||
.set(ViewConstructorVars.viewUtils.callMethod(
|
.set(ViewConstructorVars.viewUtils.callMethod(
|
||||||
|
@ -507,7 +508,8 @@ function createViewFactory(
|
||||||
[
|
[
|
||||||
o.literal(templateUrlInfo),
|
o.literal(templateUrlInfo),
|
||||||
o.literal(view.component.template.ngContentSelectors.length),
|
o.literal(view.component.template.ngContentSelectors.length),
|
||||||
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation), view.styles
|
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation), view.styles,
|
||||||
|
animationsExpr
|
||||||
]))
|
]))
|
||||||
.toStmt()
|
.toStmt()
|
||||||
])];
|
])];
|
||||||
|
|
|
@ -66,21 +66,12 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error when two or more animation triggers contain the same name', () => {
|
it('should throw an error when two or more animation triggers contain the same name', () => {
|
||||||
var doCompile = () => {
|
var t1Data: any[] = [];
|
||||||
var t1Data: any[] = [];
|
var t2Data: any[] = [];
|
||||||
var t2Data: any[] = [];
|
|
||||||
|
expect(() => {
|
||||||
compileTriggers([['myTrigger', t1Data], ['myTrigger', t2Data]]);
|
compileTriggers([['myTrigger', t1Data], ['myTrigger', t2Data]]);
|
||||||
};
|
}).toThrowError(/The animation trigger "myTrigger" has already been registered on "myCmp"/);
|
||||||
|
|
||||||
var capturedErrorMessage: string;
|
|
||||||
try {
|
|
||||||
doCompile();
|
|
||||||
} catch (e) {
|
|
||||||
capturedErrorMessage = e.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(capturedErrorMessage)
|
|
||||||
.toMatch(/The animation trigger "myTrigger" has already been registered on "myCmp"/);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -298,6 +298,22 @@ export function main() {
|
||||||
].join('\n')]);
|
].join('\n')]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should issue a warning when host attributes contain a non property-bound animation trigger',
|
||||||
|
() => {
|
||||||
|
var dirA = CompileDirectiveMetadata.create({
|
||||||
|
selector: 'div',
|
||||||
|
type: new CompileTypeMetadata({moduleUrl: someModuleUrl, name: 'DirA'}),
|
||||||
|
host: {'@prop': 'expr'}
|
||||||
|
});
|
||||||
|
|
||||||
|
humanizeTplAst(parse('<div></div>', [dirA]));
|
||||||
|
|
||||||
|
expect(console.warnings).toEqual([[
|
||||||
|
'Template parse warnings:',
|
||||||
|
`Assigning animation triggers within host data as attributes such as "@prop": "exp" is deprecated. Use "[@prop]": "exp" instead! ("[ERROR ->]<div></div>"): TestComp@0:0`
|
||||||
|
].join('\n')]);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not issue a warning when an animation property is bound without an expression',
|
it('should not issue a warning when an animation property is bound without an expression',
|
||||||
() => {
|
() => {
|
||||||
humanizeTplAst(parse('<div @something>', []));
|
humanizeTplAst(parse('<div @something>', []));
|
||||||
|
|
|
@ -34,11 +34,13 @@ export class ViewUtils {
|
||||||
/**
|
/**
|
||||||
* Used by the generated code
|
* Used by the generated code
|
||||||
*/
|
*/
|
||||||
|
// TODO (matsko): add typing for the animation function
|
||||||
createRenderComponentType(
|
createRenderComponentType(
|
||||||
templateUrl: string, slotCount: number, encapsulation: ViewEncapsulation,
|
templateUrl: string, slotCount: number, encapsulation: ViewEncapsulation,
|
||||||
styles: Array<string|any[]>): RenderComponentType {
|
styles: Array<string|any[]>, animations: {[key: string]: Function}): RenderComponentType {
|
||||||
return new RenderComponentType(
|
return new RenderComponentType(
|
||||||
`${this._appId}-${this._nextCompTypeId++}`, templateUrl, slotCount, encapsulation, styles);
|
`${this._appId}-${this._nextCompTypeId++}`, templateUrl, slotCount, encapsulation, styles,
|
||||||
|
animations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
|
|
@ -13,14 +13,15 @@ import {Injector} from '../di/injector';
|
||||||
import {unimplemented} from '../facade/exceptions';
|
import {unimplemented} from '../facade/exceptions';
|
||||||
import {ViewEncapsulation} from '../metadata/view';
|
import {ViewEncapsulation} from '../metadata/view';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
|
// TODO (matsko): add typing for the animation function
|
||||||
export class RenderComponentType {
|
export class RenderComponentType {
|
||||||
constructor(
|
constructor(
|
||||||
public id: string, public templateUrl: string, public slotCount: number,
|
public id: string, public templateUrl: string, public slotCount: number,
|
||||||
public encapsulation: ViewEncapsulation, public styles: Array<string|any[]>) {}
|
public encapsulation: ViewEncapsulation, public styles: Array<string|any[]>,
|
||||||
|
public animations: {[key: string]: Function}) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class RenderDebugInfo {
|
export abstract class RenderDebugInfo {
|
||||||
|
|
|
@ -45,7 +45,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
var makeAnimationCmp =
|
var makeAnimationCmp =
|
||||||
(tcb: TestComponentBuilder, tpl: string,
|
(tcb: TestComponentBuilder, tpl: string,
|
||||||
animationEntry: AnimationEntryMetadata | AnimationEntryMetadata[],
|
animationEntry: AnimationEntryMetadata | AnimationEntryMetadata[],
|
||||||
callback: Function = null, failure: Function = null) => {
|
callback: (fixture: any) => void = null, failure: (fixture: any) => void = null) => {
|
||||||
var entries = isArray(animationEntry) ? <AnimationEntryMetadata[]>animationEntry :
|
var entries = isArray(animationEntry) ? <AnimationEntryMetadata[]>animationEntry :
|
||||||
[<AnimationEntryMetadata>animationEntry];
|
[<AnimationEntryMetadata>animationEntry];
|
||||||
tcb = tcb.overrideTemplate(DummyIfCmp, tpl);
|
tcb = tcb.overrideTemplate(DummyIfCmp, tpl);
|
||||||
|
@ -893,13 +893,34 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
'Error: expected animations for DummyIfCmp to throw an error within this spec');
|
'Error: expected animations for DummyIfCmp to throw an error within this spec');
|
||||||
},
|
},
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
var message = e.message;
|
const message = e.message;
|
||||||
expect(message).toMatch(
|
expect(message).toMatch(
|
||||||
/Animation parsing for DummyIfCmp has failed due to the following errors:/);
|
/Animation parsing for DummyIfCmp has failed due to the following errors:/);
|
||||||
expect(message).toMatch(/- couldn't find an animation entry for status/);
|
expect(message).toMatch(/- couldn't find an animation entry for status/);
|
||||||
});
|
});
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
it('should be permitted to be registered on the host element',
|
||||||
|
inject(
|
||||||
|
[TestComponentBuilder, AnimationDriver],
|
||||||
|
fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||||
|
tcb = tcb.overrideAnimations(DummyLoadingCmp, [trigger('loading', [
|
||||||
|
state('final', style({'background': 'grey'})),
|
||||||
|
transition('* => final', [animate(1000)])
|
||||||
|
])]);
|
||||||
|
tcb.createAsync(DummyLoadingCmp).then(fixture => {
|
||||||
|
var cmp = fixture.debugElement.componentInstance;
|
||||||
|
cmp.exp = 'final';
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
var animation = driver.log.pop();
|
||||||
|
var keyframes = animation['keyframeLookup'];
|
||||||
|
expect(keyframes[1]).toEqual([1, {'background': 'grey'}]);
|
||||||
|
});
|
||||||
|
tick();
|
||||||
|
})));
|
||||||
|
|
||||||
it('should retain the destination animation state styles once the animation is complete',
|
it('should retain the destination animation state styles once the animation is complete',
|
||||||
inject(
|
inject(
|
||||||
[TestComponentBuilder, AnimationDriver],
|
[TestComponentBuilder, AnimationDriver],
|
||||||
|
@ -1189,18 +1210,6 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'if-cmp',
|
|
||||||
directives: [NgIf],
|
|
||||||
template: `
|
|
||||||
<div *ngIf="exp" [@myAnimation]="exp"></div>
|
|
||||||
`
|
|
||||||
})
|
|
||||||
class DummyIfCmp {
|
|
||||||
exp = false;
|
|
||||||
exp2 = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
class InnerContentTrackingAnimationDriver extends MockAnimationDriver {
|
class InnerContentTrackingAnimationDriver extends MockAnimationDriver {
|
||||||
animate(
|
animate(
|
||||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||||
|
@ -1214,12 +1223,39 @@ class InnerContentTrackingAnimationDriver extends MockAnimationDriver {
|
||||||
|
|
||||||
class InnerContentTrackingAnimationPlayer extends MockAnimationPlayer {
|
class InnerContentTrackingAnimationPlayer extends MockAnimationPlayer {
|
||||||
constructor(public element: any) { super(); }
|
constructor(public element: any) { super(); }
|
||||||
|
|
||||||
public computedHeight: number;
|
public computedHeight: number;
|
||||||
public capturedInnerText: string;
|
public capturedInnerText: string;
|
||||||
public playAttempts = 0;
|
public playAttempts = 0;
|
||||||
|
|
||||||
init() { this.computedHeight = getDOM().getComputedStyle(this.element)['height']; }
|
init() { this.computedHeight = getDOM().getComputedStyle(this.element)['height']; }
|
||||||
|
|
||||||
play() {
|
play() {
|
||||||
this.playAttempts++;
|
this.playAttempts++;
|
||||||
this.capturedInnerText = this.element.querySelector('.inner').innerText;
|
this.capturedInnerText = this.element.querySelector('.inner').innerText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'if-cmp',
|
||||||
|
directives: [NgIf],
|
||||||
|
template: `
|
||||||
|
<div *ngIf="exp" [@myAnimation]="exp"></div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class DummyIfCmp {
|
||||||
|
exp = false;
|
||||||
|
exp2 = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'if-cmp',
|
||||||
|
host: {'[@loading]': 'exp'},
|
||||||
|
directives: [NgIf],
|
||||||
|
template: `
|
||||||
|
<div>loading...</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class DummyLoadingCmp {
|
||||||
|
exp = false;
|
||||||
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@ export class Serializer {
|
||||||
return new RenderComponentType(
|
return new RenderComponentType(
|
||||||
map['id'], map['templateUrl'], map['slotCount'],
|
map['id'], map['templateUrl'], map['slotCount'],
|
||||||
this.deserialize(map['encapsulation'], ViewEncapsulation),
|
this.deserialize(map['encapsulation'], ViewEncapsulation),
|
||||||
this.deserialize(map['styles'], PRIMITIVE));
|
this.deserialize(map['styles'], PRIMITIVE), {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,22 +19,23 @@ import {
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
host: {
|
||||||
|
'[@backgroundAnimation]': "bgStatus"
|
||||||
|
},
|
||||||
selector: 'animate-app',
|
selector: 'animate-app',
|
||||||
styleUrls: ['css/animate-app.css'],
|
styleUrls: ['css/animate-app.css'],
|
||||||
template: `
|
template: `
|
||||||
<div [@backgroundAnimation]="bgStatus">
|
<button (click)="state='start'">Start State</button>
|
||||||
<button (click)="state='start'">Start State</button>
|
<button (click)="state='active'">Active State</button>
|
||||||
<button (click)="state='active'">Active State</button>
|
|
|
||||||
|
|
<button (click)="state='void'">Void State</button>
|
||||||
<button (click)="state='void'">Void State</button>
|
<button (click)="state='default'">Unhandled (default) State</button>
|
||||||
<button (click)="state='default'">Unhandled (default) State</button>
|
<button style="float:right" (click)="bgStatus='blur'">Blur Page (Host)</button>
|
||||||
<button style="float:right" (click)="bgStatus='blur'">Blur Page</button>
|
<hr />
|
||||||
<hr />
|
<div *ngFor="let item of items" class="box" [@boxAnimation]="state">
|
||||||
<div *ngFor="let item of items" class="box" [@boxAnimation]="state">
|
{{ item }}
|
||||||
{{ item }}
|
<div *ngIf="true">
|
||||||
<div *ngIf="true">
|
something inside
|
||||||
something inside
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|
|
@ -2,6 +2,17 @@
|
||||||
<html>
|
<html>
|
||||||
<title>Animation Example</title>
|
<title>Animation Example</title>
|
||||||
<link rel="stylesheet" type="text/css" href="./css/app.css" />
|
<link rel="stylesheet" type="text/css" href="./css/app.css" />
|
||||||
|
<style>
|
||||||
|
animate-app {
|
||||||
|
display:block;
|
||||||
|
position:fixed;
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
|
right:0;
|
||||||
|
bottom:0;
|
||||||
|
padding:50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<animate-app>Loading...</animate-app>
|
<animate-app>Loading...</animate-app>
|
||||||
<script src="../bootstrap.js"></script>
|
<script src="../bootstrap.js"></script>
|
||||||
|
|
|
@ -1200,12 +1200,17 @@ export declare class ReflectiveKey {
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class RenderComponentType {
|
export declare class RenderComponentType {
|
||||||
|
animations: {
|
||||||
|
[key: string]: Function;
|
||||||
|
};
|
||||||
encapsulation: ViewEncapsulation;
|
encapsulation: ViewEncapsulation;
|
||||||
id: string;
|
id: string;
|
||||||
slotCount: number;
|
slotCount: number;
|
||||||
styles: Array<string | any[]>;
|
styles: Array<string | any[]>;
|
||||||
templateUrl: string;
|
templateUrl: string;
|
||||||
constructor(id: string, templateUrl: string, slotCount: number, encapsulation: ViewEncapsulation, styles: Array<string | any[]>);
|
constructor(id: string, templateUrl: string, slotCount: number, encapsulation: ViewEncapsulation, styles: Array<string | any[]>, animations: {
|
||||||
|
[key: string]: Function;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
|
|
Loading…
Reference in New Issue