@markuplint/ml-spec
Version:
Types and schema that specs of the Markup languages for markuplint
103 lines (102 loc) • 3.7 kB
JavaScript
import { getRoleSpec } from '../specs/get-role-spec.js';
import { resolveNamespace } from '../utils/resolve-namespace.js';
import { getPermittedRoles } from './get-permitted-roles.js';
export function getExplicitRole(specs,
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
el, version) {
const roleValue = el.getAttribute('role');
const roleNames = roleValue?.toLowerCase().trim().split(/\s+/) ?? [];
const permittedRoles = getPermittedRoles(el, version, specs);
const { namespaceURI } = resolveNamespace(el.localName, el.namespaceURI);
let error = 'NO_EXPLICIT';
/**
* Resolve from values and **Handling Author Errors**
*
* @see https://w3c.github.io/aria/#document-handling_author-errors
*/
for (const roleName of roleNames) {
const spec = getRoleSpec(specs, roleName, namespaceURI, version);
/**
* If `spec` is null, the role DOES NOT EXIST.
*
* > If the role attribute contains no tokens matching the name
* > of a non-abstract WAI-ARIA role,
* > the user agent MUST treat the element as
* > if no role had been provided. For example,
* > <table role="foo"> should be exposed
* > in the same way as <table> and <input type="text" role="structure">
* > in the same way as <input type="text">.
*/
if (!spec) {
error = 'ROLE_NO_EXISTS';
continue;
}
/**
* > As stated in the Definition of Roles section,
* > it is considered an authoring error to use abstract roles in content.
* > User agents MUST NOT map abstract roles
* > via the standard role mechanism of the accessibility API.
*/
if (spec.isAbstract) {
error = 'ABSTRACT';
continue;
}
/**
* Whether the role is the permitted role according to ARIA in HTML.
*/
if (!permittedRoles.some(r => r.name === roleName)) {
error = 'NO_PERMITTED';
continue;
}
/**
* > Certain landmark roles require names from authors.
* > In situations where an author has not specified names for these landmarks,
* > it is considered an authoring error.
* > The user agent MUST treat such elements as if no role had been provided.
* > If a valid fallback role had been specified,
* > or if the element had an implicit ARIA role,
* > then user agents would continue to expose that role, instead.
*/
if (isLandmarkRole(spec) && !isValidLandmarkRole(el, spec)) {
error = 'INVALID_LANDMARK';
continue;
}
/**
* Otherwise
*/
return {
el,
role: {
...spec,
isImplicit: false,
},
};
}
return {
el,
role: null,
errorType: error,
};
}
function isLandmarkRole(role) {
return role?.superClassRoles.some(su => su.name === 'landmark');
}
function isValidLandmarkRole(
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
el, role) {
if (!role.accessibleNameRequired) {
return true;
}
if (role.accessibleNameFromAuthor) {
if (el.getAttribute('aria-label')) {
return true;
}
const id = el.getAttribute('aria-labelledby');
if (id && el.ownerDocument.getElementById(id)) {
return true;
}
}
// The landmark role does not require names from the content
// if (role.accessibleNameFromContent) {}
return false;
}