diff --git a/src/material/form-field/form-field.ts b/src/material/form-field/form-field.ts
index 173023a9bea4..6cdb0432570e 100644
--- a/src/material/form-field/form-field.ts
+++ b/src/material/form-field/form-field.ts
@@ -529,7 +529,7 @@ export class MatFormField extends _MatFormFieldMixinBase
}
// If the element is not present in the DOM, the outline gap will need to be calculated
// the next time it is checked and in the DOM.
- if (!document.documentElement!.contains(this._elementRef.nativeElement)) {
+ if (!this._isAttachedToDOM()) {
this._outlineGapCalculationNeededImmediately = true;
return;
}
@@ -582,4 +582,20 @@ export class MatFormField extends _MatFormFieldMixinBase
private _getStartEnd(rect: ClientRect): number {
return this._previousDirection === 'rtl' ? rect.right : rect.left;
}
+
+ /** Checks whether the form field is attached to the DOM. */
+ private _isAttachedToDOM(): boolean {
+ const element: HTMLElement = this._elementRef.nativeElement;
+
+ if (element.getRootNode) {
+ const rootNode = element.getRootNode();
+ // If the element is inside the DOM the root node will be either the document
+ // or the closest shadow root, otherwise it'll be the element itself.
+ return rootNode && rootNode !== element;
+ }
+
+ // Otherwise fall back to checking if it's in the document. This doesn't account for
+ // shadow DOM, however browser that support shadow DOM should support `getRootNode` as well.
+ return document.documentElement!.contains(element);
+ }
}
diff --git a/src/material/input/input.spec.ts b/src/material/input/input.spec.ts
index 69a7d5948ed4..72772dc18253 100644
--- a/src/material/input/input.spec.ts
+++ b/src/material/input/input.spec.ts
@@ -1,4 +1,4 @@
-import {Platform, PlatformModule} from '@angular/cdk/platform';
+import {Platform, PlatformModule, _supportsShadowDom} from '@angular/cdk/platform';
import {wrappedErrorMessage, MockNgZone} from '@angular/cdk/private/testing';
import {
createFakeEvent,
@@ -12,6 +12,8 @@ import {
Provider,
NgZone,
Directive,
+ ViewEncapsulation,
+ ElementRef,
} from '@angular/core';
import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing';
import {
@@ -1503,7 +1505,26 @@ describe('MatInput with appearance', () => {
}));
+ it('should calculate the outline gaps inside the shadow DOM', fakeAsync(() => {
+ if (!_supportsShadowDom()) {
+ return;
+ }
+ fixture.destroy();
+ TestBed.resetTestingModule();
+
+ const outlineFixture = createComponent(MatInputWithOutlineAppearanceInShadowDOM);
+ outlineFixture.detectChanges();
+ flush();
+ outlineFixture.detectChanges();
+
+ const formField = outlineFixture.componentInstance.formField.nativeElement;
+ const outlineStart = formField.querySelector('.mat-form-field-outline-start') as HTMLElement;
+ const outlineGap = formField.querySelector('.mat-form-field-outline-gap') as HTMLElement;
+
+ expect(parseInt(outlineStart.style.width || '0')).toBeGreaterThan(0);
+ expect(parseInt(outlineGap.style.width || '0')).toBeGreaterThan(0);
+ }));
});
@@ -2010,6 +2031,19 @@ class MatInputWithoutPlaceholder {
})
class MatInputWithOutlineInsideInvisibleElement {}
+@Component({
+ template: `
+
+ Hello
+
+
+ `,
+ encapsulation: ViewEncapsulation.ShadowDom
+})
+class MatInputWithOutlineAppearanceInShadowDOM {
+ @ViewChild('formField', {read: ElementRef, static: false}) formField: ElementRef;
+}
+
// Styles to reset padding and border to make measurement comparisons easier.
const textareaStyleReset = `