Skip to content

Commit 3cddc3d

Browse files
dario-piotrowiczalxhub
authored andcommitted
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
1 parent 0dfb4e8 commit 3cddc3d

7 files changed

+66
-16
lines changed

packages/animations/browser/src/dsl/animation_transition_factory.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {buildAnimationTimelines} from './animation_timeline_builder';
1616
import {TransitionMatcherFn} from './animation_transition_expr';
1717
import {AnimationTransitionInstruction, createTransitionInstruction} from './animation_transition_instruction';
1818
import {ElementInstructionMap} from './element_instruction_map';
19+
import {AnimationStyleNormalizer} from './style_normalization/animation_style_normalizer';
1920

2021
const EMPTY_OBJECT = {};
2122

@@ -99,7 +100,9 @@ function oneOrMoreTransitionsMatch(
99100
}
100101

101102
export class AnimationStateStyles {
102-
constructor(private styles: StyleAst, private defaultParams: {[key: string]: any}) {}
103+
constructor(
104+
private styles: StyleAst, private defaultParams: {[key: string]: any},
105+
private normalizer: AnimationStyleNormalizer) {}
103106

104107
buildStyles(params: {[key: string]: any}, errors: string[]): ɵStyleData {
105108
const finalStyles: ɵStyleData = {};
@@ -118,7 +121,9 @@ export class AnimationStateStyles {
118121
if (val.length > 1) {
119122
val = interpolateParams(val, combinedParams, errors);
120123
}
121-
finalStyles[prop] = val;
124+
const normalizedProp = this.normalizer.normalizePropertyName(prop, errors);
125+
val = this.normalizer.normalizeStyleValue(prop, normalizedProp, val, errors);
126+
finalStyles[normalizedProp] = val;
122127
});
123128
}
124129
});

packages/animations/browser/src/dsl/animation_trigger.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ import {copyStyles, interpolateParams} from '../util';
1111

1212
import {SequenceAst, StyleAst, TransitionAst, TriggerAst} from './animation_ast';
1313
import {AnimationStateStyles, AnimationTransitionFactory} from './animation_transition_factory';
14+
import {AnimationStyleNormalizer} from './style_normalization/animation_style_normalizer';
1415

1516

1617

