forked from angular/material
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
update(autocomplete): provide proper accessibility
* Removes the static messages, which should be detected by the screenreaders * Introduces a Screenreader Announcer service (as in Material 2 - angular/components#238) * Service can be used for other components as well (e.g Toast, Tooltip) Fixes angular#9603.
- Loading branch information
1 parent
1b9245a
commit f525dd4
Showing
5 changed files
with
276 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/** | ||
* @ngdoc module | ||
* @name material.core.liveannouncer | ||
* @description | ||
* Angular Material Live Announcer to provide accessibility for Voice Readers. | ||
*/ | ||
angular | ||
.module('material.core') | ||
.service('$mdLiveAnnouncer', MdLiveAnnouncer); | ||
|
||
/** | ||
* @ngdoc service | ||
* @name $mdLiveAnnouncer | ||
* @module material.core.liveannouncer | ||
* | ||
* @description | ||
* | ||
* Service to announce messages to supported screenreaders. | ||
* | ||
* > The `$mdLiveAnnouncer` service is internally used for components to provide proper accessibility. | ||
* | ||
* <hljs lang="js"> | ||
* module.controller('AppCtrl', function($mdLiveAnnouncer) { | ||
* // Basic announcement (Polite Mode) | ||
* $mdLiveAnnouncer.announce('Hey Google'); | ||
* | ||
* // Custom announcement (Assertive Mode) | ||
* $mdLiveAnnouncer.announce('Hey Google', 'assertive'); | ||
* }); | ||
* </hljs> | ||
* | ||
*/ | ||
function MdLiveAnnouncer($timeout) { | ||
/** @private @const @type {!angular.$timeout} */ | ||
this._$timeout = $timeout; | ||
|
||
/** @private @const @type {!HTMLElement} */ | ||
this._liveElement = this._createLiveElement(); | ||
|
||
/** @private @const @type {!number} */ | ||
this._announceTimeout = 100; | ||
} | ||
|
||
/** | ||
* @ngdoc method | ||
* @name $mdLiveAnnouncer#announce | ||
* @description Announces messages to supported screenreaders. | ||
* @param {string} message Message to be announced to the screenreader | ||
* @param {'off'|'polite'|'assertive'} politeness The politeness of the announcer element. | ||
*/ | ||
MdLiveAnnouncer.prototype.announce = function(message, politeness) { | ||
if (!politeness) { | ||
politeness = 'polite'; | ||
} | ||
|
||
var self = this; | ||
|
||
self._liveElement.textContent = ''; | ||
self._liveElement.setAttribute('aria-live', politeness); | ||
|
||
// 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. | ||
// - With Chrome and IE11 with NVDA or JAWS, a repeated (identical) message won't be read a | ||
// second time without clearing and then using a non-zero delay. | ||
// (using JAWS 17 at time of this writing). | ||
self._$timeout(function() { | ||
self._liveElement.textContent = message; | ||
}, self._announceTimeout, false); | ||
}; | ||
|
||
/** | ||
* Creates a live announcer element, which listens for DOM changes and announces them | ||
* to the screenreaders. | ||
* @returns {!HTMLElement} | ||
* @private | ||
*/ | ||
MdLiveAnnouncer.prototype._createLiveElement = function() { | ||
var liveEl = document.createElement('div'); | ||
|
||
liveEl.classList.add('md-visually-hidden'); | ||
liveEl.setAttribute('role', 'status'); | ||
liveEl.setAttribute('aria-atomic', 'true'); | ||
liveEl.setAttribute('aria-live', 'polite'); | ||
|
||
document.body.appendChild(liveEl); | ||
|
||
return liveEl; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
describe('$mdLiveAnnouncer', function() { | ||
|
||
var $mdLiveAnnouncer, $timeout = null; | ||
var liveEl = null; | ||
|
||
beforeEach(module('material.core')); | ||
|
||
beforeEach(inject(function ($injector) { | ||
$mdLiveAnnouncer = $injector.get('$mdLiveAnnouncer'); | ||
$timeout = $injector.get('$timeout'); | ||
|
||
liveEl = $mdLiveAnnouncer._liveElement; | ||
})); | ||
|
||
it('should correctly update the announce text', function() { | ||
$mdLiveAnnouncer.announce('Hey Google'); | ||
|
||
expect(liveEl.textContent).toBe(''); | ||
|
||
$timeout.flush(); | ||
|
||
expect(liveEl.textContent).toBe('Hey Google'); | ||
}); | ||
|
||
it('should correctly update the politeness attribute', function() { | ||
$mdLiveAnnouncer.announce('Hey Google', 'assertive'); | ||
|
||
$timeout.flush(); | ||
|
||
expect(liveEl.textContent).toBe('Hey Google'); | ||
expect(liveEl.getAttribute('aria-live')).toBe('assertive'); | ||
}); | ||
|
||
it('should apply the aria-live value polite by default', function() { | ||
$mdLiveAnnouncer.announce('Hey Google'); | ||
|
||
$timeout.flush(); | ||
|
||
expect(liveEl.textContent).toBe('Hey Google'); | ||
expect(liveEl.getAttribute('aria-live')).toBe('polite'); | ||
}); | ||
|
||
it('should have proper aria attributes to be detected', function() { | ||
expect(liveEl.getAttribute('aria-atomic')).toBe('true'); | ||
expect(liveEl.getAttribute('role')).toBe('status'); | ||
}); | ||
|
||
}); |