Skip to content

Commit 6318f24

Browse files
fix(material/form-field): outline label position (angular#29138)
* fix(material/form-field): fix label position When the form field is inside an overlay with ng-content or inside a side-nav (mat-drawer) the prefix container would not be visible thus the label would overlap with the prefix. In this fix we narrow down possible cases that would require an additional check for the prefix container width. * perf(material/form-field): add caching for shadow root Add caching for shadow root so it doesn't have to be resolved each time
1 parent 6e8804d commit 6318f24

File tree

1 file changed

+31
-9
lines changed

1 file changed

+31
-9
lines changed

src/material/form-field/form-field.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
*/
88
import {Directionality} from '@angular/cdk/bidi';
99
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
10-
import {Platform} from '@angular/cdk/platform';
1110
import {DOCUMENT, NgTemplateOutlet} from '@angular/common';
11+
import {Platform, _getShadowRoot} from '@angular/cdk/platform';
1212
import {
1313
ANIMATION_MODULE_TYPE,
1414
AfterContentChecked,
@@ -315,6 +315,13 @@ export class MatFormField
315315
private _explicitFormFieldControl: MatFormFieldControl<any>;
316316
private _needsOutlineLabelOffsetUpdate = false;
317317

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+
318325
private _injector = inject(Injector);
319326

320327
constructor(
@@ -711,14 +718,29 @@ export class MatFormField
711718
/** Checks whether the form field is attached to the DOM. */
712719
private _isAttachedToDom(): boolean {
713720
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);
719742
}
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;
723745
}
724746
}

0 commit comments

Comments
 (0)