Skip to content

Commit e3413ba

Browse files
devversionandrewseguin
authored andcommitted
fix(material-experimental/mdc-form-field): show required asterisk for disabled fields
We currently implement our own logic for the asterisk. We need to update our conditions for showing the asterisk to match with MDC. i.e. in MDC the required asterisk shows always, regardless of the disabled state. This is different to the standard form-field, but we want to match this with the MDC text-field. Additionally, this commit cleans up our custom asterisk code as we can now fully rely on MDC's floating label implementation for the asterisk styles. The floating label wasn't flexible enough in the past. Finally, this also simplifies the floating label directive for more optimized payload size. We don't need the getters/setters and MDC foundation/component proxy for basically toggling simple public API classes (or measuring the label width). Foundations and components generally contribute a lot to payload size due to their natural concept of providing default and fallback adapters. We'll be working on outlining these issues long-term, but generally there should be no reason in complicating this by using one of the these MDC components/foundations. Related to #19410
1 parent c7cadc3 commit e3413ba

File tree

10 files changed

+54
-71
lines changed

10 files changed

+54
-71
lines changed

src/dev-app/mdc-input/mdc-input-demo.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,10 @@ <h4>Textarea</h4>
264264
<mat-label>Disabled field</mat-label>
265265
<input matInput disabled value="Value">
266266
</mat-form-field>
267+
<mat-form-field>
268+
<mat-label>Label</mat-label>
269+
<input matInput disabled required>
270+
</mat-form-field>
267271
<mat-form-field>
268272
<mat-label>Required field</mat-label>
269273
<input matInput required>

