From bc5ab5cf3ab9efac34f879596ea8087d57d1dabb Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Mon, 20 Nov 2017 17:33:13 +0200 Subject: [PATCH 01/16] wip: all styles are valid --- nativescript-angular/animations/animation-driver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nativescript-angular/animations/animation-driver.ts b/nativescript-angular/animations/animation-driver.ts index 29eae6f9e..93e036ea1 100644 --- a/nativescript-angular/animations/animation-driver.ts +++ b/nativescript-angular/animations/animation-driver.ts @@ -69,7 +69,7 @@ class Selector { export class NativeScriptAnimationDriver implements AnimationDriver { validateStyleProperty(prop: string): boolean { - throw new Error("Not implemented!"); + return true; } matchesElement(element: NgView, rawSelector: string): boolean { From 788124e98bbda720774f01535a1c113b94dd97b5 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Tue, 21 Nov 2017 09:52:44 +0200 Subject: [PATCH 02/16] refactor: use the transition animation engine from angular --- nativescript-angular/animations/animation-engine.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nativescript-angular/animations/animation-engine.ts b/nativescript-angular/animations/animation-engine.ts index 7c8f874fc..5dbf7d0a2 100644 --- a/nativescript-angular/animations/animation-engine.ts +++ b/nativescript-angular/animations/animation-engine.ts @@ -1,17 +1,14 @@ import { AnimationDriver, ɵAnimationEngine as AnimationEngine, + ɵAnimationStyleNormalizer as AnimationStyleNormalizer, } from "@angular/animations/browser"; -import { - AnimationStyleNormalizer -} from "@angular/animations/browser/src/dsl/style_normalization/animation_style_normalizer"; import { NSTransitionAnimationEngine } from "./transition-animation-engine"; export class NativeScriptAnimationEngine extends AnimationEngine { constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) { super(driver, normalizer); - (this)._transitionEngine = new NSTransitionAnimationEngine(driver, normalizer); (this)._transitionEngine.onRemovalComplete = (element, delegate) => { const parent = delegate && delegate.parentNode(element); From 12209b7ae31eb1e7c58d1abe58f5faccc5eecb24 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Tue, 21 Nov 2017 09:53:22 +0200 Subject: [PATCH 03/16] refactor: provide a mocked document object with body (wip) --- nativescript-angular/animations/animation-driver.ts | 4 ++++ nativescript-angular/animations/animations.module.ts | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/nativescript-angular/animations/animation-driver.ts b/nativescript-angular/animations/animation-driver.ts index 93e036ea1..35d7e5024 100644 --- a/nativescript-angular/animations/animation-driver.ts +++ b/nativescript-angular/animations/animation-driver.ts @@ -89,6 +89,10 @@ export class NativeScriptAnimationDriver implements AnimationDriver { `element1: ${elm1}, element2: ${elm2}` ); + if (elm1["isOverride"]) { + return true; + } + const params: ViewMatchParams = { originalView: elm2 }; const result: ViewMatchResult = this.visitDescendants(elm1, viewMatches, params); diff --git a/nativescript-angular/animations/animations.module.ts b/nativescript-angular/animations/animations.module.ts index 7f2b3adea..97481399f 100644 --- a/nativescript-angular/animations/animations.module.ts +++ b/nativescript-angular/animations/animations.module.ts @@ -18,6 +18,12 @@ import { NativeScriptAnimationDriver } from "./animation-driver"; import { NativeScriptModule } from "../nativescript.module"; import { NativeScriptRendererFactory } from "../renderer"; +(global).document = { + body: { + isOverride: true, + } +}; + @Injectable() export class InjectableAnimationEngine extends NativeScriptAnimationEngine { constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) { From 61a012f36147f3f9526f282ac443f1d70b102ee2 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Mon, 4 Dec 2017 08:24:17 -0800 Subject: [PATCH 04/16] refactor: use property names list from CssAnimationProperty for validation --- nativescript-angular/animations/animation-driver.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nativescript-angular/animations/animation-driver.ts b/nativescript-angular/animations/animation-driver.ts index 35d7e5024..bac90980c 100644 --- a/nativescript-angular/animations/animation-driver.ts +++ b/nativescript-angular/animations/animation-driver.ts @@ -1,7 +1,9 @@ import { AnimationPlayer } from "@angular/animations"; import { AnimationDriver } from "@angular/animations/browser"; -import { ProxyViewContainer } from "tns-core-modules/ui/proxy-view-container"; +import { createSelector, SelectorCore } from "tns-core-modules/ui/styling/css-selector"; +import { CssAnimationProperty } from "tns-core-modules/ui/core/properties"; import { eachDescendant } from "tns-core-modules/ui/core/view"; +import { ProxyViewContainer } from "tns-core-modules/ui/proxy-view-container"; import { NativeScriptAnimationPlayer } from "./animation-player"; import { @@ -11,7 +13,6 @@ import { import { NgView, InvisibleNode } from "../element-registry"; import { animationsLog as traceLog } from "../trace"; -import { createSelector, SelectorCore } from "tns-core-modules/ui/styling/css-selector"; interface ViewMatchResult { found: boolean; @@ -68,8 +69,9 @@ class Selector { } export class NativeScriptAnimationDriver implements AnimationDriver { - validateStyleProperty(prop: string): boolean { - return true; + validateStyleProperty(property: string): boolean { + traceLog(`CssAnimationProperty.validateStyleProperty: ${property}`); + return CssAnimationProperty._getPropertyNames().indexOf(property) !== -1; } matchesElement(element: NgView, rawSelector: string): boolean { From 1ecc7bbd2b3a36862f391560080a5639e24ce94a Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Tue, 5 Dec 2017 00:35:47 -0800 Subject: [PATCH 05/16] fix: include "transform" in valid animation property names --- nativescript-angular/animations/animation-driver.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nativescript-angular/animations/animation-driver.ts b/nativescript-angular/animations/animation-driver.ts index bac90980c..829eb794d 100644 --- a/nativescript-angular/animations/animation-driver.ts +++ b/nativescript-angular/animations/animation-driver.ts @@ -69,9 +69,14 @@ class Selector { } export class NativeScriptAnimationDriver implements AnimationDriver { + private static validProperties = [ + ...CssAnimationProperty._getPropertyNames(), + "transform", + ]; + validateStyleProperty(property: string): boolean { traceLog(`CssAnimationProperty.validateStyleProperty: ${property}`); - return CssAnimationProperty._getPropertyNames().indexOf(property) !== -1; + return NativeScriptAnimationDriver.validProperties.indexOf(property) !== -1; } matchesElement(element: NgView, rawSelector: string): boolean { From 9c1c6b425629489770659ff7f8e3fbd05f57d790 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Tue, 5 Dec 2017 15:47:19 -0800 Subject: [PATCH 06/16] refactor: remove old private imports --- .../dsl/element_instruction_map.ts | 36 - .../private-imports/render/shared.ts | 164 -- .../render/transition_animation_engine.ts | 1468 ----------------- .../animations/private-imports/util.ts | 220 --- .../animations/transition-animation-engine.ts | 501 ------ 5 files changed, 2389 deletions(-) delete mode 100644 nativescript-angular/animations/private-imports/dsl/element_instruction_map.ts delete mode 100644 nativescript-angular/animations/private-imports/render/shared.ts delete mode 100644 nativescript-angular/animations/private-imports/render/transition_animation_engine.ts delete mode 100644 nativescript-angular/animations/private-imports/util.ts delete mode 100644 nativescript-angular/animations/transition-animation-engine.ts diff --git a/nativescript-angular/animations/private-imports/dsl/element_instruction_map.ts b/nativescript-angular/animations/private-imports/dsl/element_instruction_map.ts deleted file mode 100644 index 407db074f..000000000 --- a/nativescript-angular/animations/private-imports/dsl/element_instruction_map.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @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 - */ - -/* tslint:disable */ -import {AnimationTimelineInstruction} from '@angular/animations/browser/src/dsl/animation_timeline_instruction'; - -export class ElementInstructionMap { - private _map = new Map(); - - consume(element: any): AnimationTimelineInstruction[] { - let instructions = this._map.get(element); - if (instructions) { - this._map.delete(element); - } else { - instructions = []; - } - return instructions; - } - - append(element: any, instructions: AnimationTimelineInstruction[]) { - let existingInstructions = this._map.get(element); - if (!existingInstructions) { - this._map.set(element, existingInstructions = []); - } - existingInstructions.push(...instructions); - } - - has(element: any): boolean { return this._map.has(element); } - - clear() { this._map.clear(); } -} \ No newline at end of file diff --git a/nativescript-angular/animations/private-imports/render/shared.ts b/nativescript-angular/animations/private-imports/render/shared.ts deleted file mode 100644 index efbdc0742..000000000 --- a/nativescript-angular/animations/private-imports/render/shared.ts +++ /dev/null @@ -1,164 +0,0 @@ -/** - * @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 - */ -/* tslint:disable */ - -import {AUTO_STYLE, AnimationEvent, AnimationPlayer, NoopAnimationPlayer, ɵAnimationGroupPlayer, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations'; - -import {AnimationStyleNormalizer} from "@angular/animations/browser/src/dsl/style_normalization/animation_style_normalizer"; -import {AnimationDriver} from "@angular/animations/browser"; - -export function optimizeGroupPlayer(players: AnimationPlayer[]): AnimationPlayer { - switch (players.length) { - case 0: - return new NoopAnimationPlayer(); - case 1: - return players[0]; - default: - return new ɵAnimationGroupPlayer(players); - } -} - -export function normalizeKeyframes( - driver: AnimationDriver, normalizer: AnimationStyleNormalizer, element: any, - keyframes: ɵStyleData[], preStyles: ɵStyleData = {}, - postStyles: ɵStyleData = {}): ɵStyleData[] { - const errors: string[] = []; - const normalizedKeyframes: ɵStyleData[] = []; - let previousOffset = -1; - let previousKeyframe: ɵStyleData|null = null; - keyframes.forEach(kf => { - const offset = kf['offset'] as number; - const isSameOffset = offset == previousOffset; - const normalizedKeyframe: ɵStyleData = (isSameOffset && previousKeyframe) || {}; - Object.keys(kf).forEach(prop => { - let normalizedProp = prop; - let normalizedValue = kf[prop]; - if (normalizedValue == PRE_STYLE) { - normalizedValue = preStyles[prop]; - } else if (normalizedValue == AUTO_STYLE) { - normalizedValue = postStyles[prop]; - } else if (prop != 'offset') { - normalizedProp = normalizer.normalizePropertyName(prop, errors); - normalizedValue = normalizer.normalizeStyleValue(prop, normalizedProp, kf[prop], errors); - } - normalizedKeyframe[normalizedProp] = normalizedValue; - }); - if (!isSameOffset) { - normalizedKeyframes.push(normalizedKeyframe); - } - previousKeyframe = normalizedKeyframe; - previousOffset = offset; - }); - if (errors.length) { - const LINE_START = '\n - '; - throw new Error( - `Unable to animate due to the following errors:${LINE_START}${errors.join(LINE_START)}`); - } - - return normalizedKeyframes; -} - -export function listenOnPlayer( - player: AnimationPlayer, eventName: string, event: AnimationEvent | undefined, - callback: (event: any) => any) { - switch (eventName) { - case 'start': - player.onStart(() => callback(event && copyAnimationEvent(event, 'start', player.totalTime))); - break; - case 'done': - player.onDone(() => callback(event && copyAnimationEvent(event, 'done', player.totalTime))); - break; - case 'destroy': - player.onDestroy( - () => callback(event && copyAnimationEvent(event, 'destroy', player.totalTime))); - break; - } -} - -export function copyAnimationEvent( - e: AnimationEvent, phaseName?: string, totalTime?: number): AnimationEvent { - const event = makeAnimationEvent( - e.element, e.triggerName, e.fromState, e.toState, phaseName || e.phaseName, - totalTime == undefined ? e.totalTime : totalTime); - const data = (e as any)['_data']; - if (data != null) { - (event as any)['_data'] = data; - } - return event; -} - -export function makeAnimationEvent( - element: any, triggerName: string, fromState: string, toState: string, phaseName: string = '', - totalTime: number = 0): AnimationEvent { - return {element, triggerName, fromState, toState, phaseName, totalTime}; -} - -export function getOrSetAsInMap( - map: Map| {[key: string]: any}, key: any, defaultValue: any) { - let value: any; - if (map instanceof Map) { - value = map.get(key); - if (!value) { - map.set(key, value = defaultValue); - } - } else { - value = map[key]; - if (!value) { - value = map[key] = defaultValue; - } - } - return value; -} - -export function parseTimelineCommand(command: string): [string, string] { - const separatorPos = command.indexOf(':'); - const id = command.substring(1, separatorPos); - const action = command.substr(separatorPos + 1); - return [id, action]; -} - -let _contains: (elm1: any, elm2: any) => boolean = (elm1: any, elm2: any) => false; -let _matches: (element: any, selector: string) => boolean = (element: any, selector: string) => - false; -let _query: (element: any, selector: string, multi: boolean) => any[] = - (element: any, selector: string, multi: boolean) => { - return []; - }; - -if (typeof Element != 'undefined') { - // this is well supported in all browsers - _contains = (elm1: any, elm2: any) => { return elm1.contains(elm2) as boolean; }; - - if (Element.prototype.matches) { - _matches = (element: any, selector: string) => element.matches(selector); - } else { - const proto = Element.prototype as any; - const fn = proto.matchesSelector || proto.mozMatchesSelector || proto.msMatchesSelector || - proto.oMatchesSelector || proto.webkitMatchesSelector; - if (fn) { - _matches = (element: any, selector: string) => fn.apply(element, [selector]); - } - } - - _query = (element: any, selector: string, multi: boolean): any[] => { - let results: any[] = []; - if (multi) { - results.push(...element.querySelectorAll(selector)); - } else { - const elm = element.querySelector(selector); - if (elm) { - results.push(elm); - } - } - return results; - }; -} - -export const matchesElement = _matches; -export const containsElement = _contains; -export const invokeQuery = _query; \ No newline at end of file diff --git a/nativescript-angular/animations/private-imports/render/transition_animation_engine.ts b/nativescript-angular/animations/private-imports/render/transition_animation_engine.ts deleted file mode 100644 index bea738936..000000000 --- a/nativescript-angular/animations/private-imports/render/transition_animation_engine.ts +++ /dev/null @@ -1,1468 +0,0 @@ -/** - * @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 - */ -/* tslint:disable */ -import {AUTO_STYLE, AnimationOptions, AnimationPlayer, NoopAnimationPlayer, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations'; - -import {AnimationTimelineInstruction} from '@angular/animations/browser/src/dsl/animation_timeline_instruction'; -import {AnimationTransitionFactory} from '@angular/animations/browser/src/dsl/animation_transition_factory'; -import {AnimationTransitionInstruction} from '@angular/animations/browser/src/dsl/animation_transition_instruction'; -import {AnimationTrigger} from '@angular/animations/browser/src/dsl/animation_trigger'; - -import {ElementInstructionMap} from '../dsl/element_instruction_map'; - -import {AnimationStyleNormalizer} from '@angular/animations/browser/src/dsl/style_normalization/animation_style_normalizer'; -import {ENTER_CLASSNAME, LEAVE_CLASSNAME, NG_ANIMATING_CLASSNAME, NG_ANIMATING_SELECTOR, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, copyObj, eraseStyles, setStyles} from '../util'; - -import {AnimationDriver} from '@angular/animations/browser/src/render/animation_driver'; - -import {getOrSetAsInMap, listenOnPlayer, makeAnimationEvent, normalizeKeyframes, optimizeGroupPlayer} from "../render/shared"; - -const QUEUED_CLASSNAME = 'ng-animate-queued'; -const QUEUED_SELECTOR = '.ng-animate-queued'; -const DISABLED_CLASSNAME = 'ng-animate-disabled'; -const DISABLED_SELECTOR = '.ng-animate-disabled'; - -const EMPTY_PLAYER_ARRAY: TransitionAnimationPlayer[] = []; -const NULL_REMOVAL_STATE: ElementAnimationState = { - namespaceId: '', - setForRemoval: null, - hasAnimation: false, - removedBeforeQueried: false -}; -const NULL_REMOVED_QUERIED_STATE: ElementAnimationState = { - namespaceId: '', - setForRemoval: null, - hasAnimation: false, - removedBeforeQueried: true -}; - -interface TriggerListener { - name: string; - phase: string; - callback: (event: any) => any; -} - -export interface QueueInstruction { - element: any; - triggerName: string; - fromState: StateValue; - toState: StateValue; - transition: AnimationTransitionFactory; - player: TransitionAnimationPlayer; - isFallbackTransition: boolean; -} - -export const REMOVAL_FLAG = '__ng_removed'; - -export interface ElementAnimationState { - setForRemoval: any; - hasAnimation: boolean; - namespaceId: string; - removedBeforeQueried: boolean; -} - -export class StateValue { - public value: string; - public options: AnimationOptions; - - constructor(input: any) { - const isObj = input && input.hasOwnProperty('value'); - const value = isObj ? input['value'] : input; - this.value = normalizeTriggerValue(value); - if (isObj) { - const options = copyObj(input as any); - delete options['value']; - this.options = options as AnimationOptions; - } else { - this.options = {}; - } - if (!this.options.params) { - this.options.params = {}; - } - } - - absorbOptions(options: AnimationOptions) { - const newParams = options.params; - if (newParams) { - const oldParams = this.options.params !; - Object.keys(newParams).forEach(prop => { - if (oldParams[prop] == null) { - oldParams[prop] = newParams[prop]; - } - }); - } - } -} - -export const VOID_VALUE = 'void'; -export const DEFAULT_STATE_VALUE = new StateValue(VOID_VALUE); -export const DELETED_STATE_VALUE = new StateValue('DELETED'); - -export class AnimationTransitionNamespace { - public players: TransitionAnimationPlayer[] = []; - - private _triggers: {[triggerName: string]: AnimationTrigger} = {}; - private _queue: QueueInstruction[] = []; - - private _elementListeners = new Map(); - - private _hostClassName: string; - - constructor( - public id: string, public hostElement: any, private _engine: TransitionAnimationEngine) { - this._hostClassName = 'ng-tns-' + id; - addClass(hostElement, this._hostClassName); - } - - listen(element: any, name: string, phase: string, callback: (event: any) => boolean): () => any { - if (!this._triggers.hasOwnProperty(name)) { - throw new Error( - `Unable to listen on the animation trigger event "${phase}" because the animation trigger "${name}" doesn\'t exist!`); - } - - if (phase == null || phase.length == 0) { - throw new Error( - `Unable to listen on the animation trigger "${name}" because the provided event is undefined!`); - } - - if (!isTriggerEventValid(phase)) { - throw new Error( - `The provided animation trigger event "${phase}" for the animation trigger "${name}" is not supported!`); - } - - const listeners = getOrSetAsInMap(this._elementListeners, element, []); - const data = {name, phase, callback}; - listeners.push(data); - - const triggersWithStates = getOrSetAsInMap(this._engine.statesByElement, element, {}); - if (!triggersWithStates.hasOwnProperty(name)) { - addClass(element, NG_TRIGGER_CLASSNAME); - addClass(element, NG_TRIGGER_CLASSNAME + '-' + name); - triggersWithStates[name] = null; - } - - return () => { - // the event listener is removed AFTER the flush has occurred such - // that leave animations callbacks can fire (otherwise if the node - // is removed in between then the listeners would be deregistered) - this._engine.afterFlush(() => { - const index = listeners.indexOf(data); - if (index >= 0) { - listeners.splice(index, 1); - } - - if (!this._triggers[name]) { - delete triggersWithStates[name]; - } - }); - }; - } - - register(name: string, ast: AnimationTrigger): boolean { - if (this._triggers[name]) { - // throw - return false; - } else { - this._triggers[name] = ast; - return true; - } - } - - private _getTrigger(name: string) { - const trigger = this._triggers[name]; - if (!trigger) { - throw new Error(`The provided animation trigger "${name}" has not been registered!`); - } - return trigger; - } - - trigger(element: any, triggerName: string, value: any, defaultToFallback: boolean = true): - TransitionAnimationPlayer|undefined { - const trigger = this._getTrigger(triggerName); - const player = new TransitionAnimationPlayer(this.id, triggerName, element); - - let triggersWithStates = this._engine.statesByElement.get(element); - if (!triggersWithStates) { - addClass(element, NG_TRIGGER_CLASSNAME); - addClass(element, NG_TRIGGER_CLASSNAME + '-' + triggerName); - this._engine.statesByElement.set(element, triggersWithStates = {}); - } - - let fromState = triggersWithStates[triggerName]; - const toState = new StateValue(value); - - const isObj = value && value.hasOwnProperty('value'); - if (!isObj && fromState) { - toState.absorbOptions(fromState.options); - } - - triggersWithStates[triggerName] = toState; - - if (!fromState) { - fromState = DEFAULT_STATE_VALUE; - } else if (fromState === DELETED_STATE_VALUE) { - return player; - } - - const isRemoval = toState.value === VOID_VALUE; - - // normally this isn't reached by here, however, if an object expression - // is passed in then it may be a new object each time. Comparing the value - // is important since that will stay the same despite there being a new object. - // The removal arc here is special cased because the same element is triggered - // twice in the event that it contains animations on the outer/inner portions - // of the host container - if (!isRemoval && fromState.value === toState.value) return; - - const playersOnElement: TransitionAnimationPlayer[] = - getOrSetAsInMap(this._engine.playersByElement, element, []); - playersOnElement.forEach(player => { - // only remove the player if it is queued on the EXACT same trigger/namespace - // we only also deal with queued players here because if the animation has - // started then we want to keep the player alive until the flush happens - // (which is where the previousPlayers are passed into the new palyer) - if (player.namespaceId == this.id && player.triggerName == triggerName && player.queued) { - player.destroy(); - } - }); - - let transition = trigger.matchTransition(fromState.value, toState.value); - let isFallbackTransition = false; - if (!transition) { - if (!defaultToFallback) return; - transition = trigger.fallbackTransition; - isFallbackTransition = true; - } - - this._engine.totalQueuedPlayers++; - this._queue.push( - {element, triggerName, transition, fromState, toState, player, isFallbackTransition}); - - if (!isFallbackTransition) { - addClass(element, QUEUED_CLASSNAME); - player.onStart(() => { removeClass(element, QUEUED_CLASSNAME); }); - } - - player.onDone(() => { - let index = this.players.indexOf(player); - if (index >= 0) { - this.players.splice(index, 1); - } - - const players = this._engine.playersByElement.get(element); - if (players) { - let index = players.indexOf(player); - if (index >= 0) { - players.splice(index, 1); - } - } - }); - - this.players.push(player); - playersOnElement.push(player); - - return player; - } - - deregister(name: string) { - delete this._triggers[name]; - - this._engine.statesByElement.forEach((stateMap, element) => { delete stateMap[name]; }); - - this._elementListeners.forEach((listeners, element) => { - this._elementListeners.set( - element, listeners.filter(entry => { return entry.name != name; })); - }); - } - - clearElementCache(element: any) { - this._engine.statesByElement.delete(element); - this._elementListeners.delete(element); - const elementPlayers = this._engine.playersByElement.get(element); - if (elementPlayers) { - elementPlayers.forEach(player => player.destroy()); - this._engine.playersByElement.delete(element); - } - } - - private _destroyInnerNodes(rootElement: any, context: any, animate: boolean = false) { - this._engine.driver.query(rootElement, NG_TRIGGER_SELECTOR, true).forEach(elm => { - if (animate && containsClass(elm, this._hostClassName)) { - const innerNs = this._engine.namespacesByHostElement.get(elm); - - // special case for a host element with animations on the same element - if (innerNs) { - innerNs.removeNode(elm, context, true); - } - - this.removeNode(elm, context, true); - } else { - this.clearElementCache(elm); - } - }); - } - - removeNode(element: any, context: any, doNotRecurse?: boolean): void { - const engine = this._engine; - - if (!doNotRecurse && element.childElementCount) { - this._destroyInnerNodes(element, context, true); - } - - const triggerStates = engine.statesByElement.get(element); - if (triggerStates) { - const players: TransitionAnimationPlayer[] = []; - Object.keys(triggerStates).forEach(triggerName => { - // this check is here in the event that an element is removed - // twice (both on the host level and the component level) - if (this._triggers[triggerName]) { - const player = this.trigger(element, triggerName, VOID_VALUE, false); - if (player) { - players.push(player); - } - } - }); - - if (players.length) { - engine.markElementAsRemoved(this.id, element, true, context); - optimizeGroupPlayer(players).onDone(() => engine.processLeaveNode(element)); - return; - } - } - - // find the player that is animating and make sure that the - // removal is delayed until that player has completed - let containsPotentialParentTransition = false; - if (engine.totalAnimations) { - const currentPlayers = - engine.players.length ? engine.playersByQueriedElement.get(element) : []; - - // when this `if statement` does not continue forward it means that - // a previous animation query has selected the current element and - // is animating it. In this situation want to continue fowards and - // allow the element to be queued up for animation later. - if (currentPlayers && currentPlayers.length) { - containsPotentialParentTransition = true; - } else { - let parent = element; - while (parent = parent.parentNode) { - const triggers = engine.statesByElement.get(parent); - if (triggers) { - containsPotentialParentTransition = true; - break; - } - } - } - } - - // at this stage we know that the element will either get removed - // during flush or will be picked up by a parent query. Either way - // we need to fire the listeners for this element when it DOES get - // removed (once the query parent animation is done or after flush) - const listeners = this._elementListeners.get(element); - if (listeners) { - const visitedTriggers = new Set(); - listeners.forEach(listener => { - const triggerName = listener.name; - if (visitedTriggers.has(triggerName)) return; - visitedTriggers.add(triggerName); - - const trigger = this._triggers[triggerName]; - const transition = trigger.fallbackTransition; - const elementStates = engine.statesByElement.get(element) !; - const fromState = elementStates[triggerName] || DEFAULT_STATE_VALUE; - const toState = new StateValue(VOID_VALUE); - const player = new TransitionAnimationPlayer(this.id, triggerName, element); - - this._engine.totalQueuedPlayers++; - this._queue.push({ - element, - triggerName, - transition, - fromState, - toState, - player, - isFallbackTransition: true - }); - }); - } - - // whether or not a parent has an animation we need to delay the deferral of the leave - // operation until we have more information (which we do after flush() has been called) - if (containsPotentialParentTransition) { - engine.markElementAsRemoved(this.id, element, false, context); - } else { - // we do this after the flush has occurred such - // that the callbacks can be fired - engine.afterFlush(() => this.clearElementCache(element)); - engine.destroyInnerAnimations(element); - engine._onRemovalComplete(element, context); - } - } - - insertNode(element: any, parent: any): void { addClass(element, this._hostClassName); } - - drainQueuedTransitions(microtaskId: number): QueueInstruction[] { - const instructions: QueueInstruction[] = []; - this._queue.forEach(entry => { - const player = entry.player; - if (player.destroyed) return; - - const element = entry.element; - const listeners = this._elementListeners.get(element); - if (listeners) { - listeners.forEach((listener: TriggerListener) => { - if (listener.name == entry.triggerName) { - const baseEvent = makeAnimationEvent( - element, entry.triggerName, entry.fromState.value, entry.toState.value); - (baseEvent as any)['_data'] = microtaskId; - listenOnPlayer(entry.player, listener.phase, baseEvent, listener.callback); - } - }); - } - - if (player.markedForDestroy) { - this._engine.afterFlush(() => { - // now we can destroy the element properly since the event listeners have - // been bound to the player - player.destroy(); - }); - } else { - instructions.push(entry); - } - }); - - this._queue = []; - - return instructions.sort((a, b) => { - // if depCount == 0 them move to front - // otherwise if a contains b then move back - const d0 = a.transition.ast.depCount; - const d1 = b.transition.ast.depCount; - if (d0 == 0 || d1 == 0) { - return d0 - d1; - } - return this._engine.driver.containsElement(a.element, b.element) ? 1 : -1; - }); - } - - destroy(context: any) { - this.players.forEach(p => p.destroy()); - this._destroyInnerNodes(this.hostElement, context); - } - - elementContainsData(element: any): boolean { - let containsData = false; - if (this._elementListeners.has(element)) containsData = true; - containsData = - (this._queue.find(entry => entry.element === element) ? true : false) || containsData; - return containsData; - } -} - -export interface QueuedTransition { - element: any; - instruction: AnimationTransitionInstruction; - player: TransitionAnimationPlayer; -} - -export class TransitionAnimationEngine { - public players: TransitionAnimationPlayer[] = []; - public newHostElements = new Map(); - public playersByElement = new Map(); - public playersByQueriedElement = new Map(); - public statesByElement = new Map(); - public disabledNodes = new Set(); - - public totalAnimations = 0; - public totalQueuedPlayers = 0; - - private _namespaceLookup: {[id: string]: AnimationTransitionNamespace} = {}; - private _namespaceList: AnimationTransitionNamespace[] = []; - private _flushFns: (() => any)[] = []; - private _whenQuietFns: (() => any)[] = []; - - public namespacesByHostElement = new Map(); - public collectedEnterElements: any[] = []; - public collectedLeaveElements: any[] = []; - - // this method is designed to be overridden by the code that uses this engine - public onRemovalComplete = (element: any, context: any) => {}; - - _onRemovalComplete(element: any, context: any) { this.onRemovalComplete(element, context); } - - constructor(public driver: AnimationDriver, private _normalizer: AnimationStyleNormalizer) {} - - get queuedPlayers(): TransitionAnimationPlayer[] { - const players: TransitionAnimationPlayer[] = []; - this._namespaceList.forEach(ns => { - ns.players.forEach(player => { - if (player.queued) { - players.push(player); - } - }); - }); - return players; - } - - createNamespace(namespaceId: string, hostElement: any) { - const ns = new AnimationTransitionNamespace(namespaceId, hostElement, this); - if (hostElement.parentNode) { - this._balanceNamespaceList(ns, hostElement); - } else { - // defer this later until flush during when the host element has - // been inserted so that we know exactly where to place it in - // the namespace list - this.newHostElements.set(hostElement, ns); - - // given that this host element is apart of the animation code, it - // may or may not be inserted by a parent node that is an of an - // animation renderer type. If this happens then we can still have - // access to this item when we query for :enter nodes. If the parent - // is a renderer then the set data-structure will normalize the entry - this.collectEnterElement(hostElement); - } - return this._namespaceLookup[namespaceId] = ns; - } - - private _balanceNamespaceList(ns: AnimationTransitionNamespace, hostElement: any) { - const limit = this._namespaceList.length - 1; - if (limit >= 0) { - let found = false; - for (let i = limit; i >= 0; i--) { - const nextNamespace = this._namespaceList[i]; - if (this.driver.containsElement(nextNamespace.hostElement, hostElement)) { - this._namespaceList.splice(i + 1, 0, ns); - found = true; - break; - } - } - if (!found) { - this._namespaceList.splice(0, 0, ns); - } - } else { - this._namespaceList.push(ns); - } - - this.namespacesByHostElement.set(hostElement, ns); - return ns; - } - - register(namespaceId: string, hostElement: any) { - let ns = this._namespaceLookup[namespaceId]; - if (!ns) { - ns = this.createNamespace(namespaceId, hostElement); - } - return ns; - } - - registerTrigger(namespaceId: string, name: string, trigger: AnimationTrigger) { - let ns = this._namespaceLookup[namespaceId]; - if (ns && ns.register(name, trigger)) { - this.totalAnimations++; - } - } - - destroy(namespaceId: string, context: any) { - if (!namespaceId) return; - - const ns = this._fetchNamespace(namespaceId); - - this.afterFlush(() => { - this.namespacesByHostElement.delete(ns.hostElement); - delete this._namespaceLookup[namespaceId]; - const index = this._namespaceList.indexOf(ns); - if (index >= 0) { - this._namespaceList.splice(index, 1); - } - }); - - this.afterFlushAnimationsDone(() => ns.destroy(context)); - } - - private _fetchNamespace(id: string) { return this._namespaceLookup[id]; } - - trigger(namespaceId: string, element: any, name: string, value: any): boolean { - if (isElementNode(element)) { - this._fetchNamespace(namespaceId).trigger(element, name, value); - return true; - } - return false; - } - - insertNode(namespaceId: string, element: any, parent: any, insertBefore: boolean): void { - if (!isElementNode(element)) return; - - // special case for when an element is removed and reinserted (move operation) - // when this occurs we do not want to use the element for deletion later - const details = element[REMOVAL_FLAG] as ElementAnimationState; - if (details && details.setForRemoval) { - details.setForRemoval = false; - } - - // in the event that the namespaceId is blank then the caller - // code does not contain any animation code in it, but it is - // just being called so that the node is marked as being inserted - if (namespaceId) { - this._fetchNamespace(namespaceId).insertNode(element, parent); - } - - // only *directives and host elements are inserted before - if (insertBefore) { - this.collectEnterElement(element); - } - } - - collectEnterElement(element: any) { this.collectedEnterElements.push(element); } - - markElementAsDisabled(element: any, value: boolean) { - if (value) { - if (!this.disabledNodes.has(element)) { - this.disabledNodes.add(element); - addClass(element, DISABLED_CLASSNAME); - } - } else if (this.disabledNodes.has(element)) { - this.disabledNodes.delete(element); - removeClass(element, DISABLED_CLASSNAME); - } - } - - removeNode(namespaceId: string, element: any, context: any, doNotRecurse?: boolean): void { - if (!isElementNode(element)) { - this._onRemovalComplete(element, context); - return; - } - - const ns = namespaceId ? this._fetchNamespace(namespaceId) : null; - if (ns) { - ns.removeNode(element, context, doNotRecurse); - } else { - this.markElementAsRemoved(namespaceId, element, false, context); - } - } - - markElementAsRemoved(namespaceId: string, element: any, hasAnimation?: boolean, context?: any) { - this.collectedLeaveElements.push(element); - element[REMOVAL_FLAG] = { - namespaceId, - setForRemoval: context, hasAnimation, - removedBeforeQueried: false - }; - } - - listen( - namespaceId: string, element: any, name: string, phase: string, - callback: (event: any) => boolean): () => any { - if (isElementNode(element)) { - return this._fetchNamespace(namespaceId).listen(element, name, phase, callback); - } - return () => {}; - } - - private _buildInstruction(entry: QueueInstruction, subTimelines: ElementInstructionMap) { - return (entry).transition.build( - this.driver, entry.element, entry.fromState.value, entry.toState.value, - entry.toState.options, subTimelines); - } - - destroyInnerAnimations(containerElement: any) { - let elements = this.driver.query(containerElement, NG_TRIGGER_SELECTOR, true); - elements.forEach(element => { - const players = this.playersByElement.get(element); - if (players) { - players.forEach(player => { - // special case for when an element is set for destruction, but hasn't started. - // in this situation we want to delay the destruction until the flush occurs - // so that any event listeners attached to the player are triggered. - if (player.queued) { - player.markedForDestroy = true; - } else { - player.destroy(); - } - }); - } - const stateMap = this.statesByElement.get(element); - if (stateMap) { - Object.keys(stateMap).forEach(triggerName => stateMap[triggerName] = DELETED_STATE_VALUE); - } - }); - - if (this.playersByQueriedElement.size == 0) return; - - elements = this.driver.query(containerElement, NG_ANIMATING_SELECTOR, true); - if (elements.length) { - elements.forEach(element => { - const players = this.playersByQueriedElement.get(element); - if (players) { - players.forEach(player => player.finish()); - } - }); - } - } - - whenRenderingDone(): Promise { - return new Promise(resolve => { - if (this.players.length) { - return optimizeGroupPlayer(this.players).onDone(() => resolve()); - } else { - resolve(); - } - }); - } - - processLeaveNode(element: any) { - const details = element[REMOVAL_FLAG] as ElementAnimationState; - if (details && details.setForRemoval) { - // this will prevent it from removing it twice - element[REMOVAL_FLAG] = NULL_REMOVAL_STATE; - if (details.namespaceId) { - this.destroyInnerAnimations(element); - const ns = this._fetchNamespace(details.namespaceId); - if (ns) { - ns.clearElementCache(element); - } - } - this._onRemovalComplete(element, details.setForRemoval); - } - - if (this.driver.matchesElement(element, DISABLED_SELECTOR)) { - this.markElementAsDisabled(element, false); - } - - this.driver.query(element, DISABLED_SELECTOR, true).forEach(node => { - this.markElementAsDisabled(element, false); - }); - } - - flush(microtaskId: number = -1) { - let players: AnimationPlayer[] = []; - if (this.newHostElements.size) { - this.newHostElements.forEach((ns, element) => this._balanceNamespaceList(ns, element)); - this.newHostElements.clear(); - } - - if (this._namespaceList.length && - (this.totalQueuedPlayers || this.collectedLeaveElements.length)) { - const cleanupFns: Function[] = []; - try { - players = this._flushAnimations(cleanupFns, microtaskId); - } finally { - for (let i = 0; i < cleanupFns.length; i++) { - cleanupFns[i](); - } - } - } else { - for (let i = 0; i < this.collectedLeaveElements.length; i++) { - const element = this.collectedLeaveElements[i]; - this.processLeaveNode(element); - } - } - - this.totalQueuedPlayers = 0; - this.collectedEnterElements.length = 0; - this.collectedLeaveElements.length = 0; - this._flushFns.forEach(fn => fn()); - this._flushFns = []; - - if (this._whenQuietFns.length) { - // we move these over to a variable so that - // if any new callbacks are registered in another - // flush they do not populate the existing set - const quietFns = this._whenQuietFns; - this._whenQuietFns = []; - - if (players.length) { - optimizeGroupPlayer(players).onDone(() => { quietFns.forEach(fn => fn()); }); - } else { - quietFns.forEach(fn => fn()); - } - } - } - - private _flushAnimations(cleanupFns: Function[], microtaskId: number): - TransitionAnimationPlayer[] { - const subTimelines = new ElementInstructionMap(); - const skippedPlayers: TransitionAnimationPlayer[] = []; - const skippedPlayersMap = new Map(); - const queuedInstructions: QueuedTransition[] = []; - const queriedElements = new Map(); - const allPreStyleElements = new Map>(); - const allPostStyleElements = new Map>(); - - const disabledElementsSet = new Set(); - this.disabledNodes.forEach(node => { - const nodesThatAreDisabled = this.driver.query(node, QUEUED_SELECTOR, true); - for (let i = 0; i < nodesThatAreDisabled.length; i++) { - disabledElementsSet.add(nodesThatAreDisabled[i]); - } - }); - - const bodyNode = getBodyNode(); - const allEnterNodes: any[] = this.collectedEnterElements.length ? - this.collectedEnterElements.filter(createIsRootFilterFn(this.collectedEnterElements)) : - []; - - // this must occur before the instructions are built below such that - // the :enter queries match the elements (since the timeline queries - // are fired during instruction building). - for (let i = 0; i < allEnterNodes.length; i++) { - addClass(allEnterNodes[i], ENTER_CLASSNAME); - } - - const allLeaveNodes: any[] = []; - const leaveNodesWithoutAnimations: any[] = []; - for (let i = 0; i < this.collectedLeaveElements.length; i++) { - const element = this.collectedLeaveElements[i]; - const details = element[REMOVAL_FLAG] as ElementAnimationState; - if (details && details.setForRemoval) { - addClass(element, LEAVE_CLASSNAME); - allLeaveNodes.push(element); - if (!details.hasAnimation) { - leaveNodesWithoutAnimations.push(element); - } - } - } - - cleanupFns.push(() => { - allEnterNodes.forEach(element => removeClass(element, ENTER_CLASSNAME)); - allLeaveNodes.forEach(element => { - removeClass(element, LEAVE_CLASSNAME); - this.processLeaveNode(element); - }); - }); - - const allPlayers: TransitionAnimationPlayer[] = []; - const erroneousTransitions: AnimationTransitionInstruction[] = []; - for (let i = this._namespaceList.length - 1; i >= 0; i--) { - const ns = this._namespaceList[i]; - ns.drainQueuedTransitions(microtaskId).forEach(entry => { - const player = entry.player; - allPlayers.push(player); - - const element = entry.element; - if (!bodyNode || !this.driver.containsElement(bodyNode, element)) { - player.destroy(); - return; - } - - const instruction = this._buildInstruction(entry, subTimelines) !; - if (instruction.errors && instruction.errors.length) { - erroneousTransitions.push(instruction); - return; - } - - // if a unmatched transition is queued to go then it SHOULD NOT render - // an animation and cancel the previously running animations. - if (entry.isFallbackTransition) { - player.onStart(() => eraseStyles(element, instruction.fromStyles)); - player.onDestroy(() => setStyles(element, instruction.toStyles)); - skippedPlayers.push(player); - return; - } - - // this means that if a parent animation uses this animation as a sub trigger - // then it will instruct the timeline builder to not add a player delay, but - // instead stretch the first keyframe gap up until the animation starts. The - // reason this is important is to prevent extra initialization styles from being - // required by the user in the animation. - instruction.timelines.forEach(tl => tl.stretchStartingKeyframe = true); - - subTimelines.append(element, instruction.timelines); - - const tuple = {instruction, player, element}; - - queuedInstructions.push(tuple); - - instruction.queriedElements.forEach( - element => getOrSetAsInMap(queriedElements, element, []).push(player)); - - instruction.preStyleProps.forEach((stringMap, element) => { - const props = Object.keys(stringMap); - if (props.length) { - let setVal: Set = allPreStyleElements.get(element) !; - if (!setVal) { - allPreStyleElements.set(element, setVal = new Set()); - } - props.forEach(prop => setVal.add(prop)); - } - }); - - instruction.postStyleProps.forEach((stringMap, element) => { - const props = Object.keys(stringMap); - let setVal: Set = allPostStyleElements.get(element) !; - if (!setVal) { - allPostStyleElements.set(element, setVal = new Set()); - } - props.forEach(prop => setVal.add(prop)); - }); - }); - } - - if (erroneousTransitions.length) { - let msg = `Unable to process animations due to the following failed trigger transitions\n`; - erroneousTransitions.forEach(instruction => { - msg += `@${instruction.triggerName} has failed due to:\n`; - instruction.errors !.forEach(error => { msg += `- ${error}\n`; }); - }); - - allPlayers.forEach(player => player.destroy()); - throw new Error(msg); - } - - // these can only be detected here since we have a map of all the elements - // that have animations attached to them... - const enterNodesWithoutAnimations: any[] = []; - for (let i = 0; i < allEnterNodes.length; i++) { - const element = allEnterNodes[i]; - if (!subTimelines.has(element)) { - enterNodesWithoutAnimations.push(element); - } - } - - const allPreviousPlayersMap = new Map(); - let sortedParentElements: any[] = []; - queuedInstructions.forEach(entry => { - const element = entry.element; - if (subTimelines.has(element)) { - sortedParentElements.unshift(element); - this._beforeAnimationBuild( - entry.player.namespaceId, entry.instruction, allPreviousPlayersMap); - } - }); - - skippedPlayers.forEach(player => { - const element = player.element; - const previousPlayers = - this._getPreviousPlayers(element, false, player.namespaceId, player.triggerName, null); - previousPlayers.forEach( - prevPlayer => { getOrSetAsInMap(allPreviousPlayersMap, element, []).push(prevPlayer); }); - }); - - allPreviousPlayersMap.forEach(players => players.forEach(player => player.destroy())); - - // PRE STAGE: fill the ! styles - const preStylesMap = allPreStyleElements.size ? - cloakAndComputeStyles( - this.driver, enterNodesWithoutAnimations, allPreStyleElements, PRE_STYLE) : - new Map(); - - // POST STAGE: fill the * styles - const postStylesMap = cloakAndComputeStyles( - this.driver, leaveNodesWithoutAnimations, allPostStyleElements, AUTO_STYLE); - - const rootPlayers: TransitionAnimationPlayer[] = []; - const subPlayers: TransitionAnimationPlayer[] = []; - queuedInstructions.forEach(entry => { - const {element, player, instruction} = entry; - // this means that it was never consumed by a parent animation which - // means that it is independent and therefore should be set for animation - if (subTimelines.has(element)) { - if (disabledElementsSet.has(element)) { - skippedPlayers.push(player); - return; - } - - const innerPlayer = this._buildAnimation( - player.namespaceId, instruction, allPreviousPlayersMap, skippedPlayersMap, preStylesMap, - postStylesMap); - player.setRealPlayer(innerPlayer); - - let parentHasPriority: any = null; - for (let i = 0; i < sortedParentElements.length; i++) { - const parent = sortedParentElements[i]; - if (parent === element) break; - if (this.driver.containsElement(parent, element)) { - parentHasPriority = parent; - break; - } - } - - if (parentHasPriority) { - const parentPlayers = this.playersByElement.get(parentHasPriority); - if (parentPlayers && parentPlayers.length) { - player.parentPlayer = optimizeGroupPlayer(parentPlayers); - } - skippedPlayers.push(player); - } else { - rootPlayers.push(player); - } - } else { - eraseStyles(element, instruction.fromStyles); - player.onDestroy(() => setStyles(element, instruction.toStyles)); - subPlayers.push(player); - } - }); - - subPlayers.forEach(player => { - const playersForElement = skippedPlayersMap.get(player.element); - if (playersForElement && playersForElement.length) { - const innerPlayer = optimizeGroupPlayer(playersForElement); - player.setRealPlayer(innerPlayer); - } - }); - - // the reason why we don't actually play the animation is - // because all that a skipped player is designed to do is to - // fire the start/done transition callback events - skippedPlayers.forEach(player => { - if (player.parentPlayer) { - player.parentPlayer.onDestroy(() => player.destroy()); - } else { - player.destroy(); - } - }); - - // run through all of the queued removals and see if they - // were picked up by a query. If not then perform the removal - // operation right away unless a parent animation is ongoing. - for (let i = 0; i < allLeaveNodes.length; i++) { - const element = allLeaveNodes[i]; - const details = element[REMOVAL_FLAG] as ElementAnimationState; - removeClass(element, LEAVE_CLASSNAME); - - // this means the element has a removal animation that is being - // taken care of and therefore the inner elements will hang around - // until that animation is over (or the parent queried animation) - if (details && details.hasAnimation) continue; - - let players: AnimationPlayer[] = []; - - // if this element is queried or if it contains queried children - // then we want for the element not to be removed from the page - // until the queried animations have finished - if (queriedElements.size) { - let queriedPlayerResults = queriedElements.get(element); - if (queriedPlayerResults && queriedPlayerResults.length) { - players.push(...queriedPlayerResults); - } - - let queriedInnerElements = this.driver.query(element, NG_ANIMATING_SELECTOR, true); - for (let j = 0; j < queriedInnerElements.length; j++) { - let queriedPlayers = queriedElements.get(queriedInnerElements[j]); - if (queriedPlayers && queriedPlayers.length) { - players.push(...queriedPlayers); - } - } - } - if (players.length) { - removeNodesAfterAnimationDone(this, element, players); - } else { - this.processLeaveNode(element); - } - } - - // this is required so the cleanup method doesn't remove them - allLeaveNodes.length = 0; - - rootPlayers.forEach(player => { - this.players.push(player); - player.onDone(() => { - player.destroy(); - - const index = this.players.indexOf(player); - this.players.splice(index, 1); - }); - player.play(); - }); - - return rootPlayers; - } - - elementContainsData(namespaceId: string, element: any) { - let containsData = false; - const details = element[REMOVAL_FLAG] as ElementAnimationState; - if (details && details.setForRemoval) containsData = true; - if (this.playersByElement.has(element)) containsData = true; - if (this.playersByQueriedElement.has(element)) containsData = true; - if (this.statesByElement.has(element)) containsData = true; - return this._fetchNamespace(namespaceId).elementContainsData(element) || containsData; - } - - afterFlush(callback: () => any) { this._flushFns.push(callback); } - - afterFlushAnimationsDone(callback: () => any) { this._whenQuietFns.push(callback); } - - private _getPreviousPlayers( - element: string, isQueriedElement: boolean, namespaceId?: string, triggerName?: string, - toStateValue?: any): TransitionAnimationPlayer[] { - let players: TransitionAnimationPlayer[] = []; - if (isQueriedElement) { - const queriedElementPlayers = this.playersByQueriedElement.get(element); - if (queriedElementPlayers) { - players = queriedElementPlayers; - } - } else { - const elementPlayers = this.playersByElement.get(element); - if (elementPlayers) { - const isRemovalAnimation = !toStateValue || toStateValue == VOID_VALUE; - elementPlayers.forEach(player => { - if (player.queued) return; - if (!isRemovalAnimation && player.triggerName != triggerName) return; - players.push(player); - }); - } - } - if (namespaceId || triggerName) { - players = players.filter(player => { - if (namespaceId && namespaceId != player.namespaceId) return false; - if (triggerName && triggerName != player.triggerName) return false; - return true; - }); - } - return players; - } - - private _beforeAnimationBuild( - namespaceId: string, instruction: AnimationTransitionInstruction, - allPreviousPlayersMap: Map) { - // it's important to do this step before destroying the players - // so that the onDone callback below won't fire before this - eraseStyles(instruction.element, instruction.fromStyles); - - const triggerName = instruction.triggerName; - const rootElement = instruction.element; - - // when a removal animation occurs, ALL previous players are collected - // and destroyed (even if they are outside of the current namespace) - const targetNameSpaceId: string|undefined = - instruction.isRemovalTransition ? undefined : namespaceId; - const targetTriggerName: string|undefined = - instruction.isRemovalTransition ? undefined : triggerName; - - instruction.timelines.map(timelineInstruction => { - const element = timelineInstruction.element; - const isQueriedElement = element !== rootElement; - const players = getOrSetAsInMap(allPreviousPlayersMap, element, []); - const previousPlayers = this._getPreviousPlayers( - element, isQueriedElement, targetNameSpaceId, targetTriggerName, instruction.toState); - previousPlayers.forEach(player => { - const realPlayer = player.getRealPlayer() as any; - if (realPlayer.beforeDestroy) { - realPlayer.beforeDestroy(); - } - players.push(player); - }); - }); - } - - private _buildAnimation( - namespaceId: string, instruction: AnimationTransitionInstruction, - allPreviousPlayersMap: Map, - skippedPlayersMap: Map, preStylesMap: Map, - postStylesMap: Map): AnimationPlayer { - const triggerName = instruction.triggerName; - const rootElement = instruction.element; - - // we first run this so that the previous animation player - // data can be passed into the successive animation players - const allQueriedPlayers: TransitionAnimationPlayer[] = []; - const allConsumedElements = new Set(); - const allSubElements = new Set(); - const allNewPlayers = instruction.timelines.map(timelineInstruction => { - const element = timelineInstruction.element; - allConsumedElements.add(element); - - // FIXME (matsko): make sure to-be-removed animations are removed properly - const details = element[REMOVAL_FLAG]; - if (details && details.removedBeforeQueried) return new NoopAnimationPlayer(); - - const isQueriedElement = element !== rootElement; - const previousPlayers = - (allPreviousPlayersMap.get(element) || EMPTY_PLAYER_ARRAY).map(p => p.getRealPlayer()); - - const preStyles = preStylesMap.get(element); - const postStyles = postStylesMap.get(element); - const keyframes = normalizeKeyframes( - this.driver, this._normalizer, element, timelineInstruction.keyframes, preStyles, - postStyles); - const player = this._buildPlayer(timelineInstruction, keyframes, previousPlayers); - - // this means that this particular player belongs to a sub trigger. It is - // important that we match this player up with the corresponding (@trigger.listener) - if (timelineInstruction.subTimeline && skippedPlayersMap) { - allSubElements.add(element); - } - - if (isQueriedElement) { - const wrappedPlayer = new TransitionAnimationPlayer(namespaceId, triggerName, element); - wrappedPlayer.setRealPlayer(player); - allQueriedPlayers.push(wrappedPlayer); - } - - return player; - }); - - allQueriedPlayers.forEach(player => { - getOrSetAsInMap(this.playersByQueriedElement, player.element, []).push(player); - player.onDone(() => deleteOrUnsetInMap(this.playersByQueriedElement, player.element, player)); - }); - - allConsumedElements.forEach(element => addClass(element, NG_ANIMATING_CLASSNAME)); - const player = optimizeGroupPlayer(allNewPlayers); - player.onDestroy(() => { - allConsumedElements.forEach(element => removeClass(element, NG_ANIMATING_CLASSNAME)); - setStyles(rootElement, instruction.toStyles); - }); - - // this basically makes all of the callbacks for sub element animations - // be dependent on the upper players for when they finish - allSubElements.forEach( - element => { getOrSetAsInMap(skippedPlayersMap, element, []).push(player); }); - - return player; - } - - private _buildPlayer( - instruction: AnimationTimelineInstruction, keyframes: ɵStyleData[], - previousPlayers: AnimationPlayer[]): AnimationPlayer { - if (keyframes.length > 0) { - return this.driver.animate( - instruction.element, keyframes, instruction.duration, instruction.delay, - instruction.easing, previousPlayers); - } - - // special case for when an empty transition|definition is provided - // ... there is no point in rendering an empty animation - return new NoopAnimationPlayer(); - } -} - -export class TransitionAnimationPlayer implements AnimationPlayer { - private _player: AnimationPlayer = new NoopAnimationPlayer(); - private _containsRealPlayer = false; - - private _queuedCallbacks: {[name: string]: (() => any)[]} = {}; - private _destroyed = false; - public parentPlayer: AnimationPlayer; - - public markedForDestroy: boolean = false; - - constructor(public namespaceId: string, public triggerName: string, public element: any) {} - - get queued() { return this._containsRealPlayer == false; } - - get destroyed() { return this._destroyed; } - - setRealPlayer(player: AnimationPlayer) { - if (this._containsRealPlayer) return; - - this._player = player; - Object.keys(this._queuedCallbacks).forEach(phase => { - this._queuedCallbacks[phase].forEach( - callback => listenOnPlayer(player, phase, undefined, callback)); - }); - this._queuedCallbacks = {}; - this._containsRealPlayer = true; - } - - getRealPlayer() { return this._player; } - - private _queueEvent(name: string, callback: (event: any) => any): void { - getOrSetAsInMap(this._queuedCallbacks, name, []).push(callback); - } - - onDone(fn: () => void): void { - if (this.queued) { - this._queueEvent('done', fn); - } - this._player.onDone(fn); - } - - onStart(fn: () => void): void { - if (this.queued) { - this._queueEvent('start', fn); - } - this._player.onStart(fn); - } - - onDestroy(fn: () => void): void { - if (this.queued) { - this._queueEvent('destroy', fn); - } - this._player.onDestroy(fn); - } - - init(): void { this._player.init(); } - - hasStarted(): boolean { return this.queued ? false : this._player.hasStarted(); } - - play(): void { !this.queued && this._player.play(); } - - pause(): void { !this.queued && this._player.pause(); } - - restart(): void { !this.queued && this._player.restart(); } - - finish(): void { this._player.finish(); } - - destroy(): void { - this._destroyed = true; - this._player.destroy(); - } - - reset(): void { !this.queued && this._player.reset(); } - - setPosition(p: any): void { - if (!this.queued) { - this._player.setPosition(p); - } - } - - getPosition(): number { return this.queued ? 0 : this._player.getPosition(); } - - get totalTime(): number { return this._player.totalTime; } -} - -function deleteOrUnsetInMap(map: Map| {[key: string]: any}, key: any, value: any) { - let currentValues: any[]|null|undefined; - if (map instanceof Map) { - currentValues = map.get(key); - if (currentValues) { - if (currentValues.length) { - const index = currentValues.indexOf(value); - currentValues.splice(index, 1); - } - if (currentValues.length == 0) { - map.delete(key); - } - } - } else { - currentValues = map[key]; - if (currentValues) { - if (currentValues.length) { - const index = currentValues.indexOf(value); - currentValues.splice(index, 1); - } - if (currentValues.length == 0) { - delete map[key]; - } - } - } - return currentValues; -} - -function normalizeTriggerValue(value: any): string { - switch (typeof value) { - case 'boolean': - return value ? '1' : '0'; - default: - return value != null ? value.toString() : null; - } -} - -function isElementNode(node: any) { - return node && node['nodeType'] === 1; -} - -function isTriggerEventValid(eventName: string): boolean { - return eventName == 'start' || eventName == 'done'; -} - -function cloakElement(element: any, value?: string) { - const oldValue = element.style.display; - element.style.display = value != null ? value : 'none'; - return oldValue; -} - -function cloakAndComputeStyles( - driver: AnimationDriver, elements: any[], elementPropsMap: Map>, - defaultStyle: string): Map { - const cloakVals = elements.map(element => cloakElement(element)); - const valuesMap = new Map(); - - elementPropsMap.forEach((props: Set, element: any) => { - const styles: ɵStyleData = {}; - props.forEach(prop => { - const value = styles[prop] = driver.computeStyle(element, prop, defaultStyle); - - // there is no easy way to detect this because a sub element could be removed - // by a parent animation element being detached. - if (!value || value.length == 0) { - element[REMOVAL_FLAG] = NULL_REMOVED_QUERIED_STATE; - } - }); - valuesMap.set(element, styles); - }); - - elements.forEach((element, i) => cloakElement(element, cloakVals[i])); - return valuesMap; -} - -/* -Since the Angular renderer code will return a collection of inserted -nodes in all areas of a DOM tree, it's up to this algorithm to figure -out which nodes are roots. -By placing all nodes into a set and traversing upwards to the edge, -the recursive code can figure out if a clean path from the DOM node -to the edge container is clear. If no other node is detected in the -set then it is a root element. -This algorithm also keeps track of all nodes along the path so that -if other sibling nodes are also tracked then the lookup process can -skip a lot of steps in between and avoid traversing the entire tree -multiple times to the edge. - */ -function createIsRootFilterFn(nodes: any): (node: any) => boolean { - const nodeSet = new Set(nodes); - const knownRootContainer = new Set(); - let isRoot: (node: any) => boolean; - isRoot = node => { - if (!node) return true; - if (nodeSet.has(node.parentNode)) return false; - if (knownRootContainer.has(node.parentNode)) return true; - if (isRoot(node.parentNode)) { - knownRootContainer.add(node); - return true; - } - return false; - }; - return isRoot; -} - -const CLASSES_CACHE_KEY = '$$classes'; -function containsClass(element: any, className: string): boolean { - if (element.classList) { - return element.classList.contains(className); - } else { - const classes = element[CLASSES_CACHE_KEY]; - return classes && classes[className]; - } -} - -function addClass(element: any, className: string) { - if (element.classList) { - element.classList.add(className); - } else { - let classes: {[className: string]: boolean} = element[CLASSES_CACHE_KEY]; - if (!classes) { - classes = element[CLASSES_CACHE_KEY] = {}; - } - classes[className] = true; - } -} - -function removeClass(element: any, className: string) { - if (element.classList) { - element.classList.remove(className); - } else { - let classes: {[className: string]: boolean} = element[CLASSES_CACHE_KEY]; - if (classes) { - delete classes[className]; - } - } -} - -function getBodyNode(): any|null { - if (typeof document != 'undefined') { - return document.body; - } - return null; -} - -function removeNodesAfterAnimationDone( - engine: TransitionAnimationEngine, element: any, players: AnimationPlayer[]) { - optimizeGroupPlayer(players).onDone(() => engine.processLeaveNode(element)); -} \ No newline at end of file diff --git a/nativescript-angular/animations/private-imports/util.ts b/nativescript-angular/animations/private-imports/util.ts deleted file mode 100644 index f7f7c69b8..000000000 --- a/nativescript-angular/animations/private-imports/util.ts +++ /dev/null @@ -1,220 +0,0 @@ -/** - * @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 - */ -/* tslint:disable */ -import {AnimateTimings, AnimationMetadata, AnimationOptions, sequence, ɵStyleData} from '@angular/animations'; - -export const ONE_SECOND = 1000; - -export const ENTER_CLASSNAME = 'ng-enter'; -export const LEAVE_CLASSNAME = 'ng-leave'; -export const ENTER_SELECTOR = '.ng-enter'; -export const LEAVE_SELECTOR = '.ng-leave'; -export const NG_TRIGGER_CLASSNAME = 'ng-trigger'; -export const NG_TRIGGER_SELECTOR = '.ng-trigger'; -export const NG_ANIMATING_CLASSNAME = 'ng-animating'; -export const NG_ANIMATING_SELECTOR = '.ng-animating'; - -export function resolveTimingValue(value: string | number) { - if (typeof value == 'number') return value; - - const matches = (value as string).match(/^(-?[\.\d]+)(m?s)/); - if (!matches || matches.length < 2) return 0; - - return _convertTimeValueToMS(parseFloat(matches[1]), matches[2]); -} - -function _convertTimeValueToMS(value: number, unit: string): number { - switch (unit) { - case 's': - return value * ONE_SECOND; - default: // ms or something else - return value; - } -} - -export function resolveTiming( - timings: string | number | AnimateTimings, errors: any[], allowNegativeValues?: boolean) { - return timings.hasOwnProperty('duration') ? - timings : - parseTimeExpression(timings, errors, allowNegativeValues); -} - -function parseTimeExpression( - exp: string | number, errors: string[], allowNegativeValues?: boolean): AnimateTimings { - const regex = /^(-?[\.\d]+)(m?s)(?:\s+(-?[\.\d]+)(m?s))?(?:\s+([-a-z]+(?:\(.+?\))?))?$/i; - let duration: number; - let delay: number = 0; - let easing: string = ''; - if (typeof exp === 'string') { - const matches = exp.match(regex); - if (matches === null) { - errors.push(`The provided timing value "${exp}" is invalid.`); - return {duration: 0, delay: 0, easing: ''}; - } - - duration = _convertTimeValueToMS(parseFloat(matches[1]), matches[2]); - - const delayMatch = matches[3]; - if (delayMatch != null) { - delay = _convertTimeValueToMS(Math.floor(parseFloat(delayMatch)), matches[4]); - } - - const easingVal = matches[5]; - if (easingVal) { - easing = easingVal; - } - } else { - duration = exp; - } - - if (!allowNegativeValues) { - let containsErrors = false; - let startIndex = errors.length; - if (duration < 0) { - errors.push(`Duration values below 0 are not allowed for this animation step.`); - containsErrors = true; - } - if (delay < 0) { - errors.push(`Delay values below 0 are not allowed for this animation step.`); - containsErrors = true; - } - if (containsErrors) { - errors.splice(startIndex, 0, `The provided timing value "${exp}" is invalid.`); - } - } - - return {duration, delay, easing}; -} - -export function copyObj( - obj: {[key: string]: any}, destination: {[key: string]: any} = {}): {[key: string]: any} { - Object.keys(obj).forEach(prop => { destination[prop] = obj[prop]; }); - return destination; -} - -export function normalizeStyles(styles: ɵStyleData | ɵStyleData[]): ɵStyleData { - const normalizedStyles: ɵStyleData = {}; - if (Array.isArray(styles)) { - styles.forEach(data => copyStyles(data, false, normalizedStyles)); - } else { - copyStyles(styles, false, normalizedStyles); - } - return normalizedStyles; -} - -export function copyStyles( - styles: ɵStyleData, readPrototype: boolean, destination: ɵStyleData = {}): ɵStyleData { - if (readPrototype) { - // we make use of a for-in loop so that the - // prototypically inherited properties are - // revealed from the backFill map - for (let prop in styles) { - destination[prop] = styles[prop]; - } - } else { - copyObj(styles, destination); - } - return destination; -} - -export function setStyles(element: any, styles: ɵStyleData) { - if (element['style']) { - Object.keys(styles).forEach(prop => { - const camelProp = dashCaseToCamelCase(prop); - element.style[camelProp] = styles[prop]; - }); - } -} - -export function eraseStyles(element: any, styles: ɵStyleData) { - if (element['style']) { - Object.keys(styles).forEach(prop => { - const camelProp = dashCaseToCamelCase(prop); - element.style[camelProp] = ''; - }); - } -} - -export function normalizeAnimationEntry(steps: AnimationMetadata | AnimationMetadata[]): - AnimationMetadata { - if (Array.isArray(steps)) { - if (steps.length == 1) return steps[0]; - return sequence(steps); - } - return steps as AnimationMetadata; -} - -export function validateStyleParams( - value: string | number, options: AnimationOptions, errors: any[]) { - const params = options.params || {}; - if (typeof value !== 'string') return; - - const matches = value.toString().match(PARAM_REGEX); - if (matches) { - matches.forEach(varName => { - if (!params.hasOwnProperty(varName)) { - errors.push( - `Unable to resolve the local animation param ${varName} in the given list of values`); - } - }); - } -} - -const PARAM_REGEX = /\{\{\s*(.+?)\s*\}\}/g; -export function interpolateParams( - value: string | number, params: {[name: string]: any}, errors: any[]): string|number { - const original = value.toString(); - const str = original.replace(PARAM_REGEX, (_, varName) => { - let localVal = params[varName]; - // this means that the value was never overidden by the data passed in by the user - if (!params.hasOwnProperty(varName)) { - errors.push(`Please provide a value for the animation param ${varName}`); - localVal = ''; - } - return localVal.toString(); - }); - - // we do this to assert that numeric values stay as they are - return str == original ? value : str; -} - -export function iteratorToArray(iterator: any): any[] { - const arr: any[] = []; - let item = iterator.next(); - while (!item.done) { - arr.push(item.value); - item = iterator.next(); - } - return arr; -} - -export function mergeAnimationOptions( - source: AnimationOptions, destination: AnimationOptions): AnimationOptions { - if (source.params) { - const p0 = source.params; - if (!destination.params) { - destination.params = {}; - } - const p1 = destination.params; - Object.keys(p0).forEach(param => { - if (!p1.hasOwnProperty(param)) { - p1[param] = p0[param]; - } - }); - } - return destination; -} - -const DASH_CASE_REGEXP = /-+([a-z0-9])/g; -export function dashCaseToCamelCase(input: string): string { - return input.replace(DASH_CASE_REGEXP, (...m: any[]) => m[1].toUpperCase()); -} - -export function allowPreviousPlayerStylesMerge(duration: number, delay: number) { - return duration === 0 || delay === 0; -} \ No newline at end of file diff --git a/nativescript-angular/animations/transition-animation-engine.ts b/nativescript-angular/animations/transition-animation-engine.ts deleted file mode 100644 index e31bbea9d..000000000 --- a/nativescript-angular/animations/transition-animation-engine.ts +++ /dev/null @@ -1,501 +0,0 @@ -import { AUTO_STYLE, ɵPRE_STYLE as PRE_STYLE, AnimationPlayer, ɵStyleData } from "@angular/animations"; -import { AnimationDriver } from "@angular/animations/browser"; -import { AnimationTransitionInstruction } from "@angular/animations/browser/src/dsl/animation_transition_instruction"; -import { unsetValue } from "tns-core-modules/ui/core/view"; - -import { - TransitionAnimationEngine, - TransitionAnimationPlayer, - QueuedTransition, - ElementAnimationState, - REMOVAL_FLAG, -} from "./private-imports/render/transition_animation_engine"; -import { ElementInstructionMap } from "./private-imports/dsl/element_instruction_map"; -import { getOrSetAsInMap, optimizeGroupPlayer } from "./private-imports/render/shared"; -import { - ENTER_CLASSNAME, - LEAVE_CLASSNAME, - NG_ANIMATING_SELECTOR, -} from "./private-imports/util"; - -import { dashCaseToCamelCase } from "./utils"; -import { NgView } from "../element-registry"; - -function eraseStylesOverride(element: NgView, styles: ɵStyleData) { - if (!element.style) { - return; - } - - Object.keys(styles).forEach(prop => { - const camelCaseProp = dashCaseToCamelCase(prop); - element.style[camelCaseProp] = unsetValue; - }); -} - -function setStylesOverride(element: NgView, styles: ɵStyleData) { - if (!element.style) { - return; - } - - Object.keys(styles).forEach(prop => { - if (styles[prop] === "*") { - return; - } - - const camelCaseProp = dashCaseToCamelCase(prop); - element.style[camelCaseProp] = styles[camelCaseProp]; - }); -} - -// extending Angular's TransitionAnimationEngine -// and overriding a few methods that work on the DOM -export class NSTransitionAnimationEngine extends TransitionAnimationEngine { - flush(microtaskId: number = -1) { - let players: AnimationPlayer[] = []; - if (this.newHostElements.size) { - this.newHostElements.forEach((ns, element) => (this)._balanceNamespaceList(ns, element)); - this.newHostElements.clear(); - } - - if ((this)._namespaceList.length && - (this.totalQueuedPlayers || this.collectedLeaveElements.length)) { - players = this._flushAnimationsOverride(microtaskId); - } else { - for (let i = 0; i < this.collectedLeaveElements.length; i++) { - const element = this.collectedLeaveElements[i]; - this.processLeaveNode(element); - } - } - - this.totalQueuedPlayers = 0; - this.collectedEnterElements.length = 0; - this.collectedLeaveElements.length = 0; - (this)._flushFns.forEach(fn => fn()); - (this)._flushFns = []; - - if ((this)._whenQuietFns.length) { - // we move these over to a variable so that - // if any new callbacks are registered in another - // flush they do not populate the existing set - const quietFns = (this)._whenQuietFns; - (this)._whenQuietFns = []; - - if (players.length) { - optimizeGroupPlayer(players).onDone(() => { quietFns.forEach(fn => fn()); }); - } else { - quietFns.forEach(fn => fn()); - } - } - } - - // _flushAnimationsOverride is almost the same as - // _flushAnimations from Angular"s TransitionAnimationEngine. - // A few dom-specific method invocations are replaced - private _flushAnimationsOverride(microtaskId: number): TransitionAnimationPlayer[] { - const subTimelines = new ElementInstructionMap(); - const skippedPlayers: TransitionAnimationPlayer[] = []; - const skippedPlayersMap = new Map(); - const queuedInstructions: QueuedTransition[] = []; - const queriedElements = new Map(); - const allPreStyleElements = new Map>(); - const allPostStyleElements = new Map>(); - - const allEnterNodes: any[] = this.collectedEnterElements.length ? - this.collectedEnterElements.filter(createIsRootFilterFn(this.collectedEnterElements)) : - []; - - // this must occur before the instructions are built below such that - // the :enter queries match the elements (since the timeline queries - // are fired during instruction building). - for (let i = 0; i < allEnterNodes.length; i++) { - addClass(allEnterNodes[i], ENTER_CLASSNAME); - } - - const allLeaveNodes: any[] = []; - const leaveNodesWithoutAnimations: any[] = []; - for (let i = 0; i < this.collectedLeaveElements.length; i++) { - const element = this.collectedLeaveElements[i]; - const details = element[REMOVAL_FLAG] as ElementAnimationState; - if (details && details.setForRemoval) { - addClass(element, LEAVE_CLASSNAME); - allLeaveNodes.push(element); - if (!details.hasAnimation) { - leaveNodesWithoutAnimations.push(element); - } - } - } - - for (let i = (this)._namespaceList.length - 1; i >= 0; i--) { - const ns = (this)._namespaceList[i]; - ns.drainQueuedTransitions(microtaskId).forEach(entry => { - const player = entry.player; - - const element = entry.element; - - // the below check is skipped, because it's - // irrelevant in the NativeScript context - // if (!bodyNode || !this.driver.containsElement(bodyNode, element)) { - // player.destroy(); - // return; - // } - - const instruction = (this)._buildInstruction(entry, subTimelines); - if (!instruction) { - return; - } - - // if a unmatched transition is queued to go then it SHOULD NOT render - // an animation and cancel the previously running animations. - if (entry.isFallbackTransition) { - player.onStart(() => eraseStylesOverride(element, instruction.fromStyles)); - player.onDestroy(() => setStylesOverride(element, instruction.toStyles)); - skippedPlayers.push(player); - return; - } - - // this means that if a parent animation uses this animation as a sub trigger - // then it will instruct the timeline builder to not add a player delay, but - // instead stretch the first keyframe gap up until the animation starts. The - // reason this is important is to prevent extra initialization styles from being - // required by the user in the animation. - instruction.timelines.forEach(tl => tl.stretchStartingKeyframe = true); - - subTimelines.append(element, instruction.timelines); - - const tuple = { instruction, player, element }; - - queuedInstructions.push(tuple); - - instruction.queriedElements.forEach( - // tslint:disable-next-line:no-shadowed-variable - element => getOrSetAsInMap(queriedElements, element, []).push(player)); - - // tslint:disable-next-line:no-shadowed-variable - instruction.preStyleProps.forEach((stringMap, element) => { - const props = Object.keys(stringMap); - if (props.length) { - let setVal: Set = allPreStyleElements.get(element)!; - if (!setVal) { - allPreStyleElements.set(element, setVal = new Set()); - } - props.forEach(prop => setVal.add(prop)); - } - }); - - // tslint:disable-next-line:no-shadowed-variable - instruction.postStyleProps.forEach((stringMap, element) => { - const props = Object.keys(stringMap); - let setVal: Set = allPostStyleElements.get(element)!; - if (!setVal) { - allPostStyleElements.set(element, setVal = new Set()); - } - props.forEach(prop => setVal.add(prop)); - }); - }); - } - - // these can only be detected here since we have a map of all the elements - // that have animations attached to them... - const enterNodesWithoutAnimations: any[] = []; - for (let i = 0; i < allEnterNodes.length; i++) { - const element = allEnterNodes[i]; - if (!subTimelines.has(element)) { - enterNodesWithoutAnimations.push(element); - } - } - - const allPreviousPlayersMap = new Map(); - let sortedParentElements: any[] = []; - queuedInstructions.forEach(entry => { - const element = entry.element; - if (subTimelines.has(element)) { - sortedParentElements.unshift(element); - this._beforeAnimationBuildOverride( - entry.player.namespaceId, entry.instruction, allPreviousPlayersMap); - } - }); - - skippedPlayers.forEach(player => { - const element = player.element; - const previousPlayers = - (this)._getPreviousPlayers(element, false, player.namespaceId, player.triggerName, null); - previousPlayers.forEach( - prevPlayer => { getOrSetAsInMap(allPreviousPlayersMap, element, []).push(prevPlayer); }); - }); - - allPreviousPlayersMap.forEach(players => players.forEach(player => player.destroy())); - - // PRE STAGE: fill the ! styles - const preStylesMap = allPreStyleElements.size ? - cloakAndComputeStyles( - this.driver, enterNodesWithoutAnimations, allPreStyleElements, PRE_STYLE) : - new Map(); - - // POST STAGE: fill the * styles - const postStylesMap = cloakAndComputeStyles( - this.driver, leaveNodesWithoutAnimations, allPostStyleElements, AUTO_STYLE); - - const rootPlayers: TransitionAnimationPlayer[] = []; - const subPlayers: TransitionAnimationPlayer[] = []; - queuedInstructions.forEach(entry => { - const { element, player, instruction } = entry; - // this means that it was never consumed by a parent animation which - // means that it is independent and therefore should be set for animation - if (subTimelines.has(element)) { - const innerPlayer = (this)._buildAnimation( - player.namespaceId, instruction, allPreviousPlayersMap, skippedPlayersMap, preStylesMap, - postStylesMap); - player.setRealPlayer(innerPlayer); - - let parentHasPriority: any = null; - for (let i = 0; i < sortedParentElements.length; i++) { - const parent = sortedParentElements[i]; - - if (parent === element) { - break; - } - - if (this.driver.containsElement(parent, element)) { - parentHasPriority = parent; - break; - } - } - - if (parentHasPriority) { - const parentPlayers = this.playersByElement.get(parentHasPriority); - if (parentPlayers && parentPlayers.length) { - player.parentPlayer = optimizeGroupPlayer(parentPlayers); - } - skippedPlayers.push(player); - } else { - rootPlayers.push(player); - } - } else { - eraseStylesOverride(element, instruction.fromStyles); - player.onDestroy(() => setStylesOverride(element, instruction.toStyles)); - subPlayers.push(player); - } - }); - - subPlayers.forEach(player => { - const playersForElement = skippedPlayersMap.get(player.element); - if (playersForElement && playersForElement.length) { - const innerPlayer = optimizeGroupPlayer(playersForElement); - player.setRealPlayer(innerPlayer); - } - }); - - // the reason why we don"t actually play the animation is - // because all that a skipped player is designed to do is to - // fire the start/done transition callback events - skippedPlayers.forEach(player => { - if (player.parentPlayer) { - player.parentPlayer.onDestroy(() => player.destroy()); - } else { - player.destroy(); - } - }); - - // run through all of the queued removals and see if they - // were picked up by a query. If not then perform the removal - // operation right away unless a parent animation is ongoing. - for (let i = 0; i < allLeaveNodes.length; i++) { - const element = allLeaveNodes[i]; - const details = element[REMOVAL_FLAG] as ElementAnimationState; - - // this means the element has a removal animation that is being - // taken care of and therefore the inner elements will hang around - // until that animation is over (or the parent queried animation) - if (details && details.hasAnimation) { - continue; - } - - let players: AnimationPlayer[] = []; - - // if this element is queried or if it contains queried children - // then we want for the element not to be removed from the page - // until the queried animations have finished - if (queriedElements.size) { - let queriedPlayerResults = queriedElements.get(element); - if (queriedPlayerResults && queriedPlayerResults.length) { - players.push(...queriedPlayerResults); - } - - let queriedInnerElements = this.driver.query(element, NG_ANIMATING_SELECTOR, true); - for (let j = 0; j < queriedInnerElements.length; j++) { - let queriedPlayers = queriedElements.get(queriedInnerElements[j]); - if (queriedPlayers && queriedPlayers.length) { - players.push(...queriedPlayers); - } - } - } - if (players.length) { - removeNodesAfterAnimationDone(this, element, players); - } else { - this.processLeaveNode(element); - } - } - - rootPlayers.forEach(player => { - this.players.push(player); - player.onDone(() => { - player.destroy(); - - const index = this.players.indexOf(player); - this.players.splice(index, 1); - }); - player.play(); - }); - - allEnterNodes.forEach(element => removeClass(element, ENTER_CLASSNAME)); - - return rootPlayers; - } - - elementContainsData(namespaceId: string, element: any) { - let containsData = false; - const details = element[REMOVAL_FLAG] as ElementAnimationState; - - if (details && details.setForRemoval) { - containsData = true; - } - - if (this.playersByElement.has(element)) { - containsData = true; - } - - if (this.playersByQueriedElement.has(element)) { - containsData = true; - } - - if (this.statesByElement.has(element)) { - containsData = true; - } - - return (this)._fetchNamespace(namespaceId).elementContainsData(element) || containsData; - } - - private _beforeAnimationBuildOverride( - namespaceId: string, instruction: AnimationTransitionInstruction, - allPreviousPlayersMap: Map) { - // it"s important to do this step before destroying the players - // so that the onDone callback below won"t fire before this - eraseStylesOverride(instruction.element, instruction.fromStyles); - - const triggerName = instruction.triggerName; - const rootElement = instruction.element; - - // when a removal animation occurs, ALL previous players are collected - // and destroyed (even if they are outside of the current namespace) - const targetNameSpaceId: string | undefined = - instruction.isRemovalTransition ? undefined : namespaceId; - const targetTriggerName: string | undefined = - instruction.isRemovalTransition ? undefined : triggerName; - - instruction.timelines.map(timelineInstruction => { - const element = timelineInstruction.element; - const isQueriedElement = element !== rootElement; - const players = getOrSetAsInMap(allPreviousPlayersMap, element, []); - const previousPlayers = (this)._getPreviousPlayers( - element, isQueriedElement, targetNameSpaceId, targetTriggerName, instruction.toState); - previousPlayers.forEach(player => { - const realPlayer = player.getRealPlayer() as any; - if (realPlayer.beforeDestroy) { - realPlayer.beforeDestroy(); - } - players.push(player); - }); - }); - } -} - - -function cloakElement(element: any, value?: string) { - const oldValue = element.style.display; - element.style.display = value != null ? value : "none"; - return oldValue; -} - -function cloakAndComputeStyles( - driver: AnimationDriver, elements: any[], elementPropsMap: Map>, - defaultStyle: string): Map { - const cloakVals = elements.map(element => cloakElement(element)); - const valuesMap = new Map(); - - elementPropsMap.forEach((props: Set, element: any) => { - const styles: ɵStyleData = {}; - props.forEach(prop => { - styles[prop] = driver.computeStyle(element, prop, defaultStyle); - }); - valuesMap.set(element, styles); - }); - - elements.forEach((element, i) => cloakElement(element, cloakVals[i])); - return valuesMap; -} - -/* -Since the Angular renderer code will return a collection of inserted -nodes in all areas of a DOM tree, it"s up to this algorithm to figure -out which nodes are roots. -By placing all nodes into a set and traversing upwards to the edge, -the recursive code can figure out if a clean path from the DOM node -to the edge container is clear. If no other node is detected in the -set then it is a root element. -This algorithm also keeps track of all nodes along the path so that -if other sibling nodes are also tracked then the lookup process can -skip a lot of steps in between and avoid traversing the entire tree -multiple times to the edge. - */ -function createIsRootFilterFn(nodes: any): (node: any) => boolean { - const nodeSet = new Set(nodes); - const knownRootContainer = new Set(); - let isRoot: (node: any) => boolean; - isRoot = node => { - if (!node) { - return true; - } - if (nodeSet.has(node.parentNode)) { - return false; - } - if (knownRootContainer.has(node.parentNode)) { - return true; - } - if (isRoot(node.parentNode)) { - knownRootContainer.add(node); - return true; - } - return false; - }; - return isRoot; -} - -const CLASSES_CACHE_KEY = "$$classes"; - -function addClass(element: any, className: string) { - if (element.classList) { - element.classList.add(className); - } else { - let classes: { [className: string]: boolean } = element[CLASSES_CACHE_KEY]; - if (!classes) { - classes = element[CLASSES_CACHE_KEY] = {}; - } - classes[className] = true; - } -} - -function removeClass(element: any, className: string) { - if (element.classList) { - element.classList.remove(className); - } else { - let classes: { [className: string]: boolean } = element[CLASSES_CACHE_KEY]; - if (classes) { - delete classes[className]; - } - } -} - -function removeNodesAfterAnimationDone( - engine: TransitionAnimationEngine, element: any, players: AnimationPlayer[]) { - optimizeGroupPlayer(players).onDone(() => engine.processLeaveNode(element)); -} From 441031122cfb9f644780b4a8cca8a2cf55fe827b Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Tue, 5 Dec 2017 15:50:06 -0800 Subject: [PATCH 07/16] refactor: unify parentNode access The logic for setting/getting template parent is delegated to the ViewBase class. --- nativescript-angular/directives/action-bar.ts | 8 ++++---- nativescript-angular/element-registry.ts | 4 ++-- nativescript-angular/renderer.ts | 2 +- nativescript-angular/view-util.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/nativescript-angular/directives/action-bar.ts b/nativescript-angular/directives/action-bar.ts index 1b9053dd2..d559d47b1 100644 --- a/nativescript-angular/directives/action-bar.ts +++ b/nativescript-angular/directives/action-bar.ts @@ -34,10 +34,10 @@ const actionBarMeta: ViewClassMeta = { return; } else if (isNavigationButton(child)) { parent.navigationButton = child; - child.templateParent = parent; + child.parentNode = parent; } else if (isActionItem(child)) { addActionItem(parent, child, next); - child.templateParent = parent; + child.parentNode = parent; } else if (isView(child)) { parent.titleView = child; } @@ -50,10 +50,10 @@ const actionBarMeta: ViewClassMeta = { parent.navigationButton = null; } - child.templateParent = null; + child.parentNode = null; } else if (isActionItem(child)) { parent.actionItems.removeItem(child); - child.templateParent = null; + child.parentNode = null; } else if (isView(child) && parent.titleView && parent.titleView === child) { parent.titleView = null; } diff --git a/nativescript-angular/element-registry.ts b/nativescript-angular/element-registry.ts index 35f111cf8..3a86b93f2 100644 --- a/nativescript-angular/element-registry.ts +++ b/nativescript-angular/element-registry.ts @@ -7,7 +7,7 @@ export interface ViewExtensions { meta: ViewClassMeta; nodeType: number; nodeName: string; - templateParent: NgView; + parentNode: NgView; nextSibling: NgView; firstChild: NgView; lastChild: NgView; @@ -22,7 +22,7 @@ export abstract class InvisibleNode extends View implements NgView { meta: { skipAddToDom: boolean }; nodeType: number; nodeName: string; - templateParent: NgView; + parentNode: NgView; nextSibling: NgView; firstChild: NgView; lastChild: NgView; diff --git a/nativescript-angular/renderer.ts b/nativescript-angular/renderer.ts index cdf550fca..405f1c91d 100644 --- a/nativescript-angular/renderer.ts +++ b/nativescript-angular/renderer.ts @@ -116,7 +116,7 @@ export class NativeScriptRenderer extends Renderer2 { @profile parentNode(node: NgView): any { traceLog(`NativeScriptRenderer.parentNode for node: ${node}`); - return node.parent || node.templateParent; + return node.parentNode; } @profile diff --git a/nativescript-angular/view-util.ts b/nativescript-angular/view-util.ts index f1075bac0..9e86ba416 100644 --- a/nativescript-angular/view-util.ts +++ b/nativescript-angular/view-util.ts @@ -69,7 +69,7 @@ export class ViewUtil { this.addToQueue(extendedParent, extendedChild, previous, next); if (isInvisibleNode(child)) { - extendedChild.templateParent = extendedParent; + extendedChild.parentNode = extendedParent; } if (!isDetachedElement(child)) { From b211c42a8e9380b64786e688d6c288f217a20306 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Tue, 5 Dec 2017 15:51:21 -0800 Subject: [PATCH 08/16] refactor: remove NativeScriptAnimationEngine --- .../animations/animation-engine.ts | 20 ------------------- .../animations/animations.module.ts | 12 +++++------ 2 files changed, 6 insertions(+), 26 deletions(-) delete mode 100644 nativescript-angular/animations/animation-engine.ts diff --git a/nativescript-angular/animations/animation-engine.ts b/nativescript-angular/animations/animation-engine.ts deleted file mode 100644 index 5dbf7d0a2..000000000 --- a/nativescript-angular/animations/animation-engine.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - AnimationDriver, - ɵAnimationEngine as AnimationEngine, - ɵAnimationStyleNormalizer as AnimationStyleNormalizer, -} from "@angular/animations/browser"; - -import { NSTransitionAnimationEngine } from "./transition-animation-engine"; - -export class NativeScriptAnimationEngine extends AnimationEngine { - constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) { - super(driver, normalizer); - - (this)._transitionEngine.onRemovalComplete = (element, delegate) => { - const parent = delegate && delegate.parentNode(element); - if (parent) { - delegate.removeChild(parent, element); - } - }; - } -} diff --git a/nativescript-angular/animations/animations.module.ts b/nativescript-angular/animations/animations.module.ts index 97481399f..27e2724ee 100644 --- a/nativescript-angular/animations/animations.module.ts +++ b/nativescript-angular/animations/animations.module.ts @@ -6,6 +6,7 @@ import { AnimationDriver, ɵAnimationStyleNormalizer as AnimationStyleNormalizer, ɵWebAnimationsStyleNormalizer as WebAnimationsStyleNormalizer, + ɵAnimationEngine as AnimationEngine, } from "@angular/animations/browser"; import { @@ -13,10 +14,9 @@ import { ɵBrowserAnimationBuilder as BrowserAnimationBuilder, } from "@angular/platform-browser/animations"; -import { NativeScriptAnimationEngine } from "./animation-engine"; -import { NativeScriptAnimationDriver } from "./animation-driver"; import { NativeScriptModule } from "../nativescript.module"; import { NativeScriptRendererFactory } from "../renderer"; +import { NativeScriptAnimationDriver } from "./animation-driver"; (global).document = { body: { @@ -25,7 +25,7 @@ import { NativeScriptRendererFactory } from "../renderer"; }; @Injectable() -export class InjectableAnimationEngine extends NativeScriptAnimationEngine { +export class InjectableAnimationEngine extends AnimationEngine { constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) { super(driver, normalizer); } @@ -36,7 +36,7 @@ export function instantiateSupportedAnimationDriver() { } export function instantiateRendererFactory( - renderer: NativeScriptRendererFactory, engine: NativeScriptAnimationEngine, zone: NgZone) { + renderer: NativeScriptRendererFactory, engine: AnimationEngine, zone: NgZone) { return new AnimationRendererFactory(renderer, engine, zone); } @@ -48,11 +48,11 @@ export const NATIVESCRIPT_ANIMATIONS_PROVIDERS: Provider[] = [ {provide: AnimationBuilder, useClass: BrowserAnimationBuilder}, {provide: AnimationDriver, useFactory: instantiateSupportedAnimationDriver}, {provide: AnimationStyleNormalizer, useFactory: instantiateDefaultStyleNormalizer}, - {provide: NativeScriptAnimationEngine, useClass: InjectableAnimationEngine}, + {provide: AnimationEngine, useClass: InjectableAnimationEngine}, { provide: RendererFactory2, useFactory: instantiateRendererFactory, - deps: [NativeScriptRendererFactory, NativeScriptAnimationEngine, NgZone] + deps: [NativeScriptRendererFactory, AnimationEngine, NgZone] } ]; From b3c32efe8c753be5ed66d75a6ff35856cf327f7f Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Tue, 5 Dec 2017 16:34:26 -0800 Subject: [PATCH 09/16] chore(ng-sample): update deps --- ng-sample/package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ng-sample/package.json b/ng-sample/package.json index 6009d22fd..52afd3cc9 100644 --- a/ng-sample/package.json +++ b/ng-sample/package.json @@ -32,17 +32,17 @@ }, "homepage": "https://github.com/NativeScript/template-hello-world", "dependencies": { - "@angular/animations": "~4.4.1", - "@angular/common": "~4.4.1", - "@angular/compiler": "~4.4.1", - "@angular/core": "~4.4.1", - "@angular/forms": "~4.4.1", - "@angular/http": "~4.4.1", - "@angular/platform-browser": "~4.4.1", - "@angular/platform-browser-dynamic": "~4.4.1", - "@angular/router": "~4.4.1", + "@angular/animations": "~5.0.0", + "@angular/common": "~5.0.0", + "@angular/compiler": "~5.0.0", + "@angular/core": "~5.0.0", + "@angular/forms": "~5.0.0", + "@angular/http": "~5.0.0", + "@angular/platform-browser": "~5.0.0", + "@angular/platform-browser-dynamic": "~5.0.0", + "@angular/router": "~5.0.0", "nativescript-angular": "file:../nativescript-angular", - "rxjs": "^5.3.0", + "rxjs": "5.5.2", "tns-core-modules": "next", "zone.js": "~0.8.2" }, @@ -55,7 +55,7 @@ "nativescript-dev-typescript": "^0.3.1", "shelljs": "^0.7.0", "tslint": "^4.5.1", - "typescript": "~2.3.2" + "typescript": "~2.4.2" }, "scripts": { "tslint": "tslint --project tsconfig.json --config tslint.json" From 6a8cbe28a048c024862b0bfe3fcb88b331f1d938 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Wed, 6 Dec 2017 13:26:17 -0800 Subject: [PATCH 10/16] fix: ignore setting property of 'null' objects --- nativescript-angular/view-util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nativescript-angular/view-util.ts b/nativescript-angular/view-util.ts index 9e86ba416..ebcd966ac 100644 --- a/nativescript-angular/view-util.ts +++ b/nativescript-angular/view-util.ts @@ -297,7 +297,7 @@ export class ViewUtil { } public setProperty(view: NgView, attributeName: string, value: any, namespace?: string): void { - if (namespace && !this.runsIn(namespace)) { + if (!view || (namespace && !this.runsIn(namespace))) { return; } From d9ff9d1a501c93f372ae182abb090bf12409c1a3 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Wed, 6 Dec 2017 13:26:47 -0800 Subject: [PATCH 11/16] refactor: provide fake document object when bootstrapping the app --- nativescript-angular/platform.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nativescript-angular/platform.ts b/nativescript-angular/platform.ts index eb9666917..365afaa21 100644 --- a/nativescript-angular/platform.ts +++ b/nativescript-angular/platform.ts @@ -18,6 +18,7 @@ import { } from "@angular/platform-browser-dynamic"; import { + DOCUMENT, ɵINTERNAL_BROWSER_PLATFORM_PROVIDERS as INTERNAL_BROWSER_PLATFORM_PROVIDERS } from "@angular/platform-browser"; @@ -59,6 +60,10 @@ export const NS_COMPILER_PROVIDERS: StaticProvider[] = [ }, multi: true }, + { + provide: DOCUMENT, + useValue: { body: { isOverride: true } }, + }, ]; // Dynamic platform From 925a6242329a84cbdb89055b9c350b81d18d4750 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Wed, 6 Dec 2017 13:28:26 -0800 Subject: [PATCH 12/16] feat: provide support for AnimationBuilder --- nativescript-angular/animations/animations.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nativescript-angular/animations/animations.module.ts b/nativescript-angular/animations/animations.module.ts index 27e2724ee..9e88422ff 100644 --- a/nativescript-angular/animations/animations.module.ts +++ b/nativescript-angular/animations/animations.module.ts @@ -45,8 +45,8 @@ export function instantiateDefaultStyleNormalizer() { } export const NATIVESCRIPT_ANIMATIONS_PROVIDERS: Provider[] = [ - {provide: AnimationBuilder, useClass: BrowserAnimationBuilder}, {provide: AnimationDriver, useFactory: instantiateSupportedAnimationDriver}, + {provide: AnimationBuilder, useClass: BrowserAnimationBuilder}, {provide: AnimationStyleNormalizer, useFactory: instantiateDefaultStyleNormalizer}, {provide: AnimationEngine, useClass: InjectableAnimationEngine}, { From 1c750f305a411cb1a338a7e9eeca6b6135b22bc9 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Wed, 6 Dec 2017 14:10:03 -0800 Subject: [PATCH 13/16] refactor: provide fake polyfill for global.document --- nativescript-angular/animations/animations.module.ts | 6 ------ nativescript-angular/platform.ts | 9 ++++++++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/nativescript-angular/animations/animations.module.ts b/nativescript-angular/animations/animations.module.ts index 9e88422ff..1a33c5daf 100644 --- a/nativescript-angular/animations/animations.module.ts +++ b/nativescript-angular/animations/animations.module.ts @@ -18,12 +18,6 @@ import { NativeScriptModule } from "../nativescript.module"; import { NativeScriptRendererFactory } from "../renderer"; import { NativeScriptAnimationDriver } from "./animation-driver"; -(global).document = { - body: { - isOverride: true, - } -}; - @Injectable() export class InjectableAnimationEngine extends AnimationEngine { constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) { diff --git a/nativescript-angular/platform.ts b/nativescript-angular/platform.ts index 365afaa21..ee7c30606 100644 --- a/nativescript-angular/platform.ts +++ b/nativescript-angular/platform.ts @@ -32,6 +32,13 @@ import { StaticProvider, } from "@angular/core"; +// Add a fake polyfill for the document object +(global).document = (global).document || {}; +const doc = (global).document; +doc.body = Object.assign((doc.body || {}), { + isOverride: true, +}); + // Work around a TS bug requiring an imports of // InjectionToken, ViewEncapsulation and MissingTranslationStrategy // without using them @@ -62,7 +69,7 @@ export const NS_COMPILER_PROVIDERS: StaticProvider[] = [ }, { provide: DOCUMENT, - useValue: { body: { isOverride: true } }, + useValue: doc, }, ]; From 581a78bee2a1bb32c2bb44eeaa07eb62b6745a3f Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Wed, 6 Dec 2017 14:26:48 -0800 Subject: [PATCH 14/16] chore: unpin rxjs versions --- e2e/router/package.json | 2 +- ng-sample/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/router/package.json b/e2e/router/package.json index bc2601f79..df5a6f219 100644 --- a/e2e/router/package.json +++ b/e2e/router/package.json @@ -25,7 +25,7 @@ "nativescript-angular": "file:../../nativescript-angular", "nativescript-intl": "^3.0.0", "reflect-metadata": "~0.1.8", - "rxjs": "5.5.2", + "rxjs": "^5.5.4", "tns-core-modules": "next", "zone.js": "^0.8.4" }, diff --git a/ng-sample/package.json b/ng-sample/package.json index 52afd3cc9..7c10e3b7e 100644 --- a/ng-sample/package.json +++ b/ng-sample/package.json @@ -42,7 +42,7 @@ "@angular/platform-browser-dynamic": "~5.0.0", "@angular/router": "~5.0.0", "nativescript-angular": "file:../nativescript-angular", - "rxjs": "5.5.2", + "rxjs": "^5.5.2", "tns-core-modules": "next", "zone.js": "~0.8.2" }, From 3a2b85701fb026840bde1eabd2a36e7800ea5a87 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Wed, 6 Dec 2017 15:03:38 -0800 Subject: [PATCH 15/16] test: fix mocks --- tests/app/tests/property-sets.ts | 2 +- tests/package.json | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/tests/app/tests/property-sets.ts b/tests/app/tests/property-sets.ts index 26e78a036..823b4c098 100644 --- a/tests/app/tests/property-sets.ts +++ b/tests/app/tests/property-sets.ts @@ -15,7 +15,7 @@ class TestView extends View implements NgView { public meta: ViewClassMeta = { skipAddToDom: false }; public nodeType: number = 1; public nodeName: string = "TestView"; - public templateParent: NgView = null; + public parentNode: NgView = null; public nextSibling: NgView; public firstChild: NgView; public lastChild: NgView; diff --git a/tests/package.json b/tests/package.json index 38bef7bb7..575f5344a 100644 --- a/tests/package.json +++ b/tests/package.json @@ -1,12 +1,6 @@ { "nativescript": { - "id": "org.nativescript.ngtests", - "tns-android": { - "version": "3.1.0" - }, - "tns-ios": { - "version": "3.1.0" - } + "id": "org.nativescript.ngtests" }, "name": "ngtests", "main": "app.js", @@ -26,7 +20,7 @@ ], "homepage": "http://nativescript.org", "dependencies": { - "@angular/animations": "~4.4.1", + "@angular/animations": "~5.0.0", "@angular/common": "~5.0.0", "@angular/compiler": "~5.0.0", "@angular/core": "~5.0.0", From 1d29585084c65d1e374a046b593eec202a10dfad Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Wed, 13 Dec 2017 16:46:31 +0200 Subject: [PATCH 16/16] docs(Animations): add comment for using fake body object in Driver --- nativescript-angular/animations/animation-driver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/nativescript-angular/animations/animation-driver.ts b/nativescript-angular/animations/animation-driver.ts index 829eb794d..5e1b3af6c 100644 --- a/nativescript-angular/animations/animation-driver.ts +++ b/nativescript-angular/animations/animation-driver.ts @@ -96,6 +96,7 @@ export class NativeScriptAnimationDriver implements AnimationDriver { `element1: ${elm1}, element2: ${elm2}` ); + // Checking if the parent is our fake body object if (elm1["isOverride"]) { return true; }