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*/ 5function a11ytabsInit($el, onChangeCallBackFunc = null) { 6 var tablist = $el; 7 if(!tablist) return; 8 var tabs; 9 var panels; 10 var delay = determineDelay(); 11 var isAfterInit = false; 12 var defaultTab; 13 14 generateArrays(); 15 16 function generateArrays () { 17 tabs = tablist.querySelector('[role="tablist"]').querySelectorAll(':scope > [role="tab"]'); 18 panels = tablist.querySelectorAll(':scope > [role="tabpanel"]'); 19 defaultTab = Array.from(tabs).find((tab) => tab.getAttribute('aria-selected') === 'true'); 20 }; 21 22 // For easy reference 23 var keys = { 24 end: 35, 25 home: 36, 26 left: 37, 27 up: 38, 28 right: 39, 29 down: 40, 30 delete: 46 31 }; 32 33 // Add or substract depending on key pressed 34 var direction = { 35 37: -1, 36 38: -1, 37 39: 1, 38 40: 1 39 }; 40 41 // Bind listeners 42 for (i = 0; i < tabs.length; ++i) { 43 addListeners(i); 44 }; 45 46 function addListeners (index) { 47 $(tabs[index]).on('click', clickEventListener); 48 $(tabs[index]).on('keydown', keydownEventListener); 49 $(tabs[index]).on('keyup', keyupEventListener); 50 51 // Build an array with all tabs (<button>s) in it 52 tabs[index].index = index; 53 }; 54 activateTab(defaultTab ? defaultTab : tabs[0], false); 55 isAfterInit = true; 56 57 // When a tab is clicked, activateTab is fired to activate it 58 function clickEventListener (event) { 59 event.preventDefault(); 60 var tab = event.target; 61 activateTab(tab, false); 62 }; 63 64 // Handle keydown on tabs 65 function keydownEventListener (event) { 66 var key = event.keyCode; 67 68 switch (key) { 69 case keys.end: 70 event.preventDefault(); 71 // Activate last tab 72 activateTab(tabs[tabs.length - 1]); 73 break; 74 case keys.home: 75 event.preventDefault(); 76 // Activate first tab 77 activateTab(tabs[0]); 78 break; 79 80 // Up and down are in keydown 81 // because we need to prevent page scroll >:) 82 case keys.up: 83 case keys.down: 84 determineOrientation(event); 85 break; 86 }; 87 }; 88 89 // Handle keyup on tabs 90 function keyupEventListener (event) { 91 var key = event.keyCode; 92 93 switch (key) { 94 case keys.left: 95 case keys.right: 96 determineOrientation(event); 97 break; 98 case keys.delete: 99 determineDeletable(event); 100 break; 101 }; 102 }; 103 104 // When a tablistâs aria-orientation is set to vertical, 105 // only up and down arrow should function. 106 // In all other cases only left and right arrow function. 107 function determineOrientation (event) { 108 var key = event.keyCode; 109 var vertical = tablist.getAttribute('aria-orientation') == 'vertical'; 110 var proceed = false; 111 if (vertical) { 112 if (key === keys.up || key === keys.down) { 113 event.preventDefault(); 114 proceed = true; 115 }; 116 } 117 else { 118 if (key === keys.left || key === keys.right) { 119 proceed = true; 120 }; 121 }; 122 123 if (proceed) { 124 switchTabOnArrowPress(event); 125 }; 126 }; 127 128 // Either focus the next, previous, first, or last tab 129 // depening on key pressed 130 function switchTabOnArrowPress (event) { 131 var pressed = event.keyCode; 132 133 for (x = 0; x < tabs.length; x++) { 134 tabs[x].addEventListener('focus', focusEventHandler); 135 }; 136 137 if (direction[pressed]) { 138 var target = event.target; 139 if (target.index !== undefined) { 140 if (tabs[target.index + direction[pressed]]) { 141 tabs[target.index + direction[pressed]].focus(); 142 } 143 else if (pressed === keys.left || pressed === keys.up) { 144 focusLastTab(); 145 } 146 else if (pressed === keys.right || pressed == keys.down) { 147 focusFirstTab(); 148 }; 149 }; 150 }; 151 }; 152 153 // Activates any given tab panel 154 function activateTab (tab, setFocus) { 155 // Deactivate all other tabs 156 deactivateTabs();
157 var setFocus = setFocus === undefined ? true : setFocus; 158 159 // Remove tabindex attribute 160 tab.removeAttribute('tabindex'); 161 162 // Set the tab as selected 163 tab.setAttribute('aria-selected', 'true') 164 tab.classList.add('is-active'); 165 166 // Get the value of aria-controls (which is an ID) 167 var controls = tab.getAttribute('aria-controls'); 168 169 // Remove hidden attribute from tab panel to make it visible 170 document.getElementById(controls).removeAttribute('hidden'); 171 172 if(onChangeCallBackFunc !== null && isAfterInit) { 173 onChangeCallBackFunc(tab, controls); 174 } 175 176 // Set focus when required 177 if (setFocus) { 178 tab.focus(); 179 }; 180 }; 181 182 // Deactivate all tabs and tab panels 183 function deactivateTabs () { 184 for (t = 0; t < tabs.length; t++) { 185 tabs[t].setAttribute('tabindex', '-1'); 186 tabs[t].setAttribute('aria-selected', 'false') 187 tabs[t].classList.remove('is-active'); 188 tabs[t].removeEventListener('focus', focusEventHandler); 189 }; 190 191 for (p = 0; p < panels.length; p++) { 192 panels[p].setAttribute('hidden', 'hidden'); 193 }; 194 }; 195 196 // Make a guess 197 function focusFirstTab () { 198 tabs[0].focus(); 199 }; 200 201 // Make a guess 202 function focusLastTab () { 203 tabs[tabs.length - 1].focus(); 204 }; 205 206 // Detect if a tab is deletable 207 function determineDeletable (event) { 208 target = event.target; 209 210 if (target.getAttribute('data-deletable') !== null) { 211 // Delete target tab 212 deleteTab(event, target); 213 214 // Update arrays related to tabs widget 215 generateArrays(); 216 217 // Activate the closest tab to the one that was just deleted 218 if (target.index - 1 < 0) { 219 activateTab(tabs[0]); 220 } 221 else { 222 activateTab(tabs[target.index - 1]); 223 }; 224 }; 225 }; 226 227 // Deletes a tab and its panel 228 function deleteTab (event) { 229 var target = event.target; 230 var panel = document.getElementById(target.getAttribute('aria-controls')); 231 232 target.parentElement.removeChild(target); 233 panel.parentElement.removeChild(panel); 234 }; 235 236 // Determine whether there should be a delay 237 // when user navigates with the arrow keys 238 function determineDelay () { 239 var hasDelay = tablist.hasAttribute('data-delay'); 240 var delay = 0; 241 242 if (hasDelay) { 243 var delayValue = tablist.getAttribute('data-delay'); 244 if (delayValue) { 245 delay = delayValue; 246 } 247 else { 248 // If no value is specified, default to 300ms 249 delay = 300; 250 }; 251 }; 252 253 return delay; 254 }; 255 256 // 257 function focusEventHandler (event) { 258 var target = event.target; 259 260 setTimeout(checkTabFocus, delay, target); 261 }; 262 263 // Only activate tab on focus if it still has focus after the delay 264 function checkTabFocus (target) { 265 focused = document.activeElement; 266 267 if (target === focused) { 268 activateTab(target, false); 269 }; 270 }; 271 272};
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.