@@ -45,6 +45,20 @@ const a11y_required_attributes = {
45
45
object : [ 'title' , 'aria-label' , 'aria-labelledby' ]
46
46
} ;
47
47
48
+ const a11y_required_role_props = {
49
+ checkbox : [ 'aria-checked' ] ,
50
+ combobox : [ 'aria-controls' , 'aria-expanded' ] ,
51
+ heading : [ 'aria-level' ] ,
52
+ menuitemcheckbox : [ 'aria-checked' ] ,
53
+ menuitemradio : [ 'aria-checked' ] ,
54
+ meter : [ 'aria-valuemax' , 'aria-valuemin' , 'aria-valuenow' ] ,
55
+ option : [ 'aria-selected' ] ,
56
+ radio : [ 'aria-checked' ] ,
57
+ scrollbar : [ 'aria-controls' , 'aria-valuenow' ] ,
58
+ slider : [ 'aria-valuenow' ] ,
59
+ switch : [ 'aria-checked' ]
60
+ } ;
61
+
48
62
const a11y_distracting_elements = new Set ( [
49
63
'blink' ,
50
64
'marquee'
@@ -407,9 +421,9 @@ export default class Element extends Node {
407
421
}
408
422
409
423
validate_attributes_a11y ( ) {
410
- const { component } = this ;
424
+ const { component, attributes } = this ;
411
425
412
- this . attributes . forEach ( attribute => {
426
+ attributes . forEach ( attribute => {
413
427
if ( attribute . is_spread ) return ;
414
428
415
429
const name = attribute . name . toLowerCase ( ) ;
@@ -462,6 +476,21 @@ export default class Element extends Node {
462
476
component . warn ( attribute , compiler_warnings . a11y_no_redundant_roles ( value ) ) ;
463
477
}
464
478
}
479
+
480
+ // @ts -ignore
481
+ const required_role_props = a11y_required_role_props [ value ] ;
482
+
483
+ // role-has-required-aria-props
484
+ if ( required_role_props ) {
485
+ const has_missing_props = required_role_props . some ( prop => ! attributes . find ( a => a . name === prop ) ) ;
486
+
487
+ if ( has_missing_props ) {
488
+ component . warn ( attribute , {
489
+ code : 'a11y-role-has-required-aria-props' ,
490
+ message : `A11y: Elements with the ARIA role "${ value } " must have the following attributes defined: ${ String ( required_role_props ) } `
491
+ } ) ;
492
+ }
493
+ }
465
494
}
466
495
467
496
// no-access-key
0 commit comments