@@ -23,14 +23,12 @@ export default class SidebarFilter {
2323 */
2424 static get CSS ( ) {
2525 return {
26+ section : 'docs-sidebar__section' ,
2627 sectionHidden : 'docs-sidebar__section--hidden' ,
2728 sectionTitle : 'docs-sidebar__section-title' ,
2829 sectionTitleSelected : 'docs-sidebar__section-title--selected' ,
2930 sectionTitleActive : 'docs-sidebar__section-title--active' ,
3031 sectionList : 'docs-sidebar__section-list' ,
31- sectionListItem : 'docs-sidebar__section-list-item' ,
32- sectionListItemWrapperHidden : 'docs-sidebar__section-list-item-wrapper--hidden' ,
33- sectionListItemSlelected : 'docs-sidebar__section-list-item--selected' ,
3432 sidebarSearchWrapper : 'docs-sidebar__search-wrapper' ,
3533 } ;
3634 }
@@ -43,7 +41,7 @@ export default class SidebarFilter {
4341 * Stores refs to HTML elements needed for sidebar filter to work.
4442 */
4543 this . sidebar = null ;
46- this . sections = [ ] ;
44+ this . rootSections = [ ] ;
4745 this . sidebarContent = null ;
4846 this . search = null ;
4947 this . searchResults = [ ] ;
@@ -53,14 +51,14 @@ export default class SidebarFilter {
5351 /**
5452 * Initialize sidebar filter.
5553 *
56- * @param {HTMLElement[] } sections - Array of sections .
54+ * @param {HTMLElement[] } rootSections - Top-level sections (direct children of sidebar content) .
5755 * @param {HTMLElement } sidebarContent - Sidebar content.
5856 * @param {HTMLElement } search - Search input.
5957 * @param {Function } setSectionCollapsed - Function to set section collapsed.
6058 */
61- init ( sections , sidebarContent , search , setSectionCollapsed ) {
59+ init ( rootSections , sidebarContent , search , setSectionCollapsed ) {
6260 // Store refs to HTML elements.
63- this . sections = sections ;
61+ this . rootSections = rootSections ;
6462 this . sidebarContent = sidebarContent ;
6563 this . search = search ;
6664 this . setSectionCollapsed = setSectionCollapsed ;
@@ -202,11 +200,8 @@ export default class SidebarFilter {
202200 return ;
203201 }
204202
205- // focus title or item.
206203 if ( type === 'title' ) {
207204 element . classList . add ( SidebarFilter . CSS . sectionTitleSelected ) ;
208- } else if ( type === 'item' ) {
209- element . classList . add ( SidebarFilter . CSS . sectionListItemSlelected ) ;
210205 }
211206
212207 // scroll to focused title or item.
@@ -230,11 +225,8 @@ export default class SidebarFilter {
230225 return ;
231226 }
232227
233- // blur title or item.
234228 if ( type === 'title' ) {
235229 element . classList . remove ( SidebarFilter . CSS . sectionTitleSelected ) ;
236- } else if ( type === 'item' ) {
237- element . classList . remove ( SidebarFilter . CSS . sectionListItemSlelected ) ;
238230 }
239231 }
240232
@@ -294,54 +286,76 @@ export default class SidebarFilter {
294286 * @param {string } searchValue - Search value.
295287 */
296288 filterSection ( section , searchValue ) {
297- // match with section title.
298289 const sectionTitle = section . querySelector ( '.' + SidebarFilter . CSS . sectionTitle ) ;
299- const sectionList = section . querySelector ( '.' + SidebarFilter . CSS . sectionList ) ;
290+ const sectionList = section . querySelector ( ':scope > .' + SidebarFilter . CSS . sectionList ) ;
291+
292+ if ( ! sectionTitle ) {
293+ return false ;
294+ }
295+
296+ const empty = ! searchValue || ! searchValue . trim ( ) ;
297+
298+ if ( empty ) {
299+ section . classList . remove ( SidebarFilter . CSS . sectionHidden ) ;
300+
301+ if ( sectionList ) {
302+ Array . from ( sectionList . children ) . forEach ( ( li ) => {
303+ const nestedSection = li . querySelector ( ':scope > .' + SidebarFilter . CSS . section ) ;
304+
305+ if ( nestedSection ) {
306+ this . filterSection ( nestedSection , searchValue ) ;
307+ }
308+ } ) ;
309+ }
310+
311+ return true ;
312+ }
300313
301- // check if section title matches.
302314 const isTitleMatch = this . isValueMatched ( sectionTitle . textContent , searchValue ) ;
315+ let hasMatch = isTitleMatch ;
303316
304- const matchResults = [ ] ;
305- // match with section items.
306- let isSingleItemMatch = false ;
317+ if ( isTitleMatch ) {
318+ this . searchResults . push ( {
319+ element : sectionTitle ,
320+ type : 'title' ,
321+ } ) ;
322+ }
307323
308324 if ( sectionList ) {
309- const sectionListItems = sectionList . querySelectorAll ( '.' + SidebarFilter . CSS . sectionListItem ) ;
310-
311- sectionListItems . forEach ( item => {
312- if ( this . isValueMatched ( item . textContent , searchValue ) ) {
313- // remove hiden class from item.
314- item . parentElement . classList . remove ( SidebarFilter . CSS . sectionListItemWrapperHidden ) ;
315- // add item to search results.
316- matchResults . push ( {
317- element : item ,
318- type : 'item' ,
319- } ) ;
320- isSingleItemMatch = true ;
321- } else {
322- // hide item if it is not a match.
323- item . parentElement . classList . add ( SidebarFilter . CSS . sectionListItemWrapperHidden ) ;
325+ for ( const li of sectionList . children ) {
326+ const nestedSection = li . querySelector ( ':scope > .' + SidebarFilter . CSS . section ) ;
327+
328+ if ( ! nestedSection ) {
329+ continue ;
324330 }
325- } ) ;
326- }
327- if ( ! isTitleMatch && ! isSingleItemMatch ) {
328- // hide section if it's items are not a match.
329- section . classList . add ( SidebarFilter . CSS . sectionHidden ) ;
330- } else {
331- const parentSection = sectionTitle . closest ( 'section' ) ;
332331
333- // if item is in collapsed section, expand it.
334- if ( ! parentSection . classList . contains ( SidebarFilter . CSS . sectionTitleActive ) ) {
335- this . setSectionCollapsed ( parentSection , false ) ;
332+ const childHasMatch = this . filterSection ( nestedSection , searchValue ) ;
333+
334+ hasMatch = hasMatch || childHasMatch ;
336335 }
337- // show section if it's items are a match.
336+ }
337+
338+ if ( hasMatch ) {
338339 section . classList . remove ( SidebarFilter . CSS . sectionHidden ) ;
339- // add section title to search results.
340- this . searchResults . push ( {
341- element : sectionTitle ,
342- type : 'title' ,
343- } , ...matchResults ) ;
340+ this . setSectionCollapsed ( section , false ) ;
341+
342+ let el = section . parentElement ;
343+
344+ while ( el && el !== this . sidebarContent ) {
345+ const ancSection = el . closest ( '.' + SidebarFilter . CSS . section ) ;
346+
347+ if ( ! ancSection ) {
348+ break ;
349+ }
350+
351+ this . setSectionCollapsed ( ancSection , false ) ;
352+ el = ancSection . parentElement ;
353+ }
354+ } else {
355+ section . classList . add ( SidebarFilter . CSS . sectionHidden ) ;
344356 }
357+
358+ return hasMatch ;
345359 }
346360
347361 /**
@@ -356,8 +370,7 @@ export default class SidebarFilter {
356370 this . selectedSearchResultIndex = null ;
357371 // empty search results.
358372 this . searchResults = [ ] ;
359- // match search value with sidebar sections.
360- this . sections . forEach ( section => {
373+ this . rootSections . forEach ( section => {
361374 this . filterSection ( section , searchValue ) ;
362375 } ) ;
363376 }
0 commit comments