@@ -25,7 +25,7 @@ import { Literal } from 'estree';
25
25
import compiler_warnings from '../compiler_warnings' ;
26
26
import compiler_errors from '../compiler_errors' ;
27
27
import { ARIARoleDefinitionKey , roles , aria , ARIAPropertyDefinition , ARIAProperty } from 'aria-query' ;
28
- import { is_interactive_element , is_non_interactive_element , is_non_interactive_roles , is_presentation_role , is_interactive_roles , is_hidden_from_screen_reader , is_semantic_role_element , is_abstract_role } from '../utils/a11y' ;
28
+ import { is_interactive_element , is_non_interactive_element , is_non_interactive_roles , is_presentation_role , is_interactive_roles , is_hidden_from_screen_reader , is_semantic_role_element , is_abstract_role , is_static_element , has_disabled_attribute } from '../utils/a11y' ;
29
29
30
30
const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext' . split ( ' ' ) ;
31
31
const aria_attribute_set = new Set ( aria_attributes ) ;
@@ -75,6 +75,33 @@ const a11y_labelable = new Set([
75
75
'textarea'
76
76
] ) ;
77
77
78
+ const a11y_interactive_handlers = new Set ( [
79
+ // Keyboard events
80
+ 'keypress' ,
81
+ 'keydown' ,
82
+ 'keyup' ,
83
+
84
+ // Click events
85
+ 'click' ,
86
+ 'contextmenu' ,
87
+ 'dblclick' ,
88
+ 'drag' ,
89
+ 'dragend' ,
90
+ 'dragenter' ,
91
+ 'dragexit' ,
92
+ 'dragleave' ,
93
+ 'dragover' ,
94
+ 'dragstart' ,
95
+ 'drop' ,
96
+ 'mousedown' ,
97
+ 'mouseenter' ,
98
+ 'mouseleave' ,
99
+ 'mousemove' ,
100
+ 'mouseout' ,
101
+ 'mouseover' ,
102
+ 'mouseup'
103
+ ] ) ;
104
+
78
105
const a11y_nested_implicit_semantics = new Map ( [
79
106
[ 'header' , 'banner' ] ,
80
107
[ 'footer' , 'contentinfo' ]
@@ -603,6 +630,21 @@ export default class Element extends Node {
603
630
}
604
631
}
605
632
633
+ // interactive-supports-focus
634
+ if (
635
+ ! has_disabled_attribute ( attribute_map ) &&
636
+ ! is_hidden_from_screen_reader ( this . name , attribute_map ) &&
637
+ ! is_presentation_role ( current_role ) &&
638
+ is_interactive_roles ( current_role ) &&
639
+ is_static_element ( this . name , attribute_map ) &&
640
+ ! attribute_map . get ( 'tabindex' )
641
+ ) {
642
+ const has_interactive_handlers = handlers . some ( ( handler ) => a11y_interactive_handlers . has ( handler . name ) ) ;
643
+ if ( has_interactive_handlers ) {
644
+ component . warn ( this , compiler_warnings . a11y_interactive_supports_focus ( current_role ) ) ;
645
+ }
646
+ }
647
+
606
648
// no-interactive-element-to-noninteractive-role
607
649
if ( is_interactive_element ( this . name , attribute_map ) && ( is_non_interactive_roles ( current_role ) || is_presentation_role ( current_role ) ) ) {
608
650
component . warn ( this , compiler_warnings . a11y_no_interactive_element_to_noninteractive_role ( current_role , this . name ) ) ;
0 commit comments