Skip to content

Commit d1e4bee

Browse files
committed
Responding to PR feedback
1 parent 9b952b4 commit d1e4bee

9 files changed

+100
-27
lines changed

__mocks__/genInteractives.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,11 @@ export function genInteractiveElements () {
152152
}
153153

154154
export function genInteractiveRoleElements () {
155-
return interactiveRoles.map(
155+
return [
156+
...interactiveRoles,
157+
'button article',
158+
'fakerole button article',
159+
].map(
156160
value => JSXElementMock('div', [
157161
JSXAttributeMock('role', value)
158162
])
@@ -175,7 +179,11 @@ export function genNonInteractiveElements () {
175179
}
176180

177181
export function genNonInteractiveRoleElements () {
178-
return nonInteractiveRoles.map(
182+
return [
183+
...nonInteractiveRoles,
184+
'article button',
185+
'fakerole article button',
186+
].map(
179187
value => JSXElementMock('div', [
180188
JSXAttributeMock('role', value)
181189
])

__tests__/src/util/isAbstractRole-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
genNonAbstractRoleElements,
99
} from '../../../__mocks__/genInteractives';
1010

11-
describe('isInteractiveRole', () => {
11+
describe('isAbstractRole', () => {
1212
describe('JSX Components (no tagName)', () => {
1313
it('should NOT identify them as abstract role elements', () => {
1414
expect(isAbstractRole(undefined, []))

docs/rules/interactive-supports-focus.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Elements with an interactive role and interaction handlers (mouse or key press)
66

77
### Case: This element is a stand-alone control like a button, a link or a form element
88

9-
Add this prop to your component:
9+
Add the `tabIndex` property to your component. A value of zero indicates that this element can be tabbed to.
1010

1111
```
1212
<div
@@ -26,7 +26,7 @@ An element that can be tabbed to is said to be in the _tab ring_.
2626
One item in a group should have a tabindex of zero, the rest should have a tabindex of -1. A value of zero makes the element _tabbable_. A value of -1 makes the element _focusable_.
2727

2828
```
29-
<div role="men">
29+
<div role="menu">
3030
<div role="menuitem" tabIndex="0">Open</div>
3131
<div role="menuitem" tabIndex="-1">Save</div>
3232
<div role="menuitem" tabIndex="-1">Close</div>
@@ -41,7 +41,7 @@ If your element is catching bubbled click or key events from descendant elements
4141

4242
```
4343
<div
44-
onClick="onClickHandler"
44+
onClick={onClickHandler}
4545
role="presentation">
4646
<button>Save</button>
4747
</div>

docs/rules/no-interactive-element-to-noninteractive-role.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,23 @@ Put the content inside your interactive element.
4141

4242
## Rule details
4343

44-
This rule takes no arguments.
44+
The recommended options for this rule allow the `tr` element to be given a role of `presentation` (or its semantic equivalent `none`). Under normal circumstances, an element with an interactive role should not be semantically neutralized with `presentation` (or `none`).
45+
46+
Options are provided as an object keyed by HTML element name; the value is an array of interactive roles that are allowed on the specified element.
47+
48+
```
49+
{
50+
'no-interactive-element-to-noninteractive-role': [
51+
'error',
52+
{
53+
tr: ['none', 'presentation'],
54+
},
55+
]
56+
}
57+
```
58+
59+
Under the recommended options, the following code is valid. It would be invalid under the strict rules.
60+
61+
```
62+
<tr role="presentation" />
63+
```

docs/rules/no-noninteractive-element-to-interactive-role.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,24 @@ Or wrap the content inside your interactive element.
4545

4646
## Rule details
4747

48-
This rule takes no arguments.
48+
The recommended options for this rule allow several common interactive roles to be applied to a non-interactive element. The options are provided as an object keyed by HTML element name; the value is an array of interactive roles that are allowed on the specified element.
49+
50+
```
51+
{
52+
'no-noninteractive-element-to-interactive-role': [
53+
'error',
54+
{
55+
ul: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'],
56+
ol: ['listbox', 'menu', 'menubar', 'radiogroup', 'tablist', 'tree', 'treegrid'],
57+
li: ['menuitem', 'option', 'row', 'tab', 'treeitem'],
58+
table: ['grid'],
59+
td: ['gridcell'],
60+
},
61+
}
62+
```
63+
64+
Under the recommended options, the following code is valid. It would be invalid under the strict rules.
65+
66+
```
67+
<ul role="menu" />
68+
```

src/rules/no-interactive-element-to-noninteractive-role.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
elementType,
1717
getProp,
1818
getLiteralPropValue,
19+
propName,
1920
} from 'jsx-ast-utils';
2021
import type { JSXIdentifier } from 'ast-types-flow';
2122
import isInteractiveElement from '../util/isInteractiveElement';
@@ -48,7 +49,7 @@ module.exports = {
4849
JSXAttribute: (
4950
attribute: ESLintJSXAttribute,
5051
) => {
51-
const attributeName: JSXIdentifier = attribute.name.name;
52+
const attributeName: JSXIdentifier = propName(attribute);
5253
if (attributeName !== 'role') {
5354
return;
5455
}

src/rules/no-static-element-interactions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ module.exports = {
7272
|| isNonInteractiveRole(type, attributes)
7373
|| isAbstractRole(type, attributes)
7474
) {
75-
// This rule has no opinion about abtract roles.
75+
// This rule has no opinion about abstract roles.
7676
return;
7777
}
7878

src/util/isInteractiveRole.js

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import {
2-
roles,
2+
roles as rolesMap,
33
} from 'aria-query';
44
import type { Node } from 'ast-types-flow';
55
import { getProp, getLiteralPropValue } from 'jsx-ast-utils';
66

7-
const interactiveRoles = [...roles.keys()]
8-
.filter(name => !roles.get(name).abstract)
9-
.filter(name => roles.get(name).superClass.some(
7+
const roles = [...rolesMap.keys()];
8+
const interactiveRoles = roles
9+
.filter(name => !rolesMap.get(name).abstract)
10+
.filter(name => rolesMap.get(name).superClass.some(
1011
klasses => klasses.includes('widget')),
1112
);
1213

@@ -39,10 +40,18 @@ const isInteractiveRole = (
3940

4041
let isInteractive = false;
4142
const normalizedValues = String(value).toLowerCase().split(' ');
42-
if (normalizedValues.length > 0) {
43-
// The last role value is a series takes precedence.
44-
const val = normalizedValues[normalizedValues.length - 1];
45-
isInteractive = interactiveRoles.indexOf(val) > -1;
43+
const validRoles = normalizedValues.reduce((
44+
accumulator: Array<string>,
45+
name: string,
46+
) => {
47+
if (roles.includes(name)) {
48+
accumulator.push(name);
49+
}
50+
return accumulator;
51+
}, []);
52+
if (validRoles.length > 0) {
53+
// The first role value is a series takes precedence.
54+
isInteractive = interactiveRoles.includes(validRoles[0]);
4655
}
4756

4857
return isInteractive;

src/util/isNonInteractiveRole.js

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,17 @@
44

55
import {
66
dom,
7-
roles,
7+
roles as rolesMap,
88
} from 'aria-query';
99
import type { Node } from 'ast-types-flow';
1010
import { getProp, getLiteralPropValue } from 'jsx-ast-utils';
1111

12-
const nonInteractiveRoles = new Set(
13-
[...roles.keys()]
14-
.filter(name => !roles.get(name).abstract)
15-
.filter(name => !roles.get(name).superClass.some(
16-
klasses => klasses.includes('widget')),
17-
),
18-
);
12+
const roles = [...rolesMap.keys()];
13+
const nonInteractiveRoles = roles
14+
.filter(name => !rolesMap.get(name).abstract)
15+
.filter(name => !rolesMap.get(name).superClass.some(
16+
klasses => klasses.includes('widget')),
17+
);
1918

2019
/**
2120
* Returns boolean indicating whether the given element has a role
@@ -46,7 +45,24 @@ const isNonInteractiveRole = (
4645
}
4746

4847
const role = getLiteralPropValue(getProp(attributes, 'role'));
49-
return nonInteractiveRoles.has(role);
48+
49+
let isNonInteractive = false;
50+
const normalizedValues = String(role).toLowerCase().split(' ');
51+
const validRoles = normalizedValues.reduce((
52+
accumulator: Array<string>,
53+
name: string,
54+
) => {
55+
if (roles.includes(name)) {
56+
accumulator.push(name);
57+
}
58+
return accumulator;
59+
}, []);
60+
if (validRoles.length > 0) {
61+
// The first role value is a series takes precedence.
62+
isNonInteractive = nonInteractiveRoles.includes(validRoles[0]);
63+
}
64+
65+
return isNonInteractive;
5066
};
5167

5268
export default isNonInteractiveRole;

0 commit comments

Comments
 (0)