-
Notifications
You must be signed in to change notification settings - Fork 63
Expand file tree
/
Copy patha11y-no-visually-hidden-interactive-element.js
More file actions
90 lines (80 loc) · 2.95 KB
/
a11y-no-visually-hidden-interactive-element.js
File metadata and controls
90 lines (80 loc) · 2.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
const {getProp, getPropValue} = require('jsx-ast-utils')
const {getElementType} = require('../utils/get-element-type')
const {generateObjSchema} = require('eslint-plugin-jsx-a11y/lib/util/schemas')
const defaultClassName = 'sr-only'
const defaultcomponentName = 'VisuallyHidden'
const schema = generateObjSchema({
className: {type: 'string'},
componentName: {type: 'string'},
})
/** Note: we are not including input elements at this time
* because a visually hidden input field might cause a false positive.
* (e.g. fileUpload https://github.com/primer/react/pull/3492)
*/
const INTERACTIVELEMENTS = ['a', 'button', 'summary', 'select', 'option', 'textarea']
const checkIfInteractiveElement = (context, node) => {
const elementType = getElementType(context, node.openingElement)
const asProp = getPropValue(getProp(node.openingElement.attributes, 'as'))
for (const interactiveElement of INTERACTIVELEMENTS) {
if ((asProp ?? elementType) === interactiveElement) {
return true
}
}
return false
}
// if the node is visually hidden recursively check if it has interactive children
const checkIfVisuallyHiddenAndInteractive = (context, className, componentName, node, isParentVisuallyHidden) => {
if (node.type === 'JSXElement') {
const classes = getPropValue(getProp(node.openingElement.attributes, 'className'))
const isVisuallyHiddenElement = node.openingElement.name.name === componentName
const hasSROnlyClass = typeof classes !== 'undefined' && classes.includes(className)
let isHidden = false
if (hasSROnlyClass || isVisuallyHiddenElement || !!isParentVisuallyHidden) {
if (checkIfInteractiveElement(context, node)) {
return true
}
isHidden = true
}
if (node.children && node.children.length > 0) {
return (
typeof node.children?.find(child =>
checkIfVisuallyHiddenAndInteractive(
context,
className,
componentName,
child,
!!isParentVisuallyHidden || isHidden,
),
) !== 'undefined'
)
}
}
return false
}
module.exports = {
meta: {
docs: {
description: 'Ensures that interactive elements are not visually hidden',
url: require('../url')(module),
},
schema: [schema],
},
create(context) {
const {options} = context
const config = options[0] || {}
const className = config.className || defaultClassName
const componentName = config.componentName || defaultcomponentName
return {
JSXElement: node => {
if (checkIfVisuallyHiddenAndInteractive(context, className, componentName, node, false)) {
context.report({
node,
message:
'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.',
})
return
}
},
}
},
}