Skip to content

Commit a53212f

Browse files
committed
[new] Add basic suggestions for interactive-supports-focus
Add basic support for Eslint rule suggestions for the `interactive-supports-focus` for tabIndex values when elements are interactive but do not have implicit focusable browser behaviour. These are intentionally not auto-fix but simply optional suggestions to help developers resolve their issue. Fixes jsx-eslint#952 See also jsx-eslint#951
1 parent e5dda96 commit a53212f

File tree

4 files changed

+48
-2
lines changed

4 files changed

+48
-2
lines changed

.npmrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
package-lock=false
1+
# package-lock=false
22
allow-same-version=true
33
message=v%s

__tests__/src/rules/interactive-supports-focus-test.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ function template(strings, ...keys) {
3535
const ruleName = 'interactive-supports-focus';
3636
const type = 'JSXOpeningElement';
3737
const codeTemplate = template`<${0} role="${1}" ${2}={() => void 0} />`;
38+
const fixedTemplate = template`<${0} tabIndex={${1}} role="${2}" ${3}={() => void 0} />`;
3839
const tabindexTemplate = template`<${0} role="${1}" ${2}={() => void 0} tabIndex="0" />`;
3940
const tabbableTemplate = template`Elements with the '${0}' interactive role must be tabbable.`;
4041
const focusableTemplate = template`Elements with the '${0}' interactive role must be focusable.`;
@@ -47,7 +48,14 @@ const componentsSettings = {
4748
},
4849
};
4950

50-
const buttonError = { message: tabbableTemplate('button'), type };
51+
const buttonError = {
52+
message: tabbableTemplate('button'),
53+
suggestions: [{
54+
desc: 'Add `tabIndex={0}` to make the element focusable in sequential keyboard navigation.',
55+
output: '<Div tabIndex={0} onClick={() => void 0} role="button" />',
56+
}],
57+
type,
58+
};
5159

5260
const recommendedOptions = configs.recommended.rules[`jsx-a11y/${ruleName}`][1] || {};
5361

@@ -202,6 +210,13 @@ const failReducer = (roles, handlers, messageTemplate) => (
202210
errors: [{
203211
type,
204212
message: messageTemplate(role),
213+
suggestions: [{
214+
desc: 'Add `tabIndex={0}` to make the element focusable in sequential keyboard navigation.',
215+
output: fixedTemplate(element, '0', role, handler),
216+
}].concat(messageTemplate === focusableTemplate ? [{
217+
desc: 'Add `tabIndex={-1}` to make the element focusable but not reachable via sequential keyboard navigation.',
218+
output: fixedTemplate(element, '-1', role, handler),
219+
}] : []),
205220
}],
206221
})))
207222
), []))

docs/rules/label-has-associated-control.md

+4
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,13 @@ This rule takes one optional object argument of type object:
104104
```
105105

106106
`labelComponents` is a list of custom React Component names that should be checked for an associated control.
107+
107108
`labelAttributes` is a list of attributes to check on the label component and its children for a label. Use this if you have a custom component that uses a string passed on a prop to render an HTML `label`, for example.
109+
108110
`controlComponents` is a list of custom React Components names that will output an input element. [Glob format](https://linuxhint.com/bash_globbing_tutorial/) is also supported for specifying names (e.g., `Label*` matches `LabelComponent` but not `CustomLabel`, `????Label` matches `LinkLabel` but not `CustomLabel`).
111+
109112
`assert` asserts that the label has htmlFor, a nested label, both or either. Available options: `'htmlFor', 'nesting', 'both', 'either'`.
113+
110114
`depth` (default 2, max 25) is an integer that determines how deep within a `JSXElement` label the rule should look for text content or an element with a label to determine if the `label` element will have an accessible label.
111115

112116
### Fail

src/rules/interactive-supports-focus.js

+27
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ export default ({
5454
url: 'https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/tree/HEAD/docs/rules/interactive-supports-focus.md',
5555
description: 'Enforce that elements with interactive handlers like `onClick` must be focusable.',
5656
},
57+
hasSuggestions: true,
58+
messages: {
59+
'tabIndex=0': 'Add `tabIndex={0}` to make the element focusable in sequential keyboard navigation.',
60+
'tabIndex=-1': 'Add `tabIndex={-1}` to make the element focusable but not reachable via sequential keyboard navigation.',
61+
},
5762
schema: [schema],
5863
},
5964

@@ -100,12 +105,34 @@ export default ({
100105
context.report({
101106
node,
102107
message: `Elements with the '${role}' interactive role must be tabbable.`,
108+
suggest: [
109+
{
110+
messageId: 'tabIndex=0',
111+
fix(fixer) {
112+
return fixer.insertTextAfter(node.name, ' tabIndex={0}');
113+
},
114+
},
115+
],
103116
});
104117
} else {
105118
// Focusable, tabIndex = -1 or 0
106119
context.report({
107120
node,
108121
message: `Elements with the '${role}' interactive role must be focusable.`,
122+
suggest: [
123+
{
124+
messageId: 'tabIndex=0',
125+
fix(fixer) {
126+
return fixer.insertTextAfter(node.name, ' tabIndex={0}');
127+
},
128+
},
129+
{
130+
messageId: 'tabIndex=-1',
131+
fix(fixer) {
132+
return fixer.insertTextAfter(node.name, ' tabIndex={-1}');
133+
},
134+
},
135+
],
109136
});
110137
}
111138
}

0 commit comments

Comments
 (0)