feat(animations): noop animation module and zone fixes (#14661)
This commit is contained in:
parent
ab3527c99b
commit
e8d2743cfb
|
@ -8,11 +8,9 @@
|
||||||
import {AUTO_STYLE, AnimationEvent, animate, keyframes, state, style, transition, trigger} from '@angular/animations';
|
import {AUTO_STYLE, AnimationEvent, animate, keyframes, state, style, transition, trigger} from '@angular/animations';
|
||||||
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
|
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
|
||||||
import {Component, HostBinding, HostListener, ViewChild} from '@angular/core';
|
import {Component, HostBinding, HostListener, ViewChild} from '@angular/core';
|
||||||
import {BrowserModule} from '@angular/platform-browser';
|
|
||||||
import {AnimationDriver, BrowserAnimationModule, ɵAnimationEngine} from '@angular/platform-browser/animations';
|
import {AnimationDriver, BrowserAnimationModule, ɵAnimationEngine} from '@angular/platform-browser/animations';
|
||||||
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/platform-browser/animations/testing';
|
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/platform-browser/animations/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
|
||||||
import {TestBed} from '../../testing';
|
import {TestBed} from '../../testing';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
|
@ -46,7 +44,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
resetLog();
|
resetLog();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}],
|
providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}],
|
||||||
imports: [BrowserModule, BrowserAnimationModule]
|
imports: [BrowserAnimationModule]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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, AnimationTriggerMetadata} from '@angular/animations';
|
||||||
|
|
||||||
|
export abstract class AnimationEngine {
|
||||||
|
abstract registerTrigger(trigger: AnimationTriggerMetadata): void;
|
||||||
|
abstract onInsert(element: any, domFn: () => any): void;
|
||||||
|
abstract onRemove(element: any, domFn: () => any): void;
|
||||||
|
abstract setProperty(element: any, property: string, value: any): void;
|
||||||
|
abstract listen(
|
||||||
|
element: any, eventName: string, eventPhase: string,
|
||||||
|
callback: (event: any) => any): () => any;
|
||||||
|
abstract flush(): void;
|
||||||
|
|
||||||
|
get activePlayers(): AnimationPlayer[] { throw new Error('...'); }
|
||||||
|
get queuedPlayers(): AnimationPlayer[] { throw new Error('...'); }
|
||||||
|
}
|
|
@ -12,5 +12,6 @@
|
||||||
* Entry point for all animation APIs of the animation browser package.
|
* Entry point for all animation APIs of the animation browser package.
|
||||||
*/
|
*/
|
||||||
export {BrowserAnimationModule} from './browser_animation_module';
|
export {BrowserAnimationModule} from './browser_animation_module';
|
||||||
|
export {NoopBrowserAnimationModule} from './noop_browser_animation_module';
|
||||||
export {AnimationDriver} from './render/animation_driver';
|
export {AnimationDriver} from './render/animation_driver';
|
||||||
export * from './private_export';
|
export * from './private_export';
|
||||||
|
|
|
@ -5,18 +5,19 @@
|
||||||
* 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 {Injectable, NgModule, RendererFactoryV2} from '@angular/core';
|
import {Injectable, NgModule, NgZone, RendererFactoryV2} from '@angular/core';
|
||||||
import {BrowserModule, ɵDomRendererFactoryV2} from '@angular/platform-browser';
|
import {BrowserModule, ɵDomRendererFactoryV2} from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import {AnimationEngine} from './animation_engine';
|
||||||
import {AnimationStyleNormalizer} from './dsl/style_normalization/animation_style_normalizer';
|
import {AnimationStyleNormalizer} from './dsl/style_normalization/animation_style_normalizer';
|
||||||
import {WebAnimationsStyleNormalizer} from './dsl/style_normalization/web_animations_style_normalizer';
|
import {WebAnimationsStyleNormalizer} from './dsl/style_normalization/web_animations_style_normalizer';
|
||||||
import {AnimationDriver, NoOpAnimationDriver} from './render/animation_driver';
|
import {AnimationDriver, NoOpAnimationDriver} from './render/animation_driver';
|
||||||
import {AnimationEngine} from './render/animation_engine';
|
|
||||||
import {AnimationRendererFactory} from './render/animation_renderer';
|
import {AnimationRendererFactory} from './render/animation_renderer';
|
||||||
|
import {DomAnimationEngine} from './render/dom_animation_engine';
|
||||||
import {WebAnimationsDriver, supportsWebAnimations} from './render/web_animations/web_animations_driver';
|
import {WebAnimationsDriver, supportsWebAnimations} from './render/web_animations/web_animations_driver';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InjectableAnimationEngine extends AnimationEngine {
|
export class InjectableAnimationEngine extends DomAnimationEngine {
|
||||||
constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) {
|
constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) {
|
||||||
super(driver, normalizer);
|
super(driver, normalizer);
|
||||||
}
|
}
|
||||||
|
@ -34,8 +35,8 @@ export function instantiateDefaultStyleNormalizer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function instantiateRendererFactory(
|
export function instantiateRendererFactory(
|
||||||
renderer: ɵDomRendererFactoryV2, engine: AnimationEngine) {
|
renderer: ɵDomRendererFactoryV2, engine: AnimationEngine, zone: NgZone) {
|
||||||
return new AnimationRendererFactory(renderer, engine);
|
return new AnimationRendererFactory(renderer, engine, zone);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,7 +50,7 @@ export function instantiateRendererFactory(
|
||||||
{provide: AnimationEngine, useClass: InjectableAnimationEngine}, {
|
{provide: AnimationEngine, useClass: InjectableAnimationEngine}, {
|
||||||
provide: RendererFactoryV2,
|
provide: RendererFactoryV2,
|
||||||
useFactory: instantiateRendererFactory,
|
useFactory: instantiateRendererFactory,
|
||||||
deps: [ɵDomRendererFactoryV2, AnimationEngine]
|
deps: [ɵDomRendererFactoryV2, AnimationEngine, NgZone]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import {AnimationMetadata, AnimationPlayer, AnimationStyleMetadata, sequence, ɵStyleData} from '@angular/animations';
|
import {AnimationMetadata, AnimationPlayer, AnimationStyleMetadata, sequence, ɵStyleData} from '@angular/animations';
|
||||||
|
|
||||||
import {AnimationDriver} from '../render/animation_driver';
|
import {AnimationDriver} from '../render/animation_driver';
|
||||||
import {AnimationEngine} from '../render/animation_engine';
|
import {DomAnimationEngine} from '../render/dom_animation_engine';
|
||||||
import {normalizeStyles} from '../util';
|
import {normalizeStyles} from '../util';
|
||||||
|
|
||||||
import {AnimationTimelineInstruction} from './animation_timeline_instruction';
|
import {AnimationTimelineInstruction} from './animation_timeline_instruction';
|
||||||
|
@ -49,7 +49,7 @@ export class Animation {
|
||||||
// within core then the code below will interact with Renderer.transition(...))
|
// within core then the code below will interact with Renderer.transition(...))
|
||||||
const driver: AnimationDriver = injector.get(AnimationDriver);
|
const driver: AnimationDriver = injector.get(AnimationDriver);
|
||||||
const normalizer: AnimationStyleNormalizer = injector.get(AnimationStyleNormalizer);
|
const normalizer: AnimationStyleNormalizer = injector.get(AnimationStyleNormalizer);
|
||||||
const engine = new AnimationEngine(driver, normalizer);
|
const engine = new DomAnimationEngine(driver, normalizer);
|
||||||
return engine.animateTimeline(element, instructions);
|
return engine.animateTimeline(element, instructions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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 {NgModule, NgZone, RendererFactoryV2} from '@angular/core';
|
||||||
|
import {BrowserModule, ɵDomRendererFactoryV2} from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import {AnimationEngine} from './animation_engine';
|
||||||
|
import {AnimationRendererFactory} from './render/animation_renderer';
|
||||||
|
import {NoopAnimationEngine} from './render/noop_animation_engine';
|
||||||
|
|
||||||
|
export function instantiateRendererFactory(
|
||||||
|
renderer: ɵDomRendererFactoryV2, engine: AnimationEngine, zone: NgZone) {
|
||||||
|
return new AnimationRendererFactory(renderer, engine, zone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @experimental Animation support is experimental.
|
||||||
|
*/
|
||||||
|
@NgModule({
|
||||||
|
imports: [BrowserModule],
|
||||||
|
providers: [
|
||||||
|
{provide: AnimationEngine, useClass: NoopAnimationEngine}, {
|
||||||
|
provide: RendererFactoryV2,
|
||||||
|
useFactory: instantiateRendererFactory,
|
||||||
|
deps: [ɵDomRendererFactoryV2, AnimationEngine, NgZone]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class NoopBrowserAnimationModule {
|
||||||
|
}
|
|
@ -5,7 +5,9 @@
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
|
export {AnimationEngine as ɵAnimationEngine} from './animation_engine';
|
||||||
export {Animation as ɵAnimation} from './dsl/animation';
|
export {Animation as ɵAnimation} from './dsl/animation';
|
||||||
export {AnimationStyleNormalizer as ɵAnimationStyleNormalizer} from './dsl/style_normalization/animation_style_normalizer';
|
export {AnimationStyleNormalizer as ɵAnimationStyleNormalizer, NoOpAnimationStyleNormalizer as ɵNoOpAnimationStyleNormalizer} from './dsl/style_normalization/animation_style_normalizer';
|
||||||
export {AnimationEngine as ɵAnimationEngine} from './render/animation_engine';
|
export {NoOpAnimationDriver as ɵNoOpAnimationDriver} from './render/animation_driver';
|
||||||
export {AnimationRenderer as ɵAnimationRenderer, AnimationRendererFactory as ɵAnimationRendererFactory} from './render/animation_renderer';
|
export {AnimationRenderer as ɵAnimationRenderer, AnimationRendererFactory as ɵAnimationRendererFactory} from './render/animation_renderer';
|
||||||
|
export {DomAnimationEngine as ɵDomAnimationEngine} from './render/dom_animation_engine';
|
||||||
|
|
|
@ -6,13 +6,15 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {AnimationTriggerMetadata} from '@angular/animations';
|
import {AnimationTriggerMetadata} from '@angular/animations';
|
||||||
import {Injectable, RendererFactoryV2, RendererTypeV2, RendererV2} from '@angular/core';
|
import {Injectable, NgZone, RendererFactoryV2, RendererTypeV2, RendererV2} from '@angular/core';
|
||||||
|
|
||||||
import {AnimationEngine} from './animation_engine';
|
import {AnimationEngine} from '../animation_engine';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AnimationRendererFactory implements RendererFactoryV2 {
|
export class AnimationRendererFactory implements RendererFactoryV2 {
|
||||||
constructor(private delegate: RendererFactoryV2, private _engine: AnimationEngine) {}
|
constructor(
|
||||||
|
private delegate: RendererFactoryV2, private _engine: AnimationEngine,
|
||||||
|
private _zone: NgZone) {}
|
||||||
|
|
||||||
createRenderer(hostElement: any, type: RendererTypeV2): RendererV2 {
|
createRenderer(hostElement: any, type: RendererTypeV2): RendererV2 {
|
||||||
let delegate = this.delegate.createRenderer(hostElement, type);
|
let delegate = this.delegate.createRenderer(hostElement, type);
|
||||||
|
@ -24,16 +26,17 @@ export class AnimationRendererFactory implements RendererFactoryV2 {
|
||||||
}
|
}
|
||||||
const animationTriggers = type.data['animation'] as AnimationTriggerMetadata[];
|
const animationTriggers = type.data['animation'] as AnimationTriggerMetadata[];
|
||||||
animationRenderer = (type.data as any)['__animationRenderer__'] =
|
animationRenderer = (type.data as any)['__animationRenderer__'] =
|
||||||
new AnimationRenderer(delegate, this._engine, animationTriggers);
|
new AnimationRenderer(delegate, this._engine, this._zone, animationTriggers);
|
||||||
return animationRenderer;
|
return animationRenderer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AnimationRenderer implements RendererV2 {
|
export class AnimationRenderer implements RendererV2 {
|
||||||
public destroyNode: (node: any) => (void|any) = null;
|
public destroyNode: (node: any) => (void|any) = null;
|
||||||
|
private _flushPromise: Promise<any> = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public delegate: RendererV2, private _engine: AnimationEngine,
|
public delegate: RendererV2, private _engine: AnimationEngine, private _zone: NgZone,
|
||||||
_triggers: AnimationTriggerMetadata[] = null) {
|
_triggers: AnimationTriggerMetadata[] = null) {
|
||||||
this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode(n) : null;
|
this.destroyNode = this.delegate.destroyNode ? (n) => delegate.destroyNode(n) : null;
|
||||||
if (_triggers) {
|
if (_triggers) {
|
||||||
|
@ -92,11 +95,13 @@ export class AnimationRenderer implements RendererV2 {
|
||||||
|
|
||||||
removeChild(parent: any, oldChild: any): void {
|
removeChild(parent: any, oldChild: any): void {
|
||||||
this._engine.onRemove(oldChild, () => this.delegate.removeChild(parent, oldChild));
|
this._engine.onRemove(oldChild, () => this.delegate.removeChild(parent, oldChild));
|
||||||
|
this._queueFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
setProperty(el: any, name: string, value: any): void {
|
setProperty(el: any, name: string, value: any): void {
|
||||||
if (name.charAt(0) == '@') {
|
if (name.charAt(0) == '@') {
|
||||||
this._engine.setProperty(el, name.substr(1), value);
|
this._engine.setProperty(el, name.substr(1), value);
|
||||||
|
this._queueFlush();
|
||||||
} else {
|
} else {
|
||||||
this.delegate.setProperty(el, name, value);
|
this.delegate.setProperty(el, name, value);
|
||||||
}
|
}
|
||||||
|
@ -107,10 +112,22 @@ export class AnimationRenderer implements RendererV2 {
|
||||||
if (eventName.charAt(0) == '@') {
|
if (eventName.charAt(0) == '@') {
|
||||||
const element = resolveElementFromTarget(target);
|
const element = resolveElementFromTarget(target);
|
||||||
const [name, phase] = parseTriggerCallbackName(eventName.substr(1));
|
const [name, phase] = parseTriggerCallbackName(eventName.substr(1));
|
||||||
return this._engine.listen(element, name, phase, callback);
|
return this._engine.listen(
|
||||||
|
element, name, phase, (event: any) => this._zone.run(() => callback(event)));
|
||||||
}
|
}
|
||||||
return this.delegate.listen(target, eventName, callback);
|
return this.delegate.listen(target, eventName, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _queueFlush() {
|
||||||
|
if (!this._flushPromise) {
|
||||||
|
this._zone.runOutsideAngular(() => {
|
||||||
|
this._flushPromise = Promise.resolve(null).then(() => {
|
||||||
|
this._flushPromise = null;
|
||||||
|
this._engine.flush();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveElementFromTarget(target: 'window' | 'document' | 'body' | any): any {
|
function resolveElementFromTarget(target: 'window' | 'document' | 'body' | any): any {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {AnimationTimelineInstruction} from '../dsl/animation_timeline_instructio
|
||||||
import {AnimationTransitionInstruction} from '../dsl/animation_transition_instruction';
|
import {AnimationTransitionInstruction} from '../dsl/animation_transition_instruction';
|
||||||
import {AnimationTrigger, buildTrigger} from '../dsl/animation_trigger';
|
import {AnimationTrigger, buildTrigger} from '../dsl/animation_trigger';
|
||||||
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
|
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
|
||||||
|
import {eraseStyles, setStyles} from '../util';
|
||||||
|
|
||||||
import {AnimationDriver} from './animation_driver';
|
import {AnimationDriver} from './animation_driver';
|
||||||
|
|
||||||
|
@ -20,7 +21,6 @@ export interface QueuedAnimationTransitionTuple {
|
||||||
triggerName: string;
|
triggerName: string;
|
||||||
event: AnimationEvent;
|
event: AnimationEvent;
|
||||||
}
|
}
|
||||||
;
|
|
||||||
|
|
||||||
export interface TriggerListenerTuple {
|
export interface TriggerListenerTuple {
|
||||||
triggerName: string;
|
triggerName: string;
|
||||||
|
@ -31,7 +31,7 @@ export interface TriggerListenerTuple {
|
||||||
const MARKED_FOR_ANIMATION = 'ng-animate';
|
const MARKED_FOR_ANIMATION = 'ng-animate';
|
||||||
const MARKED_FOR_REMOVAL = '$$ngRemove';
|
const MARKED_FOR_REMOVAL = '$$ngRemove';
|
||||||
|
|
||||||
export class AnimationEngine {
|
export class DomAnimationEngine {
|
||||||
private _flaggedInserts = new Set<any>();
|
private _flaggedInserts = new Set<any>();
|
||||||
private _queuedRemovals = new Map<any, () => any>();
|
private _queuedRemovals = new Map<any, () => any>();
|
||||||
private _queuedTransitionAnimations: QueuedAnimationTransitionTuple[] = [];
|
private _queuedTransitionAnimations: QueuedAnimationTransitionTuple[] = [];
|
||||||
|
@ -43,11 +43,6 @@ export class AnimationEngine {
|
||||||
private _triggers: {[triggerName: string]: AnimationTrigger} = {};
|
private _triggers: {[triggerName: string]: AnimationTrigger} = {};
|
||||||
private _triggerListeners = new Map<any, TriggerListenerTuple[]>();
|
private _triggerListeners = new Map<any, TriggerListenerTuple[]>();
|
||||||
|
|
||||||
private _flushId = 0;
|
|
||||||
private _awaitingFlush = false;
|
|
||||||
|
|
||||||
static raf = (fn: () => any): any => { return requestAnimationFrame(fn); };
|
|
||||||
|
|
||||||
constructor(private _driver: AnimationDriver, private _normalizer: AnimationStyleNormalizer) {}
|
constructor(private _driver: AnimationDriver, private _normalizer: AnimationStyleNormalizer) {}
|
||||||
|
|
||||||
get queuedPlayers(): AnimationPlayer[] {
|
get queuedPlayers(): AnimationPlayer[] {
|
||||||
|
@ -60,7 +55,7 @@ export class AnimationEngine {
|
||||||
return players;
|
return players;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerTrigger(trigger: AnimationTriggerMetadata) {
|
registerTrigger(trigger: AnimationTriggerMetadata): void {
|
||||||
const name = trigger.name;
|
const name = trigger.name;
|
||||||
if (this._triggers[name]) {
|
if (this._triggers[name]) {
|
||||||
throw new Error(`The provided animation trigger "${name}" has already been registered!`);
|
throw new Error(`The provided animation trigger "${name}" has already been registered!`);
|
||||||
|
@ -271,16 +266,6 @@ export class AnimationEngine {
|
||||||
|
|
||||||
element.classList.add(MARKED_FOR_ANIMATION);
|
element.classList.add(MARKED_FOR_ANIMATION);
|
||||||
player.onDone(() => { element.classList.remove(MARKED_FOR_ANIMATION); });
|
player.onDone(() => { element.classList.remove(MARKED_FOR_ANIMATION); });
|
||||||
|
|
||||||
if (!this._awaitingFlush) {
|
|
||||||
const flushId = this._flushId;
|
|
||||||
AnimationEngine.raf(() => {
|
|
||||||
if (flushId == this._flushId) {
|
|
||||||
this._awaitingFlush = false;
|
|
||||||
this.flush();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _flushQueuedAnimations() {
|
private _flushQueuedAnimations() {
|
||||||
|
@ -323,7 +308,6 @@ export class AnimationEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
flush() {
|
flush() {
|
||||||
this._flushId++;
|
|
||||||
this._flushQueuedAnimations();
|
this._flushQueuedAnimations();
|
||||||
|
|
||||||
let flushAgain = false;
|
let flushAgain = false;
|
||||||
|
@ -406,18 +390,6 @@ function deleteFromArrayMap(map: Map<any, any[]>, key: any, value: any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setStyles(element: any, styles: ɵStyleData) {
|
|
||||||
Object.keys(styles).forEach(prop => { element.style[prop] = styles[prop]; });
|
|
||||||
}
|
|
||||||
|
|
||||||
function eraseStyles(element: any, styles: ɵStyleData) {
|
|
||||||
Object.keys(styles).forEach(prop => {
|
|
||||||
// IE requires '' instead of null
|
|
||||||
// see https://github.com/angular/angular/issues/7916
|
|
||||||
element.style[prop] = '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function optimizeGroupPlayer(players: AnimationPlayer[]): AnimationPlayer {
|
function optimizeGroupPlayer(players: AnimationPlayer[]): AnimationPlayer {
|
||||||
switch (players.length) {
|
switch (players.length) {
|
||||||
case 0:
|
case 0:
|
|
@ -0,0 +1,158 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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 {AnimationEvent, AnimationMetadataType, AnimationPlayer, AnimationStateMetadata, AnimationTriggerMetadata, ɵStyleData} from '@angular/animations';
|
||||||
|
|
||||||
|
import {AnimationEngine} from '../animation_engine';
|
||||||
|
import {copyStyles, eraseStyles, normalizeStyles, setStyles} from '../util';
|
||||||
|
|
||||||
|
interface ListenerTuple {
|
||||||
|
eventPhase: string;
|
||||||
|
triggerName: string;
|
||||||
|
callback: (event: any) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChangeTuple {
|
||||||
|
element: any;
|
||||||
|
triggerName: string;
|
||||||
|
oldValue: string;
|
||||||
|
newValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_STATE_VALUE = 'void';
|
||||||
|
const DEFAULT_STATE_STYLES = '*';
|
||||||
|
|
||||||
|
export class NoopAnimationEngine extends AnimationEngine {
|
||||||
|
private _listeners = new Map<any, ListenerTuple[]>();
|
||||||
|
private _changes: ChangeTuple[] = [];
|
||||||
|
private _flaggedRemovals = new Set<any>();
|
||||||
|
private _onDoneFns: (() => any)[] = [];
|
||||||
|
private _triggerStyles: {[triggerName: string]: {[stateName: string]: ɵStyleData}} = {};
|
||||||
|
|
||||||
|
registerTrigger(trigger: AnimationTriggerMetadata): void {
|
||||||
|
const stateMap: {[stateName: string]: ɵStyleData} = {};
|
||||||
|
trigger.definitions.forEach(def => {
|
||||||
|
if (def.type === AnimationMetadataType.State) {
|
||||||
|
const stateDef = def as AnimationStateMetadata;
|
||||||
|
stateMap[stateDef.name] = normalizeStyles(stateDef.styles.styles);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._triggerStyles[trigger.name] = stateMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
onInsert(element: any, domFn: () => any): void { domFn(); }
|
||||||
|
|
||||||
|
onRemove(element: any, domFn: () => any): void {
|
||||||
|
domFn();
|
||||||
|
this._flaggedRemovals.add(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
setProperty(element: any, property: string, value: any): void {
|
||||||
|
const storageProp = makeStorageProp(property);
|
||||||
|
const oldValue = element[storageProp] || DEFAULT_STATE_VALUE;
|
||||||
|
this._changes.push(<ChangeTuple>{element, oldValue, newValue: value, triggerName: property});
|
||||||
|
|
||||||
|
const triggerStateStyles = this._triggerStyles[property] || {};
|
||||||
|
const fromStateStyles =
|
||||||
|
triggerStateStyles[oldValue] || triggerStateStyles[DEFAULT_STATE_STYLES];
|
||||||
|
if (fromStateStyles) {
|
||||||
|
eraseStyles(element, fromStateStyles);
|
||||||
|
}
|
||||||
|
|
||||||
|
element[storageProp] = value;
|
||||||
|
this._onDoneFns.push(() => {
|
||||||
|
const toStateStyles = triggerStateStyles[value] || triggerStateStyles[DEFAULT_STATE_STYLES];
|
||||||
|
if (toStateStyles) {
|
||||||
|
setStyles(element, toStateStyles);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(element: any, eventName: string, eventPhase: string, callback: (event: any) => any):
|
||||||
|
() => any {
|
||||||
|
let listeners = this._listeners.get(element);
|
||||||
|
if (!listeners) {
|
||||||
|
this._listeners.set(element, listeners = []);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tuple = <ListenerTuple>{triggerName: eventName, eventPhase, callback};
|
||||||
|
listeners.push(tuple);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const index = listeners.indexOf(tuple);
|
||||||
|
if (index >= 0) {
|
||||||
|
listeners.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
flush(): void {
|
||||||
|
const onStartCallbacks: (() => any)[] = [];
|
||||||
|
const onDoneCallbacks: (() => any)[] = [];
|
||||||
|
|
||||||
|
function handleListener(listener: ListenerTuple, data: ChangeTuple) {
|
||||||
|
const phase = listener.eventPhase;
|
||||||
|
const event = makeAnimationEvent(
|
||||||
|
data.element, data.triggerName, data.oldValue, data.newValue, phase, 0);
|
||||||
|
if (phase == 'start') {
|
||||||
|
onStartCallbacks.push(() => listener.callback(event));
|
||||||
|
} else if (phase == 'done') {
|
||||||
|
onDoneCallbacks.push(() => listener.callback(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._changes.forEach(change => {
|
||||||
|
const element = change.element;
|
||||||
|
const listeners = this._listeners.get(element);
|
||||||
|
if (listeners) {
|
||||||
|
listeners.forEach(listener => {
|
||||||
|
if (listener.triggerName == change.triggerName) {
|
||||||
|
handleListener(listener, change);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// upon removal ALL the animation triggers need to get fired
|
||||||
|
this._flaggedRemovals.forEach(element => {
|
||||||
|
const listeners = this._listeners.get(element);
|
||||||
|
if (listeners) {
|
||||||
|
listeners.forEach(listener => {
|
||||||
|
const triggerName = listener.triggerName;
|
||||||
|
const storageProp = makeStorageProp(triggerName);
|
||||||
|
handleListener(listener, <ChangeTuple>{
|
||||||
|
element: element,
|
||||||
|
triggerName: triggerName,
|
||||||
|
oldValue: element[storageProp] || DEFAULT_STATE_VALUE,
|
||||||
|
newValue: DEFAULT_STATE_VALUE
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onStartCallbacks.forEach(fn => fn());
|
||||||
|
onDoneCallbacks.forEach(fn => fn());
|
||||||
|
this._flaggedRemovals.clear();
|
||||||
|
this._changes = [];
|
||||||
|
|
||||||
|
this._onDoneFns.forEach(doneFn => doneFn());
|
||||||
|
this._onDoneFns = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get activePlayers(): AnimationPlayer[] { return []; }
|
||||||
|
get queuedPlayers(): AnimationPlayer[] { return []; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeAnimationEvent(
|
||||||
|
element: any, triggerName: string, fromState: string, toState: string, phaseName: string,
|
||||||
|
totalTime: number): AnimationEvent {
|
||||||
|
return <AnimationEvent>{element, triggerName, fromState, toState, phaseName, totalTime};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeStorageProp(property: string): string {
|
||||||
|
return '_@_' + property;
|
||||||
|
}
|
|
@ -69,3 +69,19 @@ export function copyStyles(
|
||||||
}
|
}
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setStyles(element: any, styles: ɵStyleData) {
|
||||||
|
if (element['style']) {
|
||||||
|
Object.keys(styles).forEach(prop => element.style[prop] = styles[prop]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function eraseStyles(element: any, styles: ɵStyleData) {
|
||||||
|
if (element['style']) {
|
||||||
|
Object.keys(styles).forEach(prop => {
|
||||||
|
// IE requires '' instead of null
|
||||||
|
// see https://github.com/angular/angular/issues/7916
|
||||||
|
element.style[prop] = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {el} from '@angular/platform-browser/testing/browser_util';
|
||||||
import {buildAnimationKeyframes} from '../../src/dsl/animation_timeline_visitor';
|
import {buildAnimationKeyframes} from '../../src/dsl/animation_timeline_visitor';
|
||||||
import {buildTrigger} from '../../src/dsl/animation_trigger';
|
import {buildTrigger} from '../../src/dsl/animation_trigger';
|
||||||
import {AnimationStyleNormalizer, NoOpAnimationStyleNormalizer} from '../../src/dsl/style_normalization/animation_style_normalizer';
|
import {AnimationStyleNormalizer, NoOpAnimationStyleNormalizer} from '../../src/dsl/style_normalization/animation_style_normalizer';
|
||||||
import {AnimationEngine} from '../../src/render/animation_engine';
|
import {DomAnimationEngine} from '../../src/render/dom_animation_engine';
|
||||||
import {MockAnimationDriver, MockAnimationPlayer} from '../../testing/mock_animation_driver';
|
import {MockAnimationDriver, MockAnimationPlayer} from '../../testing/mock_animation_driver';
|
||||||
|
|
||||||
function makeTrigger(name: string, steps: any) {
|
function makeTrigger(name: string, steps: any) {
|
||||||
|
@ -27,7 +27,7 @@ export function main() {
|
||||||
// these tests are only mean't to be run within the DOM
|
// these tests are only mean't to be run within the DOM
|
||||||
if (typeof Element == 'undefined') return;
|
if (typeof Element == 'undefined') return;
|
||||||
|
|
||||||
describe('AnimationEngine', () => {
|
describe('DomAnimationEngine', () => {
|
||||||
let element: any;
|
let element: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -36,7 +36,7 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
function makeEngine(normalizer: AnimationStyleNormalizer = null) {
|
function makeEngine(normalizer: AnimationStyleNormalizer = null) {
|
||||||
return new AnimationEngine(driver, normalizer || new NoOpAnimationStyleNormalizer());
|
return new DomAnimationEngine(driver, normalizer || new NoOpAnimationStyleNormalizer());
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('trigger registration', () => {
|
describe('trigger registration', () => {
|
||||||
|
@ -292,51 +292,6 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('flushing animations', () => {
|
|
||||||
let ticks: (() => any)[];
|
|
||||||
let _raf: () => any;
|
|
||||||
beforeEach(() => {
|
|
||||||
ticks = [];
|
|
||||||
_raf = <() => any>AnimationEngine.raf;
|
|
||||||
AnimationEngine.raf = (cb: () => any) => { ticks.push(cb); };
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => AnimationEngine.raf = _raf);
|
|
||||||
|
|
||||||
function flushTicks() {
|
|
||||||
ticks.forEach(tick => tick());
|
|
||||||
ticks = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should invoke queued transition animations after a requestAnimationFrame flushes', () => {
|
|
||||||
const engine = makeEngine();
|
|
||||||
engine.registerTrigger(trigger('myTrigger', [transition('* => *', animate(1234))]));
|
|
||||||
|
|
||||||
engine.setProperty(element, 'myTrigger', 'on');
|
|
||||||
expect(engine.queuedPlayers.length).toEqual(1);
|
|
||||||
expect(engine.activePlayers.length).toEqual(0);
|
|
||||||
|
|
||||||
flushTicks();
|
|
||||||
expect(engine.queuedPlayers.length).toEqual(0);
|
|
||||||
expect(engine.activePlayers.length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not flush the animations twice when flushed right away before a frame changes',
|
|
||||||
() => {
|
|
||||||
const engine = makeEngine();
|
|
||||||
engine.registerTrigger(trigger('myTrigger', [transition('* => *', animate(1234))]));
|
|
||||||
|
|
||||||
engine.setProperty(element, 'myTrigger', 'on');
|
|
||||||
expect(engine.activePlayers.length).toEqual(0);
|
|
||||||
|
|
||||||
engine.flush();
|
|
||||||
expect(engine.activePlayers.length).toEqual(1);
|
|
||||||
|
|
||||||
flushTicks();
|
|
||||||
expect(engine.activePlayers.length).toEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('instructions', () => {
|
describe('instructions', () => {
|
||||||
it('should animate a transition instruction', () => {
|
it('should animate a transition instruction', () => {
|
||||||
const engine = makeEngine();
|
const engine = makeEngine();
|
||||||
|
|
|
@ -0,0 +1,209 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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 {state, style, trigger} from '@angular/animations';
|
||||||
|
import {el} from '@angular/platform-browser/testing/browser_util';
|
||||||
|
|
||||||
|
import {NoopAnimationEngine} from '../src/render/noop_animation_engine';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('NoopAnimationEngine', () => {
|
||||||
|
let captures: string[] = [];
|
||||||
|
function capture(value: string = null) { return (v: any = null) => captures.push(value || v); }
|
||||||
|
|
||||||
|
beforeEach(() => { captures = []; });
|
||||||
|
|
||||||
|
it('should immediately issue DOM removals during remove animations and then fire the animation callbacks after flush',
|
||||||
|
() => {
|
||||||
|
const engine = new NoopAnimationEngine();
|
||||||
|
|
||||||
|
const elm1 = {};
|
||||||
|
const elm2 = {};
|
||||||
|
engine.onRemove(elm1, capture('1'));
|
||||||
|
engine.onRemove(elm2, capture('2'));
|
||||||
|
|
||||||
|
engine.listen(elm1, 'trig', 'start', capture('1-start'));
|
||||||
|
engine.listen(elm2, 'trig', 'start', capture('2-start'));
|
||||||
|
engine.listen(elm1, 'trig', 'done', capture('1-done'));
|
||||||
|
engine.listen(elm2, 'trig', 'done', capture('2-done'));
|
||||||
|
|
||||||
|
expect(captures).toEqual(['1', '2']);
|
||||||
|
engine.flush();
|
||||||
|
|
||||||
|
expect(captures).toEqual(['1', '2', '1-start', '2-start', '1-done', '2-done']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only fire the `start` listener for a trigger that has had a property change', () => {
|
||||||
|
const engine = new NoopAnimationEngine();
|
||||||
|
|
||||||
|
const elm1 = {};
|
||||||
|
const elm2 = {};
|
||||||
|
const elm3 = {};
|
||||||
|
|
||||||
|
engine.listen(elm1, 'trig1', 'start', capture());
|
||||||
|
engine.setProperty(elm1, 'trig1', 'cool');
|
||||||
|
engine.setProperty(elm2, 'trig2', 'sweet');
|
||||||
|
engine.listen(elm2, 'trig2', 'start', capture());
|
||||||
|
engine.listen(elm3, 'trig3', 'start', capture());
|
||||||
|
|
||||||
|
expect(captures).toEqual([]);
|
||||||
|
engine.flush();
|
||||||
|
|
||||||
|
expect(captures.length).toEqual(2);
|
||||||
|
const trig1Data = captures.shift();
|
||||||
|
const trig2Data = captures.shift();
|
||||||
|
expect(trig1Data).toEqual({
|
||||||
|
element: elm1,
|
||||||
|
triggerName: 'trig1',
|
||||||
|
fromState: 'void',
|
||||||
|
toState: 'cool',
|
||||||
|
phaseName: 'start',
|
||||||
|
totalTime: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(trig2Data).toEqual({
|
||||||
|
element: elm2,
|
||||||
|
triggerName: 'trig2',
|
||||||
|
fromState: 'void',
|
||||||
|
toState: 'sweet',
|
||||||
|
phaseName: 'start',
|
||||||
|
totalTime: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
captures = [];
|
||||||
|
engine.flush();
|
||||||
|
expect(captures).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only fire the `done` listener for a trigger that has had a property change', () => {
|
||||||
|
const engine = new NoopAnimationEngine();
|
||||||
|
|
||||||
|
const elm1 = {};
|
||||||
|
const elm2 = {};
|
||||||
|
const elm3 = {};
|
||||||
|
|
||||||
|
engine.listen(elm1, 'trig1', 'done', capture());
|
||||||
|
engine.setProperty(elm1, 'trig1', 'awesome');
|
||||||
|
engine.setProperty(elm2, 'trig2', 'amazing');
|
||||||
|
engine.listen(elm2, 'trig2', 'done', capture());
|
||||||
|
engine.listen(elm3, 'trig3', 'done', capture());
|
||||||
|
|
||||||
|
expect(captures).toEqual([]);
|
||||||
|
engine.flush();
|
||||||
|
|
||||||
|
expect(captures.length).toEqual(2);
|
||||||
|
const trig1Data = captures.shift();
|
||||||
|
const trig2Data = captures.shift();
|
||||||
|
expect(trig1Data).toEqual({
|
||||||
|
element: elm1,
|
||||||
|
triggerName: 'trig1',
|
||||||
|
fromState: 'void',
|
||||||
|
toState: 'awesome',
|
||||||
|
phaseName: 'done',
|
||||||
|
totalTime: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(trig2Data).toEqual({
|
||||||
|
element: elm2,
|
||||||
|
triggerName: 'trig2',
|
||||||
|
fromState: 'void',
|
||||||
|
toState: 'amazing',
|
||||||
|
phaseName: 'done',
|
||||||
|
totalTime: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
captures = [];
|
||||||
|
engine.flush();
|
||||||
|
expect(captures).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deregister a listener when the return function is called', () => {
|
||||||
|
const engine = new NoopAnimationEngine();
|
||||||
|
const elm = {};
|
||||||
|
|
||||||
|
const fn1 = engine.listen(elm, 'trig1', 'start', capture('trig1-start'));
|
||||||
|
const fn2 = engine.listen(elm, 'trig2', 'done', capture('trig2-done'));
|
||||||
|
|
||||||
|
engine.setProperty(elm, 'trig1', 'value1');
|
||||||
|
engine.setProperty(elm, 'trig2', 'value2');
|
||||||
|
engine.flush();
|
||||||
|
expect(captures).toEqual(['trig1-start', 'trig2-done']);
|
||||||
|
|
||||||
|
captures = [];
|
||||||
|
engine.setProperty(elm, 'trig1', 'value3');
|
||||||
|
engine.setProperty(elm, 'trig2', 'value4');
|
||||||
|
|
||||||
|
fn1();
|
||||||
|
engine.flush();
|
||||||
|
expect(captures).toEqual(['trig2-done']);
|
||||||
|
|
||||||
|
captures = [];
|
||||||
|
engine.setProperty(elm, 'trig1', 'value5');
|
||||||
|
engine.setProperty(elm, 'trig2', 'value6');
|
||||||
|
|
||||||
|
fn2();
|
||||||
|
engine.flush();
|
||||||
|
expect(captures).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('styling', () => {
|
||||||
|
// these tests are only mean't to be run within the DOM
|
||||||
|
if (typeof Element == 'undefined') return;
|
||||||
|
|
||||||
|
it('should persist the styles on the element when the animation is complete', () => {
|
||||||
|
const engine = new NoopAnimationEngine();
|
||||||
|
engine.registerTrigger(trigger('matias', [
|
||||||
|
state('a', style({width: '100px'})),
|
||||||
|
]));
|
||||||
|
|
||||||
|
const element = el('<div></div>');
|
||||||
|
expect(element.style.width).not.toEqual('100px');
|
||||||
|
|
||||||
|
engine.setProperty(element, 'matias', 'a');
|
||||||
|
expect(element.style.width).not.toEqual('100px');
|
||||||
|
|
||||||
|
engine.flush();
|
||||||
|
expect(element.style.width).toEqual('100px');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove previously persist styles off of the element when a follow-up animation starts',
|
||||||
|
() => {
|
||||||
|
const engine = new NoopAnimationEngine();
|
||||||
|
engine.registerTrigger(trigger('matias', [
|
||||||
|
state('a', style({width: '100px'})),
|
||||||
|
state('b', style({height: '100px'})),
|
||||||
|
]));
|
||||||
|
|
||||||
|
const element = el('<div></div>');
|
||||||
|
|
||||||
|
engine.setProperty(element, 'matias', 'a');
|
||||||
|
engine.flush();
|
||||||
|
expect(element.style.width).toEqual('100px');
|
||||||
|
|
||||||
|
engine.setProperty(element, 'matias', 'b');
|
||||||
|
expect(element.style.width).not.toEqual('100px');
|
||||||
|
expect(element.style.height).not.toEqual('100px');
|
||||||
|
|
||||||
|
engine.flush();
|
||||||
|
expect(element.style.height).toEqual('100px');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fall back to `*` styles incase the target state styles are not found', () => {
|
||||||
|
const engine = new NoopAnimationEngine();
|
||||||
|
engine.registerTrigger(trigger('matias', [
|
||||||
|
state('*', style({opacity: '0.5'})),
|
||||||
|
]));
|
||||||
|
|
||||||
|
const element = el('<div></div>');
|
||||||
|
|
||||||
|
engine.setProperty(element, 'matias', 'xyz');
|
||||||
|
engine.flush();
|
||||||
|
expect(element.style.opacity).toEqual('0.5');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* 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, state, style, transition, trigger} from '@angular/animations';
|
||||||
|
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {TestBed} from '@angular/core/testing';
|
||||||
|
import {ɵAnimationEngine} from '@angular/platform-browser/animations';
|
||||||
|
import {NoopBrowserAnimationModule} from '../src/noop_browser_animation_module';
|
||||||
|
import {NoopAnimationEngine} from '../src/render/noop_animation_engine';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('NoopBrowserAnimationModule', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({imports: [NoopBrowserAnimationModule]});
|
||||||
|
TestBed.configureCompiler({
|
||||||
|
useJit: true,
|
||||||
|
providers: [{
|
||||||
|
provide: USE_VIEW_ENGINE,
|
||||||
|
useValue: true,
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('the engine should be a Noop engine', () => {
|
||||||
|
const engine = TestBed.get(ɵAnimationEngine);
|
||||||
|
expect(engine instanceof NoopAnimationEngine).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should flush and fire callbacks when the zone becomes stable', (async) => {
|
||||||
|
@Component({
|
||||||
|
selector: 'my-cmp',
|
||||||
|
template:
|
||||||
|
'<div [@myAnimation]="exp" (@myAnimation.start)="onStart($event)" (@myAnimation.done)="onDone($event)"></div>',
|
||||||
|
animations: [trigger(
|
||||||
|
'myAnimation',
|
||||||
|
[transition(
|
||||||
|
'* => state', [style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
exp: any;
|
||||||
|
startEvent: any;
|
||||||
|
doneEvent: any;
|
||||||
|
onStart(event: any) { this.startEvent = event; }
|
||||||
|
onDone(event: any) { this.doneEvent = event; }
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||||
|
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
cmp.exp = 'state';
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(cmp.startEvent.triggerName).toEqual('myAnimation');
|
||||||
|
expect(cmp.startEvent.phaseName).toEqual('start');
|
||||||
|
expect(cmp.doneEvent.triggerName).toEqual('myAnimation');
|
||||||
|
expect(cmp.doneEvent.phaseName).toEqual('done');
|
||||||
|
async();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -5,12 +5,13 @@
|
||||||
* 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 {AnimationTriggerMetadata, trigger} from '@angular/animations';
|
import {AnimationTriggerMetadata, animate, state, style, transition, trigger} from '@angular/animations';
|
||||||
import {Injectable, RendererFactoryV2, RendererTypeV2} from '@angular/core';
|
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
|
||||||
|
import {Component, Injectable, RendererFactoryV2, RendererTypeV2} from '@angular/core';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {BrowserAnimationModule, ɵAnimationEngine, ɵAnimationRendererFactory} from '@angular/platform-browser/animations';
|
import {BrowserAnimationModule, ɵAnimationEngine, ɵAnimationRendererFactory} from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
import {BrowserModule} from '../../src/browser';
|
import {InjectableAnimationEngine} from '../../animations/src/browser_animation_module';
|
||||||
import {el} from '../../testing/browser_util';
|
import {el} from '../../testing/browser_util';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
|
@ -21,7 +22,7 @@ export function main() {
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [{provide: ɵAnimationEngine, useClass: MockAnimationEngine}],
|
providers: [{provide: ɵAnimationEngine, useClass: MockAnimationEngine}],
|
||||||
imports: [BrowserModule, BrowserAnimationModule]
|
imports: [BrowserAnimationModule]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -95,7 +96,7 @@ export function main() {
|
||||||
expect(engine.captures['listen']).toBeFalsy();
|
expect(engine.captures['listen']).toBeFalsy();
|
||||||
|
|
||||||
renderer.listen(element, '@event.phase', cb);
|
renderer.listen(element, '@event.phase', cb);
|
||||||
expect(engine.captures['listen'].pop()).toEqual([element, 'event', 'phase', cb]);
|
expect(engine.captures['listen'].pop()).toEqual([element, 'event', 'phase']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve the body|document|window nodes given their values as strings as input',
|
it('should resolve the body|document|window nodes given their values as strings as input',
|
||||||
|
@ -115,6 +116,50 @@ export function main() {
|
||||||
expect(engine.captures['listen'].pop()[0]).toBe(window);
|
expect(engine.captures['listen'].pop()[0]).toBe(window);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('flushing animations', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureCompiler(
|
||||||
|
{useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should flush and fire callbacks when the zone becomes stable', (async) => {
|
||||||
|
@Component({
|
||||||
|
selector: 'my-cmp',
|
||||||
|
template: '<div [@myAnimation]="exp" (@myAnimation.start)="onStart($event)"></div>',
|
||||||
|
animations: [trigger(
|
||||||
|
'myAnimation',
|
||||||
|
[transition(
|
||||||
|
'* => state',
|
||||||
|
[style({'opacity': '0'}), animate(500, style({'opacity': '1'}))])])],
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
exp: any;
|
||||||
|
event: any;
|
||||||
|
onStart(event: any) { this.event = event; }
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [{provide: ɵAnimationEngine, useClass: InjectableAnimationEngine}],
|
||||||
|
declarations: [Cmp]
|
||||||
|
});
|
||||||
|
|
||||||
|
const engine = TestBed.get(ɵAnimationEngine);
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
cmp.exp = 'state';
|
||||||
|
fixture.detectChanges();
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(cmp.event.triggerName).toEqual('myAnimation');
|
||||||
|
expect(cmp.event.phaseName).toEqual('start');
|
||||||
|
cmp.event = null;
|
||||||
|
|
||||||
|
engine.flush();
|
||||||
|
expect(cmp.event).toBeFalsy();
|
||||||
|
async();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +185,10 @@ class MockAnimationEngine extends ɵAnimationEngine {
|
||||||
|
|
||||||
listen(element: any, eventName: string, eventPhase: string, callback: (event: any) => any):
|
listen(element: any, eventName: string, eventPhase: string, callback: (event: any) => any):
|
||||||
() => void {
|
() => void {
|
||||||
this._capture('listen', [element, eventName, eventPhase, callback]);
|
// we don't capture the callback here since the renderer wraps it in a zone
|
||||||
|
this._capture('listen', [element, eventName, eventPhase]);
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flush() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,3 +9,7 @@ export declare abstract class AnimationDriver {
|
||||||
/** @experimental */
|
/** @experimental */
|
||||||
export declare class BrowserAnimationModule {
|
export declare class BrowserAnimationModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare class NoopBrowserAnimationModule {
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue