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