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/lib/name.js CHANGED
@@ -1,42 +1,56 @@
1
- var constants = require('./constants.js');
2
- var atree = require('./atree.js');
3
- var query = require('./query.js');
4
-
5
- var getPseudoContent = function(node, selector) {
6
- var styles = window.getComputedStyle(node, selector);
7
- var ret = styles.getPropertyValue('content');
8
- var inline = styles.display.substr(0, 6) === 'inline';
9
- if (!ret) {
10
- return '';
11
- }
12
- if (ret.substr(0, 1) !== '"') {
13
- return '';
14
- } else {
15
- if (inline) {
16
- return ret.slice(1, -1);
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
- return ' ' + ret.slice(1, -1) + ' ';
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
- var getContent = function(root, visited) {
24
- var children = atree.getChildNodes(root);
41
+ const getContent = function(root, ongoingLabelledBy, visited) {
42
+ const children = atree.getChildNodes(root);
25
43
 
26
- var ret = '';
27
- for (var i = 0; i < children.length; i++) {
28
- var node = children[i];
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 += ' ' + getName(node, true, visited) + ' ';
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
- var allowNameFromContent = function(el) {
48
- var role = query.getRole(el);
49
- return (constants.roles[role] || {}).nameFromContents;
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
- var getName = function(el, recursive, visited, directReference) {
58
- var ret = '';
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 (!recursive && el.matches('[aria-labelledby]')) {
74
- var ids = el.getAttribute('aria-labelledby').split(/\s+/);
75
- var strings = ids.map(id => {
76
- var label = document.getElementById(id);
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
- var strings = Array.prototype.map.call(el.labels, label => {
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 (var selector in constants.nameFromDescendant) {
129
+ for (const selector in constants.nameFromDescendant) {
106
130
  if (el.matches(selector)) {
107
- var descendant = el.querySelector(constants.nameFromDescendant[selector]);
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
- var svgTitle = el.querySelector('title');
139
+ const svgTitle = el.querySelector('title');
116
140
  if (svgTitle && svgTitle.parentElement === el) {
117
141
  ret = svgTitle.textContent;
118
142
  }
119
143
  }
120
-
121
- // E
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 (var selector in constants.nameDefaults) {
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
- // FIXME: presentation not mentioned in the spec
158
- if (!ret.trim() && (directReference || !query.matches(el, 'presentation'))) {
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
- var before = getPseudoContent(el, ':before');
169
- var after = getPseudoContent(el, ':after');
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
- var getNameTrimmed = function(el) {
174
- return getName(el).replace(/\s+/g, ' ').trim();
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
- var getDescription = function(el) {
178
- var ret = '';
192
+ const getDescription = function(el) {
193
+ let ret = '';
179
194
 
180
195
  if (el.matches('[aria-describedby]')) {
181
- var ids = el.getAttribute('aria-describedby').split(/\s+/);
182
- var strings = ids.map(id => {
183
- var label = document.getElementById(id);
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
- var svgDesc = el.querySelector('desc');
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
- var attrs = require('./attrs.js');
2
- var atree = require('./atree.js');
1
+ const attrs = require('./attrs.js');
2
+ const atree = require('./atree.js');
3
3
 
4
4
 
5
- var matches = function(el, selector) {
6
- var actual;
7
-
5
+ const matches = function(el, selector) {
8
6
  if (selector.substr(0, 1) === ':') {
9
- var attr = selector.substr(1);
7
+ const attr = selector.substr(1);
10
8
  return attrs.getAttribute(el, attr);
11
9
  } else if (selector.substr(0, 1) === '[') {
12
- var match = /\[([a-z]+)="(.*)"\]/.exec(selector);
13
- actual = attrs.getAttribute(el, match[1]);
14
- var rawValue = match[2];
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
- var _querySelector = function(all) {
22
- return function(root, role) {
23
- var results = [];
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, role)) {
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
- var closest = function(el, selector) {
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aria-api",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Access ARIA information from JavaScript",
5
5
  "main": "index.js",
6
6
  "keywords": [