From 36d25f2a075218cac70b19d6ad52836b901f0c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Fri, 3 Jun 2016 17:52:33 -0700 Subject: [PATCH] feat(animations): support styling of the default animation state It is now possible to set a fallback state that will apply its styling when the destination state is not detected. ```ts state("*", style({ ... })) ``` Closes #9013 --- .../integrationtest/src/animate.ts | 9 +++- .../integrationtest/test/animate_spec.ts | 38 ++++++++++++-- modules/@angular/compiler/core_private.ts | 1 + .../src/animation/animation_compiler.ts | 15 ++++-- modules/@angular/core/private_export.ts | 3 ++ .../core/src/animation/animation_constants.ts | 1 + .../@angular/core/src/animation/metadata.ts | 4 ++ .../animation/animation_integration_spec.ts | 52 +++++++++++++++++++ .../playground/src/animate/app/animate-app.ts | 9 ++-- 9 files changed, 121 insertions(+), 11 deletions(-) diff --git a/modules/@angular/compiler-cli/integrationtest/src/animate.ts b/modules/@angular/compiler-cli/integrationtest/src/animate.ts index 2044716dd1..e171b5bf61 100644 --- a/modules/@angular/compiler-cli/integrationtest/src/animate.ts +++ b/modules/@angular/compiler-cli/integrationtest/src/animate.ts @@ -1,19 +1,21 @@ -import {Component, trigger, state, animate, transition, style} from '@angular/core'; +import {AUTO_STYLE, Component, trigger, state, animate, transition, style} from '@angular/core'; @Component({ selector: "animate-cmp", animations: [ trigger('openClose', [ + state("*", style({ height: AUTO_STYLE, color: 'black', borderColor: 'black' })), state('closed, void', style({ height:"0px", color: "maroon", borderColor: "maroon" })), state('open', - style({ height:"*", borderColor:"green", color:"green" })), + style({ height: AUTO_STYLE, borderColor:"green", color:"green" })), transition("* => *", animate(500)) ]) ], template: ` +
Look at this box @@ -25,6 +27,9 @@ export class AnimateCmp { constructor() { this.setAsClosed(); } + setAsSomethingElse() { + this.stateExpression = 'something'; + } setAsOpen() { this.stateExpression = 'open'; } diff --git a/modules/@angular/compiler-cli/integrationtest/test/animate_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/animate_spec.ts index 8c2f8ed1ca..dca2e41e37 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/animate_spec.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/animate_spec.ts @@ -8,15 +8,19 @@ import {AUTO_STYLE, ReflectiveInjector, DebugElement, getDebugNode} from '@angul import {browserPlatform, BROWSER_APP_PROVIDERS} from '@angular/platform-browser'; describe("template codegen output", () => { + function findTargetElement(elm: DebugElement): DebugElement { + // the open-close-container is a child of the main container + // if the template changes then please update the location below + return elm.children[4]; + } + it("should apply the animate states to the element", (done) => { const appInjector = ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, browserPlatform().injector); var comp = AnimateCmpNgFactory.create(appInjector); var debugElement = getDebugNode(comp.location.nativeElement); - // the open-close-container is a child of the main container - // if the template changes then please update the location below - var targetDebugElement = debugElement.children[3]; + var targetDebugElement = findTargetElement(debugElement); comp.instance.setAsOpen(); comp.changeDetectorRef.detectChanges(); @@ -37,4 +41,32 @@ describe("template codegen output", () => { }, 0); }, 0); }); + + it("should apply the default animate state to the element", (done) => { + const appInjector = ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, + browserPlatform().injector); + var comp = AnimateCmpNgFactory.create(appInjector); + var debugElement = getDebugNode(comp.location.nativeElement); + + var targetDebugElement = findTargetElement(debugElement); + + comp.instance.setAsSomethingElse(); + comp.changeDetectorRef.detectChanges(); + + setTimeout(() => { + expect(targetDebugElement.styles['height']).toEqual(AUTO_STYLE); + expect(targetDebugElement.styles['borderColor']).toEqual('black'); + expect(targetDebugElement.styles['color']).toEqual('black'); + + comp.instance.setAsClosed(); + comp.changeDetectorRef.detectChanges(); + + setTimeout(() => { + expect(targetDebugElement.styles['height']).not.toEqual(AUTO_STYLE); + expect(targetDebugElement.styles['borderColor']).not.toEqual('grey'); + expect(targetDebugElement.styles['color']).not.toEqual('grey'); + done(); + }, 0); + }, 0); + }); }); diff --git a/modules/@angular/compiler/core_private.ts b/modules/@angular/compiler/core_private.ts index 388b313161..e2c57ae7ad 100644 --- a/modules/@angular/compiler/core_private.ts +++ b/modules/@angular/compiler/core_private.ts @@ -77,6 +77,7 @@ export var AnimationKeyframe: typeof t.AnimationKeyframe = r.AnimationKeyframe; export type AnimationStyles = t.AnimationStyles; export var AnimationStyles: typeof t.AnimationStyles = r.AnimationStyles; export var ANY_STATE = r.ANY_STATE; +export var DEFAULT_STATE = r.DEFAULT_STATE; export var EMPTY_STATE = r.EMPTY_STATE; export var FILL_STYLE_FLAG = r.FILL_STYLE_FLAG; export var balanceAnimationStyles: typeof t.balanceAnimationStyles = r.balanceAnimationStyles; diff --git a/modules/@angular/compiler/src/animation/animation_compiler.ts b/modules/@angular/compiler/src/animation/animation_compiler.ts index c75900a15f..e1d23badaf 100644 --- a/modules/@angular/compiler/src/animation/animation_compiler.ts +++ b/modules/@angular/compiler/src/animation/animation_compiler.ts @@ -6,7 +6,7 @@ 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 {DEFAULT_STATE, ANY_STATE, EMPTY_STATE} from '../../core_private'; import { AnimationParseError, @@ -64,6 +64,7 @@ export class AnimationCompiler { } var _ANIMATION_FACTORY_ELEMENT_VAR = o.variable('element'); +var _ANIMATION_DEFAULT_STATE_VAR = o.variable('defaultStateStyles'); 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'); @@ -212,6 +213,9 @@ class _AnimationBuilder implements AnimationAstVisitor { //visit each of the declarations first to build the context state map ast.stateDeclarations.forEach(def => def.visit(this, context)); + //this should always be defined even if the user overrides it + context.stateMap.registerState(DEFAULT_STATE, {}); + var statements = []; statements.push( _ANIMATION_FACTORY_VIEW_VAR.callMethod('cancelActiveAnimation', [ @@ -223,6 +227,11 @@ class _AnimationBuilder implements AnimationAstVisitor { statements.push(_ANIMATION_COLLECTED_STYLES.set(EMPTY_MAP).toDeclStmt()); statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt()); + statements.push( + _ANIMATION_DEFAULT_STATE_VAR.set( + this._statesMapVar.key(o.literal(DEFAULT_STATE)) + ).toDeclStmt()); + statements.push( _ANIMATION_START_STATE_STYLES_VAR.set( this._statesMapVar.key(_ANIMATION_CURRENT_STATE_VAR) @@ -230,7 +239,7 @@ class _AnimationBuilder implements AnimationAstVisitor { statements.push( new o.IfStmt(_ANIMATION_START_STATE_STYLES_VAR.equals(o.NULL_EXPR), [ - _ANIMATION_START_STATE_STYLES_VAR.set(EMPTY_MAP).toStmt() + _ANIMATION_START_STATE_STYLES_VAR.set(_ANIMATION_DEFAULT_STATE_VAR).toStmt() ])); statements.push( @@ -240,7 +249,7 @@ class _AnimationBuilder implements AnimationAstVisitor { statements.push( new o.IfStmt(_ANIMATION_END_STATE_STYLES_VAR.equals(o.NULL_EXPR), [ - _ANIMATION_END_STATE_STYLES_VAR.set(EMPTY_MAP).toStmt() + _ANIMATION_END_STATE_STYLES_VAR.set(_ANIMATION_DEFAULT_STATE_VAR).toStmt() ])); diff --git a/modules/@angular/core/private_export.ts b/modules/@angular/core/private_export.ts index 77906c4817..b331a22798 100644 --- a/modules/@angular/core/private_export.ts +++ b/modules/@angular/core/private_export.ts @@ -39,6 +39,7 @@ import {AnimationStyles as AnimationStyles_} from './src/animation/animation_sty import * as animationUtils from './src/animation/animation_style_util'; 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'; @@ -130,6 +131,7 @@ export declare namespace __core_private_types__ { export type AnimationStyles = AnimationStyles_; export var AnimationStyles: typeof AnimationStyles_; export var ANY_STATE: typeof ANY_STATE_; + export var DEFAULT_STATE: typeof DEFAULT_STATE_; export var EMPTY_STATE: typeof EMPTY_STATE_; export var FILL_STYLE_FLAG: typeof FILL_STYLE_FLAG_; } @@ -199,6 +201,7 @@ export var __core_private__ = { collectAndResolveStyles: animationUtils.collectAndResolveStyles, AnimationStyles: AnimationStyles_, ANY_STATE: ANY_STATE_, + DEFAULT_STATE: DEFAULT_STATE_, EMPTY_STATE: EMPTY_STATE_, FILL_STYLE_FLAG: FILL_STYLE_FLAG_ }; diff --git a/modules/@angular/core/src/animation/animation_constants.ts b/modules/@angular/core/src/animation/animation_constants.ts index 9a6a330d54..236e86afd0 100644 --- a/modules/@angular/core/src/animation/animation_constants.ts +++ b/modules/@angular/core/src/animation/animation_constants.ts @@ -1,3 +1,4 @@ export const FILL_STYLE_FLAG = 'true'; // TODO (matsko): change to boolean export const ANY_STATE = '*'; +export const DEFAULT_STATE = '*'; export const EMPTY_STATE = 'void'; diff --git a/modules/@angular/core/src/animation/metadata.ts b/modules/@angular/core/src/animation/metadata.ts index 577d1138b2..a1148cc4e7 100644 --- a/modules/@angular/core/src/animation/metadata.ts +++ b/modules/@angular/core/src/animation/metadata.ts @@ -292,6 +292,10 @@ export function style(tokens: string|{[key: string]: string | number}|Array { + makeAnimationCmp(tcb, '
', [ + trigger('status', [ + state(DEFAULT_STATE, style({ "background": 'grey' })), + state('green', style({ "background": 'green' })), + state('red', style({ "background": 'red' })), + transition('* => *', [ animate(1000) ]) + ]) + ], (fixture) => { + tick(); + + var cmp = fixture.debugElement.componentInstance; + var node = getDOM().querySelector(fixture.debugElement.nativeElement, '.target'); + cmp.exp = 'green'; + fixture.detectChanges(); + flushMicrotasks(); + + var animation = driver.log.pop(); + var keyframes = animation['keyframeLookup']; + expect(keyframes[1]).toEqual([1, {'background': 'green' }]); + + cmp.exp = 'blue'; + fixture.detectChanges(); + flushMicrotasks(); + + animation = driver.log.pop(); + keyframes = animation['keyframeLookup']; + expect(keyframes[0]).toEqual([0, {'background': 'green' }]); + expect(keyframes[1]).toEqual([1, {'background': 'grey' }]); + + cmp.exp = 'red'; + fixture.detectChanges(); + flushMicrotasks(); + + animation = driver.log.pop(); + keyframes = animation['keyframeLookup']; + expect(keyframes[0]).toEqual([0, {'background': 'grey' }]); + expect(keyframes[1]).toEqual([1, {'background': 'red' }]); + + cmp.exp = 'orange'; + fixture.detectChanges(); + flushMicrotasks(); + + animation = driver.log.pop(); + keyframes = animation['keyframeLookup']; + expect(keyframes[0]).toEqual([0, {'background': 'red' }]); + expect(keyframes[1]).toEqual([1, {'background': 'grey' }]); + }); + }))); + it('should seed in the origin animation state styles into the first animation step', inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => { makeAnimationCmp(tcb, '
', [ diff --git a/modules/playground/src/animate/app/animate-app.ts b/modules/playground/src/animate/app/animate-app.ts index 871bda9dd4..604de904c4 100644 --- a/modules/playground/src/animate/app/animate-app.ts +++ b/modules/playground/src/animate/app/animate-app.ts @@ -17,7 +17,9 @@ import {
+ | +
@@ -34,6 +36,7 @@ import { ]) ]), trigger("boxAnimation", [ + state("*", style({ "height": "*", "background-color": "#dddddd", "color":"black" })), 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" })), @@ -65,15 +68,15 @@ export class AnimateApp { get state() { return this._state; } set state(s) { this._state = s; - if (s == 'start' || s == 'active') { + if (s == 'void') { + this.items = []; + } else { 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 = []; } } }