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 0c62f7ca809f..68b4b9fadd43 100644 --- a/src/material/form-field/form-field.html +++ b/src/material/form-field/form-field.html @@ -99,8 +99,10 @@ 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}} @@ -119,5 +123,5 @@
} - } +
diff --git a/src/material/form-field/form-field.ts b/src/material/form-field/form-field.ts index c47904e7faa0..37b342a7c7fc 100644 --- a/src/material/form-field/form-field.ts +++ b/src/material/form-field/form-field.ts @@ -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'; @@ -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; diff --git a/src/material/input/input.spec.ts b/src/material/input/input.spec.ts index 611cdc3320f2..03e8079d7f48 100644 --- a/src/material/input/input.spec.ts +++ b/src/material/input/input.spec.ts @@ -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(() => { diff --git a/tools/public_api_guard/material/form-field.md b/tools/public_api_guard/material/form-field.md index f7efb046f473..57c374860f79 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;