PageSourceSearch

https://website-cms-prod.fly.dev/modules/App/assets/vendor/kiss/components/tabs/tabs.js

js website-cms-prod.fly.dev collected 2026-10-03 08:28:35 UTC 4,387 bytes, 142 lines download raw bytes

1customElements.define('kiss-tabs', class extends HTMLElement {
2
3    static get observedAttributes() {
4        return [];
5    }
6
7    constructor() {
8        super();
9    }
10
11    connectedCallback() {
12
13        if (this.getAttribute('static') == 'true') {
14            return;
15        }
16
17        this.activeIndex = Number(this.getAttribute('index') || 0);
18
19        this.nav = document.createElement("ul");
20
21        this.nav.classList.add('kiss-tabs-nav');
22        this.nav.setAttribute('role', 'tablist');
23        this.prepend(this.nav);
24
25        this.render();
26
27        this.addEventListener('click', e => {
28            if (!e.target.classList.contains('kiss-tabs-nav-link')) return;
29            this.setIndex(e.target.getAttribute('index'));
30            e.preventDefault();
31        });
32
33        // Keyboard navigation
34        this.nav.addEventListener('keydown', e => {
35
36            let link = e.target.closest('.kiss-tabs-nav-link');
37            if (!link) return;
38
39            let handled = false;
40
41            switch (e.key) {
42                case 'ArrowRight':
43                    this.setIndex((this.activeIndex + 1) % this.tabs.length);
44                    this.focusActiveTab();
45                    handled = true;
46                    break;
47                case 'ArrowLeft':
48                    this.setIndex((this.activeIndex - 1 + this.tabs.length) % this.tabs.length);
49                    this.focusActiveTab();
50                    handled = true;
51                    break;
52                case 'Home':
53                    this.setIndex(0);
54                    this.focusActiveTab();
55                    handled = true;
56                    break;
57                case 'End':
58                    this.setIndex(this.tabs.length - 1);
59                    this.focusActiveTab();
60                    handled = true;
61                    break;
62            }
63
64            if (handled) {
65                e.preventDefault();
66            }
67        });
68    }
69
70    attributeChangedCallback(name, oldvalue, newvalue) {
71        if (oldvalue != newvalue) this.render();
72    }
73
74    focusActiveTab() {
75        let link = this.nav.querySelector('.kiss-tabs-nav-link[aria-selected="true"]');
76        if (link) link.focus();
77    }
78
79    setIndex(index) {
80
81        this.activeIndex = Number(index);
82
83        this.tabs.forEach((tab, idx) => {
84
85            let isActive = this.activeIndex == idx;
86            let link = this.nav.children[idx]?.querySelector('.kiss-tabs-nav-link');
87
88            this.nav.children[idx].setAttribute('active', isActive ? 'true' : 'false');
89            tab.setAttribute('active', isActive ? 'true' : 'false');
90
91            // ARIA: tab
92            if (link) {
93                link.setAttribute('aria-selected', isActive ? 'true' : 'false');
94                link.setAttribute('tabindex', isActive ? '0' : '-1');
95            }
96
97            // ARIA: panel
98            tab.setAttribute('aria-hidden', isActive ? 'false' : 'true');
99        });
100    }
101
102    render() {
103
104        if (this.getAttribute('static') == 'true') {
105            return;
106        }
107
108        this.tabs = [];
109
110        this.nav.innerHTML = '';
111
112        let item, counter = 0;
113
114        for (let i = 0; i < this.children.length; i++) {
115
116            if (this.children[i].tagName.toLowerCase() == 'tab') {
117
118                let tabIndex = this.tabs.length;
119                let tabId = `kiss-tab-${Date.now()}-${counter}`;
120                let panelId = `kiss-tabpanel-${Date.now()}-${counter}`;
121                counter++;
122
123                item = document.createElement("li");
124                item.setAttribute('role', 'presentation');
125                item.innerHTML = `<a class="kiss-tabs-nav-link" role="tab" id="${tabId}" aria-controls="${panelId}" index="${tabIndex}" tabindex="-1">${this.children[i].getAttribute('caption') || 'Tab'}</a>`;
126                this.nav.append(item);
127
128                this.tabs.push(this.children[i]);
129
130                // ARIA: panel
131                this.children[i].setAttribute('role', 'tabpanel');
132                this.children[i].id = panelId;
133                this.children[i].setAttribute('aria-labelledby', tabId);
134                this.children[i].setAttribute('active', 'false');
135                this.children[i].setAttribute('aria-hidden', 'true');
136                item.setAttribute('active', 'false');
137            }
138        }
139
140        this.setIndex(this.activeIndex);
141    }
142});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.