From 8a6eb1ac788c37981c1d3a38e0581c14086ceef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 2 May 2017 15:45:48 -0700 Subject: [PATCH] refactor(animations): single animation engine code pass --- .../browser/src/animation_engine.ts | 26 -- .../animations/browser/src/dsl/animation.ts | 7 +- .../src/dsl/animation_timeline_builder.ts | 314 +++++++++--------- .../src/dsl/animation_transition_factory.ts | 6 +- .../animations/browser/src/private_export.ts | 4 +- .../browser/src/render/animation_driver.ts | 19 +- ...ngine_next.ts => animation_engine_next.ts} | 4 +- .../src/render/noop_animation_engine.ts | 214 ------------ .../animations/browser/src/render/shared.ts | 41 +++ .../src/render/timeline_animation_engine.ts | 4 +- .../src/render/transition_animation_engine.ts | 135 +++++--- .../web_animations/web_animations_driver.ts | 11 + .../browser/test/dsl/animation_spec.ts | 6 +- .../test/dsl/animation_trigger_spec.ts | 4 +- .../transition_animation_engine_spec.ts | 1 - .../testing/src/mock_animation_driver.ts | 12 + packages/animations/src/animation_metadata.ts | 8 +- packages/animations/src/animations.ts | 2 +- ...ns_with_web_animations_integration_spec.ts | 2 +- .../animations/src/animation_builder.ts | 13 - .../animations/src/animation_renderer.ts | 2 +- .../animations/src/private_export.ts | 1 - .../animations/src/providers.ts | 47 +-- .../test/noop_animation_engine_spec.ts | 274 --------------- .../test/noop_animations_module_spec.ts | 34 +- .../test/animation/animation_renderer_spec.ts | 6 +- .../src/common/downgrade_component_adapter.ts | 2 +- .../animations/animations.d.ts | 8 +- .../public_api_guard/animations/browser.d.ts | 3 + .../animations/browser/testing.d.ts | 3 + 30 files changed, 395 insertions(+), 818 deletions(-) delete mode 100644 packages/animations/browser/src/animation_engine.ts rename packages/animations/browser/src/render/{dom_animation_engine_next.ts => animation_engine_next.ts} (97%) delete mode 100644 packages/animations/browser/src/render/noop_animation_engine.ts delete mode 100644 packages/platform-browser/animations/test/noop_animation_engine_spec.ts diff --git a/packages/animations/browser/src/animation_engine.ts b/packages/animations/browser/src/animation_engine.ts deleted file mode 100644 index 0bf176d0a8..0000000000 --- a/packages/animations/browser/src/animation_engine.ts +++ /dev/null @@ -1,26 +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 - */ -import {AnimationPlayer, AnimationTriggerMetadata} from '@angular/animations'; - -export abstract class AnimationEngine { - abstract registerTrigger( - componentId: string, namespaceId: string, hostElement: any, name: string, - metadata: AnimationTriggerMetadata): void; - abstract onInsert(namespaceId: string, element: any, parent: any, insertBefore: boolean): void; - abstract onRemove(namespaceId: string, element: any, context: any): void; - abstract setProperty(namespaceId: string, element: any, property: string, value: any): void; - abstract listen( - namespaceId: string, element: any, eventName: string, eventPhase: string, - callback: (event: any) => any): () => any; - abstract flush(): void; - abstract destroy(namespaceId: string, context: any): void; - - onRemovalComplete: (delegate: any, element: any) => void; - - public players: AnimationPlayer[]; -} diff --git a/packages/animations/browser/src/dsl/animation.ts b/packages/animations/browser/src/dsl/animation.ts index ffc0f2e6ef..a76bac35cb 100644 --- a/packages/animations/browser/src/dsl/animation.ts +++ b/packages/animations/browser/src/dsl/animation.ts @@ -6,7 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ import {AnimationMetadata, AnimationOptions, ɵStyleData} from '@angular/animations'; + +import {AnimationDriver} from '../render/animation_driver'; import {normalizeStyles} from '../util'; + import {Ast} from './animation_ast'; import {buildAnimationAst} from './animation_ast_builder'; import {buildAnimationTimelines} from './animation_timeline_builder'; @@ -15,7 +18,7 @@ import {ElementInstructionMap} from './element_instruction_map'; export class Animation { private _animationAst: Ast; - constructor(input: AnimationMetadata|AnimationMetadata[]) { + constructor(private _driver: AnimationDriver, input: AnimationMetadata|AnimationMetadata[]) { const errors: any[] = []; const ast = buildAnimationAst(input, errors); if (errors.length) { @@ -36,7 +39,7 @@ export class Animation { const errors: any = []; subInstructions = subInstructions || new ElementInstructionMap(); const result = buildAnimationTimelines( - element, this._animationAst, start, dest, options, subInstructions, errors); + this._driver, element, this._animationAst, start, dest, options, subInstructions, errors); if (errors.length) { const errorMessage = `animation building failed:\n${errors.join("\n")}`; throw new Error(errorMessage); diff --git a/packages/animations/browser/src/dsl/animation_timeline_builder.ts b/packages/animations/browser/src/dsl/animation_timeline_builder.ts index 09477bc45d..25872f9050 100644 --- a/packages/animations/browser/src/dsl/animation_timeline_builder.ts +++ b/packages/animations/browser/src/dsl/animation_timeline_builder.ts @@ -5,8 +5,9 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {AUTO_STYLE, AnimateTimings, AnimationOptions, AnimationQueryOptions, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations'; +import {AUTO_STYLE, AnimateChildOptions, AnimateTimings, AnimationOptions, AnimationQueryOptions, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations'; +import {AnimationDriver} from '../render/animation_driver'; import {copyObj, copyStyles, interpolateParams, iteratorToArray, resolveTiming, resolveTimingValue} from '../util'; import {AnimateAst, AnimateChildAst, AnimateRefAst, Ast, AstVisitor, DynamicTimingAst, GroupAst, KeyframesAst, QueryAst, ReferenceAst, SequenceAst, StaggerAst, StateAst, StyleAst, TimingAst, TransitionAst, TriggerAst} from './animation_ast'; @@ -100,137 +101,20 @@ const ONE_FRAME_IN_MILLISECONDS = 1; * the `AnimationValidatorVisitor` code. */ export function buildAnimationTimelines( - rootElement: any, ast: Ast, startingStyles: ɵStyleData = {}, finalStyles: ɵStyleData = {}, - options: AnimationOptions, subInstructions?: ElementInstructionMap, - errors: any[] = []): AnimationTimelineInstruction[] { + driver: AnimationDriver, rootElement: any, ast: Ast, startingStyles: ɵStyleData = {}, + finalStyles: ɵStyleData = {}, options: AnimationOptions, + subInstructions?: ElementInstructionMap, errors: any[] = []): AnimationTimelineInstruction[] { return new AnimationTimelineBuilderVisitor().buildKeyframes( - rootElement, ast, startingStyles, finalStyles, options, subInstructions, errors); -} - -export declare type StyleAtTime = { - time: number; value: string | number; -}; - -const DEFAULT_NOOP_PREVIOUS_NODE = {}; -export class AnimationTimelineContext { - public parentContext: AnimationTimelineContext|null = null; - public currentTimeline: TimelineBuilder; - public currentAnimateTimings: AnimateTimings|null = null; - public previousNode: Ast = DEFAULT_NOOP_PREVIOUS_NODE; - public subContextCount = 0; - public options: AnimationOptions = {}; - public currentQueryIndex: number = 0; - public currentQueryTotal: number = 0; - public currentStaggerTime: number = 0; - - constructor( - public element: any, public subInstructions: ElementInstructionMap, public errors: any[], - public timelines: TimelineBuilder[], initialTimeline?: TimelineBuilder) { - this.currentTimeline = initialTimeline || new TimelineBuilder(element, 0); - timelines.push(this.currentTimeline); - } - - get params() { return this.options.params; } - - updateOptions(newOptions: AnimationOptions|null, skipIfExists?: boolean) { - if (!newOptions) return; - - if (newOptions.duration != null) { - this.options.duration = resolveTimingValue(newOptions.duration); - } - - if (newOptions.delay != null) { - this.options.delay = resolveTimingValue(newOptions.delay); - } - - const newParams = newOptions.params; - if (newParams) { - let params: {[name: string]: any} = this.options && this.options.params !; - if (!params) { - params = this.options.params = {}; - } - - Object.keys(params).forEach(name => { - const value = params[name]; - if (!skipIfExists || !newOptions.hasOwnProperty(name)) { - params[name] = value; - } - }); - } - } - - private _copyOptions() { - const options: AnimationOptions = {}; - if (this.options) { - const oldParams = this.options.params; - if (oldParams) { - const params: {[name: string]: any} = options['params'] = {}; - Object.keys(this.options.params).forEach(name => { params[name] = oldParams[name]; }); - } - } - return options; - } - - createSubContext(options: AnimationOptions|null = null, element?: any, newTime?: number): - AnimationTimelineContext { - const target = element || this.element; - const context = new AnimationTimelineContext( - target, this.subInstructions, this.errors, this.timelines, - this.currentTimeline.fork(target, newTime || 0)); - context.previousNode = this.previousNode; - context.currentAnimateTimings = this.currentAnimateTimings; - - context.options = this._copyOptions(); - context.updateOptions(options); - - context.currentQueryIndex = this.currentQueryIndex; - context.currentQueryTotal = this.currentQueryTotal; - context.parentContext = this; - this.subContextCount++; - return context; - } - - transformIntoNewTimeline(newTime?: number) { - this.previousNode = DEFAULT_NOOP_PREVIOUS_NODE; - this.currentTimeline = this.currentTimeline.fork(this.element, newTime); - this.timelines.push(this.currentTimeline); - return this.currentTimeline; - } - - appendInstructionToTimeline( - instruction: AnimationTimelineInstruction, duration: number|null, - delay: number|null): AnimateTimings { - const updatedTimings: AnimateTimings = { - duration: duration != null ? duration : instruction.duration, - delay: this.currentTimeline.currentTime + (delay != null ? delay : 0) + instruction.delay, - easing: '' - }; - const builder = new SubTimelineBuilder( - instruction.element, instruction.keyframes, instruction.preStyleProps, - instruction.postStyleProps, updatedTimings, instruction.stretchStartingKeyframe); - this.timelines.push(builder); - return updatedTimings; - } - - incrementTime(time: number) { - this.currentTimeline.forwardTime(this.currentTimeline.duration + time); - } - - delayNextStep(delay: number) { - // negative delays are not yet supported - if (delay > 0) { - this.currentTimeline.delayNextStep(delay); - } - } + driver, rootElement, ast, startingStyles, finalStyles, options, subInstructions, errors); } export class AnimationTimelineBuilderVisitor implements AstVisitor { buildKeyframes( - rootElement: any, ast: Ast, startingStyles: ɵStyleData, finalStyles: ɵStyleData, - options: AnimationOptions, subInstructions?: ElementInstructionMap, + driver: AnimationDriver, rootElement: any, ast: Ast, startingStyles: ɵStyleData, + finalStyles: ɵStyleData, options: AnimationOptions, subInstructions?: ElementInstructionMap, errors: any[] = []): AnimationTimelineInstruction[] { subInstructions = subInstructions || new ElementInstructionMap(); - const context = new AnimationTimelineContext(rootElement, subInstructions, errors, []); + const context = new AnimationTimelineContext(driver, rootElement, subInstructions, errors, []); context.options = options; context.currentTimeline.setStyles([startingStyles], null, context.errors, options); @@ -266,7 +150,8 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor { if (elementInstructions) { const innerContext = context.createSubContext(ast.options); const startTime = context.currentTimeline.currentTime; - const endTime = this._visitSubInstructions(elementInstructions, innerContext); + const endTime = this._visitSubInstructions( + elementInstructions, innerContext, innerContext.options as AnimateChildOptions); if (startTime != endTime) { // we do this on the upper context because we created a sub context for // the sub child animations @@ -285,8 +170,8 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor { } private _visitSubInstructions( - instructions: AnimationTimelineInstruction[], context: AnimationTimelineContext): number { - const options = context.options; + instructions: AnimationTimelineInstruction[], context: AnimationTimelineContext, + options: AnimateChildOptions): number { const startTime = context.currentTimeline.currentTime; let furthestTime = startTime; @@ -464,8 +349,8 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor { } let furthestTime = startTime; - const elms = invokeQuery( - context.element, ast.selector, ast.originalSelector, ast.limit, ast.includeSelf, + const elms = context.invokeQuery( + ast.selector, ast.originalSelector, ast.limit, ast.includeSelf, options.optional ? true : false, context.errors); context.currentQueryTotal = elms.length; @@ -540,6 +425,146 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor { } } +export declare type StyleAtTime = { + time: number; value: string | number; +}; + +const DEFAULT_NOOP_PREVIOUS_NODE = {}; +export class AnimationTimelineContext { + public parentContext: AnimationTimelineContext|null = null; + public currentTimeline: TimelineBuilder; + public currentAnimateTimings: AnimateTimings|null = null; + public previousNode: Ast = DEFAULT_NOOP_PREVIOUS_NODE; + public subContextCount = 0; + public options: AnimationOptions = {}; + public currentQueryIndex: number = 0; + public currentQueryTotal: number = 0; + public currentStaggerTime: number = 0; + + constructor( + private _driver: AnimationDriver, public element: any, + public subInstructions: ElementInstructionMap, public errors: any[], + public timelines: TimelineBuilder[], initialTimeline?: TimelineBuilder) { + this.currentTimeline = initialTimeline || new TimelineBuilder(element, 0); + timelines.push(this.currentTimeline); + } + + get params() { return this.options.params; } + + updateOptions(options: AnimationOptions|null, skipIfExists?: boolean) { + if (!options) return; + + // NOTE: this will get patched up when other animation methods support duration overrides + const newOptions = options as any; + if (newOptions.duration != null) { + (this.options as any).duration = resolveTimingValue(newOptions.duration); + } + + if (newOptions.delay != null) { + this.options.delay = resolveTimingValue(newOptions.delay); + } + + const newParams = newOptions.params; + if (newParams) { + let params: {[name: string]: any} = this.options && this.options.params !; + if (!params) { + params = this.options.params = {}; + } + + Object.keys(params).forEach(name => { + const value = params[name]; + if (!skipIfExists || !newOptions.hasOwnProperty(name)) { + params[name] = value; + } + }); + } + } + + private _copyOptions() { + const options: AnimationOptions = {}; + if (this.options) { + const oldParams = this.options.params; + if (oldParams) { + const params: {[name: string]: any} = options['params'] = {}; + Object.keys(this.options.params).forEach(name => { params[name] = oldParams[name]; }); + } + } + return options; + } + + createSubContext(options: AnimationOptions|null = null, element?: any, newTime?: number): + AnimationTimelineContext { + const target = element || this.element; + const context = new AnimationTimelineContext( + this._driver, target, this.subInstructions, this.errors, this.timelines, + this.currentTimeline.fork(target, newTime || 0)); + context.previousNode = this.previousNode; + context.currentAnimateTimings = this.currentAnimateTimings; + + context.options = this._copyOptions(); + context.updateOptions(options); + + context.currentQueryIndex = this.currentQueryIndex; + context.currentQueryTotal = this.currentQueryTotal; + context.parentContext = this; + this.subContextCount++; + return context; + } + + transformIntoNewTimeline(newTime?: number) { + this.previousNode = DEFAULT_NOOP_PREVIOUS_NODE; + this.currentTimeline = this.currentTimeline.fork(this.element, newTime); + this.timelines.push(this.currentTimeline); + return this.currentTimeline; + } + + appendInstructionToTimeline( + instruction: AnimationTimelineInstruction, duration: number|null, + delay: number|null): AnimateTimings { + const updatedTimings: AnimateTimings = { + duration: duration != null ? duration : instruction.duration, + delay: this.currentTimeline.currentTime + (delay != null ? delay : 0) + instruction.delay, + easing: '' + }; + const builder = new SubTimelineBuilder( + instruction.element, instruction.keyframes, instruction.preStyleProps, + instruction.postStyleProps, updatedTimings, instruction.stretchStartingKeyframe); + this.timelines.push(builder); + return updatedTimings; + } + + incrementTime(time: number) { + this.currentTimeline.forwardTime(this.currentTimeline.duration + time); + } + + delayNextStep(delay: number) { + // negative delays are not yet supported + if (delay > 0) { + this.currentTimeline.delayNextStep(delay); + } + } + + invokeQuery( + selector: string, originalSelector: string, limit: number, includeSelf: boolean, + optional: boolean, errors: any[]): any[] { + let results: any[] = []; + if (includeSelf) { + results.push(this.element); + } + if (selector.length > 0) { // if :self is only used then the selector is empty + const multi = limit != 1; + results.push(...this._driver.query(this.element, selector, multi)); + } + + if (!optional && results.length == 0) { + errors.push( + `\`query("${originalSelector}")\` returned zero elements. (Use \`query("${originalSelector}", { optional: true })\` if you wish to allow this.)`); + } + return results; + } +} + + export class TimelineBuilder { public duration: number = 0; public easing: string|null; @@ -824,35 +849,6 @@ class SubTimelineBuilder extends TimelineBuilder { } } -function invokeQuery( - rootElement: any, selector: string, originalSelector: string, limit: number, - includeSelf: boolean, optional: boolean, errors: any[]): any[] { - const multi = limit != 1; - let results: any[] = []; - if (includeSelf) { - results.push(rootElement); - } - if (selector.length > 0) { // if :self is only used then the selector is empty - if (multi) { - results.push(...rootElement.querySelectorAll(selector)); - if (limit > 1) { - results = results.slice(0, limit); - } - } else { - const elm = rootElement.querySelector(selector); - if (elm) { - results.push(elm); - } - } - } - - if (!optional && results.length == 0) { - errors.push( - `\`query("${originalSelector}")\` returned zero elements. (Use \`query("${originalSelector}", { optional: true })\` if you wish to allow this.)`); - } - return results; -} - function roundOffset(offset: number, decimalPoints = 3): number { const mult = Math.pow(10, decimalPoints - 1); return Math.round(offset * mult) / mult; diff --git a/packages/animations/browser/src/dsl/animation_transition_factory.ts b/packages/animations/browser/src/dsl/animation_transition_factory.ts index 6eedcc118c..35697f7da5 100644 --- a/packages/animations/browser/src/dsl/animation_transition_factory.ts +++ b/packages/animations/browser/src/dsl/animation_transition_factory.ts @@ -7,6 +7,7 @@ */ import {AnimationOptions, ɵStyleData} from '@angular/animations'; +import {AnimationDriver} from '../render/animation_driver'; import {getOrSetAsInMap} from '../render/shared'; import {iteratorToArray, mergeAnimationOptions} from '../util'; @@ -26,7 +27,8 @@ export class AnimationTransitionFactory { } build( - element: any, currentState: any, nextState: any, options?: AnimationOptions, + driver: AnimationDriver, element: any, currentState: any, nextState: any, + options?: AnimationOptions, subInstructions?: ElementInstructionMap): AnimationTransitionInstruction|undefined { const animationOptions = mergeAnimationOptions(this.ast.options || {}, options || {}); @@ -36,7 +38,7 @@ export class AnimationTransitionFactory { const errors: any[] = []; const timelines = buildAnimationTimelines( - element, this.ast.animation, currentStateStyles, nextStateStyles, animationOptions, + driver, element, this.ast.animation, currentStateStyles, nextStateStyles, animationOptions, subInstructions, errors); if (errors.length) { diff --git a/packages/animations/browser/src/private_export.ts b/packages/animations/browser/src/private_export.ts index 26caa740a3..c0910e2dc8 100644 --- a/packages/animations/browser/src/private_export.ts +++ b/packages/animations/browser/src/private_export.ts @@ -5,12 +5,10 @@ * 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 */ -export {AnimationEngine as ɵAnimationEngine} from './animation_engine'; export {Animation as ɵAnimation} from './dsl/animation'; export {AnimationStyleNormalizer as ɵAnimationStyleNormalizer, NoopAnimationStyleNormalizer as ɵNoopAnimationStyleNormalizer} from './dsl/style_normalization/animation_style_normalizer'; export {WebAnimationsStyleNormalizer as ɵWebAnimationsStyleNormalizer} from './dsl/style_normalization/web_animations_style_normalizer'; export {NoopAnimationDriver as ɵNoopAnimationDriver} from './render/animation_driver'; -export {DomAnimationEngine as ɵDomAnimationEngine} from './render/dom_animation_engine_next'; -export {NoopAnimationEngine as ɵNoopAnimationEngine} from './render/noop_animation_engine'; +export {AnimationEngine as ɵAnimationEngine} from './render/animation_engine_next'; export {WebAnimationsDriver as ɵWebAnimationsDriver, supportsWebAnimations as ɵsupportsWebAnimations} from './render/web_animations/web_animations_driver'; export {WebAnimationsPlayer as ɵWebAnimationsPlayer} from './render/web_animations/web_animations_player'; diff --git a/packages/animations/browser/src/render/animation_driver.ts b/packages/animations/browser/src/render/animation_driver.ts index fdef299948..6fe7e732d3 100644 --- a/packages/animations/browser/src/render/animation_driver.ts +++ b/packages/animations/browser/src/render/animation_driver.ts @@ -5,16 +5,25 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - -import {ɵStyleData} from '@angular/animations'; import {AnimationPlayer, NoopAnimationPlayer} from '@angular/animations'; +import {containsElement, invokeQuery, matchesElement} from './shared'; /** * @experimental */ export class NoopAnimationDriver implements AnimationDriver { + matchesElement(element: any, selector: string): boolean { + return matchesElement(element, selector); + } + + containsElement(elm1: any, elm2: any): boolean { return containsElement(elm1, elm2); } + + query(element: any, selector: string, multi: boolean): any[] { + return invokeQuery(element, selector, multi); + } + computeStyle(element: any, prop: string, defaultValue?: string): string { return defaultValue || ''; } @@ -32,6 +41,12 @@ export class NoopAnimationDriver implements AnimationDriver { export abstract class AnimationDriver { static NOOP: AnimationDriver = new NoopAnimationDriver(); + abstract matchesElement(element: any, selector: string): boolean; + + abstract containsElement(elm1: any, elm2: any): boolean; + + abstract query(element: any, selector: string, multi: boolean): any[]; + abstract computeStyle(element: any, prop: string, defaultValue?: string): string; abstract animate( diff --git a/packages/animations/browser/src/render/dom_animation_engine_next.ts b/packages/animations/browser/src/render/animation_engine_next.ts similarity index 97% rename from packages/animations/browser/src/render/dom_animation_engine_next.ts rename to packages/animations/browser/src/render/animation_engine_next.ts index ba9d1c53ab..e84d9d3d57 100644 --- a/packages/animations/browser/src/render/dom_animation_engine_next.ts +++ b/packages/animations/browser/src/render/animation_engine_next.ts @@ -6,8 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ import {AnimationMetadata, AnimationPlayer, AnimationTriggerMetadata} from '@angular/animations'; - -import {AnimationEngine} from '../animation_engine'; import {TriggerAst} from '../dsl/animation_ast'; import {buildAnimationAst} from '../dsl/animation_ast_builder'; import {AnimationTrigger, buildTrigger} from '../dsl/animation_trigger'; @@ -18,7 +16,7 @@ import {parseTimelineCommand} from './shared'; import {TimelineAnimationEngine} from './timeline_animation_engine'; import {TransitionAnimationEngine} from './transition_animation_engine'; -export class DomAnimationEngine implements AnimationEngine { +export class AnimationEngine { private _transitionEngine: TransitionAnimationEngine; private _timelineEngine: TimelineAnimationEngine; diff --git a/packages/animations/browser/src/render/noop_animation_engine.ts b/packages/animations/browser/src/render/noop_animation_engine.ts deleted file mode 100644 index b5ec1a3c8d..0000000000 --- a/packages/animations/browser/src/render/noop_animation_engine.ts +++ /dev/null @@ -1,214 +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 - */ -import {AnimationEvent, AnimationPlayer, AnimationTriggerMetadata, ɵStyleData} from '@angular/animations'; - -import {AnimationEngine} from '../animation_engine'; -import {TriggerAst} from '../dsl/animation_ast'; -import {buildAnimationAst} from '../dsl/animation_ast_builder'; -import {buildTrigger} from '../dsl/animation_trigger'; -import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer'; -import {copyStyles, eraseStyles, normalizeStyles, setStyles} from '../util'; - -import {AnimationDriver} from './animation_driver'; -import {parseTimelineCommand} from './shared'; -import {TimelineAnimationEngine} from './timeline_animation_engine'; - -interface ListenerTuple { - eventPhase: string; - triggerName: string; - namespacedName: string; - callback: (event: any) => any; - doRemove?: boolean; -} - -interface ChangeTuple { - element: any; - namespacedName: string; - triggerName: string; - oldValue: string; - newValue: string; -} - -const DEFAULT_STATE_VALUE = 'void'; -const DEFAULT_STATE_STYLES = '*'; - -export class NoopAnimationEngine extends AnimationEngine { - private _listeners = new Map(); - private _changes: ChangeTuple[] = []; - private _flaggedRemovals = new Set(); - private _onDoneFns: (() => any)[] = []; - private _triggerStyles: {[triggerName: string]: {[stateName: string]: ɵStyleData}} = - Object.create(null); - - private _timelineEngine: TimelineAnimationEngine; - - // this method is designed to be overridden by the code that uses this engine - public onRemovalComplete = (element: any, context: any) => {}; - - constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) { - super(); - this._timelineEngine = new TimelineAnimationEngine(driver, normalizer); - } - - registerTrigger( - componentId: string, namespaceId: string, hostElement: any, name: string, - metadata: AnimationTriggerMetadata): void { - name = name || metadata.name; - name = namespaceId + '#' + name; - if (this._triggerStyles[name]) { - return; - } - - const errors: any[] = []; - const ast = buildAnimationAst(metadata, errors) as TriggerAst; - const trigger = buildTrigger(name, ast); - this._triggerStyles[name] = trigger.states; - } - - onInsert(namespaceId: string, element: any, parent: any, insertBefore: boolean): void {} - - onRemove(namespaceId: string, element: any, context: any): void { - this.onRemovalComplete(element, context); - if (element['nodeType'] == 1) { - this._flaggedRemovals.add(element); - } - } - - setProperty(namespaceId: string, element: any, property: string, value: any): boolean { - if (property.charAt(0) == '@') { - const [id, action] = parseTimelineCommand(property); - const args = value as any[]; - this._timelineEngine.command(id, element, action, args); - return false; - } - - const namespacedName = namespaceId + '#' + property; - const storageProp = makeStorageProp(namespacedName); - const oldValue = element[storageProp] || DEFAULT_STATE_VALUE; - this._changes.push( - {element, oldValue, newValue: value, triggerName: property, namespacedName}); - - const triggerStateStyles = this._triggerStyles[namespacedName] || {}; - const fromStateStyles = - triggerStateStyles[oldValue] || triggerStateStyles[DEFAULT_STATE_STYLES]; - if (fromStateStyles) { - eraseStyles(element, fromStateStyles); - } - - element[storageProp] = value; - this._onDoneFns.push(() => { - const toStateStyles = triggerStateStyles[value] || triggerStateStyles[DEFAULT_STATE_STYLES]; - if (toStateStyles) { - setStyles(element, toStateStyles); - } - }); - - return true; - } - - listen( - namespaceId: string, element: any, eventName: string, eventPhase: string, - callback: (event: any) => any): () => any { - if (eventName.charAt(0) == '@') { - const [id, action] = parseTimelineCommand(eventName); - return this._timelineEngine.listen(id, element, action, callback); - } - - let listeners = this._listeners.get(element); - if (!listeners) { - this._listeners.set(element, listeners = []); - } - - const tuple = { - namespacedName: namespaceId + '#' + eventName, - triggerName: eventName, eventPhase, callback - }; - listeners.push(tuple); - - return () => tuple.doRemove = true; - } - - flush(): void { - const onStartCallbacks: (() => any)[] = []; - const onDoneCallbacks: (() => any)[] = []; - - function handleListener(listener: ListenerTuple, data: ChangeTuple) { - const phase = listener.eventPhase; - const event = makeAnimationEvent( - data.element, data.triggerName, data.oldValue, data.newValue, phase, 0); - if (phase == 'start') { - onStartCallbacks.push(() => listener.callback(event)); - } else if (phase == 'done') { - onDoneCallbacks.push(() => listener.callback(event)); - } - } - - this._changes.forEach(change => { - const element = change.element; - const listeners = this._listeners.get(element); - if (listeners) { - listeners.forEach(listener => { - if (listener.namespacedName == change.namespacedName) { - handleListener(listener, change); - } - }); - } - }); - - // upon removal ALL the animation triggers need to get fired - this._flaggedRemovals.forEach(element => { - const listeners = this._listeners.get(element); - if (listeners) { - listeners.forEach(listener => { - const triggerName = listener.triggerName; - const namespacedName = listener.namespacedName; - const storageProp = makeStorageProp(namespacedName); - handleListener(listener, { - element, - triggerName, - namespacedName: listener.namespacedName, - oldValue: element[storageProp] || DEFAULT_STATE_VALUE, - newValue: DEFAULT_STATE_VALUE - }); - }); - } - }); - - // remove all the listeners after everything is complete - Array.from(this._listeners.keys()).forEach(element => { - const listenersToKeep = this._listeners.get(element) !.filter(l => !l.doRemove); - if (listenersToKeep.length) { - this._listeners.set(element, listenersToKeep); - } else { - this._listeners.delete(element); - } - }); - - onStartCallbacks.forEach(fn => fn()); - onDoneCallbacks.forEach(fn => fn()); - this._flaggedRemovals.clear(); - this._changes = []; - - this._onDoneFns.forEach(doneFn => doneFn()); - this._onDoneFns = []; - } - - get players(): AnimationPlayer[] { return []; } - - destroy(namespaceId: string) {} -} - -function makeAnimationEvent( - element: any, triggerName: string, fromState: string, toState: string, phaseName: string, - totalTime: number): AnimationEvent { - return {element, triggerName, fromState, toState, phaseName, totalTime}; -} - -function makeStorageProp(property: string): string { - return '_@_' + property; -} diff --git a/packages/animations/browser/src/render/shared.ts b/packages/animations/browser/src/render/shared.ts index 022c79c876..d294d0f91f 100644 --- a/packages/animations/browser/src/render/shared.ts +++ b/packages/animations/browser/src/render/shared.ts @@ -114,3 +114,44 @@ export function parseTimelineCommand(command: string): [string, string] { 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; diff --git a/packages/animations/browser/src/render/timeline_animation_engine.ts b/packages/animations/browser/src/render/timeline_animation_engine.ts index 5d356422a4..db07331d27 100644 --- a/packages/animations/browser/src/render/timeline_animation_engine.ts +++ b/packages/animations/browser/src/render/timeline_animation_engine.ts @@ -54,8 +54,8 @@ export class TimelineAnimationEngine { const autoStylesMap = new Map(); if (ast) { - instructions = - buildAnimationTimelines(element, ast, {}, {}, options, EMPTY_INSTRUCTION_MAP, errors); + instructions = buildAnimationTimelines( + this._driver, element, ast, {}, {}, options, EMPTY_INSTRUCTION_MAP, errors); instructions.forEach(inst => { const styles = getOrSetAsInMap(autoStylesMap, inst.element, {}); inst.postStyleProps.forEach(prop => styles[prop] = null); diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts index e79fa64c31..1824ba0abd 100644 --- a/packages/animations/browser/src/render/transition_animation_engine.ts +++ b/packages/animations/browser/src/render/transition_animation_engine.ts @@ -89,7 +89,7 @@ export class AnimationTransitionNamespace { constructor( public id: string, public hostElement: any, private _engine: TransitionAnimationEngine) { this._hostClassName = 'ng-tns-' + id; - hostElement.classList.add(this._hostClassName); + addClass(hostElement, this._hostClassName); } listen(element: any, name: string, phase: string, callback: (event: any) => boolean): () => any { @@ -114,8 +114,8 @@ export class AnimationTransitionNamespace { const triggersWithStates = getOrSetAsInMap(this._engine.statesByElement, element, {}); if (!triggersWithStates.hasOwnProperty(name)) { - element.classList.add(NG_TRIGGER_CLASSNAME); - element.classList.add(NG_TRIGGER_CLASSNAME + '-' + name); + addClass(element, NG_TRIGGER_CLASSNAME); + addClass(element, NG_TRIGGER_CLASSNAME + '-' + name); triggersWithStates[name] = null; } @@ -161,8 +161,8 @@ export class AnimationTransitionNamespace { let triggersWithStates = this._engine.statesByElement.get(element); if (!triggersWithStates) { - element.classList.add(NG_TRIGGER_CLASSNAME); - element.classList.add(NG_TRIGGER_CLASSNAME + '-' + triggerName); + addClass(element, NG_TRIGGER_CLASSNAME); + addClass(element, NG_TRIGGER_CLASSNAME + '-' + triggerName); this._engine.statesByElement.set(element, triggersWithStates = {}); } @@ -207,11 +207,11 @@ export class AnimationTransitionNamespace { {element, triggerName, transition, fromState, toState, player, isFallbackTransition}); if (!isFallbackTransition) { - element.classList.add(NG_ANIMATING_CLASSNAME); + addClass(element, NG_ANIMATING_CLASSNAME); } player.onDone(() => { - element.classList.remove(NG_ANIMATING_CLASSNAME); + removeClass(element, NG_ANIMATING_CLASSNAME); let index = this.players.indexOf(player); if (index >= 0) { @@ -255,8 +255,8 @@ export class AnimationTransitionNamespace { } private _destroyInnerNodes(rootElement: any, context: any, animate: boolean = false) { - listToArray(rootElement.querySelectorAll(NG_TRIGGER_SELECTOR)).forEach(elm => { - if (animate && elm.classList.contains(this._hostClassName)) { + listToArray(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 @@ -274,8 +274,8 @@ export class AnimationTransitionNamespace { removeNode(element: any, context: any, doNotRecurse?: boolean): void { const engine = this._engine; - element.classList.add(LEAVE_CLASSNAME); - engine.afterFlush(() => element.classList.remove(LEAVE_CLASSNAME)); + addClass(element, LEAVE_CLASSNAME); + engine.afterFlush(() => removeClass(element, LEAVE_CLASSNAME)); if (!doNotRecurse && element.childElementCount) { this._destroyInnerNodes(element, context, true); @@ -380,7 +380,7 @@ export class AnimationTransitionNamespace { } } - insertNode(element: any, parent: any): void { element.classList.add(this._hostClassName); } + insertNode(element: any, parent: any): void { addClass(element, this._hostClassName); } drainQueuedTransitions(): QueueInstruction[] { const instructions: QueueInstruction[] = []; @@ -415,13 +415,13 @@ export class AnimationTransitionNamespace { return instructions.sort((a, b) => { // if depCount == 0 them move to front - // otherwise if a.contains(b) then move back + // 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 a.element.contains(b.element) ? 1 : -1; + return this._engine.driver.containsElement(a.element, b.element) ? 1 : -1; }); } @@ -468,7 +468,7 @@ export class TransitionAnimationEngine { _onRemovalComplete(element: any, context: any) { this.onRemovalComplete(element, context); } - constructor(private _driver: AnimationDriver, private _normalizer: AnimationStyleNormalizer) {} + constructor(public driver: AnimationDriver, private _normalizer: AnimationStyleNormalizer) {} get queuedPlayers(): TransitionAnimationPlayer[] { const players: TransitionAnimationPlayer[] = []; @@ -508,7 +508,7 @@ export class TransitionAnimationEngine { let found = false; for (let i = limit; i >= 0; i--) { const nextNamespace = this._namespaceList[i]; - if (nextNamespace.hostElement.contains(hostElement)) { + if (this.driver.containsElement(nextNamespace.hostElement, hostElement)) { this._namespaceList.splice(i + 1, 0, ns); found = true; break; @@ -591,12 +591,12 @@ export class TransitionAnimationEngine { private _buildInstruction(entry: QueueInstruction, subTimelines: ElementInstructionMap) { return entry.transition.build( - entry.element, entry.fromState.value, entry.toState.value, entry.toState.options, - subTimelines); + this.driver, entry.element, entry.fromState.value, entry.toState.value, + entry.toState.options, subTimelines); } destroyInnerAnimations(containerElement: any) { - listToArray(containerElement.querySelectorAll(NG_TRIGGER_SELECTOR)).forEach(element => { + listToArray(this.driver.query(containerElement, NG_TRIGGER_SELECTOR, true)).forEach(element => { const players = this.playersByElement.get(element); if (players) { players.forEach(player => { @@ -662,14 +662,15 @@ export class TransitionAnimationEngine { // the :enter queries match the elements (since the timeline queries // are fired during instruction building). const allEnterNodes = iteratorToArray(this.newlyInserted.values()); - const enterNodes: any[] = collectEnterElements(allEnterNodes); + const enterNodes: any[] = collectEnterElements(this.driver, allEnterNodes); + const bodyNode = getBodyNode(); for (let i = this._namespaceList.length - 1; i >= 0; i--) { const ns = this._namespaceList[i]; ns.drainQueuedTransitions().forEach(entry => { const player = entry.player; const element = entry.element; - if (!document.body.contains(element)) { + if (!bodyNode || !this.driver.containsElement(bodyNode, element)) { player.destroy(); return; } @@ -737,18 +738,18 @@ export class TransitionAnimationEngine { allPreviousPlayersMap.forEach(players => { players.forEach(player => player.destroy()); }); - const leaveNodes: any[] = allPostStyleElements.size ? - listToArray(document.body.querySelectorAll(LEAVE_SELECTOR)) : + const leaveNodes: any[] = bodyNode && allPostStyleElements.size ? + listToArray(this.driver.query(bodyNode, LEAVE_SELECTOR, true)) : []; // PRE STAGE: fill the ! styles const preStylesMap = allPreStyleElements.size ? - cloakAndComputeStyles(this._driver, enterNodes, allPreStyleElements, PRE_STYLE) : + cloakAndComputeStyles(this.driver, enterNodes, allPreStyleElements, PRE_STYLE) : new Map(); // POST STAGE: fill the * styles const postStylesMap = - cloakAndComputeStyles(this._driver, leaveNodes, allPostStyleElements, AUTO_STYLE); + cloakAndComputeStyles(this.driver, leaveNodes, allPostStyleElements, AUTO_STYLE); const rootPlayers: TransitionAnimationPlayer[] = []; const subPlayers: TransitionAnimationPlayer[] = []; @@ -766,7 +767,7 @@ export class TransitionAnimationEngine { for (let i = 0; i < sortedParentElements.length; i++) { const parent = sortedParentElements[i]; if (parent === element) break; - if (parent.contains(element)) { + if (this.driver.containsElement(parent, element)) { parentHasPriority = parent; break; } @@ -845,7 +846,7 @@ export class TransitionAnimationEngine { player.play(); }); - enterNodes.forEach(element => element.classList.remove(ENTER_CLASSNAME)); + enterNodes.forEach(element => removeClass(element, ENTER_CLASSNAME)); return rootPlayers; } @@ -958,7 +959,7 @@ export class TransitionAnimationEngine { const preStyles = preStylesMap.get(element); const postStyles = postStylesMap.get(element); const keyframes = normalizeKeyframes( - this._driver, this._normalizer, element, timelineInstruction.keyframes, preStyles, + this.driver, this._normalizer, element, timelineInstruction.keyframes, preStyles, postStyles); const player = this._buildPlayer(timelineInstruction, keyframes, previousPlayers); @@ -983,11 +984,11 @@ export class TransitionAnimationEngine { () => { deleteOrUnsetInMap(this.playersByQueriedElement, player.element, player); }); }); - allConsumedElements.forEach(element => { element.classList.add(NG_ANIMATING_CLASSNAME); }); + allConsumedElements.forEach(element => addClass(element, NG_ANIMATING_CLASSNAME)); const player = optimizeGroupPlayer(allNewPlayers); player.onDone(() => { - allConsumedElements.forEach(element => { element.classList.remove(NG_ANIMATING_CLASSNAME); }); + allConsumedElements.forEach(element => removeClass(element, NG_ANIMATING_CLASSNAME)); setStyles(rootElement, instruction.toStyles); }); @@ -1003,7 +1004,7 @@ export class TransitionAnimationEngine { instruction: AnimationTimelineInstruction, keyframes: ɵStyleData[], previousPlayers: AnimationPlayer[]): AnimationPlayer { if (keyframes.length > 0) { - return this._driver.animate( + return this.driver.animate( instruction.element, keyframes, instruction.duration, instruction.delay, instruction.easing, previousPlayers); } @@ -1150,31 +1151,21 @@ function cloakElement(element: any, value?: string) { return oldValue; } -let elementMatches: (element: any, selector: string) => boolean = - (element: any, selector: string) => false; -if (typeof Element == 'function') { - if (Element.prototype.matches) { - elementMatches = (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; - elementMatches = (element: any, selector: string) => fn.apply(element, [selector]); - } -} - -function filterNodeClasses(rootElement: any, selector: string): any[] { +function filterNodeClasses( + driver: AnimationDriver, rootElement: any | null, selector: string): any[] { const rootElements: any[] = []; + if (!rootElement) return rootElements; + let cursor: any = rootElement; let nextCursor: any = {}; do { - nextCursor = cursor.querySelector(selector); + nextCursor = driver.query(cursor, selector, false)[0]; if (!nextCursor) { cursor = cursor.parentElement; if (!cursor) break; nextCursor = cursor = cursor.nextElementSibling; } else { - while (nextCursor && elementMatches(nextCursor, selector)) { + while (nextCursor && driver.matchesElement(nextCursor, selector)) { rootElements.push(nextCursor); nextCursor = nextCursor.nextElementSibling; if (nextCursor) { @@ -1221,10 +1212,50 @@ function listToArray(list: any): any[] { return arr; } -function collectEnterElements(allEnterNodes: any[]) { - allEnterNodes.forEach(element => element.classList.add(POTENTIAL_ENTER_CLASSNAME)); - const enterNodes = filterNodeClasses(document.body, POTENTIAL_ENTER_SELECTOR); - enterNodes.forEach(element => element.classList.add(ENTER_CLASSNAME)); - allEnterNodes.forEach(element => element.classList.remove(POTENTIAL_ENTER_CLASSNAME)); +function collectEnterElements(driver: AnimationDriver, allEnterNodes: any[]) { + allEnterNodes.forEach(element => addClass(element, POTENTIAL_ENTER_CLASSNAME)); + const enterNodes = filterNodeClasses(driver, getBodyNode(), POTENTIAL_ENTER_SELECTOR); + enterNodes.forEach(element => addClass(element, ENTER_CLASSNAME)); + allEnterNodes.forEach(element => removeClass(element, POTENTIAL_ENTER_CLASSNAME)); return enterNodes; } + +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; +} diff --git a/packages/animations/browser/src/render/web_animations/web_animations_driver.ts b/packages/animations/browser/src/render/web_animations/web_animations_driver.ts index cb3c6b58c6..b26cbe3eca 100644 --- a/packages/animations/browser/src/render/web_animations/web_animations_driver.ts +++ b/packages/animations/browser/src/render/web_animations/web_animations_driver.ts @@ -8,10 +8,21 @@ import {AnimationPlayer, ɵStyleData} from '@angular/animations'; import {AnimationDriver} from '../animation_driver'; +import {containsElement, invokeQuery, matchesElement} from '../shared'; import {WebAnimationsPlayer} from './web_animations_player'; export class WebAnimationsDriver implements AnimationDriver { + matchesElement(element: any, selector: string): boolean { + return matchesElement(element, selector); + } + + containsElement(elm1: any, elm2: any): boolean { return containsElement(elm1, elm2); } + + query(element: any, selector: string, multi: boolean): any[] { + return invokeQuery(element, selector, multi); + } + computeStyle(element: any, prop: string, defaultValue?: string): string { return (window.getComputedStyle(element) as any)[prop] as string; } diff --git a/packages/animations/browser/test/dsl/animation_spec.ts b/packages/animations/browser/test/dsl/animation_spec.ts index 301de66195..56d3590bb2 100644 --- a/packages/animations/browser/test/dsl/animation_spec.ts +++ b/packages/animations/browser/test/dsl/animation_spec.ts @@ -12,6 +12,7 @@ import {Animation} from '../../src/dsl/animation'; import {buildAnimationAst} from '../../src/dsl/animation_ast_builder'; import {AnimationTimelineInstruction} from '../../src/dsl/animation_timeline_instruction'; import {ElementInstructionMap} from '../../src/dsl/element_instruction_map'; +import {MockAnimationDriver} from '../../testing'; function createDiv() { return document.createElement('div'); @@ -868,8 +869,9 @@ function invokeAnimationSequence( element: any, steps: AnimationMetadata | AnimationMetadata[], locals: {[key: string]: any} = {}, startingStyles: ɵStyleData[] = [], destinationStyles: ɵStyleData[] = [], subInstructions?: ElementInstructionMap): AnimationTimelineInstruction[] { - return new Animation(steps).buildTimelines( - element, startingStyles, destinationStyles, locals, subInstructions); + const driver = new MockAnimationDriver(); + return new Animation(driver, steps) + .buildTimelines(element, startingStyles, destinationStyles, locals, subInstructions); } function validateAndThrowAnimationSequence(steps: AnimationMetadata | AnimationMetadata[]) { diff --git a/packages/animations/browser/test/dsl/animation_trigger_spec.ts b/packages/animations/browser/test/dsl/animation_trigger_spec.ts index 6f1d3eb4bf..eeb200005f 100644 --- a/packages/animations/browser/test/dsl/animation_trigger_spec.ts +++ b/packages/animations/browser/test/dsl/animation_trigger_spec.ts @@ -10,6 +10,7 @@ import {AnimationOptions, animate, state, style, transition} from '@angular/anim import {AnimationTransitionInstruction} from '@angular/animations/browser/src/dsl/animation_transition_instruction'; import {AnimationTrigger} from '@angular/animations/browser/src/dsl/animation_trigger'; +import {MockAnimationDriver} from '../../testing'; import {makeTrigger} from '../shared'; export function main() { @@ -221,7 +222,8 @@ function buildTransition( params?: AnimationOptions): AnimationTransitionInstruction|null { const trans = trigger.matchTransition(fromState, toState) !; if (trans) { - return trans.build(element, fromState, toState, params) !; + const driver = new MockAnimationDriver(); + return trans.build(driver, element, fromState, toState, params) !; } return null; } diff --git a/packages/animations/browser/test/engine/transition_animation_engine_spec.ts b/packages/animations/browser/test/engine/transition_animation_engine_spec.ts index f3d6ad7dc1..2878c4ce85 100644 --- a/packages/animations/browser/test/engine/transition_animation_engine_spec.ts +++ b/packages/animations/browser/test/engine/transition_animation_engine_spec.ts @@ -52,7 +52,6 @@ export function main() { describe('property setting', () => { it('should invoke a transition based on a property change', () => { const engine = makeEngine(); - const trig = trigger('myTrigger', [ transition('* => *', [style({height: '0px'}), animate(1000, style({height: '100px'}))]) ]); diff --git a/packages/animations/browser/testing/src/mock_animation_driver.ts b/packages/animations/browser/testing/src/mock_animation_driver.ts index be0f43291f..0bc3501631 100644 --- a/packages/animations/browser/testing/src/mock_animation_driver.ts +++ b/packages/animations/browser/testing/src/mock_animation_driver.ts @@ -8,6 +8,8 @@ import {AUTO_STYLE, AnimationPlayer, NoopAnimationPlayer, ɵStyleData} from '@angular/animations'; import {AnimationDriver} from '../../src/render/animation_driver'; +import {containsElement, invokeQuery, matchesElement} from '../../src/render/shared'; + /** * @experimental Animation support is experimental. @@ -15,6 +17,16 @@ import {AnimationDriver} from '../../src/render/animation_driver'; export class MockAnimationDriver implements AnimationDriver { static log: AnimationPlayer[] = []; + matchesElement(element: any, selector: string): boolean { + return matchesElement(element, selector); + } + + containsElement(elm1: any, elm2: any): boolean { return containsElement(elm1, elm2); } + + query(element: any, selector: string, multi: boolean): any[] { + return invokeQuery(element, selector, multi); + } + computeStyle(element: any, prop: string, defaultValue?: string): string { return defaultValue || ''; } diff --git a/packages/animations/src/animation_metadata.ts b/packages/animations/src/animation_metadata.ts index f9ee31643f..50d503afc7 100755 --- a/packages/animations/src/animation_metadata.ts +++ b/packages/animations/src/animation_metadata.ts @@ -21,10 +21,14 @@ export declare type AnimateTimings = { */ export declare interface AnimationOptions { delay?: number|string; - duration?: number|string; params?: {[name: string]: any}; } +/** + * @experimental Animation support is experimental. + */ +export declare interface AnimateChildOptions extends AnimationOptions { duration?: number|string; } + /** * @experimental Animation support is experimental. */ @@ -635,7 +639,7 @@ export function animation( /** * @experimental Animation support is experimental. */ -export function animateChild(options: AnimationOptions | null = null): +export function animateChild(options: AnimateChildOptions | null = null): AnimationAnimateChildMetadata { return {type: AnimationMetadataType.AnimateChild, options}; } diff --git a/packages/animations/src/animations.ts b/packages/animations/src/animations.ts index 71d819278f..66548c1847 100644 --- a/packages/animations/src/animations.ts +++ b/packages/animations/src/animations.ts @@ -13,7 +13,7 @@ */ export {Animation, AnimationBuilder} from './animation_builder'; export {AnimationEvent} from './animation_event'; -export {AUTO_STYLE, AnimateTimings, AnimationAnimateChildMetadata, AnimationAnimateMetadata, AnimationAnimateRefMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationMetadataType, AnimationOptions, AnimationQueryMetadata, AnimationQueryOptions, AnimationReferenceMetadata, AnimationSequenceMetadata, AnimationStaggerMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, AnimationTriggerMetadata, animate, animateChild, animation, group, keyframes, query, sequence, stagger, state, style, transition, trigger, useAnimation, ɵStyleData} from './animation_metadata'; +export {AUTO_STYLE, AnimateChildOptions, AnimateTimings, AnimationAnimateChildMetadata, AnimationAnimateMetadata, AnimationAnimateRefMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationMetadataType, AnimationOptions, AnimationQueryMetadata, AnimationQueryOptions, AnimationReferenceMetadata, AnimationSequenceMetadata, AnimationStaggerMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, AnimationTriggerMetadata, animate, animateChild, animation, group, keyframes, query, sequence, stagger, state, style, transition, trigger, useAnimation, ɵStyleData} from './animation_metadata'; export {AnimationPlayer, NoopAnimationPlayer} from './players/animation_player'; export * from './private_export'; diff --git a/packages/core/test/animation/animations_with_web_animations_integration_spec.ts b/packages/core/test/animation/animations_with_web_animations_integration_spec.ts index 9a56adb74b..35be818182 100644 --- a/packages/core/test/animation/animations_with_web_animations_integration_spec.ts +++ b/packages/core/test/animation/animations_with_web_animations_integration_spec.ts @@ -7,7 +7,7 @@ */ import {animate, style, transition, trigger} from '@angular/animations'; import {AnimationDriver, ɵAnimationEngine} from '@angular/animations/browser'; -import {ɵDomAnimationEngine, ɵWebAnimationsDriver, ɵWebAnimationsPlayer, ɵsupportsWebAnimations} from '@angular/animations/browser' +import {ɵWebAnimationsDriver, ɵWebAnimationsPlayer, ɵsupportsWebAnimations} from '@angular/animations/browser' import {Component, ViewChild} from '@angular/core'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; diff --git a/packages/platform-browser/animations/src/animation_builder.ts b/packages/platform-browser/animations/src/animation_builder.ts index 97fbe11a3d..96bda7eab0 100644 --- a/packages/platform-browser/animations/src/animation_builder.ts +++ b/packages/platform-browser/animations/src/animation_builder.ts @@ -35,11 +35,6 @@ export class BrowserAnimationBuilder extends AnimationBuilder { } } -@Injectable() -export class NoopAnimationBuilder extends BrowserAnimationBuilder { - build(animation: AnimationMetadata|AnimationMetadata[]): Animation { return new NoopAnimation(); } -} - export class BrowserAnimation extends Animation { constructor(private _id: string, private _renderer: AnimationRenderer) { super(); } @@ -48,14 +43,6 @@ export class BrowserAnimation extends Animation { } } -export class NoopAnimation extends Animation { - constructor() { super(); } - - create(element: any, options?: AnimationOptions): AnimationPlayer { - return new NoopAnimationPlayer(); - } -} - export class RendererAnimationPlayer implements AnimationPlayer { public parentPlayer: AnimationPlayer|null = null; private _started = false; diff --git a/packages/platform-browser/animations/src/animation_renderer.ts b/packages/platform-browser/animations/src/animation_renderer.ts index bb75f69028..011acaaa81 100644 --- a/packages/platform-browser/animations/src/animation_renderer.ts +++ b/packages/platform-browser/animations/src/animation_renderer.ts @@ -46,7 +46,7 @@ export class AnimationRendererFactory implements RendererFactory2 { this.delegate.begin(); } } - + end() { this._zone.runOutsideAngular(() => this._engine.flush()); if (this.delegate.end) { diff --git a/packages/platform-browser/animations/src/private_export.ts b/packages/platform-browser/animations/src/private_export.ts index c543c50c5f..fd5b40e4ab 100644 --- a/packages/platform-browser/animations/src/private_export.ts +++ b/packages/platform-browser/animations/src/private_export.ts @@ -5,6 +5,5 @@ * 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 */ -export {NoopAnimation as ɵNoopAnimation, NoopAnimationBuilder as ɵNoopAnimationBuilder} from './animation_builder'; export {BrowserAnimation as ɵBrowserAnimation, BrowserAnimationBuilder as ɵBrowserAnimationBuilder} from './animation_builder'; export {AnimationRenderer as ɵAnimationRenderer, AnimationRendererFactory as ɵAnimationRendererFactory} from './animation_renderer'; diff --git a/packages/platform-browser/animations/src/providers.ts b/packages/platform-browser/animations/src/providers.ts index d0bed48f77..f694722a91 100644 --- a/packages/platform-browser/animations/src/providers.ts +++ b/packages/platform-browser/animations/src/providers.ts @@ -7,22 +7,15 @@ */ import {AnimationBuilder} from '@angular/animations'; -import {AnimationDriver, ɵAnimationEngine as AnimationEngine, ɵAnimationStyleNormalizer as AnimationStyleNormalizer, ɵDomAnimationEngine as DomAnimationEngine, ɵNoopAnimationDriver as NoopAnimationDriver, ɵNoopAnimationEngine as NoopAnimationEngine, ɵNoopAnimationStyleNormalizer as NoopAnimationStyleNormalizer, ɵWebAnimationsDriver as WebAnimationsDriver, ɵWebAnimationsStyleNormalizer as WebAnimationsStyleNormalizer, ɵsupportsWebAnimations as supportsWebAnimations} from '@angular/animations/browser'; +import {AnimationDriver, ɵAnimationEngine as AnimationEngine, ɵAnimationStyleNormalizer as AnimationStyleNormalizer, ɵNoopAnimationDriver as NoopAnimationDriver, ɵWebAnimationsDriver as WebAnimationsDriver, ɵWebAnimationsStyleNormalizer as WebAnimationsStyleNormalizer, ɵsupportsWebAnimations as supportsWebAnimations} from '@angular/animations/browser'; import {Injectable, NgZone, Provider, RendererFactory2} from '@angular/core'; import {ɵDomRendererFactory2 as DomRendererFactory2} from '@angular/platform-browser'; -import {BrowserAnimationBuilder, NoopAnimationBuilder} from './animation_builder'; +import {BrowserAnimationBuilder} from './animation_builder'; import {AnimationRendererFactory} from './animation_renderer'; @Injectable() -export class InjectableAnimationEngine extends DomAnimationEngine { - constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) { - super(driver, normalizer); - } -} - -@Injectable() -export class InjectableNoopAnimationEngine extends NoopAnimationEngine { +export class InjectableAnimationEngine extends AnimationEngine { constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) { super(driver, normalizer); } @@ -44,13 +37,8 @@ export function instantiateRendererFactory( return new AnimationRendererFactory(renderer, engine, zone); } -/** - * Separate providers from the actual module so that we can do a local modification in Google3 to - * include them in the BrowserModule. - */ -export const BROWSER_ANIMATIONS_PROVIDERS: Provider[] = [ - {provide: AnimationBuilder, useClass: NoopAnimationBuilder}, - {provide: AnimationDriver, useFactory: instantiateSupportedAnimationDriver}, +const SHARED_ANIMATION_PROVIDERS: Provider[] = [ + {provide: AnimationBuilder, useClass: BrowserAnimationBuilder}, {provide: AnimationStyleNormalizer, useFactory: instantiateDefaultStyleNormalizer}, {provide: AnimationEngine, useClass: InjectableAnimationEngine}, { provide: RendererFactory2, @@ -59,21 +47,18 @@ export const BROWSER_ANIMATIONS_PROVIDERS: Provider[] = [ } ]; +/** + * Separate providers from the actual module so that we can do a local modification in Google3 to + * include them in the BrowserModule. + */ +export const BROWSER_ANIMATIONS_PROVIDERS: Provider[] = [ + {provide: AnimationDriver, useFactory: instantiateSupportedAnimationDriver}, + ...SHARED_ANIMATION_PROVIDERS +]; + /** * Separate providers from the actual module so that we can do a local modification in Google3 to * include them in the BrowserTestingModule. */ -export const BROWSER_NOOP_ANIMATIONS_PROVIDERS: Provider[] = [ - {provide: AnimationBuilder, useClass: BrowserAnimationBuilder}, - {provide: AnimationDriver, useClass: NoopAnimationDriver}, - {provide: AnimationStyleNormalizer, useFactory: instantiateDefaultStyleNormalizer}, { - provide: AnimationEngine, - useClass: NoopAnimationEngine, - deps: [AnimationDriver, AnimationStyleNormalizer] - }, - { - provide: RendererFactory2, - useFactory: instantiateRendererFactory, - deps: [DomRendererFactory2, AnimationEngine, NgZone] - } -]; +export const BROWSER_NOOP_ANIMATIONS_PROVIDERS: Provider[] = + [{provide: AnimationDriver, useClass: NoopAnimationDriver}, ...SHARED_ANIMATION_PROVIDERS]; diff --git a/packages/platform-browser/animations/test/noop_animation_engine_spec.ts b/packages/platform-browser/animations/test/noop_animation_engine_spec.ts deleted file mode 100644 index c418182801..0000000000 --- a/packages/platform-browser/animations/test/noop_animation_engine_spec.ts +++ /dev/null @@ -1,274 +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 - */ -import {AnimationMetadata, AnimationTriggerMetadata, state, style, trigger} from '@angular/animations'; -import {ɵNoopAnimationEngine as NoopAnimationEngine} from '@angular/animations/browser'; -import {NoopAnimationStyleNormalizer} from '@angular/animations/browser/src/dsl/style_normalization/animation_style_normalizer'; -import {MockAnimationDriver} from '@angular/animations/browser/testing'; -import {el} from '@angular/platform-browser/testing/src/browser_util'; - -import {TriggerAst} from '../../../animations/browser/src/dsl/animation_ast'; -import {buildAnimationAst} from '../../../animations/browser/src/dsl/animation_ast_builder'; -import {buildTrigger} from '../../../animations/browser/src/dsl/animation_trigger'; - -const DEFAULT_NAMESPACE_ID = 'id'; -const DEFAULT_COMPONENT_ID = '1'; - -export function main() { - describe('NoopAnimationEngine', () => { - let captures: string[] = []; - function capture(value?: string) { return (v: any = null) => captures.push(value || v); } - - beforeEach(() => { captures = []; }); - - function makeEngine() { - const driver = new MockAnimationDriver(); - const normalizer = new NoopAnimationStyleNormalizer(); - return new NoopAnimationEngine(driver, normalizer); - } - - it('should immediately issue DOM removals during remove animations and then fire the animation callbacks after flush', - () => { - const engine = makeEngine(); - const capture1 = capture('1'); - const capture2 = capture('2'); - engine.onRemovalComplete = (element: any, context: any) => { - switch (context as string) { - case '1': - capture1(); - break; - case '2': - capture2(); - break; - } - }; - - const elm1 = {nodeType: 1}; - const elm2 = {nodeType: 1}; - engine.onRemove(DEFAULT_NAMESPACE_ID, elm1, '1'); - engine.onRemove(DEFAULT_NAMESPACE_ID, elm2, '2'); - - listen(elm1, engine, 'trig', 'start', capture('1-start')); - listen(elm2, engine, 'trig', 'start', capture('2-start')); - listen(elm1, engine, 'trig', 'done', capture('1-done')); - listen(elm2, engine, 'trig', 'done', capture('2-done')); - - expect(captures).toEqual(['1', '2']); - engine.flush(); - - expect(captures).toEqual(['1', '2', '1-start', '2-start', '1-done', '2-done']); - }); - - it('should only fire the `start` listener for a trigger that has had a property change', () => { - const engine = makeEngine(); - - const elm1 = {}; - const elm2 = {}; - const elm3 = {}; - - listen(elm1, engine, 'trig1', 'start', capture()); - setProperty(elm1, engine, 'trig1', 'cool'); - setProperty(elm2, engine, 'trig2', 'sweet'); - listen(elm2, engine, 'trig2', 'start', capture()); - listen(elm3, engine, 'trig3', 'start', capture()); - - expect(captures).toEqual([]); - engine.flush(); - - expect(captures.length).toEqual(2); - const trig1Data = captures.shift(); - const trig2Data = captures.shift(); - expect(trig1Data).toEqual({ - element: elm1, - triggerName: 'trig1', - fromState: 'void', - toState: 'cool', - phaseName: 'start', - totalTime: 0 - }); - - expect(trig2Data).toEqual({ - element: elm2, - triggerName: 'trig2', - fromState: 'void', - toState: 'sweet', - phaseName: 'start', - totalTime: 0 - }); - - captures = []; - engine.flush(); - expect(captures).toEqual([]); - }); - - it('should only fire the `done` listener for a trigger that has had a property change', () => { - const engine = makeEngine(); - - const elm1 = {}; - const elm2 = {}; - const elm3 = {}; - - listen(elm1, engine, 'trig1', 'done', capture()); - setProperty(elm1, engine, 'trig1', 'awesome'); - setProperty(elm2, engine, 'trig2', 'amazing'); - listen(elm2, engine, 'trig2', 'done', capture()); - listen(elm3, engine, 'trig3', 'done', capture()); - - expect(captures).toEqual([]); - engine.flush(); - - expect(captures.length).toEqual(2); - const trig1Data = captures.shift(); - const trig2Data = captures.shift(); - expect(trig1Data).toEqual({ - element: elm1, - triggerName: 'trig1', - fromState: 'void', - toState: 'awesome', - phaseName: 'done', - totalTime: 0 - }); - - expect(trig2Data).toEqual({ - element: elm2, - triggerName: 'trig2', - fromState: 'void', - toState: 'amazing', - phaseName: 'done', - totalTime: 0 - }); - - captures = []; - engine.flush(); - expect(captures).toEqual([]); - }); - - it('should deregister a listener when the return function is called, but only after flush', - () => { - const engine = makeEngine(); - const elm = {}; - - const fn1 = listen(elm, engine, 'trig1', 'start', capture('trig1-start')); - const fn2 = listen(elm, engine, 'trig2', 'done', capture('trig2-done')); - - setProperty(elm, engine, 'trig1', 'value1'); - setProperty(elm, engine, 'trig2', 'value2'); - engine.flush(); - expect(captures).toEqual(['trig1-start', 'trig2-done']); - - captures = []; - setProperty(elm, engine, 'trig1', 'value3'); - setProperty(elm, engine, 'trig2', 'value4'); - - fn1(); - engine.flush(); - expect(captures).toEqual(['trig1-start', 'trig2-done']); - - captures = []; - setProperty(elm, engine, 'trig1', 'value5'); - setProperty(elm, engine, 'trig2', 'value6'); - - fn2(); - engine.flush(); - expect(captures).toEqual(['trig2-done']); - - captures = []; - setProperty(elm, engine, 'trig1', 'value7'); - setProperty(elm, engine, 'trig2', 'value8'); - engine.flush(); - expect(captures).toEqual([]); - }); - - it('should fire a removal listener even if the listener is deregistered prior to flush', () => { - const engine = makeEngine(); - const elm = {nodeType: 1}; - engine.onRemovalComplete = (element: any, context: string) => { capture(context)(); }; - - const fn = listen(elm, engine, 'trig', 'start', capture('removal listener')); - fn(); - - engine.onRemove(DEFAULT_NAMESPACE_ID, elm, 'dom removal'); - engine.flush(); - - expect(captures).toEqual(['dom removal', 'removal listener']); - }); - - describe('styling', () => { - // these tests are only mean't to be run within the DOM - if (typeof Element == 'undefined') return; - - it('should persist the styles on the element when the animation is complete', () => { - const engine = makeEngine(); - const element = el('
'); - registerTrigger(element, engine, trigger('matias', [ - state('a', style({width: '100px'})), - ])); - - expect(element.style.width).not.toEqual('100px'); - - setProperty(element, engine, 'matias', 'a'); - expect(element.style.width).not.toEqual('100px'); - - engine.flush(); - expect(element.style.width).toEqual('100px'); - }); - - it('should remove previously persist styles off of the element when a follow-up animation starts', - () => { - const engine = makeEngine(); - const element = el('
'); - - registerTrigger(element, engine, trigger('matias', [ - state('a', style({width: '100px'})), - state('b', style({height: '100px'})), - ])); - - setProperty(element, engine, 'matias', 'a'); - engine.flush(); - expect(element.style.width).toEqual('100px'); - - setProperty(element, engine, 'matias', 'b'); - expect(element.style.width).not.toEqual('100px'); - expect(element.style.height).not.toEqual('100px'); - - engine.flush(); - expect(element.style.height).toEqual('100px'); - }); - - it('should fall back to `*` styles incase the target state styles are not found', () => { - const engine = makeEngine(); - const element = el('
'); - - registerTrigger(element, engine, trigger('matias', [ - state('*', style({opacity: '0.5'})), - ])); - - setProperty(element, engine, 'matias', 'xyz'); - engine.flush(); - expect(element.style.opacity).toEqual('0.5'); - }); - }); - }); -} - -function registerTrigger( - element: any, engine: NoopAnimationEngine, metadata: AnimationTriggerMetadata, - namespaceId: string = DEFAULT_NAMESPACE_ID, componentId: string = DEFAULT_COMPONENT_ID) { - engine.registerTrigger(componentId, namespaceId, element, name, metadata) -} - -function setProperty( - element: any, engine: NoopAnimationEngine, property: string, value: any, - id: string = DEFAULT_NAMESPACE_ID) { - engine.setProperty(id, element, property, value); -} - -function listen( - element: any, engine: NoopAnimationEngine, eventName: string, phaseName: string, - callback: (event: any) => any, id: string = DEFAULT_NAMESPACE_ID) { - return engine.listen(id, element, eventName, phaseName, callback); -} diff --git a/packages/platform-browser/animations/test/noop_animations_module_spec.ts b/packages/platform-browser/animations/test/noop_animations_module_spec.ts index 0735104a85..5041fed64e 100644 --- a/packages/platform-browser/animations/test/noop_animations_module_spec.ts +++ b/packages/platform-browser/animations/test/noop_animations_module_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {animate, style, transition, trigger} from '@angular/animations'; -import {ɵAnimationEngine, ɵNoopAnimationEngine} from '@angular/animations/browser'; +import {ɵAnimationEngine} from '@angular/animations/browser'; import {Component} from '@angular/core'; import {TestBed} from '@angular/core/testing'; @@ -16,11 +16,6 @@ export function main() { describe('NoopAnimationsModule', () => { beforeEach(() => { TestBed.configureTestingModule({imports: [NoopAnimationsModule]}); }); - it('the engine should be a Noop engine', () => { - const engine = TestBed.get(ɵAnimationEngine); - expect(engine instanceof ɵNoopAnimationEngine).toBeTruthy(); - }); - it('should flush and fire callbacks when the zone becomes stable', (async) => { @Component({ selector: 'my-cmp', @@ -80,20 +75,21 @@ export function main() { cmp.exp = true; fixture.detectChanges(); - cmp.startEvent = null; - cmp.doneEvent = null; - - cmp.exp = false; - fixture.detectChanges(); - fixture.whenStable().then(() => { - expect(cmp.startEvent.triggerName).toEqual('myAnimation'); - expect(cmp.startEvent.phaseName).toEqual('start'); - expect(cmp.startEvent.toState).toEqual('void'); - expect(cmp.doneEvent.triggerName).toEqual('myAnimation'); - expect(cmp.doneEvent.phaseName).toEqual('done'); - expect(cmp.doneEvent.toState).toEqual('void'); - async(); + cmp.startEvent = null; + cmp.doneEvent = null; + + cmp.exp = false; + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(cmp.startEvent.triggerName).toEqual('myAnimation'); + expect(cmp.startEvent.phaseName).toEqual('start'); + expect(cmp.startEvent.toState).toEqual('void'); + expect(cmp.doneEvent.triggerName).toEqual('myAnimation'); + expect(cmp.doneEvent.phaseName).toEqual('done'); + expect(cmp.doneEvent.toState).toEqual('void'); + async(); + }); }); }); }); diff --git a/packages/platform-browser/test/animation/animation_renderer_spec.ts b/packages/platform-browser/test/animation/animation_renderer_spec.ts index 977b15aa63..a70c33ae11 100644 --- a/packages/platform-browser/test/animation/animation_renderer_spec.ts +++ b/packages/platform-browser/test/animation/animation_renderer_spec.ts @@ -11,7 +11,6 @@ import {Component, Injectable, NgZone, RendererFactory2, RendererType2, ViewChil import {TestBed} from '@angular/core/testing'; import {BrowserAnimationsModule, ɵAnimationRendererFactory as AnimationRendererFactory} from '@angular/platform-browser/animations'; import {DomRendererFactory2} from '@angular/platform-browser/src/dom/dom_renderer'; - import {InjectableAnimationEngine} from '../../animations/src/providers'; import {el} from '../../testing/src/browser_util'; @@ -311,7 +310,7 @@ export function main() { } @Injectable() -class MockAnimationEngine extends AnimationEngine { +class MockAnimationEngine extends InjectableAnimationEngine { captures: {[method: string]: any[]} = {}; triggers: AnimationTriggerMetadata[] = []; @@ -330,8 +329,9 @@ class MockAnimationEngine extends AnimationEngine { this._capture('onRemove', [element]); } - setProperty(namespaceId: string, element: any, property: string, value: any): void { + setProperty(namespaceId: string, element: any, property: string, value: any): boolean { this._capture('setProperty', [element, property, value]); + return true; } listen( diff --git a/packages/upgrade/src/common/downgrade_component_adapter.ts b/packages/upgrade/src/common/downgrade_component_adapter.ts index ed7b29ff7c..212c469a75 100644 --- a/packages/upgrade/src/common/downgrade_component_adapter.ts +++ b/packages/upgrade/src/common/downgrade_component_adapter.ts @@ -92,7 +92,7 @@ export class DowngradeComponentAdapter { // for `ngOnChanges()`. This is necessary if we are already in a `$digest`, which means that // `ngOnChanges()` (which is called by a watcher) will run before the `$observe()` callback. let unwatch: any = this.componentScope.$watch(() => { - unwatch(); + unwatch(''); unwatch = null; observeFn((attrs as any)[input.attr]); }); diff --git a/tools/public_api_guard/animations/animations.d.ts b/tools/public_api_guard/animations/animations.d.ts index f5da8a4545..034fcb87de 100644 --- a/tools/public_api_guard/animations/animations.d.ts +++ b/tools/public_api_guard/animations/animations.d.ts @@ -2,7 +2,12 @@ export declare function animate(timings: string | number, styles?: AnimationStyleMetadata | AnimationKeyframesSequenceMetadata | null): AnimationAnimateMetadata; /** @experimental */ -export declare function animateChild(options?: AnimationOptions | null): AnimationAnimateChildMetadata; +export declare function animateChild(options?: AnimateChildOptions | null): AnimationAnimateChildMetadata; + +/** @experimental */ +export interface AnimateChildOptions extends AnimationOptions { + duration?: number | string; +} /** @experimental */ export declare type AnimateTimings = { @@ -87,7 +92,6 @@ export declare const enum AnimationMetadataType { /** @experimental */ export interface AnimationOptions { delay?: number | string; - duration?: number | string; params?: { [name: string]: any; }; diff --git a/tools/public_api_guard/animations/browser.d.ts b/tools/public_api_guard/animations/browser.d.ts index 1c12076bdd..bc5b326649 100644 --- a/tools/public_api_guard/animations/browser.d.ts +++ b/tools/public_api_guard/animations/browser.d.ts @@ -4,5 +4,8 @@ export declare abstract class AnimationDriver { [key: string]: string | number; }[], duration: number, delay: number, easing?: string | null, previousPlayers?: any[]): any; abstract computeStyle(element: any, prop: string, defaultValue?: string): string; + abstract containsElement(elm1: any, elm2: any): boolean; + abstract matchesElement(element: any, selector: string): boolean; + abstract query(element: any, selector: string, multi: boolean): any[]; static NOOP: AnimationDriver; } diff --git a/tools/public_api_guard/animations/browser/testing.d.ts b/tools/public_api_guard/animations/browser/testing.d.ts index 7789ee8170..d551ebef61 100644 --- a/tools/public_api_guard/animations/browser/testing.d.ts +++ b/tools/public_api_guard/animations/browser/testing.d.ts @@ -4,6 +4,9 @@ export declare class MockAnimationDriver implements AnimationDriver { [key: string]: string | number; }[], duration: number, delay: number, easing: string, previousPlayers?: any[]): MockAnimationPlayer; computeStyle(element: any, prop: string, defaultValue?: string): string; + containsElement(elm1: any, elm2: any): boolean; + matchesElement(element: any, selector: string): boolean; + query(element: any, selector: string, multi: boolean): any[]; static log: AnimationPlayer[]; }