All files / shadow-root si-shadow-root.directive.ts

100% Statements 12/12
100% Branches 0/0
100% Functions 2/2
100% Lines 11/11

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59                                                                          1x 1x 1x       1x 1x 1x 1x 1x 1x   3x         1x      
/**
 * Copyright (c) Siemens 2016 - 2025
 * SPDX-License-Identifier: MIT
 */
import { Overlay, OverlayContainer } from '@angular/cdk/overlay';
import { Directive, ElementRef, inject, DOCUMENT } from '@angular/core';
 
/**
 * This directive is intended to be used in applications that do NOT load element styles in the root HTML element.
 * So typically module federation, single SPA apps or apps that bootstrap element manually in a shadow root.
 *
 * This directive will ensure that overlays created within this shadow root will have the correct styles applied.
 * It does this by creating a new shadow root in the document body
 * and copying the styles from this shadow root to new one.
 *
 * With that approach, we can ensure that overlay can span the entire screen without being limited to the current shadow root.
 *
 * To use this directive, add it to the component / element which creates the shadow root which holds the element styles.
 *
 * @example
 * ```ts
 * @Component({
 *   selector: 'app-root',
 *   encapsulation: ViewEncapsulation.ShadowDom,
 *   hostDirectives: [SiShadowRootDirective],
 *   stylesUrls: ['element-styles.scss'],
 *   template: `<si-element-component />`
 * })
 * export class AppComponent { }
 *
 * ```
 *
 */
@Directive({
  selector: '[siShadowRoot]',
  providers: [{ provide: OverlayContainer, useExisting: SiShadowRootDirective }, Overlay]
})
export class SiShadowRootDirective extends OverlayContainer {
  private elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
  private document = inject(DOCUMENT);
 
  // eslint-disable-next-line @typescript-eslint/naming-convention
  protected override _createContainer(): void {
    const root = document.createElement('element-overlay-root');
    this.document.body.append(root);
    const shadow = root.attachShadow({ mode: 'open' });
    const shadowElement = 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
    );
 
    this._containerElement = shadowElement;
  }
}