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 = { '&': '&', '<': '<', '>': '>', '"': '"', ''': "'" }; 173 return text.replace(/&|<|>|"|'/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.