fix(animations): always normalize style properties and values during compilation (#12755)
Closes #11582 Closes #12481 Closes #12755
This commit is contained in:
@ -6,11 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata} from '../compile_metadata';
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
import {ParseError} from '../parse_util';
|
||||
import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core';
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
|
||||
import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionExpression, AnimationStepAst, AnimationStylesAst, AnimationWithStepsAst} from './animation_ast';
|
||||
import {StylesCollection} from './styles_collection';
|
||||
@ -32,7 +35,10 @@ export class AnimationEntryParseResult {
|
||||
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AnimationParser {
|
||||
constructor(private _schema: ElementSchemaRegistry) {}
|
||||
|
||||
parseComponent(component: CompileDirectiveMetadata): AnimationEntryAst[] {
|
||||
const errors: string[] = [];
|
||||
const componentName = component.type.name;
|
||||
@ -73,7 +79,7 @@ export class AnimationParser {
|
||||
var stateDeclarationAsts: AnimationStateDeclarationAst[] = [];
|
||||
entry.definitions.forEach(def => {
|
||||
if (def instanceof CompileAnimationStateDeclarationMetadata) {
|
||||
_parseAnimationDeclarationStates(def, errors).forEach(ast => {
|
||||
_parseAnimationDeclarationStates(def, this._schema, errors).forEach(ast => {
|
||||
stateDeclarationAsts.push(ast);
|
||||
stateStyles[ast.stateName] = ast.styles;
|
||||
});
|
||||
@ -82,8 +88,8 @@ export class AnimationParser {
|
||||
}
|
||||
});
|
||||
|
||||
var stateTransitionAsts =
|
||||
transitions.map(transDef => _parseAnimationStateTransition(transDef, stateStyles, errors));
|
||||
var stateTransitionAsts = transitions.map(
|
||||
transDef => _parseAnimationStateTransition(transDef, stateStyles, this._schema, errors));
|
||||
|
||||
var ast = new AnimationEntryAst(entry.name, stateDeclarationAsts, stateTransitionAsts);
|
||||
return new AnimationEntryParseResult(ast, errors);
|
||||
@ -91,27 +97,17 @@ export class AnimationParser {
|
||||
}
|
||||
|
||||
function _parseAnimationDeclarationStates(
|
||||
stateMetadata: CompileAnimationStateDeclarationMetadata,
|
||||
stateMetadata: CompileAnimationStateDeclarationMetadata, schema: ElementSchemaRegistry,
|
||||
errors: AnimationParseError[]): AnimationStateDeclarationAst[] {
|
||||
var styleValues: Styles[] = [];
|
||||
stateMetadata.styles.styles.forEach(stylesEntry => {
|
||||
// TODO (matsko): change this when we get CSS class integration support
|
||||
if (typeof stylesEntry === 'object' && stylesEntry !== null) {
|
||||
styleValues.push(stylesEntry as Styles);
|
||||
} else {
|
||||
errors.push(new AnimationParseError(
|
||||
`State based animations cannot contain references to other states`));
|
||||
}
|
||||
});
|
||||
var defStyles = new AnimationStylesAst(styleValues);
|
||||
|
||||
var normalizedStyles = _normalizeStyleMetadata(stateMetadata.styles, {}, schema, errors, false);
|
||||
var defStyles = new AnimationStylesAst(normalizedStyles);
|
||||
var states = stateMetadata.stateNameExpr.split(/\s*,\s*/);
|
||||
return states.map(state => new AnimationStateDeclarationAst(state, defStyles));
|
||||
}
|
||||
|
||||
function _parseAnimationStateTransition(
|
||||
transitionStateMetadata: CompileAnimationStateTransitionMetadata,
|
||||
stateStyles: {[key: string]: AnimationStylesAst},
|
||||
stateStyles: {[key: string]: AnimationStylesAst}, schema: ElementSchemaRegistry,
|
||||
errors: AnimationParseError[]): AnimationStateTransitionAst {
|
||||
var styles = new StylesCollection();
|
||||
var transitionExprs: AnimationStateTransitionExpression[] = [];
|
||||
@ -119,7 +115,7 @@ function _parseAnimationStateTransition(
|
||||
transitionStates.forEach(
|
||||
expr => { transitionExprs.push(..._parseAnimationTransitionExpr(expr, errors)); });
|
||||
var entry = _normalizeAnimationEntry(transitionStateMetadata.steps);
|
||||
var animation = _normalizeStyleSteps(entry, stateStyles, errors);
|
||||
var animation = _normalizeStyleSteps(entry, stateStyles, schema, errors);
|
||||
var animationAst = _parseTransitionAnimation(animation, 0, styles, stateStyles, errors);
|
||||
if (errors.length == 0) {
|
||||
_fillAnimationAstStartingKeyframes(animationAst, styles, errors);
|
||||
@ -176,13 +172,31 @@ function _normalizeAnimationEntry(entry: CompileAnimationMetadata | CompileAnima
|
||||
|
||||
function _normalizeStyleMetadata(
|
||||
entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst},
|
||||
errors: AnimationParseError[]): {[key: string]: string | number}[] {
|
||||
schema: ElementSchemaRegistry, errors: AnimationParseError[],
|
||||
permitStateReferences: boolean): {[key: string]: string | number}[] {
|
||||
var normalizedStyles: {[key: string]: string | number}[] = [];
|
||||
entry.styles.forEach(styleEntry => {
|
||||
if (typeof styleEntry === 'string') {
|
||||
normalizedStyles.push(..._resolveStylesFromState(<string>styleEntry, stateStyles, errors));
|
||||
if (permitStateReferences) {
|
||||
normalizedStyles.push(..._resolveStylesFromState(<string>styleEntry, stateStyles, errors));
|
||||
} else {
|
||||
errors.push(new AnimationParseError(
|
||||
`State based animations cannot contain references to other states`));
|
||||
}
|
||||
} else {
|
||||
normalizedStyles.push(<{[key: string]: string | number}>styleEntry);
|
||||
var stylesObj = <Styles>styleEntry;
|
||||
var normalizedStylesObj: Styles = {};
|
||||
Object.keys(stylesObj).forEach(propName => {
|
||||
var normalizedProp = schema.normalizeAnimationStyleProperty(propName);
|
||||
var normalizedOutput =
|
||||
schema.normalizeAnimationStyleValue(normalizedProp, propName, stylesObj[propName]);
|
||||
var normalizationError = normalizedOutput['error'];
|
||||
if (normalizationError) {
|
||||
errors.push(new AnimationParseError(normalizationError));
|
||||
}
|
||||
normalizedStylesObj[normalizedProp] = normalizedOutput['value'];
|
||||
});
|
||||
normalizedStyles.push(normalizedStylesObj);
|
||||
}
|
||||
});
|
||||
return normalizedStyles;
|
||||
@ -190,8 +204,8 @@ function _normalizeStyleMetadata(
|
||||
|
||||
function _normalizeStyleSteps(
|
||||
entry: CompileAnimationMetadata, stateStyles: {[key: string]: AnimationStylesAst},
|
||||
errors: AnimationParseError[]): CompileAnimationMetadata {
|
||||
var steps = _normalizeStyleStepEntry(entry, stateStyles, errors);
|
||||
schema: ElementSchemaRegistry, errors: AnimationParseError[]): CompileAnimationMetadata {
|
||||
var steps = _normalizeStyleStepEntry(entry, stateStyles, schema, errors);
|
||||
return (entry instanceof CompileAnimationGroupMetadata) ?
|
||||
new CompileAnimationGroupMetadata(steps) :
|
||||
new CompileAnimationSequenceMetadata(steps);
|
||||
@ -213,7 +227,7 @@ function _mergeAnimationStyles(
|
||||
|
||||
function _normalizeStyleStepEntry(
|
||||
entry: CompileAnimationMetadata, stateStyles: {[key: string]: AnimationStylesAst},
|
||||
errors: AnimationParseError[]): CompileAnimationMetadata[] {
|
||||
schema: ElementSchemaRegistry, errors: AnimationParseError[]): CompileAnimationMetadata[] {
|
||||
var steps: CompileAnimationMetadata[];
|
||||
if (entry instanceof CompileAnimationWithStepsMetadata) {
|
||||
steps = entry.steps;
|
||||
@ -232,7 +246,8 @@ function _normalizeStyleStepEntry(
|
||||
if (!isPresent(combinedStyles)) {
|
||||
combinedStyles = [];
|
||||
}
|
||||
_normalizeStyleMetadata(<CompileAnimationStyleMetadata>step, stateStyles, errors)
|
||||
_normalizeStyleMetadata(
|
||||
<CompileAnimationStyleMetadata>step, stateStyles, schema, errors, true)
|
||||
.forEach(entry => { _mergeAnimationStyles(combinedStyles, entry); });
|
||||
} else {
|
||||
// it is important that we create a metadata entry of the combined styles
|
||||
@ -250,13 +265,14 @@ function _normalizeStyleStepEntry(
|
||||
var animateStyleValue = (<CompileAnimationAnimateMetadata>step).styles;
|
||||
if (animateStyleValue instanceof CompileAnimationStyleMetadata) {
|
||||
animateStyleValue.styles =
|
||||
_normalizeStyleMetadata(animateStyleValue, stateStyles, errors);
|
||||
_normalizeStyleMetadata(animateStyleValue, stateStyles, schema, errors, true);
|
||||
} else if (animateStyleValue instanceof CompileAnimationKeyframesSequenceMetadata) {
|
||||
animateStyleValue.steps.forEach(
|
||||
step => { step.styles = _normalizeStyleMetadata(step, stateStyles, errors); });
|
||||
animateStyleValue.steps.forEach(step => {
|
||||
step.styles = _normalizeStyleMetadata(step, stateStyles, schema, errors, true);
|
||||
});
|
||||
}
|
||||
} else if (step instanceof CompileAnimationWithStepsMetadata) {
|
||||
let innerSteps = _normalizeStyleStepEntry(step, stateStyles, errors);
|
||||
let innerSteps = _normalizeStyleStepEntry(step, stateStyles, schema, errors);
|
||||
step = step instanceof CompileAnimationGroupMetadata ?
|
||||
new CompileAnimationGroupMetadata(innerSteps) :
|
||||
new CompileAnimationSequenceMetadata(innerSteps);
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Injectable, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
|
||||
import {AnimationParser} from './animation/animation_parser';
|
||||
import {CompilerConfig} from './config';
|
||||
import {DirectiveNormalizer} from './directive_normalizer';
|
||||
import {DirectiveResolver} from './directive_resolver';
|
||||
@ -74,7 +75,8 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
|
||||
UrlResolver,
|
||||
DirectiveResolver,
|
||||
PipeResolver,
|
||||
NgModuleResolver
|
||||
NgModuleResolver,
|
||||
AnimationParser
|
||||
];
|
||||
|
||||
|
||||
|
@ -109,7 +109,6 @@ export function analyzeNgModules(
|
||||
}
|
||||
|
||||
export class OfflineCompiler {
|
||||
private _animationParser = new AnimationParser();
|
||||
private _animationCompiler = new AnimationCompiler();
|
||||
|
||||
constructor(
|
||||
@ -118,7 +117,8 @@ export class OfflineCompiler {
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _dirWrapperCompiler: DirectiveWrapperCompiler,
|
||||
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
|
||||
private _localeId: string, private _translationFormat: string) {}
|
||||
private _localeId: string, private _translationFormat: string,
|
||||
private _animationParser: AnimationParser) {}
|
||||
|
||||
clearCache() {
|
||||
this._directiveNormalizer.clearCache();
|
||||
|
@ -43,7 +43,6 @@ export class RuntimeCompiler implements Compiler {
|
||||
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
||||
private _compiledDirectiveWrapperCache = new Map<Type<any>, Type<any>>();
|
||||
private _compiledNgModuleCache = new Map<Type<any>, NgModuleFactory<any>>();
|
||||
private _animationParser = new AnimationParser();
|
||||
private _animationCompiler = new AnimationCompiler();
|
||||
|
||||
constructor(
|
||||
@ -52,7 +51,7 @@ export class RuntimeCompiler implements Compiler {
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _ngModuleCompiler: NgModuleCompiler,
|
||||
private _directiveWrapperCompiler: DirectiveWrapperCompiler,
|
||||
private _compilerConfig: CompilerConfig) {}
|
||||
private _compilerConfig: CompilerConfig, private _animationParser: AnimationParser) {}
|
||||
|
||||
get injector(): Injector { return this._injector; }
|
||||
|
||||
|
@ -6,7 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CUSTOM_ELEMENTS_SCHEMA, Injectable, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core';
|
||||
import {AUTO_STYLE, CUSTOM_ELEMENTS_SCHEMA, Injectable, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core';
|
||||
|
||||
import {dashCaseToCamelCase} from '../util';
|
||||
|
||||
import {SECURITY_SCHEMA} from './dom_security_schema';
|
||||
import {ElementSchemaRegistry} from './element_schema_registry';
|
||||
@ -373,4 +375,64 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry {
|
||||
}
|
||||
|
||||
allKnownElementNames(): string[] { return Object.keys(this._schema); }
|
||||
|
||||
normalizeAnimationStyleProperty(propName: string): string {
|
||||
return dashCaseToCamelCase(propName);
|
||||
}
|
||||
|
||||
normalizeAnimationStyleValue(camelCaseProp: string, userProvidedProp: string, val: string|number):
|
||||
{error: string, value: string} {
|
||||
var unit: string = '';
|
||||
var strVal = val.toString().trim();
|
||||
var errorMsg: string = null;
|
||||
|
||||
if (_isPixelDimensionStyle(camelCaseProp) && val !== 0 && val !== '0') {
|
||||
if (typeof val === 'number') {
|
||||
unit = 'px';
|
||||
} else {
|
||||
let valAndSuffixMatch = val.match(/^[+-]?[\d\.]+([a-z]*)$/);
|
||||
if (valAndSuffixMatch && valAndSuffixMatch[1].length == 0) {
|
||||
errorMsg = `Please provide a CSS unit value for ${userProvidedProp}:${val}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {error: errorMsg, value: strVal + unit};
|
||||
}
|
||||
}
|
||||
|
||||
function _isPixelDimensionStyle(prop: string): boolean {
|
||||
switch (prop) {
|
||||
case 'width':
|
||||
case 'height':
|
||||
case 'minWidth':
|
||||
case 'minHeight':
|
||||
case 'maxWidth':
|
||||
case 'maxHeight':
|
||||
case 'left':
|
||||
case 'top':
|
||||
case 'bottom':
|
||||
case 'right':
|
||||
case 'fontSize':
|
||||
case 'outlineWidth':
|
||||
case 'outlineOffset':
|
||||
case 'paddingTop':
|
||||
case 'paddingLeft':
|
||||
case 'paddingBottom':
|
||||
case 'paddingRight':
|
||||
case 'marginTop':
|
||||
case 'marginLeft':
|
||||
case 'marginBottom':
|
||||
case 'marginRight':
|
||||
case 'borderRadius':
|
||||
case 'borderWidth':
|
||||
case 'borderTopWidth':
|
||||
case 'borderLeftWidth':
|
||||
case 'borderRightWidth':
|
||||
case 'borderBottomWidth':
|
||||
case 'textIndent':
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -18,4 +18,8 @@ export abstract class ElementSchemaRegistry {
|
||||
abstract getDefaultComponentElementName(): string;
|
||||
abstract validateProperty(name: string): {error: boolean, msg?: string};
|
||||
abstract validateAttribute(name: string): {error: boolean, msg?: string};
|
||||
abstract normalizeAnimationStyleProperty(propName: string): string;
|
||||
abstract normalizeAnimationStyleValue(
|
||||
camelCaseProp: string, userProvidedProp: string,
|
||||
val: string|number): {error: string, value: string};
|
||||
}
|
||||
|
@ -11,11 +11,16 @@ import {isBlank, isPrimitive, isStrictStringMap} from './facade/lang';
|
||||
export const MODULE_SUFFIX = '';
|
||||
|
||||
const CAMEL_CASE_REGEXP = /([A-Z])/g;
|
||||
const DASH_CASE_REGEXP = /-+([a-z0-9])/g;
|
||||
|
||||
export function camelCaseToDashCase(input: string): string {
|
||||
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
|
||||
}
|
||||
|
||||
export function dashCaseToCamelCase(input: string): string {
|
||||
return input.replace(DASH_CASE_REGEXP, (...m: any[]) => m[1].toUpperCase());
|
||||
}
|
||||
|
||||
export function splitAtColon(input: string, defaultValues: string[]): string[] {
|
||||
return _splitAt(input, ':', defaultValues);
|
||||
}
|
||||
|
Reference in New Issue
Block a user