Skip to content

Commit 1b94295

Browse files
crisbetojelbourn
authored andcommitted
fix(form-field): outline gap no calculated in shadow dom (#17303)
Currently we check whether the form field is inside the DOM using `documentElement.contains(formField)` so that we don't try to calculate the outline gap. The problem is that `contains` won't catch elements inside the shadow DOM. These changes rework the check to use `getRootNode` instead. Fixes #17262.
1 parent 9fe32c0 commit 1b94295

File tree

2 files changed

+52
-2
lines changed

2 files changed

+52
-2
lines changed

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ export class MatFormField extends _MatFormFieldMixinBase
529529
}
530530
// If the element is not present in the DOM, the outline gap will need to be calculated
531531
// the next time it is checked and in the DOM.
532-
if (!document.documentElement!.contains(this._elementRef.nativeElement)) {
532+
if (!this._isAttachedToDOM()) {
533533
this._outlineGapCalculationNeededImmediately = true;
534534
return;
535535
}
@@ -582,4 +582,20 @@ export class MatFormField extends _MatFormFieldMixinBase
582582
private _getStartEnd(rect: ClientRect): number {
583583
return this._previousDirection === 'rtl' ? rect.right : rect.left;
584584
}
585+
586+
/** Checks whether the form field is attached to the DOM. */
587+
private _isAttachedToDOM(): boolean {
588+
const element: HTMLElement = this._elementRef.nativeElement;
589+
590+
if (element.getRootNode) {
591+
const rootNode = element.getRootNode();
592+
// If the element is inside the DOM the root node will be either the document
593+
// or the closest shadow root, otherwise it'll be the element itself.
594+
return rootNode && rootNode !== element;
595+
}
596+
597+
// Otherwise fall back to checking if it's in the document. This doesn't account for
598+
// shadow DOM, however browser that support shadow DOM should support `getRootNode` as well.
599+
return document.documentElement!.contains(element);
600+
}
585601
}

src/material/input/input.spec.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Platform, PlatformModule} from '@angular/cdk/platform';
1+
import {Platform, PlatformModule, _supportsShadowDom} from '@angular/cdk/platform';
22
import {wrappedErrorMessage, MockNgZone} from '@angular/cdk/private/testing';
33
import {
44
createFakeEvent,
@@ -12,6 +12,8 @@ import {
1212
Provider,
1313
NgZone,
1414
Directive,
15+
ViewEncapsulation,
16+
ElementRef,
1517
} from '@angular/core';
1618
import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing';
1719
import {
@@ -1503,7 +1505,26 @@ describe('MatInput with appearance', () => {
15031505
}));
15041506

15051507

1508+
it('should calculate the outline gaps inside the shadow DOM', fakeAsync(() => {
1509+
if (!_supportsShadowDom()) {
1510+
return;
1511+
}
15061512

1513+
fixture.destroy();
1514+
TestBed.resetTestingModule();
1515+
1516+
const outlineFixture = createComponent(MatInputWithOutlineAppearanceInShadowDOM);
1517+
outlineFixture.detectChanges();
1518+
flush();
1519+
outlineFixture.detectChanges();
1520+
1521+
const formField = outlineFixture.componentInstance.formField.nativeElement;
1522+
const outlineStart = formField.querySelector('.mat-form-field-outline-start') as HTMLElement;
1523+
const outlineGap = formField.querySelector('.mat-form-field-outline-gap') as HTMLElement;
1524+
1525+
expect(parseInt(outlineStart.style.width || '0')).toBeGreaterThan(0);
1526+
expect(parseInt(outlineGap.style.width || '0')).toBeGreaterThan(0);
1527+
}));
15071528

15081529
});
15091530

@@ -2010,6 +2031,19 @@ class MatInputWithoutPlaceholder {
20102031
})
20112032
class MatInputWithOutlineInsideInvisibleElement {}
20122033

2034+
@Component({
2035+
template: `
2036+
<mat-form-field appearance="outline" #formField>
2037+
<mat-label>Hello</mat-label>
2038+
<input matInput>
2039+
</mat-form-field>
2040+
`,
2041+
encapsulation: ViewEncapsulation.ShadowDom
2042+
})
2043+
class MatInputWithOutlineAppearanceInShadowDOM {
2044+
@ViewChild('formField', {read: ElementRef, static: false}) formField: ElementRef<HTMLElement>;
2045+
}
2046+
20132047

20142048
// Styles to reset padding and border to make measurement comparisons easier.
20152049
const textareaStyleReset = `

0 commit comments

Comments
 (0)