]> BookStack Code Mirror - bookstack/blobdiff - resources/js/components/tabs.js
add tests for priority
[bookstack] / resources / js / components / tabs.js
index 7121d70448c728530231eed0cbd74dc5647326f3..f0fc058ced7fd8377123f831ac93c631d27d4952 100644 (file)
@@ -1,51 +1,88 @@
+import {Component} from './component';
+
 /**
  * Tabs
- * Works by matching 'tabToggle<Key>' with 'tabContent<Key>' sections.
- * @extends {Component}
+ * Uses accessible attributes to drive its functionality.
+ * On tab wrapping element:
+ * - role=tablist
+ * On tabs (Should be a button):
+ * - id
+ * - role=tab
+ * - aria-selected=true/false
+ * - aria-controls=<id-of-panel-section>
+ * On panels:
+ * - id
+ * - tabindex=0
+ * - role=tabpanel
+ * - aria-labelledby=<id-of-tab-for-panel>
+ * - hidden (If not shown by default).
  */
-import {onSelect} from "../services/dom";
-
-class Tabs {
+export class Tabs extends Component {
 
     setup() {
-        this.tabContentsByName = {};
-        this.tabButtonsByName = {};
-        this.allContents = [];
-        this.allButtons = [];
-
-        for (const [key, elems] of Object.entries(this.$manyRefs || {})) {
-            if (key.startsWith('toggle')) {
-                const cleanKey = key.replace('toggle', '').toLowerCase();
-                onSelect(elems, e => this.show(cleanKey));
-                this.allButtons.push(...elems);
-                this.tabButtonsByName[cleanKey] = elems;
-            }
-            if (key.startsWith('content')) {
-                const cleanKey = key.replace('content', '').toLowerCase();
-                this.tabContentsByName[cleanKey] = elems;
-                this.allContents.push(...elems);
+        this.container = this.$el;
+        this.tabList = this.container.querySelector('[role="tablist"]');
+        this.tabs = Array.from(this.tabList.querySelectorAll('[role="tab"]'));
+        this.panels = Array.from(this.container.querySelectorAll(':scope > [role="tabpanel"], :scope > * > [role="tabpanel"]'));
+        this.activeUnder = this.$opts.activeUnder ? Number(this.$opts.activeUnder) : 10000;
+        this.active = null;
+
+        this.container.addEventListener('click', event => {
+            const tab = event.target.closest('[role="tab"]');
+            if (tab && this.tabs.includes(tab)) {
+                this.show(tab.getAttribute('aria-controls'));
             }
+        });
+
+        window.addEventListener('resize', this.updateActiveState.bind(this), {
+            passive: true,
+        });
+        this.updateActiveState();
+    }
+
+    show(sectionId) {
+        for (const panel of this.panels) {
+            panel.toggleAttribute('hidden', panel.id !== sectionId);
         }
+
+        for (const tab of this.tabs) {
+            const tabSection = tab.getAttribute('aria-controls');
+            const selected = tabSection === sectionId;
+            tab.setAttribute('aria-selected', selected ? 'true' : 'false');
+        }
+
+        this.$emit('change', {showing: sectionId});
     }
 
-    show(key) {
-        this.allContents.forEach(c => {
-            c.classList.add('hidden');
-            c.classList.remove('selected');
-        });
-        this.allButtons.forEach(b => b.classList.remove('selected'));
-
-        const contents = this.tabContentsByName[key] || [];
-        const buttons = this.tabButtonsByName[key] || [];
-        if (contents.length > 0) {
-            contents.forEach(c => {
-                c.classList.remove('hidden')
-                c.classList.add('selected')
-            });
-            buttons.forEach(b => b.classList.add('selected'));
+    updateActiveState() {
+        const active = window.innerWidth < this.activeUnder;
+        if (active === this.active) {
+            return;
+        }
+
+        if (active) {
+            this.activate();
+        } else {
+            this.deactivate();
         }
+
+        this.active = active;
     }
 
-}
+    activate() {
+        const panelToShow = this.panels.find(p => !p.hasAttribute('hidden')) || this.panels[0];
+        this.show(panelToShow.id);
+        this.tabList.toggleAttribute('hidden', false);
+    }
 
-export default Tabs;
\ No newline at end of file
+    deactivate() {
+        for (const panel of this.panels) {
+            panel.removeAttribute('hidden');
+        }
+        for (const tab of this.tabs) {
+            tab.setAttribute('aria-selected', 'false');
+        }
+        this.tabList.toggleAttribute('hidden', true);
+    }
+
+}