1 2/** 3 * Focus Trap 4 * 5 * This script provides a focus trap functionality for modal dialogs. 6 * 7 * @usage 8 * To use this focus trap, call `focusTrap.init()` with the selector of the modal dialog element. 9 * You can also provide an optional selector for a close button within the modal dialog. 10 * This will ensure that when the modal dialog is open, the focus remains within the dialog, 11 * cycling through the focusable elements and preventing focus from leaving the dialog. 12 * 13 * Example 01: 14 * focusTrap.init('#myModal', '.close-button'); 15 * This will initialize the focus trap for the modal dialog with the ID `myModal` 16 * and the close button with the class `close-button`. 17 * 18 * Example 02: 19 * focusTrap.init('#myModal'); 20 * Init the focus trap for the modal dialog with the ID `myModal` 21 * without a close button. 22 * gallery.listen('destroy', focusTrap.destroy); 23 * This will destroy the focus trap when the modal dialog is closed. 24 * Keep in mind, that there are a lot of different modal dialog implementations, 25 * so you might need to adapt the event registration. 26 * This example is for PhotoSwipe. 27 * 28 * Example 03: 29 * focusTrap.init('#headerSearch'); 30 * This will initialize the focus trap for the modal dialog with the ID `headerSearch`. 31 * focusTrap.setEscapeKeyListener(function (event) { 32 * $headersearch.collapse('hide'); 33 * }); 34 * This will set a custom listener for the Escape key, 35 * which will collapse the header search when the Escape key is pressed. 36 * 37 * Example 04: 38 * focusTrap.updateFocusableElements(); 39 * This will update the list of focusable elements within the modal dialog. 40 * Normally, you do not need to call this function, but it can be useful if the modal dialog content changes 41 * dynamically or if new focusable elements are added or removed. 42 * 43 */ 44(function () { 45 'use strict'; 46 47 const focusTrap = {}; 48 49 focusTrap.modalElement = null; 50 focusTrap.focusableElements = null; 51 focusTrap.firstElement = null; 52 focusTrap.lastElement = null; 53 focusTrap.closeButton = null; 54 focusTrap.escapeKeyListener = function () {}; 55 focusTrap.element = null; 56 57 58 /** 59 * Initializes the focus trap for a modal dialog. 60 * 61 * This function sets up the focus trap by 62 * finding all focusable elements within the modal dialog 63 * and adding an event listener for the keydown event. 64 * It ensures that when the Tab key is pressed, 65 * the focus remains within the modal dialog, 66 * cycling through the focusable elements. 67 * 68 * @param {String} elementSelector 69 * The selector for the modal dialog element. 70 * This should be a valid CSS selector 71 * that matches the modal dialog you want to trap focus within. 72 * @param {String} closeButtonSelector 73 * Optional selector for a close button within the modal dialog. 74 * If provided, clicking this button will destroy the focus trap. 75 * If you do not provide a close button selector, 76 * you will have to call `focusTrap.destroy()` manually. 77 * 78 * @returns {void} 79 */ 80 focusTrap.init = function (elementSelector, closeButtonSelector) { 81 focusTrap.modalElement = document.querySelector(elementSelector); 82 focusTrap.modalElement.addEventListener('keydown', focusTrap.handleTabKeyPress); 83 focusTrap.updateFocusableElements(); 84 85 if (closeButtonSelector) { 86 focusTrap.closeButton = focusTrap.modalElement.querySelector(closeButtonSelector); 87 if (focusTrap.closeButton) { 88 focusTrap.closeButton.addEventListener('click', focusTrap.destroy); 89 focusTrap.closeButton.focus(); 90 } 91 } 92 93 if (focusTrap.closeButton) { 94 focusTrap.closeButton.focus(); 95 } else { 96 focusTrap.firstElement.focus(); 97 } 98 } 99 100 /** 101 * Updates the list of focusable elements within the modal dialog. 102 * 103 * This function scans the modal dialog for all focusable elements and updates the `focusableElements`, 104 * `firstElement`, and `lastElement` properties. 105 * It should be called whenever the modal dialog content changes or when the focusable elements need to be refreshed. 106 * This is useful if the modal dialog content is dynamic or if new focu
106sable elements are added or removed. 107 * 108 * @returns {void} 109 */ 110 focusTrap.updateFocusableElements = function () { 111 focusTrap.focusableElements = []; 112 let el, parentEl, isFocusable; 113 let possibleElements = focusTrap.modalElement.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); 114 115 for (let i = 0; i < possibleElements.length; i++) { 116 el = possibleElements[i]; 117 parentEl = el; 118 isFocusable = true; 119 while (parentEl) { 120 // If the element is inside a hidden parent, skip it 121 if ( 122 window.getComputedStyle(parentEl, null).display === 'none' 123 || parentEl.style.display === 'none' 124 || parentEl.disabled 125 || window.getComputedStyle(parentEl, null).visibility === 'hidden' 126 || parentEl.style.visibility === 'hidden' 127 ) { 128 isFocusable = false; 129 break; 130 } 131 parentEl = parentEl.parentElement; 132 } 133 // Check if the element is visible and not disabled 134 if (isFocusable) { 135 focusTrap.focusableElements.push(el); 136 } 137 } 138 139 focusTrap.firstElement = focusTrap.focusableElements[0]; 140 focusTrap.lastElement = focusTrap.focusableElements[focusTrap.focusableElements.length - 1]; 141 } 142 143 /** 144 * Destroys the focus trap by removing the event listener and resetting the focus trap properties. 145 * 146 * This function should be called when the modal dialog is closed 147 * to clean up the focus trap state. 148 */ 149 focusTrap.destroy = function () { 150 if (focusTrap.modalElement) { 151 focusTrap.modalElement.removeEventListener('keydown', focusTrap.handleTabKeyPress); 152 } 153 if (focusTrap.closeButton) { 154 focusTrap.closeButton.removeEventListener('click', focusTrap.destroy); 155 } 156 157 focusTrap.closeButton = null; 158 focusTrap.modalElement = null; 159 focusTrap.focusableElements = null; 160 focusTrap.firstElement = null; 161 focusTrap.lastElement = null; 162 focusTrap.element = null; 163 focusTrap.escapeKeyListener = function () {}; 164 } 165 166 /** 167 * Handles the Tab key press to trap focus within the modal dialog. 168 * 169 * This function prevents the focus from leaving the modal dialog 170 * and cycles through the focusable elements. 171 * 172 * @param {KeyboardEvent} event - The keydown event triggered by pressing the Tab key. 173 * 174 * @returns {void} 175 */ 176 focusTrap.handleTabKeyPress = function (event) { 177 focusTrap.updateFocusableElements(); 178 179 if (event.key === 'Tab') { 180 let activeElem = document.activeElement; 181 182 if (event.shiftKey && activeElem.isEqualNode(focusTrap.firstElement)) { 183 event.preventDefault(); 184 focusTrap.lastElement.focus(); 185 return; 186 } 187 188 if (!event.shiftKey && activeElem.isEqualNode(focusTrap.lastElement)) { 189 event.preventDefault(); 190 focusTrap.firstElement.focus(); 191 } 192 } 193 194 if (event.key === 'Escape') { 195 event.preventDefault(); 196 focusTrap.escapeKeyListener(event); 197 } 198 } 199 200 /** 201 * Sets a custom listener for the Escape key. 202 * 203 * This function allows you to define a custom action 204 * when the Escape key is pressed while the focus trap is active. 205 * You can use this to close the modal dialog or perform any other action. 206 * 207 * @param {Function} callback 208 * The callback function to be executed when the Escape key is pressed. 209 * Takes the {KeyboardEvent} event as a parameter. 210 * 211 * @returns {void} 212 */ 213 focusTrap.setEscapeKeyListener = function (callback) { 214 if (typeof callback !== 'function') { 215 throw new Error('focusTrap.setEscapeKeyListener expects a function as an argument.'); 216 } 217 218 focusTrap.escapeKeyListener = callback; 219 }; 220 221 // Export the focusTrap object
222 window.focusTrap = focusTrap; 223})();
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.