|
56 | 56 | </style> |
57 | 57 |
|
58 | 58 | <script> |
59 | | - class DockitTabs { |
60 | | - private container: HTMLElement | null; |
61 | | - private tabsItemsContainer: HTMLElement | null; |
62 | | - private tabsNavContainer: HTMLElement | null; |
63 | | - private tabsContentContainer: HTMLElement | null; |
64 | | - private activeTabNumber: number = 1; |
65 | | - |
66 | | - constructor() { |
67 | | - this.container = document.querySelector('.dockit-tabs-container'); |
68 | | - this.tabsItemsContainer = document.querySelector('.dockit-tabs-items'); |
69 | | - this.tabsNavContainer = document.querySelector('.dockit-tabs-nav'); |
70 | | - this.tabsContentContainer = document.querySelector('.dockit-tabs-content'); |
71 | | - |
72 | | - this.injectCSS(); |
73 | | - this.init(); |
74 | | - } |
75 | | - |
76 | | - private injectCSS(): void { |
| 59 | + function initDockitTabs() { |
| 60 | + // Inject CSS only once |
| 61 | + if (!document.getElementById('dockit-tabs-styles')) { |
77 | 62 | const style = document.createElement('style'); |
| 63 | + style.id = 'dockit-tabs-styles'; |
78 | 64 | style.textContent = ` |
79 | 65 | .dockit-tabs-nav .dockit-tab-nav { |
80 | 66 | flex: 1; |
|
192 | 178 | document.head.appendChild(style); |
193 | 179 | } |
194 | 180 |
|
195 | | - private init(): void { |
196 | | - if (!this.container || !this.tabsItemsContainer) return; |
197 | | - setTimeout(() => this.setupTabs(), 100); |
198 | | - } |
| 181 | + // Initialize all tab containers |
| 182 | + const containers = document.querySelectorAll('.dockit-tabs-container'); |
| 183 | + |
| 184 | + containers.forEach((container) => { |
| 185 | + const tabsItemsContainer = container.querySelector('.dockit-tabs-items'); |
| 186 | + const tabsNavContainer = container.querySelector('.dockit-tabs-nav'); |
| 187 | + const tabsContentContainer = container.querySelector('.dockit-tabs-content'); |
| 188 | + |
| 189 | + if (!tabsItemsContainer || !tabsNavContainer || !tabsContentContainer) return; |
199 | 190 |
|
200 | | - private setupTabs(): void { |
201 | | - const tabItems = this.tabsItemsContainer?.querySelectorAll('.dockit-tab-item'); |
202 | | - if (!tabItems || tabItems.length === 0) return; |
| 191 | + // Skip if already initialized |
| 192 | + if (tabsNavContainer.children.length > 0) return; |
203 | 193 |
|
| 194 | + const tabItems = tabsItemsContainer.querySelectorAll('.dockit-tab-item'); |
| 195 | + if (tabItems.length === 0) return; |
| 196 | + |
| 197 | + let activeTabNumber = 1; |
| 198 | + |
| 199 | + // Setup tabs |
204 | 200 | tabItems.forEach((item, index) => { |
205 | 201 | const navElement = item.querySelector('.dockit-tab-nav'); |
206 | 202 | const contentElement = item.querySelector('.dockit-tab-content'); |
207 | 203 |
|
208 | | - if (navElement && contentElement && this.tabsNavContainer && this.tabsContentContainer) { |
209 | | - const navClone = navElement.cloneNode(true) as Element; |
210 | | - const contentClone = contentElement.cloneNode(true) as Element; |
| 204 | + if (navElement && contentElement) { |
| 205 | + const navClone = navElement.cloneNode(true); |
| 206 | + const contentClone = contentElement.cloneNode(true); |
211 | 207 |
|
212 | 208 | const isActive = index === 0; |
213 | 209 | navClone.setAttribute('data-active', isActive.toString()); |
214 | 210 | contentClone.setAttribute('data-active', isActive.toString()); |
215 | 211 |
|
216 | | - this.tabsNavContainer.appendChild(navClone); |
217 | | - this.tabsContentContainer.appendChild(contentClone); |
| 212 | + tabsNavContainer.appendChild(navClone); |
| 213 | + tabsContentContainer.appendChild(contentClone); |
218 | 214 | } |
219 | 215 | }); |
220 | 216 |
|
221 | | - setTimeout(() => this.bindEvents(), 100); |
222 | | - } |
223 | | - |
224 | | - private bindEvents(): void { |
225 | | - if (!this.tabsNavContainer || !this.tabsContentContainer) return; |
226 | | - |
227 | | - const tabButtons = this.tabsNavContainer.querySelectorAll('.tab-button'); |
| 217 | + // Bind events |
| 218 | + const tabButtons = tabsNavContainer.querySelectorAll('.tab-button'); |
228 | 219 | tabButtons.forEach((button) => { |
229 | 220 | button.addEventListener('click', (e) => { |
230 | 221 | e.preventDefault(); |
231 | | - const tabNumber = parseInt((button as HTMLElement).dataset.tab || '1'); |
232 | | - this.switchTab(tabNumber); |
| 222 | + const tabNumber = parseInt(button.dataset.tab || '1'); |
| 223 | + |
| 224 | + if (tabNumber === activeTabNumber) return; |
| 225 | + |
| 226 | + // Deactivate all tabs |
| 227 | + tabsNavContainer.querySelectorAll('.dockit-tab-nav').forEach((nav) => { |
| 228 | + nav.setAttribute('data-active', 'false'); |
| 229 | + }); |
| 230 | + |
| 231 | + tabsContentContainer.querySelectorAll('.dockit-tab-content').forEach((content) => { |
| 232 | + content.setAttribute('data-active', 'false'); |
| 233 | + content.style.display = 'none'; |
| 234 | + }); |
| 235 | + |
| 236 | + // Activate selected tab |
| 237 | + const targetNav = tabsNavContainer |
| 238 | + .querySelector(`[data-tab="${tabNumber}"]`) |
| 239 | + ?.closest('.dockit-tab-nav'); |
| 240 | + if (targetNav) { |
| 241 | + targetNav.setAttribute('data-active', 'true'); |
| 242 | + } |
| 243 | + |
| 244 | + const targetContent = tabsContentContainer.querySelector( |
| 245 | + `[data-panel="${tabNumber}"]` |
| 246 | + ); |
| 247 | + if (targetContent) { |
| 248 | + targetContent.style.display = 'flex'; |
| 249 | + targetContent.setAttribute('data-active', 'true'); |
| 250 | + } |
| 251 | + |
| 252 | + activeTabNumber = tabNumber; |
233 | 253 | }); |
234 | 254 | }); |
235 | | - } |
236 | | - |
237 | | - private switchTab(tabNumber: number): void { |
238 | | - if (tabNumber === this.activeTabNumber) return; |
239 | | - |
240 | | - // Deactivate all tabs |
241 | | - this.tabsNavContainer?.querySelectorAll('.dockit-tab-nav').forEach((nav) => { |
242 | | - nav.setAttribute('data-active', 'false'); |
243 | | - }); |
244 | | - |
245 | | - this.tabsContentContainer?.querySelectorAll('.dockit-tab-content').forEach((content) => { |
246 | | - content.setAttribute('data-active', 'false'); |
247 | | - (content as HTMLElement).style.display = 'none'; |
248 | | - }); |
249 | | - |
250 | | - // Activate selected tab |
251 | | - const targetNav = this.tabsNavContainer |
252 | | - ?.querySelector(`[data-tab="${tabNumber}"]`) |
253 | | - ?.closest('.dockit-tab-nav'); |
254 | | - if (targetNav) { |
255 | | - targetNav.setAttribute('data-active', 'true'); |
256 | | - } |
257 | | - |
258 | | - const targetContent = this.tabsContentContainer?.querySelector( |
259 | | - `[data-panel="${tabNumber}"]` |
260 | | - ) as HTMLElement; |
261 | | - if (targetContent) { |
262 | | - targetContent.style.display = 'flex'; |
263 | | - targetContent.setAttribute('data-active', 'true'); |
264 | | - } |
265 | | - |
266 | | - this.activeTabNumber = tabNumber; |
267 | | - } |
| 255 | + }); |
268 | 256 | } |
269 | 257 |
|
| 258 | + // Initialize on load |
270 | 259 | if (document.readyState === 'loading') { |
271 | | - document.addEventListener('DOMContentLoaded', () => new DockitTabs()); |
| 260 | + document.addEventListener('DOMContentLoaded', initDockitTabs); |
272 | 261 | } else { |
273 | | - new DockitTabs(); |
| 262 | + initDockitTabs(); |
274 | 263 | } |
| 264 | + |
| 265 | + // Re-initialize on page show (handles bfcache) |
| 266 | + window.addEventListener('pageshow', () => { |
| 267 | + initDockitTabs(); |
| 268 | + }); |
| 269 | + |
| 270 | + // Re-initialize after Astro page transitions (if enabled in the future) |
| 271 | + document.addEventListener('astro:page-load', () => { |
| 272 | + initDockitTabs(); |
| 273 | + }); |
275 | 274 | </script> |
0 commit comments