|
7 | 7 | */
|
8 | 8 | import {Directionality} from '@angular/cdk/bidi';
|
9 | 9 | import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
|
10 |
| -import {Platform} from '@angular/cdk/platform'; |
11 | 10 | import {DOCUMENT, NgTemplateOutlet} from '@angular/common';
|
| 11 | +import {Platform, _getShadowRoot} from '@angular/cdk/platform'; |
12 | 12 | import {
|
13 | 13 | ANIMATION_MODULE_TYPE,
|
14 | 14 | AfterContentChecked,
|
@@ -315,6 +315,13 @@ export class MatFormField
|
315 | 315 | private _explicitFormFieldControl: MatFormFieldControl<any>;
|
316 | 316 | private _needsOutlineLabelOffsetUpdate = false;
|
317 | 317 |
|
| 318 | + /** |
| 319 | + * Cached shadow root that the element is placed in. `null` means that the element isn't in |
| 320 | + * the shadow DOM and `undefined` means that it hasn't been resolved yet. Should be read via |
| 321 | + * `_getShadowRoot`, not directly. |
| 322 | + */ |
| 323 | + private _cachedShadowRoot: ShadowRoot | null | undefined; |
| 324 | + |
318 | 325 | private _injector = inject(Injector);
|
319 | 326 |
|
320 | 327 | constructor(
|
@@ -711,14 +718,29 @@ export class MatFormField
|
711 | 718 | /** Checks whether the form field is attached to the DOM. */
|
712 | 719 | private _isAttachedToDom(): boolean {
|
713 | 720 | const element: HTMLElement = this._elementRef.nativeElement;
|
714 |
| - if (element.getRootNode) { |
715 |
| - const rootNode = element.getRootNode(); |
716 |
| - // If the element is inside the DOM the root node will be either the document |
717 |
| - // or the closest shadow root, otherwise it'll be the element itself. |
718 |
| - return rootNode && rootNode !== element; |
| 721 | + const rootNode = element.getRootNode(); |
| 722 | + // If the element is inside the DOM the root node will be either the document, |
| 723 | + // the closest shadow root or an element that is not yet rendered, otherwise it'll be the element itself. |
| 724 | + return ( |
| 725 | + rootNode && |
| 726 | + rootNode !== element && |
| 727 | + // If the rootNode is the document we need to make sure that the element is visible |
| 728 | + ((rootNode === document && element.offsetParent !== null) || |
| 729 | + rootNode === this._getShadowRoot()) |
| 730 | + ); |
| 731 | + } |
| 732 | + |
| 733 | + /** |
| 734 | + * Lazily resolves and returns the shadow root of the element. We do this in a function, rather |
| 735 | + * than saving it in property directly on init, because we want to resolve it as late as possible |
| 736 | + * in order to ensure that the element has been moved into the shadow DOM. Doing it inside the |
| 737 | + * constructor might be too early if the element is inside of something like `ngFor` or `ngIf`. |
| 738 | + */ |
| 739 | + private _getShadowRoot(): ShadowRoot | null { |
| 740 | + if (this._cachedShadowRoot === undefined) { |
| 741 | + this._cachedShadowRoot = _getShadowRoot(this._elementRef.nativeElement); |
719 | 742 | }
|
720 |
| - // Otherwise fall back to checking if it's in the document. This doesn't account for |
721 |
| - // shadow DOM, however browser that support shadow DOM should support `getRootNode` as well. |
722 |
| - return document.documentElement!.contains(element); |
| 743 | + |
| 744 | + return this._cachedShadowRoot; |
723 | 745 | }
|
724 | 746 | }
|
0 commit comments