diff --git a/modules/@angular/compiler/src/animation/animation_compiler.ts b/modules/@angular/compiler/src/animation/animation_compiler.ts index b657b82835..695eaba38a 100644 --- a/modules/@angular/compiler/src/animation/animation_compiler.ts +++ b/modules/@angular/compiler/src/animation/animation_compiler.ts @@ -15,7 +15,7 @@ import {BaseException} from '../facade/exceptions'; import {isArray, isBlank, isPresent} from '../facade/lang'; import {Identifiers} from '../identifiers'; 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 {AnimationParseError, ParsedAnimationResult, parseAnimationEntry} from './animation_parser'; @@ -28,10 +28,9 @@ export class CompiledAnimation { } export class AnimationCompiler { - compileComponent(component: CompileDirectiveMetadata, template: TemplateAst[]): + compileComponent(component: CompileDirectiveMetadata, template: t.TemplateAst[]): CompiledAnimation[] { var compiledAnimations: CompiledAnimation[] = []; - var index = 0; var groupedErrors: string[] = []; var triggerLookup: {[key: string]: CompiledAnimation} = {}; 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:`; result.errors.forEach( (error: AnimationParseError) => { errorMessage += '\n-- ' + error.msg; }); - // todo (matsko): include the component name when throwing groupedErrors.push(errorMessage); } @@ -52,11 +50,9 @@ export class AnimationCompiler { groupedErrors.push( `The animation trigger "${triggerName}" has already been registered on "${componentName}"`); } else { - var factoryName = `${component.type.name}_${entry.name}_${index}`; - index++; - + var factoryName = `${componentName}_${entry.name}`; var visitor = new _AnimationBuilder(triggerName, factoryName); - var compileResult = visitor.build(result.ast) + var compileResult = visitor.build(result.ast); compiledAnimations.push(compileResult); triggerLookup[entry.name] = compileResult; } @@ -387,14 +383,13 @@ function _getStylesArray(obj: any): {[key: string]: any}[] { } function _validateAnimationProperties( - compiledAnimations: CompiledAnimation[], template: TemplateAst[]): AnimationParseError[] { + compiledAnimations: CompiledAnimation[], template: t.TemplateAst[]): AnimationParseError[] { var visitor = new _AnimationTemplatePropertyVisitor(compiledAnimations); - templateVisitAll(visitor, template); + t.templateVisitAll(visitor, template); return visitor.errors; } -class _AnimationTemplatePropertyVisitor implements TemplateAstVisitor { - private _nodeIndex: number = 0; +class _AnimationTemplatePropertyVisitor implements t.TemplateAstVisitor { private _animationRegistry: {[key: string]: boolean} = {}; public errors: AnimationParseError[] = []; @@ -403,9 +398,9 @@ class _AnimationTemplatePropertyVisitor implements TemplateAstVisitor { 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 => { - if (input.type == PropertyBindingType.Animation) { + if (input.type == t.PropertyBindingType.Animation) { var animationName = input.name; if (!isPresent(this._animationRegistry[animationName])) { 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++; } - - visitText(ast: TextAst, ctx: any): any { this._nodeIndex++; } - - visitEmbeddedTemplate(ast: EmbeddedTemplateAst, ctx: any): any { this._nodeIndex++; } - - visitNgContent(ast: NgContentAst, ctx: any): any {} - visitAttr(ast: AttrAst, ctx: any): any {} - visitDirective(ast: DirectiveAst, ctx: any): any {} - visitEvent(ast: BoundEventAst, ctx: any): any {} - visitReference(ast: ReferenceAst, ctx: any): any {} - visitVariable(ast: VariableAst, ctx: any): any {} - visitDirectiveProperty(ast: BoundDirectivePropertyAst, ctx: any): any {} - visitElementProperty(ast: BoundElementPropertyAst, 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 {} + visitElementProperty(ast: t.BoundElementPropertyAst, ctx: any): any {} } diff --git a/modules/@angular/compiler/src/compile_metadata.ts b/modules/@angular/compiler/src/compile_metadata.ts index 2c12439084..45a8ee829f 100644 --- a/modules/@angular/compiler/src/compile_metadata.ts +++ b/modules/@angular/compiler/src/compile_metadata.ts @@ -18,9 +18,11 @@ import {getUrlScheme} from './url_resolver'; import {sanitizeIdentifier, splitAtColon} from './util'; - +// group 0: "[prop] or (event) or @trigger" +// group 1: "prop" from "[prop]" // 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 { abstract toJson(): {[key: string]: any}; @@ -741,6 +743,8 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType { hostProperties[matches[1]] = value; } else if (isPresent(matches[2])) { hostListeners[matches[2]] = value; + } else if (isPresent(matches[3])) { + hostProperties[matches[3]] = value; } }); } diff --git a/modules/@angular/compiler/src/template_parser.ts b/modules/@angular/compiler/src/template_parser.ts index fcc4c16a24..0eec172660 100644 --- a/modules/@angular/compiler/src/template_parser.ts +++ b/modules/@angular/compiler/src/template_parser.ts @@ -774,13 +774,23 @@ class TemplateParseVisitor implements HtmlAstVisitor { const parts = name.split(PROPERTY_PARTS_SEPARATOR); let securityContext: SecurityContext; if (parts.length === 1) { - boundPropertyName = this._schemaRegistry.getMappedPropName(parts[0]); - securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName); - bindingType = PropertyBindingType.Property; - if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName)) { + var partValue = parts[0]; + if (partValue[0] == '@') { + boundPropertyName = partValue.substr(1); + bindingType = PropertyBindingType.Animation; + securityContext = SecurityContext.NONE; this._reportError( - `Can't bind to '${boundPropertyName}' since it isn't a known native property`, - sourceSpan); + `Assigning animation triggers within host data as attributes such as "@prop": "exp" is deprecated. Use "[@prop]": "exp" instead!`, + 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 { if (parts[0] == ATTRIBUTE_PREFIX) { diff --git a/modules/@angular/compiler/src/view_compiler/compile_view.ts b/modules/@angular/compiler/src/view_compiler/compile_view.ts index d5b68ec857..1220360fa3 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_view.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_view.ts @@ -65,16 +65,14 @@ export class CompileView implements NameResolver { public literalArrayCount = 0; public literalMapCount = 0; public pipeCount = 0; - public animations = new Map(); public componentContext: o.Expression; constructor( public component: CompileDirectiveMetadata, public genConfig: CompilerConfig, public pipeMetas: CompilePipeMetadata[], public styles: o.Expression, - animations: CompiledAnimation[], public viewIndex: number, + public 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); diff --git a/modules/@angular/compiler/src/view_compiler/property_binder.ts b/modules/@angular/compiler/src/view_compiler/property_binder.ts index 895cf031e8..869349738e 100644 --- a/modules/@angular/compiler/src/view_compiler/property_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/property_binder.ts @@ -6,8 +6,6 @@ * 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 * as cdAst from '../expression_parser/ast'; import {isBlank, isPresent} from '../facade/lang'; @@ -21,8 +19,7 @@ import {CompileMethod} from './compile_method'; import {camelCaseToDashCase} from '../util'; import {convertCdExpressionToIr} from './expression_converter'; import {CompileBinding} from './compile_binding'; -import {BaseException, SecurityContext} from '@angular/core'; - +import {SecurityContext} from '@angular/core'; function createBindFieldExpr(exprIndex: number): o.ReadPropExpr { return o.THIS_EXPR.prop(`_expr_${exprIndex}`); @@ -85,7 +82,8 @@ export function bindRenderText( } function bindAndWriteToRenderer( - boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement) { + boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement, + isHostProp: boolean) { var view = compileElement.view; var renderNode = compileElement.renderNode; boundProps.forEach((boundProp) => { @@ -129,6 +127,7 @@ function bindAndWriteToRenderer( 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') @@ -137,7 +136,13 @@ function bindAndWriteToRenderer( break; case PropertyBindingType.Animation: 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 // so that the styles data can be obtained from the stringmap @@ -158,11 +163,10 @@ function bindAndWriteToRenderer( [newRenderVar.set(emptyStateValue).toStmt()])); updateStmts.push( - animation.fnVariable.callFn([o.THIS_EXPR, renderNode, oldRenderVar, newRenderVar]) - .toStmt()); + animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderVar, newRenderVar]).toStmt()); view.detachMethod.addStmt( - animation.fnVariable.callFn([o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue]) + animationFnExpr.callFn([o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue]) .toStmt()); if (!_animationViewCheckedFlagMap.get(view)) { @@ -212,13 +216,13 @@ function sanitizedValue( export function bindRenderInputs( boundProps: BoundElementPropertyAst[], compileElement: CompileElement): void { - bindAndWriteToRenderer(boundProps, compileElement.view.componentContext, compileElement); + bindAndWriteToRenderer(boundProps, compileElement.view.componentContext, compileElement, false); } export function bindDirectiveHostProps( directiveAst: DirectiveAst, directiveInstance: o.Expression, compileElement: CompileElement): void { - bindAndWriteToRenderer(directiveAst.hostProperties, directiveInstance, compileElement); + bindAndWriteToRenderer(directiveAst.hostProperties, directiveInstance, compileElement, true); } export function bindDirectiveInputs( diff --git a/modules/@angular/compiler/src/view_compiler/view_builder.ts b/modules/@angular/compiler/src/view_compiler/view_builder.ts index 0990c225a7..183fdd5f88 100644 --- a/modules/@angular/compiler/src/view_compiler/view_builder.ts +++ b/modules/@angular/compiler/src/view_compiler/view_builder.ts @@ -500,6 +500,7 @@ function createViewFactory( templateUrlInfo = view.component.template.templateUrl; } 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), [ renderCompTypeVar .set(ViewConstructorVars.viewUtils.callMethod( @@ -507,7 +508,8 @@ function createViewFactory( [ o.literal(templateUrlInfo), 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() ])]; diff --git a/modules/@angular/compiler/test/animation/animation_compiler_spec.ts b/modules/@angular/compiler/test/animation/animation_compiler_spec.ts index 89828d810c..168e8828a5 100644 --- a/modules/@angular/compiler/test/animation/animation_compiler_spec.ts +++ b/modules/@angular/compiler/test/animation/animation_compiler_spec.ts @@ -66,21 +66,12 @@ export function main() { }); it('should throw an error when two or more animation triggers contain the same name', () => { - var doCompile = () => { - var t1Data: any[] = []; - var t2Data: any[] = []; + var t1Data: any[] = []; + var t2Data: any[] = []; + + expect(() => { compileTriggers([['myTrigger', t1Data], ['myTrigger', t2Data]]); - }; - - var capturedErrorMessage: string; - try { - doCompile(); - } catch (e) { - capturedErrorMessage = e.message; - } - - expect(capturedErrorMessage) - .toMatch(/The animation trigger "myTrigger" has already been registered on "myCmp"/); + }).toThrowError(/The animation trigger "myTrigger" has already been registered on "myCmp"/); }); }); } diff --git a/modules/@angular/compiler/test/template_parser_spec.ts b/modules/@angular/compiler/test/template_parser_spec.ts index 69f523559c..6443cb822e 100644 --- a/modules/@angular/compiler/test/template_parser_spec.ts +++ b/modules/@angular/compiler/test/template_parser_spec.ts @@ -298,6 +298,22 @@ export function main() { ].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('
', [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 ->]
"): TestComp@0:0` + ].join('\n')]); + }); + it('should not issue a warning when an animation property is bound without an expression', () => { humanizeTplAst(parse('
', [])); diff --git a/modules/@angular/core/src/linker/view_utils.ts b/modules/@angular/core/src/linker/view_utils.ts index 299108f51a..c198695ac4 100644 --- a/modules/@angular/core/src/linker/view_utils.ts +++ b/modules/@angular/core/src/linker/view_utils.ts @@ -34,11 +34,13 @@ export class ViewUtils { /** * Used by the generated code */ + // TODO (matsko): add typing for the animation function createRenderComponentType( templateUrl: string, slotCount: number, encapsulation: ViewEncapsulation, - styles: Array): RenderComponentType { + styles: Array, animations: {[key: string]: Function}): RenderComponentType { return new RenderComponentType( - `${this._appId}-${this._nextCompTypeId++}`, templateUrl, slotCount, encapsulation, styles); + `${this._appId}-${this._nextCompTypeId++}`, templateUrl, slotCount, encapsulation, styles, + animations); } /** @internal */ diff --git a/modules/@angular/core/src/render/api.ts b/modules/@angular/core/src/render/api.ts index c3a7374a4e..8bd010df96 100644 --- a/modules/@angular/core/src/render/api.ts +++ b/modules/@angular/core/src/render/api.ts @@ -13,14 +13,15 @@ import {Injector} from '../di/injector'; import {unimplemented} from '../facade/exceptions'; import {ViewEncapsulation} from '../metadata/view'; - /** * @experimental */ +// TODO (matsko): add typing for the animation function export class RenderComponentType { constructor( public id: string, public templateUrl: string, public slotCount: number, - public encapsulation: ViewEncapsulation, public styles: Array) {} + public encapsulation: ViewEncapsulation, public styles: Array, + public animations: {[key: string]: Function}) {} } export abstract class RenderDebugInfo { diff --git a/modules/@angular/core/test/animation/animation_integration_spec.ts b/modules/@angular/core/test/animation/animation_integration_spec.ts index 67917d75d9..bce82f5fa2 100644 --- a/modules/@angular/core/test/animation/animation_integration_spec.ts +++ b/modules/@angular/core/test/animation/animation_integration_spec.ts @@ -45,7 +45,7 @@ function declareTests({useJit}: {useJit: boolean}) { var makeAnimationCmp = (tcb: TestComponentBuilder, tpl: string, animationEntry: AnimationEntryMetadata | AnimationEntryMetadata[], - callback: Function = null, failure: Function = null) => { + callback: (fixture: any) => void = null, failure: (fixture: any) => void = null) => { var entries = isArray(animationEntry) ? animationEntry : [animationEntry]; 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'); }, (e: any) => { - var message = e.message; + const message = e.message; expect(message).toMatch( /Animation parsing for DummyIfCmp has failed due to the following errors:/); 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', inject( [TestComponentBuilder, AnimationDriver], @@ -1189,18 +1210,6 @@ function declareTests({useJit}: {useJit: boolean}) { }); } -@Component({ - selector: 'if-cmp', - directives: [NgIf], - template: ` -
- ` -}) -class DummyIfCmp { - exp = false; - exp2 = false; -} - class InnerContentTrackingAnimationDriver extends MockAnimationDriver { animate( element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], @@ -1214,12 +1223,39 @@ class InnerContentTrackingAnimationDriver extends MockAnimationDriver { class InnerContentTrackingAnimationPlayer extends MockAnimationPlayer { constructor(public element: any) { super(); } + public computedHeight: number; public capturedInnerText: string; public playAttempts = 0; + init() { this.computedHeight = getDOM().getComputedStyle(this.element)['height']; } + play() { this.playAttempts++; this.capturedInnerText = this.element.querySelector('.inner').innerText; } } + +@Component({ + selector: 'if-cmp', + directives: [NgIf], + template: ` +
+ ` +}) +class DummyIfCmp { + exp = false; + exp2 = false; +} + +@Component({ + selector: 'if-cmp', + host: {'[@loading]': 'exp'}, + directives: [NgIf], + template: ` +
loading...
+ ` +}) +class DummyLoadingCmp { + exp = false; +} diff --git a/modules/@angular/platform-browser/src/web_workers/shared/serializer.ts b/modules/@angular/platform-browser/src/web_workers/shared/serializer.ts index a1a76e0c35..fcb44e30d8 100644 --- a/modules/@angular/platform-browser/src/web_workers/shared/serializer.ts +++ b/modules/@angular/platform-browser/src/web_workers/shared/serializer.ts @@ -111,7 +111,7 @@ export class Serializer { return new RenderComponentType( map['id'], map['templateUrl'], map['slotCount'], this.deserialize(map['encapsulation'], ViewEncapsulation), - this.deserialize(map['styles'], PRIMITIVE)); + this.deserialize(map['styles'], PRIMITIVE), {}); } } diff --git a/modules/playground/src/animate/app/animate-app.ts b/modules/playground/src/animate/app/animate-app.ts index 36391cf0de..636f2abdda 100644 --- a/modules/playground/src/animate/app/animate-app.ts +++ b/modules/playground/src/animate/app/animate-app.ts @@ -19,22 +19,23 @@ import { } from '@angular/core'; @Component({ + host: { + '[@backgroundAnimation]': "bgStatus" + }, selector: 'animate-app', styleUrls: ['css/animate-app.css'], template: ` -
- - - | - - - -
-
- {{ item }} -
- something inside -
+ + + | + + + +
+
+ {{ item }} +
+ something inside
`, diff --git a/modules/playground/src/animate/index.html b/modules/playground/src/animate/index.html index 7e0d0b1467..acaaacef31 100644 --- a/modules/playground/src/animate/index.html +++ b/modules/playground/src/animate/index.html @@ -2,6 +2,17 @@ Animation Example + Loading... diff --git a/tools/public_api_guard/core/index.d.ts b/tools/public_api_guard/core/index.d.ts index ceaca68adf..926270a5ac 100644 --- a/tools/public_api_guard/core/index.d.ts +++ b/tools/public_api_guard/core/index.d.ts @@ -1200,12 +1200,17 @@ export declare class ReflectiveKey { /** @experimental */ export declare class RenderComponentType { + animations: { + [key: string]: Function; + }; encapsulation: ViewEncapsulation; id: string; slotCount: number; styles: Array; templateUrl: string; - constructor(id: string, templateUrl: string, slotCount: number, encapsulation: ViewEncapsulation, styles: Array); + constructor(id: string, templateUrl: string, slotCount: number, encapsulation: ViewEncapsulation, styles: Array, animations: { + [key: string]: Function; + }); } /** @experimental */