From 2a7bea7f92abc6e6e11ef7f2301d634603e810f1 Mon Sep 17 00:00:00 2001 From: jeripeierSBB Date: Tue, 15 Dec 2020 17:40:47 +0100 Subject: [PATCH] fix(animations): allow animations in shadow DOM enhanced AnimationDriver's containsElement method to not only use the native node contains-method but to walk up the DOM tree by also considering shadow elements. Fixing this allows animations to be played if they are placed inside a shadow DOM. Closes #25672 --- .../css_keyframes/css_keyframes_driver.ts | 18 ++++++++-- .../animations/browser/src/render/shared.ts | 13 ++++++- .../css_keyframes_driver_spec.ts | 36 +++++++++++++++++-- .../web_animations_driver_spec.ts | 18 ++++++++++ 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/packages/animations/browser/src/render/css_keyframes/css_keyframes_driver.ts b/packages/animations/browser/src/render/css_keyframes/css_keyframes_driver.ts index c4a6d20987614d..11b996e69ca5cc 100644 --- a/packages/animations/browser/src/render/css_keyframes/css_keyframes_driver.ts +++ b/packages/animations/browser/src/render/css_keyframes/css_keyframes_driver.ts @@ -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); @@ -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( @@ -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} = {}; diff --git a/packages/animations/browser/src/render/shared.ts b/packages/animations/browser/src/render/shared.ts index e2040f9aa15d2d..da0d10ce1dc456 100644 --- a/packages/animations/browser/src/render/shared.ts +++ b/packages/animations/browser/src/render/shared.ts @@ -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 = (() => { diff --git a/packages/animations/browser/test/render/css_keyframes/css_keyframes_driver_spec.ts b/packages/animations/browser/test/render/css_keyframes/css_keyframes_driver_spec.ts index c062712e8da238..f7e5a3e96e517d 100644 --- a/packages/animations/browser/test/render/css_keyframes/css_keyframes_driver_spec.ts +++ b/packages/animations/browser/test/render/css_keyframes/css_keyframes_driver_spec.ts @@ -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'; @@ -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