2017-02-23 08:51:00 -08:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
2017-04-26 10:44:28 -07:00
|
|
|
import {AnimationEvent, AnimationPlayer, AnimationTriggerMetadata, ɵStyleData} from '@angular/animations';
|
2017-02-23 08:51:00 -08:00
|
|
|
|
|
|
|
import {AnimationEngine} from '../animation_engine';
|
2017-04-26 10:44:28 -07:00
|
|
|
import {TriggerAst} from '../dsl/animation_ast';
|
|
|
|
import {buildAnimationAst} from '../dsl/animation_ast_builder';
|
|
|
|
import {buildTrigger} from '../dsl/animation_trigger';
|
|
|
|
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
|
2017-02-23 08:51:00 -08:00
|
|
|
import {copyStyles, eraseStyles, normalizeStyles, setStyles} from '../util';
|
|
|
|
|
2017-04-26 10:44:28 -07:00
|
|
|
import {AnimationDriver} from './animation_driver';
|
|
|
|
import {parseTimelineCommand} from './shared';
|
|
|
|
import {TimelineAnimationEngine} from './timeline_animation_engine';
|
|
|
|
|
2017-02-23 08:51:00 -08:00
|
|
|
interface ListenerTuple {
|
|
|
|
eventPhase: string;
|
|
|
|
triggerName: string;
|
2017-04-26 10:44:28 -07:00
|
|
|
namespacedName: string;
|
2017-02-23 08:51:00 -08:00
|
|
|
callback: (event: any) => any;
|
2017-02-23 09:41:00 -08:00
|
|
|
doRemove?: boolean;
|
2017-02-23 08:51:00 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
interface ChangeTuple {
|
|
|
|
element: any;
|
2017-04-26 10:44:28 -07:00
|
|
|
namespacedName: string;
|
2017-02-23 08:51:00 -08:00
|
|
|
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)[] = [];
|
2017-02-24 16:28:14 -08:00
|
|
|
private _triggerStyles: {[triggerName: string]: {[stateName: string]: ɵStyleData}} =
|
|
|
|
Object.create(null);
|
2017-02-23 08:51:00 -08:00
|
|
|
|
2017-04-26 10:44:28 -07:00
|
|
|
private _timelineEngine: TimelineAnimationEngine;
|
|
|
|
|
|
|
|
// this method is designed to be overridden by the code that uses this engine
|
|
|
|
public onRemovalComplete = (element: any, context: any) => {};
|
|
|
|
|
|
|
|
constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) {
|
|
|
|
super();
|
|
|
|
this._timelineEngine = new TimelineAnimationEngine(driver, normalizer);
|
|
|
|
}
|
|
|
|
|
|
|
|
registerTrigger(
|
|
|
|
componentId: string, namespaceId: string, hostElement: any, name: string,
|
|
|
|
metadata: AnimationTriggerMetadata): void {
|
|
|
|
name = name || metadata.name;
|
|
|
|
name = namespaceId + '#' + name;
|
2017-02-24 16:28:14 -08:00
|
|
|
if (this._triggerStyles[name]) {
|
|
|
|
return;
|
|
|
|
}
|
2017-04-26 10:44:28 -07:00
|
|
|
|
|
|
|
const errors: any[] = [];
|
|
|
|
const ast = buildAnimationAst(metadata, errors) as TriggerAst;
|
|
|
|
const trigger = buildTrigger(name, ast);
|
|
|
|
this._triggerStyles[name] = trigger.states;
|
2017-02-23 08:51:00 -08:00
|
|
|
}
|
|
|
|
|
2017-04-26 10:44:28 -07:00
|
|
|
onInsert(namespaceId: string, element: any, parent: any, insertBefore: boolean): void {}
|
2017-02-23 08:51:00 -08:00
|
|
|
|
2017-04-26 10:44:28 -07:00
|
|
|
onRemove(namespaceId: string, element: any, context: any): void {
|
|
|
|
this.onRemovalComplete(element, context);
|
2017-03-17 21:05:42 -07:00
|
|
|
if (element['nodeType'] == 1) {
|
|
|
|
this._flaggedRemovals.add(element);
|
|
|
|
}
|
2017-02-23 08:51:00 -08:00
|
|
|
}
|
|
|
|
|
2017-04-26 10:44:28 -07:00
|
|
|
setProperty(namespaceId: string, element: any, property: string, value: any): boolean {
|
|
|
|
if (property.charAt(0) == '@') {
|
|
|
|
const [id, action] = parseTimelineCommand(property);
|
|
|
|
const args = value as any[];
|
|
|
|
this._timelineEngine.command(id, element, action, args);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const namespacedName = namespaceId + '#' + property;
|
|
|
|
const storageProp = makeStorageProp(namespacedName);
|
2017-02-23 08:51:00 -08:00
|
|
|
const oldValue = element[storageProp] || DEFAULT_STATE_VALUE;
|
2017-04-26 10:44:28 -07:00
|
|
|
this._changes.push(
|
|
|
|
<ChangeTuple>{element, oldValue, newValue: value, triggerName: property, namespacedName});
|
2017-02-23 08:51:00 -08:00
|
|
|
|
2017-04-26 10:44:28 -07:00
|
|
|
const triggerStateStyles = this._triggerStyles[namespacedName] || {};
|
2017-02-23 08:51:00 -08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
2017-04-26 10:44:28 -07:00
|
|
|
|
|
|
|
return true;
|
2017-02-23 08:51:00 -08:00
|
|
|
}
|
|
|
|
|
2017-04-26 10:44:28 -07:00
|
|
|
listen(
|
|
|
|
namespaceId: string, element: any, eventName: string, eventPhase: string,
|
|
|
|
callback: (event: any) => any): () => any {
|
|
|
|
if (eventName.charAt(0) == '@') {
|
|
|
|
const [id, action] = parseTimelineCommand(eventName);
|
|
|
|
return this._timelineEngine.listen(id, element, action, callback);
|
|
|
|
}
|
|
|
|
|
2017-02-23 08:51:00 -08:00
|
|
|
let listeners = this._listeners.get(element);
|
|
|
|
if (!listeners) {
|
|
|
|
this._listeners.set(element, listeners = []);
|
|
|
|
}
|
|
|
|
|
2017-04-26 10:44:28 -07:00
|
|
|
const tuple = <ListenerTuple>{
|
|
|
|
namespacedName: namespaceId + '#' + eventName,
|
|
|
|
triggerName: eventName, eventPhase, callback
|
|
|
|
};
|
2017-02-23 08:51:00 -08:00
|
|
|
listeners.push(tuple);
|
|
|
|
|
2017-02-23 09:41:00 -08:00
|
|
|
return () => tuple.doRemove = true;
|
2017-02-23 08:51:00 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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 => {
|
2017-04-26 10:44:28 -07:00
|
|
|
if (listener.namespacedName == change.namespacedName) {
|
2017-02-23 08:51:00 -08:00
|
|
|
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;
|
2017-04-26 10:44:28 -07:00
|
|
|
const namespacedName = listener.namespacedName;
|
|
|
|
const storageProp = makeStorageProp(namespacedName);
|
2017-02-23 08:51:00 -08:00
|
|
|
handleListener(listener, <ChangeTuple>{
|
2017-04-26 10:44:28 -07:00
|
|
|
element,
|
|
|
|
triggerName,
|
|
|
|
namespacedName: listener.namespacedName,
|
2017-02-23 08:51:00 -08:00
|
|
|
oldValue: element[storageProp] || DEFAULT_STATE_VALUE,
|
|
|
|
newValue: DEFAULT_STATE_VALUE
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-02-23 09:41:00 -08:00
|
|
|
// remove all the listeners after everything is complete
|
|
|
|
Array.from(this._listeners.keys()).forEach(element => {
|
2017-03-24 09:56:34 -07:00
|
|
|
const listenersToKeep = this._listeners.get(element) !.filter(l => !l.doRemove);
|
2017-02-23 09:41:00 -08:00
|
|
|
if (listenersToKeep.length) {
|
|
|
|
this._listeners.set(element, listenersToKeep);
|
|
|
|
} else {
|
|
|
|
this._listeners.delete(element);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-02-23 08:51:00 -08:00
|
|
|
onStartCallbacks.forEach(fn => fn());
|
|
|
|
onDoneCallbacks.forEach(fn => fn());
|
|
|
|
this._flaggedRemovals.clear();
|
|
|
|
this._changes = [];
|
|
|
|
|
|
|
|
this._onDoneFns.forEach(doneFn => doneFn());
|
|
|
|
this._onDoneFns = [];
|
|
|
|
}
|
|
|
|
|
2017-04-26 10:44:28 -07:00
|
|
|
get players(): AnimationPlayer[] { return []; }
|
|
|
|
|
|
|
|
destroy(namespaceId: string) {}
|
2017-02-23 08:51:00 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|