PageSourceSearch

https://contemporaryfusionreviews.com/wp-content/plugins/my-calend…ssible-modal-window-aria.js?ver=3.5.2135205

js contemporaryfusionreviews.com collected 2026-09-24 18:22:43 UTC 17,588 bytes, 411 lines download raw bytes

1/**
2 * ES2015 accessible modal window system, using ARIA
3 *
4 * Forked from van11y.net
5 *
6 * Website: https://van11y.net/accessible-modal/
7 * License MIT: https://github.com/nico3333fr/van11y-accessible-modal-window-aria/blob/master/LICENSE
8 */
9'use strict';
10
11(function (doc) {
12
13  'use strict';
14
15  var MODAL_JS_CLASS = 'js-modal';
16  var MODAL_ID_PREFIX = 'label_modal_';
17  var MODAL_CLASS_SUFFIX = 'modal';
18  var MODAL_DATA_BACKGROUND_ATTR = 'data-modal-background-click';
19  var MODAL_PREFIX_CLASS_ATTR = 'data-modal-prefix-class';
20  var MODAL_TEXT_ATTR = 'data-modal-text';
21  var MODAL_CONTENT_ID_ATTR = 'data-modal-content-id';
22  var MODAL_DESCRIBEDBY_ID_ATTR = 'data-modal-describedby-id';
23  var MODAL_TITLE_ATTR = 'data-modal-title';
24  var MODAL_FOCUS_TO_ATTR = 'data-modal-focus-toid';
25  var MODAL_CLOSE_TEXT_ATTR = 'data-modal-close-text';
26  var MODAL_ROLE = 'dialog';
27
28  var MODAL_BUTTON_CLASS_SUFFIX = 'modal-close';
29  var MODAL_BUTTON_JS_ID = 'js-modal-close';
30  var MODAL_BUTTON_JS_CLASS = 'js-modal-close';
31  var MODAL_BUTTON_CONTENT_BACK_ID = 'data-content-back-id';
32  var MODAL_BUTTON_FOCUS_BACK_ID = 'data-focus-back';
33
34  var MODAL_WRAPPER_CLASS_SUFFIX = 'modal__wrapper';
35  var MODAL_CONTENT_CLASS_SUFFIX = 'modal__content';
36  var MODAL_CONTENT_JS_ID = 'js-modal-content';
37
38  var MODAL_CLOSE_TEXT_CLASS_SUFFIX = 'modal-close__text';
39
40  var MODAL_TITLE_ID = 'modal-title';
41  var MODAL_TITLE_CLASS_SUFFIX = 'modal-title';
42
43  var FOCUSABLE_ELEMENTS_STRING = "a[href], area[href], input:not([type='hidden']):not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]";
44  var WRAPPER_PAGE_JS = 'js-modal-page';
45
46  var MODAL_JS_ID = 'js-modal';
47
48  var MODAL_OVERLAY_ID = 'js-modal-overlay';
49  var MODAL_OVERLAY_CLASS_SUFFIX = 'modal-overlay';
50  var MODAL_OVERLAY_TXT = 'Close modal';
51  var MODAL_OVERLAY_BG_ENABLED_ATTR = 'data-background-click';
52
53  var VISUALLY_HIDDEN_CLASS = 'invisible';
54  var NO_SCROLL_CLASS = 'mc-no-scroll';
55
56  var ATTR_ROLE = 'role';
57  var ATTR_OPEN = 'open';
58  var ATTR_LABELLEDBY = 'aria-labelledby';
59  var ATTR_DESCRIBEDBY = 'aria-describedby';
60  var ATTR_HIDDEN = 'aria-hidden';
61  //const ATTR_MODAL = 'aria-modal="true"';
62  var ATTR_HASPOPUP = 'aria-haspopup';
63  var ATTR_HASPOPUP_VALUE = 'dialog';
64
65  var findById = function findById(id) {
66    return doc.getElementById(id);
67  };
68
69  var addClass = function addClass(el, className) {
70    if (el.classList) {
71      el.classList.add(className); // IE 10+
72    } else {
73        el.className += ' ' + className; // IE 8+
74      }
75  };
76
77  var removeClass = function removeClass(el, className) {
78    if (el.classList) {
79      el.classList.remove(className); // IE 10+
80    } else {
81        el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); // IE 8+
82      }
83  };
84
85  var hasClass = function hasClass(el, className) {
86    if (el.classList) {
87      return el.classList.contains(className); // IE 10+
88    } else {
89        return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className); // IE 8+ ?
90      }
91  };
92  /*const wrapInner = (el, wrapper_el) => { // doesn't work on IE/Edge, f…
93      while (el.firstChild)
94          wrapper_el.append(el.firstChild);
95      el.append(wrapper_el);
96   }*/
97  function wrapInner(parent, wrapper) {
98    if (typeof wrapper === "string") wrapper = document.createElement(wrapper);
99
100    parent.appendChild(wrapper);
101
102    while (parent.firstChild !== wrapper) wrapper.appendChild(parent.firstChild);
103  }
104
105  function remove(el) {
106    /* node.remove() is too modern for IE≤11 */
107    el.parentNode.removeChild(el);
108  }
109
110  /* gets an element el, search if it is child of parent class, returns id of the parent */
111  var searchParent = function searchParent(el, parentClass) {
112    var found = false;
113    var parentElement = el.parentNode;
114    while (parentElement && found === false) {
115      if (hasClass(parentElement, parentClass) === true) {
116        found = true;
117      } else {
118        parentElement = parentElement.parentNode;
119      }
120    }
121    if (found === true) {
122      return parentElement.getAttribute('id');
123    } else {
124      return '';
125    }
126  };
127
128  /**
129   * Create the template for an overlay
130   * @param  {Object} config
131   * @return {String}
132   */
133  var createOverlay = function createOverlay(config) {
134
135    var id = MODAL_OVERLAY_ID;
136    var overlayText = config.text || MODAL_OVERLAY_TXT;
137    var overlayClass = config.prefixClass + MODAL_OVERLAY_CLASS_SUFFIX;
138    var overlayBackgroundEnabled = config.backgroundEnabled === 'disabled' ? 'disabled' : 'enabled';
139
140    return '<span\n                    id="' + id + '"\n                    class="' + overlayClass + '"\n                    ' + MODAL_OVERLAY_BG_ENABLED_ATTR + '="' + overlayBackgroundEnabled + '"\n                    title="' + overlayText + '"\n                    >\n                    <span class="' + VISUALLY_HIDDEN_CLASS + '">' + overlayText + '</span>\n                  </span>';
141  };
142
143  /**
144   * Create the template for a modal
145   * @param  {Object} config
146   * @return {String}
147   */
148  var createModal = function createModal(config) {
149	var dialog = document.createElement( 'dialog' );
150	var outerContent = document.createElement( 'div' );
151	var innerContent = document.createElement( 'div' );
152	var contentContainer = document.createElement( 'div' );
153	contentContainer.setAttribute( 'id', MODAL_CONTENT_JS_ID );
154
155	var id = MODAL_JS_ID;
156    var modalClassName = config.modalPrefixClass + MODAL_CLASS_SUFFIX;
157    var modalClassWrapper = config.modalPrefixClass + MODAL_WRAPPER_CLASS_SUFFIX;
158    var buttonCloseClassName = config.modalPrefixClass + MODAL_BUTTON_CLASS_SUFFIX;
159    var buttonCloseInner = '<span class="' + config.modalPrefixClass + MODAL_CLOSE_TEXT_CLASS_SUFFIX + '">\n                                          ' + config.modalCloseText + '\n                                         </span>';
160    var contentClassName = config.modalPrefixClass + MODAL_CONTENT_CLASS_SUFFIX;
161    var titleClassName = config.modalPrefixClass + MODAL_TITLE_CLASS_SUFFIX;
162    var title = config.modalTitle !== '' ? '<div class="js-modal-title-container"><h1 id="' + MODAL_TITLE_ID + '" class="' + titleClassName + '">\n                                          ' + config.modalTitle + '\n                                         </h1></div>' : '';
163    var button_close = '<button type="button" class="' + MODAL_BUTTON_JS_CLASS + ' ' + buttonCloseClassName + '" id="' + MODAL_BUTTON_JS_ID + '" ' + MODAL_BUTTON_CONTENT_BACK_ID + '="' + config.modalContentId + '" ' + MODAL_BUTTON_FOCUS_BACK_ID + '="' + config.modalFocusBackId + '"><span class="dashicons dashicons-no" aria-hidden="true"></span>\n                               ' + buttonCloseInner + '\n                              </button>';
164    var content = config.modalText;
165
166    // If there is no content but an id we try to fetch content id
167    if (content === '' && config.modalContentId) {
168      var contentFromId = findById(config.modalContentId);
169      if (contentFromId) {
170		contentContainer.insertAdjacentElement( 'beforeEnd', contentFromId );
171      }
172    } else {
173		contentContainer.insertAdjacentHTML( 'beforeEnd', content );
174	}
175	dialog.setAttribute( 'id', id );
176	dialog.classList.add( modalClassName );
177	dialog.setAttribute( ATTR_ROLE, MODAL_ROLE );
178	dialog.setAttribute( ATTR_DESCRIBEDBY, config.modalDescribedById + ' ' + ATTR_OPEN );
179	dialog.setAttribute( ATTR_LABELLEDBY, MODAL_TITLE_ID );
180	outerContent.setAttribute( 'role', 'document' );
181	outerContent.classList.add( modalClassWrapper );
182	outerContent.insertAdjacentHTML( 'afterBegin', button_close );
183	innerContent.classList.add( contentClassName );
184	innerContent.insertAdjacentHTML( 'afterBegin', title );
185	innerContent.insertAdjacentElement( 'beforeEnd', contentContainer );
186	outerContent.insertAdjacentElement( 'afterBegin', innerContent );
187	dialog.insertAdjacentElement( 'afterBegin', outerContent );
188
189	return dialog;
190  };
191
192  var closeModal = function closeModal(config) {
193
194    if (config.modalFocusBackId !== '') {
195		var modalReturn = findById( config.modalFocusBackId );
196		var modalReturnContainer = modalReturn.closest( '.mc-events' ); // only works when control is inside container.
197		if (modalReturnContainer ) {
198			modalReturnContainer.insertAdjacentElement( 'beforeEnd', config.modalContent.firstChild );
199		}
200	}
201
202	remove(config.modal);
203    remove(config.overlay);
204
205    if (config.modalFocusBackId) {
206      var contentFocus = findById(config.modalFocusBackId);
207      if (contentFocus) {
208        contentFocus.focus();
209      }
210    }
211  };
212
213  /** Find all modals inside a container
214   * @param  {Node} node Default document
215   * @return {Array}
216   */
217  var $listModals = function $listModals() {
218    var node = arguments.length <= 0 || arguments[0] === undefined ? doc : arguments[0];
219    return [].slice.call(node.querySelectorAll('.' + MODAL_JS_CLASS));
220  };
221
222  /**
223   * Build modals for a container
224   * @param  {Node} node
225   */
226  var attach = function attach(node) {
227    var addListeners = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];
228
229    $listModals(node).forEach(function (modal_node) {
230
231      var iLisible = Math.random().toString(32).slice(2, 12);
232      var wrapperBody = findById(WRAPPER_PAGE_JS);
233      var body = doc.querySelector('body');
234
235      modal_node.setAttribute('id', MODAL_ID_PREFIX + iLisible);
236      modal_node.setAttribute(ATTR_HASPOPUP, ATTR_HASPOPUP_VALUE);
237
238      if (wrapperBody === null || wrapperBody.length === 0) {
239        var wrapper = doc.createElement('DIV');
240        wrapper.setAttribute('id', WRAPPER_PAGE_JS);
241        wrapInner(body, wrapper);
242		wrapperBody = wrapper;
243      }
244    });
245
246    if (addListeners) {
247
248      /* listeners */
249      ['click', 'keydown'].forEach(function (eventName) {
250
251        doc.body.addEventListener(eventName, function (e) {
252
253          // click on link modal
254          var parentModalLauncher = searchParent(e.target, MODAL_JS_CLASS);
255          if ((hasClass(e.target, MODAL_JS_CLASS) === true || parentModalLauncher !== '') && eventName === 'click') {
256            var body = doc.querySelector('body');
257            var modalLauncher = parentModalLauncher !== '' ? findById(parentModalLauncher) : e.target;
258            var modalPrefixClass = modalLauncher.hasAttribute(MODAL_PREFIX_CLASS_ATTR) === true ? modalLauncher.getAttribute(MODAL_PREFIX_CLASS_ATTR) + '-' : '';
259            var modalText = modalLauncher.hasAttribute(MODAL_TEXT_ATTR) === true ? modalLauncher.getAttribute(MODAL_TEXT_ATTR) : '';
260            var modalContentId = modalLauncher.hasAttribute(MODAL_CONTENT_ID_ATTR) === true ? modalLauncher.getAttribute(MODAL_CONTENT_ID_ATTR) : '';
261            var modalDescribedById = modalLauncher.hasAttribute(MODAL_DESCRIBEDBY_ID_ATTR) === true ? modalLauncher.getAttribute(MODAL_DESCRIBEDBY_ID_ATTR) : '';
262            var modalTitle = modalLauncher.hasAttribute(MODAL_TITLE_ATTR) === true ? modalLauncher.getAttribute(MODAL_TITLE_ATTR) : '';
263            var modalCloseText = modalLauncher.hasAttribute(MODAL_CLOSE_TEXT_ATTR) === true ? modalLauncher.getAttribute(MODAL_CLOSE_TEXT_ATTR) : MODAL_OVERLAY_TXT;
264            var backgroundEnabled = modalLauncher.hasAttribute(MODAL_DATA_BACKGROUND_ATTR) === true ? modalLauncher.getAttribute(MODAL_DATA_BACKGROUND_ATTR) : '';
265            var modalGiveFocusToId = modalLauncher.hasAttribute(MODAL_FOCUS_TO_ATTR) === true ? modalLauncher.getAttribute(MODAL_FOCUS_TO_ATTR) : '';
266
267            var wrapperBody = findById(WRAPPER_PAGE_JS);
268
269            // insert overlay
270            body.insertAdjacentHTML('beforeEnd', createOverlay({
271              text: modalCloseText,
272              backgroundEnabled: backgroundEnabled,
273              prefixClass: modalPrefixClass
274            }));
275
276			// insert modal
277            body.insertAdjacentElement('beforeEnd', createModal({
278              modalText: modalText,
279              modalPrefixClass: modalPrefixClass,
280              backgroundEnabled: modalContentId,
281              modalTitle: modalTitle,
282              modalCloseText: modalCloseText,
283              modalCloseTitle: modalCloseText,
284              modalContentId: modalContentId,
285              modalDescribedById: modalDescribedById,
286              modalFocusBackId: modalLauncher.getAttribute('id')
287            }));
288
289            // hide page
290            wrapperBody.setAttribute(ATTR_HIDDEN, 'true');
291
292            // add class noscroll to body
293            addClass(body, NO_SCROLL_CLASS);
294
295            // give focus to close button or specified element
296            var closeButton = findById(MODAL_BUTTON_JS_ID);
297            if (modalGiveFocusToId !== '') {
298              var focusTo = findById(modalGiveFocusToId);
299              if (focusTo) {
300                focusTo.focus();
301              } else {
302                closeButton.focus();
303              }
304            } else {
305              closeButton.focus();
306            }
307
308            e.preventDefault();
309          }
310
311          // click on close button or on overlay not blocked
312          var parentButton = searchParent(e.target, MODAL_BUTTON_JS_CLASS);
313          if ((e.target.getAttribute('id') === MODAL_BUTTON_JS_ID || parentButton !== '' || e.target.getAttribute('id') === MODAL_OVERLAY_ID || hasClass(e.target, MODAL_BUTTON_JS_CLASS) === true) && eventName === 'click') {
314            var body = doc.querySelector('body');
315            var wrapperBody = findById(WRAPPER_PAGE_JS);
316            var modal = findById(MODAL_JS_ID);
317            var modalContent = findById(MODAL_CONTENT_JS_ID) ? findById(MODAL_CONTENT_JS_ID) : '';
318            var overlay = findById(MODAL_OVERLAY_ID);
319            var modalButtonClose = findById(MODAL_BUTTON_JS_ID);
320            var modalFocusBackId = modalButtonClose.getAttribute(MODAL_BUTTON_FOCUS_BACK_ID);
321            var contentBackId = modalButtonClose.getAttribute(MODAL_BUTTON_CONTENT_BACK_ID);
322            var backgroundEnabled = overlay.getAttribute(MODAL_OVERLAY_BG_ENABLED_ATTR);
323
324            if (!(e.target.getAttribute('id') === MODAL_OVERLAY_ID && backgroundEnabled === 'disabled')) {
325
326              closeModal({
327                modal: modal,
328                modalContent: modalContent,
329                overlay: overlay,
330                modalFocusBackId: modalFocusBackId,
331                contentBackId: contentBackId,
332                backgroundEnabled: backgroundEnabled,
333                fromId: e.target.getAttribute('id')
334              });
335
336              // show back page
337              wrapperBody.removeAttribute(ATTR_HIDDEN);
338
339              // remove class noscroll to body
340              removeClass(body, NO_SCROLL_CLASS);
341            }
342          }
343
344          // strike a key when modal opened
345          if (findById(MODAL_JS_ID) && eventName === 'keydown') {
346            var body = doc.querySelector('body');
347            var wrapperBody = findById(WRAPPER_PAGE_JS);
348            var modal = findById(MODAL_JS_ID);
349            var modalContent = findById(MODAL_CONTENT_JS_ID) ? findById(MODAL_CONTENT_JS_ID) : '';
350            var overlay = findById(MODAL_OVERLAY_ID);
351            var modalButtonClose = findById(MODAL_BUTTON_JS_ID);
352            var modalFocusBackId = modalButtonClose.getAttribute(MODAL_BUTTON_FOCUS_BACK_ID);
353            var contentBackId = modalButtonClose.getAttribute(MODAL_BUTTON_CONTENT_BACK_ID);
354            var listFocusables = [].slice.call(modal.querySelectorAll(FOCUSABLE_ELEMENTS_STRING));
355
356            // esc
357            if (e.keyCode === 27) {
358
359              closeModal({
360                modal: modal,
361                modalContent: modalContent,
362                overlay: overlay,
363                modalFocusBackId: modalFocusBackId,
364                contentBackId: contentBackId
365              });
366
367              // show back page
368              wrapperBody.removeAttribute(ATTR_HIDDEN);
369
370              // remove class noscroll to body
371              removeClass(body, NO_SCROLL_CLASS);
372            }
373
374            // tab or Maj Tab in modal => capture focus
375            if (e.keyCode === 9 && listFocusables.indexOf(e.target) >= 0) {
376
377              // maj-tab on first element focusable => focus on last
378              if (e.shiftKey) {
379                if (e.target === listFocusables[0]) {
380                  listFocusables[listFocusables.length - 1].focus();
381                  e.preventDefault();
382                }
383              } else {
384                // tab on last element focusable => focus on first
385                if (e.target === listFocusables[listFocusables.length - 1]) {
386                  listFocusables[0].focus();
387                  e.preventDefault();
388                }
389              }
390            }
391
392            // tab outside modal => put it in focus
393            if (e.keyCode === 9 && listFocusables.indexOf(e.target) === -1) {
394              e.preventDefault();
395              listFocusables[0].focus();
396            }
397          }
398        }, true);
399      });
400    }
401  };
402
403  var onLoad = function onLoad() {
404    attach();
405    document.removeEventListener('DOMContentLoaded', onLoad);
406  };
407
408  document.addEventListener('DOMContentLoaded', onLoad);
409
410  window.accessibleModalWindowAria = attach;
411})(document);

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.