src/material-experimental/mdc-form-field/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ ng_module(
2424
"//src/material/core",
2525
"//src/material/form-field",
2626
"@npm//@angular/forms",
27-
"@npm//@material/floating-label",
2827
"@npm//@material/line-ripple",
2928
"@npm//@material/textfield",
3029
"@npm//rxjs",

src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,6 @@
66
// styles to fit our needs. See individual comments for context on why
77
// certain MDC styles need to be modified.
88
@mixin _mat-mdc-text-field-structure-overrides() {
9-
// Always hide the asterisk displayed by MDC. This is necessary because MDC can only display
10-
// the asterisk if the label is directly preceded by the input. MDC does this because it
11-
// relies on CSS to figure out if the input/textarea is required. This does not apply for us
12-
// because it's not guaranteed that the form control is an input/textarea. The required state
13-
// is handled as part of the registered form-field control instance. The asterisk will be
14-
// rendered conditionally through the floating label.
15-
.mat-mdc-form-field .mdc-floating-label::after {
16-
display: none;
17-
}
18-
199
// Unset the border set by MDC. We move the border (which serves as the Material Design
2010
// text-field bottom line) into its own element. This is necessary because we want the
2111
// bottom-line to span across the whole form-field (including prefixes and suffixes). Also

src/material-experimental/mdc-form-field/directives/floating-label.ts

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,41 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Directive, ElementRef, Input, OnDestroy} from '@angular/core';
10-
import {MDCFloatingLabel} from '@material/floating-label';
9+
import {Directive, ElementRef, Input} from '@angular/core';
10+
import {ponyfill} from '@material/dom';
1111

1212
/**
13-
* Internal directive that creates an instance of the MDC floating label
14-
* component. Using a directive allows us to conditionally render a floating label
15-
* in the template without having to manually instantiate the `MDCFloatingLabel` component.
13+
* Internal directive that maintains a MDC floating label. This directive does not
14+
* use the `MDCFloatingLabelFoundation` class, as it is not worth the size cost of
15+
* including it just to measure the label width and toggle some classes.
1616
*
17-
* The component is responsible for setting up the floating label styles, and for providing
18-
* an @Input that can be used by the form-field to toggle floating state of the label.
17+
* The use of a directive allows us to conditionally render a floating label in the
18+
* template without having to manually manage instantiation and destruction of the
19+
* floating label component based on.
20+
*
21+
* The component is responsible for setting up the floating label styles, measuring label
22+
* width for the outline notch, and providing inputs that can be used to toggle the
23+
* label's floating or required state.
1924
*/
2025
@Directive({
2126
selector: 'label[matFormFieldFloatingLabel]',
2227
host: {
2328
'class': 'mdc-floating-label',
29+
'[class.mdc-floating-label--required]': 'required',
30+
'[class.mdc-floating-label--float-above]': 'floating',
2431
},
2532
})
26-
export class MatFormFieldFloatingLabel extends MDCFloatingLabel implements OnDestroy {
27-
@Input()
28-
get floating() { return this._floating; }
29-
set floating(shouldFloat: boolean) {
30-
if (shouldFloat !== this._floating) {
31-
this._floating = shouldFloat;
32-
this.float(shouldFloat);
33-
}
34-
}
35-
private _floating = false;
33+
export class MatFormFieldFloatingLabel {
34+
/** Whether the label is floating. */
35+
@Input() floating: boolean = false;
36+
/** Whether the label is required. */
37+
@Input() required: boolean = false;
3638

37-
constructor(private _elementRef: ElementRef) {
38-
super(_elementRef.nativeElement);
39-
}
39+
constructor(private _elementRef: ElementRef) {}
4040

41-
ngOnDestroy() {
42-
this.destroy();
41+
/** Gets the width of the label. Used for the outline notch. */
42+
getWidth(): number {
43+
return ponyfill.estimateScrollWidth(this._elementRef.nativeElement);
4344
}
4445

4546
/** Gets the HTML element for the floating label. */

src/material-experimental/mdc-form-field/form-field.html

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,16 @@
1414
*Note*: We add aria-owns as a workaround for an issue in JAWS & NVDA where the label isn't
1515
read if it comes before the control in the DOM.
1616
-->
17-
<label matFormFieldFloatingLabel [floating]="_shouldLabelFloat()"
17+
<label matFormFieldFloatingLabel
18+
[floating]="_shouldLabelFloat()"
19+
[required]="!hideRequiredMarker && _control.required"
1820
*ngIf="_hasFloatingLabel()"
1921
(cdkObserveContent)="_refreshOutlineNotchWidth()"
2022
[cdkObserveContentDisabled]="!_hasOutline()"
2123
[id]="_labelId"
2224
[attr.for]="_control.id"
2325
[attr.aria-owns]="_control.id">
2426
<ng-content select="mat-label"></ng-content>
25-
26-
<!-- Manually handle the required asterisk. This is necessary because MDC can only
27-
display the asterisk if the label is directly preceded by the input. This cannot
28-
be guaranteed here since the form control is not necessarily an input, or is wrapped.
29-
-->
30-
<span class="mat-mdc-form-field-required-marker" aria-hidden="true"
31-
*ngIf="!hideRequiredMarker && _control.required && !_control.disabled">&#32;*</span>
3227
</label>
3328
</ng-template>
3429

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,10 @@ export class MatFormField implements AfterViewInit, OnDestroy, AfterContentCheck
244244
// want to update the notch whenever the `_shouldLabelFloat()` value changes.
245245
getLabelWidth: () => 0,
246246

247-
// TODO: MDC now supports setting the required asterisk marker directly on
248-
// the label component. This adapter method may be implemented and
249-
// mat-mdc-form-field-required-marker removed.
247+
// We don't use `setLabelRequired` as it relies on a mutation observer for determining
248+
// when the `required` state changes. This is not reliable and flexible enough for
249+
// our form field, as we support custom controls and detect the required state through
250+
// a public property in the abstract form control.
250251
setLabelRequired: () => {},
251252
notchOutline: () => {},
252253
closeOutline: () => {},

src/material-experimental/mdc-form-field/testing/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ ng_web_test_suite(
5050
"@npm//:node_modules/@material/textfield/dist/mdc.textfield.js",
5151
"@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js",
5252
"@npm//:node_modules/@material/notched-outline/dist/mdc.notchedOutline.js",
53-
"@npm//:node_modules/@material/floating-label/dist/mdc.floatingLabel.js",
53+
"@npm//:node_modules/@material/dom/dist/mdc.dom.js",
5454
],
5555
deps = [
5656
":unit_tests_lib",

src/material-experimental/mdc-input/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ ng_test_library(
6060
ng_web_test_suite(
6161
name = "unit_tests",
6262
static_files = [
63+
"@npm//:node_modules/@material/dom/dist/mdc.dom.js",
6364
"@npm//:node_modules/@material/textfield/dist/mdc.textfield.js",
6465
"@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js",
6566
"@npm//:node_modules/@material/notched-outline/dist/mdc.notchedOutline.js",
66-
"@npm//:node_modules/@material/floating-label/dist/mdc.floatingLabel.js",
6767
],
6868
deps = [
6969
":input_tests_lib",

src/material-experimental/mdc-input/input.spec.ts

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -315,48 +315,41 @@ describe('MatMdcInput without forms', () => {
315315
}));
316316

317317
it('supports label required star', fakeAsync(() => {
318-
let fixture = createComponent(MatInputLabelRequiredTestComponent);
318+
const fixture = createComponent(MatInputLabelRequiredTestComponent);
319319
fixture.detectChanges();
320320

321-
let el = fixture.debugElement.query(By.css('label'))!;
322-
expect(el).not.toBeNull();
323-
expect(el.nativeElement.textContent).toBe('hello *');
321+
const label = fixture.debugElement.query(By.css('label'))!;
322+
expect(label).not.toBeNull();
323+
expect(label.nativeElement.textContent).toBe('hello');
324+
expect(label.nativeElement.classList).toContain('mdc-floating-label--required');
324325
}));
325326

326-
it('should hide the required star if input is disabled', () => {
327+
it('should not hide the required star if input is disabled', () => {
327328
const fixture = createComponent(MatInputLabelRequiredTestComponent);
328329

329330
fixture.componentInstance.disabled = true;
330331
fixture.detectChanges();
331332

332-
const el = fixture.debugElement.query(By.css('label'))!;
333-
334-
expect(el).not.toBeNull();
335-
expect(el.nativeElement.textContent).toBe('hello');
333+
const label = fixture.debugElement.query(By.css('label'))!;
334+
expect(label).not.toBeNull();
335+
expect(label.nativeElement.textContent).toBe('hello');
336+
expect(label.nativeElement.classList).toContain('mdc-floating-label--required');
336337
});
337338

338-
it('should hide the required star from screen readers', fakeAsync(() => {
339-
let fixture = createComponent(MatInputLabelRequiredTestComponent);
340-
fixture.detectChanges();
341-
342-
let el = fixture.debugElement
343-
.query(By.css('.mat-mdc-form-field-required-marker'))!.nativeElement;
344-
345-
expect(el.getAttribute('aria-hidden')).toBe('true');
346-
}));
347-
348339
it('hide label required star when set to hide the required marker', fakeAsync(() => {
349-
let fixture = createComponent(MatInputLabelRequiredTestComponent);
340+
const fixture = createComponent(MatInputLabelRequiredTestComponent);
350341
fixture.detectChanges();
351342

352-
let el = fixture.debugElement.query(By.css('label'))!;
353-
expect(el).not.toBeNull();
354-
expect(el.nativeElement.textContent).toBe('hello *');
343+
const label = fixture.debugElement.query(By.css('label'))!;
344+
expect(label).not.toBeNull();
345+
expect(label.nativeElement.classList).toContain('mdc-floating-label--required');
346+
expect(label.nativeElement.textContent).toBe('hello');
355347

356348
fixture.componentInstance.hideRequiredMarker = true;
357349
fixture.detectChanges();
358350

359-
expect(el.nativeElement.textContent).toBe('hello');
351+
expect(label.nativeElement.classList).not.toContain('mdc-floating-label--required');
352+
expect(label.nativeElement.textContent).toBe('hello');
360353
}));
361354

362355
it('supports the disabled attribute as binding', fakeAsync(() => {

src/material-experimental/mdc-input/testing/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ ng_web_test_suite(
3939
"@npm//:node_modules/@material/textfield/dist/mdc.textfield.js",
4040
"@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js",
4141
"@npm//:node_modules/@material/notched-outline/dist/mdc.notchedOutline.js",
42-
"@npm//:node_modules/@material/floating-label/dist/mdc.floatingLabel.js",
42+
"@npm//:node_modules/@material/dom/dist/mdc.dom.js",
4343
],
4444
deps = [
4545
":unit_tests_lib",

0 commit comments

Comments
 (0)