@@ -30,18 +30,49 @@ document$.subscribe(function() {
3030 loadingOverlay . classList . remove ( "active" ) ;
3131 } ;
3232
33- // Find the best anchor element and calculate offset from it
33+ // Find the tab container currently visible in the viewport
3434 const findScrollAnchor = ( ) => {
35- const headings = document . querySelectorAll ( '.md-content__inner h1, .md-content__inner h2, .md-content__inner h3, .md-content__inner h4, .md-content__inner h5, .md-content__inner h6' ) ;
36- const currentScrollY = window . scrollY ;
3735 const headerOffset = 80 ; // Account for sticky header
36+ const viewportTop = window . scrollY + headerOffset ;
37+ const viewportCenter = viewportTop + ( window . innerHeight / 3 ) ; // Upper third of visible area
38+
39+ // First, try to find a tabbed container that's currently in view
40+ const tabbedContainers = document . querySelectorAll ( '.md-typeset .tabbed-set' ) ;
41+ let bestContainer = null ;
42+ let bestDistance = Infinity ;
43+
44+ for ( const container of tabbedContainers ) {
45+ const rect = container . getBoundingClientRect ( ) ;
46+ const containerTop = rect . top + window . scrollY ;
47+ const containerBottom = containerTop + rect . height ;
48+
49+ // Check if this container is visible in the viewport
50+ if ( containerBottom > viewportTop && containerTop < viewportTop + window . innerHeight ) {
51+ // Find the container closest to the top of the viewport
52+ const distance = Math . abs ( containerTop - viewportTop ) ;
53+ if ( distance < bestDistance ) {
54+ bestDistance = distance ;
55+ bestContainer = container ;
56+ }
57+ }
58+ }
59+
60+ if ( bestContainer ) {
61+ const containerTop = bestContainer . getBoundingClientRect ( ) . top + window . scrollY ;
62+ return {
63+ element : bestContainer ,
64+ offset : window . scrollY - containerTop ,
65+ type : 'tabbed-set'
66+ } ;
67+ }
3868
69+ // Fallback: find the nearest heading
70+ const headings = document . querySelectorAll ( '.md-content__inner h1, .md-content__inner h2, .md-content__inner h3, .md-content__inner h4, .md-content__inner h5, .md-content__inner h6' ) ;
3971 let anchorElement = null ;
4072
41- // Find the last heading that is above or at the current scroll position
4273 for ( const heading of headings ) {
4374 const headingTop = heading . getBoundingClientRect ( ) . top + window . scrollY ;
44- if ( headingTop <= currentScrollY + headerOffset + 50 ) {
75+ if ( headingTop <= viewportCenter ) {
4576 anchorElement = heading ;
4677 } else {
4778 break ;
@@ -52,7 +83,8 @@ document$.subscribe(function() {
5283 const anchorTop = anchorElement . getBoundingClientRect ( ) . top + window . scrollY ;
5384 return {
5485 element : anchorElement ,
55- offset : currentScrollY - anchorTop
86+ offset : window . scrollY - anchorTop ,
87+ type : 'heading'
5688 } ;
5789 }
5890
@@ -63,17 +95,28 @@ document$.subscribe(function() {
6395 const restoreScrollPosition = ( anchor ) => {
6496 if ( ! anchor || ! anchor . element ) return ;
6597
66- // Wait for DOM to reflow after tab switch
98+ // Wait for DOM to reflow after tab switch - use multiple frames for large content changes
6799 requestAnimationFrame ( ( ) => {
68100 requestAnimationFrame ( ( ) => {
69- const newAnchorTop = anchor . element . getBoundingClientRect ( ) . top + window . scrollY ;
70- const newScrollY = newAnchorTop + anchor . offset ;
71-
72- window . scroll ( {
73- top : Math . max ( 0 , newScrollY ) ,
74- left : 0 ,
75- behavior : "instant"
76- } ) ;
101+ setTimeout ( ( ) => {
102+ const newAnchorTop = anchor . element . getBoundingClientRect ( ) . top + window . scrollY ;
103+
104+ // For tabbed containers, scroll to show the container at the same relative position
105+ // For headings, restore the exact offset
106+ let newScrollY ;
107+ if ( anchor . type === 'tabbed-set' ) {
108+ // Keep the container at roughly the same position relative to viewport top
109+ newScrollY = newAnchorTop + Math . min ( anchor . offset , 0 ) ;
110+ } else {
111+ newScrollY = newAnchorTop + anchor . offset ;
112+ }
113+
114+ window . scroll ( {
115+ top : Math . max ( 0 , newScrollY ) ,
116+ left : 0 ,
117+ behavior : "instant"
118+ } ) ;
119+ } , 50 ) ; // Additional delay for large DOM changes
77120 } ) ;
78121 } ) ;
79122 } ;
@@ -110,6 +153,33 @@ document$.subscribe(function() {
110153 switchToTab ( initialLang , false , null ) ;
111154 } ) ;
112155
156+ // Flag to prevent circular updates when we programmatically click tabs
157+ let isToggleSwitching = false ;
158+
159+ // Sync toggle button state based on which tab is active
160+ const syncToggleState = ( tabName ) => {
161+ if ( isToggleSwitching ) return ; // Don't sync if we triggered the change
162+
163+ const isYaml = tabName . toLowerCase ( ) === LANG_B . toLowerCase ( ) ;
164+ if ( switchInput . checked !== isYaml ) {
165+ switchInput . checked = isYaml ;
166+ localStorage . setItem ( STORAGE_KEY , isYaml ? LANG_B : LANG_A ) ;
167+ }
168+ } ;
169+
170+ // Listen for clicks on tab labels to sync toggle state
171+ const tabLabels = document . querySelectorAll ( ".md-typeset .tabbed-labels > label" ) ;
172+ tabLabels . forEach ( label => {
173+ label . addEventListener ( "click" , ( ) => {
174+ const tabName = label . textContent . trim ( ) ;
175+ // Only sync if it's a Table or YAML tab
176+ if ( tabName . toLowerCase ( ) === LANG_A . toLowerCase ( ) ||
177+ tabName . toLowerCase ( ) === LANG_B . toLowerCase ( ) ) {
178+ syncToggleState ( tabName ) ;
179+ }
180+ } ) ;
181+ } ) ;
182+
113183 if ( ! switchInput . hasAttribute ( 'data-listener-attached' ) ) {
114184 switchInput . addEventListener ( "change" , ( event ) => {
115185 const targetLang = event . target . checked ? LANG_B : LANG_A ;
@@ -120,13 +190,17 @@ document$.subscribe(function() {
120190 // Show loading spinner
121191 showLoading ( ) ;
122192
193+ // Set flag to prevent circular sync
194+ isToggleSwitching = true ;
195+
123196 // Use setTimeout to allow the spinner to render before switching tabs
124197 setTimeout ( ( ) => {
125198 switchToTab ( targetLang , true , scrollAnchor ) ;
126199 localStorage . setItem ( STORAGE_KEY , targetLang ) ;
127200
128- // Hide loading spinner after scroll restoration completes
201+ // Reset flag after tabs have switched
129202 setTimeout ( ( ) => {
203+ isToggleSwitching = false ;
130204 hideLoading ( ) ;
131205 } , 100 ) ;
132206 } , 50 ) ;
0 commit comments