diff --git a/projects/element-ng/shadow-root/si-shadow-root.directive.spec.ts b/projects/element-ng/shadow-root/si-shadow-root.directive.spec.ts index 96435b41fa..be1e227c16 100644 --- a/projects/element-ng/shadow-root/si-shadow-root.directive.spec.ts +++ b/projects/element-ng/shadow-root/si-shadow-root.directive.spec.ts @@ -9,9 +9,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SiShadowRootDirective } from './si-shadow-root.directive'; describe('ShadowRootDirective', () => { + @Component({ + selector: 'si-late-styled', + template: `Late styled`, + styles: ` + .late-style { + color: #f00; + } + ` + }) + class LateStyledComponent {} + @Component({ selector: 'si-test', - imports: [CdkConnectedOverlay, CdkOverlayOrigin], + imports: [CdkConnectedOverlay, CdkOverlayOrigin, LateStyledComponent], template: ` { [cdkConnectedOverlayOrigin]="trigger" > Text + @if (showLateStyled()) { + + } `, styles: ` .test-style { @@ -30,13 +44,14 @@ describe('ShadowRootDirective', () => { }) class WithOverlayComponent { readonly open = input(false); + readonly showLateStyled = input(false); } @Component({ imports: [WithOverlayComponent], template: ` Text - + `, styles: ` .test-style { @@ -46,6 +61,7 @@ describe('ShadowRootDirective', () => { }) class TestHostComponent { readonly open = signal(false); + readonly showLateStyled = signal(false); } let fixture: ComponentFixture; @@ -66,4 +82,18 @@ describe('ShadowRootDirective', () => { ).color ).toBe('rgb(255, 255, 255)'); }); + + it('should copy styles registered after the overlay container was created', async () => { + fixture.componentInstance.open.set(true); + fixture.detectChanges(); + + fixture.componentInstance.showLateStyled.set(true); + fixture.detectChanges(); + await fixture.whenStable(); + + const overlayShadowRoot = document.querySelector('element-overlay-root')!.shadowRoot!; + expect(getComputedStyle(overlayShadowRoot.getElementById('late-styled')!).color).toBe( + 'rgb(255, 0, 0)' + ); + }); }); diff --git a/projects/element-ng/shadow-root/si-shadow-root.directive.ts b/projects/element-ng/shadow-root/si-shadow-root.directive.ts index 86029ced4a..e5adf2e657 100644 --- a/projects/element-ng/shadow-root/si-shadow-root.directive.ts +++ b/projects/element-ng/shadow-root/si-shadow-root.directive.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ import { Overlay, OverlayContainer } from '@angular/cdk/overlay'; -import { Directive, ElementRef, inject, DOCUMENT } from '@angular/core'; +import { Directive, ElementRef, inject, DOCUMENT, DestroyRef } from '@angular/core'; /** * This directive is intended to be used in applications that do NOT load element styles in the root HTML element. @@ -38,20 +38,46 @@ import { Directive, ElementRef, inject, DOCUMENT } from '@angular/core'; export class SiShadowRootDirective extends OverlayContainer { private elementRef = inject>(ElementRef); private document = inject(DOCUMENT); + private destroyRef = inject(DestroyRef); // eslint-disable-next-line @typescript-eslint/naming-convention protected override _createContainer(): void { - const root = document.createElement('element-overlay-root'); + const sourceShadow = this.elementRef.nativeElement.shadowRoot!; + const root = this.document.createElement('element-overlay-root'); this.document.body.append(root); const shadow = root.attachShadow({ mode: 'open' }); - const shadowElement = document.createElement('div'); + const shadowElement = this.document.createElement('div'); shadowElement.classList.add('cdk-overlay-container'); - shadow.append( - ...Array.from(this.elementRef.nativeElement.shadowRoot!.styleSheets).map(styleSheet => - styleSheet.ownerNode!.cloneNode(true) - ), - shadowElement - ); + shadow.append(shadowElement); + + const styleCopies = new Map(); + const syncStyleElements = (): void => { + const sourceStyleSheets = new Set(sourceShadow.styleSheets); + + styleCopies.forEach((copy, source) => { + if (!sourceStyleSheets.has(source)) { + copy.parentNode?.removeChild(copy); + styleCopies.delete(source); + } + }); + + sourceStyleSheets.forEach(source => { + let copy = styleCopies.get(source); + if (!copy) { + copy = source.ownerNode!.cloneNode(true); + styleCopies.set(source, copy); + } + shadow.insertBefore(copy, shadowElement); + }); + }; + + syncStyleElements(); + const observer = new MutationObserver(syncStyleElements); + observer.observe(sourceShadow, { childList: true }); + this.destroyRef.onDestroy(() => { + observer.disconnect(); + root.remove(); + }); this._containerElement = shadowElement; }