1(function (root, factory) { 2 if (typeof define === "function" && define.amd) { 3 define([], function () { 4 return factory(root); 5 }); 6 } else if (typeof exports === "object") { 7 module.exports = factory(root); 8 } else { 9 root.Tabby = factory(root); 10 } 11})( 12 typeof global !== "undefined" 13 ? global 14 : typeof window !== "undefined" 15 ? window 16 : this, 17 function (window) { 18 "use strict"; 19 20 // 21 // Variables 22 // 23 24 var defaults = { 25 idPrefix: "tabby-toggle_", 26 default: "[data-tabby-default]", 27 }; 28 29 // 30 // Methods 31 // 32 33 /** 34 * Merge two or more objects together. 35 * @param {Object} objects The objects to merge together 36 * @returns {Object} Merged values of defaults and options 37 */ 38 var extend = function () { 39 var merged = {}; 40 Array.prototype.forEach.call(arguments, function (obj) { 41 for (var key in obj) { 42 if (!obj.hasOwnProperty(key)) return; 43 merged[key] = obj[key]; 44 } 45 }); 46 return merged; 47 }; 48 49 /** 50 * Emit a custom event 51 * @param {String} type The event type 52 * @param {Node} tab The tab to attach the event to 53 * @param {Node} details Details about the event 54 */ 55 var emitEvent = function (tab, details) { 56 // Create a new event 57 var event; 58 if (typeof window.CustomEvent === "function") { 59 event = new CustomEvent("tabby", { 60 bubbles: true, 61 cancelable: true, 62 detail: details, 63 }); 64 } else { 65 event = document.createEvent("CustomEvent"); 66 event.initCustomEvent("tabby", true, true, details); 67 } 68 69 // Dispatch the event 70 tab.dispatchEvent(event); 71 }; 72 73 var focusHandler = function (event) { 74 toggle(event.target); 75 }; 76 77 var getKeyboardFocusableElements = function (element) { 78 return [ 79 ...element.querySelectorAll( 80 'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])' 81 ), 82 ].filter( 83 (el) => !el.hasAttribute("disabled") && !el.getAttribute("aria-hidden") 84 ); 85 }; 86 87 /** 88 * Remove roles and attributes from a tab and its content 89 * @param {Node} tab The tab 90 * @param {Node} content The tab content 91 * @param {Object} settings User settings and options 92 */ 93 var destroyTab = function (tab, content, settings) { 94 // Remove the generated ID 95 if (tab.id.slice(0, settings.idPrefix.length) === settings.idPrefix) { 96 tab.id = ""; 97 } 98 99 // remove event listener 100 tab.removeEventListener("focus", focusHandler, true); 101 102 // Remove roles 103 tab.removeAttribute("role"); 104 tab.removeAttribute("aria-controls"); 105 tab.removeAttribute("aria-selected"); 106 tab.removeAttribute("tabindex"); 107 tab.closest("li").removeAttribute("role"); 108 content.removeAttribute("role"); 109 content.removeAttribute("aria-labelledby"); 110 content.removeAttribute("hidden"); 111 }; 112 113 /** 114 * Add the required roles and attributes to a tab and its content 115 * @param {Node} tab The tab 116 * @param {Node} content The tab content 117 * @param {Object} settings User settings and options 118 */ 119 var setupTab = function (tab, content, settings) { 120 // Give tab an ID if it doesn't already have one 121 if (!tab.id) { 122 tab.id = settings.idPrefix + content.id; 123 } 124 125 // Add roles 126 tab.setAttribute("role", "tab"); 127 tab.setAttribute("aria-controls", content.id); 128 tab.closest("li").setAttribute("role", "presentation"); 129 content.setAttribute("role", "tabpanel"); 130 content.setAttribute("aria-labelledby", tab.id); 131 132 // Add selected state 133 if (tab.matches(settings.default)) { 134 tab.setAttribute("aria-selected", "true"); 135 } else { 136 tab.setAttribute("aria-selected", "false"); 137 content.setAttribute("hidden", "hidden"); 138 } 139
140 // add focus event listender 141 tab.addEventListener("focus", focusHandler); 142 }; 143 144 /** 145 * Hide a tab and its content 146 * @param {Node} newTab The new tab that's replacing it 147 */ 148 var hide = function (newTab) { 149 // Variables 150 var tabGroup = newTab.closest('[role="tablist"]'); 151 if (!tabGroup) return {}; 152 var tab = tabGroup.querySelector('[role="tab"][aria-selected="true"]'); 153 if (!tab) return {}; 154 var content = document.querySelector(tab.hash); 155 156 // Hide the tab 157 tab.setAttribute("aria-selected", "false"); 158 159 // Hide the content 160 if (!content) return { previousTab: tab }; 161 content.setAttribute("hidden", "hidden"); 162 163 // Return the hidden tab and content 164 return { 165 previousTab: tab, 166 previousContent: content, 167 }; 168 }; 169 170 /** 171 * Show a tab and its content 172 * @param {Node} tab The tab 173 * @param {Node} content The tab content 174 */ 175 var show = function (tab, content) { 176 tab.setAttribute("aria-selected", "true"); 177 content.removeAttribute("hidden"); 178 tab.focus(); 179 }; 180 181 /** 182 * Toggle a new tab 183 * @param {Node} tab The tab to show 184 */ 185 var toggle = function (tab) { 186 // Make sure there's a tab to toggle and it's not already active 187 if (!tab || tab.getAttribute("aria-selected") == "true") return; 188 189 // Variables 190 var content = document.querySelector(tab.hash); 191 if (!content) return; 192 193 // Hide active tab and content 194 var details = hide(tab); 195 196 // Show new tab and content 197 show(tab, content); 198 199 // Add event details 200 details.tab = tab; 201 details.content = content; 202 203 // Emit a custom event 204 emitEvent(tab, details); 205 }; 206 207 /** 208 * Get all of the tabs in a tablist 209 * @param {Node} tab A tab from the list 210 * @return {Object} The tabs and the index of the currently active one 211 */ 212 var getTabsMap = function (tab) { 213 var tabGroup = tab.closest('[role="tablist"]'); 214 var tabs = tabGroup ? tabGroup.querySelectorAll('[role="tab"]') : null; 215 if (!tabs) return; 216 return { 217 tabs: tabs, 218 index: Array.prototype.indexOf.call(tabs, tab), 219 }; 220 }; 221 222 /** 223 * Switch the active tab based on keyboard activity 224 * @param {Node} tab The currently active tab 225 * @param {Key} key The key that was pressed 226 */ 227 var switchTabs = function (tab, key) { 228 // Get a map of tabs 229 var map = getTabsMap(tab); 230 if (!map) return; 231 var length = map.tabs.length - 1; 232 var index; 233 234 // Go to previous tab 235 if (["ArrowUp", "ArrowLeft", "Up", "Left"].indexOf(key) > -1) { 236 index = map.index < 1 ? length : map.index - 1; 237 } 238 239 // Go to next tab 240 else if (["ArrowDown", "ArrowRight", "Down", "Right"].indexOf(key) > -1) { 241 index = map.index === length ? 0 : map.index + 1; 242 } 243 244 // Go to home 245 else if (key === "Home") { 246 index = 0; 247 } 248 249 // Go to end 250 else if (key === "End") { 251 index = length; 252 } 253 254 // Toggle the tab 255 toggle(map.tabs[index]); 256 }; 257 258 /** 259 * Create the Constructor object 260 */ 261 var Constructor = function (selector, options) { 262 // 263 // Variables 264 // 265 266 var publicAPIs = {}; 267 var settings, tabWrapper; 268 269 // 270 // Methods 271 // 272 273 publicAPIs.destroy = function () { 274 // Get all tabs 275 var tabs = tabWrapper.querySelectorAll("a"); 276 277 // Add roles to tabs 278 Array.prototype.forEach.call(tabs, function (tab) { 279 // Get the tab content 280 var content = document.querySelector(tab.hash); 281 if (!content) return; 282 283 // Setup the tab 284 destroyTab(tab, content, settings); 285 }); 286 287 // Remove role from wrapper 288 tabWrapper.removeAttribute("role"); 289 290 // Remove event listeners 291 document.documentElement.removeEventListener( 292 "click", 293 clickHandler, 294 true 295 ); 296 tabWrapper.removeEventListener("keydown", keyHandler, true); 297 298 // Reset variables 299 settings = null; 300 tabWrapper = null; 301 }; 302 303 /** 304 * Setup the DOM with the proper attributes 305 */ 306 publicAPIs.setup = function () { 307 // Variables 308 tabWrapper = document.querySelector(selector); 309 if (!tabWrapper) return; 310 var tabs = tabWrapper.querySelectorAll("a"); 311 312 // Add role to wrapper 313 tabWrapper.setAttribute("role", "tablist"); 314 315 // Add roles to tabs. provide dynanmic tab indexes if we are within reveal 316 var contentTabindexes = 317 window.document.body.classList.contains("reveal-viewport"); 318 var nextTabindex = 1; 319 Array.prototype.forEach.call(tabs, function (tab) { 320 if (contentTabindexes) { 321 tab.setAttribute("tabindex", "" + nextTabindex++); 322 } else { 323 tab.setAttribute("tabindex", "0"); 324 } 325 326 // Get the tab content 327 var content = document.querySelector(tab.hash); 328 if (!content) return; 329 330 // set tab indexes for content 331 if (contentTabindexes) { 332 getKeyboardFocusableElements(content).forEach(function (el) { 333 el.setAttribute("tabindex", "" + nextTabindex++); 334 }); 335 } 336 337 // Setup the tab 338 setupTab(tab, content, settings); 339 }); 340 }; 341 342 /** 343 * Toggle a tab based on an ID 344 * @param {String|Node} id The tab to toggle 345 */ 346 publicAPIs.toggle = function (id) { 347 // Get the tab 348 var tab = id; 349 if (typeof id === "string") { 350 tab = document.querySelector( 351 selector + ' [role="tab"][href*="' + id + '"]' 352 ); 353 } 354 355 // Toggle the tab 356 toggle(tab); 357 }; 358 359 /** 360 * Handle click events 361 */ 362 var clickHandler = function (event) { 363 // Only run on toggles 364 var tab = event.target.closest(selector + ' [role="tab"]'); 365 if (!tab) return; 366 367 // Prevent link behavior 368 event.preventDefault(); 369 370 // Toggle the tab 371 toggle(tab); 372 }; 373 374 /** 375 * Handle keydown events 376 */ 377 var keyHandler = function (event) { 378 // Only run if a tab is in focus 379 var tab = document.activeElement; 380 if (!tab.matches(selector + ' [role="tab"]')) return; 381 382 // Only run for specific keys 383 if (["Home", "End"].indexOf(event.key) < 0) return; 384 385 // Switch tabs 386 switchTabs(tab, event.key); 387 }; 388 389 /** 390 * Initialize the instance 391 */ 392 var init = function () { 393 // Merge user options with defaults 394 settings = extend(defaults, options || {}); 395 396 // Setup the DOM 397 publicAPIs.setup(); 398 399 // Add event listeners 400 document.documentElement.addEventListener("click", clickHandler, true); 401 tabWrapper.addEventListener("keydown", keyHandler, true); 402 }; 403 404 // 405 // Initialize and return the Public APIs 406 // 407 408 init(); 409 return publicAPIs; 410 }; 411 412 //
413 // Return the Constructor 414 // 415 416 return Constructor; 417 } 418);
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.