|
(function (root, factory) { |
|
if (typeof define === "function" && define.amd) { |
|
define([], function () { |
|
return factory(root); |
|
}); |
|
} else if (typeof exports === "object") { |
|
module.exports = factory(root); |
|
} else { |
|
root.Tabby = factory(root); |
|
} |
|
})( |
|
typeof global !== "undefined" |
|
? global |
|
: typeof window !== "undefined" |
|
? window |
|
: this, |
|
function (window) { |
|
"use strict"; |
|
|
|
|
|
|
|
|
|
|
|
var defaults = { |
|
idPrefix: "tabby-toggle_", |
|
default: "[data-tabby-default]", |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var extend = function () { |
|
var merged = {}; |
|
Array.prototype.forEach.call(arguments, function (obj) { |
|
for (var key in obj) { |
|
if (!obj.hasOwnProperty(key)) return; |
|
merged[key] = obj[key]; |
|
} |
|
}); |
|
return merged; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var emitEvent = function (tab, details) { |
|
|
|
var event; |
|
if (typeof window.CustomEvent === "function") { |
|
event = new CustomEvent("tabby", { |
|
bubbles: true, |
|
cancelable: true, |
|
detail: details, |
|
}); |
|
} else { |
|
event = document.createEvent("CustomEvent"); |
|
event.initCustomEvent("tabby", true, true, details); |
|
} |
|
|
|
|
|
tab.dispatchEvent(event); |
|
}; |
|
|
|
var focusHandler = function (event) { |
|
toggle(event.target); |
|
}; |
|
|
|
var getKeyboardFocusableElements = function (element) { |
|
return [ |
|
...element.querySelectorAll( |
|
'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])' |
|
), |
|
].filter( |
|
(el) => !el.hasAttribute("disabled") && !el.getAttribute("aria-hidden") |
|
); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var destroyTab = function (tab, content, settings) { |
|
|
|
if (tab.id.slice(0, settings.idPrefix.length) === settings.idPrefix) { |
|
tab.id = ""; |
|
} |
|
|
|
|
|
tab.removeEventListener("focus", focusHandler, true); |
|
|
|
|
|
tab.removeAttribute("role"); |
|
tab.removeAttribute("aria-controls"); |
|
tab.removeAttribute("aria-selected"); |
|
tab.removeAttribute("tabindex"); |
|
tab.closest("li").removeAttribute("role"); |
|
content.removeAttribute("role"); |
|
content.removeAttribute("aria-labelledby"); |
|
content.removeAttribute("hidden"); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var setupTab = function (tab, content, settings) { |
|
|
|
if (!tab.id) { |
|
tab.id = settings.idPrefix + content.id; |
|
} |
|
|
|
|
|
tab.setAttribute("role", "tab"); |
|
tab.setAttribute("aria-controls", content.id); |
|
tab.closest("li").setAttribute("role", "presentation"); |
|
content.setAttribute("role", "tabpanel"); |
|
content.setAttribute("aria-labelledby", tab.id); |
|
|
|
|
|
if (tab.matches(settings.default)) { |
|
tab.setAttribute("aria-selected", "true"); |
|
} else { |
|
tab.setAttribute("aria-selected", "false"); |
|
content.setAttribute("hidden", "hidden"); |
|
} |
|
|
|
|
|
tab.addEventListener("focus", focusHandler); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
var hide = function (newTab) { |
|
|
|
var tabGroup = newTab.closest('[role="tablist"]'); |
|
if (!tabGroup) return {}; |
|
var tab = tabGroup.querySelector('[role="tab"][aria-selected="true"]'); |
|
if (!tab) return {}; |
|
var content = document.querySelector(tab.hash); |
|
|
|
|
|
tab.setAttribute("aria-selected", "false"); |
|
|
|
|
|
if (!content) return { previousTab: tab }; |
|
content.setAttribute("hidden", "hidden"); |
|
|
|
|
|
return { |
|
previousTab: tab, |
|
previousContent: content, |
|
}; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
var show = function (tab, content) { |
|
tab.setAttribute("aria-selected", "true"); |
|
content.removeAttribute("hidden"); |
|
tab.focus(); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
var toggle = function (tab) { |
|
|
|
if (!tab || tab.getAttribute("aria-selected") == "true") return; |
|
|
|
|
|
var content = document.querySelector(tab.hash); |
|
if (!content) return; |
|
|
|
|
|
var details = hide(tab); |
|
|
|
|
|
show(tab, content); |
|
|
|
|
|
details.tab = tab; |
|
details.content = content; |
|
|
|
|
|
emitEvent(tab, details); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
var getTabsMap = function (tab) { |
|
var tabGroup = tab.closest('[role="tablist"]'); |
|
var tabs = tabGroup ? tabGroup.querySelectorAll('[role="tab"]') : null; |
|
if (!tabs) return; |
|
return { |
|
tabs: tabs, |
|
index: Array.prototype.indexOf.call(tabs, tab), |
|
}; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
var switchTabs = function (tab, key) { |
|
|
|
var map = getTabsMap(tab); |
|
if (!map) return; |
|
var length = map.tabs.length - 1; |
|
var index; |
|
|
|
|
|
if (["ArrowUp", "ArrowLeft", "Up", "Left"].indexOf(key) > -1) { |
|
index = map.index < 1 ? length : map.index - 1; |
|
} |
|
|
|
|
|
else if (["ArrowDown", "ArrowRight", "Down", "Right"].indexOf(key) > -1) { |
|
index = map.index === length ? 0 : map.index + 1; |
|
} |
|
|
|
|
|
else if (key === "Home") { |
|
index = 0; |
|
} |
|
|
|
|
|
else if (key === "End") { |
|
index = length; |
|
} |
|
|
|
|
|
toggle(map.tabs[index]); |
|
}; |
|
|
|
|
|
|
|
|
|
var Constructor = function (selector, options) { |
|
|
|
|
|
|
|
|
|
var publicAPIs = {}; |
|
var settings, tabWrapper; |
|
|
|
|
|
|
|
|
|
|
|
publicAPIs.destroy = function () { |
|
|
|
var tabs = tabWrapper.querySelectorAll("a"); |
|
|
|
|
|
Array.prototype.forEach.call(tabs, function (tab) { |
|
|
|
var content = document.querySelector(tab.hash); |
|
if (!content) return; |
|
|
|
|
|
destroyTab(tab, content, settings); |
|
}); |
|
|
|
|
|
tabWrapper.removeAttribute("role"); |
|
|
|
|
|
document.documentElement.removeEventListener( |
|
"click", |
|
clickHandler, |
|
true |
|
); |
|
tabWrapper.removeEventListener("keydown", keyHandler, true); |
|
|
|
|
|
settings = null; |
|
tabWrapper = null; |
|
}; |
|
|
|
|
|
|
|
|
|
publicAPIs.setup = function () { |
|
|
|
tabWrapper = document.querySelector(selector); |
|
if (!tabWrapper) return; |
|
var tabs = tabWrapper.querySelectorAll("a"); |
|
|
|
|
|
tabWrapper.setAttribute("role", "tablist"); |
|
|
|
|
|
var contentTabindexes = |
|
window.document.body.classList.contains("reveal-viewport"); |
|
var nextTabindex = 1; |
|
Array.prototype.forEach.call(tabs, function (tab) { |
|
if (contentTabindexes) { |
|
tab.setAttribute("tabindex", "" + nextTabindex++); |
|
} else { |
|
tab.setAttribute("tabindex", "0"); |
|
} |
|
|
|
|
|
var content = document.querySelector(tab.hash); |
|
if (!content) return; |
|
|
|
|
|
if (contentTabindexes) { |
|
getKeyboardFocusableElements(content).forEach(function (el) { |
|
el.setAttribute("tabindex", "" + nextTabindex++); |
|
}); |
|
} |
|
|
|
|
|
setupTab(tab, content, settings); |
|
}); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
publicAPIs.toggle = function (id) { |
|
|
|
var tab = id; |
|
if (typeof id === "string") { |
|
tab = document.querySelector( |
|
selector + ' [role="tab"][href*="' + id + '"]' |
|
); |
|
} |
|
|
|
|
|
toggle(tab); |
|
}; |
|
|
|
|
|
|
|
|
|
var clickHandler = function (event) { |
|
|
|
var tab = event.target.closest(selector + ' [role="tab"]'); |
|
if (!tab) return; |
|
|
|
|
|
event.preventDefault(); |
|
|
|
|
|
toggle(tab); |
|
}; |
|
|
|
|
|
|
|
|
|
var keyHandler = function (event) { |
|
|
|
var tab = document.activeElement; |
|
if (!tab.matches(selector + ' [role="tab"]')) return; |
|
|
|
|
|
if (["Home", "End"].indexOf(event.key) < 0) return; |
|
|
|
|
|
switchTabs(tab, event.key); |
|
}; |
|
|
|
|
|
|
|
|
|
var init = function () { |
|
|
|
settings = extend(defaults, options || {}); |
|
|
|
|
|
publicAPIs.setup(); |
|
|
|
|
|
document.documentElement.addEventListener("click", clickHandler, true); |
|
tabWrapper.addEventListener("keydown", keyHandler, true); |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
init(); |
|
return publicAPIs; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
return Constructor; |
|
} |
|
); |
|
|