-
Notifications
You must be signed in to change notification settings - Fork 779
/
get-role.js
180 lines (157 loc) · 5.75 KB
/
get-role.js
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import getExplicitRole from './get-explicit-role';
import getImplicitRole from './implicit-role';
import getGlobalAriaAttrs from '../standards/get-global-aria-attrs';
import isFocusable from '../dom/is-focusable';
import { nodeLookup } from '../../core/utils';
// when an element inherits the presentational role from a parent
// is not defined in the spec, but through testing it seems to be
// when a specific HTML parent relationship is required and that
// parent has `role=presentation`, then the child inherits the
// role (i.e. table, ul, dl). Further testing has shown that
// intermediate elements (such as divs) break this chain only in
// Chrome.
//
// Also, any nested structure chains reset the role (so two nested
// lists with the topmost list role=none will not cause the nested
// list to inherit the role=none).
//
// from Scott O'Hara:
//
// "the expectation for me, in standard html is that element
// structures that require specific parent/child relationships,
// if the parent is set to presentational that should set the
// children to presentational. ala, tables and lists."
// "but outside of those specific constructs, i would not expect
// role=presentation to do anything to child element roles"
const inheritsPresentationChain = {
// valid parent elements, any other element will prevent any
// children from inheriting a presentational role from a valid
// ancestor
td: ['tr'],
th: ['tr'],
tr: ['thead', 'tbody', 'tfoot', 'table'],
thead: ['table'],
tbody: ['table'],
tfoot: ['table'],
li: ['ol', 'ul'],
// dts and dds can be wrapped in divs and the div will pass through
// the presentation role
dt: ['dl', 'div'],
dd: ['dl', 'div'],
div: ['dl']
};
// role presentation inheritance.
// Source: https://www.w3.org/TR/wai-aria-1.1/#conflict_resolution_presentation_none
function getInheritedRole(vNode, explicitRoleOptions) {
const parentNodeNames = inheritsPresentationChain[vNode.props.nodeName];
if (!parentNodeNames) {
return null;
}
// if we can't look at the parent then we can't know if the node
// inherits the presentational role or not
if (!vNode.parent) {
if (!vNode.actualNode) {
return null;
}
throw new ReferenceError(
'Cannot determine role presentational inheritance of a required parent outside the current scope.'
);
}
// parent is not a valid ancestor that can inherit presentation
if (!parentNodeNames.includes(vNode.parent.props.nodeName)) {
return null;
}
const parentRole = getExplicitRole(vNode.parent, explicitRoleOptions);
if (
['none', 'presentation'].includes(parentRole) &&
!hasConflictResolution(vNode.parent)
) {
return parentRole;
}
// an explicit role of anything other than presentational will
// prevent any children from inheriting a presentational role
// from a valid ancestor
if (parentRole) {
return null;
}
return getInheritedRole(vNode.parent, explicitRoleOptions);
}
function resolveImplicitRole(vNode, { chromium, ...explicitRoleOptions }) {
const implicitRole = getImplicitRole(vNode, {
chromium
});
if (!implicitRole) {
return null;
}
const presentationalRole = getInheritedRole(vNode, explicitRoleOptions);
if (presentationalRole) {
return presentationalRole;
}
return implicitRole;
}
// role conflict resolution
// note: Chrome returns a list with resolved role as "generic"
// instead of as a list
// (e.g. <ul role="none" aria-label><li>hello</li></ul>)
// we will return it as a list as that is the best option.
// Source: https://www.w3.org/TR/wai-aria-1.1/#conflict_resolution_presentation_none
// See also: https://github.com/w3c/aria/issues/1270
function hasConflictResolution(vNode) {
const hasGlobalAria = getGlobalAriaAttrs().some(attr => vNode.hasAttr(attr));
return hasGlobalAria || isFocusable(vNode);
}
/**
*
* @method resolveRole
* @param {Element|VirtualNode} node
* @param {Object} options
* @see getRole for option details
* @returns {string|null} Role or null
* @deprecated noImplicit option is deprecated. Use aria.getExplicitRole instead.
*/
function resolveRole(node, { noImplicit, ...roleOptions } = {}) {
const { vNode } = nodeLookup(node);
if (vNode.props.nodeType !== 1) {
return null;
}
const explicitRole = getExplicitRole(vNode, roleOptions);
if (!explicitRole) {
return noImplicit ? null : resolveImplicitRole(vNode, roleOptions);
}
if (!['presentation', 'none'].includes(explicitRole)) {
return explicitRole;
}
if (hasConflictResolution(vNode)) {
// return null if there is a conflict resolution but no implicit
// has been set as the explicit role is not the true role
return noImplicit ? null : resolveImplicitRole(vNode, roleOptions);
}
// role presentation or none and no conflict resolution
return explicitRole;
}
/**
* Return the semantic role of an element.
*
* @method getRole
* @memberof axe.commons.aria
* @instance
* @param {Element|VirtualNode} node
* @param {Object} options
* @param {boolean} options.noImplicit Do not return the implicit role // @deprecated
* @param {boolean} options.fallback Allow fallback roles
* @param {boolean} options.abstracts Allow role to be abstract
* @param {boolean} options.dpub Allow role to be any (valid) doc-* roles
* @param {boolean} options.noPresentational return null if role is presentation or none
* @param {boolean} options.chromium Include implicit roles from chromium-based browsers in role result
* @returns {string|null} Role or null
*
* @deprecated noImplicit option is deprecated. Use aria.getExplicitRole instead.
*/
function getRole(node, { noPresentational, ...options } = {}) {
const role = resolveRole(node, options);
if (noPresentational && ['presentation', 'none'].includes(role)) {
return null;
}
return role;
}
export default getRole;