1/* 2 * This content is licensed according to the W3C Software License at 3 * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document 4 */ 5 6"use strict"; 7 8var aria = aria || {}; 9 10aria.Utils = aria.Utils || {}; 11 12(function () { 13 /* 14 * When util functions move focus around, set this true so the focus listener 15 * can ignore the events. 16 */ 17 aria.Utils.IgnoreUtilFocusChanges = false; 18 19 aria.Utils.dialogOpenClass = "has-dialog"; 20 21 /** 22 * @description Set focus on descendant nodes until the first focusable element is 23 * found. 24 * @param element 25 * DOM node for which to find the first focusable descendant. 26 * @returns {boolean} 27 * true if a focusable element is found and focus is set. 28 */ 29 aria.Utils.focusFirstDescendant = function (element) { 30 for (var i = 0; i < element.childNodes.length; i++) { 31 var child = element.childNodes[i]; 32 if ( 33 aria.Utils.attemptFocus(child) || 34 aria.Utils.focusFirstDescendant(child) 35 ) { 36 return true; 37 } 38 } 39 return false; 40 }; // end focusFirstDescendant 41 42 /** 43 * @description Find the last descendant node that is focusable. 44 * @param element 45 * DOM node for which to find the last focusable descendant. 46 * @returns {boolean} 47 * true if a focusable element is found and focus is set. 48 */ 49 aria.Utils.focusLastDescendant = function (element) { 50 for (var i = element.childNodes.length - 1; i >= 0; i--) { 51 var child = element.childNodes[i]; 52 if ( 53 aria.Utils.attemptFocus(child) || 54 aria.Utils.focusLastDescendant(child) 55 ) { 56 return true; 57 } 58 } 59 return false; 60 }; // end focusLastDescendant 61 62 /** 63 * @description Set Attempt to set focus on the current node. 64 * @param element 65 * The node to attempt to focus on. 66 * @returns {boolean} 67 * true if element is focused. 68 */ 69 aria.Utils.attemptFocus = function (element) { 70 if (!aria.Utils.isFocusable(element)) { 71 return false; 72 } 73 74 aria.Utils.IgnoreUtilFocusChanges = true; 75 try { 76 element.focus(); 77 } catch (e) { 78 // continue regardless of error 79 } 80 aria.Utils.IgnoreUtilFocusChanges = false; 81 return document.activeElement === element; 82 }; // end attemptFocus 83 84 /* Modals can open modals. Keep track of them with this array. */ 85 aria.OpenDialogList = aria.OpenDialogList || new Array(0); 86 87 /** 88 * @returns {object} the last opened dialog (the current dialog) 89 */ 90 aria.getCurrentDialog = function () { 91 if (aria.OpenDialogList && aria.OpenDialogList.length) { 92 return aria.OpenDialogList[aria.OpenDialogList.length - 1]; 93 } 94 }; 95 96 aria.closeCurrentDialog = function () { 97 var currentDialog = aria.getCurrentDialog(); 98 if (currentDialog) { 99 currentDialog.close(); 100 return true; 101 } 102 103 return false; 104 }; 105 106 aria.handleEscape = function (event) { 107 var key = event.which || event.keyCode; 108 109 if (key === aria.KeyCode.ESC && aria.closeCurrentDialog()) { 110 event.stopPropagation(); 111 } 112 }; 113 114 document.addEventListener("keyup", aria.handleEscape); 115 116 /** 117 * @class 118 * @description Dialog object providing modal focus management. 119 * 120 * Assumptions: The element serving as the dialog container is present in the 121 * DOM and hidden. The dialog container has role='dialog'. 122 * @param dialogId 123 * The ID of the element serving as the dialog container. 124 * @param focusAfterClosed 125 * Either the DOM node or the ID of the DOM node to focus when the 126 * dialog closes. 127 * @param focusFirst 128 * Optional parameter containing either the DOM node or the ID of the 129 * DOM node to focus when the dialog opens. If not specified, the 130 * first focusable element in the dialog will receive focus. 131 */ 132 aria.Dialog = function (dialogId, focusAfterClosed, focusFirst) { 133 this.dialogNode = document.getElementById(dialogId); 134 if (this.dialogNode === null) { 135 throw new Error('No element found with id="' + dialogId + '".'); 136 } 137 138 var validRoles = ["dialog", "alertdialog"]; 139 var isDialog = (this.dialogNode.getAttribute("role") || "") 140 .trim() 141 .split(/\s+/g) 142 .some(function (token) { 143 return validRoles.some(function (role) { 144 return token === role; 145 }); 146 }); 147 if (!isDialog) { 148 throw new Error( 149 "Dialog() requires a DOM element with ARIA role of dialog or alertdialog." 150 ); 151 } 152 153 // Wrap in an individual backdrop element if one doesn't exist 154 // Native <dialog> elements use the ::backdrop pseudo-element, which 155 // works similarly. 156 var backdropClass = "dialog-backdrop"; 157 if (this.dialogNode.parentNode.classList.contains(backdropClass)) { 158 this.backdropNode = this.dialogNode.parentNode; 159 } else { 160 this.backdropNode = document.createElement("div"); 161 this.backdropNode.className = backdropClass; 162 this.dialogNode.parentNode.insertBefore( 163 this.backdropNode, 164 this.dialogNode 165 ); 166 this.backdropNode.appendChild(this.dialogNode); 167 } 168 this.backdropNode.classList.add("active"); 169 170 // Disable scroll on the body element 171 document.body.classList.add(aria.Utils.dialogOpenClass); 172 173 if (typeof focusAfterClosed === "string") { 174 this.focusAfterClosed = document.getElementById(focusAfterClosed); 175 } else if (typeof focusAfterClosed === "object") { 176 this.focusAfterClosed = focusAfterClosed; 177 } else { 178 throw new Error( 179 "the focusAfterClosed parameter is required for the aria.Dialog constructor." 180 ); 181 } 182 183 if (typeof focusFirst === "string") { 184 this.focusFirst = document.getElementById(focusFirst); 185 } else if (typeof focusFirst === "object") { 186 this.focusFirst = focusFirst; 187 } else { 188 this.focusFirst = null; 189 } 190 191 // Bracket the dialog node with two invisible, focusable nodes. 192 // While this dialog is open, we use these to make sure that focus never 193 // leaves the document even if dialogNode is the first or last node. 194 var preDiv = document.createElement("div"); 195 this.preNode = this.dialogNode.parentNode.insertBefore( 196 preDiv, 197 this.dialogNode 198 ); 199 this.preNode.tabIndex = 0; 200 var postDiv = document.createElement("div"); 201 this.postNode = this.dialogNode.parentNode.insertBefore( 202 postDiv, 203 this.dialogNode.nextSibling 204 ); 205 this.postNode.tabIndex = 0; 206 207 // If this modal is opening on top of one that is already open, 208 // get rid of the document focus listener of the open dialog. 209 if (aria.OpenDialogList.length > 0) { 210 aria.getCurrentDialog().removeListeners(); 211 } 212 213 this.addListeners(); 214 aria.OpenDialogList.push(this); 215 this.clearDialog(); 216 this.dialogNode.className = "default_dialog"; // make visible 217 218 if (this.focusFirst) { 219 this.focusFirst.focus(); 220 } else { 221 aria.Utils.focusFirstDescendant(this.dialogNode); 222 } 223 224 this.lastFocus = document.activeElement; 225 }; // end Dialog constructor 226 227 aria.Dialog.prototype.clearDialog = function () { 228 Array.prototype.map.call( 229 this.dialogNode.querySelectorAll("input"), 230 function (input) { 231 input.value = ""; 232 } 233 ); 234 }; 235 236 /** 237 * @description 238 * Hides the current top dialog, 239 * removes listeners of the top dialog, 240 * restore listeners of a parent dialog if one was open under the one that just closed, 241 * and sets focus on the element specified for focusAfterClosed. 242 */ 243 aria.Dialog.prototype.close = function () { 244 aria.OpenDialogList.pop(); 245 this.removeListeners(); 246 aria.Utils.remove(this.preNode); 247 aria.Utils.remove(this.postNode); 248 this.dialogNode.className = "hidden"; 249 this.backdropNode.classList.remove("active"); 250 this.focusAfterClosed.focus(); 251 252 // If a dialog was open underneath this one, restore its listeners. 253 if (aria.OpenDialogList.length > 0) { 254 aria.getCurrentDialog().addListeners(); 255 } else { 256 document.body.classList.remove(aria.Utils.dialogOpenClass); 257 } 258 }; // end close 259 260 /** 261 * @description 262 * Hides the current dialog and replaces it with another. 263 * @param newDialogId 264 * ID of the dialog that will replace the currently open top dialog. 265 * @param newFocusAfterClosed 266 * Optional ID or DOM node specifying where to place focus when the new dialog closes. 267 * If not specified, focus will be placed on the element specified by the dialog being replaced. 268 * @param newFocusFirst 269 * Optional ID or DOM node specifying where to place focus in the new dialog when it opens. 270 * If not specified, the first focusable element will receive focus. 271 */ 272 aria.Dialog.prototype.replace = function ( 273 newDialogId, 274 newFocusAfterClosed, 275 newFocusFirst 276 ) { 277 aria.OpenDialogList.pop(); 278 this.removeListeners(); 279 aria.Utils.remove(this.preNode); 280 aria.Utils.remove(this.postNode); 281 this.dialogNode.className = "hidden"; 282 this.backdropNode.classList.remove("active"); 283 284 var focusAfterClosed = newFocusAfterClosed || this.focusAfterClosed; 285 new aria.Dialog(newDialogId, focusAfterClosed, newFocusFirst); 286 }; // end replace 287 288 aria.Dialog.prototype.addListeners = function () { 289 document.addEventListener("focus", this.trapFocus, true); 290 }; // end addListeners 291 292 aria.Dialog.prototype.removeListeners = function () { 293 document.removeEventListener("focus", this.trapFocus, true); 294 }; // end removeListeners 295 296 aria.Dialog.prototype.trapFocus = function (event) { 297 if (aria.Utils.IgnoreUtilFocusChanges) { 298 return; 299 } 300 var currentDialog = aria.getCurrentDialog(); 301 if (currentDialog.dialogNode.contains(event.target)) { 302 currentDialog.lastFocus = event.target; 303 } else { 304 aria.Utils.focusFirstDescendant(currentDialog.dialogNode); 305 if (currentDialog.lastFocus == document.activeElement) { 306 aria.Utils.focusLastDescendant(currentDialog.dialogNode); 307 } 308 currentDialog.lastFocus = document.activeElement; 309 } 310 }; // end trapFocus 311 312 window.openDialog = function (dialogId, focusAfterClosed, focusFirst) { 313 new aria.Dialog(dialogId, focusAfterClosed, focusFirst); 314 }; 315 316 window.closeDialog = function (closeButton) { 317 var topDialog = aria.getCurrentDialog(); 318 if (topDialog.dialogNode.contains(closeButton)) { 319 topDialog.close(); 320 } 321 }; // end closeDialog 322 323 window.replaceDialog = function ( 324 newDialogId, 325 newFocusAfterClosed, 326 newFocusFirst 327 ) { 328 var topDialog = aria.getCurrentDialog(); 329 if (topDialog.dialogNode.contains(document.activeElement)) { 330 topDialog.replace(newDialogId, newFocusAfterClosed, newFocusFirst); 331 } 332 }; // end replaceDialog 333})(); 334("use strict"); 335/** 336 * @namespace aria 337 */ 338 339var aria = aria || {}; 340 341/** 342 * @description 343 * Key code constants 344 */ 345aria.KeyCode = { 346 BACKSPACE: 8, 347 TAB: 9, 348 RETURN: 13, 349 SHIFT: 16, 350 ESC: 27, 351 SPACE: 32, 352 PAGE_UP: 33, 353 PAGE_DOWN: 34, 354 END: 35, 355 HOME: 36, 356 LEFT: 37, 357 UP: 38, 358 RIGHT: 39, 359 DOWN: 40, 360 DELETE: 46, 361}; 362 363aria.Utils = aria.Utils || {}; 364 365// Polyfill src https://developer.mozilla.org/en-US/docs/Web/API/Element/matches 366aria.Utils.matches = function (element, selector) { 367 if (!Element.prototype.matches) { 368 Element.prototype.matches = 369 Element.prototype.matchesSelector || 370 Element.prototype.mozMatchesSelector || 371 Element.prototype.msMatchesSelector || 372 Element.prototype.oMatchesSelector || 373 Element.prototype.webkitMatchesSelector || 374 function (s) { 375 var matches = element.parentNode.querySelectorAll(s); 376 var i = matches.length; 377 while (--i >= 0 && matches.item(i) !== this) { 378 // empty 379 } 380 return i > -1; 381 }; 382 } 383 384 return element.matches(selector); 385}; 386 387aria.Utils.remove = function (item) { 388 if (item.remove && typeof item.remove === "function") { 389 return item.remove(); 390 } 391 if ( 392 item.parentNode && 393 item.parentNode.removeChild && 394 typeof item.parentNode.removeChild === "function" 395 ) { 396 return item.parentNode.removeChild(item); 397 } 398 return false;
399}; 400 401aria.Utils.isFocusable = function (element) { 402 if (element.tabIndex < 0) { 403 return false; 404 } 405 406 if (element.disabled) { 407 return false; 408 } 409 410 switch (element.nodeName) { 411 case "A": 412 return !!element.href && element.rel != "ignore"; 413 case "INPUT": 414 return element.type != "hidden"; 415 case "BUTTON": 416 case "SELECT": 417 case "TEXTAREA": 418 return true; 419 default: 420 return false; 421 } 422}; 423 424aria.Utils.getAncestorBySelector = function (element, selector) { 425 if (!aria.Utils.matches(element, selector + " " + element.tagName)) { 426 // Element is not inside an element that matches selector 427 return null; 428 } 429 430 // Move up the DOM tree until a parent matching the selector is found 431 var currentNode = element; 432 var ancestor = null; 433 while (ancestor === null) { 434 if (aria.Utils.matches(currentNode.parentNode, selector)) { 435 ancestor = currentNode.parentNode; 436 } else { 437 currentNode = currentNode.parentNode; 438 } 439 } 440 441 return ancestor; 442}; 443 444aria.Utils.hasClass = function (element, className) { 445 return new RegExp("(\\s|^)" + className + "(\\s|$)").test(element.className); 446}; 447 448aria.Utils.addClass = function (element, className) { 449 if (!aria.Utils.hasClass(element, className)) { 450 element.className += " " + className; 451 } 452}; 453 454aria.Utils.removeClass = function (element, className) { 455 var classRegex = new RegExp("(\\s|^)" + className + "(\\s|$)"); 456 element.className = element.className.replace(classRegex, " ").trim(); 457}; 458 459aria.Utils.bindMethods = function (object /* , ...methodNames */) { 460 var methodNames = Array.prototype.slice.call(arguments, 1); 461 methodNames.forEach(function (method) { 462 object[method] = object[method].bind(object); 463 }); 464};
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.