@@ -58,97 +58,87 @@ const closeOtherDetails = function(keepOpenSet) {
5858} ;
5959
6060const highlightActiveOnPageLink = function ( hash ) {
61- const hashToUse = hash || location . hash ;
62- if ( ! hashToUse ) {
61+ // Use the passed hash parameter, not location.hash (which doesn't update with pushState)
62+ if ( ! hash ) {
6363 return ;
6464 }
6565
66- const activeHash = hashToUse . substring ( 1 ) ;
67- const allLinks = document . querySelectorAll ( 'a' ) ;
66+ const activeHash = hash . substring ( 1 ) ;
6867
69- for ( let i = 0 ; i < allLinks . length ; i ++ ) {
70- const link = allLinks [ i ] ;
71- link . classList . remove ( 'active' ) ;
72-
73- if ( link . parentElement && link . parentElement . parentElement && link . parentElement . parentElement . tagName === 'UL' ) {
74- link . parentElement . parentElement . classList . remove ( 'active' ) ;
68+ const updateTOC = ( ) => {
69+ // Remove active class from all TOC links
70+ const allLinks = document . querySelectorAll ( '.table-of-contents a' ) ;
71+ for ( let i = 0 ; i < allLinks . length ; i ++ ) {
72+ allLinks [ i ] . classList . remove ( 'table-of-contents__link--active' ) ;
7573 }
76- }
7774
78- const activeLinks = document . querySelectorAll ( "a[href='#" + activeHash + "']" ) ;
79- for ( let i = 0 ; i < activeLinks . length ; i ++ ) {
80- activeLinks [ i ] . classList . add ( 'active' ) ;
81- }
75+ // Add active class to matching TOC links
76+ const activeLinks = document . querySelectorAll ( ".table-of-contents a[href='#" + activeHash + "']" ) ;
77+ for ( let i = 0 ; i < activeLinks . length ; i ++ ) {
78+ activeLinks [ i ] . classList . add ( 'table-of-contents__link--active' ) ;
79+ }
80+ } ;
81+
82+ // Update immediately
83+ updateTOC ( ) ;
84+
85+ // Keep updating after scroll ends to override Docusaurus IntersectionObserver
86+ let scrollEndTimer ;
87+ const handleScrollEnd = ( ) => {
88+ clearTimeout ( scrollEndTimer ) ;
89+ scrollEndTimer = setTimeout ( ( ) => {
90+ updateTOC ( ) ;
91+ window . removeEventListener ( 'scroll' , handleScrollEnd ) ;
92+ } , 50 ) ;
93+ } ;
94+
95+ window . addEventListener ( 'scroll' , handleScrollEnd ) ;
96+ handleScrollEnd ( ) ;
8297} ;
8398
8499const highlightDetailsOnActiveHash = function ( activeHash , doNotOpen ) {
85100 const activeAnchors = document . querySelectorAll ( ".anchor[id='" + activeHash + "']" ) ;
86101 const detailsElements = document . querySelectorAll ( 'details' ) ;
87102 const activeSectionElements = document . querySelectorAll ( '.active-section' ) ;
88103
104+ // Clear all previous highlights
89105 for ( let i = 0 ; i < activeSectionElements . length ; i ++ ) {
90106 activeSectionElements [ i ] . classList . remove ( 'active-section' ) ;
91107 }
92108
93- for ( let i = 0 ; i < activeAnchors . length ; i ++ ) {
94- const headline = activeAnchors [ i ] . parentElement ;
95- const headlineRank = activeAnchors [ i ] . parentElement . nodeName . substr ( 1 ) ;
96- let el = headline ;
97-
98- while ( el ) {
99- if ( el . tagName !== 'BR' && el . tagName !== 'HR' ) {
100- el . classList . add ( 'active-section' ) ;
101- }
102- el = el . nextElementSibling ;
103-
104- if ( el ) {
105- const elRank = el . nodeName . substr ( 1 ) ;
106- if ( elRank > 0 && elRank <= headlineRank ) {
107- break ;
108- }
109- }
110- }
111- }
112-
113109 for ( let i = 0 ; i < detailsElements . length ; i ++ ) {
114110 detailsElements [ i ] . classList . remove ( 'active' ) ;
115111 }
116112
117- if ( activeAnchors . length > 0 ) {
118- for ( let i = 0 ; i < activeAnchors . length ; i ++ ) {
119- let element = activeAnchors [ i ] ;
120-
121- for ( ; element && element !== document ; element = element . parentElement ) {
122- if ( element . tagName === 'DETAILS' ) {
123- element . classList . add ( 'active' ) ;
124-
125- if ( ! doNotOpen ) {
126- element . open = true ;
127- element . setAttribute ( 'data-collapsed' , 'false' ) ;
128- const collapsibleContent = element . querySelector ( ':scope > div[style]' ) ;
129- if ( collapsibleContent ) {
130- collapsibleContent . style . display = 'block' ;
131- collapsibleContent . style . overflow = 'visible' ;
132- collapsibleContent . style . height = 'auto' ;
133- }
134- }
135- }
136- }
137- }
113+ // Add active-section class only to the heading itself
114+ for ( let i = 0 ; i < activeAnchors . length ; i ++ ) {
115+ const headline = activeAnchors [ i ] . parentElement ;
116+ headline . classList . add ( 'active-section' ) ;
138117 }
139118
119+ // Find the target element and handle its details
140120 const targetElement = activeHash ? document . getElementById ( activeHash ) : null ;
141121 if ( targetElement ) {
142122 const parentDetails = getParentDetailsElements ( targetElement ) ;
143123 const keepOpenSet = new Set ( parentDetails ) ;
144124
125+ // Close other unrelated details
145126 if ( ! doNotOpen ) {
146127 closeOtherDetails ( keepOpenSet ) ;
147128 }
148129
130+ // Open parent details and mark only the DIRECT target detail as active
131+ let directTargetDetail = null ;
149132 for ( let i = 0 ; i < parentDetails . length ; i ++ ) {
150133 const element = parentDetails [ i ] ;
151- element . classList . add ( 'active' ) ;
134+
135+ // The first (innermost) detail that contains the target is the direct one
136+ if ( i === 0 ) {
137+ directTargetDetail = element ;
138+ element . classList . add ( 'active' ) ;
139+ }
140+
141+ // Open all parent details
152142 if ( ! doNotOpen ) {
153143 element . open = true ;
154144 element . setAttribute ( 'data-collapsed' , 'false' ) ;
@@ -209,10 +199,10 @@ export function onRouteDidUpdate({ location, previousLocation }) {
209199 handleHashNavigation ( location . hash ) ;
210200
211201 if ( targetEl ) {
212- setTimeout ( ( ) => {
213- const y = targetEl . getBoundingClientRect ( ) . top + window . scrollY - 280 ;
202+ requestAnimationFrame ( ( ) => {
203+ const y = targetEl . getBoundingClientRect ( ) . top + window . scrollY - 100 ;
214204 window . scrollTo ( { top : y , behavior : 'smooth' } ) ;
215- } , 150 ) ;
205+ } ) ;
216206 }
217207 }
218208 } ) ;
@@ -229,18 +219,19 @@ export function onRouteDidUpdate({ location, previousLocation }) {
229219 handleHashNavigation ( hash ) ;
230220
231221 if ( targetEl ) {
232- setTimeout ( ( ) => {
233- const y = targetEl . getBoundingClientRect ( ) . top + window . scrollY - 280 ;
222+ requestAnimationFrame ( ( ) => {
223+ const y = targetEl . getBoundingClientRect ( ) . top + window . scrollY - 100 ;
234224 window . scrollTo ( { top : y , behavior : 'smooth' } ) ;
235- } , 150 ) ;
225+ } ) ;
236226 }
237227 } ) ;
238228
239229 // NOTE: hashchange only handles highlighting, NOT scrolling (to avoid race condition)
240230 // Scrolling is handled by DetailsClicksClient's universal click handler
241231 window . addEventListener ( 'hashchange' , function ( e ) {
232+ // Extract hash from newURL to handle pushState correctly
242233 const newHash = e . newURL ? e . newURL . split ( '#' ) [ 1 ] : '' ;
243- const hashToUse = newHash ? '#' + newHash : location . hash ;
234+ const hashToUse = newHash ? '#' + newHash : '' ;
244235
245236 if ( ! hashToUse ) {
246237 return ;
0 commit comments