Skip to content

Commit

Permalink
feat(animations): retain styling when transition destinations are cha…
Browse files Browse the repository at this point in the history
…nged

Closes angular#9661
Closes angular#12208
  • Loading branch information
matsko committed Oct 17, 2016
1 parent 33c8948 commit 7e76f27
Show file tree
Hide file tree
Showing 19 changed files with 301 additions and 48 deletions.
19 changes: 13 additions & 6 deletions modules/@angular/compiler/src/animation/animation_compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var _ANIMATION_TIME_VAR = o.variable('totalTime');
var _ANIMATION_START_STATE_STYLES_VAR = o.variable('startStateStyles');
var _ANIMATION_END_STATE_STYLES_VAR = o.variable('endStateStyles');
var _ANIMATION_COLLECTED_STYLES = o.variable('collectedStyles');
const _PREVIOUS_ANIMATION_STYLE_STATE = o.variable('previousState');
var EMPTY_MAP = o.literalMap([]);

class _AnimationBuilder implements AnimationAstVisitor {
Expand Down Expand Up @@ -110,10 +111,15 @@ class _AnimationBuilder implements AnimationAstVisitor {
_callAnimateMethod(
ast: AnimationStepAst, startingStylesExpr: any, keyframesExpr: any,
context: _AnimationBuilderContext) {
var previousStylesValue: o.Expression = o.NULL_EXPR;
if (context.isExpectingFirstAnimateStep) {
previousStylesValue = _PREVIOUS_ANIMATION_STYLE_STATE;
context.isExpectingFirstAnimateStep = false;
}
context.totalTransitionTime += ast.duration + ast.delay;
return _ANIMATION_FACTORY_RENDERER_VAR.callMethod('animate', [
_ANIMATION_FACTORY_ELEMENT_VAR, startingStylesExpr, keyframesExpr, o.literal(ast.duration),
o.literal(ast.delay), o.literal(ast.easing)
o.literal(ast.delay), o.literal(ast.easing), previousStylesValue
]);
}

Expand Down Expand Up @@ -150,6 +156,7 @@ class _AnimationBuilder implements AnimationAstVisitor {

context.totalTransitionTime = 0;
context.isExpectingFirstStyleStep = true;
context.isExpectingFirstAnimateStep = true;

var stateChangePreconditions: o.Expression[] = [];

Expand Down Expand Up @@ -187,15 +194,14 @@ class _AnimationBuilder implements AnimationAstVisitor {
context.stateMap.registerState(DEFAULT_STATE, {});

var statements: o.Statement[] = [];
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
.callMethod(
statements.push(_PREVIOUS_ANIMATION_STYLE_STATE
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
'cancelActiveAnimation',
[
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
])
.toStmt());

]))
.toDeclStmt());

statements.push(_ANIMATION_COLLECTED_STYLES.set(EMPTY_MAP).toDeclStmt());
statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt());
Expand Down Expand Up @@ -321,6 +327,7 @@ class _AnimationBuilderContext {
stateMap = new _AnimationBuilderStateMap();
endStateAnimateStep: AnimationStepAst = null;
isExpectingFirstStyleStep = false;
isExpectingFirstAnimateStep = false;
totalTransitionTime = 0;
}

Expand Down
2 changes: 2 additions & 0 deletions modules/@angular/core/src/animation/animation_group_player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,6 @@ export class AnimationGroupPlayer implements AnimationPlayer {
});
return min;
}

captureStyles(): any[] { return this._players.map(player => player.captureStyles()); }
}
2 changes: 2 additions & 0 deletions modules/@angular/core/src/animation/animation_player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export abstract class AnimationPlayer {
abstract getPosition(): number;
get parentPlayer(): AnimationPlayer { throw new Error('NOT IMPLEMENTED: Base Class'); }
set parentPlayer(player: AnimationPlayer) { throw new Error('NOT IMPLEMENTED: Base Class'); }
captureStyles?: () => any[];
}

export class NoOpAnimationPlayer implements AnimationPlayer {
Expand Down Expand Up @@ -58,4 +59,5 @@ export class NoOpAnimationPlayer implements AnimationPlayer {
reset(): void {}
setPosition(p: any /** TODO #9100 */): void {}
getPosition(): number { return 0; }
captureStyles(): any[] { return []; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,6 @@ export class AnimationSequencePlayer implements AnimationPlayer {
setPosition(p: any /** TODO #9100 */): void { this._players[0].setPosition(p); }

getPosition(): number { return this._players[0].getPosition(); }

captureStyles(): any[] { return this._players.map(player => player.captureStyles()); }
}
2 changes: 2 additions & 0 deletions modules/@angular/core/src/animation/animation_style_util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export function balanceAnimationKeyframes(
firstKeyframe.styles.styles.push(extraFirstKeyframeStyles);
}

collectAndResolveStyles(collectedStyles, [finalStateStyles]);

return keyframes;
}

Expand Down
8 changes: 5 additions & 3 deletions modules/@angular/core/src/debug/debug_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -149,7 +149,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,
previousStyles: {[key: string]: string | number}[] = []): AnimationPlayer {
return this._delegate.animate(
element, startingStyles, keyframes, duration, delay, easing, previousStyles);
}
}
13 changes: 9 additions & 4 deletions modules/@angular/core/src/linker/animation_view_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,30 @@ export class AnimationViewContext {
queueAnimationGlobally(player);

this._players.set(element, animationName, player);

player.onStart(() => this._triggerOutputHandler(element, animationName, 'start', event));
player.onDone(() => {
// TODO: add codegen to remove the need to store these values
this._triggerOutputHandler(element, animationName, 'done', event);
this._players.remove(element, animationName);
});

player.onStart(() => this._triggerOutputHandler(element, animationName, 'start', event));
}

