Skip to content

Commit

Permalink
feat(material/form-field): make mat-errors more polite (#21870)
Browse files Browse the repository at this point in the history
Make the "politeness" of mat-error "polite" by default (instead of "assertive") and also make this
configurable via an input binding.

Fixes #21781
  • Loading branch information
chrisbubernak authored Mar 5, 2021
1 parent b537ba1 commit a405ce6
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 13 deletions.
4 changes: 2 additions & 2 deletions src/material-experimental/mdc-chips/chip-grid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -905,11 +905,11 @@ describe('MDC-based MatChipGrid', () => {
});
}));

it('should set the proper role on the error messages', () => {
it('should set the proper aria-live attribute on the error messages', () => {
errorTestComponent.formControl.markAsTouched();
fixture.detectChanges();

expect(containerEl.querySelector('mat-error')!.getAttribute('role')).toBe('alert');
expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
});

it('sets the aria-describedby to reference errors when in error state', () => {
Expand Down
12 changes: 10 additions & 2 deletions src/material-experimental/mdc-form-field/directives/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Directive, InjectionToken, Input} from '@angular/core';
import {Attribute, Directive, ElementRef, InjectionToken, Input} from '@angular/core';

let nextUniqueId = 0;

Expand All @@ -22,11 +22,19 @@ export const MAT_ERROR = new InjectionToken<MatError>('MatError');
selector: 'mat-error',
host: {
'class': 'mat-mdc-form-field-error mat-mdc-form-field-bottom-align',
'role': 'alert',
'aria-atomic': 'true',
'[id]': 'id',
},
providers: [{provide: MAT_ERROR, useExisting: MatError}],
})
export class MatError {
@Input() id: string = `mat-mdc-error-${nextUniqueId++}`;

constructor(@Attribute('aria-live') ariaLive: string, elementRef: ElementRef) {
// If no aria-live value is set add 'polite' as a default. This is preferred over setting
// role='alert' so that screen readers do not interrupt the current task to read this aloud.
if (!ariaLive) {
elementRef.nativeElement.setAttribute('aria-live', 'polite');
}
}
}
4 changes: 2 additions & 2 deletions src/material-experimental/mdc-input/input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1044,11 +1044,11 @@ describe('MatMdcInput with forms', () => {
.toBe(1, 'Expected one hint to still be shown.');
}));

it('should set the proper role on the error messages', fakeAsync(() => {
it('should set the proper aria-live attribute on the error messages', fakeAsync(() => {
testComponent.formControl.markAsTouched();
fixture.detectChanges();

expect(containerEl.querySelector('mat-error')!.getAttribute('role')).toBe('alert');
expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
}));

it('sets the aria-describedby to reference errors when in error state', fakeAsync(() => {
Expand Down
4 changes: 2 additions & 2 deletions src/material/chips/chip-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1282,11 +1282,11 @@ describe('MatChipList', () => {
});
}));

it('should set the proper role on the error messages', () => {
it('should set the proper aria-live attribute on the error messages', () => {
errorTestComponent.formControl.markAsTouched();
fixture.detectChanges();

expect(containerEl.querySelector('mat-error')!.getAttribute('role')).toBe('alert');
expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
});

it('sets the aria-describedby to reference errors when in error state', () => {
Expand Down
12 changes: 10 additions & 2 deletions src/material/form-field/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Directive, InjectionToken, Input} from '@angular/core';
import {Attribute, Directive, ElementRef, InjectionToken, Input} from '@angular/core';

let nextUniqueId = 0;

Expand All @@ -22,11 +22,19 @@ export const MAT_ERROR = new InjectionToken<MatError>('MatError');
selector: 'mat-error',
host: {
'class': 'mat-error',
'role': 'alert',
'[attr.id]': 'id',
'aria-atomic': 'true',
},
providers: [{provide: MAT_ERROR, useExisting: MatError}],
})
export class MatError {
@Input() id: string = `mat-error-${nextUniqueId++}`;

constructor(@Attribute('aria-live') ariaLive: string, elementRef: ElementRef) {
// If no aria-live value is set add 'polite' as a default. This is preferred over setting
// role='alert' so that screen readers do not interrupt the current task to read this aloud.
if (!ariaLive) {
elementRef.nativeElement.setAttribute('aria-live', 'polite');
}
}
}
4 changes: 2 additions & 2 deletions src/material/input/input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1193,11 +1193,11 @@ describe('MatInput with forms', () => {
.toBe(1, 'Expected one hint to still be shown.');
}));

it('should set the proper role on the error messages', fakeAsync(() => {
it('should set the proper aria-live attribute on the error messages', fakeAsync(() => {
testComponent.formControl.markAsTouched();
fixture.detectChanges();

expect(containerEl.querySelector('mat-error')!.getAttribute('role')).toBe('alert');
expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
}));

it('sets the aria-describedby to reference errors when in error state', fakeAsync(() => {
Expand Down
3 changes: 2 additions & 1 deletion tools/public_api_guard/material/form-field.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ export declare const MAT_SUFFIX: InjectionToken<MatSuffix>;

export declare class MatError {
id: string;
constructor(ariaLive: string, elementRef: ElementRef);
static ɵdir: i0.ɵɵDirectiveDefWithMeta<MatError, "mat-error", never, { "id": "id"; }, {}, never>;
static ɵfac: i0.ɵɵFactoryDef<MatError, never>;
static ɵfac: i0.ɵɵFactoryDef<MatError, [{ attribute: "aria-live"; }, null]>;
}

export declare class MatFormField extends _MatFormFieldMixinBase implements AfterContentInit, AfterContentChecked, AfterViewInit, OnDestroy, CanColor {
Expand Down

0 comments on commit a405ce6

Please sign in to comment.