Skip to content

Commit 6f45519

Browse files
matskoalxhub
authored andcommitted
feat(animations): support :increment and :decrement transition aliases
1 parent 65c9e13 commit 6f45519

File tree

3 files changed

+182
-4
lines changed

3 files changed

+182
-4
lines changed

packages/animations/browser/src/dsl/animation_transition_expr.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,14 @@ export function parseTransitionExpr(
2424
function parseInnerTransitionStr(
2525
eventStr: string, expressions: TransitionMatcherFn[], errors: string[]) {
2626
if (eventStr[0] == ':') {
27-
eventStr = parseAnimationAlias(eventStr, errors);
27+
const result = parseAnimationAlias(eventStr, errors);
28+
if (typeof result == 'function') {
29+
expressions.push(result);
30+
return;
31+
}
32+
eventStr = result as string;
2833
}
34+
2935
const match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/);
3036
if (match == null || match.length < 4) {
3137
errors.push(`The provided transition expression "${eventStr}" is not supported`);
@@ -43,12 +49,16 @@ function parseInnerTransitionStr(
4349
}
4450
}
4551

46-
function parseAnimationAlias(alias: string, errors: string[]): string {
52+
function parseAnimationAlias(alias: string, errors: string[]): string|TransitionMatcherFn {
4753
switch (alias) {
4854
case ':enter':
4955
return 'void => *';
5056
case ':leave':
5157
return '* => void';
58+
case ':increment':
59+
return (fromState: any, toState: any): boolean => parseFloat(toState) > parseFloat(fromState);
60+
case ':decrement':
61+
return (fromState: any, toState: any): boolean => parseFloat(toState) < parseFloat(fromState);
5262
default:
5363
errors.push(`The transition alias value "${alias}" is not supported`);
5464
return '* => *';

packages/animations/src/animation_metadata.ts

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
707707
* ])
708708
* ```
709709
*
710-
* ### Transition Aliases (`:enter` and `:leave`)
710+
* ### Using :enter and :leave
711711
*
712712
* Given that enter (insertion) and leave (removal) animations are so common, the `transition`
713713
* function accepts both `:enter` and `:leave` values which are aliases for the `void => *` and `*
@@ -717,12 +717,88 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
717717
* transition(":enter", [
718718
* style({ opacity: 0 }),
719719
* animate(500, style({ opacity: 1 }))
720-
* ])
720+
* ]),
721721
* transition(":leave", [
722722
* animate(500, style({ opacity: 0 }))
723723
* ])
724724
* ```
725725
*
726+
* ### Using :increment and :decrement
727+
* In addition to the :enter and :leave transition aliases, the :increment and :decrement aliases
728+
* can be used to kick off a transition when a numeric value has increased or decreased in value.
729+
*
730+
* ```
731+
* import {group, animate, query, transition, style, trigger} from '@angular/animations';
732+
* import {Component} from '@angular/core';
733+
*
734+
* @Component({
735+
* selector: 'banner-carousel-component',
736+
* styles: [`
737+
* .banner-container {
738+
* position:relative;
739+
* height:500px;
740+
* overflow:hidden;
741+
* }
742+
* .banner-container > .banner {
743+
* position:absolute;
744+
* left:0;
745+
* top:0;
746+
* font-size:200px;
747+
* line-height:500px;
748+
* font-weight:bold;
749+
* text-align:center;
750+
* width:100%;
751+
* }
752+
* `],
753+
* template: `
754+
* <button (click)="previous()">Previous</button>
755+
* <button (click)="next()">Next</button>
756+
* <hr>
757+
* <div [@bannerAnimation]="selectedIndex" class="banner-container">
758+
* <div class="banner"> {{ banner }} </div>
759+
* </div>
760+
* `
761+
* animations: [
762+
* trigger('bannerAnimation', [
763+
* transition(":increment", group([
764+
* query(':enter', [
765+
* style({ left: '100%' }),
766+
* animate('0.5s ease-out', style('*'))
767+
* ]),
768+
* query(':leave', [
769+
* animate('0.5s ease-out', style({ left: '-100%' }))
770+
* ])
771+
* ])),
772+
* transition(":decrement", group([
773+
* query(':enter', [
774+
* style({ left: '-100%' }),
775+
* animate('0.5s ease-out', style('*'))
776+
* ]),
777+
* query(':leave', [
778+
* animate('0.5s ease-out', style({ left: '100%' }))
779+
* ])
780+
* ])),
781+
* ])
782+
* ]
783+
* })
784+
* class BannerCarouselComponent {
785+
* allBanners: string[] = ['1', '2', '3', '4'];
786+
* selectedIndex: number = 0;
787+
*
788+
* get banners() {
789+
* return [this.allBanners[this.selectedIndex]];
790+
* }
791+
*
792+
* previous() {
793+
* this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
794+
* }
795+
*
796+
* next() {
797+
* this.selectedIndex = Math.min(this.selectedIndex + 1, this.allBanners.length - 1);
798+
* }
799+
* }
800+
* ```
801+
*
726802
* {@example core/animation/ts/dsl/animation_example.ts region='Component'}
727803
*
728804
* @experimental Animation support is experimental.

packages/core/test/animation/animation_integration_spec.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,6 +1541,98 @@ export function main() {
15411541
const players = getLog();
15421542
expect(players.length).toEqual(2);
15431543
});
1544+
1545+
describe('transition aliases', () => {
1546+
describe(':increment', () => {
1547+
it('should detect when a value has incremented', () => {
1548+
@Component({
1549+
selector: 'if-cmp',
1550+
template: `
1551+
<div [@myAnimation]="exp"></div>
1552+
`,
1553+
animations: [
1554+
trigger(
1555+
'myAnimation',
1556+
[
1557+
transition(
1558+
':increment',
1559+
[
1560+
animate(1234, style({background: 'red'})),
1561+
]),
1562+
]),
1563+
]
1564+
})
1565+
class Cmp {
1566+
exp: number = 0;
1567+
}
1568+
1569+
TestBed.configureTestingModule({declarations: [Cmp]});
1570+
const fixture = TestBed.createComponent(Cmp);
1571+
const cmp = fixture.componentInstance;
1572+
fixture.detectChanges();
1573+
let players = getLog();
1574+
expect(players.length).toEqual(0);
1575+
1576+
cmp.exp++;
1577+
fixture.detectChanges();
1578+
players = getLog();
1579+
expect(players.length).toEqual(1);
1580+
expect(players[0].duration).toEqual(1234);
1581+
resetLog();
1582+
1583+
cmp.exp = 5;
1584+
fixture.detectChanges();
1585+
players = getLog();
1586+
expect(players.length).toEqual(1);
1587+
expect(players[0].duration).toEqual(1234);
1588+
});
1589+
});
1590+
1591+
describe(':decrement', () => {
1592+
it('should detect when a value has decremented', () => {
1593+
@Component({
1594+
selector: 'if-cmp',
1595+
template: `
1596+
<div [@myAnimation]="exp"></div>
1597+
`,
1598+
animations: [
1599+
trigger(
1600+
'myAnimation',
1601+
[
1602+
transition(
1603+
':decrement',
1604+
[
1605+
animate(1234, style({background: 'red'})),
1606+
]),
1607+
]),
1608+
]
1609+
})
1610+
class Cmp {
1611+
exp: number = 5;
1612+
}
1613+
1614+
TestBed.configureTestingModule({declarations: [Cmp]});
1615+
const fixture = TestBed.createComponent(Cmp);
1616+
const cmp = fixture.componentInstance;
1617+
fixture.detectChanges();
1618+
let players = getLog();
1619+
expect(players.length).toEqual(0);
1620+
1621+
cmp.exp--;
1622+
fixture.detectChanges();
1623+
players = getLog();
1624+
expect(players.length).toEqual(1);
1625+
expect(players[0].duration).toEqual(1234);
1626+
resetLog();
1627+
1628+
cmp.exp = 0;
1629+
fixture.detectChanges();
1630+
players = getLog();
1631+
expect(players.length).toEqual(1);
1632+
expect(players[0].duration).toEqual(1234);
1633+
});
1634+
});
1635+
});
15441636
});
15451637

15461638
describe('animation listeners', () => {

0 commit comments

Comments
 (0)