aria-api 0.9.1 → 0.9.2
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/README.md +1 -1
- package/dist/aria.js +105 -84
- package/lib/atree.js +19 -19
- package/lib/attrs.js +55 -28
- package/lib/constants.js +12 -18
- package/lib/name.js +15 -15
- package/lib/query.js +6 -6
- package/package.json +3 -3
package/README.md
CHANGED
package/dist/aria.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
'current': 'token',
|
|
22
22
|
'describedby': 'id-list',
|
|
23
23
|
'description': 'string',
|
|
24
|
-
'details': 'id',
|
|
24
|
+
'details': 'id-list',
|
|
25
25
|
'disabled': 'bool',
|
|
26
26
|
'dropeffect': 'token-list',
|
|
27
27
|
'errormessage': 'id',
|
|
@@ -75,12 +75,13 @@
|
|
|
75
75
|
'multiselectable': 'multiple',
|
|
76
76
|
'rowspan': 'rowSpan',
|
|
77
77
|
'selected': 'selected',
|
|
78
|
+
'valuenow': 'value',
|
|
78
79
|
};
|
|
79
80
|
|
|
80
81
|
// https://www.w3.org/TR/html/dom.html#sectioning-content-2
|
|
81
82
|
const scoped = ['article *', 'aside *', 'nav *', 'section *'].join(',');
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
function svgSelectors(selector) {
|
|
84
85
|
return [
|
|
85
86
|
// `${selector}:has(> title:not(:empty))`,
|
|
86
87
|
// `${selector}:has(> desc:not(:empty))`,
|
|
@@ -91,7 +92,7 @@
|
|
|
91
92
|
`${selector}[tabindex]`,
|
|
92
93
|
`${selector}[role]`,
|
|
93
94
|
];
|
|
94
|
-
}
|
|
95
|
+
}
|
|
95
96
|
|
|
96
97
|
// https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings
|
|
97
98
|
// https://www.w3.org/TR/wai-aria/roles
|
|
@@ -122,7 +123,7 @@
|
|
|
122
123
|
'input[type="image"]',
|
|
123
124
|
'input[type="reset"]',
|
|
124
125
|
'input[type="submit"]',
|
|
125
|
-
'summary',
|
|
126
|
+
'details > summary:first-of-type',
|
|
126
127
|
],
|
|
127
128
|
nameFromContents: true,
|
|
128
129
|
},
|
|
@@ -174,12 +175,7 @@
|
|
|
174
175
|
nameFromContents: true,
|
|
175
176
|
},
|
|
176
177
|
complementary: {
|
|
177
|
-
selectors: [
|
|
178
|
-
`aside:not(${scoped})`,
|
|
179
|
-
'aside[aria-label]',
|
|
180
|
-
'aside[aria-labelledby]',
|
|
181
|
-
'aside[title]',
|
|
182
|
-
],
|
|
178
|
+
selectors: ['aside'],
|
|
183
179
|
},
|
|
184
180
|
composite: {
|
|
185
181
|
abstract: true,
|
|
@@ -252,7 +248,6 @@
|
|
|
252
248
|
'doc-tip': {},
|
|
253
249
|
'doc-toc': {},
|
|
254
250
|
document: {
|
|
255
|
-
selectors: ['html'],
|
|
256
251
|
childRoles: ['article', 'graphics-document'],
|
|
257
252
|
},
|
|
258
253
|
emphasis: {
|
|
@@ -264,13 +259,12 @@
|
|
|
264
259
|
childRoles: ['doc-example'],
|
|
265
260
|
},
|
|
266
261
|
form: {
|
|
267
|
-
selectors: ['form
|
|
262
|
+
selectors: ['form'],
|
|
268
263
|
},
|
|
269
264
|
generic: {
|
|
270
265
|
selectors: [
|
|
271
266
|
'a:not([*|href])',
|
|
272
267
|
'area:not([*|href])',
|
|
273
|
-
`aside:not(${scoped}):not([aria-label]):not([aria-labelledby]):not([title])`,
|
|
274
268
|
'b',
|
|
275
269
|
'bdi',
|
|
276
270
|
'bdo',
|
|
@@ -279,12 +273,12 @@
|
|
|
279
273
|
'div',
|
|
280
274
|
// footer scoped
|
|
281
275
|
// header scoped
|
|
276
|
+
'html',
|
|
282
277
|
'i',
|
|
283
278
|
'li:not(ul > li):not(ol > li)',
|
|
284
279
|
'pre',
|
|
285
280
|
'q',
|
|
286
281
|
'samp',
|
|
287
|
-
'section:not([aria-label]):not([aria-labelledby]):not([title])',
|
|
288
282
|
'small',
|
|
289
283
|
'span',
|
|
290
284
|
'u',
|
|
@@ -506,7 +500,7 @@
|
|
|
506
500
|
childRoles: ['meter', 'progressbar', 'scrollbar', 'slider', 'spinbutton'],
|
|
507
501
|
},
|
|
508
502
|
region: {
|
|
509
|
-
selectors: ['section
|
|
503
|
+
selectors: ['section'],
|
|
510
504
|
},
|
|
511
505
|
roletype: {
|
|
512
506
|
abstract: true,
|
|
@@ -737,7 +731,7 @@
|
|
|
737
731
|
},
|
|
738
732
|
};
|
|
739
733
|
|
|
740
|
-
|
|
734
|
+
function getSubRoles(role) {
|
|
741
735
|
const children = (roles[role]).childRoles || [];
|
|
742
736
|
const descendents = children.map(getSubRoles);
|
|
743
737
|
|
|
@@ -752,7 +746,7 @@
|
|
|
752
746
|
});
|
|
753
747
|
|
|
754
748
|
return result;
|
|
755
|
-
}
|
|
749
|
+
}
|
|
756
750
|
|
|
757
751
|
const attrsWithDefaults = [];
|
|
758
752
|
|
|
@@ -783,59 +777,86 @@
|
|
|
783
777
|
'summary': 'Details',
|
|
784
778
|
};
|
|
785
779
|
|
|
786
|
-
|
|
780
|
+
function unique(arr) {
|
|
787
781
|
return arr.filter((a, i) => arr.indexOf(a) === i);
|
|
788
|
-
}
|
|
782
|
+
}
|
|
789
783
|
|
|
790
|
-
|
|
784
|
+
function flatten(arr) {
|
|
791
785
|
return [].concat.apply([], arr);
|
|
792
|
-
}
|
|
786
|
+
}
|
|
793
787
|
|
|
794
|
-
|
|
788
|
+
function normalizeRoles(roles$1, includeAbstract) {
|
|
795
789
|
return unique(roles$1
|
|
796
790
|
.map(r => aliases[r] || r)
|
|
797
791
|
.filter(r => roles[r])
|
|
798
792
|
.filter(r => includeAbstract || !roles[r].abstract)
|
|
799
793
|
);
|
|
800
|
-
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function hasName(el) {
|
|
797
|
+
// FIXME: we should do full getName() here. However, that would be
|
|
798
|
+
// complex and slow. In practice, this function is only called for
|
|
799
|
+
// roles that do not allow nameFromContent. Still, we do not verify
|
|
800
|
+
// that labelledby results in a non-empty name.
|
|
801
|
+
return (
|
|
802
|
+
(el.getAttribute('aria-label') || '').trim().length > 0
|
|
803
|
+
|| (el.getAttribute('title') || '').trim().length > 0
|
|
804
|
+
|| (el.getAttribute('aria-labelledby') || '').split(/\s+/).some(id => {
|
|
805
|
+
return el.getRootNode().getElementById(id);
|
|
806
|
+
})
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function checkExtraConditions(el, role) {
|
|
811
|
+
if (role === 'region' || role === 'form') {
|
|
812
|
+
return hasName(el);
|
|
813
|
+
} else if (role === 'complementary') {
|
|
814
|
+
return el.matches('[role~="complementary"]') || el.matches(`:not(${scoped})`) || hasName(el);
|
|
815
|
+
} else {
|
|
816
|
+
return true;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
801
819
|
|
|
802
820
|
// candidates can be passed for performance optimization
|
|
803
|
-
|
|
804
|
-
// TODO: filter out any invalid roles (e.g.
|
|
821
|
+
function getRoleRaw(el, candidates) {
|
|
822
|
+
// TODO: filter out any invalid roles (e.g. context required)
|
|
805
823
|
const roles$1 = normalizeRoles(
|
|
806
824
|
(el.getAttribute('role') || '').toLowerCase().split(/\s+/)
|
|
807
825
|
);
|
|
808
826
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
if (!candidates || candidates.includes(role)) {
|
|
814
|
-
return role;
|
|
827
|
+
for (const role of roles$1) {
|
|
828
|
+
if (checkExtraConditions(el, role)) {
|
|
829
|
+
if (candidates && !candidates.includes(role)) {
|
|
830
|
+
return null;
|
|
815
831
|
}
|
|
832
|
+
return role;
|
|
816
833
|
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
834
|
+
}
|
|
835
|
+
for (const role of (candidates || Object.keys(roles))) {
|
|
836
|
+
const r = roles[role];
|
|
837
|
+
if (
|
|
838
|
+
!r.abstract
|
|
839
|
+
&& r.selectors
|
|
840
|
+
&& el.matches(r.selectors.join(','))
|
|
841
|
+
&& checkExtraConditions(el, role)
|
|
842
|
+
) {
|
|
843
|
+
return role;
|
|
823
844
|
}
|
|
824
845
|
}
|
|
825
|
-
}
|
|
846
|
+
}
|
|
826
847
|
|
|
827
|
-
|
|
848
|
+
function getRole(el) {
|
|
828
849
|
return getRoleRaw(el);
|
|
829
|
-
}
|
|
850
|
+
}
|
|
830
851
|
|
|
831
|
-
|
|
852
|
+
function hasRole(el, roles$1) {
|
|
832
853
|
const subRoles = normalizeRoles(roles$1, true).map(role => {
|
|
833
854
|
return roles[role].subRoles || [role];
|
|
834
855
|
});
|
|
835
856
|
return !!getRoleRaw(el, unique(flatten(subRoles)));
|
|
836
|
-
}
|
|
857
|
+
}
|
|
837
858
|
|
|
838
|
-
|
|
859
|
+
function getAttribute(el, key) {
|
|
839
860
|
if (attributeStrongMapping.hasOwnProperty(key)) {
|
|
840
861
|
const value = el[attributeStrongMapping[key]];
|
|
841
862
|
if (value) {
|
|
@@ -899,7 +920,7 @@
|
|
|
899
920
|
return el[attributeWeakMapping[key]];
|
|
900
921
|
}
|
|
901
922
|
|
|
902
|
-
if (key
|
|
923
|
+
if (attrsWithDefaults.includes(key)) {
|
|
903
924
|
const role = getRole(el);
|
|
904
925
|
const defaults = roles[role].defaults;
|
|
905
926
|
if (defaults && defaults.hasOwnProperty(key)) {
|
|
@@ -910,20 +931,20 @@
|
|
|
910
931
|
if (type === 'bool' || type === 'tristate') {
|
|
911
932
|
return false;
|
|
912
933
|
}
|
|
913
|
-
}
|
|
934
|
+
}
|
|
914
935
|
|
|
915
|
-
|
|
936
|
+
function _getOwner(node) {
|
|
916
937
|
if (node.nodeType === node.ELEMENT_NODE && node.id) {
|
|
917
938
|
const selector = `[aria-owns~="${CSS.escape(node.id)}"]`;
|
|
918
939
|
return node.getRootNode().querySelector(selector);
|
|
919
940
|
}
|
|
920
|
-
}
|
|
941
|
+
}
|
|
921
942
|
|
|
922
|
-
|
|
943
|
+
function _getParentNode(node) {
|
|
923
944
|
return _getOwner(node) || node.parentNode || node.host;
|
|
924
|
-
}
|
|
945
|
+
}
|
|
925
946
|
|
|
926
|
-
|
|
947
|
+
function detectLoop(node) {
|
|
927
948
|
const seen = [node];
|
|
928
949
|
while ((node = _getParentNode(node))) {
|
|
929
950
|
if (seen.includes(node)) {
|
|
@@ -931,24 +952,24 @@
|
|
|
931
952
|
}
|
|
932
953
|
seen.push(node);
|
|
933
954
|
}
|
|
934
|
-
}
|
|
955
|
+
}
|
|
935
956
|
|
|
936
|
-
|
|
957
|
+
function getOwner(node) {
|
|
937
958
|
const owner = _getOwner(node);
|
|
938
959
|
if (owner && !detectLoop(node)) {
|
|
939
960
|
return owner;
|
|
940
961
|
}
|
|
941
|
-
}
|
|
962
|
+
}
|
|
942
963
|
|
|
943
|
-
|
|
964
|
+
function getParentNode(node) {
|
|
944
965
|
return getOwner(node) || node.parentNode || node.host;
|
|
945
|
-
}
|
|
966
|
+
}
|
|
946
967
|
|
|
947
|
-
|
|
968
|
+
function isHidden(node) {
|
|
948
969
|
return node.nodeType === node.ELEMENT_NODE && getAttribute(node, 'hidden');
|
|
949
|
-
}
|
|
970
|
+
}
|
|
950
971
|
|
|
951
|
-
|
|
972
|
+
function getChildNodes(node) {
|
|
952
973
|
const childNodes = [];
|
|
953
974
|
let rawChildNodes = [];
|
|
954
975
|
|
|
@@ -979,18 +1000,18 @@
|
|
|
979
1000
|
}
|
|
980
1001
|
|
|
981
1002
|
return childNodes;
|
|
982
|
-
}
|
|
1003
|
+
}
|
|
983
1004
|
|
|
984
|
-
|
|
1005
|
+
function walk(root, fn) {
|
|
985
1006
|
let queue = [root];
|
|
986
1007
|
while (queue.length) {
|
|
987
1008
|
const item = queue.shift();
|
|
988
1009
|
fn(item);
|
|
989
1010
|
queue = getChildNodes(item).concat(queue);
|
|
990
1011
|
}
|
|
991
|
-
}
|
|
1012
|
+
}
|
|
992
1013
|
|
|
993
|
-
|
|
1014
|
+
function searchUp(node, test) {
|
|
994
1015
|
const candidate = getParentNode(node);
|
|
995
1016
|
if (candidate) {
|
|
996
1017
|
if (test(candidate)) {
|
|
@@ -999,9 +1020,9 @@
|
|
|
999
1020
|
return searchUp(candidate, test);
|
|
1000
1021
|
}
|
|
1001
1022
|
}
|
|
1002
|
-
}
|
|
1023
|
+
}
|
|
1003
1024
|
|
|
1004
|
-
|
|
1025
|
+
function matches(el, selector) {
|
|
1005
1026
|
if (selector.substr(0, 1) === ':') {
|
|
1006
1027
|
const attr = selector.substr(1);
|
|
1007
1028
|
return getAttribute(el, attr);
|
|
@@ -1013,9 +1034,9 @@
|
|
|
1013
1034
|
} else {
|
|
1014
1035
|
return hasRole(el, selector.split(','));
|
|
1015
1036
|
}
|
|
1016
|
-
}
|
|
1037
|
+
}
|
|
1017
1038
|
|
|
1018
|
-
|
|
1039
|
+
function _querySelector(all) {
|
|
1019
1040
|
return function(root, selector) {
|
|
1020
1041
|
const results = [];
|
|
1021
1042
|
try {
|
|
@@ -1037,27 +1058,27 @@
|
|
|
1037
1058
|
}
|
|
1038
1059
|
return all ? results : results[0];
|
|
1039
1060
|
};
|
|
1040
|
-
}
|
|
1061
|
+
}
|
|
1041
1062
|
|
|
1042
|
-
|
|
1063
|
+
function closest(el, selector) {
|
|
1043
1064
|
return searchUp(el, candidate => {
|
|
1044
1065
|
if (candidate.nodeType === candidate.ELEMENT_NODE) {
|
|
1045
1066
|
return matches(candidate, selector);
|
|
1046
1067
|
}
|
|
1047
1068
|
});
|
|
1048
|
-
}
|
|
1069
|
+
}
|
|
1049
1070
|
|
|
1050
1071
|
const querySelector = _querySelector();
|
|
1051
1072
|
const querySelectorAll = _querySelector(true);
|
|
1052
1073
|
|
|
1053
|
-
|
|
1074
|
+
function addSpaces(text, el, pseudoSelector) {
|
|
1054
1075
|
// https://github.com/w3c/accname/issues/3
|
|
1055
1076
|
const styles = window.getComputedStyle(el, pseudoSelector);
|
|
1056
1077
|
const inline = styles.display === 'inline';
|
|
1057
1078
|
return inline ? text : ` ${text} `;
|
|
1058
|
-
}
|
|
1079
|
+
}
|
|
1059
1080
|
|
|
1060
|
-
|
|
1081
|
+
function getPseudoContent(el, pseudoSelector) {
|
|
1061
1082
|
const styles = window.getComputedStyle(el, pseudoSelector);
|
|
1062
1083
|
let tail = styles.getPropertyValue('content').trim();
|
|
1063
1084
|
let ret = [];
|
|
@@ -1084,9 +1105,9 @@
|
|
|
1084
1105
|
}
|
|
1085
1106
|
|
|
1086
1107
|
return addSpaces(ret.join(''), el, pseudoSelector);
|
|
1087
|
-
}
|
|
1108
|
+
}
|
|
1088
1109
|
|
|
1089
|
-
|
|
1110
|
+
function getContent(root, ongoingLabelledBy, visited) {
|
|
1090
1111
|
const children = getChildNodes(root);
|
|
1091
1112
|
|
|
1092
1113
|
let ret = '';
|
|
@@ -1117,16 +1138,16 @@
|
|
|
1117
1138
|
}
|
|
1118
1139
|
|
|
1119
1140
|
return ret;
|
|
1120
|
-
}
|
|
1141
|
+
}
|
|
1121
1142
|
|
|
1122
|
-
|
|
1143
|
+
function allowNameFromContent(el) {
|
|
1123
1144
|
const role = getRole(el);
|
|
1124
1145
|
if (role) {
|
|
1125
1146
|
return roles[role].nameFromContents;
|
|
1126
1147
|
}
|
|
1127
|
-
}
|
|
1148
|
+
}
|
|
1128
1149
|
|
|
1129
|
-
|
|
1150
|
+
function getNameRaw(el, recursive, ongoingLabelledBy, visited, directReference) {
|
|
1130
1151
|
let ret = '';
|
|
1131
1152
|
|
|
1132
1153
|
visited = visited || [];
|
|
@@ -1174,7 +1195,7 @@
|
|
|
1174
1195
|
}
|
|
1175
1196
|
|
|
1176
1197
|
// D
|
|
1177
|
-
if (!ret.trim() &&
|
|
1198
|
+
if (!ret.trim() && el.labels) {
|
|
1178
1199
|
const strings = Array.prototype.map.call(el.labels, label => {
|
|
1179
1200
|
return getNameRaw(label, true, ongoingLabelledBy, visited);
|
|
1180
1201
|
});
|
|
@@ -1241,16 +1262,16 @@
|
|
|
1241
1262
|
const before = getPseudoContent(el, ':before');
|
|
1242
1263
|
const after = getPseudoContent(el, ':after');
|
|
1243
1264
|
return addSpaces(before + ret + after, el);
|
|
1244
|
-
}
|
|
1265
|
+
}
|
|
1245
1266
|
|
|
1246
|
-
|
|
1267
|
+
function getName(el) {
|
|
1247
1268
|
return getNameRaw(el)
|
|
1248
1269
|
.replace(/[ \n\r\t\f]+/g, ' ')
|
|
1249
1270
|
.replace(/^ /, '')
|
|
1250
1271
|
.replace(/ $/, '');
|
|
1251
|
-
}
|
|
1272
|
+
}
|
|
1252
1273
|
|
|
1253
|
-
|
|
1274
|
+
function getDescription(el) {
|
|
1254
1275
|
let ret = '';
|
|
1255
1276
|
|
|
1256
1277
|
if (el.matches('[aria-describedby]')) {
|
|
@@ -1281,7 +1302,7 @@
|
|
|
1281
1302
|
}
|
|
1282
1303
|
|
|
1283
1304
|
return ret;
|
|
1284
|
-
}
|
|
1305
|
+
}
|
|
1285
1306
|
|
|
1286
1307
|
exports.closest = closest;
|
|
1287
1308
|
exports.getAttribute = getAttribute;
|
package/lib/atree.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import * as attrs from './attrs';
|
|
1
|
+
import * as attrs from './attrs.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function _getOwner(node) {
|
|
4
4
|
if (node.nodeType === node.ELEMENT_NODE && node.id) {
|
|
5
5
|
const selector = `[aria-owns~="${CSS.escape(node.id)}"]`;
|
|
6
6
|
return node.getRootNode().querySelector(selector);
|
|
7
7
|
}
|
|
8
|
-
}
|
|
8
|
+
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
function _getParentNode(node) {
|
|
11
11
|
return _getOwner(node) || node.parentNode || node.host;
|
|
12
|
-
}
|
|
12
|
+
}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
function detectLoop(node) {
|
|
15
15
|
const seen = [node];
|
|
16
16
|
while ((node = _getParentNode(node))) {
|
|
17
17
|
if (seen.includes(node)) {
|
|
@@ -19,24 +19,24 @@ const detectLoop = function(node) {
|
|
|
19
19
|
}
|
|
20
20
|
seen.push(node);
|
|
21
21
|
}
|
|
22
|
-
}
|
|
22
|
+
}
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
function getOwner(node) {
|
|
25
25
|
const owner = _getOwner(node);
|
|
26
26
|
if (owner && !detectLoop(node)) {
|
|
27
27
|
return owner;
|
|
28
28
|
}
|
|
29
|
-
}
|
|
29
|
+
}
|
|
30
30
|
|
|
31
|
-
export
|
|
31
|
+
export function getParentNode(node) {
|
|
32
32
|
return getOwner(node) || node.parentNode || node.host;
|
|
33
|
-
}
|
|
33
|
+
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
function isHidden(node) {
|
|
36
36
|
return node.nodeType === node.ELEMENT_NODE && attrs.getAttribute(node, 'hidden');
|
|
37
|
-
}
|
|
37
|
+
}
|
|
38
38
|
|
|
39
|
-
export
|
|
39
|
+
export function getChildNodes(node) {
|
|
40
40
|
const childNodes = [];
|
|
41
41
|
let rawChildNodes = [];
|
|
42
42
|
|
|
@@ -67,18 +67,18 @@ export const getChildNodes = function(node) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
return childNodes;
|
|
70
|
-
}
|
|
70
|
+
}
|
|
71
71
|
|
|
72
|
-
export
|
|
72
|
+
export function walk(root, fn) {
|
|
73
73
|
let queue = [root];
|
|
74
74
|
while (queue.length) {
|
|
75
75
|
const item = queue.shift();
|
|
76
76
|
fn(item);
|
|
77
77
|
queue = getChildNodes(item).concat(queue);
|
|
78
78
|
}
|
|
79
|
-
}
|
|
79
|
+
}
|
|
80
80
|
|
|
81
|
-
export
|
|
81
|
+
export function searchUp(node, test) {
|
|
82
82
|
const candidate = getParentNode(node);
|
|
83
83
|
if (candidate) {
|
|
84
84
|
if (test(candidate)) {
|
|
@@ -87,4 +87,4 @@ export const searchUp = function(node, test) {
|
|
|
87
87
|
return searchUp(candidate, test);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
}
|
|
90
|
+
}
|
package/lib/attrs.js
CHANGED
|
@@ -1,58 +1,85 @@
|
|
|
1
1
|
import * as constants from './constants.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function unique(arr) {
|
|
4
4
|
return arr.filter((a, i) => arr.indexOf(a) === i);
|
|
5
|
-
}
|
|
5
|
+
}
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
function flatten(arr) {
|
|
8
8
|
return [].concat.apply([], arr);
|
|
9
|
-
}
|
|
9
|
+
}
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
function normalizeRoles(roles, includeAbstract) {
|
|
12
12
|
return unique(roles
|
|
13
13
|
.map(r => constants.aliases[r] || r)
|
|
14
14
|
.filter(r => constants.roles[r])
|
|
15
15
|
.filter(r => includeAbstract || !constants.roles[r].abstract)
|
|
16
16
|
);
|
|
17
|
-
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function hasName(el) {
|
|
20
|
+
// FIXME: we should do full getName() here. However, that would be
|
|
21
|
+
// complex and slow. In practice, this function is only called for
|
|
22
|
+
// roles that do not allow nameFromContent. Still, we do not verify
|
|
23
|
+
// that labelledby results in a non-empty name.
|
|
24
|
+
return (
|
|
25
|
+
(el.getAttribute('aria-label') || '').trim().length > 0
|
|
26
|
+
|| (el.getAttribute('title') || '').trim().length > 0
|
|
27
|
+
|| (el.getAttribute('aria-labelledby') || '').split(/\s+/).some(id => {
|
|
28
|
+
return el.getRootNode().getElementById(id);
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function checkExtraConditions(el, role) {
|
|
34
|
+
if (role === 'region' || role === 'form') {
|
|
35
|
+
return hasName(el);
|
|
36
|
+
} else if (role === 'complementary') {
|
|
37
|
+
return el.matches('[role~="complementary"]') || el.matches(`:not(${constants.scoped})`) || hasName(el);
|
|
38
|
+
} else {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
18
42
|
|
|
19
43
|
// candidates can be passed for performance optimization
|
|
20
|
-
|
|
21
|
-
// TODO: filter out any invalid roles (e.g.
|
|
44
|
+
function getRoleRaw(el, candidates) {
|
|
45
|
+
// TODO: filter out any invalid roles (e.g. context required)
|
|
22
46
|
const roles = normalizeRoles(
|
|
23
47
|
(el.getAttribute('role') || '').toLowerCase().split(/\s+/)
|
|
24
48
|
);
|
|
25
49
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!candidates || candidates.includes(role)) {
|
|
31
|
-
return role;
|
|
50
|
+
for (const role of roles) {
|
|
51
|
+
if (checkExtraConditions(el, role)) {
|
|
52
|
+
if (candidates && !candidates.includes(role)) {
|
|
53
|
+
return null;
|
|
32
54
|
}
|
|
55
|
+
return role;
|
|
33
56
|
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
57
|
+
}
|
|
58
|
+
for (const role of (candidates || Object.keys(constants.roles))) {
|
|
59
|
+
const r = constants.roles[role];
|
|
60
|
+
if (
|
|
61
|
+
!r.abstract
|
|
62
|
+
&& r.selectors
|
|
63
|
+
&& el.matches(r.selectors.join(','))
|
|
64
|
+
&& checkExtraConditions(el, role)
|
|
65
|
+
) {
|
|
66
|
+
return role;
|
|
40
67
|
}
|
|
41
68
|
}
|
|
42
|
-
}
|
|
69
|
+
}
|
|
43
70
|
|
|
44
|
-
export
|
|
71
|
+
export function getRole(el) {
|
|
45
72
|
return getRoleRaw(el);
|
|
46
|
-
}
|
|
73
|
+
}
|
|
47
74
|
|
|
48
|
-
export
|
|
75
|
+
export function hasRole(el, roles) {
|
|
49
76
|
const subRoles = normalizeRoles(roles, true).map(role => {
|
|
50
77
|
return constants.roles[role].subRoles || [role];
|
|
51
78
|
});
|
|
52
79
|
return !!getRoleRaw(el, unique(flatten(subRoles)));
|
|
53
|
-
}
|
|
80
|
+
}
|
|
54
81
|
|
|
55
|
-
export
|
|
82
|
+
export function getAttribute(el, key) {
|
|
56
83
|
if (constants.attributeStrongMapping.hasOwnProperty(key)) {
|
|
57
84
|
const value = el[constants.attributeStrongMapping[key]];
|
|
58
85
|
if (value) {
|
|
@@ -116,7 +143,7 @@ export const getAttribute = function(el, key) {
|
|
|
116
143
|
return el[constants.attributeWeakMapping[key]];
|
|
117
144
|
}
|
|
118
145
|
|
|
119
|
-
if (
|
|
146
|
+
if (constants.attrsWithDefaults.includes(key)) {
|
|
120
147
|
const role = getRole(el);
|
|
121
148
|
const defaults = constants.roles[role].defaults;
|
|
122
149
|
if (defaults && defaults.hasOwnProperty(key)) {
|
|
@@ -127,4 +154,4 @@ export const getAttribute = function(el, key) {
|
|
|
127
154
|
if (type === 'bool' || type === 'tristate') {
|
|
128
155
|
return false;
|
|
129
156
|
}
|
|
130
|
-
}
|
|
157
|
+
}
|
package/lib/constants.js
CHANGED
|
@@ -15,7 +15,7 @@ export const attributes = {
|
|
|
15
15
|
'current': 'token',
|
|
16
16
|
'describedby': 'id-list',
|
|
17
17
|
'description': 'string',
|
|
18
|
-
'details': 'id',
|
|
18
|
+
'details': 'id-list',
|
|
19
19
|
'disabled': 'bool',
|
|
20
20
|
'dropeffect': 'token-list',
|
|
21
21
|
'errormessage': 'id',
|
|
@@ -69,12 +69,13 @@ export const attributeWeakMapping = {
|
|
|
69
69
|
'multiselectable': 'multiple',
|
|
70
70
|
'rowspan': 'rowSpan',
|
|
71
71
|
'selected': 'selected',
|
|
72
|
+
'valuenow': 'value',
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
// https://www.w3.org/TR/html/dom.html#sectioning-content-2
|
|
75
|
-
const scoped = ['article *', 'aside *', 'nav *', 'section *'].join(',');
|
|
76
|
+
export const scoped = ['article *', 'aside *', 'nav *', 'section *'].join(',');
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
function svgSelectors(selector) {
|
|
78
79
|
return [
|
|
79
80
|
// `${selector}:has(> title:not(:empty))`,
|
|
80
81
|
// `${selector}:has(> desc:not(:empty))`,
|
|
@@ -85,7 +86,7 @@ const svgSelectors = function(selector) {
|
|
|
85
86
|
`${selector}[tabindex]`,
|
|
86
87
|
`${selector}[role]`,
|
|
87
88
|
];
|
|
88
|
-
}
|
|
89
|
+
}
|
|
89
90
|
|
|
90
91
|
// https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings
|
|
91
92
|
// https://www.w3.org/TR/wai-aria/roles
|
|
@@ -116,7 +117,7 @@ export const roles = {
|
|
|
116
117
|
'input[type="image"]',
|
|
117
118
|
'input[type="reset"]',
|
|
118
119
|
'input[type="submit"]',
|
|
119
|
-
'summary',
|
|
120
|
+
'details > summary:first-of-type',
|
|
120
121
|
],
|
|
121
122
|
nameFromContents: true,
|
|
122
123
|
},
|
|
@@ -168,12 +169,7 @@ export const roles = {
|
|
|
168
169
|
nameFromContents: true,
|
|
169
170
|
},
|
|
170
171
|
complementary: {
|
|
171
|
-
selectors: [
|
|
172
|
-
`aside:not(${scoped})`,
|
|
173
|
-
'aside[aria-label]',
|
|
174
|
-
'aside[aria-labelledby]',
|
|
175
|
-
'aside[title]',
|
|
176
|
-
],
|
|
172
|
+
selectors: ['aside'],
|
|
177
173
|
},
|
|
178
174
|
composite: {
|
|
179
175
|
abstract: true,
|
|
@@ -246,7 +242,6 @@ export const roles = {
|
|
|
246
242
|
'doc-tip': {},
|
|
247
243
|
'doc-toc': {},
|
|
248
244
|
document: {
|
|
249
|
-
selectors: ['html'],
|
|
250
245
|
childRoles: ['article', 'graphics-document'],
|
|
251
246
|
},
|
|
252
247
|
emphasis: {
|
|
@@ -258,13 +253,12 @@ export const roles = {
|
|
|
258
253
|
childRoles: ['doc-example'],
|
|
259
254
|
},
|
|
260
255
|
form: {
|
|
261
|
-
selectors: ['form
|
|
256
|
+
selectors: ['form'],
|
|
262
257
|
},
|
|
263
258
|
generic: {
|
|
264
259
|
selectors: [
|
|
265
260
|
'a:not([*|href])',
|
|
266
261
|
'area:not([*|href])',
|
|
267
|
-
`aside:not(${scoped}):not([aria-label]):not([aria-labelledby]):not([title])`,
|
|
268
262
|
'b',
|
|
269
263
|
'bdi',
|
|
270
264
|
'bdo',
|
|
@@ -273,12 +267,12 @@ export const roles = {
|
|
|
273
267
|
'div',
|
|
274
268
|
// footer scoped
|
|
275
269
|
// header scoped
|
|
270
|
+
'html',
|
|
276
271
|
'i',
|
|
277
272
|
'li:not(ul > li):not(ol > li)',
|
|
278
273
|
'pre',
|
|
279
274
|
'q',
|
|
280
275
|
'samp',
|
|
281
|
-
'section:not([aria-label]):not([aria-labelledby]):not([title])',
|
|
282
276
|
'small',
|
|
283
277
|
'span',
|
|
284
278
|
'u',
|
|
@@ -500,7 +494,7 @@ export const roles = {
|
|
|
500
494
|
childRoles: ['meter', 'progressbar', 'scrollbar', 'slider', 'spinbutton'],
|
|
501
495
|
},
|
|
502
496
|
region: {
|
|
503
|
-
selectors: ['section
|
|
497
|
+
selectors: ['section'],
|
|
504
498
|
},
|
|
505
499
|
roletype: {
|
|
506
500
|
abstract: true,
|
|
@@ -731,7 +725,7 @@ export const roles = {
|
|
|
731
725
|
},
|
|
732
726
|
};
|
|
733
727
|
|
|
734
|
-
|
|
728
|
+
function getSubRoles(role) {
|
|
735
729
|
const children = (roles[role]).childRoles || [];
|
|
736
730
|
const descendents = children.map(getSubRoles);
|
|
737
731
|
|
|
@@ -746,7 +740,7 @@ const getSubRoles = function(role) {
|
|
|
746
740
|
});
|
|
747
741
|
|
|
748
742
|
return result;
|
|
749
|
-
}
|
|
743
|
+
}
|
|
750
744
|
|
|
751
745
|
export const attrsWithDefaults = [];
|
|
752
746
|
|
package/lib/name.js
CHANGED
|
@@ -3,14 +3,14 @@ import * as atree from './atree.js';
|
|
|
3
3
|
import * as attrs from './attrs.js';
|
|
4
4
|
import * as query from './query.js';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
function addSpaces(text, el, pseudoSelector) {
|
|
7
7
|
// https://github.com/w3c/accname/issues/3
|
|
8
8
|
const styles = window.getComputedStyle(el, pseudoSelector);
|
|
9
9
|
const inline = styles.display === 'inline';
|
|
10
10
|
return inline ? text : ` ${text} `;
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
function getPseudoContent(el, pseudoSelector) {
|
|
14
14
|
const styles = window.getComputedStyle(el, pseudoSelector);
|
|
15
15
|
let tail = styles.getPropertyValue('content').trim();
|
|
16
16
|
let ret = [];
|
|
@@ -37,9 +37,9 @@ const getPseudoContent = function(el, pseudoSelector) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
return addSpaces(ret.join(''), el, pseudoSelector);
|
|
40
|
-
}
|
|
40
|
+
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
function getContent(root, ongoingLabelledBy, visited) {
|
|
43
43
|
const children = atree.getChildNodes(root);
|
|
44
44
|
|
|
45
45
|
let ret = '';
|
|
@@ -70,16 +70,16 @@ const getContent = function(root, ongoingLabelledBy, visited) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
return ret;
|
|
73
|
-
}
|
|
73
|
+
}
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
function allowNameFromContent(el) {
|
|
76
76
|
const role = attrs.getRole(el);
|
|
77
77
|
if (role) {
|
|
78
78
|
return constants.roles[role].nameFromContents;
|
|
79
79
|
}
|
|
80
|
-
}
|
|
80
|
+
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
function getNameRaw(el, recursive, ongoingLabelledBy, visited, directReference) {
|
|
83
83
|
let ret = '';
|
|
84
84
|
|
|
85
85
|
visited = visited || [];
|
|
@@ -127,7 +127,7 @@ const getNameRaw = function(el, recursive, ongoingLabelledBy, visited, directRef
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// D
|
|
130
|
-
if (!ret.trim() &&
|
|
130
|
+
if (!ret.trim() && el.labels) {
|
|
131
131
|
const strings = Array.prototype.map.call(el.labels, label => {
|
|
132
132
|
return getNameRaw(label, true, ongoingLabelledBy, visited);
|
|
133
133
|
});
|
|
@@ -194,16 +194,16 @@ const getNameRaw = function(el, recursive, ongoingLabelledBy, visited, directRef
|
|
|
194
194
|
const before = getPseudoContent(el, ':before');
|
|
195
195
|
const after = getPseudoContent(el, ':after');
|
|
196
196
|
return addSpaces(before + ret + after, el);
|
|
197
|
-
}
|
|
197
|
+
}
|
|
198
198
|
|
|
199
|
-
export
|
|
199
|
+
export function getName(el) {
|
|
200
200
|
return getNameRaw(el)
|
|
201
201
|
.replace(/[ \n\r\t\f]+/g, ' ')
|
|
202
202
|
.replace(/^ /, '')
|
|
203
203
|
.replace(/ $/, '');
|
|
204
|
-
}
|
|
204
|
+
}
|
|
205
205
|
|
|
206
|
-
export
|
|
206
|
+
export function getDescription(el) {
|
|
207
207
|
let ret = '';
|
|
208
208
|
|
|
209
209
|
if (el.matches('[aria-describedby]')) {
|
|
@@ -234,4 +234,4 @@ export const getDescription = function(el) {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
return ret;
|
|
237
|
-
}
|
|
237
|
+
}
|
package/lib/query.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as attrs from './attrs.js';
|
|
2
2
|
import * as atree from './atree.js';
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export function matches(el, selector) {
|
|
5
5
|
if (selector.substr(0, 1) === ':') {
|
|
6
6
|
const attr = selector.substr(1);
|
|
7
7
|
return attrs.getAttribute(el, attr);
|
|
@@ -13,9 +13,9 @@ export const matches = function(el, selector) {
|
|
|
13
13
|
} else {
|
|
14
14
|
return attrs.hasRole(el, selector.split(','));
|
|
15
15
|
}
|
|
16
|
-
}
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
function _querySelector(all) {
|
|
19
19
|
return function(root, selector) {
|
|
20
20
|
const results = [];
|
|
21
21
|
try {
|
|
@@ -37,15 +37,15 @@ const _querySelector = function(all) {
|
|
|
37
37
|
}
|
|
38
38
|
return all ? results : results[0];
|
|
39
39
|
};
|
|
40
|
-
}
|
|
40
|
+
}
|
|
41
41
|
|
|
42
|
-
export
|
|
42
|
+
export function closest(el, selector) {
|
|
43
43
|
return atree.searchUp(el, candidate => {
|
|
44
44
|
if (candidate.nodeType === candidate.ELEMENT_NODE) {
|
|
45
45
|
return matches(candidate, selector);
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
|
-
}
|
|
48
|
+
}
|
|
49
49
|
|
|
50
50
|
export const querySelector = _querySelector();
|
|
51
51
|
export const querySelectorAll = _querySelector(true);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aria-api",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Access ARIA information from JavaScript",
|
|
5
5
|
"main": "dist/aria.js",
|
|
6
6
|
"module": "index.js",
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"expect": "^1.20.2",
|
|
16
|
-
"mocha": "^
|
|
16
|
+
"mocha": "^12.0.3",
|
|
17
17
|
"mocha-headless-chrome": "^5.1.0",
|
|
18
|
-
"rollup": "^4.
|
|
18
|
+
"rollup": "^4.68.2"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"index.js",
|