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 11, 2024
1 parent 4a0818d commit f57542a
Show file tree
Hide file tree
Showing 6 changed files with 21 additions and 30 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() {}
}
12 changes: 8 additions & 4 deletions src/material/form-field/form-field.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,21 @@
class="mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align"
[class.mat-mdc-form-field-subscript-dynamic-size]="subscriptSizing === 'dynamic'"
>
@switch (_getDisplayedMessages()) {
@case ('error') {
@let subscriptMessageType = _getSubscriptMessageType();

<div aria-atomic="true" aria-live="polite">
@if (subscriptMessageType == 'error') {
<div
class="mat-mdc-form-field-error-wrapper"
[@transitionMessages]="_subscriptAnimationState"
>
<ng-content select="mat-error, [matError]"></ng-content>
</div>
}
</div>

@case ('hint') {
<div aria-atomic="true" aria-live="polite">
@if (subscriptMessageType == 'hint') {
<div class="mat-mdc-form-field-hint-wrapper" [@transitionMessages]="_subscriptAnimationState">
@if (hintLabel) {
<mat-hint [id]="_hintLabelId">{{hintLabel}}</mat-hint>
Expand All @@ -119,5 +123,5 @@
<ng-content select="mat-hint[align='end']"></ng-content>
</div>
}
}
</div>
</div>
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 f57542a

Please sign in to comment.