@@ -279,14 +279,16 @@ export namespace SRNavigator {
279279 }
280280 if ( elem . nodeName . toUpperCase ( ) === "BODY" ) return retVal = { skipCurrent : false , skipChildren : false } ;
281281
282- // Skip labels that are associated with controls - they'll be read with the related input
282+ // Skip labels that are associated with controls - they'll be read with the related input.
283+ // Keep children so interactive content (e.g. links) inside the label still appears in
284+ // reading/tab order.
283285 if ( elem . nodeName . toUpperCase ( ) === "LABEL" ) {
284286 if (
285287 elem . hasAttribute ( "for" )
286288 && document . getElementById ( elem . getAttribute ( "for" ) )
287289 && document . getElementById ( elem . getAttribute ( "for" ) ) . getAttribute ( "type" ) !== "hidden"
288290 ) {
289- return retVal = { skipCurrent : true , skipChildren : true } ;
291+ return retVal = { skipCurrent : true , skipChildren : false } ;
290292 } else {
291293 const nestedControl = elem . querySelector ( "input, select, textarea, button, [role='checkbox'], [role='combobox'], [role='listbox'], [role='menuitemcheckbox'], [role='menuitemradio'], [role='radio'], [role='searchbox'], [role='slider'], [role='spinbutton'], [role='switch'], [role='textbox']" ) ;
292294 if ( nestedControl && ( nestedControl as HTMLElement ) . getAttribute ( "type" ) !== "hidden" ) {
@@ -295,6 +297,23 @@ export namespace SRNavigator {
295297 }
296298 }
297299
300+ // Skip non-interactive content that is a descendant of a for-linked label.
301+ // Such content is already surfaced as part of the associated control's announcement.
302+ // Interactive descendants (links, buttons, etc.) are allowed through.
303+ {
304+ const labelFor = elem . closest ( "label[for]" ) as HTMLElement | null ;
305+ const labelForTarget = labelFor && labelFor . getAttribute ( "for" ) ;
306+ if (
307+ labelFor
308+ && labelForTarget
309+ && document . getElementById ( labelForTarget )
310+ && document . getElementById ( labelForTarget ) . getAttribute ( "type" ) !== "hidden"
311+ && ! elem . closest ( "a[href], [role='link'], button, [role='button']" )
312+ ) {
313+ return retVal = { skipCurrent : true , skipChildren : nodeType === 1 } ;
314+ }
315+ }
316+
298317 const role = cursorStart . getRole ( ) ;
299318
300319 // If we have presentational children, read the element, skip the children
@@ -349,14 +368,33 @@ export namespace SRNavigator {
349368 if ( nodeType === 3 ) return VisUtil . isNodeHiddenFromAT ( elem ) ? { skipCurrent : true , skipChildren : false } : null ;
350369 // We have an elemenet
351370 if ( VisUtil . isNodeHiddenFromAT ( elem ) ) return { skipCurrent : true , skipChildren : true } ;
352- // Skip label fors - they'll be read with the related input
371+ // Skip label fors - they'll be read with the related input.
372+ // Keep children so interactive content (e.g. links) inside the label still appears in
373+ // reading/tab order.
353374 if (
354- elem . nodeName . toUpperCase ( ) === "LABEL"
355- && elem . hasAttribute ( "for" )
375+ elem . nodeName . toUpperCase ( ) === "LABEL"
376+ && elem . hasAttribute ( "for" )
356377 && document . getElementById ( elem . getAttribute ( "for" ) )
357378 && document . getElementById ( elem . getAttribute ( "for" ) ) . getAttribute ( "type" ) !== "hidden"
358379 ) {
359- return { skipCurrent : true , skipChildren : true } ;
380+ return { skipCurrent : true , skipChildren : false } ;
381+ }
382+
383+ // Skip non-interactive content that is a descendant of a for-linked label.
384+ // Such content is already surfaced as part of the associated control's announcement.
385+ // Interactive descendants (links, buttons, etc.) are allowed through.
386+ {
387+ const labelFor = elem . closest ( "label[for]" ) as HTMLElement | null ;
388+ const labelForTarget = labelFor && labelFor . getAttribute ( "for" ) ;
389+ if (
390+ labelFor
391+ && labelForTarget
392+ && document . getElementById ( labelForTarget )
393+ && document . getElementById ( labelForTarget ) . getAttribute ( "type" ) !== "hidden"
394+ && ! elem . closest ( "a[href], [role='link'], button, [role='button']" )
395+ ) {
396+ return { skipCurrent : true , skipChildren : nodeType === 1 } ;
397+ }
360398 }
361399
362400 const role = cursor . getRole ( ) ;
0 commit comments