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
This commit is contained in:
parent
c3d2459a4e
commit
36d25f2a07
|
@ -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: `
|
||||
<button (click)="setAsOpen()">Open</button>
|
||||
<button (click)="setAsClosed()">Closed</button>
|
||||
<button (click)="setAsSomethingElse()">Something Else</button>
|
||||
<hr />
|
||||
<div @openClose="stateExpression">
|
||||
Look at this box
|
||||
|
@ -25,6 +27,9 @@ export class AnimateCmp {
|
|||
constructor() {
|
||||
this.setAsClosed();
|
||||
}
|
||||
setAsSomethingElse() {
|
||||
this.stateExpression = 'something';
|
||||
}
|
||||
setAsOpen() {
|
||||
this.stateExpression = 'open';
|
||||
}
|
||||
|
|
|
@ -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 = <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>debugElement.children[3];
|
||||
var targetDebugElement = findTargetElement(<DebugElement>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 = <DebugElement>getDebugNode(comp.location.nativeElement);
|
||||
|
||||
var targetDebugElement = findTargetElement(<DebugElement>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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
]));
|
||||
|
||||
|
||||
|
|
|
@ -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_
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -292,6 +292,10 @@ export function style(tokens: string|{[key: string]: string | number}|Array<stri
|
|||
* of the application anymore (e.g. when an `ngIf` evaluates to false then the state of the associated element
|
||||
* is void).
|
||||
*
|
||||
* #### The `*` (default) state
|
||||
*
|
||||
* The `*` state (when styled) is a fallback state that will be used if
|
||||
* the state that is being animated is not declared within the trigger.
|
||||
*
|
||||
* ### Usage
|
||||
*
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
import {isPresent, isArray, IS_DART} from '../../src/facade/lang';
|
||||
|
||||
import {Component} from '../../index';
|
||||
import {DEFAULT_STATE} from '../../src/animation/animation_constants';
|
||||
|
||||
import {NgIf} from '@angular/common';
|
||||
|
||||
|
@ -702,6 +703,57 @@ function declareTests() {
|
|||
});
|
||||
})));
|
||||
|
||||
it('should animate to and retain the default animation state styles once the animation is complete if defined',
|
||||
inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||
makeAnimationCmp(tcb, '<div class="target" @status="exp"></div>', [
|
||||
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, '<div class="target" @status="exp"></div>', [
|
||||
|
|
|
@ -17,7 +17,9 @@ import {
|
|||
<div @backgroundAnimation="bgStatus">
|
||||
<button (click)="state='start'">Start State</button>
|
||||
<button (click)="state='active'">Active State</button>
|
||||
|
|
||||
<button (click)="state='void'">Void State</button>
|
||||
<button (click)="state='default'">Unhandled (default) State</button>
|
||||
<button style="float:right" (click)="bgStatus='blur'">Blur Page</button>
|
||||
<hr />
|
||||
<div *ngFor="let item of items" class="box" @boxAnimation="state">
|
||||
|
@ -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 = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue