Skip to content

Commit 6b2b518

Browse files
authored
fix(material/input): preserve native placeholder on non-legacy appearances (#20936)
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.
1 parent 67a3eb1 commit 6b2b518

File tree

5 files changed

+47
-4
lines changed

5 files changed

+47
-4
lines changed

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,17 @@ describe('MatMdcInput without forms', () => {
882882
expect(formField.classList).not.toContain('mat-mdc-form-field-type-mat-native-select');
883883
});
884884

885+
it('should preserve the native placeholder on a non-legacy appearance', fakeAsync(() => {
886+
const fixture = createComponent(MatInputWithLabelAndPlaceholder);
887+
fixture.componentInstance.floatLabel = 'auto';
888+
fixture.componentInstance.appearance = 'outline';
889+
fixture.detectChanges();
890+
891+
expect(fixture.nativeElement.querySelector('input').getAttribute('placeholder')).toBe(
892+
'Placeholder',
893+
);
894+
}));
895+
885896
it(
886897
'should use the native input value when determining whether ' +
887898
'the element is empty with a custom accessor',
@@ -1861,14 +1872,15 @@ class MatInputWithLabel {}
18611872

18621873
@Component({
18631874
template: `
1864-
<mat-form-field [floatLabel]="floatLabel">
1875+
<mat-form-field [floatLabel]="floatLabel" [appearance]="appearance">
18651876
<mat-label>Label</mat-label>
18661877
<input matInput placeholder="Placeholder">
18671878
</mat-form-field>
18681879
`,
18691880
})
18701881
class MatInputWithLabelAndPlaceholder {
18711882
floatLabel: FloatLabelType;
1883+
appearance: MatFormFieldAppearance;
18721884
}
18731885

18741886
@Component({

src/material/datepicker/date-range-input.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ $date-range-input-part-max-width: calc(50% - #{$date-range-input-separator-spaci
2828
.mat-date-range-input-separator {
2929
@include _placeholder-transition(opacity);
3030
margin: 0 $date-range-input-separator-spacing;
31+
32+
._mat-animation-noopable & {
33+
transition: none;
34+
}
3135
}
3236

3337
.mat-date-range-input-separator-hidden {
@@ -85,6 +89,12 @@ $date-range-input-part-max-width: calc(50% - #{$date-range-input-separator-spaci
8589
}
8690
}
8791
}
92+
93+
._mat-animation-noopable & {
94+
@include vendor-prefixes.input-placeholder {
95+
transition: none;
96+
}
97+
}
8898
}
8999

90100
// We want the start input to be flush against the separator, no matter how much text it has, but

src/material/form-field/form-field-input.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@
126126
}
127127
}
128128
}
129+
130+
._mat-animation-noopable & {
131+
@include vendor-prefixes.input-placeholder {
132+
transition: none;
133+
}
134+
}
129135
}
130136

131137
// Prevents IE from always adding a scrollbar by default.

src/material/input/input.spec.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,6 @@ describe('MatInput without forms', () => {
973973
expect(container.classList).toContain('mat-form-field-hide-placeholder');
974974
expect(container.classList).not.toContain('mat-form-field-should-float');
975975
expect(label.textContent.trim()).toBe('Label');
976-
expect(input.hasAttribute('placeholder')).toBe(false);
977976

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

1022+
it('should preserve the native placeholder on a non-legacy appearance', fakeAsync(() => {
1023+
const fixture = createComponent(MatInputWithLabelAndPlaceholder);
1024+
fixture.componentInstance.floatLabel = 'auto';
1025+
fixture.componentInstance.appearance = 'standard';
1026+
fixture.detectChanges();
1027+
1028+
expect(fixture.nativeElement.querySelector('input').getAttribute('placeholder')).toBe(
1029+
'Placeholder',
1030+
);
1031+
}));
1032+
10231033
it('should not add the native select class if the control is not a native select', () => {
10241034
const fixture = createComponent(MatInputWithId);
10251035
fixture.detectChanges();
@@ -2232,14 +2242,15 @@ class MatInputWithLabel {}
22322242

22332243
@Component({
22342244
template: `
2235-
<mat-form-field [floatLabel]="floatLabel">
2245+
<mat-form-field [floatLabel]="floatLabel" [appearance]="appearance">
22362246
<mat-label>Label</mat-label>
22372247
<input matInput placeholder="Placeholder">
22382248
</mat-form-field>
22392249
`,
22402250
})
22412251
class MatInputWithLabelAndPlaceholder {
22422252
floatLabel: FloatLabelType;
2253+
appearance: MatFormFieldAppearance = 'legacy';
22432254
}
22442255

22452256
@Component({

src/material/input/input.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,11 @@ export class MatInput
396396
// screen readers will read it out twice: once from the label and once from the attribute.
397397
// TODO: can be removed once we get rid of the `legacy` style for the form field, because it's
398398
// the only one that supports promoting the placeholder to a label.
399-
const placeholder = this._formField?._hideControlPlaceholder?.() ? null : this.placeholder;
399+
const formField = this._formField;
400+
const placeholder =
401+
formField && formField.appearance === 'legacy' && !formField._hasLabel?.()
402+
? null
403+
: this.placeholder;
400404
if (placeholder !== this._previousPlaceholder) {
401405
const element = this._elementRef.nativeElement;
402406
this._previousPlaceholder = placeholder;

0 commit comments

Comments
 (0)