1/** 2 * @file 3 * Adds an HTML element and method to trigger audio UAs to read system messages. 4 * 5 * Use {@link Drupal.announce} to indicate to screen reader users that an 6 * element on the page has changed state. For instance, if clicking a link 7 * loads 10 more items into a list, one might announce the change like this. 8 * 9 * @example 10 * $('#search-list') 11 * .on('itemInsert', function (event, data) { 12 * // Insert the new items. 13 * $(data.container.el).append(data.items.el); 14 * // Announce the change to the page contents. 15 * Drupal.announce(Drupal.t('@count items added to @container', 16 * {'@count': data.items.length, '@container': data.container.title} 17 * )); 18 * }); 19 */ 20 21(function (Drupal, debounce) { 22 let liveElement; 23 const announcements = []; 24 25 /** 26 * Builds a div element with the aria-live attribute and add it to the DOM. 27 * 28 * @type {Drupal~behavior} 29 * 30 * @prop {Drupal~behaviorAttach} attach 31 * Attaches the behavior for drupalAnnounce. 32 */ 33 Drupal.behaviors.drupalAnnounce = { 34 attach(context) { 35 // Create only one aria-live element. 36 if (!liveElement) { 37 liveElement = document.createElement('div'); 38 liveElement.id = 'drupal-live-announce'; 39 liveElement.className = 'visually-hidden'; 40 liveElement.setAttribute('aria-live', 'polite'); 41 liveElement.setAttribute('aria-busy', 'false'); 42 document.body.appendChild(liveElement); 43 } 44 }, 45 }; 46 47 /** 48 * Concatenates announcements to a single string; appends to the live region. 49 */ 50 function announce() { 51 const text = []; 52 let priority = 'polite'; 53 let announcement; 54 55 // Create an array of announcement strings to be joined and appended to the 56 // aria live region. 57 const il = announcements.length; 58 for (let i = 0; i < il; i++) { 59 announcement = announcements.pop(); 60 text.unshift(announcement.text); 61 // If any of the announcements has a priority of assertive then the group 62 // of joined announcements will have this priority. 63 if (announcement.priority === 'assertive') { 64 priority = 'assertive'; 65 } 66 } 67 68 if (text.length) { 69 // Clear the liveElement so that repeated strings will be read. 70 liveElement.innerHTML = ''; 71 // Set the busy state to true until the node changes are complete. 72 liveElement.setAttribute('aria-busy', 'true'); 73 // Set the priority to assertive, or default to polite. 74 liveElement.setAttribute('aria-live', priority); 75 // Print the text to the live region. Text should be run through 76 // Drupal.t() before being passed to Drupal.announce(). 77 liveElement.innerHTML = text.join('\n'); 78 // The live text area is updated. Allow the AT to announce the text. 79 liveElement.setAttribute('aria-busy', 'false'); 80 } 81 } 82 83 /** 84 * Triggers audio UAs to read the supplied text. 85 * 86 * The aria-live region will only read the text that currently populates its 87 * text node. Replacing text quickly in rapid calls to announce results in 88 * only the text from the most recent call to {@link Drupal.announce} being 89 * read. By wrapping the call to announce in a debounce function, we allow for 90 * time for multiple calls to {@link Drupal.announce} to queue up their 91 * messages. These messages are then joined and append to the aria-live region 92 * as one text node. 93 * 94 * @param {string} text 95 * A string to be read by the UA. 96 * @param {string} [priority='polite'] 97 * A string to indicate the priority of the message. Can be either 98 * 'polite' or 'assertive'. 99 * 100 * @return {function} 101 * The return of the call to debounce. 102 * 103 * @see https://www.w3.org/WAI/PF/aria-practices/#liveprops 104 */ 105 Drupal.announce = function (text, priority) { 106 // Save the text and priority into a closure variable. Multiple simultaneous 107 // announcements will be concatenated and read in sequence. 108 announcements.push({ 109 text, 110 priority, 111 }); 112 // Immediately invoke the function that debounce returns. 200 ms is right at 113 // the cusp where humans notice a pause, so we will wait 114 // at most this much time before the set of queued announcements is read. 115 return debounce(announce, 200)(); 116 }; 117})(Drupal, Drupal.debounce);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.