fix(animations): restore auto-style support for removed DOM nodes (#18787)
PR Close #18787
This commit is contained in:

committed by
Miško Hevery

parent
9a754f9f0f
commit
e1f45a33b7
@ -1087,59 +1087,61 @@ export function main() {
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
it('should animate removals of nodes to the `void` state for each animation trigger', () => {
|
||||
@Component({
|
||||
selector: 'ani-cmp',
|
||||
template: `
|
||||
it('should animate removals of nodes to the `void` state for each animation trigger, but treat all auto styles as pre styles',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'ani-cmp',
|
||||
template: `
|
||||
<div *ngIf="exp" class="ng-if" [@trig1]="exp2" @trig2></div>
|
||||
`,
|
||||
animations: [
|
||||
trigger('trig1', [transition('state => void', [animate(1000, style({opacity: 0}))])]),
|
||||
trigger('trig2', [transition(':leave', [animate(1000, style({width: '0px'}))])])
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
public exp = true;
|
||||
public exp2 = 'state';
|
||||
}
|
||||
animations: [
|
||||
trigger(
|
||||
'trig1', [transition('state => void', [animate(1000, style({opacity: 0}))])]),
|
||||
trigger('trig2', [transition(':leave', [animate(1000, style({width: '0px'}))])])
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
public exp = true;
|
||||
public exp2 = 'state';
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
resetLog();
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
resetLog();
|
||||
|
||||
const element = getDOM().querySelector(fixture.nativeElement, '.ng-if');
|
||||
assertHasParent(element, true);
|
||||
const element = getDOM().querySelector(fixture.nativeElement, '.ng-if');
|
||||
assertHasParent(element, true);
|
||||
|
||||
cmp.exp = false;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
cmp.exp = false;
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
assertHasParent(element, true);
|
||||
assertHasParent(element, true);
|
||||
|
||||
expect(getLog().length).toEqual(2);
|
||||
expect(getLog().length).toEqual(2);
|
||||
|
||||
const player2 = getLog().pop() !;
|
||||
const player1 = getLog().pop() !;
|
||||
const player2 = getLog().pop() !;
|
||||
const player1 = getLog().pop() !;
|
||||
|
||||
expect(player2.keyframes).toEqual([
|
||||
{width: AUTO_STYLE, offset: 0},
|
||||
{width: '0px', offset: 1},
|
||||
]);
|
||||
expect(player2.keyframes).toEqual([
|
||||
{width: PRE_STYLE, offset: 0},
|
||||
{width: '0px', offset: 1},
|
||||
]);
|
||||
|
||||
expect(player1.keyframes).toEqual([
|
||||
{opacity: AUTO_STYLE, offset: 0}, {opacity: '0', offset: 1}
|
||||
]);
|
||||
expect(player1.keyframes).toEqual([
|
||||
{opacity: PRE_STYLE, offset: 0}, {opacity: '0', offset: 1}
|
||||
]);
|
||||
|
||||
player2.finish();
|
||||
player1.finish();
|
||||
assertHasParent(element, false);
|
||||
});
|
||||
player2.finish();
|
||||
player1.finish();
|
||||
assertHasParent(element, false);
|
||||
});
|
||||
|
||||
it('should properly cancel all existing animations when a removal occurs', () => {
|
||||
@Component({
|
||||
|
@ -5,8 +5,9 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {animate, query, state, style, transition, trigger} from '@angular/animations';
|
||||
import {animate, group, query, state, style, transition, trigger} from '@angular/animations';
|
||||
import {AnimationDriver, ɵAnimationEngine, ɵWebAnimationsDriver, ɵWebAnimationsPlayer, ɵsupportsWebAnimations} from '@angular/animations/browser';
|
||||
import {TransitionAnimationPlayer} from '@angular/animations/browser/src/render/transition_animation_engine';
|
||||
import {AnimationGroupPlayer} from '@angular/animations/src/players/animation_group_player';
|
||||
import {Component} from '@angular/core';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
@ -127,7 +128,7 @@ export function main() {
|
||||
template: `
|
||||
<div [@myAnimation]="exp" #parent>
|
||||
<div *ngFor="let item of items" class="child" style="line-height:20px">
|
||||
- {{ item }}
|
||||
- {{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
@ -177,6 +178,164 @@ export function main() {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should treat * styles as ! when a removal animation is being rendered', () => {
|
||||
@Component({
|
||||
selector: 'ani-cmp',
|
||||
styles: [`
|
||||
.box {
|
||||
width: 500px;
|
||||
overflow:hidden;
|
||||
background:orange;
|
||||
line-height:300px;
|
||||
font-size:100px;
|
||||
text-align:center;
|
||||
}
|
||||
`],
|
||||
template: `
|
||||
<button (click)="toggle()">Open / Close</button>
|
||||
<hr />
|
||||
<div *ngIf="exp" @slide class="box">
|
||||
...
|
||||
</div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'slide',
|
||||
[
|
||||
state('void', style({height: '0px'})),
|
||||
state('*', style({height: '*'})),
|
||||
transition('* => *', animate('500ms')),
|
||||
])]
|
||||
})
|
||||
class Cmp {
|
||||
exp = false;
|
||||
|
||||
toggle() { this.exp = !this.exp; }
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
cmp.exp = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let player = engine.players[0] !;
|
||||
let webPlayer = player.getRealPlayer() as ɵWebAnimationsPlayer;
|
||||
expect(webPlayer.keyframes).toEqual([
|
||||
{height: '0px', offset: 0},
|
||||
{height: '300px', offset: 1},
|
||||
]);
|
||||
player.finish();
|
||||
|
||||
cmp.exp = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
player = engine.players[0] !;
|
||||
webPlayer = player.getRealPlayer() as ɵWebAnimationsPlayer;
|
||||
expect(webPlayer.keyframes).toEqual([
|
||||
{height: '300px', offset: 0},
|
||||
{height: '0px', offset: 1},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should treat * styles as ! for queried items that are collected in a container that is being removed',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
styles: [`
|
||||
.list .outer {
|
||||
overflow:hidden;
|
||||
}
|
||||
.list .inner {
|
||||
line-height:50px;
|
||||
}
|
||||
`],
|
||||
template: `
|
||||
<button (click)="empty()">Empty</button>
|
||||
<button (click)="middle()">Middle</button>
|
||||
<button (click)="full()">Full</button>
|
||||
<hr />
|
||||
<div [@list]="exp" class="list">
|
||||
<div *ngFor="let item of items" class="outer">
|
||||
<div class="inner">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
animations: [
|
||||
trigger('list', [
|
||||
transition(':enter', []),
|
||||
transition('* => empty', [
|
||||
query(':leave', [
|
||||
animate(500, style({ height: '0px' }))
|
||||
])
|
||||
]),
|
||||
transition('* => full', [
|
||||
query(':enter', [
|
||||
style({ height: '0px' }),
|
||||
animate(500, style({ height: '*' }))
|
||||
])
|
||||
]),
|
||||
])
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
items: any[] = [];
|
||||
|
||||
get exp() { return this.items.length ? 'full' : 'empty'; }
|
||||
|
||||
empty() { this.items = []; }
|
||||
|
||||
full() { this.items = [0, 1, 2, 3, 4]; }
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
cmp.empty();
|
||||
fixture.detectChanges();
|
||||
let player = engine.players[0] !as TransitionAnimationPlayer;
|
||||
player.finish();
|
||||
|
||||
cmp.full();
|
||||
fixture.detectChanges();
|
||||
|
||||
player = engine.players[0] !as TransitionAnimationPlayer;
|
||||
let queriedPlayers = (player.getRealPlayer() as AnimationGroupPlayer).players;
|
||||
expect(queriedPlayers.length).toEqual(5);
|
||||
|
||||
let i = 0;
|
||||
for (i = 0; i < queriedPlayers.length; i++) {
|
||||
let player = queriedPlayers[i] as ɵWebAnimationsPlayer;
|
||||
expect(player.keyframes).toEqual([
|
||||
{height: '0px', offset: 0},
|
||||
{height: '50px', offset: 1},
|
||||
]);
|
||||
player.finish();
|
||||
}
|
||||
|
||||
cmp.empty();
|
||||
fixture.detectChanges();
|
||||
|
||||
player = engine.players[0] !as TransitionAnimationPlayer;
|
||||
queriedPlayers = (player.getRealPlayer() as AnimationGroupPlayer).players;
|
||||
expect(queriedPlayers.length).toEqual(5);
|
||||
|
||||
for (i = 0; i < queriedPlayers.length; i++) {
|
||||
let player = queriedPlayers[i] as ɵWebAnimationsPlayer;
|
||||
expect(player.keyframes).toEqual([
|
||||
{height: '50px', offset: 0},
|
||||
{height: '0px', offset: 1},
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should compute intermediate styles properly when an animation is cancelled', () => {
|
||||
@Component({
|
||||
selector: 'ani-cmp',
|
||||
|
Reference in New Issue
Block a user