feat(animations): introduce a wave of new animation features

This commit is contained in:
Matias Niemelä
2017-04-26 10:44:28 -07:00
committed by Jason Aden
parent d761059e4d
commit 16c8167886
55 changed files with 7732 additions and 2217 deletions

View File

@ -5,37 +5,62 @@
* 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, ɵStyleData} from '@angular/animations';
import {AnimateTimings, AnimationMetadata, AnimationOptions, sequence, ɵStyleData} from '@angular/animations';
export const ONE_SECOND = 1000;
export function parseTimeExpression(exp: string | number, errors: string[]): AnimateTimings {
const regex = /^([\.\d]+)(m?s)(?:\s+([\.\d]+)(m?s))?(?:\s+([-a-z]+(?:\(.+?\))?))?$/i;
export const ENTER_CLASSNAME = 'ng-enter';
export const LEAVE_CLASSNAME = 'ng-leave';
export const ENTER_SELECTOR = '.ng-enter';
export const LEAVE_SELECTOR = '.ng-leave';
export const NG_TRIGGER_CLASSNAME = 'ng-trigger';
export const NG_TRIGGER_SELECTOR = '.ng-trigger';
export const NG_ANIMATING_CLASSNAME = 'ng-animating';
export const NG_ANIMATING_SELECTOR = '.ng-animating';
export function resolveTimingValue(value: string | number) {
if (typeof value == 'number') return value;
const matches = (value as string).match(/^(-?[\.\d]+)(m?s)/);
if (!matches || matches.length < 2) return 0;
return _convertTimeValueToMS(parseFloat(matches[1]), matches[2]);
}
function _convertTimeValueToMS(value: number, unit: string): number {
switch (unit) {
case 's':
return value * ONE_SECOND;
default: // ms or something else
return value;
}
}
export function resolveTiming(
timings: string | number | AnimateTimings, errors: any[], allowNegativeValues?: boolean) {
return timings.hasOwnProperty('duration') ?
<AnimateTimings>timings :
parseTimeExpression(<string|number>timings, errors, allowNegativeValues);
}
function parseTimeExpression(
exp: string | number, errors: string[], allowNegativeValues?: boolean): AnimateTimings {
const regex = /^(-?[\.\d]+)(m?s)(?:\s+(-?[\.\d]+)(m?s))?(?:\s+([-a-z]+(?:\(.+?\))?))?$/i;
let duration: number;
let delay: number = 0;
let easing: string|null = null;
let easing: string = '';
if (typeof exp === 'string') {
const matches = exp.match(regex);
if (matches === null) {
errors.push(`The provided timing value "${exp}" is invalid.`);
return {duration: 0, delay: 0, easing: null};
return {duration: 0, delay: 0, easing: ''};
}
let durationMatch = parseFloat(matches[1]);
const durationUnit = matches[2];
if (durationUnit == 's') {
durationMatch *= ONE_SECOND;
}
duration = Math.floor(durationMatch);
duration = _convertTimeValueToMS(parseFloat(matches[1]), matches[2]);
const delayMatch = matches[3];
const delayUnit = matches[4];
if (delayMatch != null) {
let delayVal: number = parseFloat(delayMatch);
if (delayUnit != null && delayUnit == 's') {
delayVal *= ONE_SECOND;
}
delay = Math.floor(delayVal);
delay = _convertTimeValueToMS(Math.floor(parseFloat(delayMatch)), matches[4]);
}
const easingVal = matches[5];
@ -46,9 +71,31 @@ export function parseTimeExpression(exp: string | number, errors: string[]): Ani
duration = <number>exp;
}
if (!allowNegativeValues) {
let containsErrors = false;
let startIndex = errors.length;
if (duration < 0) {
errors.push(`Duration values below 0 are not allowed for this animation step.`);
containsErrors = true;
}
if (delay < 0) {
errors.push(`Delay values below 0 are not allowed for this animation step.`);
containsErrors = true;
}
if (containsErrors) {
errors.splice(startIndex, 0, `The provided timing value "${exp}" is invalid.`);
}
}
return {duration, delay, easing};
}
export function copyObj(
obj: {[key: string]: any}, destination: {[key: string]: any} = {}): {[key: string]: any} {
Object.keys(obj).forEach(prop => { destination[prop] = obj[prop]; });
return destination;
}
export function normalizeStyles(styles: ɵStyleData | ɵStyleData[]): ɵStyleData {
const normalizedStyles: ɵStyleData = {};
if (Array.isArray(styles)) {
@ -69,7 +116,7 @@ export function copyStyles(
destination[prop] = styles[prop];
}
} else {
Object.keys(styles).forEach(prop => destination[prop] = styles[prop]);
copyObj(styles, destination);
}
return destination;
}
@ -89,3 +136,73 @@ export function eraseStyles(element: any, styles: ɵStyleData) {
});
}
}
export function normalizeAnimationEntry(steps: AnimationMetadata | AnimationMetadata[]):
AnimationMetadata {
if (Array.isArray(steps)) {
if (steps.length == 1) return steps[0];
return sequence(steps);
}
return steps as AnimationMetadata;
}
export function validateStyleParams(
value: string | number, options: AnimationOptions, errors: any[]) {
const params = options.params || {};
if (typeof value !== 'string') return;
const matches = value.toString().match(PARAM_REGEX);
if (matches) {
matches.forEach(varName => {
if (!params.hasOwnProperty(varName)) {
errors.push(
`Unable to resolve the local animation param ${varName} in the given list of values`);
}
});
}
}
const PARAM_REGEX = /\{\{\s*(.+?)\s*\}\}/g;
export function interpolateParams(
value: string | number, params: {[name: string]: any}, errors: any[]): string|number {
const original = value.toString();
const str = original.replace(PARAM_REGEX, (_, varName) => {
let localVal = params[varName];
// this means that the value was never overidden by the data passed in by the user
if (!params.hasOwnProperty(varName)) {
errors.push(`Please provide a value for the animation param ${varName}`);
localVal = '';
}
return localVal.toString();
});
// we do this to assert that numeric values stay as they are
return str == original ? value : str;
}
export function iteratorToArray(iterator: any): any[] {
const arr: any[] = [];
let item = iterator.next();
while (!item.done) {
arr.push(item.value);
item = iterator.next();
}
return arr;
}
export function mergeAnimationOptions(
source: AnimationOptions, destination: AnimationOptions): AnimationOptions {
if (source.params) {
const p0 = source.params;
if (!destination.params) {
destination.params = {};
}
const p1 = destination.params;
Object.keys(p0).forEach(param => {
if (!p1.hasOwnProperty(param)) {
p1[param] = p0[param];
}
});
}
return destination;
}