diff --git a/src/dev-app/mdc-input/mdc-input-demo.html b/src/dev-app/mdc-input/mdc-input-demo.html index b5e8067ad19d..c51add4cfe34 100644 --- a/src/dev-app/mdc-input/mdc-input-demo.html +++ b/src/dev-app/mdc-input/mdc-input-demo.html @@ -264,6 +264,10 @@

Textarea

Disabled field + + Label + + Required field diff --git a/src/material-experimental/mdc-form-field/BUILD.bazel b/src/material-experimental/mdc-form-field/BUILD.bazel index d7264e2b63a3..9c0f48573354 100644 --- a/src/material-experimental/mdc-form-field/BUILD.bazel +++ b/src/material-experimental/mdc-form-field/BUILD.bazel @@ -24,7 +24,6 @@ ng_module( "//src/material/core", "//src/material/form-field", "@npm//@angular/forms", - "@npm//@material/floating-label", "@npm//@material/line-ripple", "@npm//@material/textfield", "@npm//rxjs", diff --git a/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss b/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss index efd0c5b31be7..d0ba77ce49bc 100644 --- a/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss +++ b/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss @@ -6,16 +6,6 @@ // styles to fit our needs. See individual comments for context on why // certain MDC styles need to be modified. @mixin _mat-mdc-text-field-structure-overrides() { - // Always hide the asterisk displayed by MDC. This is necessary because MDC can only display - // the asterisk if the label is directly preceded by the input. MDC does this because it - // relies on CSS to figure out if the input/textarea is required. This does not apply for us - // because it's not guaranteed that the form control is an input/textarea. The required state - // is handled as part of the registered form-field control instance. The asterisk will be - // rendered conditionally through the floating label. - .mat-mdc-form-field .mdc-floating-label::after { - display: none; - } - // Unset the border set by MDC. We move the border (which serves as the Material Design // text-field bottom line) into its own element. This is necessary because we want the // bottom-line to span across the whole form-field (including prefixes and suffixes). Also diff --git a/src/material-experimental/mdc-form-field/directives/floating-label.ts b/src/material-experimental/mdc-form-field/directives/floating-label.ts index 7468961c6603..e0932abb3d3b 100644 --- a/src/material-experimental/mdc-form-field/directives/floating-label.ts +++ b/src/material-experimental/mdc-form-field/directives/floating-label.ts @@ -6,40 +6,41 @@ * found in the LICENSE file at https://angular.io/license */ -import {Directive, ElementRef, Input, OnDestroy} from '@angular/core'; -import {MDCFloatingLabel} from '@material/floating-label'; +import {Directive, ElementRef, Input} from '@angular/core'; +import {ponyfill} from '@material/dom'; /** - * Internal directive that creates an instance of the MDC floating label - * component. Using a directive allows us to conditionally render a floating label - * in the template without having to manually instantiate the `MDCFloatingLabel` component. + * Internal directive that maintains a MDC floating label. This directive does not + * use the `MDCFloatingLabelFoundation` class, as it is not worth the size cost of + * including it just to measure the label width and toggle some classes. * - * The component is responsible for setting up the floating label styles, and for providing - * an @Input that can be used by the form-field to toggle floating state of the label. + * The use of a directive allows us to conditionally render a floating label in the + * template without having to manually manage instantiation and destruction of the + * floating label component based on. + * + * The component is responsible for setting up the floating label styles, measuring label + * width for the outline notch, and providing inputs that can be used to toggle the + * label's floating or required state. */ @Directive({ selector: 'label[matFormFieldFloatingLabel]', host: { 'class': 'mdc-floating-label', + '[class.mdc-floating-label--required]': 'required', + '[class.mdc-floating-label--float-above]': 'floating', }, }) -export class MatFormFieldFloatingLabel extends MDCFloatingLabel implements OnDestroy { - @Input() - get floating() { return this._floating; } - set floating(shouldFloat: boolean) { - if (shouldFloat !== this._floating) { - this._floating = shouldFloat; - this.float(shouldFloat); - } - } - private _floating = false; +export class MatFormFieldFloatingLabel { + /** Whether the label is floating. */ + @Input() floating: boolean = false; + /** Whether the label is required. */ + @Input() required: boolean = false; - constructor(private _elementRef: ElementRef) { - super(_elementRef.nativeElement); - } + constructor(private _elementRef: ElementRef) {} - ngOnDestroy() { - this.destroy(); + /** Gets the width of the label. Used for the outline notch. */ + getWidth(): number { + return ponyfill.estimateScrollWidth(this._elementRef.nativeElement); } /** Gets the HTML element for the floating label. */ diff --git a/src/material-experimental/mdc-form-field/form-field.html b/src/material-experimental/mdc-form-field/form-field.html index 595d94629e78..55d54f3c43b3 100644 --- a/src/material-experimental/mdc-form-field/form-field.html +++ b/src/material-experimental/mdc-form-field/form-field.html @@ -14,7 +14,9 @@ *Note*: We add aria-owns as a workaround for an issue in JAWS & NVDA where the label isn't read if it comes before the control in the DOM. --> - diff --git a/src/material-experimental/mdc-form-field/form-field.ts b/src/material-experimental/mdc-form-field/form-field.ts index b8c7eb7b6223..5f7e6546f28f 100644 --- a/src/material-experimental/mdc-form-field/form-field.ts +++ b/src/material-experimental/mdc-form-field/form-field.ts @@ -244,9 +244,10 @@ export class MatFormField implements AfterViewInit, OnDestroy, AfterContentCheck // want to update the notch whenever the `_shouldLabelFloat()` value changes. getLabelWidth: () => 0, - // TODO: MDC now supports setting the required asterisk marker directly on - // the label component. This adapter method may be implemented and - // mat-mdc-form-field-required-marker removed. + // We don't use `setLabelRequired` as it relies on a mutation observer for determining + // when the `required` state changes. This is not reliable and flexible enough for + // our form field, as we support custom controls and detect the required state through + // a public property in the abstract form control. setLabelRequired: () => {}, notchOutline: () => {}, closeOutline: () => {}, diff --git a/src/material-experimental/mdc-form-field/testing/BUILD.bazel b/src/material-experimental/mdc-form-field/testing/BUILD.bazel index 2a43cdd7a25c..50bea3cd6769 100644 --- a/src/material-experimental/mdc-form-field/testing/BUILD.bazel +++ b/src/material-experimental/mdc-form-field/testing/BUILD.bazel @@ -50,7 +50,7 @@ ng_web_test_suite( "@npm//:node_modules/@material/textfield/dist/mdc.textfield.js", "@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js", "@npm//:node_modules/@material/notched-outline/dist/mdc.notchedOutline.js", - "@npm//:node_modules/@material/floating-label/dist/mdc.floatingLabel.js", + "@npm//:node_modules/@material/dom/dist/mdc.dom.js", ], deps = [ ":unit_tests_lib", diff --git a/src/material-experimental/mdc-input/BUILD.bazel b/src/material-experimental/mdc-input/BUILD.bazel index 53c2c3c390a8..89bbacd425f8 100644 --- a/src/material-experimental/mdc-input/BUILD.bazel +++ b/src/material-experimental/mdc-input/BUILD.bazel @@ -60,10 +60,10 @@ ng_test_library( ng_web_test_suite( name = "unit_tests", static_files = [ + "@npm//:node_modules/@material/dom/dist/mdc.dom.js", "@npm//:node_modules/@material/textfield/dist/mdc.textfield.js", "@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js", "@npm//:node_modules/@material/notched-outline/dist/mdc.notchedOutline.js", - "@npm//:node_modules/@material/floating-label/dist/mdc.floatingLabel.js", ], deps = [ ":input_tests_lib", diff --git a/src/material-experimental/mdc-input/input.spec.ts b/src/material-experimental/mdc-input/input.spec.ts index d03328246cd0..178b71132b23 100644 --- a/src/material-experimental/mdc-input/input.spec.ts +++ b/src/material-experimental/mdc-input/input.spec.ts @@ -315,48 +315,41 @@ describe('MatMdcInput without forms', () => { })); it('supports label required star', fakeAsync(() => { - let fixture = createComponent(MatInputLabelRequiredTestComponent); + const fixture = createComponent(MatInputLabelRequiredTestComponent); fixture.detectChanges(); - let el = fixture.debugElement.query(By.css('label'))!; - expect(el).not.toBeNull(); - expect(el.nativeElement.textContent).toBe('hello *'); + const label = fixture.debugElement.query(By.css('label'))!; + expect(label).not.toBeNull(); + expect(label.nativeElement.textContent).toBe('hello'); + expect(label.nativeElement.classList).toContain('mdc-floating-label--required'); })); - it('should hide the required star if input is disabled', () => { + it('should not hide the required star if input is disabled', () => { const fixture = createComponent(MatInputLabelRequiredTestComponent); fixture.componentInstance.disabled = true; fixture.detectChanges(); - const el = fixture.debugElement.query(By.css('label'))!; - - expect(el).not.toBeNull(); - expect(el.nativeElement.textContent).toBe('hello'); + const label = fixture.debugElement.query(By.css('label'))!; + expect(label).not.toBeNull(); + expect(label.nativeElement.textContent).toBe('hello'); + expect(label.nativeElement.classList).toContain('mdc-floating-label--required'); }); - it('should hide the required star from screen readers', fakeAsync(() => { - let fixture = createComponent(MatInputLabelRequiredTestComponent); - fixture.detectChanges(); - - let el = fixture.debugElement - .query(By.css('.mat-mdc-form-field-required-marker'))!.nativeElement; - - expect(el.getAttribute('aria-hidden')).toBe('true'); - })); - it('hide label required star when set to hide the required marker', fakeAsync(() => { - let fixture = createComponent(MatInputLabelRequiredTestComponent); + const fixture = createComponent(MatInputLabelRequiredTestComponent); fixture.detectChanges(); - let el = fixture.debugElement.query(By.css('label'))!; - expect(el).not.toBeNull(); - expect(el.nativeElement.textContent).toBe('hello *'); + const label = fixture.debugElement.query(By.css('label'))!; + expect(label).not.toBeNull(); + expect(label.nativeElement.classList).toContain('mdc-floating-label--required'); + expect(label.nativeElement.textContent).toBe('hello'); fixture.componentInstance.hideRequiredMarker = true; fixture.detectChanges(); - expect(el.nativeElement.textContent).toBe('hello'); + expect(label.nativeElement.classList).not.toContain('mdc-floating-label--required'); + expect(label.nativeElement.textContent).toBe('hello'); })); it('supports the disabled attribute as binding', fakeAsync(() => { diff --git a/src/material-experimental/mdc-input/testing/BUILD.bazel b/src/material-experimental/mdc-input/testing/BUILD.bazel index ea5662001bb2..8f48869fe2d6 100644 --- a/src/material-experimental/mdc-input/testing/BUILD.bazel +++ b/src/material-experimental/mdc-input/testing/BUILD.bazel @@ -39,7 +39,7 @@ ng_web_test_suite( "@npm//:node_modules/@material/textfield/dist/mdc.textfield.js", "@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js", "@npm//:node_modules/@material/notched-outline/dist/mdc.notchedOutline.js", - "@npm//:node_modules/@material/floating-label/dist/mdc.floatingLabel.js", + "@npm//:node_modules/@material/dom/dist/mdc.dom.js", ], deps = [ ":unit_tests_lib",