Skip to content

Commit 6b767a5

Browse files
pass params
1 parent 7ddd508 commit 6b767a5

File tree

6 files changed

+139
-99
lines changed

6 files changed

+139
-99
lines changed

README.md

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -82,29 +82,29 @@ This config will be interpreted in the following way:
8282
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
8383
❌ Deprecated.
8484

85-
| Name                                 | Description | 💼 | 🔧 ||
86-
| :----------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |
87-
| [a11y-aria-label-is-well-formatted](docs/rules/a11y-aria-label-is-well-formatted.md) | [aria-label] text should be formatted as you would visual text. | ⚛️ | | |
88-
| [a11y-no-generic-link-text](docs/rules/a11y-no-generic-link-text.md) | disallow generic link text | | ||
89-
| [a11y-no-sr-only-class-when-focusable](docs/rules/a11y-no-sr-only-class-when-focusable.md) | Ensures that interactive elements are not visually hidden | | | |
90-
| [array-foreach](docs/rules/array-foreach.md) | enforce `for..of` loops over `Array.forEach` || | |
91-
| [async-currenttarget](docs/rules/async-currenttarget.md) | disallow `event.currentTarget` calls inside of async functions | 🔍 | | |
92-
| [async-preventdefault](docs/rules/async-preventdefault.md) | disallow `event.preventDefault` calls inside of async functions | 🔍 | | |
93-
| [authenticity-token](docs/rules/authenticity-token.md) | disallow usage of CSRF tokens in JavaScript | 🔐 | | |
94-
| [get-attribute](docs/rules/get-attribute.md) | disallow wrong usage of attribute names | 🔍 | 🔧 | |
95-
| [js-class-name](docs/rules/js-class-name.md) | enforce a naming convention for js- prefixed classes | 🔐 | | |
96-
| [no-blur](docs/rules/no-blur.md) | disallow usage of `Element.prototype.blur()` | 🔍 | | |
97-
| [no-d-none](docs/rules/no-d-none.md) | disallow usage the `d-none` CSS class | 🔐 | | |
98-
| [no-dataset](docs/rules/no-dataset.md) | enforce usage of `Element.prototype.getAttribute` instead of `Element.prototype.datalist` | 🔍 | | |
99-
| [no-dynamic-script-tag](docs/rules/no-dynamic-script-tag.md) | disallow creating dynamic script tags || | |
100-
| [no-implicit-buggy-globals](docs/rules/no-implicit-buggy-globals.md) | disallow implicit global variables || | |
101-
| [no-inner-html](docs/rules/no-inner-html.md) | disallow `Element.prototype.innerHTML` in favor of `Element.prototype.textContent` | 🔍 | | |
102-
| [no-innerText](docs/rules/no-innerText.md) | disallow `Element.prototype.innerText` in favor of `Element.prototype.textContent` | 🔍 | 🔧 | |
103-
| [no-then](docs/rules/no-then.md) | enforce using `async/await` syntax over Promises || | |
104-
| [no-useless-passive](docs/rules/no-useless-passive.md) | disallow marking a event handler as passive when it has no effect | 🔍 | 🔧 | |
105-
| [prefer-observers](docs/rules/prefer-observers.md) | disallow poorly performing event listeners | 🔍 | | |
106-
| [require-passive-events](docs/rules/require-passive-events.md) | enforce marking high frequency event handlers as passive | 🔍 | | |
107-
| [role-supports-aria-props](docs/rules/role-supports-aria-props.md) | Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`. | ⚛️ | | |
108-
| [unescaped-html-literal](docs/rules/unescaped-html-literal.md) | disallow unescaped HTML literals | 🔍 | | |
85+
| Name                                        | Description | 💼 | 🔧 ||
86+
| :------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |
87+
| [a11y-aria-label-is-well-formatted](docs/rules/a11y-aria-label-is-well-formatted.md) | [aria-label] text should be formatted as you would visual text. | ⚛️ | | |
88+
| [a11y-no-generic-link-text](docs/rules/a11y-no-generic-link-text.md) | disallow generic link text | | ||
89+
| [a11y-no-visually-hidden-interactive-element](docs/rules/a11y-no-visually-hidden-interactive-element.md) | Ensures that interactive elements are not visually hidden | | | |
90+
| [array-foreach](docs/rules/array-foreach.md) | enforce `for..of` loops over `Array.forEach` || | |
91+
| [async-currenttarget](docs/rules/async-currenttarget.md) | disallow `event.currentTarget` calls inside of async functions | 🔍 | | |
92+
| [async-preventdefault](docs/rules/async-preventdefault.md) | disallow `event.preventDefault` calls inside of async functions | 🔍 | | |
93+
| [authenticity-token](docs/rules/authenticity-token.md) | disallow usage of CSRF tokens in JavaScript | 🔐 | | |
94+
| [get-attribute](docs/rules/get-attribute.md) | disallow wrong usage of attribute names | 🔍 | 🔧 | |
95+
| [js-class-name](docs/rules/js-class-name.md) | enforce a naming convention for js- prefixed classes | 🔐 | | |
96+
| [no-blur](docs/rules/no-blur.md) | disallow usage of `Element.prototype.blur()` | 🔍 | | |
97+
| [no-d-none](docs/rules/no-d-none.md) | disallow usage the `d-none` CSS class | 🔐 | | |
98+
| [no-dataset](docs/rules/no-dataset.md) | enforce usage of `Element.prototype.getAttribute` instead of `Element.prototype.datalist` | 🔍 | | |
99+
| [no-dynamic-script-tag](docs/rules/no-dynamic-script-tag.md) | disallow creating dynamic script tags || | |
100+
| [no-implicit-buggy-globals](docs/rules/no-implicit-buggy-globals.md) | disallow implicit global variables || | |
101+
| [no-inner-html](docs/rules/no-inner-html.md) | disallow `Element.prototype.innerHTML` in favor of `Element.prototype.textContent` | 🔍 | | |
102+
| [no-innerText](docs/rules/no-innerText.md) | disallow `Element.prototype.innerText` in favor of `Element.prototype.textContent` | 🔍 | 🔧 | |
103+
| [no-then](docs/rules/no-then.md) | enforce using `async/await` syntax over Promises || | |
104+
| [no-useless-passive](docs/rules/no-useless-passive.md) | disallow marking a event handler as passive when it has no effect | 🔍 | 🔧 | |
105+
| [prefer-observers](docs/rules/prefer-observers.md) | disallow poorly performing event listeners | 🔍 | | |
106+
| [require-passive-events](docs/rules/require-passive-events.md) | enforce marking high frequency event handlers as passive | 🔍 | | |
107+
| [role-supports-aria-props](docs/rules/role-supports-aria-props.md) | Enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`. | ⚛️ | | |
108+
| [unescaped-html-literal](docs/rules/unescaped-html-literal.md) | disallow unescaped HTML literals | 🔍 | | |
109109

