refactor(animations): support browser animation rendering (#14578)

This commit is contained in:
Matias Niemelä
2017-02-22 15:14:49 -08:00
committed by Igor Minar
parent 88755b0dae
commit 830393d234
88 changed files with 3429 additions and 1225 deletions
+70 -1
View File
@@ -7,6 +7,7 @@ cd `dirname $0`
PACKAGES=(core
compiler
common
animations
forms
platform-browser
platform-browser-dynamic
@@ -14,7 +15,6 @@ PACKAGES=(core
platform-server
platform-webworker
platform-webworker-dynamic
animation
upgrade
router
compiler-cli
@@ -161,6 +161,10 @@ do
JS_STATIC_PATH_ES5=${DEST_MODULE}/${PACKAGE}/static.es5.js
JS_UPGRADE_PATH=${DEST_MODULE}/${PACKAGE}/upgrade.js
JS_UPGRADE_PATH_ES5=${DEST_MODULE}/${PACKAGE}/upgrade.es5.js
JS_ANIMATIONS_PATH=${DEST_MODULE}/${PACKAGE}/animations.js
JS_ANIMATIONS_PATH_ES5=${DEST_MODULE}/${PACKAGE}/animations.es5.js
JS_ANIMATIONS_TESTING_PATH=${DEST_MODULE}/${PACKAGE}/animations/testing.js
JS_ANIMATIONS_TESTING_PATH_ES5=${DEST_MODULE}/${PACKAGE}/animations/testing.es5.js
# UMD/ES5
UMD_ES5_PATH=${DEST_BUNDLES}/${PACKAGE}.umd.js
@@ -170,6 +174,9 @@ do
UMD_ES5_MIN_PATH=${DEST_BUNDLES}/${PACKAGE}.umd.min.js
UMD_STATIC_ES5_MIN_PATH=${DEST_BUNDLES}/${PACKAGE}-static.umd.min.js
UMD_UPGRADE_ES5_MIN_PATH=${DEST_BUNDLES}/${PACKAGE}-upgrade.umd.min.js
UMD_ANIMATIONS_ES5_PATH=${DEST_BUNDLES}/${PACKAGE}-animations.umd.js
UMD_ANIMATIONS_ES5_MIN_PATH=${DEST_BUNDLES}/${PACKAGE}-animations.umd.min.js
UMD_ANIMATIONS_TESTING_ES5_PATH=${DEST_BUNDLES}/${PACKAGE}-animations-testing.umd.js
if [[ ${PACKAGE} != router ]]; then
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
@@ -218,6 +225,16 @@ do
$TSC -p ${SRCDIR}/tsconfig-testing.json
fi
if [[ -e ${SRCDIR}/tsconfig-animations.json ]]; then
echo "====== [${PACKAGE}]: COMPILING (ANIMATIONS): ${TSC} -p ${SRCDIR}/tsconfig-animations.json"
$TSC -p ${SRCDIR}/tsconfig-animations.json
if [[ -e ${SRCDIR}/tsconfig-animations-testing.json ]]; then
echo "====== [${PACKAGE}]: COMPILING (ANIMATION TESTING): ${TSC} -p ${SRCDIR}/tsconfig-animations-testing.json"
$TSC -p ${SRCDIR}/tsconfig-animations-testing.json
fi
fi
if [[ -e ${SRCDIR}/tsconfig-static.json ]]; then
echo "====== [${PACKAGE}]: COMPILING (STATIC): ${TSC} -p ${SRCDIR}/tsconfig-static.json"
$TSC -p ${SRCDIR}/tsconfig-static.json
@@ -357,6 +374,58 @@ do
mv ${UMD_UPGRADE_ES5_PATH}.tmp ${UMD_UPGRADE_ES5_PATH}
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_UPGRADE_ES5_MIN_PATH} ${UMD_UPGRADE_ES5_PATH}
fi
if [[ -d animations ]]; then
echo "====== Rollup ${PACKAGE} animations"
../../../node_modules/.bin/rollup -i ${DESTDIR}/animations/index.js -o ${DESTDIR}/animations.tmp.js
echo "====== Downleveling ${PACKAGE} ANIMATIONS to ES5/UMD"
[[ -e ${SRCDIR}/.babelrc-animations ]] && cp ${SRCDIR}/.babelrc-animations ${DESTDIR}/.babelrc
$BABELJS ${DESTDIR}/animations.tmp.js -o ${UMD_ANIMATIONS_ES5_PATH}
rm -f ${DESTDIR}/.babelrc
echo "====== Move ${PACKAGE} animations typings"
rsync -a --exclude=*.js --exclude=*.js.map ${DESTDIR}/animations/ ${DESTDIR}/typings/animations
mv ${DESTDIR}/typings/animations/index.d.ts ${DESTDIR}/typings/animations/animations.d.ts
mv ${DESTDIR}/typings/animations/index.metadata.json ${DESTDIR}/typings/animations/animations.metadata.json
echo "====== Rollup ${PACKAGE} animations/testing"
../../../node_modules/.bin/rollup -i ${DESTDIR}/animations/testing/index.js -o ${DESTDIR}/animations-testing.tmp.js
echo "====== Downleveling ${PACKAGE} ANIMATIONS TESTING to ES5/UMD"
[[ -e ${SRCDIR}/.babelrc-animations-testing ]] && cp ${SRCDIR}/.babelrc-animations-testing ${DESTDIR}/.babelrc
$BABELJS ${DESTDIR}/animations-testing.tmp.js -o ${UMD_ANIMATIONS_TESTING_ES5_PATH}
rm -f ${DESTDIR}/.babelrc
echo "====== Move ${PACKAGE} animations testing typings"
rsync -a --exclude=*.js --exclude=*.js.map ${DESTDIR}/animations/testing/ ${DESTDIR}/typings/animations/testing
mv ${DESTDIR}/typings/animations/testing/index.d.ts ${DESTDIR}/typings/animations/testing/testing.d.ts
mv ${DESTDIR}/typings/animations/testing/index.metadata.json ${DESTDIR}/typings/animations/testing/testing.metadata.json
rm -rf ${DESTDIR}/animations
mkdir ${DESTDIR}/animations && [[ -d ${DEST_MODULE}/${PACKAGE} ]] || mkdir ${DEST_MODULE}/${PACKAGE}
mkdir ${DESTDIR}/animations/testing
getPackageContents "${PACKAGE}" "animations" > ${DESTDIR}/animations/package.json
echo '{"typings": "../../typings/animations/testing/testing.d.ts", "main": "../../bundles/platform-browser-animations-testing.umd.js", "module": "../../@angular/platform-browser/animations/testing.es5.js", "es2015": "../../@angular/platform-browser/animations/testing.js"}' > ${DESTDIR}/animations/testing/package.json
mv ${DESTDIR}/animations.tmp.js ${JS_ANIMATIONS_PATH}
$BABELJS ${JS_ANIMATIONS_PATH} -o ${JS_ANIMATIONS_PATH_ES5}
cat ${LICENSE_BANNER} > ${UMD_ANIMATIONS_ES5_PATH}.tmp
cat ${UMD_ANIMATIONS_ES5_PATH} >> ${UMD_ANIMATIONS_ES5_PATH}.tmp
mv ${UMD_ANIMATIONS_ES5_PATH}.tmp ${UMD_ANIMATIONS_ES5_PATH}
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_ANIMATIONS_ES5_MIN_PATH} ${UMD_ANIMATIONS_ES5_PATH}
mkdir ${DEST_MODULE}/${PACKAGE}/animations
mv ${DESTDIR}/animations-testing.tmp.js ${JS_ANIMATIONS_TESTING_PATH}
$BABELJS ${JS_ANIMATIONS_TESTING_PATH} -o ${JS_ANIMATIONS_TESTING_PATH_ES5}
cat ${LICENSE_BANNER} > ${UMD_ANIMATIONS_TESTING_ES5_PATH}.tmp
cat ${UMD_ANIMATIONS_TESTING_ES5_PATH} >> ${UMD_ANIMATIONS_TESTING_ES5_PATH}.tmp
mv ${UMD_ANIMATIONS_TESTING_ES5_PATH}.tmp ${UMD_ANIMATIONS_TESTING_ES5_PATH}
fi
) 2>&1 | grep -v "as external dependency"
fi
+1 -1
View File
@@ -36,4 +36,4 @@ gulp.task('tools:build', loadTask('tools-build'));
gulp.task('check-cycle', loadTask('check-cycle'));
gulp.task('serve', loadTask('serve', 'default'));
gulp.task('serve-examples', loadTask('serve', 'examples'));
gulp.task('changelog', loadTask('changelog'));
gulp.task('changelog', loadTask('changelog'));
-18
View File
@@ -1,18 +0,0 @@
{
"name": "@angular/animation",
"version": "0.0.0-PLACEHOLDER",
"description": "Angular - animation integration with web-animations",
"main": "./bundles/animation.umd.js",
"module": "./@angular/animation.es5.js",
"es2015": "./@angular/animation.js",
"typings": "./typings/animation.d.ts",
"author": "angular",
"license": "MIT",
"peerDependencies": {
"@angular/core": "0.0.0-PLACEHOLDER"
},
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.git"
}
}
@@ -1,11 +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
*/
export {AnimationModule} from './animation_module';
export {Animation} from './dsl/animation';
export {AUTO_STYLE, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationSequenceMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, animate, group, keyframes, sequence, state, style, transition} from './dsl/animation_metadata';
export {AnimationTrigger, trigger} from './dsl/animation_trigger';
@@ -1,35 +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 {NgModule, ɵTransitionEngine} from '@angular/core';
import {AnimationStyleNormalizer} from './dsl/style_normalization/animation_style_normalizer';
import {WebAnimationsStyleNormalizer} from './dsl/style_normalization/web_animations_style_normalizer';
import {AnimationDriver, NoOpAnimationDriver} from './engine/animation_driver';
import {DomAnimationTransitionEngine} from './engine/dom_animation_transition_engine';
import {WebAnimationsDriver, supportsWebAnimations} from './engine/web_animations/web_animations_driver';
export function resolveDefaultAnimationDriver(): AnimationDriver {
if (supportsWebAnimations()) {
return new WebAnimationsDriver();
}
return new NoOpAnimationDriver();
}
/**
* The module that includes all animation code such as `style()`, `animate()`, `trigger()`, etc...
*
* @experimental
*/
@NgModule({
providers: [
{provide: AnimationDriver, useFactory: resolveDefaultAnimationDriver},
{provide: AnimationStyleNormalizer, useClass: WebAnimationsStyleNormalizer},
{provide: ɵTransitionEngine, useClass: DomAnimationTransitionEngine}
]
})
export class AnimationModule {
}
@@ -1,40 +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 * as meta from './animation_metadata';
export interface AnimationDslVisitor {
visitState(ast: meta.AnimationStateMetadata, context: any): any;
visitTransition(ast: meta.AnimationTransitionMetadata, context: any): any;
visitSequence(ast: meta.AnimationSequenceMetadata, context: any): any;
visitGroup(ast: meta.AnimationGroupMetadata, context: any): any;
visitAnimate(ast: meta.AnimationAnimateMetadata, context: any): any;
visitStyle(ast: meta.AnimationStyleMetadata, context: any): any;
visitKeyframeSequence(ast: meta.AnimationKeyframesSequenceMetadata, context: any): any;
}
export function visitAnimationNode(
visitor: AnimationDslVisitor, node: meta.AnimationMetadata, context: any) {
switch (node.type) {
case meta.AnimationMetadataType.State:
return visitor.visitState(<meta.AnimationStateMetadata>node, context);
case meta.AnimationMetadataType.Transition:
return visitor.visitTransition(<meta.AnimationTransitionMetadata>node, context);
case meta.AnimationMetadataType.Sequence:
return visitor.visitSequence(<meta.AnimationSequenceMetadata>node, context);
case meta.AnimationMetadataType.Group:
return visitor.visitGroup(<meta.AnimationGroupMetadata>node, context);
case meta.AnimationMetadataType.Animate:
return visitor.visitAnimate(<meta.AnimationAnimateMetadata>node, context);
case meta.AnimationMetadataType.KeyframeSequence:
return visitor.visitKeyframeSequence(<meta.AnimationKeyframesSequenceMetadata>node, context);
case meta.AnimationMetadataType.Style:
return visitor.visitStyle(<meta.AnimationStyleMetadata>node, context);
default:
throw new Error(`Unable to resolve animation metadata node #${node.type}`);
}
}
@@ -1,31 +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, ɵNoOpAnimationPlayer} from '@angular/core';
import {StyleData} from '../common/style_data';
/**
* @experimental
*/
export class NoOpAnimationDriver implements AnimationDriver {
animate(
element: any, keyframes: StyleData[], duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return new ɵNoOpAnimationPlayer();
}
}
/**
* @experimental
*/
export abstract class AnimationDriver {
static NOOP: AnimationDriver = new NoOpAnimationDriver();
abstract animate(
element: any, keyframes: StyleData[], duration: number, delay: number, easing: string,
previousPlayers?: AnimationPlayer[]): AnimationPlayer;
}
@@ -1,235 +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, Injectable, ɵAnimationGroupPlayer, ɵNoOpAnimationPlayer, ɵTransitionEngine} from '@angular/core';
import {StyleData} from '../common/style_data';
import {AnimationTimelineInstruction} from '../dsl/animation_timeline_instruction';
import {AnimationTransitionInstruction} from '../dsl/animation_transition_instruction';
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
import {AnimationDriver} from './animation_driver';
import {AnimationEngineInstruction, AnimationTransitionInstructionType} from './animation_engine_instruction';
export declare type AnimationPlayerTuple = {
element: any; player: AnimationPlayer;
};
@Injectable()
export class DomAnimationTransitionEngine extends ɵTransitionEngine {
private _flaggedInserts = new Set<any>();
private _queuedRemovals: any[] = [];
private _queuedAnimations: AnimationPlayerTuple[] = [];
private _activeElementAnimations = new Map<any, AnimationPlayer[]>();
private _activeTransitionAnimations = new Map<any, {[triggerName: string]: AnimationPlayer}>();
constructor(private _driver: AnimationDriver, private _normalizer: AnimationStyleNormalizer) {
super();
}
insertNode(container: any, element: any) {
container.appendChild(element);
this._flaggedInserts.add(element);
}
removeNode(element: any) { this._queuedRemovals.push(element); }
process(element: any, instructions: AnimationEngineInstruction[]): AnimationPlayer {
const players = instructions.map(instruction => {
if (instruction.type == AnimationTransitionInstructionType.TransitionAnimation) {
return this._handleTransitionAnimation(
element, <AnimationTransitionInstruction>instruction);
}
if (instruction.type == AnimationTransitionInstructionType.TimelineAnimation) {
return this._handleTimelineAnimation(
element, <AnimationTimelineInstruction>instruction, []);
}
return new ɵNoOpAnimationPlayer();
});
return optimizeGroupPlayer(players);
}
private _handleTransitionAnimation(element: any, instruction: AnimationTransitionInstruction):
AnimationPlayer {
const triggerName = instruction.triggerName;
const elmTransitionMap = getOrSetAsInMap(this._activeTransitionAnimations, element, {});
let previousPlayers: AnimationPlayer[];
if (instruction.isRemovalTransition) {
// we make a copy of the array because the actual source array is modified
// each time a player is finished/destroyed (the forEach loop would fail otherwise)
previousPlayers = copyArray(this._activeElementAnimations.get(element));
} else {
previousPlayers = [];
const existingPlayer = elmTransitionMap[triggerName];
if (existingPlayer) {
previousPlayers.push(existingPlayer);
}
}
// it's important to do this step before destroying the players
// so that the onDone callback below won't fire before this
eraseStyles(element, instruction.fromStyles);
// we first run this so that the previous animation player
// data can be passed into the successive animation players
const players = instruction.timelines.map(
timelineInstruction => this._buildPlayer(element, timelineInstruction, previousPlayers));
previousPlayers.forEach(previousPlayer => previousPlayer.destroy());
const player = optimizeGroupPlayer(players);
player.onDone(() => {
player.destroy();
const elmTransitionMap = this._activeTransitionAnimations.get(element);
if (elmTransitionMap) {
delete elmTransitionMap[triggerName];
if (Object.keys(elmTransitionMap).length == 0) {
this._activeTransitionAnimations.delete(element);
}
}
deleteFromArrayMap(this._activeElementAnimations, element, player);
setStyles(element, instruction.toStyles);
});
this._queuePlayer(element, player);
elmTransitionMap[triggerName] = player;
return player;
}
private _handleTimelineAnimation(
element: any, instruction: AnimationTimelineInstruction,
previousPlayers: AnimationPlayer[]): AnimationPlayer {
const player = this._buildPlayer(element, instruction, previousPlayers);
player.onDestroy(() => { deleteFromArrayMap(this._activeElementAnimations, element, player); });
this._queuePlayer(element, player);
return player;
}
private _buildPlayer(
element: any, instruction: AnimationTimelineInstruction,
previousPlayers: AnimationPlayer[]): AnimationPlayer {
return this._driver.animate(
element, this._normalizeKeyframes(instruction.keyframes), instruction.duration,
instruction.delay, instruction.easing, previousPlayers);
}
private _normalizeKeyframes(keyframes: StyleData[]): StyleData[] {
const errors: string[] = [];
const normalizedKeyframes: StyleData[] = [];
keyframes.forEach(kf => {
const normalizedKeyframe: StyleData = {};
Object.keys(kf).forEach(prop => {
let normalizedProp = prop;
let normalizedValue = kf[prop];
if (prop != 'offset') {
normalizedProp = this._normalizer.normalizePropertyName(prop, errors);
normalizedValue =
this._normalizer.normalizeStyleValue(prop, normalizedProp, kf[prop], errors);
}
normalizedKeyframe[normalizedProp] = normalizedValue;
});
normalizedKeyframes.push(normalizedKeyframe);
});
if (errors.length) {
const LINE_START = '\n - ';
throw new Error(
`Unable to animate due to the following errors:${LINE_START}${errors.join(LINE_START)}`);
}
return normalizedKeyframes;
}
private _queuePlayer(element: any, player: AnimationPlayer) {
const tuple = <AnimationPlayerTuple>{element, player};
this._queuedAnimations.push(tuple);
player.init();
const elementAnimations = getOrSetAsInMap(this._activeElementAnimations, element, []);
elementAnimations.push(player);
}
triggerAnimations() {
while (this._queuedAnimations.length) {
const {player, element} = this._queuedAnimations.shift();
// in the event that an animation throws an error then we do
// not want to re-run animations on any previous animations
// if they have already been kicked off beforehand
if (!player.hasStarted()) {
player.play();
}
}
this._queuedRemovals.forEach(element => {
if (this._flaggedInserts.has(element)) return;
let parent = element;
let players: AnimationPlayer[];
while (parent = parent.parentNode) {
const match = this._activeElementAnimations.get(parent);
if (match) {
players = match;
break;
}
}
if (players) {
optimizeGroupPlayer(players).onDone(() => remove(element));
} else {
if (element.parentNode) {
remove(element);
}
}
});
this._queuedRemovals = [];
this._flaggedInserts.clear();
}
}
function getOrSetAsInMap(map: Map<any, any>, key: any, defaultValue: any) {
let value = map.get(key);
if (!value) {
map.set(key, value = defaultValue);
}
return value;
}
function deleteFromArrayMap(map: Map<any, any[]>, key: any, value: any) {
let arr = map.get(key);
if (arr) {
const index = arr.indexOf(value);
if (index >= 0) {
arr.splice(index, 1);
if (arr.length == 0) {
map.delete(key);
}
}
}
}
function setStyles(element: any, styles: StyleData) {
Object.keys(styles).forEach(prop => { element.style[prop] = styles[prop]; });
}
function eraseStyles(element: any, styles: StyleData) {
Object.keys(styles).forEach(prop => {
// IE requires '' instead of null
// see https://github.com/angular/angular/issues/7916
element.style[prop] = '';
});
}
function optimizeGroupPlayer(players: AnimationPlayer[]): AnimationPlayer {
return players.length == 1 ? players[0] : new ɵAnimationGroupPlayer(players);
}
function copyArray(source: any[]): any[] {
return source ? source.splice(0) : [];
}
function remove(element: any) {
element.parentNode.removeChild(element);
}
@@ -1,31 +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, ɵNoOpAnimationPlayer} from '@angular/core';
import {StyleData} from '../src/common/style_data';
import {AnimationDriver} from '../src/engine/animation_driver';
export class MockAnimationDriver implements AnimationDriver {
static log: AnimationPlayer[] = [];
animate(
element: any, keyframes: StyleData[], duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
const player =
new MockAnimationPlayer(element, keyframes, duration, delay, easing, previousPlayers);
MockAnimationDriver.log.push(<AnimationPlayer>player);
return <AnimationPlayer>player;
}
}
export class MockAnimationPlayer extends ɵNoOpAnimationPlayer {
constructor(
public element: any, public keyframes: StyleData[], public duration: number,
public delay: number, public easing: string, public previousPlayers: AnimationPlayer[]) {
super();
}
}
@@ -1,18 +0,0 @@
{
"extends": "./tsconfig-build",
"compilerOptions": {
"outDir": "../../../dist/packages-dist/animation",
"paths": {
"@angular/core": ["../../../dist/packages-dist/core/"],
"@angular/animation": ["../../../dist/packages-dist/animation/"]
}
},
"files": [
"testing/index.ts",
"../../../node_modules/zone.js/dist/zone.js.d.ts"
],
"angularCompilerOptions": {
"strictMetadataEmit": true
}
}
@@ -4,7 +4,7 @@
"plugins": [["transform-es2015-modules-umd", {
"globals": {
"@angular/core": "ng.core",
"@angular/animation": "ng.animation",
"@angular/animations": "ng.animations",
"rxjs/Observable": "Rx",
"rxjs/Subject": "Rx"
},
@@ -4,8 +4,7 @@
"plugins": [["transform-es2015-modules-umd", {
"globals": {
"@angular/core": "ng.core",
"@angular/animation": "ng.animation",
"@angular/animation/testing": "ng.animation.testing",
"@angular/animations": "ng.animations",
"rxjs/Observable": "Rx",
"rxjs/Subject": "Rx"
},
+18
View File
@@ -0,0 +1,18 @@
{
"name": "@angular/animations",
"version": "0.0.0-PLACEHOLDER",
"description": "Angular - animations integration with web-animationss",
"main": "./bundles/animations.umd.js",
"module": "./@angular/animations.es5.js",
"es2015": "./@angular/animations.js",
"typings": "./typings/animations.d.ts",
"author": "angular",
"license": "MIT",
"peerDependencies": {
"@angular/core": "0.0.0-PLACEHOLDER"
},
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.git"
}
}
@@ -11,4 +11,4 @@
* @description
* Entry point for all public APIs of the animation package.
*/
export * from './src/animation';
export * from './src/animations';
@@ -0,0 +1,47 @@
/**
* @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
*/
/**
* An instance of this class is returned as an event parameter when an animation
* callback is captured for an animation either during the start or done phase.
*
* ```typescript
* @Component({
* host: {
* '[@myAnimationTrigger]': 'someExpression',
* '(@myAnimationTrigger.start)': 'captureStartEvent($event)',
* '(@myAnimationTrigger.done)': 'captureDoneEvent($event)',
* },
* animations: [
* trigger("myAnimationTrigger", [
* // ...
* ])
* ]
* })
* class MyComponent {
* someExpression: any = false;
* captureStartEvent(event: AnimationEvent) {
* // the toState, fromState and totalTime data is accessible from the event variable
* }
*
* captureDoneEvent(event: AnimationEvent) {
* // the toState, fromState and totalTime data is accessible from the event variable
* }
* }
* ```
*
* @experimental Animation support is experimental.
*/
export interface AnimationEvent {
fromState: string;
toState: string;
totalTime: number;
phaseName: string;
element: any;
triggerName: string;
}
@@ -5,14 +5,20 @@
* 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 '../common/style_data';
export interface ɵStyleData { [key: string]: string|number; }
/**
* @experimental Animation support is experimental.
*/
export declare type AnimateTimings = {
duration: number,
delay: number,
easing: string
};
/**
* @experimental Animation support is experimental.
*/
export const enum AnimationMetadataType {
State,
Transition,
@@ -33,6 +39,14 @@ export const AUTO_STYLE = '*';
*/
export interface AnimationMetadata { type: AnimationMetadataType; }
/**
* @experimental Animation support is experimental.
*/
export interface AnimationTriggerMetadata {
name: string;
definitions: AnimationMetadata[];
}
/**
* Metadata representing the entry of animations. Instances of this class are provided via the
* animation DSL when the {@link state state animation function} is called.
@@ -72,7 +86,7 @@ export interface AnimationKeyframesSequenceMetadata extends AnimationMetadata {
* @experimental Animation support is experimental.
*/
export interface AnimationStyleMetadata extends AnimationMetadata {
styles: StyleData[];
styles: {[key: string]: string | number}[];
offset: number;
}
@@ -103,6 +117,61 @@ export interface AnimationSequenceMetadata extends AnimationMetadata { steps: An
*/
export interface AnimationGroupMetadata extends AnimationMetadata { steps: AnimationMetadata[]; }
/**
* `trigger` 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
Component#animations-anchor component animations metadata page} to gain a better understanding of
how animations in Angular2 are used.
*
* `trigger` Creates an animation trigger which will a list of {@link state state} and {@link
transition transition} entries that will be evaluated when the expression bound to the trigger
changes.
*
* Triggers are registered within the component annotation data under the {@link
Component#animations-anchor animations section}. An animation trigger can be placed on an element
within a template by referencing the name of the trigger followed by the expression value that the
trigger is bound to (in the form of `[@triggerName]="expression"`.
*
* ### Usage
*
* `trigger` will create an animation trigger reference based on the provided `name` value. The
provided `animation` value is expected to be an array consisting of {@link state state} and {@link
transition transition} declarations.
*
* ```typescript
* @Component({
* selector: 'my-component',
* templateUrl: 'my-component-tpl.html',
* animations: [
* trigger("myAnimationTrigger", [
* state(...),
* state(...),
* transition(...),
* transition(...)
* ])
* ]
* })
* class MyComponent {
* myStatusExp = "something";
* }
* ```
*
* The template associated with this component will make use of the `myAnimationTrigger` animation
trigger by binding to an element within its template code.
*
* ```html
* <!-- somewhere inside of my-component-tpl.html -->
* <div [@myAnimationTrigger]="myStatusExp">...</div>
tools/gulp-tasks/validate-commit-message.js ```
*
* {@example core/animation/ts/dsl/animation_example.ts region='Component'}
*
* @experimental Animation support is experimental.
*/
export function trigger(name: string, definitions: AnimationMetadata[]): AnimationTriggerMetadata {
return {name, definitions};
}
/**
* `animate` 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
@@ -272,15 +341,15 @@ export function sequence(steps: AnimationMetadata[]): AnimationSequenceMetadata
export function style(
tokens: {[key: string]: string | number} |
Array<{[key: string]: string | number}>): AnimationStyleMetadata {
let input: StyleData[];
let input: ɵStyleData[];
let offset: number = null;
if (Array.isArray(tokens)) {
input = <StyleData[]>tokens;
input = <ɵStyleData[]>tokens;
} else {
input = [<StyleData>tokens];
input = [<ɵStyleData>tokens];
}
input.forEach(entry => {
const entryOffset = (entry as StyleData)['offset'];
const entryOffset = (entry as ɵStyleData)['offset'];
if (entryOffset != null) {
offset = offset == null ? parseFloat(<string>entryOffset) : offset;
}
@@ -288,7 +357,7 @@ export function style(
return _style(offset, input);
}
function _style(offset: number, styles: StyleData[]): AnimationStyleMetadata {
function _style(offset: number, styles: ɵStyleData[]): AnimationStyleMetadata {
return {type: AnimationMetadataType.Style, styles: styles, offset: offset};
}
@@ -0,0 +1,18 @@
/**
* @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
*/
/**
* @module
* @description
* Entry point for all animation APIs of the animation package.
*/
export {AnimationEvent} from './animation_event';
export {AUTO_STYLE, AnimateTimings, AnimationAnimateMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationMetadataType, AnimationSequenceMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, AnimationTriggerMetadata, animate, group, keyframes, sequence, state, style, transition, trigger, ɵStyleData} from './animation_metadata';
export {AnimationPlayer, NoOpAnimationPlayer} from './players/animation_player';
export * from './private_export';
@@ -0,0 +1,109 @@
/**
* @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 {scheduleMicroTask} from '../util';
import {AnimationPlayer} from './animation_player';
export class AnimationGroupPlayer implements AnimationPlayer {
private _onDoneFns: Function[] = [];
private _onStartFns: Function[] = [];
private _finished = false;
private _started = false;
private _destroyed = false;
private _onDestroyFns: Function[] = [];
public parentPlayer: AnimationPlayer = null;
constructor(private _players: AnimationPlayer[]) {
let count = 0;
const total = this._players.length;
if (total == 0) {
scheduleMicroTask(() => this._onFinish());
} else {
this._players.forEach(player => {
player.parentPlayer = this;
player.onDone(() => {
if (++count >= total) {
this._onFinish();
}
});
});
}
}
private _onFinish() {
if (!this._finished) {
this._finished = true;
this._onDoneFns.forEach(fn => fn());
this._onDoneFns = [];
}
}
init(): void { this._players.forEach(player => player.init()); }
onStart(fn: () => void): void { this._onStartFns.push(fn); }
onDone(fn: () => void): void { this._onDoneFns.push(fn); }
onDestroy(fn: () => void): void { this._onDestroyFns.push(fn); }
hasStarted() { return this._started; }
play() {
if (!this.parentPlayer) {
this.init();
}
if (!this.hasStarted()) {
this._onStartFns.forEach(fn => fn());
this._onStartFns = [];
this._started = true;
}
this._players.forEach(player => player.play());
}
pause(): void { this._players.forEach(player => player.pause()); }
restart(): void { this._players.forEach(player => player.restart()); }
finish(): void {
this._onFinish();
this._players.forEach(player => player.finish());
}
destroy(): void {
if (!this._destroyed) {
this._onFinish();
this._players.forEach(player => player.destroy());
this._destroyed = true;
this._onDestroyFns.forEach(fn => fn());
this._onDestroyFns = [];
}
}
reset(): void {
this._players.forEach(player => player.reset());
this._destroyed = false;
this._finished = false;
this._started = false;
}
setPosition(p: number): void {
this._players.forEach(player => { player.setPosition(p); });
}
getPosition(): number {
let min = 0;
this._players.forEach(player => {
const p = player.getPosition();
min = Math.min(p, min);
});
return min;
}
get players(): AnimationPlayer[] { return this._players; }
}
@@ -0,0 +1,76 @@
/**
* @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 {scheduleMicroTask} from '../util';
/**
* @experimental Animation support is experimental.
*/
export abstract class AnimationPlayer {
abstract onDone(fn: () => void): void;
abstract onStart(fn: () => void): void;
abstract onDestroy(fn: () => void): void;
abstract init(): void;
abstract hasStarted(): boolean;
abstract play(): void;
abstract pause(): void;
abstract restart(): void;
abstract finish(): void;
abstract destroy(): void;
abstract reset(): void;
abstract setPosition(p: any /** TODO #9100 */): void;
abstract getPosition(): number;
get parentPlayer(): AnimationPlayer { throw new Error('NOT IMPLEMENTED: Base Class'); }
set parentPlayer(player: AnimationPlayer) { throw new Error('NOT IMPLEMENTED: Base Class'); }
}
/**
* @experimental Animation support is experimental.
*/
export class NoOpAnimationPlayer implements AnimationPlayer {
private _onDoneFns: Function[] = [];
private _onStartFns: Function[] = [];
private _onDestroyFns: Function[] = [];
private _started = false;
private _destroyed = false;
private _finished = false;
public parentPlayer: AnimationPlayer = null;
constructor() { scheduleMicroTask(() => this._onFinish()); }
private _onFinish() {
if (!this._finished) {
this._finished = true;
this._onDoneFns.forEach(fn => fn());
this._onDoneFns = [];
}
}
onStart(fn: () => void): void { this._onStartFns.push(fn); }
onDone(fn: () => void): void { this._onDoneFns.push(fn); }
onDestroy(fn: () => void): void { this._onDestroyFns.push(fn); }
hasStarted(): boolean { return this._started; }
init(): void {}
play(): void {
if (!this.hasStarted()) {
this._onStartFns.forEach(fn => fn());
this._onStartFns = [];
}
this._started = true;
}
pause(): void {}
restart(): void {}
finish(): void { this._onFinish(); }
destroy(): void {
if (!this._destroyed) {
this._destroyed = true;
this.finish();
this._onDestroyFns.forEach(fn => fn());
this._onDestroyFns = [];
}
}
reset(): void {}
setPosition(p: number): void {}
getPosition(): number { return 0; }
}
@@ -0,0 +1,8 @@
/**
* @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
*/
export {AnimationGroupPlayer as ɵAnimationGroupPlayer} from './players/animation_group_player';
@@ -5,4 +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
*/
export interface StyleData { [key: string]: string|number; }
export function scheduleMicroTask(cb: () => any) {
// FIXME
setTimeout(cb, 0);
}
@@ -6,9 +6,10 @@
"experimentalDecorators": true,
"module": "es2015",
"moduleResolution": "node",
"outDir": "../../../dist/packages-dist/animation",
"outDir": "../../../dist/packages-dist/animations",
"paths": {
"@angular/core": ["../../../dist/packages-dist/core"]
"@angular/core": ["../../../dist/packages-dist/core"],
"@angular/core/testing": ["../../../dist/packages-dist/core/testing"]
},
"rootDir": ".",
"sourceMap": true,
@@ -28,6 +29,6 @@
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"flatModuleOutFile": "index.js",
"flatModuleId": "@angular/animation"
"flatModuleId": "@angular/animations"
}
}
@@ -250,7 +250,7 @@ export class CompileTemplateMetadata {
styles: string[];
styleUrls: string[];
externalStylesheets: CompileStylesheetMetadata[];
animations: CompileAnimationEntryMetadata[];
animations: any[];
ngContentSelectors: string[];
interpolation: [string, string];
constructor(
@@ -263,7 +263,7 @@ export class CompileTemplateMetadata {
styleUrls?: string[],
externalStylesheets?: CompileStylesheetMetadata[],
ngContentSelectors?: string[],
animations?: CompileAnimationEntryMetadata[],
animations?: any[],
interpolation?: [string, string],
} = {}) {
this.encapsulation = encapsulation;
@@ -8,7 +8,7 @@
import {Compiler, ComponentFactory, Inject, Injector, ModuleWithComponentFactories, NgModuleFactory, Type, ɵgetComponentFactoryViewClass as getComponentFactoryViewClass} from '@angular/core';
import {AnimationCompiler} from '../animation/animation_compiler';
import {AnimationCompiler, AnimationEntryCompileResult} from '../animation/animation_compiler';
import {AnimationParser} from '../animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName} from '../compile_metadata';
import {CompilerConfig} from '../config';
@@ -272,7 +272,8 @@ export class JitCompiler implements Compiler {
(r) => { externalStylesheetsByModuleUrl.set(r.meta.moduleUrl, r); });
this._resolveStylesCompileResult(
stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl);
const parsedAnimations = this._animationParser.parseComponent(compMeta);
const parsedAnimations =
this._compilerConfig.useViewEngine ? [] : this._animationParser.parseComponent(compMeta);
const directives =
template.directives.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
const pipes = template.ngModule.transitiveModule.pipes.map(
@@ -280,7 +281,8 @@ export class JitCompiler implements Compiler {
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
identifierName(compMeta.type));
const compiledAnimations =
const compiledAnimations = this._compilerConfig.useViewEngine ?
[] :
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
const compileResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
@@ -308,9 +308,14 @@ export class CompileMetadataResolver {
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
const animations = dirMeta.animations ?
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
null;
let animations: any[];
if (this._config.useViewEngine) {
animations = dirMeta.animations;
} else {
animations = dirMeta.animations ?
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
null;
}
nonNormalizedTemplateMetadata = new cpl.CompileTemplateMetadata({
encapsulation: dirMeta.encapsulation,
@@ -414,6 +414,8 @@ class TemplateParseVisitor implements html.Visitor {
private _validateElementAnimationInputOutputs(
inputs: BoundElementPropertyAst[], outputs: BoundEventAst[],
template: CompileTemplateSummary) {
if (this.config.useViewEngine) return;
const triggerLookup = new Set<string>();
template.animations.forEach(entry => { triggerLookup.add(entry); });
@@ -49,8 +49,9 @@ export class ViewCompilerNext extends ViewCompiler {
new o.LiteralMapExpr([
new o.LiteralMapEntry('encapsulation', o.literal(component.template.encapsulation)),
new o.LiteralMapEntry('styles', styles),
// TODO: copy this from the @Component directive...
new o.LiteralMapEntry('data', o.literalMap([])),
new o.LiteralMapEntry('data', o.literalMap([
['animation', convertValueToOutputAst(component.template.animations)]
])),
])
]))
.toDeclStmt(
@@ -347,11 +348,13 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
}
const usedEvents = new Map<string, [string, string]>();
ast.outputs.forEach((event) => {
usedEvents.set(elementEventFullName(event.target, event.name), [event.target, event.name]);
const en = eventName(event);
usedEvents.set(elementEventFullName(event.target, en), [event.target, en]);
});
ast.directives.forEach((dirAst) => {
dirAst.hostEvents.forEach((event) => {
usedEvents.set(elementEventFullName(event.target, event.name), [event.target, event.name]);
const en = eventName(event);
usedEvents.set(elementEventFullName(event.target, en), [event.target, en]);
});
});
const hostBindings: {value: AST, context: o.Expression}[] = [];
@@ -713,7 +716,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverter
if (allowDefault) {
trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt());
}
const fullEventName = elementEventFullName(eventAst.target, eventAst.name);
const fullEventName = elementEventFullName(eventAst.target, eventName(eventAst));
handleEventStmts.push(
new o.IfStmt(o.literal(fullEventName).identical(EVENT_NAME_VAR), trueStmts));
});
@@ -895,7 +898,7 @@ function elementBindingDefs(inputAsts: BoundElementPropertyAst[]): o.Expression[
]);
case PropertyBindingType.Animation:
return o.literalArr([
o.literal(BindingType.ElementProperty), o.literal(inputAst.name),
o.literal(BindingType.ElementProperty), o.literal('@' + inputAst.name),
o.literal(inputAst.securityContext)
]);
case PropertyBindingType.Class:
@@ -1021,3 +1024,7 @@ function createComponentFactoryResolver(directives: DirectiveAst[]): ProviderAst
}
return null;
}
function eventName(eventAst: BoundEventAst): string {
return eventAst.isAnimation ? `@${eventAst.name}.${eventAst.phase}` : eventAst.name;
}

Some files were not shown because too many files have changed in this diff Show More