You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
5.0 KiB
192 lines
5.0 KiB
"use strict";
|
|
|
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.getRoles = getRoles;
|
|
exports.getImplicitAriaRoles = getImplicitAriaRoles;
|
|
exports.isSubtreeInaccessible = isSubtreeInaccessible;
|
|
exports.prettyRoles = prettyRoles;
|
|
exports.isInaccessible = isInaccessible;
|
|
exports.logRoles = void 0;
|
|
|
|
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
|
|
var _ariaQuery = require("aria-query");
|
|
|
|
var _domAccessibilityApi = require("dom-accessibility-api");
|
|
|
|
var _prettyDom = require("./pretty-dom");
|
|
|
|
const elementRoleList = buildElementRoleList(_ariaQuery.elementRoles);
|
|
/**
|
|
* @param {Element} element -
|
|
* @returns {boolean} - `true` if `element` and its subtree are inaccessible
|
|
*/
|
|
|
|
function isSubtreeInaccessible(element) {
|
|
if (element.hidden === true) {
|
|
return true;
|
|
}
|
|
|
|
if (element.getAttribute('aria-hidden') === 'true') {
|
|
return true;
|
|
}
|
|
|
|
const window = element.ownerDocument.defaultView;
|
|
|
|
if (window.getComputedStyle(element).display === 'none') {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
/**
|
|
* Partial implementation https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion
|
|
* which should only be used for elements with a non-presentational role i.e.
|
|
* `role="none"` and `role="presentation"` will not be excluded.
|
|
*
|
|
* Implements aria-hidden semantics (i.e. parent overrides child)
|
|
* Ignores "Child Presentational: True" characteristics
|
|
*
|
|
* @param {Element} element -
|
|
* @param {object} [options] -
|
|
* @param {function (element: Element): boolean} options.isSubtreeInaccessible -
|
|
* can be used to return cached results from previous isSubtreeInaccessible calls
|
|
* @returns {boolean} true if excluded, otherwise false
|
|
*/
|
|
|
|
|
|
function isInaccessible(element, options = {}) {
|
|
const {
|
|
isSubtreeInaccessible: isSubtreeInaccessibleImpl = isSubtreeInaccessible
|
|
} = options;
|
|
const window = element.ownerDocument.defaultView; // since visibility is inherited we can exit early
|
|
|
|
if (window.getComputedStyle(element).visibility === 'hidden') {
|
|
return true;
|
|
}
|
|
|
|
let currentElement = element;
|
|
|
|
while (currentElement) {
|
|
if (isSubtreeInaccessibleImpl(currentElement)) {
|
|
return true;
|
|
}
|
|
|
|
currentElement = currentElement.parentElement;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getImplicitAriaRoles(currentNode) {
|
|
// eslint bug here:
|
|
// eslint-disable-next-line no-unused-vars
|
|
for (const {
|
|
selector,
|
|
roles
|
|
} of elementRoleList) {
|
|
if (currentNode.matches(selector)) {
|
|
return [...roles];
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
function buildElementRoleList(elementRolesMap) {
|
|
function makeElementSelector({
|
|
name,
|
|
attributes = []
|
|
}) {
|
|
return `${name}${attributes.map(({
|
|
name: attributeName,
|
|
value,
|
|
constraints = []
|
|
}) => {
|
|
const shouldNotExist = constraints.indexOf('undefined') !== -1;
|
|
|
|
if (shouldNotExist) {
|
|
return `:not([${attributeName}])`;
|
|
} else if (value) {
|
|
return `[${attributeName}="${value}"]`;
|
|
} else {
|
|
return `[${attributeName}]`;
|
|
}
|
|
}).join('')}`;
|
|
}
|
|
|
|
function getSelectorSpecificity({
|
|
attributes = []
|
|
}) {
|
|
return attributes.length;
|
|
}
|
|
|
|
let result = []; // eslint bug here:
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
|
for (const [element, roles] of elementRolesMap.entries()) {
|
|
result = [...result, {
|
|
selector: makeElementSelector(element),
|
|
roles: Array.from(roles),
|
|
specificity: getSelectorSpecificity(element)
|
|
}];
|
|
}
|
|
|
|
return result.sort(function ({
|
|
specificity: leftSpecificity
|
|
}, {
|
|
specificity: rightSpecificity
|
|
}) {
|
|
return rightSpecificity - leftSpecificity;
|
|
});
|
|
}
|
|
|
|
function getRoles(container, {
|
|
hidden = false
|
|
} = {}) {
|
|
function flattenDOM(node) {
|
|
return [node, ...Array.from(node.children).reduce((acc, child) => [...acc, ...flattenDOM(child)], [])];
|
|
}
|
|
|
|
return flattenDOM(container).filter(element => {
|
|
return hidden === false ? isInaccessible(element) === false : true;
|
|
}).reduce((acc, node) => {
|
|
const roles = getImplicitAriaRoles(node);
|
|
return roles.reduce((rolesAcc, role) => Array.isArray(rolesAcc[role]) ? (0, _extends2.default)({}, rolesAcc, {
|
|
[role]: [...rolesAcc[role], node]
|
|
}) : (0, _extends2.default)({}, rolesAcc, {
|
|
[role]: [node]
|
|
}), acc);
|
|
}, {});
|
|
}
|
|
|
|
function prettyRoles(dom, {
|
|
hidden
|
|
}) {
|
|
const roles = getRoles(dom, {
|
|
hidden
|
|
});
|
|
return Object.entries(roles).map(([role, elements]) => {
|
|
const delimiterBar = '-'.repeat(50);
|
|
const elementsString = elements.map(el => {
|
|
const nameString = `Name "${(0, _domAccessibilityApi.computeAccessibleName)(el)}":\n`;
|
|
const domString = (0, _prettyDom.prettyDOM)(el.cloneNode(false));
|
|
return `${nameString}${domString}`;
|
|
}).join('\n\n');
|
|
return `${role}:\n\n${elementsString}\n\n${delimiterBar}`;
|
|
}).join('\n');
|
|
}
|
|
|
|
const logRoles = (dom, {
|
|
hidden = false
|
|
} = {}) => console.log(prettyRoles(dom, {
|
|
hidden
|
|
}));
|
|
/* eslint no-console:0 */
|
|
|
|
|
|
exports.logRoles = logRoles; |