@@ -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 ) ;
@@ -1117,21 +1126,20 @@ function escapeRegExp(string) {
11171126
11181127// After search highlighting, activate any tabs whose panes contain <mark> matches.
11191128// This ensures that search results inside inactive Bootstrap tabs become visible.
1120- // Only switches tabs when no match is already visible in the active tab of that tabset .
1129+ // Handles nested tabsets by walking up ancestor panes and activating outermost first .
11211130function activateTabsWithMatches ( mainEl ) {
11221131 if ( typeof bootstrap === "undefined" ) return ;
11231132
11241133 const marks = mainEl . querySelectorAll ( "mark" ) ;
11251134 if ( marks . length === 0 ) return ;
11261135
1127- // Group marks by their parent tabset (.tab-content container)
1136+ // Collect all tab panes that contain marks, including ancestor panes for nesting.
1137+ // Group by their parent tabset (.tab-content container).
11281138 const tabsetMatches = new Map ( ) ;
1129- for ( const mark of marks ) {
1130- const pane = mark . closest ( ".tab-pane" ) ;
1131- if ( ! pane ) continue ;
1132- const tabContent = pane . closest ( ".tab-content" ) ;
1133- if ( ! tabContent ) continue ;
11341139
1140+ const recordPane = ( pane ) => {
1141+ const tabContent = pane . closest ( ".tab-content" ) ;
1142+ if ( ! tabContent ) return ;
11351143 if ( ! tabsetMatches . has ( tabContent ) ) {
11361144 tabsetMatches . set ( tabContent , { activeHasMatch : false , firstInactivePane : null } ) ;
11371145 }
@@ -1141,10 +1149,25 @@ function activateTabsWithMatches(mainEl) {
11411149 } else if ( ! info . firstInactivePane ) {
11421150 info . firstInactivePane = pane ;
11431151 }
1152+ } ;
1153+
1154+ for ( const mark of marks ) {
1155+ // Walk up all ancestor tab panes (handles nested tabsets)
1156+ let pane = mark . closest ( ".tab-pane" ) ;
1157+ while ( pane ) {
1158+ recordPane ( pane ) ;
1159+ pane = pane . parentElement ?. closest ( ".tab-pane" ) ?? null ;
1160+ }
11441161 }
11451162
1146- // For each tabset, only activate if the active tab has no match
1147- for ( const [ , info ] of tabsetMatches ) {
1163+ // Sort tabsets by DOM depth (outermost first) so outer tabs activate before inner
1164+ const sorted = [ ...tabsetMatches . entries ( ) ] . sort ( ( a , b ) => {
1165+ const depthA = ancestorCount ( a [ 0 ] , mainEl ) ;
1166+ const depthB = ancestorCount ( b [ 0 ] , mainEl ) ;
1167+ return depthA - depthB ;
1168+ } ) ;
1169+
1170+ for ( const [ , info ] of sorted ) {
11481171 if ( info . activeHasMatch || ! info . firstInactivePane ) continue ;
11491172
11501173 const escapedId = CSS . escape ( info . firstInactivePane . id ) ;
@@ -1157,6 +1180,16 @@ function activateTabsWithMatches(mainEl) {
11571180 }
11581181}
11591182
1183+ function ancestorCount ( el , stopAt ) {
1184+ let count = 0 ;
1185+ let node = el . parentElement ;
1186+ while ( node && node !== stopAt ) {
1187+ count ++ ;
1188+ node = node . parentElement ;
1189+ }
1190+ return count ;
1191+ }
1192+
11601193// highlight matches
11611194function highlight ( term , el ) {
11621195 const termRegex = new RegExp ( term , "ig" ) ;
0 commit comments