@@ -49,13 +49,20 @@ module.exports = {
4949
5050 if ( role && ROLES_REQUIRING_CONTEXT [ role ] ) {
5151 // Skip check if at root level (no parent elements — context may be external)
52- if ( elementStack . length > 1 && ! isInsideAriaHidden ( elementStack ) ) {
53- const parentRole = getAccessibleParentRole ( elementStack ) ;
52+ if ( elementStack . length > 1 ) {
53+ const parentContext = getParentContext ( elementStack ) ;
54+ if ( parentContext . ariaHidden ) {
55+ // aria-hidden on the effective parent (or a transparent wrapper
56+ // walked through on the way up) — upstream suppresses the rule.
57+ return ;
58+ }
59+ const parentRole = parentContext . role ;
5460 if ( parentRole === undefined ) {
5561 // No non-transparent parent found (effectively root) — skip
5662 } else if ( ! parentRole || ! ROLES_REQUIRING_CONTEXT [ role ] . includes ( parentRole ) ) {
63+ const roleAttr = node . attributes ?. find ( ( a ) => a . name === 'role' ) ;
5764 context . report ( {
58- node,
65+ node : roleAttr || node ,
5966 messageId : 'missingContext' ,
6067 data : {
6168 role,
@@ -82,46 +89,37 @@ function getRoleFromNode(node) {
8289 return null ;
8390}
8491
85- /**
86- * Check if any ancestor element in the stack has aria-hidden="true".
87- */
88- function isInsideAriaHidden ( elementStack ) {
89- // Check ancestors (all elements except the current one)
90- for ( let i = elementStack . length - 2 ; i >= 0 ; i -- ) {
91- const node = elementStack [ i ] ;
92- const ariaHidden = node . attributes ?. find ( ( a ) => a . name === 'aria-hidden' ) ;
93- if ( ariaHidden ?. value ?. type === 'GlimmerTextNode' && ariaHidden . value . chars === 'true' ) {
94- return true ;
95- }
96- }
97- return false ;
92+ function hasAriaHiddenTrue ( node ) {
93+ const attr = node . attributes ?. find ( ( a ) => a . name === 'aria-hidden' ) ;
94+ return attr ?. value ?. type === 'GlimmerTextNode' && attr . value . chars === 'true' ;
9895}
9996
10097/**
101- * Get the role of the nearest non-transparent ancestor element.
102- * Transparent elements are those with role="presentation"/"none" or named blocks (tag starts with ':').
103- * Returns:
104- * - a role string if a non-transparent ancestor with a role is found
105- * - null if a non-transparent ancestor WITHOUT a role is found (breaks context)
106- * - undefined if no non-transparent ancestor exists (root level)
98+ * Walk up the ancestor chain through transparent wrappers (named-block slots,
99+ * `<template>`, role="presentation"/"none") checking `aria-hidden` at each
100+ * layer. Returns { ariaHidden, role } where:
101+ * - `ariaHidden` is true if aria-hidden="true" was seen on any traversed
102+ * element (including transparent wrappers) up to and including the first
103+ * non-transparent parent — matches upstream's semantics.
104+ * - `role` is the role of the first non-transparent parent: a role string,
105+ * null (element with no role), or undefined (no non-transparent parent).
107106 */
108- function getAccessibleParentRole ( elementStack ) {
107+ function getParentContext ( elementStack ) {
109108 for ( let i = elementStack . length - 2 ; i >= 0 ; i -- ) {
110109 const node = elementStack [ i ] ;
111-
112- // Named blocks (e.g. <:content>) and <template> wrapper are transparent
110+ if ( hasAriaHiddenTrue ( node ) ) {
111+ return { ariaHidden : true , role : undefined } ;
112+ }
113+ // Named blocks (`<:content>`) and the `<template>` wrapper are transparent
113114 if ( node . tag && ( node . tag . startsWith ( ':' ) || node . tag === 'template' ) ) {
114115 continue ;
115116 }
116-
117117 const role = getRoleFromNode ( node ) ;
118-
119- // Presentation/none roles are transparent in the accessibility tree
118+ // presentation/none roles are transparent in the accessibility tree
120119 if ( role === 'presentation' || role === 'none' ) {
121120 continue ;
122121 }
123-
124- return role ; // could be null (element with no role) or a role string
122+ return { ariaHidden : false , role } ;
125123 }
126- return undefined ; // no non-transparent ancestor found
124+ return { ariaHidden : false , role : undefined } ;
127125}
0 commit comments