fix(animations): normalize final styles in buildStyles (#42763)
the final styles created in buildStyles lack normalization, meaning that pixel values remain as numbers (without "px") and so such properties fail to be correctly set/applied Example: "width: 300" is applies as "width": "300" (and thus ignored) instead of the correct "width": "300px" PR Close #42763
This commit is contained in:
parent
5d7359e3f0
commit
f12c53342c
|
@ -16,6 +16,7 @@ import {buildAnimationTimelines} from './animation_timeline_builder';
|
||||||
import {TransitionMatcherFn} from './animation_transition_expr';
|
import {TransitionMatcherFn} from './animation_transition_expr';
|
||||||
import {AnimationTransitionInstruction, createTransitionInstruction} from './animation_transition_instruction';
|
import {AnimationTransitionInstruction, createTransitionInstruction} from './animation_transition_instruction';
|
||||||
import {ElementInstructionMap} from './element_instruction_map';
|
import {ElementInstructionMap} from './element_instruction_map';
|
||||||
|
import {AnimationStyleNormalizer} from './style_normalization/animation_style_normalizer';
|
||||||
|
|
||||||
const EMPTY_OBJECT = {};
|
const EMPTY_OBJECT = {};
|
||||||
|
|
||||||
|
@ -99,7 +100,9 @@ function oneOrMoreTransitionsMatch(
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AnimationStateStyles {
|
export class AnimationStateStyles {
|
||||||
constructor(private styles: StyleAst, private defaultParams: {[key: string]: any}) {}
|
constructor(
|
||||||
|
private styles: StyleAst, private defaultParams: {[key: string]: any},
|
||||||
|
private normalizer: AnimationStyleNormalizer) {}
|
||||||
|
|
||||||
buildStyles(params: {[key: string]: any}, errors: string[]): ɵStyleData {
|
buildStyles(params: {[key: string]: any}, errors: string[]): ɵStyleData {
|
||||||
const finalStyles: ɵStyleData = {};
|
const finalStyles: ɵStyleData = {};
|
||||||
|
@ -118,7 +121,9 @@ export class AnimationStateStyles {
|
||||||
if (val.length > 1) {
|
if (val.length > 1) {
|
||||||
val = interpolateParams(val, combinedParams, errors);
|
val = interpolateParams(val, combinedParams, errors);
|
||||||
}
|
}
|
||||||
finalStyles[prop] = val;
|
const normalizedProp = this.normalizer.normalizePropertyName(prop, errors);
|
||||||
|
val = this.normalizer.normalizeStyleValue(prop, normalizedProp, val, errors);
|
||||||
|
finalStyles[normalizedProp] = val;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,14 +11,16 @@ import {copyStyles, interpolateParams} from '../util';
|
||||||
|
|
||||||
import {SequenceAst, StyleAst, TransitionAst, TriggerAst} from './animation_ast';
|
import {SequenceAst, StyleAst, TransitionAst, TriggerAst} from './animation_ast';
|
||||||
import {AnimationStateStyles, AnimationTransitionFactory} from './animation_transition_factory';
|
import {AnimationStateStyles, AnimationTransitionFactory} from './animation_transition_factory';
|
||||||
|
import {AnimationStyleNormalizer} from './style_normalization/animation_style_normalizer';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @publicApi
|
* @publicApi
|
||||||
*/
|
*/
|
||||||
export function buildTrigger(name: string, ast: TriggerAst): AnimationTrigger {
|
export function buildTrigger(
|
||||||
return new AnimationTrigger(name, ast);
|
name: string, ast: TriggerAst, normalizer: AnimationStyleNormalizer): AnimationTrigger {
|
||||||
|
return new AnimationTrigger(name, ast, normalizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,10 +31,11 @@ export class AnimationTrigger {
|
||||||
public fallbackTransition: AnimationTransitionFactory;
|
public fallbackTransition: AnimationTransitionFactory;
|
||||||
public states: {[stateName: string]: AnimationStateStyles} = {};
|
public states: {[stateName: string]: AnimationStateStyles} = {};
|
||||||
|
|
||||||
constructor(public name: string, public ast: TriggerAst) {
|
constructor(
|
||||||
|
public name: string, public ast: TriggerAst, private _normalizer: AnimationStyleNormalizer) {
|
||||||
ast.states.forEach(ast => {
|
ast.states.forEach(ast => {
|
||||||
const defaultParams = (ast.options && ast.options.params) || {};
|
const defaultParams = (ast.options && ast.options.params) || {};
|
||||||
this.states[ast.name] = new AnimationStateStyles(ast.style, defaultParams);
|
this.states[ast.name] = new AnimationStateStyles(ast.style, defaultParams, _normalizer);
|
||||||
});
|
});
|
||||||
|
|
||||||
balanceProperties(this.states, 'true', '1');
|
balanceProperties(this.states, 'true', '1');
|
||||||
|
@ -42,7 +45,7 @@ export class AnimationTrigger {
|
||||||
this.transitionFactories.push(new AnimationTransitionFactory(name, ast, this.states));
|
this.transitionFactories.push(new AnimationTransitionFactory(name, ast, this.states));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.fallbackTransition = createFallbackTransition(name, this.states);
|
this.fallbackTransition = createFallbackTransition(name, this.states, this._normalizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
get containsQueries() {
|
get containsQueries() {
|
||||||
|
@ -62,8 +65,8 @@ export class AnimationTrigger {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFallbackTransition(
|
function createFallbackTransition(
|
||||||
triggerName: string,
|
triggerName: string, states: {[stateName: string]: AnimationStateStyles},
|
||||||
states: {[stateName: string]: AnimationStateStyles}): AnimationTransitionFactory {
|
normalizer: AnimationStyleNormalizer): AnimationTransitionFactory {
|
||||||
const matchers = [(fromState: any, toState: any) => true];
|
const matchers = [(fromState: any, toState: any) => true];
|
||||||
const animation: SequenceAst = {type: AnimationMetadataType.Sequence, steps: [], options: null};
|
const animation: SequenceAst = {type: AnimationMetadataType.Sequence, steps: [], options: null};
|
||||||
const transition: TransitionAst = {
|
const transition: TransitionAst = {
|
||||||
|
|
|
@ -27,9 +27,9 @@ export class AnimationEngine {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private bodyNode: any, private _driver: AnimationDriver,
|
private bodyNode: any, private _driver: AnimationDriver,
|
||||||
normalizer: AnimationStyleNormalizer) {
|
private _normalizer: AnimationStyleNormalizer) {
|
||||||
this._transitionEngine = new TransitionAnimationEngine(bodyNode, _driver, normalizer);
|
this._transitionEngine = new TransitionAnimationEngine(bodyNode, _driver, _normalizer);
|
||||||
this._timelineEngine = new TimelineAnimationEngine(bodyNode, _driver, normalizer);
|
this._timelineEngine = new TimelineAnimationEngine(bodyNode, _driver, _normalizer);
|
||||||
|
|
||||||
this._transitionEngine.onRemovalComplete = (element: any, context: any) =>
|
this._transitionEngine.onRemovalComplete = (element: any, context: any) =>
|
||||||
this.onRemovalComplete(element, context);
|
this.onRemovalComplete(element, context);
|
||||||
|
@ -48,7 +48,7 @@ export class AnimationEngine {
|
||||||
throw new Error(`The animation trigger "${
|
throw new Error(`The animation trigger "${
|
||||||
name}" has failed to build due to the following errors:\n - ${errors.join('\n - ')}`);
|
name}" has failed to build due to the following errors:\n - ${errors.join('\n - ')}`);
|
||||||
}
|
}
|
||||||
trigger = buildTrigger(name, ast);
|
trigger = buildTrigger(name, ast, this._normalizer);
|
||||||
this._triggerCache[cacheKey] = trigger;
|
this._triggerCache[cacheKey] = trigger;
|
||||||
}
|
}
|
||||||
this._transitionEngine.registerTrigger(namespaceId, name, trigger);
|
this._transitionEngine.registerTrigger(namespaceId, name, trigger);
|
||||||
|
|
|
@ -72,7 +72,6 @@ import {makeTrigger} from '../shared';
|
||||||
transition('off => on', animate(1000))
|
transition('off => on', animate(1000))
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
expect(result.states['on'].buildStyles({}, [])).toEqual({width: 50});
|
expect(result.states['on'].buildStyles({}, [])).toEqual({width: 50});
|
||||||
expect(result.states['off'].buildStyles({}, [])).toEqual({width: 50});
|
expect(result.states['off'].buildStyles({}, [])).toEqual({width: 50});
|
||||||
});
|
});
|
||||||
|
|
|
@ -736,7 +736,7 @@ function registerTrigger(
|
||||||
const ast = buildAnimationAst(driver, metadata as AnimationMetadata, errors) as TriggerAst;
|
const ast = buildAnimationAst(driver, metadata as AnimationMetadata, errors) as TriggerAst;
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
}
|
}
|
||||||
const trigger = buildTrigger(name, ast);
|
const trigger = buildTrigger(name, ast, new NoopAnimationStyleNormalizer());
|
||||||
engine.register(id, element);
|
engine.register(id, element);
|
||||||
engine.registerTrigger(id, name, trigger);
|
engine.registerTrigger(id, name, trigger);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {trigger} from '@angular/animations';
|
||||||
import {TriggerAst} from '../src/dsl/animation_ast';
|
import {TriggerAst} from '../src/dsl/animation_ast';
|
||||||
import {buildAnimationAst} from '../src/dsl/animation_ast_builder';
|
import {buildAnimationAst} from '../src/dsl/animation_ast_builder';
|
||||||
import {AnimationTrigger, buildTrigger} from '../src/dsl/animation_trigger';
|
import {AnimationTrigger, buildTrigger} from '../src/dsl/animation_trigger';
|
||||||
|
import {NoopAnimationStyleNormalizer} from '../src/dsl/style_normalization/animation_style_normalizer';
|
||||||
import {MockAnimationDriver} from '../testing/src/mock_animation_driver';
|
import {MockAnimationDriver} from '../testing/src/mock_animation_driver';
|
||||||
|
|
||||||
export function makeTrigger(
|
export function makeTrigger(
|
||||||
|
@ -24,5 +25,5 @@ export function makeTrigger(
|
||||||
throw new Error(`Animation parsing for the ${name} trigger have failed:${LINE_START}${
|
throw new Error(`Animation parsing for the ${name} trigger have failed:${LINE_START}${
|
||||||
errors.join(LINE_START)}`);
|
errors.join(LINE_START)}`);
|
||||||
}
|
}
|
||||||
return buildTrigger(name, triggerAst);
|
return buildTrigger(name, triggerAst, new NoopAnimationStyleNormalizer());
|
||||||
}
|
}
|
||||||
|
|
|
@ -512,6 +512,48 @@ describe('animation integration tests using web animations', function() {
|
||||||
expect(elm.style.getPropertyValue('display')).toEqual('inline-block');
|
expect(elm.style.getPropertyValue('display')).toEqual('inline-block');
|
||||||
expect(elm.style.getPropertyValue('position')).toEqual('fixed');
|
expect(elm.style.getPropertyValue('position')).toEqual('fixed');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set normalized style property values on animation end', () => {
|
||||||
|
@Component({
|
||||||
|
selector: 'ani-cmp',
|
||||||
|
template: `
|
||||||
|
<div #elm [@myAnimation]="myAnimationExp" style="width: 100%; font-size: 2rem"></div>
|
||||||
|
`,
|
||||||
|
animations: [
|
||||||
|
trigger(
|
||||||
|
'myAnimation',
|
||||||
|
[
|
||||||
|
state('go', style({width: 300, 'font-size': 14})),
|
||||||
|
transition('* => go', [animate('1s')])
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
@ViewChild('elm', {static: true}) public element: any;
|
||||||
|
|
||||||
|
public myAnimationExp = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||||
|
|
||||||
|
const engine = TestBed.inject(ɵAnimationEngine);
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
|
||||||
|
const elm = cmp.element.nativeElement;
|
||||||
|
expect(elm.style.getPropertyValue('width')).toEqual('100%');
|
||||||
|
expect(elm.style.getPropertyValue('font-size')).toEqual('2rem');
|
||||||
|
|
||||||
|
cmp.myAnimationExp = 'go';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const player = engine.players.pop()!;
|
||||||
|
player.finish();
|
||||||
|
player.destroy();
|
||||||
|
|
||||||
|
expect(elm.style.getPropertyValue('width')).toEqual('300px');
|
||||||
|
expect(elm.style.getPropertyValue('font-size')).toEqual('14px');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue