1document.addEventListener('DOMContentLoaded', function () { 2 // ===== TAB FUNCTIONALITY ===== 3 // Handle each tab navigation list independently 4 const tabLists = document.querySelectorAll('ul[role="tablist"]'); 5 6 tabLists.forEach((tabList, listIndex) => { 7 const tabs = tabList.querySelectorAll('[data-bs-toggle="pill"]'); 8 if (tabs.length === 0) return; 9 10 // Find the container for tab panes 11 // For FIDS tabs, look for specific container 12 let paneContainer = null; 13 14 if (tabList.hasAttribute('data-fids-tabs')) { 15 // For FIDS, the tab content has a specific ID 16 paneContainer = document.getElementById('v-pills-fids-tabContent'); 17 } else { 18 // For tabs-widget, look within the parent container 19 const widgetContainer = tabList.closest('.tabs-widget'); 20 if (widgetContainer) { 21 paneContainer = widgetContainer.querySelector('.tab-content'); 22 } 23 24 if (!paneContainer) { 25 // For regular tabs, it's the next sibling (.tab-content) 26 paneContainer = tabList.nextElementSibling; 27 if (!paneContainer || !paneContainer.classList.contains('tab-content')) { 28 // Try to find tab-widget__content in parent 29 paneContainer = tabList 30 .closest('.container') 31 ?.querySelector('.tab-widget__content, .tab-content'); 32 } 33 } 34 } 35 36 if (!paneContainer) { 37 console.warn('Could not find pane container for tab list', tabList); 38 return; 39 } 40 41 // Get all tab panes for this specific group 42 const tabPanes = [];
43 tabs.forEach((tab) => { 44 const targetId = tab.getAttribute('data-bs-target')?.substring(1); 45 if (targetId) { 46 const pane = paneContainer.querySelector(`#${targetId}`); 47 if (pane) { 48 tabPanes.push(pane); 49 // Hide all panes initially 50 pane.classList.add('hidden'); 51 } 52 } 53 }); 54 55 // Deactivate all tabs 56 tabs.forEach((tab) => { 57 tab.classList.remove('active-tab'); 58 tab.setAttribute('aria-selected', 'false'); 59 }); 60 61 // Create a unique identifier for this tab widget based on its tabs 62 // This ensures each widget has its own storage key even with duplicate IDs 63 const tabIdentifiers = Array.from(tabs).map(tab => 64 tab.getAttribute('data-bs-target')?.substring(1) || '' 65 ).join('_'); 66 const widgetSignature = tabIdentifiers.substring(0, 50); // Limit length 67 68 // Check for saved state 69 const urlParams = new URLSearchParams(window.location.search); 70 const deeplinkedTab = urlParams.get('selectedTab'); 71 const storageKey = `activeTab_${widgetSignature}_${listIndex}`; 72 const savedTabId = localStorage.getItem(storageKey); 73 let activeTabFound = false; 74 75 // Try to activate saved/deeplinked tab 76 const targetToActivate = deeplinkedTab || savedTabId; 77 if (targetToActivate) {
78 tabs.forEach((tab, index) => { 79 const targetId = tab.getAttribute('data-bs-target')?.substring(1); 80 if (targetId === targetToActivate) { 81 tab.classList.add('active-tab'); 82 tab.setAttribute('aria-selected', 'true'); 83 if (tabPanes[index]) { 84 tabPanes[index].classList.remove('hidden'); 85 activeTabFound = true; 86 } 87 } 88 }); 89 } 90 91 // If no active tab, activate the first one 92 if (!activeTabFound && tabs.length > 0) { 93 tabs[0].classList.add('active-tab'); 94 tabs[0].setAttribute('aria-selected', 'true'); 95 if (tabPanes[0]) { 96 tabPanes[0].classList.remove('hidden'); 97 } 98 } 99 100 // Add click handlers 101 tabs.forEach((tab, tabIndex) => { 102 tab.addEventListener('click', function (e) { 103 e.preventDefault(); 104 105 const clickedTargetId = 106 this.getAttribute('data-bs-target')?.substring(1); 107 if (!clickedTargetId) return; 108 109 // Check if already active to avoid unnecessary work 110 if (this.classList.contains('active-tab')) return; 111 112 // Use requestAnimationFrame to batch DOM updates 113 requestAnimationFrame(() => { 114 // Save to localStorage with the unique storage key 115 localStorage.setItem(storageKey, clickedTargetId); 116 117 // Batch all DOM reads first 118 const updates = [];
119 tabs.forEach((t, i) => { 120 updates.push({ tab: t, pane: tabPanes[i], isTarget: i === tabIndex }); 121 }); 122 123 // Then batch all DOM writes 124 updates.forEach(({ tab, pane, isTarget }) => { 125 if (isTarget) { 126 tab.classList.add('active-tab'); 127 tab.setAttribute('aria-selected', 'true'); 128 if (pane) pane.classList.remove('hidden'); 129 } else { 130 tab.classList.remove('active-tab'); 131 tab.setAttribute('aria-selected', 'false'); 132 if (pane) pane.classList.add('hidden'); 133 } 134 }); 135 }); 136 }); 137 }); 138 }); 139 140 // ===== ACCORDION FUNCTIONALITY ===== 141 const accordionButtons = document.querySelectorAll( 142 '.accordion-header button' 143 ); 144 145 // Initialize accordions 146 accordionButtons.forEach((button) => { 147 const isExpanded = button.getAttribute('aria-expanded') === 'true'; 148 const targetId = button.getAttribute('data-bs-target')?.substring(1); 149 if (!targetId) return; 150 151 const content = document.getElementById(targetId); 152 if (!content) return; 153 154 // Find the icon span (first span with class containing w-7 h-7) 155 const iconSpan = button.querySelector('accordion-item__button-icon'); 156 // Find the h4 heading with text-primary class 157 const headingText = button.querySelector('accordion-item__heading'); 158 159 // Set initial state 160 if (isExpanded) { 161 button.classList.add('active-tab'); 162 163 // Change icon color to deep-strata-brand if expanded 164 if (iconSpan) { 165 iconSpan.classList.remove('text-primary'); 166 iconSpan.classList.add('text-deep-strata-brand'); 167 } 168 169 // Change heading text color to deep-strata-brand if expanded 170 if (headingText) { 171 headingText.classList.remove('text-primary'); 172 headingText.classList.add('text-deep-strata-brand'); 173 } 174 175 // Target the arrow specifically (assume it's the last SVG) 176 const allSvgs = button.querySelectorAll('svg'); 177 const arrowSvg = allSvgs[allSvgs.length - 1]; 178 if (arrowSvg) arrowSvg.classList.add('rotate-180'); 179 180 content.style.display = 'block'; 181 } else { 182 content.style.display = 'none'; 183 } 184 185 // Add click handler - only toggle the clicked panel 186 button.addEventListener('click', function () { 187 const isCurrentlyExpanded = this.getAttribute('aria-expanded') === 'true'; 188 const clickedPanelId = this.getAttribute('data-bs-target')?.substring(1); 189 if (!clickedPanelId) return; 190 191 const clickedPanel = document.getElementById(clickedPanelId); 192 if (!clickedPanel) return; 193 194 // Find the icon span (first span with class containing w-7 h-7) 195 const iconSpan = this.querySelector('.accordion-item__button-icon'); 196 // Find the h4 heading with text-primary or text-deep-strata-brand class 197 const headingText = this.querySelector('h4'); 198 199 // Toggle this panel only 200 if (isCurrentlyExpanded) { 201 // Close this panel 202 this.setAttribute('aria-expanded', 'false'); 203 this.classList.remove('active-tab'); 204 205 // Change icon color back to primary when collapsed 206 if (iconSpan) { 207 iconSpan.classList.remove('text-deep-strata-brand'); 208 iconSpan.classList.add('text-primary'); 209 } 210 211 // Change heading text color back to primary when collapsed 212 if (headingText) { 213 headingText.classList.remove('text-deep-strata-brand'); 214 headingText.classList.add('text-primary'); 215 } 216 217 // Target the arrow specifically (assume it's the last SVG) 218 const allSvgs = this.querySelectorAll('svg'); 219 const arrowSvg = allSvgs[allSvgs.length - 1]; 220 if (arrowSvg) arrowSvg.classList.remove('rotate-180'); 221 222 clickedPanel.style.display = 'none'; 223 } else { 224 // Open this panel 225 this.setAttribute('aria-expanded', 'true'); 226 this.classList.add('active-tab'); 227 228 // Change icon color to deep-strata-brand when expanded 229 if (iconSpan) { 230 iconSpan.classList.remove('text-primary'); 231 iconSpan.classList.add('text-deep-strata-brand'); 232 } 233 234 // Change heading text color to deep-strata-brand when expanded 235 if (headingText) { 236 headingText.classList.remove('text-primary'); 237 headingText.classList.add('text-deep-strata-brand'); 238 } 239 240 // Target the arrow specifically (assume it's the last SVG) 241 const allSvgs = this.querySelectorAll('svg'); 242 const arrowSvg = allSvgs[allSvgs.length - 1]; 243 if (arrowSvg) arrowSvg.classList.add('rotate-180'); 244 245 clickedPanel.style.display = 'block'; 246 } 247 }); 248 }); 249 250 // Default: Open first accordion if none is open 251 if (accordionButtons.length > 0) { 252 let hasExpandedAccordion = false;
253 accordionButtons.forEach((btn) => { 254 if (btn.getAttribute('aria-expanded') === 'true') { 255 hasExpandedAccordion = true; 256 } 257 }); 258 259 if (!hasExpandedAccordion) { 260 const firstButton = accordionButtons[0]; 261 const firstPanelId = firstButton 262 .getAttribute('data-bs-target') 263 ?.substring(1); 264 265 if (firstPanelId) { 266 const firstPanel = document.getElementById(firstPanelId); 267 if (firstPanel) { 268 firstButton.setAttribute('aria-expanded', 'true'); 269 firstButton.classList.add('active-tab'); 270 271 // Change icon color for first accordion if automatically opened 272 const iconSpan = firstButton.querySelector( 273 '.accordion-item__button-icon' 274 ); 275 if (iconSpan) { 276 iconSpan.classList.remove('text-primary'); 277 iconSpan.classList.add('text-deep-strata-brand'); 278 } 279 280 // Change heading text color for first accordion if automatically opened 281 const headingText = firstButton.querySelector('h4'); 282 if (headingText) { 283 headingText.classList.remove('text-primary'); 284 headingText.classList.add('text-deep-strata-brand'); 285 } 286 287 // Target the arrow specifically (assume it's the last SVG) 288 const allSvgs = firstButton.querySelectorAll('svg'); 289 const arrowSvg = allSvgs[allSvgs.length - 1]; 290 if (arrowSvg) arrowSvg.classList.add('rotate-180'); 291 292 firstPanel.style.display = 'block'; 293 } 294 } 295 } 296 } 297});
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.