angular-cn/packages/animations/browser/src/render/noop_animation_engine.ts

170 lines
5.5 KiB
TypeScript

/**
* @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;
doRemove?: boolean;
}
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}} =
Object.create(null);
registerTrigger(trigger: AnimationTriggerMetadata, name: string = null): void {
name = name || trigger.name;
if (this._triggerStyles[name]) {
return;
}
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[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 () => tuple.doRemove = true;
}
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
});
});
}
});
// remove all the listeners after everything is complete
Array.from(this._listeners.keys()).forEach(element => {
const listenersToKeep = this._listeners.get(element).filter(l => !l.doRemove);
if (listenersToKeep.length) {
this._listeners.set(element, listenersToKeep);
} else {
this._listeners.delete(element);
}
});
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;
}