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:
@ -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({
|
@Component({
|
||||||
selector: "animate-cmp",
|
selector: "animate-cmp",
|
||||||
animations: [
|
animations: [
|
||||||
trigger('openClose', [
|
trigger('openClose', [
|
||||||
|
state("*", style({ height: AUTO_STYLE, color: 'black', borderColor: 'black' })),
|
||||||
state('closed, void',
|
state('closed, void',
|
||||||
style({ height:"0px", color: "maroon", borderColor: "maroon" })),
|
style({ height:"0px", color: "maroon", borderColor: "maroon" })),
|
||||||
state('open',
|
state('open',
|
||||||
style({ height:"*", borderColor:"green", color:"green" })),
|
style({ height: AUTO_STYLE, borderColor:"green", color:"green" })),
|
||||||
transition("* => *", animate(500))
|
transition("* => *", animate(500))
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
template: `
|
template: `
|
||||||
<button (click)="setAsOpen()">Open</button>
|
<button (click)="setAsOpen()">Open</button>
|
||||||
<button (click)="setAsClosed()">Closed</button>
|
<button (click)="setAsClosed()">Closed</button>
|
||||||
|
<button (click)="setAsSomethingElse()">Something Else</button>
|
||||||
<hr />
|
<hr />
|
||||||
<div @openClose="stateExpression">
|
<div @openClose="stateExpression">
|
||||||
Look at this box
|
Look at this box
|
||||||
@ -25,6 +27,9 @@ export class AnimateCmp {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.setAsClosed();
|
this.setAsClosed();
|
||||||
}
|
}
|
||||||
|
setAsSomethingElse() {
|
||||||
|
this.stateExpression = 'something';
|
||||||
|
}
|
||||||
setAsOpen() {
|
setAsOpen() {
|
||||||
this.stateExpression = 'open';
|
this.stateExpression = 'open';
|
||||||
}
|
}
|
||||||
|
@ -8,15 +8,19 @@ import {AUTO_STYLE, ReflectiveInjector, DebugElement, getDebugNode} from '@angul
|
|||||||
import {browserPlatform, BROWSER_APP_PROVIDERS} from '@angular/platform-browser';
|
import {browserPlatform, BROWSER_APP_PROVIDERS} from '@angular/platform-browser';
|
||||||
|
|
||||||
describe("template codegen output", () => {
|
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) => {
|
it("should apply the animate states to the element", (done) => {
|
||||||
const appInjector = ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS,
|
const appInjector = ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS,
|
||||||
browserPlatform().injector);
|
browserPlatform().injector);
|
||||||
var comp = AnimateCmpNgFactory.create(appInjector);
|
var comp = AnimateCmpNgFactory.create(appInjector);
|
||||||
var debugElement = <DebugElement>getDebugNode(comp.location.nativeElement);
|
var debugElement = <DebugElement>getDebugNode(comp.location.nativeElement);
|
||||||
|
|
||||||
// the open-close-container is a child of the main container
|
var targetDebugElement = findTargetElement(<DebugElement>debugElement);
|
||||||
// if the template changes then please update the location below
|
|
||||||
var targetDebugElement = <DebugElement>debugElement.children[3];
|
|
||||||
|
|
||||||
comp.instance.setAsOpen();
|
comp.instance.setAsOpen();
|
||||||
comp.changeDetectorRef.detectChanges();
|
comp.changeDetectorRef.detectChanges();
|
||||||
@ -37,4 +41,32 @@ describe("template codegen output", () => {
|
|||||||
}, 0);
|
}, 0);
|
||||||
}, 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 type AnimationStyles = t.AnimationStyles;
|
||||||
export var AnimationStyles: typeof t.AnimationStyles = r.AnimationStyles;
|
export var AnimationStyles: typeof t.AnimationStyles = r.AnimationStyles;
|
||||||
export var ANY_STATE = r.ANY_STATE;
|
export var ANY_STATE = r.ANY_STATE;
|
||||||
|
export var DEFAULT_STATE = r.DEFAULT_STATE;
|
||||||
export var EMPTY_STATE = r.EMPTY_STATE;
|
export var EMPTY_STATE = r.EMPTY_STATE;
|
||||||
export var FILL_STYLE_FLAG = r.FILL_STYLE_FLAG;
|
export var FILL_STYLE_FLAG = r.FILL_STYLE_FLAG;
|
||||||
export var balanceAnimationStyles: typeof t.balanceAnimationStyles = r.balanceAnimationStyles;
|
export var balanceAnimationStyles: typeof t.balanceAnimationStyles = r.balanceAnimationStyles;
|
||||||
|
@ -6,7 +6,7 @@ import {Identifiers} from '../identifiers';
|
|||||||
import * as o from '../output/output_ast';
|
import * as o from '../output/output_ast';
|
||||||
|
|
||||||
import {AUTO_STYLE} from '@angular/core';
|
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 {
|
import {
|
||||||
AnimationParseError,
|
AnimationParseError,
|
||||||
@ -64,6 +64,7 @@ export class AnimationCompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ANIMATION_FACTORY_ELEMENT_VAR = o.variable('element');
|
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_VIEW_VAR = o.variable('view');
|
||||||
var _ANIMATION_FACTORY_RENDERER_VAR = _ANIMATION_FACTORY_VIEW_VAR.prop('renderer');
|
var _ANIMATION_FACTORY_RENDERER_VAR = _ANIMATION_FACTORY_VIEW_VAR.prop('renderer');
|
||||||
var _ANIMATION_CURRENT_STATE_VAR = o.variable('currentState');
|
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
|
//visit each of the declarations first to build the context state map
|
||||||
ast.stateDeclarations.forEach(def => def.visit(this, context));
|
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 = [];
|
var statements = [];
|
||||||
statements.push(
|
statements.push(
|
||||||
_ANIMATION_FACTORY_VIEW_VAR.callMethod('cancelActiveAnimation', [
|
_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_COLLECTED_STYLES.set(EMPTY_MAP).toDeclStmt());
|
||||||
statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).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(
|
statements.push(
|
||||||
_ANIMATION_START_STATE_STYLES_VAR.set(
|
_ANIMATION_START_STATE_STYLES_VAR.set(
|
||||||
this._statesMapVar.key(_ANIMATION_CURRENT_STATE_VAR)
|
this._statesMapVar.key(_ANIMATION_CURRENT_STATE_VAR)
|
||||||
@ -230,7 +239,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
|
|
||||||
statements.push(
|
statements.push(
|
||||||
new o.IfStmt(_ANIMATION_START_STATE_STYLES_VAR.equals(o.NULL_EXPR), [
|
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(
|
statements.push(
|
||||||
@ -240,7 +249,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
|||||||
|
|
||||||
statements.push(
|
statements.push(
|
||||||
new o.IfStmt(_ANIMATION_END_STATE_STYLES_VAR.equals(o.NULL_EXPR), [
|
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 * as animationUtils from './src/animation/animation_style_util';
|
||||||
import {
|
import {
|
||||||
ANY_STATE as ANY_STATE_,
|
ANY_STATE as ANY_STATE_,
|
||||||
|
DEFAULT_STATE as DEFAULT_STATE_,
|
||||||
EMPTY_STATE as EMPTY_STATE_,
|
EMPTY_STATE as EMPTY_STATE_,
|
||||||
FILL_STYLE_FLAG as FILL_STYLE_FLAG_
|
FILL_STYLE_FLAG as FILL_STYLE_FLAG_
|
||||||
} from './src/animation/animation_constants';
|
} from './src/animation/animation_constants';
|
||||||
@ -130,6 +131,7 @@ export declare namespace __core_private_types__ {
|
|||||||
export type AnimationStyles = AnimationStyles_;
|
export type AnimationStyles = AnimationStyles_;
|
||||||
export var AnimationStyles: typeof AnimationStyles_;
|
export var AnimationStyles: typeof AnimationStyles_;
|
||||||
export var ANY_STATE: typeof ANY_STATE_;
|
export var ANY_STATE: typeof ANY_STATE_;
|
||||||
|
export var DEFAULT_STATE: typeof DEFAULT_STATE_;
|
||||||
export var EMPTY_STATE: typeof EMPTY_STATE_;
|
export var EMPTY_STATE: typeof EMPTY_STATE_;
|
||||||
export var FILL_STYLE_FLAG: typeof FILL_STYLE_FLAG_;
|
export var FILL_STYLE_FLAG: typeof FILL_STYLE_FLAG_;
|
||||||
}
|
}
|
||||||
@ -199,6 +201,7 @@ export var __core_private__ = {
|
|||||||
collectAndResolveStyles: animationUtils.collectAndResolveStyles,
|
collectAndResolveStyles: animationUtils.collectAndResolveStyles,
|
||||||
AnimationStyles: AnimationStyles_,
|
AnimationStyles: AnimationStyles_,
|
||||||
ANY_STATE: ANY_STATE_,
|
ANY_STATE: ANY_STATE_,
|
||||||
|
DEFAULT_STATE: DEFAULT_STATE_,
|
||||||
EMPTY_STATE: EMPTY_STATE_,
|
EMPTY_STATE: EMPTY_STATE_,
|
||||||
FILL_STYLE_FLAG: FILL_STYLE_FLAG_
|
FILL_STYLE_FLAG: FILL_STYLE_FLAG_
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
export const FILL_STYLE_FLAG = 'true'; // TODO (matsko): change to boolean
|
export const FILL_STYLE_FLAG = 'true'; // TODO (matsko): change to boolean
|
||||||
export const ANY_STATE = '*';
|
export const ANY_STATE = '*';
|
||||||
|
export const DEFAULT_STATE = '*';
|
||||||
export const EMPTY_STATE = 'void';
|
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
|
* of the application anymore (e.g. when an `ngIf` evaluates to false then the state of the associated element
|
||||||
* is void).
|
* 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
|
* ### Usage
|
||||||
*
|
*
|
||||||
|
@ -25,6 +25,7 @@ import {
|
|||||||
import {isPresent, isArray, IS_DART} from '../../src/facade/lang';
|
import {isPresent, isArray, IS_DART} from '../../src/facade/lang';
|
||||||
|
|
||||||
import {Component} from '../../index';
|
import {Component} from '../../index';
|
||||||
|
import {DEFAULT_STATE} from '../../src/animation/animation_constants';
|
||||||
|
|
||||||
import {NgIf} from '@angular/common';
|
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',
|
it('should seed in the origin animation state styles into the first animation step',
|
||||||
inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
inject([TestComponentBuilder, AnimationDriver], fakeAsync((tcb: TestComponentBuilder, driver: MockAnimationDriver) => {
|
||||||
makeAnimationCmp(tcb, '<div class="target" @status="exp"></div>', [
|
makeAnimationCmp(tcb, '<div class="target" @status="exp"></div>', [
|
||||||
|
@ -17,7 +17,9 @@ import {
|
|||||||
<div @backgroundAnimation="bgStatus">
|
<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 style="float:right" (click)="bgStatus='blur'">Blur Page</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">
|
||||||
@ -34,6 +36,7 @@ import {
|
|||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
trigger("boxAnimation", [
|
trigger("boxAnimation", [
|
||||||
|
state("*", style({ "height": "*", "background-color": "#dddddd", "color":"black" })),
|
||||||
state("void, hidden", style({ "height": 0, "opacity": 0 })),
|
state("void, hidden", style({ "height": 0, "opacity": 0 })),
|
||||||
state("start", style({ "background-color": "red", "height": "*" })),
|
state("start", style({ "background-color": "red", "height": "*" })),
|
||||||
state("active", style({ "background-color": "orange", "color": "white", "font-size":"100px" })),
|
state("active", style({ "background-color": "orange", "color": "white", "font-size":"100px" })),
|
||||||
@ -65,15 +68,15 @@ export class AnimateApp {
|
|||||||
get state() { return this._state; }
|
get state() { return this._state; }
|
||||||
set state(s) {
|
set state(s) {
|
||||||
this._state = s;
|
this._state = s;
|
||||||
if (s == 'start' || s == 'active') {
|
if (s == 'void') {
|
||||||
|
this.items = [];
|
||||||
|
} else {
|
||||||
this.items = [
|
this.items = [
|
||||||
1,2,3,4,5,
|
1,2,3,4,5,
|
||||||
6,7,8,9,10,
|
6,7,8,9,10,
|
||||||
11,12,13,14,15,
|
11,12,13,14,15,
|
||||||
16,17,18,19,20
|
16,17,18,19,20
|
||||||
];
|
];
|
||||||
} else {
|
|
||||||
this.items = [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user