11const { dom } = require ( 'aria-query' ) ;
22const { isNativeElement } = require ( '../utils/is-native-element' ) ;
33const { isHtmlInteractiveContent } = require ( '../utils/html-interactive-content' ) ;
4+ const { getStaticAttrValue } = require ( '../utils/static-attr-value' ) ;
45
56const KEYBOARD_EVENT_NAMES = new Set ( [ 'keydown' , 'keyup' , 'keypress' ] ) ;
67
@@ -21,33 +22,20 @@ function getTextAttrValue(node, attrName) {
2122 return getAttrTextValue ( findAttr ( node , attrName ) ) ;
2223}
2324
24- // True iff the attribute's mustache value is the literal boolean `true` —
25- // e.g. `aria-hidden={{true}}`. Any other expression (path reference, helper
26- // call, etc.) is left to runtime and not treated as a static escape hatch.
27- function isMustacheLiteralTrue ( attr ) {
28- if ( attr ?. value ?. type !== 'GlimmerMustacheStatement' ) {
29- return false ;
30- }
31- const path = attr . value . path ;
32- return path ?. type === 'GlimmerBooleanLiteral' && path . value === true ;
33- }
34-
3525function isHiddenFromScreenReader ( node ) {
3626 const ariaHidden = findAttr ( node , 'aria-hidden' ) ;
3727 if ( ariaHidden ) {
3828 // WAI-ARIA 1.2: aria-hidden is NOT a boolean HTML attribute. Only the
3929 // string value "true" hides the element. A valueless attribute or an
4030 // empty string is invalid and must NOT be treated as hiding the element.
41- if ( ariaHidden . value ?. type === 'GlimmerTextNode' ) {
42- const chars = ariaHidden . value . chars ;
43- if ( chars . trim ( ) . toLowerCase ( ) === 'true' ) {
44- return true ;
45- }
46- }
47- // Mustache-literal `{{true}}` — unambiguous static escape hatch. Any
48- // other mustache shape (path reference, helper invocation) is dynamic
49- // and intentionally NOT treated as hidden.
50- if ( isMustacheLiteralTrue ( ariaHidden ) ) {
31+ //
32+ // getStaticAttrValue resolves GlimmerTextNode, GlimmerMustacheStatement
33+ // with a literal path (boolean/string), and GlimmerConcatStatement whose
34+ // parts are all static — covering aria-hidden="TRUE", aria-hidden={{true}},
35+ // aria-hidden={{"true"}}, and aria-hidden="{{true}}". Dynamic expressions
36+ // return undefined and are intentionally not treated as hidden.
37+ const resolved = getStaticAttrValue ( ariaHidden . value ) ;
38+ if ( resolved !== undefined && resolved . trim ( ) . toLowerCase ( ) === 'true' ) {
5139 return true ;
5240 }
5341 }
@@ -109,7 +97,7 @@ module.exports = {
10997 schema : [ ] ,
11098 messages : {
11199 needsKeyEvent :
112- 'Visible, non -interactive elements with click handlers must have at least one keyboard listener (keydown/keyup/keypress).' ,
100+ 'Non -interactive elements with click handlers must have at least one keyboard listener (keydown/keyup/keypress).' ,
113101 } ,
114102 } ,
115103
@@ -149,12 +137,14 @@ module.exports = {
149137 // Elements outside HTML §3.2.5.2.7 that are nonetheless ARIA widgets
150138 // or conventionally interactive surfaces — click-without-key on them
151139 // isn't what this rule targets. The HTML-content-model util covers
152- // the spec-normative list; these are the ARIA-widget / convention
153- // additions (see `html-interactive-content.js` docstring for why the
154- // two authorities diverge).
140+ // the spec-normative list; these are explicit exemptions for elements
141+ // that do not qualify as interactive content under the HTML spec but
142+ // are carved out here due to their ARIA roles or browser-native
143+ // behavior (see `html-interactive-content.js` docstring for context).
155144 // - <canvas>: drawing/game surface (axobject-query: CanvasRole).
156- // - <option>: ARIA role="option" (widget).
157- // - <datalist>: ARIA role="listbox" (widget).
145+ // - <option>: ARIA role="option" (widget), but not keyboard-activatable
146+ // as a standalone element — exempted as a special case.
147+ // - <datalist>: ARIA role="listbox" (widget), same rationale as <option>.
158148 const lowerTag = node . tag . toLowerCase ( ) ;
159149 if ( lowerTag === 'canvas' || lowerTag === 'option' || lowerTag === 'datalist' ) {
160150 return ;
0 commit comments