tabs.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. try {
  2. var session = window.sessionStorage || {};
  3. } catch (e) {
  4. var session = {};
  5. }
  6. window.addEventListener("DOMContentLoaded", () => {
  7. const allTabs = document.querySelectorAll('.sphinx-tabs-tab');
  8. const tabLists = document.querySelectorAll('[role="tablist"]');
  9. allTabs.forEach(tab => {
  10. tab.addEventListener("click", changeTabs);
  11. });
  12. tabLists.forEach(tabList => {
  13. tabList.addEventListener("keydown", keyTabs);
  14. });
  15. // Restore group tab selection from session
  16. const lastSelected = session.getItem('sphinx-tabs-last-selected');
  17. if (lastSelected != null) selectNamedTabs(lastSelected);
  18. });
  19. /**
  20. * Key focus left and right between sibling elements using arrows
  21. * @param {Node} e the element in focus when key was pressed
  22. */
  23. function keyTabs(e) {
  24. const tab = e.target;
  25. let nextTab = null;
  26. if (e.keyCode === 39 || e.keyCode === 37) {
  27. tab.setAttribute("tabindex", -1);
  28. // Move right
  29. if (e.keyCode === 39) {
  30. nextTab = tab.nextElementSibling;
  31. if (nextTab === null) {
  32. nextTab = tab.parentNode.firstElementChild;
  33. }
  34. // Move left
  35. } else if (e.keyCode === 37) {
  36. nextTab = tab.previousElementSibling;
  37. if (nextTab === null) {
  38. nextTab = tab.parentNode.lastElementChild;
  39. }
  40. }
  41. }
  42. if (nextTab !== null) {
  43. nextTab.setAttribute("tabindex", 0);
  44. nextTab.focus();
  45. }
  46. }
  47. /**
  48. * Select or deselect clicked tab. If a group tab
  49. * is selected, also select tab in other tabLists.
  50. * @param {Node} e the element that was clicked
  51. */
  52. function changeTabs(e) {
  53. // Use this instead of the element that was clicked, in case it's a child
  54. const notSelected = this.getAttribute("aria-selected") === "false";
  55. const positionBefore = this.parentNode.getBoundingClientRect().top;
  56. const notClosable = !this.parentNode.classList.contains("closeable");
  57. deselectTabList(this);
  58. if (notSelected || notClosable) {
  59. selectTab(this);
  60. const name = this.getAttribute("name");
  61. selectNamedTabs(name, this.id);
  62. if (this.classList.contains("group-tab")) {
  63. // Persist during session
  64. session.setItem('sphinx-tabs-last-selected', name);
  65. }
  66. }
  67. const positionAfter = this.parentNode.getBoundingClientRect().top;
  68. const positionDelta = positionAfter - positionBefore;
  69. // Scroll to offset content resizing
  70. window.scrollTo(0, window.scrollY + positionDelta);
  71. }
  72. /**
  73. * Select tab and show associated panel.
  74. * @param {Node} tab tab to select
  75. */
  76. function selectTab(tab) {
  77. tab.setAttribute("aria-selected", true);
  78. // Show the associated panel
  79. document
  80. .getElementById(tab.getAttribute("aria-controls"))
  81. .removeAttribute("hidden");
  82. }
  83. /**
  84. * Hide the panels associated with all tabs within the
  85. * tablist containing this tab.
  86. * @param {Node} tab a tab within the tablist to deselect
  87. */
  88. function deselectTabList(tab) {
  89. const parent = tab.parentNode;
  90. const grandparent = parent.parentNode;
  91. Array.from(parent.children)
  92. .forEach(t => t.setAttribute("aria-selected", false));
  93. Array.from(grandparent.children)
  94. .slice(1) // Skip tablist
  95. .forEach(panel => panel.setAttribute("hidden", true));
  96. }
  97. /**
  98. * Select grouped tabs with the same name, but no the tab
  99. * with the given id.
  100. * @param {Node} name name of grouped tab to be selected
  101. * @param {Node} clickedId id of clicked tab
  102. */
  103. function selectNamedTabs(name, clickedId=null) {
  104. const groupedTabs = document.querySelectorAll(`.sphinx-tabs-tab[name="${name}"]`);
  105. const tabLists = Array.from(groupedTabs).map(tab => tab.parentNode);
  106. tabLists
  107. .forEach(tabList => {
  108. // Don't want to change the tabList containing the clicked tab
  109. const clickedTab = tabList.querySelector(`[id="${clickedId}"]`);
  110. if (clickedTab === null ) {
  111. // Select first tab with matching name
  112. const tab = tabList.querySelector(`.sphinx-tabs-tab[name="${name}"]`);
  113. deselectTabList(tab);
  114. selectTab(tab);
  115. }
  116. })
  117. }
  118. if (typeof exports === 'undefined') {
  119. exports = {};
  120. }
  121. exports.keyTabs = keyTabs;
  122. exports.changeTabs = changeTabs;
  123. exports.selectTab = selectTab;
  124. exports.deselectTabList = deselectTabList;
  125. exports.selectNamedTabs = selectNamedTabs;