@@ -39,6 +39,20 @@ const a11y_required_attributes = {
39
39
object : [ 'title' , 'aria-label' , 'aria-labelledby' ]
40
40
} ;
41
41
42
+ const a11y_required_role_props = {
43
+ checkbox : [ 'aria-checked' ] ,
44
+ combobox : [ 'aria-controls' , 'aria-expanded' ] ,
45
+ heading : [ 'aria-level' ] ,
46
+ menuitemcheckbox : [ 'aria-checked' ] ,
47
+ menuitemradio : [ 'aria-checked' ] ,
48
+ meter : [ 'aria-valuemax' , 'aria-valuemin' , 'aria-valuenow' ] ,
49
+ option : [ 'aria-selected' ] ,
50
+ radio : [ 'aria-checked' ] ,
51
+ scrollbar : [ 'aria-controls' , 'aria-valuenow' ] ,
52
+ slider : [ 'aria-valuenow' ] ,
53
+ switch : [ 'aria-checked' ]
54
+ } ;
55
+
42
56
const a11y_distracting_elements = new Set ( [
43
57
'blink' ,
44
58
'marquee'
@@ -304,11 +318,11 @@ export default class Element extends Node {
304
318
}
305
319
306
320
validate_attributes ( ) {
307
- const { component, parent } = this ;
321
+ const { component, parent, attributes } = this ;
308
322
309
323
const attribute_map = new Map ( ) ;
310
324
311
- this . attributes . forEach ( attribute => {
325
+ attributes . forEach ( attribute => {
312
326
if ( attribute . is_spread ) return ;
313
327
314
328
const name = attribute . name . toLowerCase ( ) ;
@@ -365,6 +379,21 @@ export default class Element extends Node {
365
379
code : 'a11y-unknown-role' ,
366
380
message
367
381
} ) ;
382
+ } else {
383
+ // @ts -ignore
384
+ const required_role_props = a11y_required_role_props [ value ] ;
385
+
386
+ // role-has-required-aria-props
387
+ if ( required_role_props ) {
388
+ const has_missing_props = required_role_props . some ( prop => ! attributes . find ( a => a . name === prop ) ) ;
389
+
390
+ if ( has_missing_props ) {
391
+ component . warn ( attribute , {
392
+ code : 'a11y-role-has-required-aria-props' ,
393
+ message : `A11y: Elements with the ARIA role "${ value } " must have the following attributes defined: ${ String ( required_role_props ) } `
394
+ } ) ;
395
+ }
396
+ }
368
397
}
369
398
}
370
399
0 commit comments