muellerzr's picture
muellerzr HF staff
Try with this?
b0d1496
(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";
//
// Variables
//
var defaults = {
idPrefix: "tabby-toggle_",
default: "[data-tabby-default]",
};
//
// Methods
//
/**
* Merge two or more objects together.
* @param {Object} objects The objects to merge together
* @returns {Object} Merged values of defaults and options
*/
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;
};
/**
* Emit a custom event
* @param {String} type The event type
* @param {Node} tab The tab to attach the event to
* @param {Node} details Details about the event
*/
var emitEvent = function (tab, details) {
// Create a new event
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);
}
// Dispatch the event
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")
);
};
/**
* Remove roles and attributes from a tab and its content
* @param {Node} tab The tab
* @param {Node} content The tab content
* @param {Object} settings User settings and options
*/
var destroyTab = function (tab, content, settings) {
// Remove the generated ID
if (tab.id.slice(0, settings.idPrefix.length) === settings.idPrefix) {
tab.id = "";
}
// remove event listener
tab.removeEventListener("focus", focusHandler, true);
// Remove roles
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");
};
/**
* Add the required roles and attributes to a tab and its content
* @param {Node} tab The tab
* @param {Node} content The tab content
* @param {Object} settings User settings and options
*/
var setupTab = function (tab, content, settings) {
// Give tab an ID if it doesn't already have one
if (!tab.id) {
tab.id = settings.idPrefix + content.id;
}
// Add roles
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);
// Add selected state
if (tab.matches(settings.default)) {
tab.setAttribute("aria-selected", "true");
} else {
tab.setAttribute("aria-selected", "false");
content.setAttribute("hidden", "hidden");
}
// add focus event listender
tab.addEventListener("focus", focusHandler);
};
/**
* Hide a tab and its content
* @param {Node} newTab The new tab that's replacing it
*/
var hide = function (newTab) {
// Variables
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);
// Hide the tab
tab.setAttribute("aria-selected", "false");
// Hide the content
if (!content) return { previousTab: tab };
content.setAttribute("hidden", "hidden");
// Return the hidden tab and content
return {
previousTab: tab,
previousContent: content,
};
};
/**
* Show a tab and its content
* @param {Node} tab The tab
* @param {Node} content The tab content
*/
var show = function (tab, content) {
tab.setAttribute("aria-selected", "true");
content.removeAttribute("hidden");
tab.focus();
};
/**
* Toggle a new tab
* @param {Node} tab The tab to show
*/
var toggle = function (tab) {
// Make sure there's a tab to toggle and it's not already active
if (!tab || tab.getAttribute("aria-selected") == "true") return;
// Variables
var content = document.querySelector(tab.hash);
if (!content) return;
// Hide active tab and content
var details = hide(tab);
// Show new tab and content
show(tab, content);
// Add event details
details.tab = tab;
details.content = content;
// Emit a custom event
emitEvent(tab, details);
};
/**
* Get all of the tabs in a tablist
* @param {Node} tab A tab from the list
* @return {Object} The tabs and the index of the currently active one
*/
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),
};
};
/**
* Switch the active tab based on keyboard activity
* @param {Node} tab The currently active tab
* @param {Key} key The key that was pressed
*/
var switchTabs = function (tab, key) {
// Get a map of tabs
var map = getTabsMap(tab);
if (!map) return;
var length = map.tabs.length - 1;
var index;
// Go to previous tab
if (["ArrowUp", "ArrowLeft", "Up", "Left"].indexOf(key) > -1) {
index = map.index < 1 ? length : map.index - 1;
}
// Go to next tab
else if (["ArrowDown", "ArrowRight", "Down", "Right"].indexOf(key) > -1) {
index = map.index === length ? 0 : map.index + 1;
}
// Go to home
else if (key === "Home") {
index = 0;
}
// Go to end
else if (key === "End") {
index = length;
}
// Toggle the tab
toggle(map.tabs[index]);
};
/**
* Create the Constructor object
*/
var Constructor = function (selector, options) {
//
// Variables
//
var publicAPIs = {};
var settings, tabWrapper;
//
// Methods
//
publicAPIs.destroy = function () {
// Get all tabs
var tabs = tabWrapper.querySelectorAll("a");
// Add roles to tabs
Array.prototype.forEach.call(tabs, function (tab) {
// Get the tab content
var content = document.querySelector(tab.hash);
if (!content) return;
// Setup the tab
destroyTab(tab, content, settings);
});
// Remove role from wrapper
tabWrapper.removeAttribute("role");
// Remove event listeners
document.documentElement.removeEventListener(
"click",
clickHandler,
true
);
tabWrapper.removeEventListener("keydown", keyHandler, true);
// Reset variables
settings = null;
tabWrapper = null;
};
/**
* Setup the DOM with the proper attributes
*/
publicAPIs.setup = function () {
// Variables
tabWrapper = document.querySelector(selector);
if (!tabWrapper) return;
var tabs = tabWrapper.querySelectorAll("a");
// Add role to wrapper
tabWrapper.setAttribute("role", "tablist");
// Add roles to tabs. provide dynanmic tab indexes if we are within reveal
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");
}
// Get the tab content
var content = document.querySelector(tab.hash);
if (!content) return;
// set tab indexes for content
if (contentTabindexes) {
getKeyboardFocusableElements(content).forEach(function (el) {
el.setAttribute("tabindex", "" + nextTabindex++);
});
}
// Setup the tab
setupTab(tab, content, settings);
});
};
/**
* Toggle a tab based on an ID
* @param {String|Node} id The tab to toggle
*/
publicAPIs.toggle = function (id) {
// Get the tab
var tab = id;
if (typeof id === "string") {
tab = document.querySelector(
selector + ' [role="tab"][href*="' + id + '"]'
);
}
// Toggle the tab
toggle(tab);
};
/**
* Handle click events
*/
var clickHandler = function (event) {
// Only run on toggles
var tab = event.target.closest(selector + ' [role="tab"]');
if (!tab) return;
// Prevent link behavior
event.preventDefault();
// Toggle the tab
toggle(tab);
};
/**
* Handle keydown events
*/
var keyHandler = function (event) {
// Only run if a tab is in focus
var tab = document.activeElement;
if (!tab.matches(selector + ' [role="tab"]')) return;
// Only run for specific keys
if (["Home", "End"].indexOf(event.key) < 0) return;
// Switch tabs
switchTabs(tab, event.key);
};
/**
* Initialize the instance
*/
var init = function () {
// Merge user options with defaults
settings = extend(defaults, options || {});
// Setup the DOM
publicAPIs.setup();
// Add event listeners
document.documentElement.addEventListener("click", clickHandler, true);
tabWrapper.addEventListener("keydown", keyHandler, true);
};
//
// Initialize and return the Public APIs
//
init();
return publicAPIs;
};
//
// Return the Constructor
//
return Constructor;
}
);