Skip to content

Commit

Permalink
fix(animations): allow animations in shadow DOM
Browse files Browse the repository at this point in the history
  • Loading branch information
jeripeierSBB committed Dec 16, 2020
1 parent 973bb40 commit 1586ce3
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 6 deletions.
Expand Up @@ -20,7 +20,6 @@ const TAB_SPACE = ' ';

export class CssKeyframesDriver implements AnimationDriver {
private _count = 0;
private readonly _head: any = document.querySelector('head');

validateStyleProperty(prop: string): boolean {
return validateStyleProperty(prop);
Expand Down Expand Up @@ -107,7 +106,8 @@ export class CssKeyframesDriver implements AnimationDriver {

const animationName = `${KEYFRAMES_NAME_PREFIX}${this._count++}`;
const kfElm = this.buildKeyframeElement(element, animationName, keyframes);
document.querySelector('head')!.appendChild(kfElm);
const nodeToAppendKfElm = findNodeToAppendKfElm(element);
nodeToAppendKfElm.appendChild(kfElm);

const specialStyles = packageNonAnimatableStyles(element, keyframes);
const player = new CssKeyframesPlayer(
Expand All @@ -118,6 +118,20 @@ export class CssKeyframesDriver implements AnimationDriver {
}
}

function findNodeToAppendKfElm(element: any): Node {
let current = element;
let shadowRoot = null;
while (current && current !== document.documentElement) {
if (current.shadowRoot) {
shadowRoot = current.shadowRoot;
break;
}
current = current.parentNode || current.host;
}

return shadowRoot || document.querySelector('head')!;
}

function flattenKeyframesIntoStyles(keyframes: null|{[key: string]: any}|
{[key: string]: any}[]): {[key: string]: any} {
let flatKeyframes: {[key: string]: any} = {};
Expand Down
13 changes: 12 additions & 1 deletion packages/animations/browser/src/render/shared.ts
Expand Up @@ -162,7 +162,18 @@ const _isNode = isNode();
if (_isNode || typeof Element !== 'undefined') {
// this is well supported in all browsers
_contains = (elm1: any, elm2: any) => {
return elm1.contains(elm2) as boolean;
if (!isBrowser()) {
return elm1.contains(elm2) as boolean;
}
// walk up DOM tree
let current = elm2;
while (current && current !== document.documentElement) {
if (current === elm1) {
return true;
}
current = current.parentNode || current.host; // consider host to support shadow DOM
}
return false;
};

_matches = (() => {
Expand Down
Expand Up @@ -5,7 +5,7 @@
* 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 {fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
import {fakeAsync, flushMicrotasks} from '@angular/core/testing';

import {CssKeyframesDriver} from '../../../src/render/css_keyframes/css_keyframes_driver';
import {CssKeyframesPlayer} from '../../../src/render/css_keyframes/css_keyframes_player';
Expand Down Expand Up @@ -104,7 +104,7 @@ describe('CssKeyframesDriver tests', () => {
expect(easing).toEqual('ease-out');
});

it('should animate until the `animationend` method is emitted, but stil retain the <style> method and the element animation details',
it('should animate until the `animationend` method is emitted, but still retain the <style> method and the element animation details',
fakeAsync(() => {
// IE11 cannot create an instanceof AnimationEvent
if (!supportsAnimationEventCreation()) return;
Expand Down Expand Up @@ -144,7 +144,7 @@ describe('CssKeyframesDriver tests', () => {
assertElementExistsInDom(matchingStyleElm, true);
}));

it('should animate until finish() is called, but stil retain the <style> method and the element animation details',
it('should animate until finish() is called, but still retain the <style> method and the element animation details',
fakeAsync(() => {
const elm = createElement();
const animator = new CssKeyframesDriver();
Expand Down Expand Up @@ -369,6 +369,30 @@ describe('CssKeyframesDriver tests', () => {
expect(k2).toEqual({width: '400px', height: '400px', offset: 0.5});
expect(k3).toEqual({width: '500px', height: '500px', offset: 1});
});

it('should append <style> in shadow DOM root element', fakeAsync(() => {
const hostElement = createElement();
const shadowRoot = hostElement.attachShadow({mode: 'open'});
const elementToAnimate = createElement();
shadowRoot.appendChild(elementToAnimate);
const animator = new CssKeyframesDriver();

assertExistingAnimationDuration(elementToAnimate, 0);
expect(shadowRoot.querySelector('style')).toBeFalsy();

const player = animator.animate(
elementToAnimate,
[
{width: '0px', offset: 0},
{width: '200px', offset: 1},
],
1234, 0, 'ease-out');

player.play();

assertExistingAnimationDuration(elementToAnimate, 1234);
assertElementExistsInDom(shadowRoot.querySelector('style'), true);
}));
});
});

Expand Down
Expand Up @@ -49,6 +49,18 @@ import {WebAnimationsPlayer} from '../../../src/render/web_animations/web_animat
expect(player instanceof WebAnimationsPlayer).toBeTruthy();
});
});

describe('when animation is inside a shadow DOM', () => {
it('should the driver determine that body contains element to animate', (() => {
const hostElement = createElement();
const shadowRoot = hostElement.attachShadow({mode: 'open'});
const elementToAnimate = createElement();
shadowRoot.appendChild(elementToAnimate);
document.body.appendChild(hostElement);
const animator = new WebAnimationsDriver();
expect(animator.containsElement(document.body, elementToAnimate)).toBeTrue();
}));
});
});
}

Expand Down

0 comments on commit 1586ce3

Please sign in to comment.