@@ -47,8 +47,17 @@ window.document.addEventListener("DOMContentLoaded", function (_event) {
4747 // perform any highlighting
4848 highlight ( escapeRegExp ( query ) , mainEl ) ;
4949
50- // activate tabs that contain highlighted matches
51- activateTabsWithMatches ( mainEl ) ;
50+ // Activate tabs that contain highlighted matches on pageshow rather than
51+ // DOMContentLoaded. tabsets.js (loaded as a module) registers its pageshow
52+ // handler during module execution, before DOMContentLoaded. By registering
53+ // ours during DOMContentLoaded, listener ordering guarantees we run after
54+ // tabsets.js restores tab state from localStorage — so search activation
55+ // wins over stored tab preference.
56+ window . addEventListener ( "pageshow" , function ( event ) {
57+ if ( ! event . persisted ) {
58+ activateTabsWithMatches ( mainEl ) ;
59+ }
60+ } , { once : true } ) ;
5261
5362 // fix up the URL to remove the q query param
5463 const replacementUrl = new URL ( window . location ) ;
@@ -1133,21 +1142,20 @@ function escapeRegExp(string) {
11331142
11341143// After search highlighting, activate any tabs whose panes contain <mark> matches.
11351144// This ensures that search results inside inactive Bootstrap tabs become visible.
1136- // Only switches tabs when no match is already visible in the active tab of that tabset .
1145+ // Handles nested tabsets by walking up ancestor panes and activating outermost first .
11371146function activateTabsWithMatches ( mainEl ) {
11381147 if ( typeof bootstrap === "undefined" ) return ;
11391148
11401149 const marks = mainEl . querySelectorAll ( "mark" ) ;
11411150 if ( marks . length === 0 ) return ;
11421151
1143- // Group marks by their parent tabset (.tab-content container)
1152+ // Collect all tab panes that contain marks, including ancestor panes for nesting.
1153+ // Group by their parent tabset (.tab-content container).
11441154 const tabsetMatches = new Map ( ) ;
1145- for ( const mark of marks ) {
1146- const pane = mark . closest ( ".tab-pane" ) ;
1147- if ( ! pane ) continue ;
1148- const tabContent = pane . closest ( ".tab-content" ) ;
1149- if ( ! tabContent ) continue ;
11501155
1156+ const recordPane = ( pane ) => {
1157+ const tabContent = pane . closest ( ".tab-content" ) ;
1158+ if ( ! tabContent ) return ;
11511159 if ( ! tabsetMatches . has ( tabContent ) ) {
11521160 tabsetMatches . set ( tabContent , { activeHasMatch : false , firstInactivePane : null } ) ;
11531161 }
@@ -1157,10 +1165,25 @@ function activateTabsWithMatches(mainEl) {
11571165 } else if ( ! info . firstInactivePane ) {
11581166 info . firstInactivePane = pane ;
11591167 }
1168+ } ;
1169+
1170+ for ( const mark of marks ) {
1171+ // Walk up all ancestor tab panes (handles nested tabsets)
1172+ let pane = mark . closest ( ".tab-pane" ) ;
1173+ while ( pane ) {
1174+ recordPane ( pane ) ;
1175+ pane = pane . parentElement ?. closest ( ".tab-pane" ) ?? null ;
1176+ }
11601177 }
11611178
1162- // For each tabset, only activate if the active tab has no match
1163- for ( const [ , info ] of tabsetMatches ) {
1179+ // Sort tabsets by DOM depth (outermost first) so outer tabs activate before inner
1180+ const sorted = [ ...tabsetMatches . entries ( ) ] . sort ( ( a , b ) => {
1181+ const depthA = ancestorCount ( a [ 0 ] , mainEl ) ;
1182+ const depthB = ancestorCount ( b [ 0 ] , mainEl ) ;
1183+ return depthA - depthB ;
1184+ } ) ;
1185+
1186+ for ( const [ , info ] of sorted ) {
11641187 if ( info . activeHasMatch || ! info . firstInactivePane ) continue ;
11651188
11661189 const escapedId = CSS . escape ( info . firstInactivePane . id ) ;
@@ -1173,6 +1196,16 @@ function activateTabsWithMatches(mainEl) {
11731196 }
11741197}
11751198
1199+ function ancestorCount ( el , stopAt ) {
1200+ let count = 0 ;
1201+ let node = el . parentElement ;
1202+ while ( node && node !== stopAt ) {
1203+ count ++ ;
1204+ node = node . parentElement ;
1205+ }
1206+ return count ;
1207+ }
1208+
11761209// highlight matches
11771210function highlight ( term , el ) {
11781211 const termRegex = new RegExp ( term , "ig" ) ;
0 commit comments