@@ -82,11 +82,15 @@ const a11y_nested_implicit_semantics = new Map([
82
82
83
83
const a11y_implicit_semantics = new Map ( [
84
84
[ 'a' , 'link' ] ,
85
+ [ 'area' , 'link' ] ,
86
+ [ 'article' , 'article' ] ,
85
87
[ 'aside' , 'complementary' ] ,
86
88
[ 'body' , 'document' ] ,
89
+ [ 'button' , 'button' ] ,
87
90
[ 'datalist' , 'listbox' ] ,
88
91
[ 'dd' , 'definition' ] ,
89
92
[ 'dfn' , 'term' ] ,
93
+ [ 'dialog' , 'dialog' ] ,
90
94
[ 'details' , 'group' ] ,
91
95
[ 'dt' , 'term' ] ,
92
96
[ 'fieldset' , 'group' ] ,
@@ -98,10 +102,14 @@ const a11y_implicit_semantics = new Map([
98
102
[ 'h5' , 'heading' ] ,
99
103
[ 'h6' , 'heading' ] ,
100
104
[ 'hr' , 'separator' ] ,
105
+ [ 'img' , 'img' ] ,
101
106
[ 'li' , 'listitem' ] ,
107
+ [ 'link' , 'link' ] ,
102
108
[ 'menu' , 'list' ] ,
109
+ [ 'meter' , 'progressbar' ] ,
103
110
[ 'nav' , 'navigation' ] ,
104
111
[ 'ol' , 'list' ] ,
112
+ [ 'option' , 'option' ] ,
105
113
[ 'optgroup' , 'group' ] ,
106
114
[ 'output' , 'status' ] ,
107
115
[ 'progress' , 'progressbar' ] ,
@@ -115,6 +123,61 @@ const a11y_implicit_semantics = new Map([
115
123
[ 'ul' , 'list' ]
116
124
] ) ;
117
125
126
+ const menuitem_type_to_implicit_role = new Map ( [
127
+ [ 'command' , 'menuitem' ] ,
128
+ [ 'checkbox' , 'menuitemcheckbox' ] ,
129
+ [ 'radio' , 'menuitemradio' ]
130
+ ] ) ;
131
+
132
+ const input_type_to_implicit_role = new Map ( [
133
+ [ 'button' , 'button' ] ,
134
+ [ 'image' , 'button' ] ,
135
+ [ 'reset' , 'button' ] ,
136
+ [ 'submit' , 'button' ] ,
137
+ [ 'checkbox' , 'checkbox' ] ,
138
+ [ 'radio' , 'radio' ] ,
139
+ [ 'range' , 'slider' ] ,
140
+ [ 'number' , 'spinbutton' ] ,
141
+ [ 'email' , 'textbox' ] ,
142
+ [ 'search' , 'searchbox' ] ,
143
+ [ 'tel' , 'textbox' ] ,
144
+ [ 'text' , 'textbox' ] ,
145
+ [ 'url' , 'textbox' ]
146
+ ] ) ;
147
+
148
+ const combobox_if_list = new Set ( [ 'email' , 'search' , 'tel' , 'text' , 'url' ] ) ;
149
+
150
+ function input_implicit_role ( attribute_map : Map < string , Attribute > ) {
151
+ const type_attribute = attribute_map . get ( 'type' ) ;
152
+ if ( ! type_attribute || ! type_attribute . is_static ) return ;
153
+ const type = type_attribute . get_static_value ( ) as string ;
154
+
155
+ const list_attribute_exists = attribute_map . has ( 'list' ) ;
156
+
157
+ if ( list_attribute_exists && combobox_if_list . has ( type ) ) {
158
+ return 'combobox' ;
159
+ }
160
+
161
+ return input_type_to_implicit_role . get ( type ) ;
162
+ }
163
+
164
+ function menuitem_implicit_role ( attribute_map : Map < string , Attribute > ) {
165
+ const type_attribute = attribute_map . get ( 'type' ) ;
166
+ if ( ! type_attribute || ! type_attribute . is_static ) return ;
167
+ const type = type_attribute . get_static_value ( ) as string ;
168
+ return menuitem_type_to_implicit_role . get ( type ) ;
169
+ }
170
+
171
+ function get_implicit_role ( name : string , attribute_map : Map < string , Attribute > ) : ( string | undefined ) {
172
+ if ( name === 'menuitem' ) {
173
+ return menuitem_implicit_role ( attribute_map ) ;
174
+ } else if ( name === 'input' ) {
175
+ return input_implicit_role ( attribute_map ) ;
176
+ } else {
177
+ return a11y_implicit_semantics . get ( name ) ;
178
+ }
179
+ }
180
+
118
181
const invisible_elements = new Set ( [ 'meta' , 'html' , 'script' , 'style' ] ) ;
119
182
120
183
const valid_modifiers = new Set ( [
@@ -488,7 +551,7 @@ export default class Element extends Node {
488
551
489
552
// aria-activedescendant-has-tabindex
490
553
if ( name === 'aria-activedescendant' && ! is_interactive_element ( this . name , attribute_map ) && ! attribute_map . has ( 'tabindex' ) ) {
491
- component . warn ( attribute , compiler_warnings . a11y_aria_activedescendant_has_tabindex ) ;
554
+ component . warn ( attribute , compiler_warnings . a11y_aria_activedescendant_has_tabindex ) ;
492
555
}
493
556
}
494
557
@@ -511,7 +574,7 @@ export default class Element extends Node {
511
574
}
512
575
513
576
// no-redundant-roles
514
- const has_redundant_role = current_role === a11y_implicit_semantics . get ( this . name ) ;
577
+ const has_redundant_role = current_role === get_implicit_role ( this . name , attribute_map ) ;
515
578
516
579
if ( this . name === current_role || has_redundant_role ) {
517
580
component . warn ( attribute , compiler_warnings . a11y_no_redundant_roles ( current_role ) ) ;
@@ -605,6 +668,23 @@ export default class Element extends Node {
605
668
component . warn ( this , compiler_warnings . a11y_no_noninteractive_tabindex ) ;
606
669
}
607
670
}
671
+
672
+ // role-supports-aria-props
673
+ const role = attribute_map . get ( 'role' ) ;
674
+ const role_value = ( role ? role . get_static_value ( ) : get_implicit_role ( this . name , attribute_map ) ) as ARIARoleDefintionKey ;
675
+ if ( typeof role_value === 'string' && roles . has ( role_value ) ) {
676
+ const { props } = roles . get ( role_value ) ;
677
+ const invalid_aria_props = new Set ( aria . keys ( ) . filter ( attribute => ! ( attribute in props ) ) ) ;
678
+ const is_implicit = role_value && role === undefined ;
679
+
680
+ attributes
681
+ . filter ( prop => prop . type !== 'Spread' )
682
+ . forEach ( prop => {
683
+ if ( invalid_aria_props . has ( prop . name as ARIAProperty ) ) {
684
+ component . warn ( prop , compiler_warnings . a11y_role_supports_aria_props ( prop . name , role_value , is_implicit , this . name ) ) ;
685
+ }
686
+ } ) ;
687
+ }
608
688
}
609
689
610
690
validate_special_cases ( ) {
0 commit comments