1718
/**
1819
* @publicApi
1920
*/
20-
export function buildTrigger(name: string, ast: TriggerAst): AnimationTrigger {
21-
return new AnimationTrigger(name, ast);
21+
export function buildTrigger(
22+
name: string, ast: TriggerAst, normalizer: AnimationStyleNormalizer): AnimationTrigger {
23+
return new AnimationTrigger(name, ast, normalizer);
2224
}
2325

2426
/**
@@ -29,10 +31,11 @@ export class AnimationTrigger {
2931
public fallbackTransition: AnimationTransitionFactory;
3032
public states: {[stateName: string]: AnimationStateStyles} = {};
3133

32-
constructor(public name: string, public ast: TriggerAst) {
34+
constructor(
35+
public name: string, public ast: TriggerAst, private _normalizer: AnimationStyleNormalizer) {
3336
ast.states.forEach(ast => {
3437
const defaultParams = (ast.options && ast.options.params) || {};
35-
this.states[ast.name] = new AnimationStateStyles(ast.style, defaultParams);
38+
this.states[ast.name] = new AnimationStateStyles(ast.style, defaultParams, _normalizer);
3639
});
3740

3841
balanceProperties(this.states, 'true', '1');
@@ -42,7 +45,7 @@ export class AnimationTrigger {
4245
this.transitionFactories.push(new AnimationTransitionFactory(name, ast, this.states));
4346
});
4447

45-
this.fallbackTransition = createFallbackTransition(name, this.states);
48+
this.fallbackTransition = createFallbackTransition(name, this.states, this._normalizer);
4649
}
4750

4851
get containsQueries() {
@@ -62,8 +65,8 @@ export class AnimationTrigger {
6265
}
6366

6467
function createFallbackTransition(
65-
triggerName: string,
66-
states: {[stateName: string]: AnimationStateStyles}): AnimationTransitionFactory {
68+
triggerName: string, states: {[stateName: string]: AnimationStateStyles},
69+
normalizer: AnimationStyleNormalizer): AnimationTransitionFactory {
6770
const matchers = [(fromState: any, toState: any) => true];
6871
const animation: SequenceAst = {type: AnimationMetadataType.Sequence, steps: [], options: null};
6972
const transition: TransitionAst = {

packages/animations/browser/src/render/animation_engine_next.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ export class AnimationEngine {
2727

2828
constructor(
2929
private bodyNode: any, private _driver: AnimationDriver,
30-
normalizer: AnimationStyleNormalizer) {
31-
this._transitionEngine = new TransitionAnimationEngine(bodyNode, _driver, normalizer);
32-
this._timelineEngine = new TimelineAnimationEngine(bodyNode, _driver, normalizer);
30+
private _normalizer: AnimationStyleNormalizer) {
31+
this._transitionEngine = new TransitionAnimationEngine(bodyNode, _driver, _normalizer);
32+
this._timelineEngine = new TimelineAnimationEngine(bodyNode, _driver, _normalizer);
3333

3434
this._transitionEngine.onRemovalComplete = (element: any, context: any) =>
3535
this.onRemovalComplete(element, context);
@@ -48,7 +48,7 @@ export class AnimationEngine {
4848
throw new Error(`The animation trigger "${
4949
name}" has failed to build due to the following errors:\n - ${errors.join('\n - ')}`);
5050
}
51-
trigger = buildTrigger(name, ast);
51+
trigger = buildTrigger(name, ast, this._normalizer);
5252
this._triggerCache[cacheKey] = trigger;
5353
}
5454
this._transitionEngine.registerTrigger(namespaceId, name, trigger);

packages/animations/browser/test/dsl/animation_trigger_spec.ts

-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ import {makeTrigger} from '../shared';
7272
transition('off => on', animate(1000))
7373
]);
7474

75-
7675
expect(result.states['on'].buildStyles({}, [])).toEqual({width: 50});
7776
expect(result.states['off'].buildStyles({}, [])).toEqual({width: 50});
7877
});

packages/animations/browser/test/render/transition_animation_engine_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ function registerTrigger(
736736
const ast = buildAnimationAst(driver, metadata as AnimationMetadata, errors) as TriggerAst;
737737
if (errors.length) {
738738
}
739-
const trigger = buildTrigger(name, ast);
739+
const trigger = buildTrigger(name, ast, new NoopAnimationStyleNormalizer());
740740
engine.register(id, element);
741741
engine.registerTrigger(id, name, trigger);
742742
}

packages/animations/browser/test/shared.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {trigger} from '@angular/animations';
1111
import {TriggerAst} from '../src/dsl/animation_ast';
1212
import {buildAnimationAst} from '../src/dsl/animation_ast_builder';
1313
import {AnimationTrigger, buildTrigger} from '../src/dsl/animation_trigger';
14+
import {NoopAnimationStyleNormalizer} from '../src/dsl/style_normalization/animation_style_normalizer';
1415
import {MockAnimationDriver} from '../testing/src/mock_animation_driver';
1516

1617
export function makeTrigger(
@@ -24,5 +25,5 @@ export function makeTrigger(
2425
throw new Error(`Animation parsing for the ${name} trigger have failed:${LINE_START}${
2526
errors.join(LINE_START)}`);
2627
}
27-
return buildTrigger(name, triggerAst);
28+
return buildTrigger(name, triggerAst, new NoopAnimationStyleNormalizer());
2829
}

packages/core/test/animation/animations_with_web_animations_integration_spec.ts

+42
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,48 @@ describe('animation integration tests using web animations', function() {
512512
expect(elm.style.getPropertyValue('display')).toEqual('inline-block');
513513
expect(elm.style.getPropertyValue('position')).toEqual('fixed');
514514
});
515+
516+
it('should set normalized style property values on animation end', () => {
517+
@Component({
518+
selector: 'ani-cmp',
519+
template: `
520+
<div #elm [@myAnimation]="myAnimationExp" style="width: 100%; font-size: 2rem"></div>
521+
`,
522+
animations: [
523+
trigger(
524+
'myAnimation',
525+
[
526+
state('go', style({width: 300, 'font-size': 14})),
527+
transition('* => go', [animate('1s')])
528+
]),
529+
]
530+
})
531+
class Cmp {
532+
@ViewChild('elm', {static: true}) public element: any;
533+
534+
public myAnimationExp = '';
535+
}
536+
537+
TestBed.configureTestingModule({declarations: [Cmp]});
538+
539+
const engine = TestBed.inject(ɵAnimationEngine);
540+
const fixture = TestBed.createComponent(Cmp);
541+
const cmp = fixture.componentInstance;
542+
543+
const elm = cmp.element.nativeElement;
544+
expect(elm.style.getPropertyValue('width')).toEqual('100%');
545+
expect(elm.style.getPropertyValue('font-size')).toEqual('2rem');
546+
547+
cmp.myAnimationExp = 'go';
548+
fixture.detectChanges();
549+
550+
const player = engine.players.pop()!;
551+
player.finish();
552+
player.destroy();
553+
554+
expect(elm.style.getPropertyValue('width')).toEqual('300px');
555+
expect(elm.style.getPropertyValue('font-size')).toEqual('14px');
556+
});
515557
});
516558
})();
517559

0 commit comments

Comments
 (0)