@@ -96,6 +96,39 @@ export function TableOfContents({
9696 const headingSelector = headingLevels . join ( ', ' ) ;
9797 const headingElements = container . querySelectorAll < HTMLHeadingElement > ( headingSelector ) ;
9898
99+ // Helper function to extract text excluding headerActions
100+ const getHeadingText = ( heading : HTMLHeadingElement ) : string => {
101+ // Clone the heading to avoid modifying the original
102+ const clone = heading . cloneNode ( true ) as HTMLHeadingElement ;
103+
104+ // Remove all interactive elements first (buttons, switches, etc.)
105+ const interactiveElements = clone . querySelectorAll ( 'button, [role="switch"], [role="button"], input[type="checkbox"]' ) ;
106+ interactiveElements . forEach ( ( el ) => el . remove ( ) ) ;
107+
108+ // Remove any elements that contain "Show Code" or "Show Preview" text
109+ // Process from deepest to shallowest to handle nested structures correctly
110+ const allElements = Array . from ( clone . querySelectorAll ( '*' ) ) . reverse ( ) ;
111+ allElements . forEach ( ( el ) => {
112+ const text = el . textContent ?. trim ( ) || '' ;
113+ // Check if element's text matches button labels exactly or starts with them
114+ // This avoids false positives from headings that might contain words like "Code"
115+ if ( text === 'Show Code' || text === 'Show Preview' ||
116+ text === 'Hide Code' || text === 'Hide Preview' ||
117+ text . startsWith ( 'Show Code' ) || text . startsWith ( 'Show Preview' ) ||
118+ text . startsWith ( 'Hide Code' ) || text . startsWith ( 'Hide Preview' ) ) {
119+ // Remove this element and check if parent should also be removed
120+ const parent = el . parentElement ;
121+ el . remove ( ) ;
122+ // If parent is now empty or only contains whitespace, remove it too
123+ if ( parent && parent . textContent ?. trim ( ) === '' ) {
124+ parent . remove ( ) ;
125+ }
126+ }
127+ } ) ;
128+
129+ return clone . textContent ?. trim ( ) || '' ;
130+ } ;
131+
99132 const extractedHeadings : Heading [ ] = Array . from ( headingElements )
100133 . filter ( heading => {
101134 // Only include actual h2/h3 HTML elements (not divs with role="heading")
@@ -105,7 +138,8 @@ export function TableOfContents({
105138 }
106139
107140 // Filter out empty headings or headings with only whitespace
108- const text = heading . textContent ?. trim ( ) ;
141+ // Use getHeadingText to exclude headerActions
142+ const text = getHeadingText ( heading ) ;
109143 if ( ! text || text . length === 0 ) {
110144 return false ;
111145 }
@@ -149,10 +183,12 @@ export function TableOfContents({
149183 return true ;
150184 } )
151185 . map ( ( heading , index ) => {
186+ // Extract text excluding headerActions using the helper function
187+ const headingText = getHeadingText ( heading ) ;
188+
152189 // Generate ID if it doesn't exist
153190 if ( ! heading . id ) {
154- const text = heading . textContent || '' ;
155- const id = text
191+ const id = headingText
156192 . toLowerCase ( )
157193 . replace ( / [ ^ a - z 0 - 9 ] + / g, '-' )
158194 . replace ( / ^ - | - $ / g, '' ) ;
@@ -161,7 +197,7 @@ export function TableOfContents({
161197
162198 return {
163199 id : heading . id ,
164- text : heading . textContent ?. trim ( ) || '' ,
200+ text : headingText ,
165201 level : parseInt ( heading . tagName [ 1 ] || '2' , 10 ) ,
166202 } ;
167203 } ) ;
0 commit comments