summaryrefslogtreecommitdiff
path: root/_static/tabs.js
blob: 48dc303c8c0165f4c8735d84bb8e09132d0edb58 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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;