From a55d34c0d9ac5b9a3a97f78177ec8b46386fdd1a Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Thu, 23 Jan 2025 16:01:53 -0700 Subject: [PATCH] fix(material/form-field): move error aria-live to parent container --- src/material/chips/chip-grid.spec.ts | 4 +++- src/material/form-field/directives/error.ts | 21 ++----------------- src/material/form-field/form-field.html | 12 +++++++---- src/material/form-field/form-field.ts | 6 +++--- src/material/input/input.spec.ts | 6 ++++-- tools/public_api_guard/material/form-field.md | 2 +- 6 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/material/chips/chip-grid.spec.ts b/src/material/chips/chip-grid.spec.ts index a4a67b84961b..7bf77e5dd96f 100644 --- a/src/material/chips/chip-grid.spec.ts +++ b/src/material/chips/chip-grid.spec.ts @@ -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(() => { diff --git a/src/material/form-field/directives/error.ts b/src/material/form-field/directives/error.ts index a304e90a8171..9d474d478ad8 100644 --- a/src/material/form-field/directives/error.ts +++ b/src/material/form-field/directives/error.ts @@ -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'; /** @@ -28,7 +21,6 @@ export const MAT_ERROR = new InjectionToken('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}], @@ -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() {} } diff --git a/src/material/form-field/form-field.html b/src/material/form-field/form-field.html index f1a46075b46b..9ee78a1e571d 100644 --- a/src/material/form-field/form-field.html +++ b/src/material/form-field/form-field.html @@ -99,14 +99,18 @@ 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(); + +
+ @if (subscriptMessageType == 'error') {
} +
- @case ('hint') { +
+ @if (subscriptMessageType == 'hint') {
@if (hintLabel) { {{hintLabel}} @@ -116,5 +120,5 @@
} - } +
diff --git a/src/material/form-field/form-field.ts b/src/material/form-field/form-field.ts index 0ea473c78d41..d7ef992f763d 100644 --- a/src/material/form-field/form-field.ts +++ b/src/material/form-field/form-field.ts @@ -597,8 +597,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'; @@ -666,7 +666,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; diff --git a/src/material/input/input.spec.ts b/src/material/input/input.spec.ts index 3a778acbcee4..df3118007c33 100644 --- a/src/material/input/input.spec.ts +++ b/src/material/input/input.spec.ts @@ -1229,11 +1229,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(() => { diff --git a/tools/public_api_guard/material/form-field.md b/tools/public_api_guard/material/form-field.md index 539f205be870..ccec71731263 100644 --- a/tools/public_api_guard/material/form-field.md +++ b/tools/public_api_guard/material/form-field.md @@ -83,8 +83,8 @@ export class MatFormField implements FloatingLabelParent, AfterContentInit, Afte // (undocumented) _formFieldControl: MatFormFieldControl_2; getConnectedOverlayOrigin(): ElementRef; - _getDisplayedMessages(): 'error' | 'hint'; getLabelId: Signal; + _getSubscriptMessageType(): 'error' | 'hint'; _handleLabelResized(): void; // (undocumented) _hasFloatingLabel: Signal;