cancelActiveAnimation(element: any, animationName: string, removeAllAnimations: boolean = false):
void {
any {
var capturedStyles: any = null;
if (removeAllAnimations) {
this._players.findAllPlayersByElement(element).forEach(player => player.destroy());
} else {
var player = this._players.find(element, animationName);
const player = this._players.find(element, animationName);
if (player) {
if (player.captureStyles) {
capturedStyles = player.captureStyles();
}
player.destroy();
}
}
return capturedStyles;
}

registerOutputHandler(
Expand Down
3 changes: 2 additions & 1 deletion modules/@angular/core/src/render/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,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,
previousStyles?: {[key: string]: string | number}[]): AnimationPlayer;
}

/**
Expand Down
46 changes: 46 additions & 0 deletions modules/@angular/core/test/animation/animation_integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,8 @@ function declareTests({useJit}: {useJit: boolean}) {
var animation = driver.log.pop();
var kf = animation['keyframeLookup'];
expect(kf[1]).toEqual([1, {'background': 'green'}]);
var player = animation['player'];
player.finish();

cmp.exp = 'blue';
fixture.detectChanges();
Expand All @@ -1688,6 +1690,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();
Expand All @@ -1697,6 +1701,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();
Expand All @@ -1706,6 +1712,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',
Expand Down Expand Up @@ -1736,6 +1744,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, {
Expand Down
36 changes: 35 additions & 1 deletion modules/@angular/core/testing/mock_animation_player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/

import {AnimationPlayer} from '@angular/core';
import {AUTO_STYLE, AnimationPlayer} from '@angular/core';

import {isPresent} from './facade/lang';

export class MockAnimationPlayer implements AnimationPlayer {
Expand All @@ -20,6 +21,11 @@ export class MockAnimationPlayer implements AnimationPlayer {

public log: any[] /** TODO #9100 */ = [];

constructor(
public startingStyles: {[key: string]: string | number} = {},
public keyframes: Array<[number, {[style: string]: string | number}]> = [],
public previousStyles: {[style: string]: string | number} = {}) {}

private _onFinish(): void {
if (!this._finished) {
this._finished = true;
Expand Down Expand Up @@ -68,4 +74,32 @@ export class MockAnimationPlayer implements AnimationPlayer {

setPosition(p: any /** TODO #9100 */): void {}
getPosition(): number { return 0; }

captureStyles(): any[] {
if (!this.hasStarted()) return [];

// when assembling the captured styles, it's important that
// we build the keyframe styles in the following order:
// {startingStyles, ... other styles within keyframes, ... previousStyles }
var captures: {[prop: string]: string | number} = {};
Object.keys(this.startingStyles).forEach(prop => {
captures[prop] = this.startingStyles[prop];
});

this.keyframes.forEach(kf => {
let [offset, styles] = kf;
var newStyles: {[prop: string]: string | number} = {};
Object.keys(styles).forEach(prop => {
if (prop != 'offset') {
captures[prop] = this._finished ? styles[prop] : AUTO_STYLE;
}
});
});

Object.keys(this.previousStyles).forEach(prop => {
captures[prop] = this.previousStyles[prop];
});

return [captures];
}
}
6 changes: 4 additions & 2 deletions modules/@angular/platform-browser/src/dom/animation_driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {AnimationKeyframe, AnimationStyles, NoOpAnimationPlayer} from '../privat
class _NoOpAnimationDriver implements AnimationDriver {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
duration: number, delay: number, easing: string,
previousStyles: {[style: string]: string | number}[] = []): AnimationPlayer {
return new NoOpAnimationPlayer();
}
}
Expand All @@ -25,5 +26,6 @@ export abstract class AnimationDriver {
static NOOP: AnimationDriver = new _NoOpAnimationDriver();
abstract animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer;
duration: number, delay: number, easing: string,
previousStyles?: {[style: string]: string | number}[]): AnimationPlayer;
}
8 changes: 5 additions & 3 deletions modules/@angular/platform-browser/src/dom/dom_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import {Inject, Injectable, RenderComponentType, Renderer, RootRenderer, ViewEncapsulation} from '@angular/core';

import {Json, isArray, isBlank, isPresent, isString, stringify} from '../facade/lang';
import {AnimationKeyframe, AnimationPlayer, AnimationStyles, RenderDebugInfo} from '../private_import_core';

Expand Down Expand Up @@ -51,7 +52,7 @@ export class DomRootRenderer_ extends DomRootRenderer {
}
}

export class DomRenderer implements Renderer {
export class DomRenderer {
private _contentAttr: string;
private _hostAttr: string;
private _styles: string[];
Expand Down Expand Up @@ -227,9 +228,10 @@ export class DomRenderer implements Renderer {

animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
duration: number, delay: number, easing: string,
previousStyles: {[style: string]: string | number}[] = []): AnimationPlayer {
return this._animationDriver.animate(
element, startingStyles, keyframes, duration, delay, easing);
element, startingStyles, keyframes, duration, delay, easing, previousStyles);
}
}

Expand Down
Loading

0 comments on commit 7e76f27

Please sign in to comment.