@@ -69,6 +69,24 @@ function isComponentInvocation(tag) {
6969// inherent-focusability we'd otherwise grant the tag.
7070const DISABLABLE_FORM_CONTROLS = new Set ( [ 'button' , 'input' , 'select' , 'textarea' , 'fieldset' ] ) ;
7171
72+ // True when the UA ignores otherwise-focusing attributes (`tabindex`,
73+ // `contenteditable`) on this element because the element is itself removed
74+ // from sequential focus navigation by HTML semantics:
75+ // - disabled form controls (HTML §4.10.18.5)
76+ // - <input type="hidden"> (no rendered element)
77+ function isSuppressedFromFocus ( node , tag , getTextAttrValueFn ) {
78+ if ( DISABLABLE_FORM_CONTROLS . has ( tag ) && findAttr ( node , 'disabled' ) ) {
79+ return true ;
80+ }
81+ if ( tag === 'input' ) {
82+ const type = getTextAttrValueFn ( findAttr ( node , 'type' ) ) ;
83+ if ( typeof type === 'string' && type . trim ( ) . toLowerCase ( ) === 'hidden' ) {
84+ return true ;
85+ }
86+ }
87+ return false ;
88+ }
89+
7290// Is the element inherently focusable without needing tabindex?
7391function isInherentlyFocusable ( node ) {
7492 const tag = node . tag ?. toLowerCase ( ) ;
@@ -209,32 +227,16 @@ module.exports = {
209227 // HTML attribute names are case-insensitive, so accept `tabindex` or
210228 // any other casing (e.g. `tabIndex`, the React-style camelCase).
211229 const hasTabindex = node . attributes ?. some ( ( a ) => a . name ?. toLowerCase ( ) === 'tabindex' ) ;
212- if ( hasTabindex ) {
213- const disabled = DISABLABLE_FORM_CONTROLS . has ( tag ) && findAttr ( node , 'disabled' ) ;
214- let hiddenInput = false ;
215- if ( tag === 'input' ) {
216- const type = getTextAttrValue ( findAttr ( node , 'type' ) ) ;
217- hiddenInput = typeof type === 'string' && type . trim ( ) . toLowerCase ( ) === 'hidden' ;
218- }
219- if ( ! disabled && ! hiddenInput ) {
220- return ;
221- }
230+ if ( hasTabindex && ! isSuppressedFromFocus ( node , tag , getTextAttrValue ) ) {
231+ return ;
222232 }
223233
224234 // contenteditable also makes an element focusable, with the same
225235 // HTML-spec carve-outs as tabindex: the UA ignores it on disabled
226236 // form controls (HTML §4.10.18.5) and on <input type="hidden">
227237 // (no rendered element to edit), so the a11y conflict still stands.
228- if ( isContentEditable ( node ) ) {
229- const disabled = DISABLABLE_FORM_CONTROLS . has ( tag ) && findAttr ( node , 'disabled' ) ;
230- let hiddenInput = false ;
231- if ( tag === 'input' ) {
232- const type = getTextAttrValue ( findAttr ( node , 'type' ) ) ;
233- hiddenInput = typeof type === 'string' && type . trim ( ) . toLowerCase ( ) === 'hidden' ;
234- }
235- if ( ! disabled && ! hiddenInput ) {
236- return ;
237- }
238+ if ( isContentEditable ( node ) && ! isSuppressedFromFocus ( node , tag , getTextAttrValue ) ) {
239+ return ;
238240 }
239241
240242 context . report ( {
0 commit comments