Skip to content

Commit d3d4163

Browse files
committed
fix(material/form-field): move error aria-live to parent container
1 parent e91d509 commit d3d4163

File tree

7 files changed

+22
-31
lines changed

7 files changed

+22
-31
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5557,4 +5557,4 @@ _Breaking changes:_
55575557

55585558
# Changes Prior to 12.0.0
55595559

5560-
To view changes that occurred prior to 12.0.0, see [CHANGELOG_ARCHIVE.md](https://github.com/angular/components/blob/main/CHANGELOG_ARCHIVE.md).
5560+
To view changes that occurred prior to 12.0.0, see [CHANGELOG_ARCHIVE.md](https://github.com/angular/components/blob/main/CHANGELOG_ARCHIVE.md).

src/material/chips/chip-grid.spec.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,9 @@ describe('MatChipGrid', () => {
10031003
errorTestComponent.formControl.markAsTouched();
10041004
fixture.detectChanges();
10051005

1006-
expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
1006+
expect(
1007+
containerEl.querySelector('[aria-live]:has(mat-error)')!.getAttribute('aria-live'),
1008+
).toBe('polite');
10071009
});
10081010

10091011
it('sets the aria-describedby on the input to reference errors when in error state', fakeAsync(() => {

src/material/form-field/directives/error.ts

+2-19
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {
10-
Directive,
11-
ElementRef,
12-
InjectionToken,
13-
Input,
14-
HostAttributeToken,
15-
inject,
16-
} from '@angular/core';
9+
import {Directive, InjectionToken, Input, inject} from '@angular/core';
1710
import {_IdGenerator} from '@angular/cdk/a11y';
1811

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

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

41-
constructor() {
42-
const ariaLive = inject(new HostAttributeToken('aria-live'), {optional: true});
43-
44-
// If no aria-live value is set add 'polite' as a default. This is preferred over setting
45-
// role='alert' so that screen readers do not interrupt the current task to read this aloud.
46-
if (!ariaLive) {
47-
const elementRef = inject(ElementRef);
48-
elementRef.nativeElement.setAttribute('aria-live', 'polite');
49-
}
50-
}
33+
constructor() {}
5134
}

src/material/form-field/form-field.html

+8-4
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,18 @@
9999
class="mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align"
100100
[class.mat-mdc-form-field-subscript-dynamic-size]="subscriptSizing === 'dynamic'"
101101
>
102-
@switch (_getDisplayedMessages()) {
103-
@case ('error') {
102+
@let subscriptMessageType = _getSubscriptMessageType();
103+
104+
<div aria-atomic="true" aria-live="polite">
105+
@if (subscriptMessageType === 'error') {
104106
<div class="mat-mdc-form-field-error-wrapper">
105107
<ng-content select="mat-error, [matError]"></ng-content>
106108
</div>
107109
}
110+
</div>
108111

109-
@case ('hint') {
112+
<div aria-atomic="true" aria-live="polite">
113+
@if (subscriptMessageType === 'hint') {
110114
<div class="mat-mdc-form-field-hint-wrapper">
111115
@if (hintLabel) {
112116
<mat-hint [id]="_hintLabelId">{{hintLabel}}</mat-hint>
@@ -116,5 +120,5 @@
116120
<ng-content select="mat-hint[align='end']"></ng-content>
117121
</div>
118122
}
119-
}
123+
</div>
120124
</div>

src/material/form-field/form-field.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -597,8 +597,8 @@ export class MatFormField
597597
return control && control[prop];
598598
}
599599

600-
/** Determines whether to display hints or errors. */
601-
_getDisplayedMessages(): 'error' | 'hint' {
600+
/** Gets the type of subscript message to render (error or hint). */
601+
_getSubscriptMessageType(): 'error' | 'hint' {
602602
return this._errorChildren && this._errorChildren.length > 0 && this._control.errorState
603603
? 'error'
604604
: 'hint';
@@ -666,7 +666,7 @@ export class MatFormField
666666
ids.push(...this._control.userAriaDescribedBy.split(' '));
667667
}
668668

669-
if (this._getDisplayedMessages() === 'hint') {
669+
if (this._getSubscriptMessageType() === 'hint') {
670670
const startHint = this._hintChildren
671671
? this._hintChildren.find(hint => hint.align === 'start')
672672
: null;

src/material/input/input.spec.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1229,11 +1229,13 @@ describe('MatMdcInput with forms', () => {
12291229
.toBe(1);
12301230
}));
12311231

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

1236-
expect(containerEl.querySelector('mat-error')!.getAttribute('aria-live')).toBe('polite');
1236+
expect(
1237+
containerEl.querySelector('[aria-live]:has(mat-error)')!.getAttribute('aria-live'),
1238+
).toBe('polite');
12371239
}));
12381240

12391241
it('sets the aria-describedby to reference errors when in error state', fakeAsync(() => {

tools/public_api_guard/material/form-field.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ export class MatFormField implements FloatingLabelParent, AfterContentInit, Afte
8383
// (undocumented)
8484
_formFieldControl: MatFormFieldControl_2<any>;
8585
getConnectedOverlayOrigin(): ElementRef;
86-
_getDisplayedMessages(): 'error' | 'hint';
8786
getLabelId: Signal<string | null>;
87+
_getSubscriptMessageType(): 'error' | 'hint';
8888
_handleLabelResized(): void;
8989
// (undocumented)
9090
_hasFloatingLabel: Signal<boolean>;

0 commit comments

Comments
 (0)