From 33d3cb3ce92a689b43e731834924a11139870bab Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sat, 3 Nov 2018 01:21:24 +0100 Subject: [PATCH] fix(snack-bar): set appropriate role based on passed in politeness (#13864) Currently we always have a `role` of `alert` on the snack bar container which has an implicit politeness of `assertive` and which can override the message announced by the live announcer. These changes set the role to `status` if it's set to `polite` or remove the role completely if the politeness is turned off. Fixes #13493. --- src/lib/snack-bar/snack-bar-container.ts | 15 +++++++++++++- src/lib/snack-bar/snack-bar.spec.ts | 26 ++++++++++++++++++++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/lib/snack-bar/snack-bar-container.ts b/src/lib/snack-bar/snack-bar-container.ts index bff547d4dad5..da36b7d39163 100644 --- a/src/lib/snack-bar/snack-bar-container.ts +++ b/src/lib/snack-bar/snack-bar-container.ts @@ -44,7 +44,7 @@ import {MatSnackBarConfig} from './snack-bar-config'; encapsulation: ViewEncapsulation.None, animations: [matSnackBarAnimations.snackBarState], host: { - 'role': 'alert', + '[attr.role]': '_role', 'class': 'mat-snack-bar-container', '[@state]': '_animationState', '(@state.done)': 'onAnimationEnd($event)' @@ -66,6 +66,9 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy /** The state of the snack bar animations. */ _animationState = 'void'; + /** ARIA role for the snack bar container. */ + _role: 'alert' | 'status' | null; + constructor( private _ngZone: NgZone, private _elementRef: ElementRef, @@ -74,6 +77,16 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy public snackBarConfig: MatSnackBarConfig) { super(); + + // Based on the ARIA spec, `alert` and `status` roles have an + // implicit `assertive` and `polite` politeness respectively. + if (snackBarConfig.politeness === 'assertive') { + this._role = 'alert'; + } else if (snackBarConfig.politeness === 'polite') { + this._role = 'status'; + } else { + this._role = null; + } } /** Attach a component portal as content to this snack bar container. */ diff --git a/src/lib/snack-bar/snack-bar.spec.ts b/src/lib/snack-bar/snack-bar.spec.ts index 951f7592acc6..b12bc1d88d29 100644 --- a/src/lib/snack-bar/snack-bar.spec.ts +++ b/src/lib/snack-bar/snack-bar.spec.ts @@ -67,13 +67,31 @@ describe('MatSnackBar', () => { testViewContainerRef = viewContainerFixture.componentInstance.childViewContainer; }); - it('should have the role of alert', () => { - snackBar.open(simpleMessage, simpleActionLabel); + it('should have the role of `alert` with an `assertive` politeness', () => { + snackBar.open(simpleMessage, simpleActionLabel, {politeness: 'assertive'}); + viewContainerFixture.detectChanges(); - let containerElement = overlayContainerElement.querySelector('snack-bar-container')!; + const containerElement = overlayContainerElement.querySelector('snack-bar-container')!; expect(containerElement.getAttribute('role')) .toBe('alert', 'Expected snack bar container to have role="alert"'); - }); + }); + + it('should have the role of `status` with a `polite` politeness', () => { + snackBar.open(simpleMessage, simpleActionLabel, {politeness: 'polite'}); + viewContainerFixture.detectChanges(); + + const containerElement = overlayContainerElement.querySelector('snack-bar-container')!; + expect(containerElement.getAttribute('role')) + .toBe('status', 'Expected snack bar container to have role="status"'); + }); + + it('should remove the role if the politeness is turned off', () => { + snackBar.open(simpleMessage, simpleActionLabel, {politeness: 'off'}); + viewContainerFixture.detectChanges(); + + const containerElement = overlayContainerElement.querySelector('snack-bar-container')!; + expect(containerElement.getAttribute('role')).toBeFalsy('Expected role to be removed'); + }); it('should open and close a snackbar without a ViewContainerRef', fakeAsync(() => { let snackBarRef = snackBar.open('Snack time!', 'Chew');