142 lines
4.1 KiB
JavaScript
142 lines
4.1 KiB
JavaScript
|
try {
|
||
|
var session = window.sessionStorage || {};
|
||
|
} catch (e) {
|
||
|
var session = {};
|
||
|
}
|
||
|
|
||
|
window.addEventListener("DOMContentLoaded", () => {
|
||
|
const allTabs = document.querySelectorAll('.sphinx-tabs-tab');
|
||
|
const tabLists = document.querySelectorAll('[role="tablist"]');
|
||
|
|
||
|
allTabs.forEach(tab => {
|
||
|
tab.addEventListener("click", changeTabs);
|
||
|
});
|
||
|
|
||
|
tabLists.forEach(tabList => {
|
||
|
tabList.addEventListener("keydown", keyTabs);
|
||
|
});
|
||
|
|
||
|
// Restore group tab selection from session
|
||
|
const lastSelected = session.getItem('sphinx-tabs-last-selected');
|
||
|
if (lastSelected != null) selectNamedTabs(lastSelected);
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Key focus left and right between sibling elements using arrows
|
||
|
* @param {Node} e the element in focus when key was pressed
|
||
|
*/
|
||
|
function keyTabs(e) {
|
||
|
const tab = e.target;
|
||
|
let nextTab = null;
|
||
|
if (e.keyCode === 39 || e.keyCode === 37) {
|
||
|
tab.setAttribute("tabindex", -1);
|
||
|
// Move right
|
||
|
if (e.keyCode === 39) {
|
||
|
nextTab = tab.nextElementSibling;
|
||
|
if (nextTab === null) {
|
||
|
nextTab = tab.parentNode.firstElementChild;
|
||
|
}
|
||
|
// Move left
|
||
|
} else if (e.keyCode === 37) {
|
||
|
nextTab = tab.previousElementSibling;
|
||
|
if (nextTab === null) {
|
||
|
nextTab = tab.parentNode.lastElementChild;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (nextTab !== null) {
|
||
|
nextTab.setAttribute("tabindex", 0);
|
||
|
nextTab.focus();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Select or deselect clicked tab. If a group tab
|
||
|
* is selected, also select tab in other tabLists.
|
||
|
* @param {Node} e the element that was clicked
|
||
|
*/
|
||
|
function changeTabs(e) {
|
||
|
// Use this instead of the element that was clicked, in case it's a child
|
||
|
const notSelected = this.getAttribute("aria-selected") === "false";
|
||
|
const positionBefore = this.parentNode.getBoundingClientRect().top;
|
||
|
const notClosable = !this.parentNode.classList.contains("closeable");
|
||
|
|
||
|
deselectTabList(this);
|
||
|
|
||
|
if (notSelected || notClosable) {
|
||
|
selectTab(this);
|
||
|
const name = this.getAttribute("name");
|
||
|
selectNamedTabs(name, this.id);
|
||
|
|
||
|
if (this.classList.contains("group-tab")) {
|
||
|
// Persist during session
|
||
|
session.setItem('sphinx-tabs-last-selected', name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const positionAfter = this.parentNode.getBoundingClientRect().top;
|
||
|
const positionDelta = positionAfter - positionBefore;
|
||
|
// Scroll to offset content resizing
|
||
|
window.scrollTo(0, window.scrollY + positionDelta);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Select tab and show associated panel.
|
||
|
* @param {Node} tab tab to select
|
||
|
*/
|
||
|
function selectTab(tab) {
|
||
|
tab.setAttribute("aria-selected", true);
|
||
|
|
||
|
// Show the associated panel
|
||
|
document
|
||
|
.getElementById(tab.getAttribute("aria-controls"))
|
||
|
.removeAttribute("hidden");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Hide the panels associated with all tabs within the
|
||
|
* tablist containing this tab.
|
||
|
* @param {Node} tab a tab within the tablist to deselect
|
||
|
*/
|
||
|
function deselectTabList(tab) {
|
||
|
const parent = tab.parentNode;
|
||
|
const grandparent = parent.parentNode;
|
||
|
|
||
|
Array.from(parent.children)
|
||
|
.forEach(t => t.setAttribute("aria-selected", false));
|
||
|
|
||
|
Array.from(grandparent.children)
|
||
|
.slice(1) // Skip tablist
|
||
|
.forEach(panel => panel.setAttribute("hidden", true));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Select grouped tabs with the same name, but no the tab
|
||
|
* with the given id.
|
||
|
* @param {Node} name name of grouped tab to be selected
|
||
|
* @param {Node} clickedId id of clicked tab
|
||
|
*/
|
||
|
function selectNamedTabs(name, clickedId=null) {
|
||
|
const groupedTabs = document.querySelectorAll(`.sphinx-tabs-tab[name="${name}"]`);
|
||
|
const tabLists = Array.from(groupedTabs).map(tab => tab.parentNode);
|
||
|
|
||
|
tabLists
|
||
|
.forEach(tabList => {
|
||
|
// Don't want to change the tabList containing the clicked tab
|
||
|
const clickedTab = tabList.querySelector(`[id="${clickedId}"]`);
|
||
|
if (clickedTab === null ) {
|
||
|
// Select first tab with matching name
|
||
|
const tab = tabList.querySelector(`.sphinx-tabs-tab[name="${name}"]`);
|
||
|
deselectTabList(tab);
|
||
|
selectTab(tab);
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
exports.keyTabs = keyTabs;
|
||
|
exports.changeTabs = changeTabs;
|
||
|
exports.selectTab = selectTab;
|
||
|
exports.deselectTabList = deselectTabList;
|
||
|
exports.selectNamedTabs = selectNamedTabs;
|