Skip to content

Commit da9ff25

Browse files
matskoIgorMinar
authored andcommitted
fix(animations): properly clean up queried element styles in safari/edge (#23633)
Prior to this patch, if an element is queried and animated for 0 seconds (just a style() call and nothing else) then the styles applied would not be properly cleaned up due to their camelCased nature. PR Close #23633
1 parent 2cf6244 commit da9ff25

File tree

4 files changed

+74
-13
lines changed

4 files changed

+74
-13
lines changed

packages/animations/browser/src/render/css_keyframes/css_keyframes_driver.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {AnimationPlayer, ɵStyleData} from '@angular/animations';
99

1010
import {allowPreviousPlayerStylesMerge, balancePreviousStylesIntoKeyframes, computeStyle} from '../../util';
1111
import {AnimationDriver} from '../animation_driver';
12-
import {containsElement, invokeQuery, matchesElement, validateStyleProperty} from '../shared';
12+
import {containsElement, hypenatePropsObject, invokeQuery, matchesElement, validateStyleProperty} from '../shared';
1313

1414
import {CssKeyframesPlayer} from './css_keyframes_player';
1515
import {DirectStylePlayer} from './direct_style_player';
@@ -137,15 +137,6 @@ function flattenKeyframesIntoStyles(
137137
return flatKeyframes;
138138
}
139139

140-
function hypenatePropsObject(object: {[key: string]: any}): {[key: string]: any} {
141-
const newObj: {[key: string]: any} = {};
142-
Object.keys(object).forEach(prop => {
143-
const newProp = prop.replace(/([a-z])([A-Z])/g, '$1-$2');
144-
newObj[newProp] = object[prop];
145-
});
146-
return newObj;
147-
}
148-
149140
function removeElement(node: any) {
150141
node.parentNode.removeChild(node);
151142
}

packages/animations/browser/src/render/css_keyframes/direct_style_player.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import {NoopAnimationPlayer} from '@angular/animations';
9+
import {hypenatePropsObject} from '../shared';
910

1011
export class DirectStylePlayer extends NoopAnimationPlayer {
1112
private _startingStyles: {[key: string]: any}|null = {};
1213
private __initialized = false;
14+
private _styles: {[key: string]: any};
1315

14-
constructor(public element: any, private _styles: {[key: string]: any}) { super(); }
16+
constructor(public element: any, styles: {[key: string]: any}) {
17+
super();
18+
this._styles = hypenatePropsObject(styles);
19+
}
1520

1621
init() {
1722
if (this.__initialized || !this._startingStyles) return;
@@ -25,7 +30,8 @@ export class DirectStylePlayer extends NoopAnimationPlayer {
2530
play() {
2631
if (!this._startingStyles) return;
2732
this.init();
28-
Object.keys(this._styles).forEach(prop => { this.element.style[prop] = this._styles[prop]; });
33+
Object.keys(this._styles)
34+
.forEach(prop => this.element.style.setProperty(prop, this._styles[prop]));
2935
super.play();
3036
}
3137

@@ -34,7 +40,7 @@ export class DirectStylePlayer extends NoopAnimationPlayer {
3440
Object.keys(this._startingStyles).forEach(prop => {
3541
const value = this._startingStyles ![prop];
3642
if (value) {
37-
this.element.style[prop] = value;
43+
this.element.style.setProperty(prop, value);
3844
} else {
3945
this.element.style.removeProperty(prop);
4046
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,12 @@ export function getBodyNode(): any|null {
203203
export const matchesElement = _matches;
204204
export const containsElement = _contains;
205205
export const invokeQuery = _query;
206+
207+
export function hypenatePropsObject(object: {[key: string]: any}): {[key: string]: any} {
208+
const newObj: {[key: string]: any} = {};
209+
Object.keys(object).forEach(prop => {
210+
const newProp = prop.replace(/([a-z])([A-Z])/g, '$1-$2');
211+
newObj[newProp] = object[prop];
212+
});
213+
return newObj;
214+
}

packages/core/test/animation/animations_with_css_keyframes_animations_integration_spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,61 @@ import {TestBed} from '../../testing';
248248
assertStyle(element, 'width', '200px');
249249
assertStyle(element, 'height', '50px');
250250
});
251+
252+
it('should clean up 0 second animation styles (queried styles) that contain camel casing when complete',
253+
() => {
254+
@Component({
255+
selector: 'ani-cmp',
256+
template: `
257+
<div #elm [@myAnimation]="myAnimationExp">
258+
<div class="foo"></div>
259+
<div class="bar"></div>
260+
</div>
261+
`,
262+
animations: [
263+
trigger(
264+
'myAnimation',
265+
[
266+
state('go', style({width: '200px'})),
267+
transition(
268+
'* => go',
269+
[
270+
query('.foo', [style({maxHeight: '0px'})]),
271+
query(
272+
'.bar',
273+
[
274+
style({width: '0px'}),
275+
animate('1s', style({width: '100px'})),
276+
]),
277+
]),
278+
]),
279+
]
280+
})
281+
class Cmp {
282+
@ViewChild('elm') public element: any;
283+
284+
public myAnimationExp = '';
285+
}
286+
287+
TestBed.configureTestingModule({declarations: [Cmp]});
288+
289+
const engine = TestBed.get(AnimationEngine);
290+
const fixture = TestBed.createComponent(Cmp);
291+
const cmp = fixture.componentInstance;
292+
293+
const elm = cmp.element.nativeElement;
294+
const foo = elm.querySelector('.foo') as HTMLElement;
295+
296+
cmp.myAnimationExp = 'go';
297+
fixture.detectChanges();
298+
299+
expect(foo.style.getPropertyValue('max-height')).toEqual('0px');
300+
301+
const player = engine.players.pop();
302+
player.finish();
303+
304+
expect(foo.style.getPropertyValue('max-height')).toBeFalsy();
305+
});
251306
});
252307
})();
253308

0 commit comments

Comments
 (0)