fix(animations): retain styling when transition destinations are changed (#12208)

Closes #9661
Closes #12208
This commit is contained in:
Matias Niemelä
2016-11-14 16:59:06 -08:00
committed by Victor Berchet
parent 46023e4792
commit 9de76ebfa5
24 changed files with 403 additions and 79 deletions

View File

@ -88,7 +88,7 @@ export class AnimationGroupPlayer implements AnimationPlayer {
this._started = false;
}
setPosition(p: any /** TODO #9100 */): void {
setPosition(p: number): void {
this._players.forEach(player => { player.setPosition(p); });
}
@ -100,4 +100,6 @@ export class AnimationGroupPlayer implements AnimationPlayer {
});
return min;
}
get players(): AnimationPlayer[] { return this._players; }
}

View File

@ -56,6 +56,6 @@ export class NoOpAnimationPlayer implements AnimationPlayer {
finish(): void { this._onFinish(); }
destroy(): void {}
reset(): void {}
setPosition(p: any /** TODO #9100 */): void {}
setPosition(p: number): void {}
getPosition(): number { return 0; }
}

View File

@ -104,7 +104,9 @@ export class AnimationSequencePlayer implements AnimationPlayer {
}
}
setPosition(p: any /** TODO #9100 */): void { this._players[0].setPosition(p); }
setPosition(p: number): void { this._players[0].setPosition(p); }
getPosition(): number { return this._players[0].getPosition(); }
get players(): AnimationPlayer[] { return this._players; }
}

View File

@ -84,6 +84,8 @@ export function balanceAnimationKeyframes(
firstKeyframe.styles.styles.push(extraFirstKeyframeStyles);
}
collectAndResolveStyles(collectedStyles, [finalStateStyles]);
return keyframes;
}

View File

