Skip to content

Commit

Permalink
fix(material/form-field): move error aria-live to parent container
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewseguin committed Dec 10, 2024
1 parent 4a0818d commit 8b038a3
Show file tree
Hide file tree
Showing 6 changed files with 15 additions and 27 deletions.
4 changes: 3 additions & 1 deletion src/material/chips/chip-grid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,9 @@ describe('MatChipGrid', () => {
errorTestComponent.formControl.markAsTouched();
fixture.detectChanges();

expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
expect(
containerEl.querySelector('[aria-live]:has(mat-error)')!.getAttribute('aria-live'),
).toBe('polite');
});

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

import {
Directive,
ElementRef,
InjectionToken,
Input,
HostAttributeToken,
inject,
} from '@angular/core';
import {Directive, InjectionToken, Input, inject} from '@angular/core';
import {_IdGenerator} from '@angular/cdk/a11y';

/**
Expand All @@ -28,7 +21,6 @@ export const MAT_ERROR = new InjectionToken<MatError>('MatError');
selector: 'mat-error, [matError]',
host: {
'class': 'mat-mdc-form-field-error mat-mdc-form-field-bottom-align',
'aria-atomic': 'true',
'[id]': 'id',
},
providers: [{provide: MAT_ERROR, useExisting: MatError}],
Expand All @@ -38,14 +30,5 @@ export class MatError {

constructor(...args: unknown[]);

constructor() {
const ariaLive = inject(new HostAttributeToken('aria-live'), {optional: true});

// 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) {
const elementRef = inject(ElementRef);
elementRef.nativeElement.setAttribute('aria-live', 'polite');
}
}
constructor() {}
}
3 changes: 2 additions & 1 deletion src/material/form-field/form-field.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@
<div
class="mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align"
[class.mat-mdc-form-field-subscript-dynamic-size]="subscriptSizing === 'dynamic'"
aria-atomic="true" aria-live="polite"
>
@switch (_getDisplayedMessages()) {
@switch (_getSubscriptMessageType()) {
@case ('error') {
<div
class="mat-mdc-form-field-error-wrapper"
Expand Down
6 changes: 3 additions & 3 deletions src/material/form-field/form-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,8 +592,8 @@ export class MatFormField
return control && control[prop];
}

/** Determines whether to display hints or errors. */
_getDisplayedMessages(): 'error' | 'hint' {
/** Gets the type of subscript message to render (error or hint). */
_getSubscriptMessageType(): 'error' | 'hint' {
return this._errorChildren && this._errorChildren.length > 0 && this._control.errorState
? 'error'
: 'hint';
Expand Down Expand Up @@ -661,7 +661,7 @@ export class MatFormField
ids.push(...this._control.userAriaDescribedBy.split(' '));
}

if (this._getDisplayedMessages() === 'hint') {
if (this._getSubscriptMessageType() === 'hint') {
const startHint = this._hintChildren
? this._hintChildren.find(hint => hint.align === 'start')
: null;
Expand Down
6 changes: 4 additions & 2 deletions src/material/input/input.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1238,11 +1238,13 @@ describe('MatMdcInput with forms', () => {
.toBe(1);
}));

it('should set the proper aria-live attribute on the error messages', fakeAsync(() => {
it('should be in a parent element with the an aria-live attribute to announce the error', fakeAsync(() => {
testComponent.formControl.markAsTouched();
fixture.detectChanges();

expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
expect(
containerEl.querySelector('[aria-live]:has(mat-error)')!.getAttribute('aria-live'),
).toBe('polite');
}));

it('sets the aria-describedby to reference errors when in error state', fakeAsync(() => {
Expand Down
2 changes: 1 addition & 1 deletion tools/public_api_guard/material/form-field.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ export class MatFormField implements FloatingLabelParent, AfterContentInit, Afte
// (undocumented)
_formFieldControl: MatFormFieldControl_2<any>;
getConnectedOverlayOrigin(): ElementRef;
_getDisplayedMessages(): 'error' | 'hint';
getLabelId: Signal<string | null>;
_getSubscriptMessageType(): 'error' | 'hint';
_handleLabelResized(): void;
// (undocumented)
_hasFloatingLabel: Signal<boolean>;
Expand Down

0 comments on commit 8b038a3

Please sign in to comment.