PageSourceSearch

https://www.eventeny.com/js/Components/TabsComponent.js?v=1790189618

js eventeny.com collected 2026-09-24 08:55:35 UTC 7,492 bytes, 252 lines download raw bytes

1class BreadcrumbTabsComponent extends HTMLElement {
2    constructor() {
3        super();
4        this.tabs = [];
5        this.rendered = false;
6    }
7    
8    static get observedAttributes() {
9        return ['unsaved-warning', 'active-tab', 'tabs-config'];
10    }
11    
12    connectedCallback() {
13        this.loadTabsFromConfig();
14    }
15    
16    attributeChangedCallback(name, oldValue, newValue) {
17        if (oldValue === newValue) return;
18        
19        switch (name) {
20            case 'tabs-config':
21                this.loadTabsFromConfig();
22                break;
23            case 'active-tab':
24                const index = parseInt(newValue);
25                if (!isNaN(index)) {
26                    this.setActiveTab(index);
27                }
28                break;
29        }
30    }
31
32    
33    loadTabsFromConfig() {
34        const tabsConfigAttr = this.getAttribute('tabs-config');
35        if (!tabsConfigAttr) return;
36        
37
38        const tabsConfig = JSON.parse(tabsConfigAttr);
39        
40        this.tabs = tabsConfig.map(config => ({
41            label: config.title || config.label,
42            href: config.href,
43            active: config.active || false,
44            external: config.external || false,
45            target: config.target || '_self',
46            noWarning: config.noWarning || false
47        }));
48        
49        this.render();
50    }
51    
52
53    
54    render() {
55        this.innerHTML = this.renderTabs();
56        this.bindEvents();
57        this.rendered = true;
58    }
59    
60    renderTabs() {
61        if (this.tabs.length === 0) return '';
62        
63        const navStyle = `
64            display: flex;
65            align-items: center;
66            background: transparent;
67            padding: 5px 0;
68        `;
69        
70        let html = `<nav style="${navStyle}">`;
71        
72        this.tabs.forEach((tab, index) => {
73            if (!tab.label) return;
74            if (index > 0) {
75                const separatorStyle = `
76                    margin: 0 12px;
77                    color: rgba(126, 133, 132, 1);
78                    font-size: 18px;
79                `;
80                html += `<span style="${separatorStyle}">›</span>`;
81            }
82            
83            const baseStyle = `
84                color: rgba(2, 110, 128, 1);
85                text-decoration: none;
86                font-weight: 500;
87                padding: 0;
88                transition: all 0.3s ease;
89                font-size: 14px;
90                cursor: pointer;
91            `;
92            
93            const activeStyle = tab.active ? `
94                color: rgba(94, 100, 99, 1);
95                font-weight: 400;
96                cursor: default;
97            ` : '';
98            
99            const hoverStyle = tab.active ? '' : `
100                onmouseover="this.style.textDecoration='underline'"
101                onmouseout="this.style.textDecoration='none'"
102            `;
103            
104            if (tab.active) {
105                html += `<span style="${baseStyle}${activeStyle}" data-tab-index="${index}">`;
106                html += this.decodeHtml(tab.label);
107                if (tab.external) {
108                    const iconStyle = `font-size: 12px; margin-left: 4px; opacity: 0.7;`;
109                    html += ` <span style="${iconStyle}">↗</span>`;
110                }
111                html += '</span>';
112            } else {
113                const targetAttr = tab.target ? ` target="${tab.target}"` : '';
114                html += `<a href="${this.decodeHtml(tab.href)}" style="${baseStyle}" ${hoverStyle}${targetAttr} data-tab-index="${index}">`;
115                html += this.decodeHtml(tab.label);
116                if (tab.external) {
117                    const iconStyle = `font-size: 12px; margin-left: 4px; opacity: 0.7;`;
118                    html += ` <span style="${iconStyle}">↗</span>`;
119                }
120                html += '</a>';
121            }
122        });
123        
124        html += '</nav>';
125        return html;
126    }
127    
128    bindEvents() {
129        const links = this.querySelectorAll('a[data-tab-index]');
130        links.forEach((link) => {
131            link.addEventListener('click', (e) => {
132                e.preventDefault();
133                const tabIndex = parseInt(link.getAttribute('data-tab-index'));
134                this.handleTabClick(link, tabIndex);
135            });
136        });
137    }
138    
139    handleTabClick(clickedItem, tabIndex) {
140        const tab = this.tabs[tabIndex];
141        
142        if (!tab) return;
143        
144        const unsavedWarning = this.getAttribute('unsaved-warning') !== 'false';
145        
146        if (!tab.noWarning && unsavedWarning && !this.checkContentSaved()) {
147            if (!confirm('You have unsaved changes. Are you sure you want to leave?')) {
148                return;
149            }
150        }
151        
152        this.dispatchEvent(new CustomEvent('tab-changed', {
153            detail: { tabIndex: tabIndex, tab }
154        }));
155        
156        const href = tab.href;
157        if (href && href !== '#') {
158            if (tab.target === '_blank') {
159                window.open(href, '_blank');
160            } else {
161                window.location.href = href;
162            }
163        }
164    }
165    
166    checkContentSaved() {
167        return typeof window.isContentSaved !== 'undefined' ? window.isContentSaved : true;
168    }
169        
170    decodeHtml(text) {
171        if (!text) return '';
172        const map = { '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"', '&#039;': "'" };
173        return text.replace(/&amp;|&lt;|&gt;|&quot;|&#039;/g, (m) => map[m]);
174    }
175    
176    addTab(label, href, active = false, options = {}) {
177        const tab = {
178            label: label,
179            href: href,
180            active: active,
181            external: options.external || false,
182            target: options.target || '_self',
183            noWarning: options.noWarning || false
184        };
185        
186        if (active) {
187            this.tabs.forEach(t => t.active = false);
188        }
189        
190        this.tabs.push(tab);
191        
192        if (this.rendered) {
193            this.render();
194        }
195        
196        return this;
197    }
198    
199    addTabs(tabsArray) {
200        tabsArray.forEach(tab => {
201            this.addTab(tab.label, tab.href, tab.active, {
202                external: tab.external,
203                target: tab.target,
204                noWarning: tab.noWarning
205            });
206        });
207        return this;
208    }
209    
210    clearTabs() {
211        this.tabs = [];
212        if (this.rendered) this.render();
213        return this;
214    }
215    
216    updateTabs(newTabs) {
217        this.tabs = newTabs;
218        if (this.rendered) this.render();
219        return this;
220    }
221    
222    setActiveTab(index) {
223        this.tabs.forEach((tab, i) => {
224            this.tabs[i].active = i === index;
225        });
226        if (this.rendered) this.render();
227        return this;
228    }
229    
230    setActiveByUrl(url) {
231        this.tabs.forEach(tab => {
232            tab.active = tab.href === url;
233        });
234        if (this.rendered) this.render();
235        return this;
236    }
237    
238    removeTab(index) {
239        if (this.tabs[index]) {
240            this.tabs.splice(index, 1);
241            if (this.rendered) this.render();
242        }
243        return this;
244    }
245    
246    getTabs() { return [...this.tabs]; }
247    getActiveTabIndex() { return this.tabs.findIndex(tab => tab.active); }
248    hasTab(href) { return this.tabs.some(tab => tab.href === href); }
249}
250
251
252customElements.define('breadcrumb-tabs-component', BreadcrumbTabsComponent);

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.