118 lines
4.2 KiB
JavaScript
118 lines
4.2 KiB
JavaScript
/**
|
|
* @file
|
|
* Adds an HTML element and method to trigger audio UAs to read system messages.
|
|
*
|
|
* Use {@link Drupal.announce} to indicate to screen reader users that an
|
|
* element on the page has changed state. For instance, if clicking a link
|
|
* loads 10 more items into a list, one might announce the change like this.
|
|
*
|
|
* @example
|
|
* $('#search-list')
|
|
* .on('itemInsert', function (event, data) {
|
|
* // Insert the new items.
|
|
* $(data.container.el).append(data.items.el);
|
|
* // Announce the change to the page contents.
|
|
* Drupal.announce(Drupal.t('@count items added to @container',
|
|
* {'@count': data.items.length, '@container': data.container.title}
|
|
* ));
|
|
* });
|
|
*/
|
|
|
|
(function (Drupal, debounce) {
|
|
let liveElement;
|
|
const announcements = [];
|
|
|
|
/**
|
|
* Builds a div element with the aria-live attribute and add it to the DOM.
|
|
*
|
|
* @type {Drupal~behavior}
|
|
*
|
|
* @prop {Drupal~behaviorAttach} attach
|
|
* Attaches the behavior for drupalAnnounce.
|
|
*/
|
|
Drupal.behaviors.drupalAnnounce = {
|
|
attach(context) {
|
|
// Create only one aria-live element.
|
|
if (!liveElement) {
|
|
liveElement = document.createElement('div');
|
|
liveElement.id = 'drupal-live-announce';
|
|
liveElement.className = 'visually-hidden';
|
|
liveElement.setAttribute('aria-live', 'polite');
|
|
liveElement.setAttribute('aria-busy', 'false');
|
|
document.body.appendChild(liveElement);
|
|
}
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Concatenates announcements to a single string; appends to the live region.
|
|
*/
|
|
function announce() {
|
|
const text = [];
|
|
let priority = 'polite';
|
|
let announcement;
|
|
|
|
// Create an array of announcement strings to be joined and appended to the
|
|
// aria live region.
|
|
const il = announcements.length;
|
|
for (let i = 0; i < il; i++) {
|
|
announcement = announcements.pop();
|
|
text.unshift(announcement.text);
|
|
// If any of the announcements has a priority of assertive then the group
|
|
// of joined announcements will have this priority.
|
|
if (announcement.priority === 'assertive') {
|
|
priority = 'assertive';
|
|
}
|
|
}
|
|
|
|
if (text.length) {
|
|
// Clear the liveElement so that repeated strings will be read.
|
|
liveElement.innerHTML = '';
|
|
// Set the busy state to true until the node changes are complete.
|
|
liveElement.setAttribute('aria-busy', 'true');
|
|
// Set the priority to assertive, or default to polite.
|
|
liveElement.setAttribute('aria-live', priority);
|
|
// Print the text to the live region. Text should be run through
|
|
// Drupal.t() before being passed to Drupal.announce().
|
|
liveElement.innerHTML = text.join('\n');
|
|
// The live text area is updated. Allow the AT to announce the text.
|
|
liveElement.setAttribute('aria-busy', 'false');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggers audio UAs to read the supplied text.
|
|
*
|
|
* The aria-live region will only read the text that currently populates its
|
|
* text node. Replacing text quickly in rapid calls to announce results in
|
|
* only the text from the most recent call to {@link Drupal.announce} being
|
|
* read. By wrapping the call to announce in a debounce function, we allow for
|
|
* time for multiple calls to {@link Drupal.announce} to queue up their
|
|
* messages. These messages are then joined and append to the aria-live region
|
|
* as one text node.
|
|
*
|
|
* @param {string} text
|
|
* A string to be read by the UA.
|
|
* @param {string} [priority='polite']
|
|
* A string to indicate the priority of the message. Can be either
|
|
* 'polite' or 'assertive'.
|
|
*
|
|
* @return {function}
|
|
* The return of the call to debounce.
|
|
*
|
|
* @see http://www.w3.org/WAI/PF/aria-practices/#liveprops
|
|
*/
|
|
Drupal.announce = function (text, priority) {
|
|
// Save the text and priority into a closure variable. Multiple simultaneous
|
|
// announcements will be concatenated and read in sequence.
|
|
announcements.push({
|
|
text,
|
|
priority,
|
|
});
|
|
// Immediately invoke the function that debounce returns. 200 ms is right at
|
|
// the cusp where humans notice a pause, so we will wait
|
|
// at most this much time before the set of queued announcements is read.
|
|
return debounce(announce, 200)();
|
|
};
|
|
})(Drupal, Drupal.debounce);
|