aria-api

access ARIA information from JavaScript
git clone https://git.ce9e.org/aria-api.git

commit
90aff05a50a6221c35af0612b6c007e59821c4a5
parent
2d62a3c5026da87acff5455b430a723687485d81
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2024-11-01 12:18
Merge branch 'esm'

Diffstat

M Makefile 2 +-
M index.js 22 ++++------------------
M lib/atree.js 17 +++++------------
M lib/attrs.js 20 +++++++++-----------
M lib/constants.js 28 ++++++++++++++--------------
M lib/name.js 46 +++++++++++++++++++++-------------------------
M lib/query.js 19 ++++++-------------
M package.json 2 +-

8 files changed, 61 insertions, 95 deletions


diff --git a/Makefile b/Makefile

@@ -1,6 +1,6 @@
    1     1 dist/aria.js: index.js lib/*.js
    2     2 	mkdir -p dist
    3    -1 	npx browserify $< -o $@ -s aria
   -1     3 	rollup $< -o $@ -f umd -n aria
    4     4 
    5     5 wpt-master:
    6     6 	wget https://github.com/web-platform-tests/wpt/archive/refs/heads/master.zip -O wpt-master.zip

diff --git a/index.js b/index.js

@@ -1,18 +1,4 @@
    1    -1 var query = require('./lib/query.js');
    2    -1 var name = require('./lib/name.js');
    3    -1 var atree = require('./lib/atree.js');
    4    -1 
    5    -1 module.exports = {
    6    -1 	getRole: query.getRole,
    7    -1 	getAttribute: query.getAttribute,
    8    -1 	getName: name.getName,
    9    -1 	getDescription: name.getDescription,
   10    -1 
   11    -1 	matches: query.matches,
   12    -1 	querySelector: query.querySelector,
   13    -1 	querySelectorAll: query.querySelectorAll,
   14    -1 	closest: query.closest,
   15    -1 
   16    -1 	getParentNode: atree.getParentNode,
   17    -1 	getChildNodes: atree.getChildNodes,
   18    -1 };
   -1     1 export { getParentNode, getChildNodes } from './lib/atree.js';
   -1     2 export { getRole, getAttribute } from './lib/attrs.js';
   -1     3 export { matches, querySelector, querySelectorAll, closest } from './lib/query.js';
   -1     4 export { getName, getDescription } from './lib/name.js';

diff --git a/lib/atree.js b/lib/atree.js

@@ -1,4 +1,4 @@
    1    -1 const attrs = require('./attrs');
   -1     1 import * as attrs from './attrs';
    2     2 
    3     3 const _getOwner = function(node, owners) {
    4     4 	if (node.nodeType === node.ELEMENT_NODE && node.id) {
@@ -36,7 +36,7 @@ const getOwner = function(node, owners) {
   36    36 	}
   37    37 };
   38    38 
   39    -1 const getParentNode = function(node, owners) {
   -1    39 export const getParentNode = function(node, owners) {
   40    40 	return getOwner(node, owners) || node.parentNode;
   41    41 };
   42    42 
@@ -44,7 +44,7 @@ const isHidden = function(node) {
   44    44 	return node.nodeType === node.ELEMENT_NODE && attrs.getAttribute(node, 'hidden');
   45    45 };
   46    46 
   47    -1 const getChildNodes = function(node, owners) {
   -1    47 export const getChildNodes = function(node, owners) {
   48    48 	const childNodes = [];
   49    49 
   50    50 	for (let i = 0; i < node.childNodes.length; i++) {
@@ -68,7 +68,7 @@ const getChildNodes = function(node, owners) {
   68    68 	return childNodes;
   69    69 };
   70    70 
   71    -1 const walk = function(root, fn) {
   -1    71 export const walk = function(root, fn) {
   72    72 	const owners = document.querySelectorAll('[aria-owns]');
   73    73 	let queue = [root];
   74    74 	while (queue.length) {
@@ -78,7 +78,7 @@ const walk = function(root, fn) {
   78    78 	}
   79    79 };
   80    80 
   81    -1 const searchUp = function(node, test) {
   -1    81 export const searchUp = function(node, test) {
   82    82 	const candidate = getParentNode(node);
   83    83 	if (candidate) {
   84    84 		if (test(candidate)) {
@@ -88,10 +88,3 @@ const searchUp = function(node, test) {
   88    88 		}
   89    89 	}
   90    90 };
   91    -1 
   92    -1 module.exports = {
   93    -1 	'getParentNode': getParentNode,
   94    -1 	'getChildNodes': getChildNodes,
   95    -1 	'walk': walk,
   96    -1 	'searchUp': searchUp,
   97    -1 };

diff --git a/lib/attrs.js b/lib/attrs.js

@@ -1,4 +1,4 @@
    1    -1 const constants = require('./constants.js');
   -1     1 import * as constants from './constants.js';
    2     2 
    3     3 var unique = function(arr) {
    4     4 	return arr.filter((a, i) => arr.indexOf(a) === i);
@@ -17,7 +17,7 @@ var normalizeRoles = function(roles, includeAbstract) {
   17    17 };
   18    18 
   19    19 // candidates can be passed for performance optimization
   20    -1 const getRole = function(el, candidates) {
   -1    20 const getRoleRaw = function(el, candidates) {
   21    21 	// TODO: filter out any invalid roles (e.g. name or context required)
   22    22 	const roles = normalizeRoles(
   23    23 		(el.getAttribute('role') || '').toLowerCase().split(/\s+/)
@@ -41,14 +41,18 @@ const getRole = function(el, candidates) {
   41    41 	}
   42    42 };
   43    43 
   44    -1 const hasRole = function(el, roles) {
   -1    44 export const getRole = function(el) {
   -1    45 	return getRoleRaw(el);
   -1    46 };
   -1    47 
   -1    48 export const hasRole = function(el, roles) {
   45    49 	const subRoles = normalizeRoles(roles, true).map(role => {
   46    50 		return constants.roles[role].subRoles || [role];
   47    51 	});
   48    -1 	return !!getRole(el, unique(flatten(subRoles)));
   -1    52 	return !!getRoleRaw(el, unique(flatten(subRoles)));
   49    53 };
   50    54 
   51    -1 const getAttribute = function(el, key) {
   -1    55 export const getAttribute = function(el, key) {
   52    56 	if (constants.attributeStrongMapping.hasOwnProperty(key)) {
   53    57 		const value = el[constants.attributeStrongMapping[key]];
   54    58 		if (value) {
@@ -124,9 +128,3 @@ const getAttribute = function(el, key) {
  124   128 		return false;
  125   129 	}
  126   130 };
  127    -1 
  128    -1 module.exports = {
  129    -1 	getRole: getRole,
  130    -1 	hasRole: hasRole,
  131    -1 	getAttribute: getAttribute,
  132    -1 };

diff --git a/lib/constants.js b/lib/constants.js

@@ -1,5 +1,5 @@
    1     1 // https://www.w3.org/TR/wai-aria/#state_prop_def
    2    -1 exports.attributes = {
   -1     2 export const attributes = {
    3     3 	'activedescendant': 'id',
    4     4 	'atomic': 'bool',
    5     5 	'autocomplete': 'token',
@@ -55,14 +55,14 @@ exports.attributes = {
   55    55 	'valuetext': 'string',
   56    56 };
   57    57 
   58    -1 exports.attributeStrongMapping = {
   -1    58 export const attributeStrongMapping = {
   59    59 	'disabled': 'disabled',
   60    60 	'placeholder': 'placeholder',
   61    61 	'readonly': 'readOnly',
   62    62 	'required': 'required',
   63    63 };
   64    64 
   65    -1 exports.attributeWeakMapping = {
   -1    65 export const attributeWeakMapping = {
   66    66 	'checked': 'checked',
   67    67 	'colspan': 'colSpan',
   68    68 	'expanded': 'open',
@@ -89,7 +89,7 @@ const svgSelectors = function(selector) {
   89    89 
   90    90 // https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings
   91    91 // https://www.w3.org/TR/wai-aria/roles
   92    -1 exports.roles = {
   -1    92 export const roles = {
   93    93 	alert: {
   94    94 		childRoles: ['alertdialog'],
   95    95 		defaults: {
@@ -732,7 +732,7 @@ exports.roles = {
  732   732 };
  733   733 
  734   734 const getSubRoles = function(role) {
  735    -1 	const children = (exports.roles[role]).childRoles || [];
   -1   735 	const children = (roles[role]).childRoles || [];
  736   736 	const descendents = children.map(getSubRoles);
  737   737 
  738   738 	const result = [role];
@@ -748,30 +748,30 @@ const getSubRoles = function(role) {
  748   748 	return result;
  749   749 };
  750   750 
  751    -1 exports.attrsWithDefaults = [];
   -1   751 export const attrsWithDefaults = [];
  752   752 
  753    -1 for (const role in exports.roles) {
  754    -1 	exports.roles[role].subRoles = getSubRoles(role);
  755    -1 	for (const key in exports.roles[role].defaults) {
  756    -1 		if (!exports.attrsWithDefaults.includes(key)) {
  757    -1 			exports.attrsWithDefaults.push(key);
   -1   753 for (const role in roles) {
   -1   754 	roles[role].subRoles = getSubRoles(role);
   -1   755 	for (const key in roles[role].defaults) {
   -1   756 		if (!attrsWithDefaults.includes(key)) {
   -1   757 			attrsWithDefaults.push(key);
  758   758 		}
  759   759 	}
  760   760 }
  761   761 
  762    -1 exports.aliases = {
   -1   762 export const aliases = {
  763   763 	'presentation': 'none',
  764   764 	'directory': 'list',
  765   765 	'img': 'image',
  766   766 };
  767   767 
  768    -1 exports.nameFromDescendant = {
   -1   768 export const nameFromDescendant = {
  769   769 	'figure': 'figcaption',
  770   770 	'table': 'caption',
  771   771 	'fieldset': 'legend',
  772   772 };
  773   773 
  774    -1 exports.nameDefaults = {
   -1   774 export const nameDefaults = {
  775   775 	'input[type="submit"]': 'Submit',
  776   776 	'input[type="reset"]': 'Reset',
  777   777 	'summary': 'Details',

diff --git a/lib/name.js b/lib/name.js

@@ -1,6 +1,7 @@
    1    -1 const constants = require('./constants.js');
    2    -1 const atree = require('./atree.js');
    3    -1 const query = require('./query.js');
   -1     1 import * as constants from './constants.js';
   -1     2 import * as atree from './atree.js';
   -1     3 import * as attrs from './attrs.js';
   -1     4 import * as query from './query.js';
    4     5 
    5     6 const addSpaces = function(text, el, pseudoSelector) {
    6     7 	// https://github.com/w3c/accname/issues/3
@@ -16,17 +17,17 @@ const getPseudoContent = function(el, pseudoSelector) {
   16    17 
   17    18 	let match;
   18    19 	while (tail.length) {
   19    -1 		if (match = tail.match(/^"([^"]*)"/)) {
   -1    20 		if ((match = tail.match(/^"([^"]*)"/))) {
   20    21 			ret.push(match[1]);
   21    -1 		} else if (match = tail.match(/^([a-z-]+)\(([^)]*)\)/)) {
   -1    22 		} else if ((match = tail.match(/^([a-z-]+)\(([^)]*)\)/))) {
   22    23 			if (match[1] === 'attr') {
   23    24 				ret.push(el.getAttribute(match[2]) || '');
   24    25 			}
   25    -1 		} else if (match = tail.match(/^([a-z-]+)/)) {
   -1    26 		} else if ((match = tail.match(/^([a-z-]+)/))) {
   26    27 			if (match[1] === 'open-quote' || match[1] === 'close-quote') {
   27    28 				ret.push('"');
   28    29 			}
   29    -1 		} else if (match = tail.match(/^\//)) {
   -1    30 		} else if ((match = tail.match(/^\//))) {
   30    31 			ret = [];
   31    32 		} else {
   32    33 			// invalid content, ignore
@@ -50,7 +51,7 @@ const getContent = function(root, ongoingLabelledBy, visited) {
   50    51 			if (node.tagName.toLowerCase() === 'br') {
   51    52 				ret += '\n';
   52    53 			} else {
   53    -1 				ret += getName(node, true, ongoingLabelledBy, visited);
   -1    54 				ret += getNameRaw(node, true, ongoingLabelledBy, visited);
   54    55 			}
   55    56 		}
   56    57 	}
@@ -59,13 +60,13 @@ const getContent = function(root, ongoingLabelledBy, visited) {
   59    60 };
   60    61 
   61    62 const allowNameFromContent = function(el) {
   62    -1 	const role = query.getRole(el);
   -1    63 	const role = attrs.getRole(el);
   63    64 	if (role) {
   64    65 		return constants.roles[role].nameFromContents;
   65    66 	}
   66    67 };
   67    68 
   68    -1 const getName = function(el, recursive, ongoingLabelledBy, visited, directReference) {
   -1    69 const getNameRaw = function(el, recursive, ongoingLabelledBy, visited, directReference) {
   69    70 	let ret = '';
   70    71 
   71    72 	visited = visited || [];
@@ -85,7 +86,7 @@ const getName = function(el, recursive, ongoingLabelledBy, visited, directRefere
   85    86 		const ids = el.getAttribute('aria-labelledby').split(/\s+/);
   86    87 		const strings = ids.map(id => {
   87    88 			const label = document.getElementById(id);
   88    -1 			return label ? getName(label, true, true, visited, true) : '';
   -1    89 			return label ? getNameRaw(label, true, true, visited, true) : '';
   89    90 		});
   90    91 		ret = strings.join(' ');
   91    92 	}
@@ -97,12 +98,12 @@ const getName = function(el, recursive, ongoingLabelledBy, visited, directRefere
   97    98 		} else if (query.matches(el, 'combobox,listbox')) {
   98    99 			const selected = query.querySelector(el, ':selected') || query.querySelector(el, 'option');
   99   100 			if (selected) {
  100    -1 				ret = getName(selected, recursive, ongoingLabelledBy, visited);
   -1   101 				ret = getNameRaw(selected, recursive, ongoingLabelledBy, visited);
  101   102 			} else {
  102   103 				ret = el.value || '';
  103   104 			}
  104   105 		} else if (query.matches(el, 'range')) {
  105    -1 			ret = '' + (query.getAttribute(el, 'valuetext') || query.getAttribute(el, 'valuenow') || el.value);
   -1   106 			ret = '' + (attrs.getAttribute(el, 'valuetext') || attrs.getAttribute(el, 'valuenow') || el.value);
  106   107 		}
  107   108 	}
  108   109 
@@ -115,7 +116,7 @@ const getName = function(el, recursive, ongoingLabelledBy, visited, directRefere
  115   116 	// D
  116   117 	if (!ret.trim() && !recursive && el.labels) {
  117   118 		const strings = Array.prototype.map.call(el.labels, label => {
  118    -1 			return getName(label, true, ongoingLabelledBy, visited);
   -1   119 			return getNameRaw(label, true, ongoingLabelledBy, visited);
  119   120 		});
  120   121 		ret = strings.join(' ');
  121   122 	}
@@ -130,7 +131,7 @@ const getName = function(el, recursive, ongoingLabelledBy, visited, directRefere
  130   131 			if (el.matches(selector)) {
  131   132 				const descendant = el.querySelector(constants.nameFromDescendant[selector]);
  132   133 				if (descendant) {
  133    -1 					ret = getName(descendant, true, ongoingLabelledBy, visited);
   -1   134 					ret = getNameRaw(descendant, true, ongoingLabelledBy, visited);
  134   135 				}
  135   136 			}
  136   137 		}
@@ -182,21 +183,21 @@ const getName = function(el, recursive, ongoingLabelledBy, visited, directRefere
  182   183 	return addSpaces(before + ret + after, el);
  183   184 };
  184   185 
  185    -1 const getNameTrimmed = function(el) {
  186    -1 	return getName(el)
   -1   186 export const getName = function(el) {
   -1   187 	return getNameRaw(el)
  187   188 		.replace(/[ \n\r\t\f]+/g, ' ')
  188   189 		.replace(/^ /, '')
  189   190 		.replace(/ $/, '');
  190   191 };
  191   192 
  192    -1 const getDescription = function(el) {
   -1   193 export const getDescription = function(el) {
  193   194 	let ret = '';
  194   195 
  195   196 	if (el.matches('[aria-describedby]')) {
  196   197 		const ids = el.getAttribute('aria-describedby').split(/\s+/);
  197   198 		const strings = ids.map(id => {
  198   199 			const label = document.getElementById(id);
  199    -1 			return label ? getName(label, true, true) : '';
   -1   200 			return label ? getNameRaw(label, true, true) : '';
  200   201 		});
  201   202 		ret = strings.join(' ');
  202   203 	} else if (el.matches('[aria-description]')) {
@@ -215,14 +216,9 @@ const getDescription = function(el) {
  215   216 
  216   217 	ret = (ret || '').trim().replace(/\s+/g, ' ');
  217   218 
  218    -1 	if (ret === getNameTrimmed(el)) {
   -1   219 	if (ret === getName(el)) {
  219   220 		ret = '';
  220   221 	}
  221   222 
  222   223 	return ret;
  223   224 };
  224    -1 
  225    -1 module.exports = {
  226    -1 	getName: getNameTrimmed,
  227    -1 	getDescription: getDescription,
  228    -1 };

diff --git a/lib/query.js b/lib/query.js

@@ -1,8 +1,7 @@
    1    -1 const attrs = require('./attrs.js');
    2    -1 const atree = require('./atree.js');
   -1     1 import * as attrs from './attrs.js';
   -1     2 import * as atree from './atree.js';
    3     3 
    4    -1 
    5    -1 const matches = function(el, selector) {
   -1     4 export const matches = function(el, selector) {
    6     5 	if (selector.substr(0, 1) === ':') {
    7     6 		const attr = selector.substr(1);
    8     7 		return attrs.getAttribute(el, attr);
@@ -40,7 +39,7 @@ const _querySelector = function(all) {
   40    39 	};
   41    40 };
   42    41 
   43    -1 const closest = function(el, selector) {
   -1    42 export const closest = function(el, selector) {
   44    43 	return atree.searchUp(el, candidate => {
   45    44 		if (candidate.nodeType === candidate.ELEMENT_NODE) {
   46    45 			return matches(candidate, selector);
@@ -48,11 +47,5 @@ const closest = function(el, selector) {
   48    47 	});
   49    48 };
   50    49 
   51    -1 module.exports = {
   52    -1 	getRole: el => attrs.getRole(el),
   53    -1 	getAttribute: attrs.getAttribute,
   54    -1 	matches: matches,
   55    -1 	querySelector: _querySelector(),
   56    -1 	querySelectorAll: _querySelector(true),
   57    -1 	closest: closest,
   58    -1 };
   -1    50 export const querySelector = _querySelector();
   -1    51 export const querySelectorAll = _querySelector(true);

diff --git a/package.json b/package.json

@@ -2,7 +2,7 @@
    2     2   "name": "aria-api",
    3     3   "version": "0.7.0",
    4     4   "description": "Access ARIA information from JavaScript",
    5    -1   "main": "index.js",
   -1     5   "module": "index.js",
    6     6   "keywords": [
    7     7     "aria",
    8     8     "accessibility",