aria-api 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aria.js +339 -179
- package/lib/atree.js +24 -24
- package/lib/attrs.js +45 -30
- package/lib/constants.js +155 -26
- package/lib/name.js +103 -85
- package/lib/query.js +12 -14
- package/package.json +1 -1
package/lib/name.js
CHANGED
|
@@ -1,42 +1,56 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
const constants = require('./constants.js');
|
|
2
|
+
const atree = require('./atree.js');
|
|
3
|
+
const query = require('./query.js');
|
|
4
|
+
|
|
5
|
+
const addSpaces = function(text, el, pseudoSelector) {
|
|
6
|
+
// https://github.com/w3c/accname/issues/3
|
|
7
|
+
const styles = window.getComputedStyle(el, pseudoSelector);
|
|
8
|
+
const inline = styles.display === 'inline';
|
|
9
|
+
return inline ? text : ` ${text} `;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const getPseudoContent = function(el, pseudoSelector) {
|
|
13
|
+
const styles = window.getComputedStyle(el, pseudoSelector);
|
|
14
|
+
let tail = styles.getPropertyValue('content').trim();
|
|
15
|
+
let ret = [];
|
|
16
|
+
|
|
17
|
+
let match;
|
|
18
|
+
while (tail.length) {
|
|
19
|
+
if (match = tail.match(/^"([^"]*)"/)) {
|
|
20
|
+
ret.push(match[1]);
|
|
21
|
+
} else if (match = tail.match(/^([a-z-]+)\(([^)]*)\)/)) {
|
|
22
|
+
if (match[1] === 'attr') {
|
|
23
|
+
ret.push(el.getAttribute(match[2]) || '');
|
|
24
|
+
}
|
|
25
|
+
} else if (match = tail.match(/^([a-z-]+)/)) {
|
|
26
|
+
if (match[1] === 'open-quote' || match[1] === 'close-quote') {
|
|
27
|
+
ret.push('"');
|
|
28
|
+
}
|
|
29
|
+
} else if (match = tail.match(/^\//)) {
|
|
30
|
+
ret = [];
|
|
17
31
|
} else {
|
|
18
|
-
|
|
32
|
+
// invalid content, ignore
|
|
33
|
+
return '';
|
|
19
34
|
}
|
|
35
|
+
tail = tail.slice(match[0].length).trim();
|
|
20
36
|
}
|
|
37
|
+
|
|
38
|
+
return addSpaces(ret.join(''), el, pseudoSelector);
|
|
21
39
|
};
|
|
22
40
|
|
|
23
|
-
|
|
24
|
-
|
|
41
|
+
const getContent = function(root, ongoingLabelledBy, visited) {
|
|
42
|
+
const children = atree.getChildNodes(root);
|
|
25
43
|
|
|
26
|
-
|
|
27
|
-
for (
|
|
28
|
-
|
|
44
|
+
let ret = '';
|
|
45
|
+
for (let i = 0; i < children.length; i++) {
|
|
46
|
+
const node = children[i];
|
|
29
47
|
if (node.nodeType === node.TEXT_NODE) {
|
|
30
48
|
ret += node.textContent;
|
|
31
49
|
} else if (node.nodeType === node.ELEMENT_NODE) {
|
|
32
50
|
if (node.tagName.toLowerCase() === 'br') {
|
|
33
51
|
ret += '\n';
|
|
34
|
-
} else if (window.getComputedStyle(node).display.substr(0, 6) === 'inline' &&
|
|
35
|
-
node.tagName.toLowerCase() !== 'input' &&
|
|
36
|
-
node.tagName.toLowerCase() !== 'img') { // https://github.com/w3c/accname/issues/3
|
|
37
|
-
ret += getName(node, true, visited);
|
|
38
52
|
} else {
|
|
39
|
-
ret +=
|
|
53
|
+
ret += getName(node, true, ongoingLabelledBy, visited);
|
|
40
54
|
}
|
|
41
55
|
}
|
|
42
56
|
}
|
|
@@ -44,18 +58,15 @@ var getContent = function(root, visited) {
|
|
|
44
58
|
return ret;
|
|
45
59
|
};
|
|
46
60
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
var isInLabelForOtherWidget = function(el) {
|
|
53
|
-
var label = el.parentElement.closest('label');
|
|
54
|
-
return label && (!el.labels || !Array.prototype.includes.call(el.labels, label));
|
|
61
|
+
const allowNameFromContent = function(el) {
|
|
62
|
+
const role = query.getRole(el);
|
|
63
|
+
if (role) {
|
|
64
|
+
return constants.roles[role].nameFromContents;
|
|
65
|
+
}
|
|
55
66
|
};
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
const getName = function(el, recursive, ongoingLabelledBy, visited, directReference) {
|
|
69
|
+
let ret = '';
|
|
59
70
|
|
|
60
71
|
visited = visited || [];
|
|
61
72
|
if (visited.includes(el)) {
|
|
@@ -70,15 +81,31 @@ var getName = function(el, recursive, visited, directReference) {
|
|
|
70
81
|
// handled in atree
|
|
71
82
|
|
|
72
83
|
// B
|
|
73
|
-
if (!
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return label ? getName(label, true, visited, true) : '';
|
|
84
|
+
if (!ongoingLabelledBy && el.matches('[aria-labelledby]')) {
|
|
85
|
+
const ids = el.getAttribute('aria-labelledby').split(/\s+/);
|
|
86
|
+
const strings = ids.map(id => {
|
|
87
|
+
const label = document.getElementById(id);
|
|
88
|
+
return label ? getName(label, true, true, visited, true) : '';
|
|
78
89
|
});
|
|
79
90
|
ret = strings.join(' ');
|
|
80
91
|
}
|
|
81
92
|
|
|
93
|
+
// E (the current draft has this at this high priority)
|
|
94
|
+
if (!ret.trim() && recursive) {
|
|
95
|
+
if (query.matches(el, 'textbox')) {
|
|
96
|
+
ret = el.value || el.textContent;
|
|
97
|
+
} else if (query.matches(el, 'combobox,listbox')) {
|
|
98
|
+
const selected = query.querySelector(el, ':selected') || query.querySelector(el, 'option');
|
|
99
|
+
if (selected) {
|
|
100
|
+
ret = getName(selected, recursive, ongoingLabelledBy, visited);
|
|
101
|
+
} else {
|
|
102
|
+
ret = el.value || '';
|
|
103
|
+
}
|
|
104
|
+
} else if (query.matches(el, 'range')) {
|
|
105
|
+
ret = '' + (query.getAttribute(el, 'valuetext') || query.getAttribute(el, 'valuenow') || el.value);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
82
109
|
// C
|
|
83
110
|
if (!ret.trim() && el.matches('[aria-label]')) {
|
|
84
111
|
// FIXME: may skip to 2E
|
|
@@ -87,14 +114,11 @@ var getName = function(el, recursive, visited, directReference) {
|
|
|
87
114
|
|
|
88
115
|
// D
|
|
89
116
|
if (!ret.trim() && !recursive && el.labels) {
|
|
90
|
-
|
|
91
|
-
return getName(label, true, visited);
|
|
117
|
+
const strings = Array.prototype.map.call(el.labels, label => {
|
|
118
|
+
return getName(label, true, ongoingLabelledBy, visited);
|
|
92
119
|
});
|
|
93
120
|
ret = strings.join(' ');
|
|
94
121
|
}
|
|
95
|
-
if (!ret.trim()) {
|
|
96
|
-
ret = el.placeholder || '';
|
|
97
|
-
}
|
|
98
122
|
if (!ret.trim()) {
|
|
99
123
|
ret = el.alt || '';
|
|
100
124
|
}
|
|
@@ -102,48 +126,37 @@ var getName = function(el, recursive, visited, directReference) {
|
|
|
102
126
|
ret = el.title;
|
|
103
127
|
}
|
|
104
128
|
if (!ret.trim()) {
|
|
105
|
-
for (
|
|
129
|
+
for (const selector in constants.nameFromDescendant) {
|
|
106
130
|
if (el.matches(selector)) {
|
|
107
|
-
|
|
131
|
+
const descendant = el.querySelector(constants.nameFromDescendant[selector]);
|
|
108
132
|
if (descendant) {
|
|
109
|
-
ret = getName(descendant, true, visited);
|
|
133
|
+
ret = getName(descendant, true, ongoingLabelledBy, visited);
|
|
110
134
|
}
|
|
111
135
|
}
|
|
112
136
|
}
|
|
113
137
|
}
|
|
114
138
|
if (!ret.trim() && el.matches('svg *')) {
|
|
115
|
-
|
|
139
|
+
const svgTitle = el.querySelector('title');
|
|
116
140
|
if (svgTitle && svgTitle.parentElement === el) {
|
|
117
141
|
ret = svgTitle.textContent;
|
|
118
142
|
}
|
|
119
143
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (!ret.trim() && (recursive || isInLabelForOtherWidget(el) || query.matches(el, 'button'))) {
|
|
123
|
-
if (query.matches(el, 'textbox,button,combobox,listbox,range')) {
|
|
124
|
-
if (query.matches(el, 'textbox,button')) {
|
|
125
|
-
ret = el.value || el.textContent;
|
|
126
|
-
} else if (query.matches(el, 'combobox,listbox')) {
|
|
127
|
-
var selected = query.querySelector(el, ':selected') || query.querySelector(el, 'option');
|
|
128
|
-
if (selected) {
|
|
129
|
-
ret = getName(selected, recursive, visited);
|
|
130
|
-
} else {
|
|
131
|
-
ret = el.value || '';
|
|
132
|
-
}
|
|
133
|
-
} else if (query.matches(el, 'range')) {
|
|
134
|
-
ret = '' + (query.getAttribute(el, 'valuetext') || query.getAttribute(el, 'valuenow') || el.value);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
144
|
+
if (!ret.trim() && el.matches('a')) {
|
|
145
|
+
ret = el.getAttribute('xlink:title') || '';
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
// F
|
|
140
149
|
// FIXME: menu is not mentioned in the spec
|
|
141
150
|
if (!ret.trim() && (recursive || allowNameFromContent(el) || el.closest('label')) && !query.matches(el, 'menu')) {
|
|
142
|
-
ret = getContent(el, visited);
|
|
151
|
+
ret = getContent(el, ongoingLabelledBy, visited);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!ret.trim() && query.matches(el, 'button')) {
|
|
155
|
+
ret = el.value || '';
|
|
143
156
|
}
|
|
144
157
|
|
|
145
158
|
if (!ret.trim()) {
|
|
146
|
-
for (
|
|
159
|
+
for (const selector in constants.nameDefaults) {
|
|
147
160
|
if (el.matches(selector)) {
|
|
148
161
|
ret = constants.nameDefaults[selector];
|
|
149
162
|
}
|
|
@@ -154,9 +167,8 @@ var getName = function(el, recursive, visited, directReference) {
|
|
|
154
167
|
// handled in getContent
|
|
155
168
|
|
|
156
169
|
// I
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
ret = el.title || '';
|
|
170
|
+
if (!ret.trim()) {
|
|
171
|
+
ret = el.title || el.placeholder || '';
|
|
160
172
|
}
|
|
161
173
|
|
|
162
174
|
// FIXME: not exactly sure about this, but it reduces the number of failing
|
|
@@ -165,35 +177,41 @@ var getName = function(el, recursive, visited, directReference) {
|
|
|
165
177
|
ret = ' ';
|
|
166
178
|
}
|
|
167
179
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return before + ret + after;
|
|
180
|
+
const before = getPseudoContent(el, ':before');
|
|
181
|
+
const after = getPseudoContent(el, ':after');
|
|
182
|
+
return addSpaces(before + ret + after, el);
|
|
171
183
|
};
|
|
172
184
|
|
|
173
|
-
|
|
174
|
-
return getName(el)
|
|
185
|
+
const getNameTrimmed = function(el) {
|
|
186
|
+
return getName(el)
|
|
187
|
+
.replace(/[ \n\r\t\f]+/g, ' ')
|
|
188
|
+
.replace(/^ /, '')
|
|
189
|
+
.replace(/ $/, '');
|
|
175
190
|
};
|
|
176
191
|
|
|
177
|
-
|
|
178
|
-
|
|
192
|
+
const getDescription = function(el) {
|
|
193
|
+
let ret = '';
|
|
179
194
|
|
|
180
195
|
if (el.matches('[aria-describedby]')) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return label ? getName(label, true) : '';
|
|
196
|
+
const ids = el.getAttribute('aria-describedby').split(/\s+/);
|
|
197
|
+
const strings = ids.map(id => {
|
|
198
|
+
const label = document.getElementById(id);
|
|
199
|
+
return label ? getName(label, true, true) : '';
|
|
185
200
|
});
|
|
186
201
|
ret = strings.join(' ');
|
|
187
202
|
} else if (el.matches('[aria-description]')) {
|
|
188
203
|
ret = el.getAttribute('aria-description');
|
|
189
204
|
} else if (el.matches('svg *')) {
|
|
190
|
-
|
|
205
|
+
const svgDesc = el.querySelector('desc');
|
|
191
206
|
if (svgDesc && svgDesc.parentElement === el) {
|
|
192
207
|
ret = svgDesc.textContent;
|
|
193
208
|
}
|
|
194
209
|
} else if (el.title) {
|
|
195
210
|
ret = el.title;
|
|
196
211
|
}
|
|
212
|
+
if (!ret.trim() && el.matches('a')) {
|
|
213
|
+
ret = el.getAttribute('xlink:title') || '';
|
|
214
|
+
}
|
|
197
215
|
|
|
198
216
|
ret = (ret || '').trim().replace(/\s+/g, ' ');
|
|
199
217
|
|
package/lib/query.js
CHANGED
|
@@ -1,31 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const attrs = require('./attrs.js');
|
|
2
|
+
const atree = require('./atree.js');
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
var actual;
|
|
7
|
-
|
|
5
|
+
const matches = function(el, selector) {
|
|
8
6
|
if (selector.substr(0, 1) === ':') {
|
|
9
|
-
|
|
7
|
+
const attr = selector.substr(1);
|
|
10
8
|
return attrs.getAttribute(el, attr);
|
|
11
9
|
} else if (selector.substr(0, 1) === '[') {
|
|
12
|
-
|
|
13
|
-
actual = attrs.getAttribute(el, match[1]);
|
|
14
|
-
|
|
10
|
+
const match = /\[([a-z]+)="(.*)"\]/.exec(selector);
|
|
11
|
+
const actual = attrs.getAttribute(el, match[1]);
|
|
12
|
+
const rawValue = match[2];
|
|
15
13
|
return actual.toString() === rawValue;
|
|
16
14
|
} else {
|
|
17
15
|
return attrs.hasRole(el, selector.split(','));
|
|
18
16
|
}
|
|
19
17
|
};
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
return function(root,
|
|
23
|
-
|
|
19
|
+
const _querySelector = function(all) {
|
|
20
|
+
return function(root, selector) {
|
|
21
|
+
const results = [];
|
|
24
22
|
try {
|
|
25
23
|
atree.walk(root, node => {
|
|
26
24
|
if (node.nodeType === node.ELEMENT_NODE) {
|
|
27
25
|
// FIXME: skip hidden elements
|
|
28
|
-
if (matches(node,
|
|
26
|
+
if (matches(node, selector)) {
|
|
29
27
|
results.push(node);
|
|
30
28
|
if (!all) {
|
|
31
29
|
throw 'StopIteration';
|
|
@@ -42,7 +40,7 @@ var _querySelector = function(all) {
|
|
|
42
40
|
};
|
|
43
41
|
};
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
const closest = function(el, selector) {
|
|
46
44
|
return atree.searchUp(el, candidate => {
|
|
47
45
|
if (candidate.nodeType === candidate.ELEMENT_NODE) {
|
|
48
46
|
return matches(candidate, selector);
|