diff --git a/modules/@angular/animations/src/animation_metadata.ts b/modules/@angular/animations/src/animation_metadata.ts index 85e2a11ef6..90bf2de8f9 100755 --- a/modules/@angular/animations/src/animation_metadata.ts +++ b/modules/@angular/animations/src/animation_metadata.ts @@ -66,7 +66,7 @@ export interface AnimationStateMetadata extends AnimationMetadata { */ export interface AnimationTransitionMetadata extends AnimationMetadata { expr: string|((fromState: string, toState: string) => boolean); - animation: AnimationMetadata; + animation: AnimationMetadata|AnimationMetadata[]; } /** @@ -86,8 +86,8 @@ export interface AnimationKeyframesSequenceMetadata extends AnimationMetadata { * @experimental Animation support is experimental. */ export interface AnimationStyleMetadata extends AnimationMetadata { - styles: {[key: string]: string | number}[]; - offset: number; + styles: {[key: string]: string | number}|{[key: string]: string | number}[]; + offset?: number; } /** @@ -341,27 +341,9 @@ export function sequence(steps: AnimationMetadata[]): AnimationSequenceMetadata export function style( tokens: {[key: string]: string | number} | Array<{[key: string]: string | number}>): AnimationStyleMetadata { - let input: ɵStyleData[]; - let offset: number = null; - if (Array.isArray(tokens)) { - input = <ɵStyleData[]>tokens; - } else { - input = [<ɵStyleData>tokens]; - } - input.forEach(entry => { - const entryOffset = (entry as ɵStyleData)['offset']; - if (entryOffset != null) { - offset = offset == null ? parseFloat(entryOffset) : offset; - } - }); - return _style(offset, input); + return {type: AnimationMetadataType.Style, styles: tokens}; } -function _style(offset: number, styles: ɵStyleData[]): AnimationStyleMetadata { - return {type: AnimationMetadataType.Style, styles: styles, offset: offset}; -} - - /** * `state` is an animation-specific function that is designed to be used inside of Angular2's * animation DSL language. If this information is new, please navigate to the {@link @@ -573,9 +555,5 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe export function transition( stateChangeExpr: string | ((fromState: string, toState: string) => boolean), steps: AnimationMetadata | AnimationMetadata[]): AnimationTransitionMetadata { - return { - type: AnimationMetadataType.Transition, - expr: stateChangeExpr, - animation: Array.isArray(steps) ? sequence(steps) : steps - }; + return {type: AnimationMetadataType.Transition, expr: stateChangeExpr, animation: steps}; } diff --git a/modules/@angular/compiler-cli/integrationtest/src/animate.ts b/modules/@angular/compiler-cli/integrationtest/src/animate.ts index 234a650c5a..0c746888bd 100644 --- a/modules/@angular/compiler-cli/integrationtest/src/animate.ts +++ b/modules/@angular/compiler-cli/integrationtest/src/animate.ts @@ -5,12 +5,8 @@ * 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, Component, animate, state, style, transition, trigger} from '@angular/core'; - -export function anyToAny(stateA: string, stateB: string): boolean { - return Math.random() != Math.random(); -} +import {AUTO_STYLE, animate, state, style, transition, trigger} from '@angular/animations'; +import {Component} from '@angular/core'; @Component({ selector: 'animate-cmp', @@ -20,7 +16,7 @@ export function anyToAny(stateA: string, stateB: string): boolean { state('*', style({height: AUTO_STYLE, color: 'black', borderColor: 'black'})), state('closed, void', style({height: '0px', color: 'maroon', borderColor: 'maroon'})), state('open', style({height: AUTO_STYLE, borderColor: 'green', color: 'green'})), - transition(anyToAny, animate('1s')), transition('* => *', animate(500)) + transition('* => *', animate('1s')) ])], template: ` diff --git a/modules/@angular/compiler-cli/integrationtest/src/module.ts b/modules/@angular/compiler-cli/integrationtest/src/module.ts index 6772466f42..2d1b6d94e3 100644 --- a/modules/@angular/compiler-cli/integrationtest/src/module.ts +++ b/modules/@angular/compiler-cli/integrationtest/src/module.ts @@ -8,9 +8,9 @@ import {ApplicationRef, NgModule} from '@angular/core'; import {FormsModule} from '@angular/forms'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {ServerModule} from '@angular/platform-server'; import {MdButtonModule} from '@angular2-material/button'; - // Note: don't refer to third_party_src as we want to test that // we can compile components from node_modules! import {ThirdpartyModule} from 'third_party/module'; @@ -50,6 +50,7 @@ import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, Directive ComponentUsingThirdParty, ], imports: [ + NoopAnimationsModule, ServerModule, FormsModule, MdButtonModule, diff --git a/modules/@angular/core/src/animation/animation_metadata_wrapped.ts b/modules/@angular/core/src/animation/animation_metadata_wrapped.ts index 9c0823f05d..b433d7592e 100644 --- a/modules/@angular/core/src/animation/animation_metadata_wrapped.ts +++ b/modules/@angular/core/src/animation/animation_metadata_wrapped.ts @@ -39,7 +39,7 @@ export interface AnimationStateMetadata extends AnimationMetadata { */ export interface AnimationTransitionMetadata extends AnimationMetadata { expr: string|((fromState: string, toState: string) => boolean); - animation: AnimationMetadata; + animation: AnimationMetadata|AnimationMetadata[]; } /** @@ -53,8 +53,8 @@ export interface AnimationKeyframesSequenceMetadata extends AnimationMetadata { * @deprecated This symbol has moved. Please Import from @angular/animations instead! */ export interface AnimationStyleMetadata extends AnimationMetadata { - styles: {[key: string]: string | number}[]; - offset: number; + styles: {[key: string]: string | number}|{[key: string]: string | number}[]; + offset?: number; } /** diff --git a/modules/@angular/platform-browser/animations/src/dsl/animation_timeline_visitor.ts b/modules/@angular/platform-browser/animations/src/dsl/animation_timeline_visitor.ts index 084df085b3..6422cc1eec 100644 --- a/modules/@angular/platform-browser/animations/src/dsl/animation_timeline_visitor.ts +++ b/modules/@angular/platform-browser/animations/src/dsl/animation_timeline_visitor.ts @@ -278,7 +278,7 @@ export class AnimationTimelineVisitor implements AnimationDslVisitor { const firstKeyframe = ast.steps[0]; let offsetGap = 0; - const containsOffsets = firstKeyframe.styles.find(styles => styles['offset'] >= 0); + const containsOffsets = getOffset(firstKeyframe) != null; if (!containsOffsets) { offsetGap = MAX_KEYFRAME_OFFSET / limit; } @@ -291,8 +291,9 @@ export class AnimationTimelineVisitor implements AnimationDslVisitor { ast.steps.forEach((step: AnimationStyleMetadata, i: number) => { const normalizedStyles = normalizeStyles(step.styles); - const offset = containsOffsets ? normalizedStyles['offset'] : - (i == limit ? MAX_KEYFRAME_OFFSET : i * offsetGap); + const offset = containsOffsets ? + (step.offset != null ? step.offset : parseFloat(normalizedStyles['offset'] as string)) : + (i == limit ? MAX_KEYFRAME_OFFSET : i * offsetGap); innerTimeline.forwardTime(offset * duration); innerTimeline.setStyles(normalizedStyles); }); @@ -424,3 +425,22 @@ export class TimelineBuilder { return createTimelineInstruction(finalKeyframes, this.duration, this.startTime, this.easing); } } + +function getOffset(ast: AnimationStyleMetadata): number { + let offset = ast.offset; + if (offset == null) { + const styles = ast.styles; + if (Array.isArray(styles)) { + for (let i = 0; i < styles.length; i++) { + const o = styles[i]['offset'] as number; + if (o != null) { + offset = o; + break; + } + } + } else { + offset = styles['offset'] as number; + } + } + return offset; +} diff --git a/modules/@angular/platform-browser/animations/src/dsl/animation_transition_factory.ts b/modules/@angular/platform-browser/animations/src/dsl/animation_transition_factory.ts index 946d97d2eb..f90b8639f7 100644 --- a/modules/@angular/platform-browser/animations/src/dsl/animation_transition_factory.ts +++ b/modules/@angular/platform-browser/animations/src/dsl/animation_transition_factory.ts @@ -5,7 +5,8 @@ * 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, AnimationTransitionMetadata, ɵStyleData} from '@angular/animations'; +import {AnimationMetadata, AnimationTransitionMetadata, sequence, ɵStyleData} from '@angular/animations'; + import {buildAnimationKeyframes} from './animation_timeline_visitor'; import {TransitionMatcherFn} from './animation_transition_expr'; import {AnimationTransitionInstruction, createTransitionInstruction} from './animation_transition_instruction'; @@ -17,7 +18,10 @@ export class AnimationTransitionFactory { private _triggerName: string, ast: AnimationTransitionMetadata, private matchFns: TransitionMatcherFn[], private _stateStyles: {[stateName: string]: ɵStyleData}) { - this._animationAst = ast.animation; + const normalizedAst = Array.isArray(ast.animation) ? + sequence(ast.animation) : + ast.animation; + this._animationAst = normalizedAst; } match(currentState: any, nextState: any): AnimationTransitionInstruction { diff --git a/modules/@angular/platform-browser/animations/src/dsl/animation_trigger.ts b/modules/@angular/platform-browser/animations/src/dsl/animation_trigger.ts index 384c556783..397b779c16 100644 --- a/modules/@angular/platform-browser/animations/src/dsl/animation_trigger.ts +++ b/modules/@angular/platform-browser/animations/src/dsl/animation_trigger.ts @@ -93,9 +93,23 @@ class AnimationTriggerVisitor implements AnimationDslVisitor { context.transitions.push(ast); } - visitSequence(ast: AnimationSequenceMetadata, context: any) {} - visitGroup(ast: AnimationGroupMetadata, context: any) {} - visitAnimate(ast: AnimationAnimateMetadata, context: any) {} - visitStyle(ast: AnimationStyleMetadata, context: any) {} - visitKeyframeSequence(ast: AnimationKeyframesSequenceMetadata, context: any) {} + visitSequence(ast: AnimationSequenceMetadata, context: any) { + // these values are not visited in this AST + } + + visitGroup(ast: AnimationGroupMetadata, context: any) { + // these values are not visited in this AST + } + + visitAnimate(ast: AnimationAnimateMetadata, context: any) { + // these values are not visited in this AST + } + + visitStyle(ast: AnimationStyleMetadata, context: any) { + // these values are not visited in this AST + } + + visitKeyframeSequence(ast: AnimationKeyframesSequenceMetadata, context: any) { + // these values are not visited in this AST + } } diff --git a/modules/@angular/platform-browser/animations/src/dsl/animation_validator_visitor.ts b/modules/@angular/platform-browser/animations/src/dsl/animation_validator_visitor.ts index e9a0d52c3f..e8341307e1 100644 --- a/modules/@angular/platform-browser/animations/src/dsl/animation_validator_visitor.ts +++ b/modules/@angular/platform-browser/animations/src/dsl/animation_validator_visitor.ts @@ -5,7 +5,7 @@ * 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 {AnimateTimings, AnimationAnimateMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationMetadataType, AnimationSequenceMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata} from '@angular/animations'; +import {AnimateTimings, AnimationAnimateMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationMetadataType, AnimationSequenceMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, sequence} from '@angular/animations'; import {normalizeStyles, parseTimeExpression} from '../util'; @@ -52,7 +52,9 @@ export type StyleTimeTuple = { * Otherwise an error will be thrown. */ export function validateAnimationSequence(ast: AnimationMetadata) { - return new AnimationValidatorVisitor().validate(ast); + const normalizedAst = + Array.isArray(ast) ? sequence(ast) : ast; + return new AnimationValidatorVisitor().validate(normalizedAst); } export class AnimationValidatorVisitor implements AnimationDslVisitor { @@ -62,9 +64,13 @@ export class AnimationValidatorVisitor implements AnimationDslVisitor { return context.errors; } - visitState(ast: AnimationStateMetadata, context: any): any {} + visitState(ast: AnimationStateMetadata, context: any): any { + // these values are not visited in this AST + } - visitTransition(ast: AnimationTransitionMetadata, context: any): any {} + visitTransition(ast: AnimationTransitionMetadata, context: any): any { + // these values are not visited in this AST + } visitSequence(ast: AnimationSequenceMetadata, context: AnimationValidatorContext): any { ast.steps.forEach(step => visitAnimationNode(this, step, context)); diff --git a/modules/@angular/platform-browser/animations/src/util.ts b/modules/@angular/platform-browser/animations/src/util.ts index f6c85f0b5a..c51dc046e6 100644 --- a/modules/@angular/platform-browser/animations/src/util.ts +++ b/modules/@angular/platform-browser/animations/src/util.ts @@ -49,9 +49,13 @@ export function parseTimeExpression(exp: string | number, errors: string[]): Ani return {duration, delay, easing}; } -export function normalizeStyles(styles: ɵStyleData[]): ɵStyleData { +export function normalizeStyles(styles: ɵStyleData | ɵStyleData[]): ɵStyleData { const normalizedStyles: ɵStyleData = {}; - styles.forEach(data => copyStyles(data, false, normalizedStyles)); + if (Array.isArray(styles)) { + styles.forEach(data => copyStyles(data, false, normalizedStyles)); + } else { + copyStyles(styles, false, normalizedStyles); + } return normalizedStyles; } diff --git a/modules/@angular/platform-browser/animations/test/dsl/animation_spec.ts b/modules/@angular/platform-browser/animations/test/dsl/animation_spec.ts index c45456f37f..50f8c6ebb6 100644 --- a/modules/@angular/platform-browser/animations/test/dsl/animation_spec.ts +++ b/modules/@angular/platform-browser/animations/test/dsl/animation_spec.ts @@ -5,7 +5,7 @@ * 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, AnimationMetadata, animate, group, keyframes, sequence, style, ɵStyleData} from '@angular/animations'; +import {AUTO_STYLE, AnimationMetadata, AnimationMetadataType, animate, group, keyframes, sequence, style, ɵStyleData} from '@angular/animations'; import {Animation} from '../../src/dsl/animation'; import {AnimationTimelineInstruction} from '../../src/dsl/animation_timeline_instruction'; @@ -374,6 +374,38 @@ export function main() { expect(finalAnimatePlayerKeyframes[0]['height']).toEqual('300px'); expect(finalAnimatePlayerKeyframes[1]['height']).toEqual('500px'); }); + + it('should respect offsets if provided directly within the style data', () => { + const steps = animate(1000, keyframes([ + style({opacity: 0, offset: 0}), style({opacity: .6, offset: .6}), + style({opacity: 1, offset: 1}) + ])); + + const players = invokeAnimationSequence(steps); + expect(players.length).toEqual(1); + const player = players[0]; + + expect(player.keyframes).toEqual([ + {opacity: 0, offset: 0}, {opacity: .6, offset: .6}, {opacity: 1, offset: 1} + ]); + }); + + it('should respect offsets if provided directly within the style metadata type', () => { + const steps = + animate(1000, keyframes([ + {type: AnimationMetadataType.Style, offset: 0, styles: {opacity: 0}}, + {type: AnimationMetadataType.Style, offset: .4, styles: {opacity: .4}}, + {type: AnimationMetadataType.Style, offset: 1, styles: {opacity: 1}}, + ])); + + const players = invokeAnimationSequence(steps); + expect(players.length).toEqual(1); + const player = players[0]; + + expect(player.keyframes).toEqual([ + {opacity: 0, offset: 0}, {opacity: .4, offset: .4}, {opacity: 1, offset: 1} + ]); + }); }); describe('group()', () => { diff --git a/scripts/ci-lite/offline_compiler_test.sh b/scripts/ci-lite/offline_compiler_test.sh index cd0ada6e8d..50d8fe8e97 100755 --- a/scripts/ci-lite/offline_compiler_test.sh +++ b/scripts/ci-lite/offline_compiler_test.sh @@ -3,7 +3,7 @@ set -ex -o pipefail # These ones can be `npm link`ed for fast development LINKABLE_PKGS=( - $(pwd)/dist/packages-dist/{common,forms,core,compiler,compiler-cli,platform-{browser,server},platform-browser-dynamic,router,http} + $(pwd)/dist/packages-dist/{common,forms,core,compiler,compiler-cli,platform-{browser,server},platform-browser-dynamic,router,http,animations} $(pwd)/dist/tools/@angular/tsc-wrapped ) diff --git a/tools/public_api_guard/animations/typings/animations.d.ts b/tools/public_api_guard/animations/typings/animations.d.ts index 16286467c8..8d8af5ed45 100644 --- a/tools/public_api_guard/animations/typings/animations.d.ts +++ b/tools/public_api_guard/animations/typings/animations.d.ts @@ -81,15 +81,17 @@ export interface AnimationStateMetadata extends AnimationMetadata { /** @experimental */ export interface AnimationStyleMetadata extends AnimationMetadata { - offset: number; + offset?: number; styles: { [key: string]: string | number; + } | { + [key: string]: string | number; }[]; } /** @experimental */ export interface AnimationTransitionMetadata extends AnimationMetadata { - animation: AnimationMetadata; + animation: AnimationMetadata | AnimationMetadata[]; expr: string | ((fromState: string, toState: string) => boolean); } diff --git a/tools/public_api_guard/core/typings/core.d.ts b/tools/public_api_guard/core/typings/core.d.ts index 25b6a35321..8105f77149 100644 --- a/tools/public_api_guard/core/typings/core.d.ts +++ b/tools/public_api_guard/core/typings/core.d.ts @@ -70,9 +70,11 @@ export declare type AnimationStateTransitionMetadata = any; /** @deprecated */ export interface AnimationStyleMetadata extends AnimationMetadata { - offset: number; + offset?: number; styles: { [key: string]: string | number; + } | { + [key: string]: string | number; }[]; } @@ -91,7 +93,7 @@ export interface AnimationTransitionEvent { /** @deprecated */ export interface AnimationTransitionMetadata extends AnimationMetadata { - animation: AnimationMetadata; + animation: AnimationMetadata | AnimationMetadata[]; expr: string | ((fromState: string, toState: string) => boolean); }