feat(animations): support :increment and :decrement transition aliases

This commit is contained in:
Matias Niemelä 2017-07-07 09:10:10 -07:00 committed by Alex Rickabaugh
parent 65c9e13105
commit 6f45519d6f
3 changed files with 182 additions and 4 deletions

View File

@ -24,8 +24,14 @@ export function parseTransitionExpr(
function parseInnerTransitionStr( function parseInnerTransitionStr(
eventStr: string, expressions: TransitionMatcherFn[], errors: string[]) { eventStr: string, expressions: TransitionMatcherFn[], errors: string[]) {
if (eventStr[0] == ':') { if (eventStr[0] == ':') {
eventStr = parseAnimationAlias(eventStr, errors); const result = parseAnimationAlias(eventStr, errors);
if (typeof result == 'function') {
expressions.push(result);
return;
}
eventStr = result as string;
} }
const match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/); const match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/);
if (match == null || match.length < 4) { if (match == null || match.length < 4) {
errors.push(`The provided transition expression "${eventStr}" is not supported`); errors.push(`The provided transition expression "${eventStr}" is not supported`);
@ -43,12 +49,16 @@ function parseInnerTransitionStr(
} }
} }
function parseAnimationAlias(alias: string, errors: string[]): string { function parseAnimationAlias(alias: string, errors: string[]): string|TransitionMatcherFn {
switch (alias) { switch (alias) {
case ':enter': case ':enter':
return 'void => *'; return 'void => *';
case ':leave': case ':leave':
return '* => void'; return '* => void';
case ':increment':
return (fromState: any, toState: any): boolean => parseFloat(toState) > parseFloat(fromState);
case ':decrement':
return (fromState: any, toState: any): boolean => parseFloat(toState) < parseFloat(fromState);
default: default:
errors.push(`The transition alias value "${alias}" is not supported`); errors.push(`The transition alias value "${alias}" is not supported`);
return '* => *'; return '* => *';

View File

@ -707,7 +707,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
* ]) * ])
* ``` * ```
* *
* ### Transition Aliases (`:enter` and `:leave`) * ### Using :enter and :leave
* *
* Given that enter (insertion) and leave (removal) animations are so common, the `transition` * Given that enter (insertion) and leave (removal) animations are so common, the `transition`
* function accepts both `:enter` and `:leave` values which are aliases for the `void => *` and `* * function accepts both `:enter` and `:leave` values which are aliases for the `void => *` and `*
@ -717,12 +717,88 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
* transition(":enter", [ * transition(":enter", [
* style({ opacity: 0 }), * style({ opacity: 0 }),
* animate(500, style({ opacity: 1 })) * animate(500, style({ opacity: 1 }))
* ]) * ]),
* transition(":leave", [ * transition(":leave", [
* animate(500, style({ opacity: 0 })) * animate(500, style({ opacity: 0 }))
* ]) * ])
* ``` * ```
* *
* ### Using :increment and :decrement
* In addition to the :enter and :leave transition aliases, the :increment and :decrement aliases
* can be used to kick off a transition when a numeric value has increased or decreased in value.
*
* ```
* import {group, animate, query, transition, style, trigger} from '@angular/animations';
* import {Component} from '@angular/core';
*
* @Component({
* selector: 'banner-carousel-component',
* styles: [`
* .banner-container {
* position:relative;
* height:500px;
* overflow:hidden;
* }
* .banner-container > .banner {
* position:absolute;
* left:0;
* top:0;
* font-size:200px;
* line-height:500px;
* font-weight:bold;
* text-align:center;
* width:100%;
* }
* `],
* template: `
* <button (click)="previous()">Previous</button>
* <button (click)="next()">Next</button>
* <hr>
* <div [@bannerAnimation]="selectedIndex" class="banner-container">
* <div class="banner"> {{ banner }} </div>
* </div>
* `
* animations: [
* trigger('bannerAnimation', [
* transition(":increment", group([
* query(':enter', [
* style({ left: '100%' }),
* animate('0.5s ease-out', style('*'))
* ]),
* query(':leave', [
* animate('0.5s ease-out', style({ left: '-100%' }))
* ])
* ])),
* transition(":decrement", group([
* query(':enter', [
* style({ left: '-100%' }),
* animate('0.5s ease-out', style('*'))
* ]),
* query(':leave', [
* animate('0.5s ease-out', style({ left: '100%' }))
* ])
* ])),
* ])
* ]
* })
* class BannerCarouselComponent {
* allBanners: string[] = ['1', '2', '3', '4'];
* selectedIndex: number = 0;
*
* get banners() {
* return [this.allBanners[this.selectedIndex]];
* }
*
* previous() {
* this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
* }
*
* next() {
* this.selectedIndex = Math.min(this.selectedIndex + 1, this.allBanners.length - 1);
* }
* }
* ```
*
* {@example core/animation/ts/dsl/animation_example.ts region='Component'} * {@example core/animation/ts/dsl/animation_example.ts region='Component'}
* *
* @experimental Animation support is experimental. * @experimental Animation support is experimental.

View File

@ -1541,6 +1541,98 @@ export function main() {
const players = getLog(); const players = getLog();
expect(players.length).toEqual(2); expect(players.length).toEqual(2);
}); });
describe('transition aliases', () => {
describe(':increment', () => {
it('should detect when a value has incremented', () => {
@Component({
selector: 'if-cmp',
template: `
<div [@myAnimation]="exp"></div>
`,
animations: [
trigger(
'myAnimation',
[
transition(
':increment',
[
animate(1234, style({background: 'red'})),
]),
]),
]
})
class Cmp {
exp: number = 0;
}
TestBed.configureTestingModule({declarations: [Cmp]});
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
fixture.detectChanges();
let players = getLog();
expect(players.length).toEqual(0);
cmp.exp++;
fixture.detectChanges();
players = getLog();
expect(players.length).toEqual(1);
expect(players[0].duration).toEqual(1234);
resetLog();
cmp.exp = 5;
fixture.detectChanges();
players = getLog();
expect(players.length).toEqual(1);
expect(players[0].duration).toEqual(1234);
});
});
describe(':decrement', () => {
it('should detect when a value has decremented', () => {
@Component({
selector: 'if-cmp',
template: `
<div [@myAnimation]="exp"></div>
`,
animations: [
trigger(
'myAnimation',
[
transition(
':decrement',
[
animate(1234, style({background: 'red'})),
]),
]),
]
})
class Cmp {
exp: number = 5;
}
TestBed.configureTestingModule({declarations: [Cmp]});
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
fixture.detectChanges();
let players = getLog();
expect(players.length).toEqual(0);
cmp.exp--;
fixture.detectChanges();
players = getLog();
expect(players.length).toEqual(1);
expect(players[0].duration).toEqual(1234);
resetLog();
cmp.exp = 0;
fixture.detectChanges();
players = getLog();
expect(players.length).toEqual(1);
expect(players[0].duration).toEqual(1234);
});
});
});
}); });
describe('animation listeners', () => { describe('animation listeners', () => {