@ -22,7 +22,7 @@ export class DebugDomRootRenderer implements RootRenderer {
}
}
export class DebugDomRenderer implements Renderer {
export class DebugDomRenderer {
constructor(private _delegate: Renderer) {}
selectRootElement(selectorOrNode: string|any, debugInfo?: RenderDebugInfo): any {
@ -150,7 +150,9 @@ export class DebugDomRenderer implements Renderer {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
return this._delegate.animate(element, startingStyles, keyframes, duration, delay, easing);
duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return this._delegate.animate(
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
}
}

View File

@ -8,8 +8,9 @@
import {AnimationGroupPlayer} from '../animation/animation_group_player';
import {AnimationPlayer} from '../animation/animation_player';
import {queueAnimation as queueAnimationGlobally} from '../animation/animation_queue';
import {AnimationTransitionEvent} from '../animation/animation_transition_event';
import {AnimationSequencePlayer} from '../animation/animation_sequence_player';
import {ViewAnimationMap} from '../animation/view_animation_map';
import {ListWrapper} from '../facade/collection';
export class AnimationViewContext {
private _players = new ViewAnimationMap();
@ -30,15 +31,26 @@ export class AnimationViewContext {
this._players.set(element, animationName, player);
}
cancelActiveAnimation(element: any, animationName: string, removeAllAnimations: boolean = false):
void {
getAnimationPlayers(element: any, animationName: string, removeAllAnimations: boolean = false):
AnimationPlayer[] {
const players: AnimationPlayer[] = [];
if (removeAllAnimations) {
this._players.findAllPlayersByElement(element).forEach(player => player.destroy());
this._players.findAllPlayersByElement(element).forEach(
player => { _recursePlayers(player, players); });
} else {
const player = this._players.find(element, animationName);
if (player) {
player.destroy();
const currentPlayer = this._players.find(element, animationName);
if (currentPlayer) {
_recursePlayers(currentPlayer, players);
}
}
return players;
}
}
function _recursePlayers(player: AnimationPlayer, collectedPlayers: AnimationPlayer[]) {
if ((player instanceof AnimationGroupPlayer) || (player instanceof AnimationSequencePlayer)) {
player.players.forEach(player => _recursePlayers(player, collectedPlayers));
} else {
collectedPlayers.push(player);
}
}

View File

@ -88,7 +88,8 @@ export abstract class Renderer {
abstract animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer;
duration: number, delay: number, easing: string,
previousPlayers?: AnimationPlayer[]): AnimationPlayer;
}
/**

View File

@ -1854,6 +1854,8 @@ function declareTests({useJit}: {useJit: boolean}) {
let animation = driver.log.pop();
let kf = animation['keyframeLookup'];
expect(kf[1]).toEqual([1, {'background': 'green'}]);
let player = animation['player'];
player.finish();
cmp.exp = 'blue';
fixture.detectChanges();
@ -1863,6 +1865,8 @@ function declareTests({useJit}: {useJit: boolean}) {
kf = animation['keyframeLookup'];
expect(kf[0]).toEqual([0, {'background': 'green'}]);
expect(kf[1]).toEqual([1, {'background': 'grey'}]);
player = animation['player'];
player.finish();
cmp.exp = 'red';
fixture.detectChanges();
@ -1872,6 +1876,8 @@ function declareTests({useJit}: {useJit: boolean}) {
kf = animation['keyframeLookup'];
expect(kf[0]).toEqual([0, {'background': 'grey'}]);
expect(kf[1]).toEqual([1, {'background': 'red'}]);
player = animation['player'];
player.finish();
cmp.exp = 'orange';
fixture.detectChanges();
@ -1881,6 +1887,8 @@ function declareTests({useJit}: {useJit: boolean}) {
kf = animation['keyframeLookup'];
expect(kf[0]).toEqual([0, {'background': 'red'}]);
expect(kf[1]).toEqual([1, {'background': 'grey'}]);
player = animation['player'];
player.finish();
}));
it('should seed in the origin animation state styles into the first animation step',
@ -1911,6 +1919,44 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(animation['startingStyles']).toEqual({'height': '100px'});
}));
it('should seed in the previous animation styles into the transition if the previous transition was interupted midway',
fakeAsync(() => {
TestBed.overrideComponent(DummyIfCmp, {
set: {
template: `
<div class="target" [@status]="exp"></div>
`,
animations: [trigger(
'status',
[
state('*', style({ opacity: 0 })),
state('a', style({height: '100px', width: '200px'})),
state('b', style({height: '1000px' })),
transition('* => *', [
animate(1000, style({ fontSize: '20px' })),
animate(1000)
])
])]
}
});
const driver = TestBed.get(AnimationDriver) as MockAnimationDriver;
const fixture = TestBed.createComponent(DummyIfCmp);
const cmp = fixture.componentInstance;
cmp.exp = 'a';
fixture.detectChanges();
flushMicrotasks();
driver.log = [];
cmp.exp = 'b';
fixture.detectChanges();
flushMicrotasks();
const animation = driver.log[0];
expect(animation['previousStyles']).toEqual({opacity: '0', fontSize: '*'});
}));
it('should perform a state change even if there is no transition that is found',
fakeAsync(() => {
TestBed.overrideComponent(DummyIfCmp, {

View File

@ -5,8 +5,7 @@
* 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 {AnimationPlayer} from '@angular/core';
import {AUTO_STYLE, AnimationPlayer} from '@angular/core';
export class MockAnimationPlayer implements AnimationPlayer {
private _onDoneFns: Function[] = [];
@ -16,8 +15,21 @@ export class MockAnimationPlayer implements AnimationPlayer {
private _started = false;
public parentPlayer: AnimationPlayer = null;
public previousStyles: {[styleName: string]: string | number} = {};
public log: any[] /** TODO #9100 */ = [];
public log: any[] = [];
constructor(
public startingStyles: {[key: string]: string | number} = {},
public keyframes: Array<[number, {[style: string]: string | number}]> = [],
previousPlayers: AnimationPlayer[] = []) {
previousPlayers.forEach(player => {
if (player instanceof MockAnimationPlayer) {
const styles = player._captureStyles();
Object.keys(styles).forEach(prop => this.previousStyles[prop] = styles[prop]);
}
});
}
private _onFinish(): void {
if (!this._finished) {
@ -67,6 +79,32 @@ export class MockAnimationPlayer implements AnimationPlayer {
}
}
setPosition(p: any /** TODO #9100 */): void {}
setPosition(p: number): void {}
getPosition(): number { return 0; }
private _captureStyles(): {[styleName: string]: string | number} {
const captures: {[prop: string]: string | number} = {};
if (this.hasStarted()) {
// when assembling the captured styles, it's important that
// we build the keyframe styles in the following order:
// {startingStyles, ... other styles within keyframes, ... previousStyles }
Object.keys(this.startingStyles).forEach(prop => {
captures[prop] = this.startingStyles[prop];
});
this.keyframes.forEach(kf => {
const [offset, styles] = kf;
const newStyles: {[prop: string]: string | number} = {};
Object.keys(styles).forEach(
prop => { captures[prop] = this._finished ? styles[prop] : AUTO_STYLE; });
});
}
Object.keys(this.previousStyles).forEach(prop => {
captures[prop] = this.previousStyles[prop];
});
return captures;
}
}