]> BookStack Code Mirror - bookstack/blob - resources/js/components/tabs.js
Comments: Started archive display, created mode for tree node
[bookstack] / resources / js / components / tabs.js
1 import {Component} from './component';
2
3 /**
4  * Tabs
5  * Uses accessible attributes to drive its functionality.
6  * On tab wrapping element:
7  * - role=tablist
8  * On tabs (Should be a button):
9  * - id
10  * - role=tab
11  * - aria-selected=true/false
12  * - aria-controls=<id-of-panel-section>
13  * On panels:
14  * - id
15  * - tabindex=0
16  * - role=tabpanel
17  * - aria-labelledby=<id-of-tab-for-panel>
18  * - hidden (If not shown by default).
19  */
20 export class Tabs extends Component {
21
22     setup() {
23         this.container = this.$el;
24         this.tabList = this.container.querySelector('[role="tablist"]');
25         this.tabs = Array.from(this.tabList.querySelectorAll('[role="tab"]'));
26         this.panels = Array.from(this.container.querySelectorAll(':scope > [role="tabpanel"], :scope > * > [role="tabpanel"]'));
27         this.activeUnder = this.$opts.activeUnder ? Number(this.$opts.activeUnder) : 10000;
28         this.active = null;
29
30         this.container.addEventListener('click', event => {
31             const tab = event.target.closest('[role="tab"]');
32             if (tab && this.tabs.includes(tab)) {
33                 this.show(tab.getAttribute('aria-controls'));
34             }
35         });
36
37         window.addEventListener('resize', this.updateActiveState.bind(this), {
38             passive: true,
39         });
40         this.updateActiveState();
41     }
42
43     show(sectionId) {
44         for (const panel of this.panels) {
45             panel.toggleAttribute('hidden', panel.id !== sectionId);
46         }
47
48         for (const tab of this.tabs) {
49             const tabSection = tab.getAttribute('aria-controls');
50             const selected = tabSection === sectionId;
51             tab.setAttribute('aria-selected', selected ? 'true' : 'false');
52         }
53
54         this.$emit('change', {showing: sectionId});
55     }
56
57     updateActiveState() {
58         const active = window.innerWidth < this.activeUnder;
59         if (active === this.active) {
60             return;
61         }
62
63         if (active) {
64             this.activate();
65         } else {
66             this.deactivate();
67         }
68
69         this.active = active;
70     }
71
72     activate() {
73         const panelToShow = this.panels.find(p => !p.hasAttribute('hidden')) || this.panels[0];
74         this.show(panelToShow.id);
75         this.tabList.toggleAttribute('hidden', false);
76     }
77
78     deactivate() {
79         for (const panel of this.panels) {
80             panel.removeAttribute('hidden');
81         }
82         for (const tab of this.tabs) {
83             tab.setAttribute('aria-selected', 'false');
84         }
85         this.tabList.toggleAttribute('hidden', true);
86     }
87
88 }