fix(animations): compute removal node height correctly

This commit is contained in:
Matias Niemelä 2017-06-09 14:39:36 -07:00 committed by Alex Rickabaugh
parent 451257a2fd
commit 185075d870
2 changed files with 120 additions and 16 deletions

View File

@ -740,18 +740,20 @@ export class TransitionAnimationEngine {
// the :enter queries match the elements (since the timeline queries // the :enter queries match the elements (since the timeline queries
// are fired during instruction building). // are fired during instruction building).
const bodyNode = getBodyNode(); const bodyNode = getBodyNode();
const allEnterNodes: any[] = this.collectedEnterElements; const allEnterNodes: any[] = this.collectedEnterElements.length ?
const enterNodes: any[] = collectEnterElements(this.driver, this.collectedEnterElements) :
allEnterNodes.length ? collectEnterElements(this.driver, allEnterNodes) : []; [];
const leaveNodes: any[] = []; const allLeaveNodes: any[] = [];
const leaveNodesWithoutAnimations: any[] = [];
for (let i = 0; i < this.collectedLeaveElements.length; i++) { for (let i = 0; i < this.collectedLeaveElements.length; i++) {
const element = this.collectedLeaveElements[i]; const element = this.collectedLeaveElements[i];
if (isElementNode(element)) {
const details = element[REMOVAL_FLAG] as ElementAnimationState; const details = element[REMOVAL_FLAG] as ElementAnimationState;
if (details && details.setForRemoval) { if (details && details.setForRemoval) {
addClass(element, LEAVE_CLASSNAME); addClass(element, LEAVE_CLASSNAME);
leaveNodes.push(element); allLeaveNodes.push(element);
if (!details.hasAnimation) {
leaveNodesWithoutAnimations.push(element);
} }
} }
} }
@ -817,6 +819,16 @@ export class TransitionAnimationEngine {
}); });
} }
// these can only be detected here since we have a map of all the elements
// that have animations attached to them...
const enterNodesWithoutAnimations: any[] = [];
for (let i = 0; i < allEnterNodes.length; i++) {
const element = allEnterNodes[i];
if (!subTimelines.has(element)) {
enterNodesWithoutAnimations.push(element);
}
}
const allPreviousPlayersMap = new Map<any, TransitionAnimationPlayer[]>(); const allPreviousPlayersMap = new Map<any, TransitionAnimationPlayer[]>();
let sortedParentElements: any[] = []; let sortedParentElements: any[] = [];
queuedInstructions.forEach(entry => { queuedInstructions.forEach(entry => {
@ -840,12 +852,13 @@ export class TransitionAnimationEngine {
// PRE STAGE: fill the ! styles // PRE STAGE: fill the ! styles
const preStylesMap = allPreStyleElements.size ? const preStylesMap = allPreStyleElements.size ?
cloakAndComputeStyles(this.driver, enterNodes, allPreStyleElements, PRE_STYLE) : cloakAndComputeStyles(
this.driver, enterNodesWithoutAnimations, allPreStyleElements, PRE_STYLE) :
new Map<any, ɵStyleData>(); new Map<any, ɵStyleData>();
// POST STAGE: fill the * styles // POST STAGE: fill the * styles
const postStylesMap = const postStylesMap = cloakAndComputeStyles(
cloakAndComputeStyles(this.driver, leaveNodes, allPostStyleElements, AUTO_STYLE); this.driver, leaveNodesWithoutAnimations, allPostStyleElements, AUTO_STYLE);
const rootPlayers: TransitionAnimationPlayer[] = []; const rootPlayers: TransitionAnimationPlayer[] = [];
const subPlayers: TransitionAnimationPlayer[] = []; const subPlayers: TransitionAnimationPlayer[] = [];
@ -907,8 +920,8 @@ export class TransitionAnimationEngine {
// run through all of the queued removals and see if they // run through all of the queued removals and see if they
// were picked up by a query. If not then perform the removal // were picked up by a query. If not then perform the removal
// operation right away unless a parent animation is ongoing. // operation right away unless a parent animation is ongoing.
for (let i = 0; i < leaveNodes.length; i++) { for (let i = 0; i < allLeaveNodes.length; i++) {
const element = leaveNodes[i]; const element = allLeaveNodes[i];
const players = queriedElements.get(element); const players = queriedElements.get(element);
if (players) { if (players) {
removeNodesAfterAnimationDone(this, element, players); removeNodesAfterAnimationDone(this, element, players);
@ -931,7 +944,7 @@ export class TransitionAnimationEngine {
player.play(); player.play();
}); });
enterNodes.forEach(element => removeClass(element, ENTER_CLASSNAME)); allEnterNodes.forEach(element => removeClass(element, ENTER_CLASSNAME));
return rootPlayers; return rootPlayers;
} }

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {animate, style, transition, trigger} from '@angular/animations'; import {animate, state, style, transition, trigger} from '@angular/animations';
import {AnimationDriver, ɵAnimationEngine} from '@angular/animations/browser'; import {AnimationDriver, ɵAnimationEngine} from '@angular/animations/browser';
import {ɵWebAnimationsDriver, ɵWebAnimationsPlayer, ɵsupportsWebAnimations} from '@angular/animations/browser' import {ɵWebAnimationsDriver, ɵWebAnimationsPlayer, ɵsupportsWebAnimations} from '@angular/animations/browser'
import {Component, ViewChild} from '@angular/core'; import {Component, ViewChild} from '@angular/core';
@ -26,6 +26,97 @@ export function main() {
}); });
}); });
it('should compute (*) animation styles for a container that is being removed', () => {
@Component({
selector: 'ani-cmp',
template: `
<div @auto *ngIf="exp">
<div style="line-height:20px;">1</div>
<div style="line-height:20px;">2</div>
<div style="line-height:20px;">3</div>
<div style="line-height:20px;">4</div>
<div style="line-height:20px;">5</div>
</div>
`,
animations: [trigger(
'auto',
[
state('void', style({height: '0px'})), state('*', style({height: '*'})),
transition('* => *', animate(1000))
])]
})
class Cmp {
public exp: boolean = false;
}
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();
expect(engine.players.length).toEqual(1);
let webPlayer = engine.players[0].getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '0px', offset: 0}, {height: '100px', offset: 1}
]);
cmp.exp = false;
fixture.detectChanges();
engine.flush();
expect(engine.players.length).toEqual(1);
webPlayer = engine.players[0].getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '100px', offset: 0}, {height: '0px', offset: 1}
]);
});
it('should compute (!) animation styles for a container that is being inserted', () => {
@Component({
selector: 'ani-cmp',
template: `
<div @auto *ngIf="exp">
<div style="line-height:20px;">1</div>
<div style="line-height:20px;">2</div>
<div style="line-height:20px;">3</div>
<div style="line-height:20px;">4</div>
<div style="line-height:20px;">5</div>
</div>
`,
animations: [trigger(
'auto',
[transition(
':enter', [style({height: '!'}), animate(1000, style({height: '120px'}))])])]
})
class Cmp {
public exp: boolean = false;
}
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();
expect(engine.players.length).toEqual(1);
let webPlayer = engine.players[0].getRealPlayer() as ɵWebAnimationsPlayer;
expect(webPlayer.keyframes).toEqual([
{height: '100px', offset: 0}, {height: '120px', offset: 1}
]);
});
it('should compute pre (!) and post (*) animation styles with different dom states', () => { it('should compute pre (!) and post (*) animation styles with different dom states', () => {
@Component({ @Component({
selector: 'ani-cmp', selector: 'ani-cmp',