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;
}