@@ -7,13 +7,102 @@ const initCodeTabs = () => {
7
7
const { allowedRegions } = regionConfig ;
8
8
const tabQueryParameter = getQueryParameterByName ( 'tab' ) || getQueryParameterByName ( 'tabs' )
9
9
const codeTabParameters = allowedRegions . reduce ( ( k , v ) => ( { ...k , [ v ] : { } } ) , { } ) ;
10
+ let resizeTimeout ;
11
+ let currentActiveTab = null ; // Store the current active tab
12
+
13
+ const cleanupExistingTabs = ( ) => {
14
+ // Store current active tab before cleanup
15
+ const activeTab = document . querySelector ( '.code-tabs .nav-tabs li.active a' ) ;
16
+ if ( activeTab ) {
17
+ currentActiveTab = activeTab . getAttribute ( 'data-lang' ) ;
18
+ }
19
+
20
+ // Remove all existing tab navigation elements
21
+ document . querySelectorAll ( '.nav-tabs' ) . forEach ( navTabs => {
22
+ // Only remove if it's a child of a code-tabs container
23
+ if ( navTabs . closest ( '.code-tabs' ) ) {
24
+ navTabs . innerHTML = '' ;
25
+ }
26
+ } ) ;
27
+ }
28
+
29
+ const detectTabWrapping = ( ) => {
30
+ const tabContainers = document . querySelectorAll ( '.code-tabs' ) ;
31
+
32
+ tabContainers . forEach ( container => {
33
+ const tabsNav = container . querySelector ( '.nav-tabs' ) ;
34
+ if ( ! tabsNav ) return ;
35
+
36
+ const tabs = tabsNav . querySelectorAll ( 'li' ) ;
37
+ if ( tabs . length < 2 ) return ; // Need at least two tabs to wrap
38
+
39
+ const firstTab = tabs [ 0 ] ;
40
+ const lastTab = tabs [ tabs . length - 1 ] ;
41
+
42
+ // Store original state
43
+ const originalHasClass = container . classList . contains ( 'tabs-wrap-layout' ) ;
44
+
45
+ // Ensure measurement happens in the unwrapped state
46
+ container . classList . remove ( 'tabs-wrap-layout' ) ;
47
+
48
+ // Force layout recalculation
49
+ void tabsNav . offsetHeight ;
50
+
51
+ const firstTop = firstTab . offsetTop ;
52
+ const lastTop = lastTab . offsetTop ;
53
+ const heightDifference = lastTop - firstTop ;
54
+
55
+ // Determine if it *should* be wrapped based on measurement in unwrapped state
56
+ // Use a small buffer (e.g., 2px) to account for rendering variations
57
+ const shouldBeWrapped = heightDifference > 2 ;
58
+
59
+ // Apply the correct class if the state needs to change
60
+ if ( shouldBeWrapped !== originalHasClass ) {
61
+ if ( shouldBeWrapped ) {
62
+ container . classList . add ( 'tabs-wrap-layout' ) ;
63
+ } else {
64
+ container . classList . remove ( 'tabs-wrap-layout' ) ;
65
+ }
66
+ } else {
67
+ // If no change needed, ensure the class is restored if it was removed for measurement
68
+ if ( originalHasClass ) {
69
+ container . classList . add ( 'tabs-wrap-layout' ) ;
70
+ }
71
+ }
72
+ } ) ;
73
+ } ;
74
+
75
+ const debouncedDetectTabWrapping = ( ) => {
76
+ clearTimeout ( resizeTimeout ) ;
77
+ resizeTimeout = setTimeout ( detectTabWrapping , 150 ) ; // Increased debounce time
78
+ } ;
10
79
11
80
const init = ( ) => {
81
+ // Clean up existing tabs first
82
+ cleanupExistingTabs ( ) ;
83
+
12
84
renderCodeTabElements ( )
13
85
addEventListeners ( )
14
86
activateTabsOnLoad ( )
15
87
getContentTabHeight ( )
16
88
addObserversToCodeTabs ( )
89
+
90
+ // Initial detection with font loading check
91
+ if ( document . fonts && document . fonts . ready ) {
92
+ document . fonts . ready . then ( ( ) => {
93
+ // Force reflow after fonts are loaded
94
+ document . body . offsetHeight ;
95
+ detectTabWrapping ( ) ;
96
+ } ) ;
97
+ } else {
98
+ // Fallback for browsers without font loading API
99
+ detectTabWrapping ( ) ;
100
+ }
101
+
102
+ // Remove any existing resize listeners
103
+ window . removeEventListener ( 'resize' , debouncedDetectTabWrapping ) ;
104
+ // Add new resize listener
105
+ window . addEventListener ( 'resize' , debouncedDetectTabWrapping ) ;
17
106
}
18
107
19
108
/**
@@ -25,16 +114,26 @@ const initCodeTabs = () => {
25
114
const navTabsElement = codeTabsElement . querySelector ( '.nav-tabs' )
26
115
const tabContent = codeTabsElement . querySelector ( '.tab-content' )
27
116
const tabPaneNodeList = tabContent . querySelectorAll ( '.tab-pane' )
117
+
118
+ // Create a map to track unique tab titles
119
+ const uniqueTabs = new Map ( ) ;
120
+
28
121
tabPaneNodeList . forEach ( tabPane => {
29
122
const title = tabPane . getAttribute ( 'title' )
30
123
const lang = tabPane . getAttribute ( 'data-lang' )
31
- const li = document . createElement ( 'li' )
32
- const anchor = document . createElement ( 'a' )
33
- anchor . dataset . lang = lang
34
- anchor . href = '#'
35
- anchor . innerText = title
36
- li . appendChild ( anchor )
37
- navTabsElement . appendChild ( li )
124
+
125
+ // Only add the tab if we haven't seen this title before
126
+ if ( ! uniqueTabs . has ( title ) ) {
127
+ uniqueTabs . set ( title , true ) ;
128
+
129
+ const li = document . createElement ( 'li' )
130
+ const anchor = document . createElement ( 'a' )
131
+ anchor . dataset . lang = lang
132
+ anchor . href = '#'
133
+ anchor . innerText = title
134
+ li . appendChild ( anchor )
135
+ navTabsElement . appendChild ( li )
136
+ }
38
137
} )
39
138
} )
40
139
}
@@ -56,12 +155,17 @@ const initCodeTabs = () => {
56
155
57
156
if ( activeLangTab && activePane ) {
58
157
// Hide all tab content and remove 'active' class from all tab elements.
59
- tabsList . forEach ( tab => tab . classList . remove ( 'active' ) )
60
- tabPanesList . forEach ( pane => pane . classList . remove ( 'active' , 'show' ) )
158
+ // Also, remove any inline display style to let CSS classes control visibility.
159
+ tabsList . forEach ( tab => tab . classList . remove ( 'active' ) ) ;
160
+ tabPanesList . forEach ( pane => {
161
+ pane . classList . remove ( 'active' , 'show' ) ;
162
+ pane . style . removeProperty ( 'display' ) ;
163
+ } ) ;
61
164
62
165
// Show the active content and highlight active tab.
63
- activeLangTab . closest ( 'li' ) . classList . add ( 'active' )
64
- activePane . classList . add ( 'active' , 'show' )
166
+ activeLangTab . closest ( 'li' ) . classList . add ( 'active' ) ;
167
+ activePane . classList . add ( 'active' , 'show' ) ;
168
+ activePane . style . removeProperty ( 'display' ) ;
65
169
}
66
170
67
171
const currentActiveTab = codeTabsElement . querySelector ( '.nav-tabs li.active' )
@@ -75,14 +179,17 @@ const initCodeTabs = () => {
75
179
firstTabPane . classList . add ( 'active' , 'show' )
76
180
}
77
181
} )
182
+
183
+ // Run tab wrapping detection after tab activation with slight delay
184
+ setTimeout ( detectTabWrapping , 10 ) ;
78
185
}
79
186
80
187
updateUrl ( activeLang )
81
188
}
82
189
83
190
const scrollToAnchor = ( tab , anchorname ) => {
84
191
const anchor = document . querySelectorAll ( `[data-lang='${ tab } '] ${ anchorname } ` ) [ 0 ] ;
85
-
192
+
86
193
if ( anchor ) {
87
194
anchor . scrollIntoView ( ) ;
88
195
} else {
@@ -91,6 +198,16 @@ const initCodeTabs = () => {
91
198
}
92
199
93
200
const activateTabsOnLoad = ( ) => {
201
+ // If we have a stored active tab from before reinitialization, use that
202
+ if ( currentActiveTab ) {
203
+ const selectedLanguageTab = document . querySelector ( `a[data-lang="${ currentActiveTab } "]` ) ;
204
+ if ( selectedLanguageTab ) {
205
+ activateCodeTab ( selectedLanguageTab ) ;
206
+ return ;
207
+ }
208
+ }
209
+
210
+ // Otherwise use URL parameter or fall back to first tab
94
211
const firstTab = document . querySelectorAll ( '.code-tabs .nav-tabs a' ) . item ( 0 )
95
212
if ( tabQueryParameter ) {
96
213
const selectedLanguageTab = document . querySelector ( `a[data-lang="${ tabQueryParameter } "]` ) ;
@@ -102,7 +219,7 @@ const initCodeTabs = () => {
102
219
scrollToAnchor ( tabQueryParameter , window . location . hash ) ;
103
220
} , 300 ) ;
104
221
}
105
- } else {
222
+ } else {
106
223
activateCodeTab ( firstTab )
107
224
}
108
225
} else {
@@ -124,7 +241,7 @@ const initCodeTabs = () => {
124
241
125
242
activateCodeTab ( link ) ;
126
243
getContentTabHeight ( ) ;
127
-
244
+
128
245
// ensures page doesnt jump when navigating tabs.
129
246
// takes into account page shifting that occurs due to navigating tabbed content w/ height changes.
130
247
// implementation of synced tabs from https://github.com/withastro/starlight/blob/main/packages/starlight/user-components/Tabs.astro
@@ -223,7 +340,29 @@ const initCodeTabs = () => {
223
340
} )
224
341
}
225
342
343
+ /**
344
+ * If Cdocs is running on this page,
345
+ * tell it to refresh the tabs when content changes
346
+ */
347
+ if ( window . clientFiltersManager ) {
348
+ // Update the tabs after the page is initially rendered
349
+ clientFiltersManager . registerHook ( 'afterReveal' , ( ) => {
350
+ // Reset stored tab on initial reveal
351
+ currentActiveTab = null ;
352
+ init ( ) ;
353
+ } ) ;
354
+
355
+ // Update the tabs after the page is re-rendered
356
+ clientFiltersManager . registerHook ( 'afterRerender' , init ) ;
357
+ }
358
+
226
359
init ( )
360
+
361
+ // Add window load event to handle final detection after everything is loaded
362
+ window . addEventListener ( 'load' , ( ) => {
363
+ // Final check after page is fully loaded (all resources)
364
+ setTimeout ( detectTabWrapping , 50 ) ;
365
+ } ) ;
227
366
}
228
367
229
- export default initCodeTabs
368
+ export default initCodeTabs
0 commit comments