- commit
- 93ad11dbcad85a30f7c6eec93db08e33d6060397
- parent
- df71d4a90daaf0c9e9594788275fadf8440260cd
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2024-06-22 20:58
bump version to 0.7.0
Diffstat
| M | CHANGES.md | 19 | +++++++++++++++++++ |
| M | dist/aria.js | 164 | +++++++++++++++++++++++++++++++++++++++++++------------------ |
| M | package.json | 2 | +- |
3 files changed, 137 insertions, 48 deletions
diff --git a/CHANGES.md b/CHANGES.md
@@ -1,3 +1,21 @@ -1 1 0.7.0 (2024-06-22) -1 2 ------------------ -1 3 -1 4 - add support for SVG-AAM -1 5 - add support for WAI-ARIA 1.3 -1 6 - fix: when deciding if an element is hidden, treat `visibility: collapsed` the -1 7 same as `visibility: hidden` -1 8 - getName -1 9 - do not trim non-breaking spaces -1 10 - add support for `open-quote`, `close-quote`, `attr(…)` and fallback values -1 11 in pseudo content -1 12 - treat `placeholder` as tooltip -1 13 - only add whitespace for online elements, not for inline-block -1 14 - fallback to tooltip for any element -1 15 - only ignore `aria-labelledby` when recursion was caused by -1 16 `aria-labelledby` or `aria-describedby` -1 17 -1 18 1 19 0.6.0 (2024-02-04) 2 20 ------------------ 3 21 @@ -11,6 +29,7 @@ 11 29 - getName 12 30 - increase priority of embedded input element 13 31 -1 32 14 33 0.5.0 (2023-06-07) 15 34 ------------------ 16 35
diff --git a/dist/aria.js b/dist/aria.js
@@ -188,7 +188,7 @@ const getAttribute = function(el, key) {
188 188 return true;
189 189 }
190 190 const style = window.getComputedStyle(el);
191 -1 if (style.display === 'none' || style.visibility === 'hidden') {
-1 191 if (style.display === 'none' || style.visibility === 'hidden' || style.visibility === 'collapse') {
192 192 return true;
193 193 }
194 194 }
@@ -257,14 +257,18 @@ exports.attributes = {
257 257 'activedescendant': 'id',
258 258 'atomic': 'bool',
259 259 'autocomplete': 'token',
-1 260 'braillelabel': 'string',
-1 261 'brailleroledescription': 'string',
260 262 'busy': 'bool',
261 263 'checked': 'tristate',
262 264 'colcount': 'int',
263 265 'colindex': 'int',
-1 266 'colindextext': 'string',
264 267 'colspan': 'int',
265 268 'controls': 'id-list',
266 269 'current': 'token',
267 270 'describedby': 'id-list',
-1 271 'description': 'string',
268 272 'details': 'id',
269 273 'disabled': 'bool',
270 274 'dropeffect': 'token-list',
@@ -294,6 +298,7 @@ exports.attributes = {
294 298 'roledescription': 'string',
295 299 'rowcount': 'int',
296 300 'rowindex': 'int',
-1 301 'rowindextext': 'string',
297 302 'rowspan': 'int',
298 303 'selected': 'bool-undefined',
299 304 'setsize': 'int',
@@ -323,6 +328,19 @@ exports.attributeWeakMapping = {
323 328 // https://www.w3.org/TR/html/dom.html#sectioning-content-2
324 329 const scoped = ['article *', 'aside *', 'nav *', 'section *'].join(',');
325 330
-1 331 const svgSelectors = function(selector) {
-1 332 return [
-1 333 // `${selector}:has(> title:not(:empty))`,
-1 334 // `${selector}:has(> desc:not(:empty))`,
-1 335 `${selector}[aria-label]`,
-1 336 `${selector}[aria-roledescription]`,
-1 337 `${selector}[aria-labelledby]`,
-1 338 `${selector}[aria-describedby]`,
-1 339 `${selector}[tabindex]`,
-1 340 `${selector}[role]`,
-1 341 ];
-1 342 };
-1 343
326 344 // https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings
327 345 // https://www.w3.org/TR/wai-aria/roles
328 346 exports.roles = {
@@ -337,6 +355,7 @@ exports.roles = {
337 355 application: {},
338 356 article: {
339 357 selectors: ['article'],
-1 358 childRoles: ['comment'],
340 359 },
341 360 banner: {
342 361 selectors: [`header:not(main *, ${scoped})`],
@@ -399,6 +418,9 @@ exports.roles = {
399 418 abstract: true,
400 419 childRoles: ['button', 'link', 'menuitem'],
401 420 },
-1 421 comment: {
-1 422 nameFromContents: true,
-1 423 },
402 424 complementary: {
403 425 selectors: [
404 426 `aside:not(${scoped})`,
@@ -494,8 +516,8 @@ exports.roles = {
494 516 },
495 517 generic: {
496 518 selectors: [
497 -1 'a:not([href])',
498 -1 'area:not([href])',
-1 519 'a:not([*|href])',
-1 520 'area:not([*|href])',
499 521 `aside:not(${scoped}):not([aria-label]):not([aria-labelledby]):not([title])`,
500 522 'b',
501 523 'bdi',
@@ -519,8 +541,23 @@ exports.roles = {
519 541 'graphics-document': {
520 542 selectors: ['svg'],
521 543 },
522 -1 'graphics-object': {},
523 -1 'graphics-symbol': {},
-1 544 'graphics-object': {
-1 545 selectors: [
-1 546 ...svgSelectors('symbol'),
-1 547 ...svgSelectors('use'),
-1 548 ],
-1 549 },
-1 550 'graphics-symbol': {
-1 551 selectors: [
-1 552 ...svgSelectors('circle'),
-1 553 ...svgSelectors('ellipse'),
-1 554 ...svgSelectors('line'),
-1 555 ...svgSelectors('path'),
-1 556 ...svgSelectors('polygon'),
-1 557 ...svgSelectors('polyline'),
-1 558 ...svgSelectors('rect'),
-1 559 ],
-1 560 },
524 561 grid: {
525 562 childRoles: ['treegrid'],
526 563 },
@@ -535,7 +572,11 @@ exports.roles = {
535 572 'fieldset',
536 573 'hgroup',
537 574 'optgroup',
-1 575 ...svgSelectors('foreignObject'),
-1 576 ...svgSelectors('g'),
538 577 'text',
-1 578 ...svgSelectors('textPath'),
-1 579 ...svgSelectors('tspan'),
539 580 ],
540 581 childRoles: ['row', 'select', 'toolbar', 'graphics-object'],
541 582 },
@@ -546,8 +587,13 @@ exports.roles = {
546 587 'level': 2,
547 588 },
548 589 },
549 -1 img: {
550 -1 selectors: ['img:not([alt=""])', 'graphics-symbol'],
-1 590 image: {
-1 591 selectors: [
-1 592 'img:not([alt=""])',
-1 593 'graphics-symbol',
-1 594 ...svgSelectors('image'),
-1 595 ...svgSelectors('mesh'),
-1 596 ],
551 597 childRoles: ['doc-cover'],
552 598 },
553 599 input: {
@@ -595,7 +641,7 @@ exports.roles = {
595 641 ],
596 642 },
597 643 link: {
598 -1 selectors: ['a[href]', 'area[href]'],
-1 644 selectors: ['a[*|href]', 'area[href]'],
599 645 childRoles: ['doc-backlink', 'doc-biblioref', 'doc-glossref', 'doc-noteref'],
600 646 nameFromContents: true,
601 647 },
@@ -625,6 +671,9 @@ exports.roles = {
625 671 main: {
626 672 selectors: ['main'],
627 673 },
-1 674 mark: {
-1 675 selectors: ['mark'],
-1 676 },
628 677 marquee: {},
629 678 math: {
630 679 selectors: ['math'],
@@ -648,11 +697,10 @@ exports.roles = {
648 697 },
649 698 },
650 699 menuitem: {
651 -1 childRoles: ['menuitemcheckbox'],
-1 700 childRoles: ['menuitemcheckbox', 'menuitemradio'],
652 701 nameFromContents: true,
653 702 },
654 703 menuitemcheckbox: {
655 -1 childRoles: ['menuitemradio'],
656 704 nameFromContents: true,
657 705 defaults: {
658 706 'checked': 'false',
@@ -759,12 +807,13 @@ exports.roles = {
759 807 'emphasis',
760 808 'figure',
761 809 'group',
762 -1 'img',
-1 810 'image',
763 811 'insertion',
764 812 'landmark',
765 813 'list',
766 814 'listitem',
767 815 'log',
-1 816 'mark',
768 817 'marquee',
769 818 'math',
770 819 'note',
@@ -772,6 +821,7 @@ exports.roles = {
772 821 'status',
773 822 'strong',
774 823 'subscript',
-1 824 'suggestion',
775 825 'superscript',
776 826 'table',
777 827 'tabpanel',
@@ -966,6 +1016,7 @@ for (const role in exports.roles) {
966 1016 exports.aliases = {
967 1017 'presentation': 'none',
968 1018 'directory': 'list',
-1 1019 'img': 'image',
969 1020 };
970 1021
971 1022 exports.nameFromDescendant = {
@@ -985,25 +1036,43 @@ const constants = require('./constants.js');
985 1036 const atree = require('./atree.js');
986 1037 const query = require('./query.js');
987 1038
988 -1 const getPseudoContent = function(node, selector) {
989 -1 const styles = window.getComputedStyle(node, selector);
990 -1 const ret = styles.getPropertyValue('content');
991 -1 const inline = styles.display.substr(0, 6) === 'inline';
992 -1 if (!ret) {
993 -1 return '';
994 -1 }
995 -1 if (ret.substr(0, 1) !== '"') {
996 -1 return '';
997 -1 } else {
998 -1 if (inline) {
999 -1 return ret.slice(1, -1);
-1 1039 const addSpaces = function(text, el, pseudoSelector) {
-1 1040 // https://github.com/w3c/accname/issues/3
-1 1041 const styles = window.getComputedStyle(el, pseudoSelector);
-1 1042 const inline = styles.display === 'inline';
-1 1043 return inline ? text : ` ${text} `;
-1 1044 };
-1 1045
-1 1046 const getPseudoContent = function(el, pseudoSelector) {
-1 1047 const styles = window.getComputedStyle(el, pseudoSelector);
-1 1048 let tail = styles.getPropertyValue('content').trim();
-1 1049 let ret = [];
-1 1050
-1 1051 let match;
-1 1052 while (tail.length) {
-1 1053 if (match = tail.match(/^"([^"]*)"/)) {
-1 1054 ret.push(match[1]);
-1 1055 } else if (match = tail.match(/^([a-z-]+)\(([^)]*)\)/)) {
-1 1056 if (match[1] === 'attr') {
-1 1057 ret.push(el.getAttribute(match[2]) || '');
-1 1058 }
-1 1059 } else if (match = tail.match(/^([a-z-]+)/)) {
-1 1060 if (match[1] === 'open-quote' || match[1] === 'close-quote') {
-1 1061 ret.push('"');
-1 1062 }
-1 1063 } else if (match = tail.match(/^\//)) {
-1 1064 ret = [];
1000 1065 } else {
1001 -1 return ' ' + ret.slice(1, -1) + ' ';
-1 1066 // invalid content, ignore
-1 1067 return '';
1002 1068 }
-1 1069 tail = tail.slice(match[0].length).trim();
1003 1070 }
-1 1071
-1 1072 return addSpaces(ret.join(''), el, pseudoSelector);
1004 1073 };
1005 1074
1006 -1 const getContent = function(root, visited) {
-1 1075 const getContent = function(root, ongoingLabelledBy, visited) {
1007 1076 const children = atree.getChildNodes(root);
1008 1077
1009 1078 let ret = '';
@@ -1014,12 +1083,8 @@ const getContent = function(root, visited) {
1014 1083 } else if (node.nodeType === node.ELEMENT_NODE) {
1015 1084 if (node.tagName.toLowerCase() === 'br') {
1016 1085 ret += '\n';
1017 -1 } else if (window.getComputedStyle(node).display.substr(0, 6) === 'inline' &&
1018 -1 node.tagName.toLowerCase() !== 'input' &&
1019 -1 node.tagName.toLowerCase() !== 'img') { // https://github.com/w3c/accname/issues/3
1020 -1 ret += getName(node, true, visited);
1021 1086 } else {
1022 -1 ret += ' ' + getName(node, true, visited) + ' ';
-1 1087 ret += getName(node, true, ongoingLabelledBy, visited);
1023 1088 }
1024 1089 }
1025 1090 }
@@ -1034,7 +1099,7 @@ const allowNameFromContent = function(el) {
1034 1099 }
1035 1100 };
1036 1101
1037 -1 const getName = function(el, recursive, visited, directReference) {
-1 1102 const getName = function(el, recursive, ongoingLabelledBy, visited, directReference) {
1038 1103 let ret = '';
1039 1104
1040 1105 visited = visited || [];
@@ -1050,23 +1115,23 @@ const getName = function(el, recursive, visited, directReference) {
1050 1115 // handled in atree
1051 1116
1052 1117 // B
1053 -1 if (!recursive && el.matches('[aria-labelledby]')) {
-1 1118 if (!ongoingLabelledBy && el.matches('[aria-labelledby]')) {
1054 1119 const ids = el.getAttribute('aria-labelledby').split(/\s+/);
1055 1120 const strings = ids.map(id => {
1056 1121 const label = document.getElementById(id);
1057 -1 return label ? getName(label, true, visited, true) : '';
-1 1122 return label ? getName(label, true, true, visited, true) : '';
1058 1123 });
1059 1124 ret = strings.join(' ');
1060 1125 }
1061 1126
1062 1127 // E (the current draft has this at this high priority)
1063 1128 if (!ret.trim() && recursive) {
1064 -1 if (query.matches(el, 'textbox,button')) {
-1 1129 if (query.matches(el, 'textbox')) {
1065 1130 ret = el.value || el.textContent;
1066 1131 } else if (query.matches(el, 'combobox,listbox')) {
1067 1132 const selected = query.querySelector(el, ':selected') || query.querySelector(el, 'option');
1068 1133 if (selected) {
1069 -1 ret = getName(selected, recursive, visited);
-1 1134 ret = getName(selected, recursive, ongoingLabelledBy, visited);
1070 1135 } else {
1071 1136 ret = el.value || '';
1072 1137 }
@@ -1084,14 +1149,11 @@ const getName = function(el, recursive, visited, directReference) {
1084 1149 // D
1085 1150 if (!ret.trim() && !recursive && el.labels) {
1086 1151 const strings = Array.prototype.map.call(el.labels, label => {
1087 -1 return getName(label, true, visited);
-1 1152 return getName(label, true, ongoingLabelledBy, visited);
1088 1153 });
1089 1154 ret = strings.join(' ');
1090 1155 }
1091 1156 if (!ret.trim()) {
1092 -1 ret = el.placeholder || '';
1093 -1 }
1094 -1 if (!ret.trim()) {
1095 1157 ret = el.alt || '';
1096 1158 }
1097 1159 if (!ret.trim() && el.matches('abbr,acronym') && el.title) {
@@ -1102,7 +1164,7 @@ const getName = function(el, recursive, visited, directReference) {
1102 1164 if (el.matches(selector)) {
1103 1165 const descendant = el.querySelector(constants.nameFromDescendant[selector]);
1104 1166 if (descendant) {
1105 -1 ret = getName(descendant, true, visited);
-1 1167 ret = getName(descendant, true, ongoingLabelledBy, visited);
1106 1168 }
1107 1169 }
1108 1170 }
@@ -1113,11 +1175,14 @@ const getName = function(el, recursive, visited, directReference) {
1113 1175 ret = svgTitle.textContent;
1114 1176 }
1115 1177 }
-1 1178 if (!ret.trim() && el.matches('a')) {
-1 1179 ret = el.getAttribute('xlink:title') || '';
-1 1180 }
1116 1181
1117 1182 // F
1118 1183 // FIXME: menu is not mentioned in the spec
1119 1184 if (!ret.trim() && (recursive || allowNameFromContent(el) || el.closest('label')) && !query.matches(el, 'menu')) {
1120 -1 ret = getContent(el, visited);
-1 1185 ret = getContent(el, ongoingLabelledBy, visited);
1121 1186 }
1122 1187
1123 1188 if (!ret.trim() && query.matches(el, 'button')) {
@@ -1136,9 +1201,8 @@ const getName = function(el, recursive, visited, directReference) {
1136 1201 // handled in getContent
1137 1202
1138 1203 // I
1139 -1 // FIXME: presentation not mentioned in the spec
1140 -1 if (!ret.trim() && (directReference || !query.matches(el, 'presentation'))) {
1141 -1 ret = el.title || '';
-1 1204 if (!ret.trim()) {
-1 1205 ret = el.title || el.placeholder || '';
1142 1206 }
1143 1207
1144 1208 // FIXME: not exactly sure about this, but it reduces the number of failing
@@ -1149,11 +1213,14 @@ const getName = function(el, recursive, visited, directReference) {
1149 1213
1150 1214 const before = getPseudoContent(el, ':before');
1151 1215 const after = getPseudoContent(el, ':after');
1152 -1 return before + ret + after;
-1 1216 return addSpaces(before + ret + after, el);
1153 1217 };
1154 1218
1155 1219 const getNameTrimmed = function(el) {
1156 -1 return getName(el).replace(/\s+/g, ' ').trim();
-1 1220 return getName(el)
-1 1221 .replace(/[ \n\r\t\f]+/g, ' ')
-1 1222 .replace(/^ /, '')
-1 1223 .replace(/ $/, '');
1157 1224 };
1158 1225
1159 1226 const getDescription = function(el) {
@@ -1163,7 +1230,7 @@ const getDescription = function(el) {
1163 1230 const ids = el.getAttribute('aria-describedby').split(/\s+/);
1164 1231 const strings = ids.map(id => {
1165 1232 const label = document.getElementById(id);
1166 -1 return label ? getName(label, true) : '';
-1 1233 return label ? getName(label, true, true) : '';
1167 1234 });
1168 1235 ret = strings.join(' ');
1169 1236 } else if (el.matches('[aria-description]')) {
@@ -1176,6 +1243,9 @@ const getDescription = function(el) {
1176 1243 } else if (el.title) {
1177 1244 ret = el.title;
1178 1245 }
-1 1246 if (!ret.trim() && el.matches('a')) {
-1 1247 ret = el.getAttribute('xlink:title') || '';
-1 1248 }
1179 1249
1180 1250 ret = (ret || '').trim().replace(/\s+/g, ' ');
1181 1251
diff --git a/package.json b/package.json
@@ -1,6 +1,6 @@ 1 1 { 2 2 "name": "aria-api",3 -1 "version": "0.6.0",-1 3 "version": "0.7.0", 4 4 "description": "Access ARIA information from JavaScript", 5 5 "main": "index.js", 6 6 "keywords": [