Skip to content

Commit 99baf1d

Browse files
committed
fix(material/input): do not override existing aria-describedby value
The form-field notifies controls whenever hints or errors have been displayed. It does this, so that controls like the input can refresh their `aria-describedby` value to point to the errors and hints. This currently has the downside of overriding existing `aria-describedby` values that have been manually set by developers. We could fix this by reading the initial value and merging it with the ids received from the form-field.
1 parent 5f5f5aa commit 99baf1d

File tree

4 files changed

+40
-24
lines changed

4 files changed

+40
-24
lines changed

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -474,10 +474,11 @@ describe('MatMdcInput without forms', () => {
474474
fixture.componentInstance.label = 'label';
475475
fixture.detectChanges();
476476

477-
let hint = fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))!.nativeElement;
478-
let input = fixture.debugElement.query(By.css('input'))!.nativeElement;
477+
const hint = fixture.debugElement.query(By.css('.mat-mdc-form-field-hint'))!.nativeElement;
478+
const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
479+
const hintId = hint.getAttribute('id');
479480

480-
expect(input.getAttribute('aria-describedby')).toBe(hint.getAttribute('id'));
481+
expect(input.getAttribute('aria-describedby')).toBe(`initial ${hintId}`);
481482
}));
482483

483484
it('sets the aria-describedby to the id of the mat-hint', fakeAsync(() => {
@@ -1253,7 +1254,10 @@ class MatInputHintLabel2TestController {
12531254
}
12541255

12551256
@Component({
1256-
template: `<mat-form-field [hintLabel]="label"><input matInput></mat-form-field>`
1257+
template: `
1258+
<mat-form-field [hintLabel]="label">
1259+
<input matInput aria-describedby="initial">
1260+
</mat-form-field>`
12571261
})
12581262
class MatInputHintLabelTestController {
12591263
label: string = '';

src/material/input/input.spec.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -564,10 +564,11 @@ describe('MatInput without forms', () => {
564564
fixture.componentInstance.label = 'label';
565565
fixture.detectChanges();
566566

567-
let hint = fixture.debugElement.query(By.css('.mat-hint'))!.nativeElement;
568-
let input = fixture.debugElement.query(By.css('input'))!.nativeElement;
567+
const hint = fixture.debugElement.query(By.css('.mat-hint'))!.nativeElement;
568+
const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
569+
const hintId = hint.getAttribute('id');
569570

570-
expect(input.getAttribute('aria-describedby')).toBe(hint.getAttribute('id'));
571+
expect(input.getAttribute('aria-describedby')).toBe(`initial ${hintId}`);
571572
}));
572573

573574
it('sets the aria-describedby to the id of the mat-hint', fakeAsync(() => {
@@ -1767,7 +1768,10 @@ class MatInputHintLabel2TestController {
17671768
}
17681769

17691770
@Component({
1770-
template: `<mat-form-field [hintLabel]="label"><input matInput></mat-form-field>`
1771+
template: `
1772+
<mat-form-field [hintLabel]="label">
1773+
<input matInput aria-describedby="initial">
1774+
</mat-form-field>`
17711775
})
17721776
class MatInputHintLabelTestController {
17731777
label: string = '';

src/material/input/input.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
1010
import {getSupportedInputTypes, Platform} from '@angular/cdk/platform';
1111
import {AutofillMonitor} from '@angular/cdk/text-field';
1212
import {
13+
Attribute,
1314
Directive,
1415
DoCheck,
1516
ElementRef,
@@ -228,19 +229,20 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
228229
].filter(t => getSupportedInputTypes().has(t));
229230

230231
constructor(
231-
protected _elementRef: ElementRef<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
232-
protected _platform: Platform,
233-
/** @docs-private */
234-
@Optional() @Self() public ngControl: NgControl,
235-
@Optional() _parentForm: NgForm,
236-
@Optional() _parentFormGroup: FormGroupDirective,
237-
_defaultErrorStateMatcher: ErrorStateMatcher,
238-
@Optional() @Self() @Inject(MAT_INPUT_VALUE_ACCESSOR) inputValueAccessor: any,
239-
private _autofillMonitor: AutofillMonitor,
240-
ngZone: NgZone,
241-
// TODO: Remove this once the legacy appearance has been removed. We only need
242-
// to inject the form-field for determining whether the placeholder has been promoted.
243-
@Optional() @Inject(MAT_FORM_FIELD) private _formField?: MatFormField) {
232+
protected _elementRef: ElementRef<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
233+
protected _platform: Platform,
234+
/** @docs-private */
235+
@Optional() @Self() public ngControl: NgControl,
236+
@Optional() _parentForm: NgForm,
237+
@Optional() _parentFormGroup: FormGroupDirective,
238+
_defaultErrorStateMatcher: ErrorStateMatcher,
239+
@Optional() @Self() @Inject(MAT_INPUT_VALUE_ACCESSOR) inputValueAccessor: any,
240+
private _autofillMonitor: AutofillMonitor,
241+
ngZone: NgZone,
242+
// TODO: Remove this once the legacy appearance has been removed. We only need
243+
// to inject the form-field for determining whether the placeholder has been promoted.
244+
@Optional() @Inject(MAT_FORM_FIELD) private _formField?: MatFormField,
245+
@Attribute('aria-describedby') private _initialAriaDescribedBy?: string|null) {
244246
super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl);
245247

246248
const element = this._elementRef.nativeElement;
@@ -439,7 +441,13 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
439441
* @docs-private
440442
*/
441443
setDescribedByIds(ids: string[]) {
442-
this._ariaDescribedby = ids.join(' ');
444+
let value = ids.join(' ');
445+
// Append the describe-by ids from the form-field without discarding the initial
446+
// `aria-describedby` value that has been specified as static attribute.
447+
if (this._initialAriaDescribedBy != null) {
448+
value = `${this._initialAriaDescribedBy} ${value}`;
449+
}
450+
this._ariaDescribedby = value;
443451
}
444452

445453
/**

tools/public_api_guard/material/input.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export declare class MatInput extends _MatInputMixinBase implements MatFormField
4040
get value(): string;
4141
set value(value: string);
4242
constructor(_elementRef: ElementRef<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>, _platform: Platform,
43-
ngControl: NgControl, _parentForm: NgForm, _parentFormGroup: FormGroupDirective, _defaultErrorStateMatcher: ErrorStateMatcher, inputValueAccessor: any, _autofillMonitor: AutofillMonitor, ngZone: NgZone, _formField?: MatFormField | undefined);
43+
ngControl: NgControl, _parentForm: NgForm, _parentFormGroup: FormGroupDirective, _defaultErrorStateMatcher: ErrorStateMatcher, inputValueAccessor: any, _autofillMonitor: AutofillMonitor, ngZone: NgZone, _formField?: MatFormField | undefined, _initialAriaDescribedBy?: string | null | undefined);
4444
protected _dirtyCheckNativeValue(): void;
4545
_focusChanged(isFocused: boolean): void;
4646
protected _isBadInput(): boolean;
@@ -59,7 +59,7 @@ export declare class MatInput extends _MatInputMixinBase implements MatFormField
5959
static ngAcceptInputType_required: BooleanInput;
6060
static ngAcceptInputType_value: any;
6161
static ɵdir: i0.ɵɵDirectiveDefWithMeta<MatInput, "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", ["matInput"], { "disabled": "disabled"; "id": "id"; "placeholder": "placeholder"; "required": "required"; "type": "type"; "errorStateMatcher": "errorStateMatcher"; "value": "value"; "readonly": "readonly"; }, {}, never>;
62-
static ɵfac: i0.ɵɵFactoryDef<MatInput, [null, null, { optional: true; self: true; }, { optional: true; }, { optional: true; }, null, { optional: true; self: true; }, null, null, { optional: true; }]>;
62+
static ɵfac: i0.ɵɵFactoryDef<MatInput, [null, null, { optional: true; self: true; }, { optional: true; }, { optional: true; }, null, { optional: true; self: true; }, null, null, { optional: true; }, { attribute: "aria-describedby"; }]>;
6363
}
6464

6565
export declare class MatInputModule {

0 commit comments

Comments
 (0)