Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(live-announcer): add ability to clear live element #11996

Merged
merged 1 commit into from
Nov 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/cdk/a11y/live-announcer/live-announcer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,34 @@ describe('LiveAnnouncer', () => {
expect(ariaLiveElement.getAttribute('aria-live')).toBe('polite');
}));

it('should be able to clear out the aria-live element manually', fakeAsync(() => {
announcer.announce('Hey Google');
tick(100);
expect(ariaLiveElement.textContent).toBe('Hey Google');

announcer.clear();
expect(ariaLiveElement.textContent).toBeFalsy();
}));

it('should be able to clear out the aria-live element by setting a duration', fakeAsync(() => {
announcer.announce('Hey Google', 2000);
tick(100);
expect(ariaLiveElement.textContent).toBe('Hey Google');

tick(2000);
expect(ariaLiveElement.textContent).toBeFalsy();
}));

it('should clear the duration of previous messages when announcing a new one', fakeAsync(() => {
announcer.announce('Hey Google', 2000);
tick(100);
expect(ariaLiveElement.textContent).toBe('Hey Google');

announcer.announce('Hello there');
tick(2500);
expect(ariaLiveElement.textContent).toBe('Hello there');
}));

it('should remove the aria-live element from the DOM on destroy', fakeAsync(() => {
announcer.announce('Hey Google');

Expand Down
65 changes: 60 additions & 5 deletions src/cdk/a11y/live-announcer/live-announcer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,55 @@ export class LiveAnnouncer implements OnDestroy {

/**
* Announces a message to screenreaders.
* @param message Message to be announced to the screenreader
* @param politeness The politeness of the announcer element
* @param message Message to be announced to the screenreader.
* @returns Promise that will be resolved when the message is added to the DOM.
*/
announce(message: string, politeness: AriaLivePoliteness = 'polite'): Promise<void> {
this._liveElement.textContent = '';
announce(message: string): Promise<void>;

/**
* Announces a message to screenreaders.
* @param message Message to be announced to the screenreader.
* @param politeness The politeness of the announcer element.
* @returns Promise that will be resolved when the message is added to the DOM.
*/
announce(message: string, politeness?: AriaLivePoliteness): Promise<void>;

/**
* Announces a message to screenreaders.
* @param message Message to be announced to the screenreader.
* @param duration Time in milliseconds after which to clear out the announcer element. Note
* that this takes effect after the message has been added to the DOM, which can be up to
* 100ms after `announce` has been called.
* @returns Promise that will be resolved when the message is added to the DOM.
*/
announce(message: string, duration?: number): Promise<void>;

/**
* Announces a message to screenreaders.
* @param message Message to be announced to the screenreader.
* @param politeness The politeness of the announcer element.
* @param duration Time in milliseconds after which to clear out the announcer element. Note
* that this takes effect after the message has been added to the DOM, which can be up to
* 100ms after `announce` has been called.
* @returns Promise that will be resolved when the message is added to the DOM.
*/
announce(message: string, politeness?: AriaLivePoliteness, duration?: number): Promise<void>;

announce(message: string, ...args: any[]): Promise<void> {
let politeness: AriaLivePoliteness;
let duration: number;

if (args.length === 1 && typeof args[0] === 'number') {
duration = args[0];
} else {
[politeness, duration] = args;
}

this.clear();
clearTimeout(this._previousTimeout);

// TODO: ensure changing the politeness works on all environments we support.
this._liveElement.setAttribute('aria-live', politeness);
this._liveElement.setAttribute('aria-live', politeness! || 'polite');

// This 100ms timeout is necessary for some browser + screen-reader combinations:
// - Both JAWS and NVDA over IE11 will not announce anything without a non-zero timeout.
Expand All @@ -68,11 +108,26 @@ export class LiveAnnouncer implements OnDestroy {
this._previousTimeout = setTimeout(() => {
this._liveElement.textContent = message;
resolve();

if (typeof duration === 'number') {
this._previousTimeout = setTimeout(() => this.clear(), duration);
}
}, 100);
});
});
}

/**
* Clears the current text from the announcer element. Can be used to prevent
* screen readers from reading the text out again while the user is going
* through the page landmarks.
*/
clear() {
if (this._liveElement) {
this._liveElement.textContent = '';
}
}

ngOnDestroy() {
clearTimeout(this._previousTimeout);

Expand Down
4 changes: 4 additions & 0 deletions src/lib/snack-bar/snack-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ export class MatSnackBar implements OnDestroy {
if (this._openedSnackBarRef == snackBarRef) {
this._openedSnackBarRef = null;
}

if (config.announcementMessage) {
this._live.clear();
}
});

if (this._openedSnackBarRef) {
Expand Down