Skip to content

Commit

Permalink
fix(material/input): preserve native placeholder on non-legacy appear…
Browse files Browse the repository at this point in the history
…ances

The `legacy` form field appearance has a feature where it promotes the input placeholder
to the form field label which introduces a problem where screen readers will read out
the placeholder twice. Some time ago we added logic to clear the placeholder, but
it seems to be a bit too aggressive since it also clears the placeholder for other
appearances.

These changes scope the workaround only to the case when a placeholder would be promoted to a label.

Fixes #20903.
  • Loading branch information
crisbeto committed Feb 28, 2022
1 parent 29ac6cf commit be89dcd
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 4 deletions.
14 changes: 13 additions & 1 deletion src/material-experimental/mdc-input/input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,17 @@ describe('MatMdcInput without forms', () => {
expect(formField.classList).not.toContain('mat-mdc-form-field-type-mat-native-select');
});

it('should preserve the native placeholder on a non-legacy appearance', fakeAsync(() => {
const fixture = createComponent(MatInputWithLabelAndPlaceholder);
fixture.componentInstance.floatLabel = 'auto';
fixture.componentInstance.appearance = 'outline';
fixture.detectChanges();

expect(fixture.nativeElement.querySelector('input').getAttribute('placeholder')).toBe(
'Placeholder',
);
}));

it(
'should use the native input value when determining whether ' +
'the element is empty with a custom accessor',
Expand Down Expand Up @@ -1861,14 +1872,15 @@ class MatInputWithLabel {}

@Component({
template: `
<mat-form-field [floatLabel]="floatLabel">
<mat-form-field [floatLabel]="floatLabel" [appearance]="appearance">
<mat-label>Label</mat-label>
<input matInput placeholder="Placeholder">
</mat-form-field>
`,
})
class MatInputWithLabelAndPlaceholder {
floatLabel: FloatLabelType;
appearance: MatFormFieldAppearance;
}

@Component({
Expand Down
15 changes: 13 additions & 2 deletions src/material/input/input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,6 @@ describe('MatInput without forms', () => {
expect(container.classList).toContain('mat-form-field-hide-placeholder');
expect(container.classList).not.toContain('mat-form-field-should-float');
expect(label.textContent.trim()).toBe('Label');
expect(input.hasAttribute('placeholder')).toBe(false);

input.value = 'Value';
fixture.detectChanges();
Expand Down Expand Up @@ -1020,6 +1019,17 @@ describe('MatInput without forms', () => {
expect(container.classList).not.toContain('mat-form-field-should-float');
});

it('should preserve the native placeholder on a non-legacy appearance', fakeAsync(() => {
const fixture = createComponent(MatInputWithLabelAndPlaceholder);
fixture.componentInstance.floatLabel = 'auto';
fixture.componentInstance.appearance = 'standard';
fixture.detectChanges();

expect(fixture.nativeElement.querySelector('input').getAttribute('placeholder')).toBe(
'Placeholder',
);
}));

it('should not add the native select class if the control is not a native select', () => {
const fixture = createComponent(MatInputWithId);
fixture.detectChanges();
Expand Down Expand Up @@ -2232,14 +2242,15 @@ class MatInputWithLabel {}

@Component({
template: `
<mat-form-field [floatLabel]="floatLabel">
<mat-form-field [floatLabel]="floatLabel" [appearance]="appearance">
<mat-label>Label</mat-label>
<input matInput placeholder="Placeholder">
</mat-form-field>
`,
})
class MatInputWithLabelAndPlaceholder {
floatLabel: FloatLabelType;
appearance: MatFormFieldAppearance = 'legacy';
}

@Component({
Expand Down
6 changes: 5 additions & 1 deletion src/material/input/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,11 @@ export class MatInput
// screen readers will read it out twice: once from the label and once from the attribute.
// TODO: can be removed once we get rid of the `legacy` style for the form field, because it's
// the only one that supports promoting the placeholder to a label.
const placeholder = this._formField?._hideControlPlaceholder?.() ? null : this.placeholder;
const formField = this._formField;
const placeholder =
formField && formField.appearance === 'legacy' && !formField._hasLabel?.()
? null
: this.placeholder;
if (placeholder !== this._previousPlaceholder) {
const element = this._elementRef.nativeElement;
this._previousPlaceholder = placeholder;
Expand Down

0 comments on commit be89dcd

Please sign in to comment.