diff --git a/src/material/form-field/_mdc-text-field-structure.scss b/src/material/form-field/_mdc-text-field-structure.scss index e84c27a5a683..b940f16342e0 100644 --- a/src/material/form-field/_mdc-text-field-structure.scss +++ b/src/material/form-field/_mdc-text-field-structure.scss @@ -193,6 +193,11 @@ $fallbacks: m3-form-field.get-tokens(); display: inline-block; position: relative; max-width: 100%; + vertical-align: top; + } + + .mdc-notched-outline:not(.mdc-notched-outline--notched) & { + margin-top: calc(-1 * var(--mat-mdc-form-field-outline-width, 1px)); } .mdc-text-field--outlined & { @@ -373,9 +378,11 @@ $fallbacks: m3-form-field.get-tokens(); border: none; border-top: 1px solid; border-bottom: 1px solid; + --mat-mdc-form-field-outline-width: 1px; .mdc-text-field--focused & { border-width: 2px; + --mat-mdc-form-field-outline-width: 2px; } // Moved out into variables because the selectors we inherited were too long. @@ -385,6 +392,8 @@ $fallbacks: m3-form-field.get-tokens(); #{$enabled-selector} & { border-color: token-utils.slot(form-field-outlined-outline-color, $fallbacks); border-width: token-utils.slot(form-field-outlined-outline-width, $fallbacks); + --mat-mdc-form-field-outline-width: #{token-utils.slot( + form-field-outlined-outline-width, $fallbacks)}; } #{$enabled-selector}#{$hover-selector} & { @@ -413,6 +422,8 @@ $fallbacks: m3-form-field.get-tokens(); #{$enabled-selector}.mdc-text-field--focused .mdc-notched-outline & { border-width: token-utils.slot(form-field-outlined-focus-outline-width, $fallbacks); + --mat-mdc-form-field-outline-width: #{token-utils.slot( + form-field-outlined-focus-outline-width, $fallbacks)}; } } diff --git a/src/material/input/input.spec.ts b/src/material/input/input.spec.ts index f7bf11ae5ab9..03c79a2321a6 100644 --- a/src/material/input/input.spec.ts +++ b/src/material/input/input.spec.ts @@ -1719,6 +1719,48 @@ describe('MatFormField without label', () => { }); }); +describe('MatFormField label alignment', () => { + let fixture: ComponentFixture; + let formField: HTMLElement; + let wrapper: HTMLElement; + let label: HTMLElement; + + /** Distance between the center of the resting label and the center of the form field. */ + function getLabelOffsetFromCenter(): number { + const wrapperRect = wrapper.getBoundingClientRect(); + const labelRect = label.getBoundingClientRect(); + return labelRect.top + labelRect.height / 2 - (wrapperRect.top + wrapperRect.height / 2); + } + + beforeEach(() => { + fixture = TestBed.createComponent(MatInputWithAppearance); + fixture.componentInstance.appearance = 'outline'; + fixture.detectChanges(); + + formField = fixture.nativeElement.querySelector('.mat-mdc-form-field'); + wrapper = fixture.nativeElement.querySelector('.mat-mdc-text-field-wrapper'); + label = fixture.nativeElement.querySelector('.mat-mdc-floating-label'); + }); + + it('should center the resting label irrespective of the outline width', () => { + expect(Math.abs(getLabelOffsetFromCenter())).toBeLessThan(1); + + formField.style.setProperty('--mat-form-field-outlined-outline-width', '8px'); + + // Sanity check that the token is actually being applied so that the assertion below + // can't pass because the form field styles aren't loaded. + const notch = fixture.nativeElement.querySelector('.mdc-notched-outline__notch'); + expect(getComputedStyle(notch).borderTopWidth).toBe('8px'); + expect(Math.abs(getLabelOffsetFromCenter())).toBeLessThan(1); + }); + + it('should center the resting label irrespective of the label font size', () => { + formField.style.setProperty('--mat-form-field-outlined-label-text-size', '10px'); + expect(getComputedStyle(label).fontSize).toBe('10px'); + expect(Math.abs(getLabelOffsetFromCenter())).toBeLessThan(1); + }); +}); + @Component({ template: `