110110
<!-- end auto-generated rules list -->

docs/rules/a11y-no-sr-only-class-when-focusable.md renamed to docs/rules/a11y-no-visually-hidden-interactive-element.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Ensures that interactive elements are not visually hidden (`github/a11y-no-sr-only-class-when-focusable`)
1+
# Ensures that interactive elements are not visually hidden (`github/a11y-no-visually-hidden-interactive-element`)
22

33
<!-- end auto-generated rule header -->
44

@@ -9,7 +9,7 @@ This rule guards against visually hiding interactive elements. If a sighted keyb
99
👎 Examples of **incorrect** code for this rule:
1010

1111
```jsx
12-
<button className="sr-only">Submit</button>
12+
<button className="visually-hidden">Submit</button>
1313
```
1414

1515
```jsx
@@ -25,7 +25,7 @@ This rule guards against visually hiding interactive elements. If a sighted keyb
2525
👍 Examples of **correct** code for this rule:
2626

2727
```jsx
28-
<h2 className="sr-only">Welcome to GitHub</h2>
28+
<h2 className="visually-hidden">Welcome to GitHub</h2>
2929
```
3030

3131
```jsx
@@ -38,4 +38,18 @@ This rule guards against visually hiding interactive elements. If a sighted keyb
3838
<VisuallyHidden as="h2">Welcome to GitHub</VisuallyHidden>
3939
```
4040

41+
## Options
42+
43+
```json
44+
{
45+
"a11y-no-visually-hidden-interactive-element": [
46+
"error",
47+
{
48+
"className": "visually-hidden",
49+
"componentName": "VisuallyHidden"
50+
}
51+
]
52+
}
53+
```
54+
4155
## Version

lib/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = {
22
rules: {
3-
'a11y-no-sr-only-class-when-focusable': require('./rules/a11y-no-sr-only-class-when-focusable'),
3+
'a11y-no-visually-hidden-interactive-element': require('./rules/a11y-no-visually-hidden-interactive-element'),
44
'a11y-no-generic-link-text': require('./rules/a11y-no-generic-link-text'),
55
'a11y-aria-label-is-well-formatted': require('./rules/a11y-aria-label-is-well-formatted'),
66
'array-foreach': require('./rules/array-foreach'),

lib/rules/a11y-no-sr-only-class-when-focusable.js

Lines changed: 0 additions & 66 deletions
This file was deleted.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
const {getProp, getPropValue} = require('jsx-ast-utils')
2+
const {getElementType} = require('../utils/get-element-type')
3+
const {generateObjSchema} = require('eslint-plugin-jsx-a11y/lib/util/schemas')
4+
5+
const defaultClassName = 'sr-only'
6+
const defaultcomponentName = 'VisuallyHidden'
7+
8+
const schema = generateObjSchema({
9+
className: {type: 'string'},
10+
componentName: {type: 'string'},
11+
})
12+
13+
/** Note: we are not including input elements at this time
14+
* because a visually hidden input field might cause a false positive.
15+
* (e.g. fileUpload https://github.com/primer/react/pull/3492)
16+
*/
17+
const INTERACTIVELEMENTS = ['a', 'button', 'summary', 'select', 'option', 'textarea']
18+
19+
const checkIfInteractiveElement = (context, node) => {
20+
const elementType = getElementType(context, node.openingElement)
21+
const asProp = getPropValue(getProp(node.openingElement.attributes, 'as'))
22+
23+
for (const interactiveElement of INTERACTIVELEMENTS) {
24+
if ((asProp ?? elementType) === interactiveElement) {
25+
return true
26+
}
27+
}
28+
return false
29+
}
30+
31+
// if the node is visually hidden recursively check if it has interactive children
32+
const checkIfVisuallyHiddenAndInteractive = (context, className, componentName, node, isParentVisuallyHidden) => {
33+
if (node.type === 'JSXElement') {
34+
const classes = getPropValue(getProp(node.openingElement.attributes, 'className'))
35+
const isVisuallyHiddenElement = node.openingElement.name.name === componentName
36+
const hasSROnlyClass = typeof classes !== 'undefined' && classes.includes(className)
37+
let isHidden = false
38+
39+
if (hasSROnlyClass || isVisuallyHiddenElement || !!isParentVisuallyHidden) {
40+
if (checkIfInteractiveElement(context, node)) {
41+
return true
42+
}
43+
isHidden = true
44+
}
45+
if (node.children && node.children.length > 0) {
46+
return (
47+
typeof node.children?.find(child =>
48+
checkIfVisuallyHiddenAndInteractive(
49+
context,
50+
className,
51+
componentName,
52+
child,
53+
!!isParentVisuallyHidden || isHidden,
54+
),
55+
) !== 'undefined'
56+
)
57+
}
58+
}
59+
return false
60+
}
61+
62+
module.exports = {
63+
meta: {
64+
docs: {
65+
description: 'Ensures that interactive elements are not visually hidden',
66+
url: require('../url')(module),
67+
},
68+
schema: [schema],
69+
},
70+
71+
create(context) {
72+
const {options} = context
73+
const config = options[0] || {}
74+
const className = config.className || defaultClassName
75+
const componentName = config.componentName || defaultcomponentName
76+
77+
return {
78+
JSXElement: node => {
79+
if (checkIfVisuallyHiddenAndInteractive(context, className, componentName, node, false)) {
80+
context.report({
81+
node,
82+
message:
83+
'Avoid visually hidding interactive elements. Visually hiding interactive elements can be confusing to sighted keyboard users as it appears their focus has been lost when they navigate to the hidden element.',
84+
})
85+
return
86+
}
87+
},
88+
}
89+
},
90+
}

0 commit comments

Comments
 (0)