146 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			146 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);
 | |
|       }
 | |
|     })
 | |
| }
 | |
| 
 | |
| if (typeof exports === 'undefined') {
 | |
|   exports = {};
 | |
| }
 | |
| 
 | |
| exports.keyTabs = keyTabs;
 | |
| exports.changeTabs = changeTabs;
 | |
| exports.selectTab = selectTab;
 | |
| exports.deselectTabList = deselectTabList;
 | |
| exports.selectNamedTabs = selectNamedTabs;
 | 
