1// https://github.com/cohesiondx8/accessible_modal_window - forked to here 2// @sitestudioexcludesonar 3;(function (w, doc, Drupal) { 4 'use strict'; 5 Drupal.behaviors.ARIAmodal = { 6 attach: function (context, settings) { 7 8 var ARIAmodal = {}; 9 w.ARIAmodal = ARIAmodal; 10 ARIAmodal.NS = 'ARIAmodal'; 11 ARIAmodal.AUTHOR = 'Scott O\'Hara'; 12 ARIAmodal.VERSION = '3.4.0'; 13 ARIAmodal.LICENSE = 'https://github.com/scottaohara/accessible_modal_window/blob/master/LICENSE'; 14 var activeClass = 'modal-open'; 15 var body = doc.body; 16 var main = doc.getElementsByTagName('main')[0] || body; 17 var modal = doc.querySelectorAll('[data-modal]'); 18 var children = doc.querySelectorAll('body > *:not([data-modal])'); 19 var initialTrigger; 20 var activeModal; 21 var useAriaModal = false; 22 var returnToBody = false; 23 var firstClass = 'js-first-focus'; 24 var lastClass = 'js-last-focus'; 25 var tabFocusElements = 'button:not([hidden]):not([disabled]), [href]:not([hidden]), input:not([hidden]):not([type="hidden"]):not([disabled]), select:not([hidden]):not([disabled]), textarea:not([hidden]):not([disabled]), [tabindex="0"]:not([hidden]):not([disabled]), summary:not([hidden]), [contenteditable]:not([hidden]), audio[controls]:not([hidden]), video[controls]:not([hidden])'; 26 27 ARIAmodal.getOnce = function(element, namespace) { 28 return element.dataset[namespace] || false; 29 }; 30 ARIAmodal.unsetOnce = function(element, namespace) { 31 element.dataset[namespace] = undefined; 32 }; 33 ARIAmodal.setOnce = function(element, namespace) { 34 element.dataset[namespace] = true; 35 }; 36 ARIAmodal.organizeDOM = function () { 37 var refEl = body.firstElementChild || null; 38 var i; 39 var self; 40 for (i = 0; i < modal.length; i += 1) { 41 42 self = modal[i]; 43 44 if(ARIAmodal.getOnce(self, 'modal')) { 45 continue; 46 } 47 ARIAmodal.setOnce(self, 'modal'); 48 49 body.insertBefore(modal[i], refEl) 50 } 51 }; 52 ARIAmodal.setupTrigger = function () { 53 var trigger = context.querySelectorAll('[data-modal-open]'); 54 var self; 55 var i; 56 for (i = 0; i < trigger.length; i += 1) { 57 self = trigger[i]; 58 59 // Skip if already set 60 if(ARIAmodal.getOnce(self, 'trigger')) { 61 continue; 62 } 63 64 ARIAmodal.setOnce(self, 'trigger'); 65 66 var getOpenTarget = self.getAttribute('data-modal-open'); 67 var hasHref = self.getAttribute('href'); 68 if (self.nodeName !== 'BUTTON') { 69 self.setAttribute('role', 'button'); 70 self.tabIndex = 0 71 } 72 if (getOpenTarget === '' && hasHref) { 73 self.setAttribute('data-modal-open', hasHref.split('#')[1]); 74 getOpenTarget = hasHref.split('#')[1] 75 } 76 self.removeAttribute('href'); 77 if (getOpenTarget) { 78 if (self.hasAttribute('disabled') && !self.hasAttribute('data-modal-disabled')) { 79 self.removeAttribute('disabled') 80 } 81 self.removeAttribute('hidden'); 82 self.addEventListener('click', ARIAmodal.openModal); 83 self.addEventListener('keydown', ARIAmodal.keyEvents, false) 84 } else { 85 console.warn('Missing target modal dialog - [data-modal-open="IDREF"]') 86 } 87 } 88 }; 89 ARIAmodal.setupModal = function () { 90 var self; 91 var i; 92 for (i = 0; i < modal.length; i += 1) { 93 self = modal[i]; 94 // Skip if already set 95 if(ARIAmodal.getOnce(self, 'setupModal')) { 96 continue; 97 } 98 ARIAmodal.setOnce(self, 'setupModal'); 99 100 var modalType = self.getAttribute('data-modal'); 101 var getClass = self.getAttribute('data-modal-class') || 'a11y-modal'; 102 var heading = self.querySelector('h1, h2, h3, h4, h5, h6'); 103 var modalLabel = self.getAttribute('data-modal-label'); 104 var hideHeading = self.hasAttribute('data-modal-hide-heading');
105 var modalDesc = self.querySelector('[data-modal-description]'); 106 var modalDoc = self.querySelector('[data-modal-document]'); 107 if (modalType === 'alert') { 108 self.setAttribute('role', 'alertdialog') 109 } else { 110 self.setAttribute('role', 'dialog') 111 } 112 self.classList.add(getClass); 113 self.hidden = true; 114 self.tabIndex = '-1'; 115 if (modalDoc) { 116 modalDoc.setAttribute('role', 'document') 117 } 118 ARIAmodal.setupModalCloseBtn(self, getClass, modalType); 119 if (self.hasAttribute('data-aria-modal')) { 120 self.setAttribute('aria-modal', 'true') 121 } 122 if (modalDesc) { 123 modalDesc.id = modalDesc.id || 'md_desc_' + Math.floor(Math.random() * 999) + 1; 124 self.setAttribute('aria-describedby', modalDesc.id) 125 } 126 if (modalLabel) { 127 self.setAttribute('aria-label', modalLabel) 128 } else { 129 if (heading) { 130 var makeHeading = self.id + '_heading'; 131 heading.classList.add(getClass + '__heading'); 132 heading.id = makeHeading; 133 self.setAttribute('aria-labelledby', makeHeading); 134 if (heading.hasAttribute('data-autofocus')) { 135 heading.tabIndex = '-1' 136 } 137 } else { 138 console.warn('Dialogs should have their purpose conveyed by a heading element (h1).') 139 } 140 } 141 if (hideHeading) { 142 self.querySelector('#' + heading.id).classList.add('at-only') 143 } 144 var focusable = self.querySelectorAll(tabFocusElements); 145 focusable[0].classList.add(firstClass); 146 focusable[focusable.length - 1].classList.add(lastClass) 147 148 149 } 150 }; 151 ARIAmodal.setupModalCloseBtn = function (self, modalClass, modalType) { 152 var doNotGenerate = self.hasAttribute('data-modal-manual-close'); 153 var manualClose = self.querySelectorAll('[data-modal-close-btn]'); 154 var modalClose = self.getAttribute('data-modal-close'); 155 var modalCloseClass = self.getAttribute('data-modal-close-class'); 156 var closeIcon = '<span data-modal-x></span>'; 157 var btnClass = modalClass; 158 var i; 159 if (!doNotGenerate) { 160 if (manualClose.length < 2) { 161 var closeBtn = doc.createElement('button'); 162 closeBtn.type = 'button'; 163 self.classList.add(modalClass); 164 closeBtn.classList.add(modalClass + '__close-btn'); 165 if (!modalClose && modalType !== 'alert') { 166 closeBtn.innerHTML = closeIcon; 167 closeBtn.setAttribute('aria-label', 'Close'); 168 closeBtn.classList.add('is-icon-btn') 169 } else { 170 closeBtn.innerHTML = modalClose; 171 if (modalCloseClass) { 172 closeBtn.classList.add(modalCloseClass) 173 } 174 } 175 if (modalType !== 'alert') { 176 if (self.querySelector('[role="document"]')) { 177 self.querySelector('[role="document"]').appendChild(closeBtn) 178 } else { 179 self.appendChild(closeBtn) 180 } 181 } 182 closeBtn.addEventListener('click', ARIAmodal.closeModal) 183 } 184 } 185 for (i = 0; i < manualClose.length; i += 1) { 186 manualClose[i].addEventListener('click', ARIAmodal.closeModal) 187 } 188 doc.addEventListener('keydown', ARIAmodal.keyEvents, false) 189 }; 190 ARIAmodal.openModal = function (e, autoOpen) { 191 var i; 192 var getTargetModal = autoOpen || this.getAttribute('data-modal-open'); 193 activeModal = doc.getElementById(getTargetModal); 194 var focusTarget = activeModal; 195 var getAutofocus = activeModal.querySelector('[autofocus]') || activeModal.querySelector('[data-autofocus]'); 196 var setInert = activeModal.hasAttribute('data-coh-modal-overlay'); 197 useAriaModal = activeModal.hasAttribute('aria-modal'); 198 if (autoOpen) { 199 returnToBody = true 200 } 201 if (!autoOpen) { 202 e.preventDefault(); 203 initialTrigger = this.id 204 } 205 if (getAutofocus) { 206 focusTarget = getAutofocus 207 } else if (activeModal.hasAttribute('data-modal-close-focus')) { 208 focusTarget = activeModal.querySelector('[class*="close-btn"]') 209 } 210 if (!body.classList.contains(activeClass)) { 211 body.classList.add(activeClass); 212 213 if(setInert) { 214 for (i = 0; i < children.length; i += 1) { 215 if (!useAriaModal) { 216 if (children[i].hasAttribute('aria-hidden')) { 217 children[i].setAttribute('data-keep-hidden', children[i].getAttribute('aria-hidden')) 218 } 219 children[i].setAttribute('aria-hidden', 'true') 220 } 221 if (children[i].getAttribute('inert')) { 222 children[i].setAttribute('data-keep-inert', '') 223 } else { 224 children[i].setAttribute('inert', 'true') 225 } 226 } 227 } 228 } else { 229 console.warn('It is not advised to open dialogs from within other dialogs. Instead consider replacing the contents of this dialog with new content. Or providing a stepped, or tabbed interface within this dialog.') 230 } 231 activeModal.removeAttribute('hidden'); 232 requestAnimationFrame(function () {
233 focusTarget.focus() 234 }); 235 236 return [initialTrigger, activeModal, returnToBody] 237 }; 238 ARIAmodal.closeModal = function (e) { 239 var trigger = doc.getElementById(initialTrigger) || null; 240 var i; 241 var m; 242 for (i = 0; i < children.length; i += 1) { 243 if (!children[i].hasAttribute('data-keep-inert')) { 244 children[i].removeAttribute('inert') 245 } 246 children[i].removeAttribute('data-keep-inert'); 247 if (children[i].getAttribute('data-keep-hidden')) { 248 children[i].setAttribute('aria-hidden', children[i].getAttribute('data-keep-hidden')) 249 } else { 250 children[i].removeAttribute('aria-hidden') 251 } 252 children[i].removeAttribute('data-keep-hidden') 253 } 254 body.classList.remove(activeClass); 255 for (m = 0; m < modal.length; m += 1) { 256 if (!modal[m].hasAttribute('hidden')) { 257 modal[m].hidden = true 258 } 259 } 260 if (trigger !== null) { 261 trigger.focus() 262 } else { 263 if (main && !returnToBody) { 264 main.tabIndex = -1; 265 main.focus() 266 } else { 267 body.tabIndex = -1; 268 body.focus() 269 } 270 } 271 initialTrigger = undefined; 272 activeModal = undefined; 273 returnToBody = false; 274 return [initialTrigger, activeModal, returnToBody] 275 }; 276 ARIAmodal.keyEvents = function (e) { 277 var keyCode = e.keyCode || e.which; 278 var escKey = 27; 279 var enterKey = 13; 280 var spaceKey = 32; 281 var tabKey = 9; 282 if (e.target.hasAttribute('data-modal-open')) { 283 switch (keyCode) { 284 case enterKey: 285 case spaceKey: 286 e.preventDefault(); 287 e.target.click(); 288 break 289 } 290 } 291 if (body.classList.contains(activeClass)) { 292 switch (keyCode) { 293 case escKey: 294 ARIAmodal.closeModal(); 295 break; 296 default: 297 break 298 } 299 if (body.classList.contains(activeClass)) { 300 var firstFocus = activeModal.querySelector('.' + firstClass); 301 var lastFocus = activeModal.querySelector('.' + lastClass) 302 } 303 if (doc.activeElement.classList.contains(lastClass)) { 304 if (keyCode === tabKey && !e.shiftKey) { 305 e.preventDefault(); 306 firstFocus.focus() 307 } 308 } 309 if (doc.activeElement.classList.contains(firstClass)) { 310 if (keyCode === tabKey && e.shiftKey) { 311 e.preventDefault(); 312 lastFocus.focus() 313 } 314 } 315 } 316 }; 317 318 ARIAmodal.autoLoad = function () { 319 var getAuto = context.querySelectorAll('[data-modal-auto]'); 320 321 // Skip if already set 322 if(getAuto.length <= 0 || ARIAmodal.getOnce(getAuto[0], 'autoload')) { 323 return; 324 } 325 326 ARIAmodal.setOnce(getAuto[0], 'autoload'); 327 328 var hashValue = w.location.hash || null; 329 var autoOpen; 330 var useHash = false; 331 var e = null; 332 if (hashValue !== null) { 333 autoOpen = hashValue.split('#')[1]; 334 if (autoOpen === '') { 335 return false 336 } else if (autoOpen === '!null') { 337 return false 338 } else { 339 var checkforDialog = context.getElementById(autoOpen) || null; 340 if (checkforDialog !== null) { 341 if (checkforDialog.getAttribute('role') === 'dialog' || checkforDialog.getAttribute('role') === 'alertdialog') { 342 useHash = true 343 } 344 } 345 } 346 } 347 if (useHash) { 348 ARIAmodal.openModal(e, autoOpen); 349 if (getAuto.length > 1) { 350 console.warn('Only the modal indicated by the hash value will load.') 351 } 352 } else if (getAuto.length !== 0) { 353 if (getAuto[0].getAttribute('role') === 'dialog' || getAuto[0].getAttribute('role') === 'alertdialog') { 354 autoOpen = getAuto[0].id; 355 ARIAmodal.openModal(e, autoOpen); 356 if (getAuto.length > 1) { 357 console.warn('Multiple modal dialogs can not auto load.') 358 } 359 } else if (getAuto[0].getAttribute('role') === 'button' || getAuto[0].tagName === 'BUTTON') { 360 autoOpen = getAuto[0].id; 361 getAuto[0].click() 362 } 363 } 364 if (getAuto.length !== 0 && !doc.getElementById(autoOpen).hasAttribute('data-modal-auto-persist')) { 365 w.location.replace("#!null") 366 } 367 }; 368 ARIAmodal.init = function () { 369 ARIAmodal.organizeDOM(); 370 ARIAmodal.setupTrigger(); 371 ARIAmodal.setupModal(); 372 ARIAmodal.autoLoad() 373 }; 374 375 376 ARIAmodal.init() 377 } 378 } 379})(window, document, Drupal);
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.