Convert src/compiler/compile/utils/a11y.ts to JavaScript

pull/8569/head
Simon Holthausen 3 years ago
parent dfee586567
commit 13de5cbdad

@ -1,19 +1,10 @@
import { import { roles as roles_map, elementRoles } from 'aria-query';
ARIARoleDefinitionKey,
roles as roles_map,
elementRoles,
ARIARoleRelationConcept
} from 'aria-query';
import { AXObjects, AXObjectRoles, elementAXObjects } from 'axobject-query'; import { AXObjects, AXObjectRoles, elementAXObjects } from 'axobject-query';
import Attribute from '../nodes/Attribute'; import { regex_whitespaces } from '../../utils/patterns.js';
import { regex_whitespaces } from '../../utils/patterns';
const aria_roles = roles_map.keys(); const aria_roles = roles_map.keys();
const abstract_roles = new Set(aria_roles.filter((role) => roles_map.get(role).abstract)); const abstract_roles = new Set(aria_roles.filter((role) => roles_map.get(role).abstract));
const non_abstract_roles = aria_roles.filter((name) => !abstract_roles.has(name)); const non_abstract_roles = aria_roles.filter((name) => !abstract_roles.has(name));
const non_interactive_roles = new Set(non_abstract_roles
const non_interactive_roles = new Set(
non_abstract_roles
.filter((name) => { .filter((name) => {
const role = roles_map.get(name); const role = roles_map.get(name);
return ( return (
@ -22,69 +13,74 @@ const non_interactive_roles = new Set(
// focusable tabpanel elements are recommended if any panels in a set contain content where the first element in the panel is not focusable. // focusable tabpanel elements are recommended if any panels in a set contain content where the first element in the panel is not focusable.
// 'generic' is meant to have no semantic meaning. // 'generic' is meant to have no semantic meaning.
!['toolbar', 'tabpanel', 'generic'].includes(name) && !['toolbar', 'tabpanel', 'generic'].includes(name) &&
!role.superClass.some((classes) => classes.includes('widget')) !role.superClass.some((classes) => classes.includes('widget')));
);
}) })
.concat( .concat(
// The `progressbar` is descended from `widget`, but in practice, its // The `progressbar` is descended from `widget`, but in practice, its
// value is always `readonly`, so we treat it as a non-interactive role. // value is always `readonly`, so we treat it as a non-interactive role.
'progressbar' 'progressbar'));
) const interactive_roles = new Set(non_abstract_roles.filter((name) => !non_interactive_roles.has(name) &&
);
const interactive_roles = new Set(
non_abstract_roles.filter(
(name) =>
!non_interactive_roles.has(name) &&
// 'generic' is meant to have no semantic meaning. // 'generic' is meant to have no semantic meaning.
name !== 'generic' name !== 'generic'));
)
);
export function is_non_interactive_roles(role: ARIARoleDefinitionKey) { /**
* @param {ARIARoleDefinitionKey} role
*/
export function is_non_interactive_roles(role) {
return non_interactive_roles.has(role); return non_interactive_roles.has(role);
} }
export function is_interactive_roles(role: ARIARoleDefinitionKey) { /**
* @param {ARIARoleDefinitionKey} role
*/
export function is_interactive_roles(role) {
return interactive_roles.has(role); return interactive_roles.has(role);
} }
export function is_abstract_role(role: ARIARoleDefinitionKey) { /**
* @param {ARIARoleDefinitionKey} role
*/
export function is_abstract_role(role) {
return abstract_roles.has(role); return abstract_roles.has(role);
} }
const presentation_roles = new Set(['presentation', 'none']); const presentation_roles = new Set(['presentation', 'none']);
export function is_presentation_role(role: ARIARoleDefinitionKey) { /**
* @param {ARIARoleDefinitionKey} role
*/
export function is_presentation_role(role) {
return presentation_roles.has(role); return presentation_roles.has(role);
} }
export function is_hidden_from_screen_reader( /**
tag_name: string, * @param {string} tag_name
attribute_map: Map<string, Attribute> * @param {Map<string, Attribute>} attribute_map
) { */
export function is_hidden_from_screen_reader(tag_name, attribute_map) {
if (tag_name === 'input') { if (tag_name === 'input') {
const type = attribute_map.get('type')?.get_static_value(); const type = attribute_map.get('type')?.get_static_value();
if (type && type === 'hidden') { if (type && type === 'hidden') {
return true; return true;
} }
} }
const aria_hidden = attribute_map.get('aria-hidden'); const aria_hidden = attribute_map.get('aria-hidden');
if (!aria_hidden) return false; if (!aria_hidden)
if (!aria_hidden.is_static) return true; return false;
if (!aria_hidden.is_static)
return true;
const aria_hidden_value = aria_hidden.get_static_value(); const aria_hidden_value = aria_hidden.get_static_value();
return aria_hidden_value === true || aria_hidden_value === 'true'; return aria_hidden_value === true || aria_hidden_value === 'true';
} }
export function has_disabled_attribute(attribute_map: Map<string, Attribute>) { /**
* @param {Map<string, Attribute>} attribute_map
*/
export function has_disabled_attribute(attribute_map) {
const disabled_attr = attribute_map.get('disabled'); const disabled_attr = attribute_map.get('disabled');
const disabled_attr_value = disabled_attr && disabled_attr.get_static_value(); const disabled_attr_value = disabled_attr && disabled_attr.get_static_value();
if (disabled_attr_value) { if (disabled_attr_value) {
return true; return true;
} }
const aria_disabled_attr = attribute_map.get('aria-disabled'); const aria_disabled_attr = attribute_map.get('aria-disabled');
if (aria_disabled_attr) { if (aria_disabled_attr) {
const aria_disabled_attr_value = aria_disabled_attr.get_static_value(); const aria_disabled_attr_value = aria_disabled_attr.get_static_value();
@ -92,149 +88,138 @@ export function has_disabled_attribute(attribute_map: Map<string, Attribute>) {
return true; return true;
} }
} }
return false; return false;
} }
const non_interactive_element_role_schemas: ARIARoleRelationConcept[] = []; /**
* @type {ARIARoleRelationConcept[]}
*/
const non_interactive_element_role_schemas = [];
elementRoles.entries().forEach(([schema, roles]) => { elementRoles.entries().forEach(([schema, roles]) => {
if ([...roles].every((role) => role !== 'generic' && non_interactive_roles.has(role))) { if ([...roles].every((role) => role !== 'generic' && non_interactive_roles.has(role))) {
non_interactive_element_role_schemas.push(schema); non_interactive_element_role_schemas.push(schema);
} }
}); });
const interactive_element_role_schemas: ARIARoleRelationConcept[] = []; /**
* @type {ARIARoleRelationConcept[]}
*/
const interactive_element_role_schemas = [];
elementRoles.entries().forEach(([schema, roles]) => { elementRoles.entries().forEach(([schema, roles]) => {
if ([...roles].every((role) => interactive_roles.has(role))) { if ([...roles].every((role) => interactive_roles.has(role))) {
interactive_element_role_schemas.push(schema); interactive_element_role_schemas.push(schema);
} }
}); });
const interactive_ax_objects = new Set([...AXObjects.keys()].filter((name) => AXObjects.get(name).type === 'widget'));
const non_interactive_ax_objects = new Set([...AXObjects.keys()].filter((name) => ['windows', 'structure'].includes(AXObjects.get(name).type)));
const interactive_ax_objects = new Set( /**
[...AXObjects.keys()].filter((name) => AXObjects.get(name).type === 'widget') * @type {ARIARoleRelationConcept[]}
); */
const interactive_element_ax_object_schemas = [];
const non_interactive_ax_objects = new Set(
[...AXObjects.keys()].filter((name) =>
['windows', 'structure'].includes(AXObjects.get(name).type)
)
);
const interactive_element_ax_object_schemas: ARIARoleRelationConcept[] = [];
elementAXObjects.entries().forEach(([schema, ax_object]) => { elementAXObjects.entries().forEach(([schema, ax_object]) => {
if ([...ax_object].every((role) => interactive_ax_objects.has(role))) { if ([...ax_object].every((role) => interactive_ax_objects.has(role))) {
interactive_element_ax_object_schemas.push(schema); interactive_element_ax_object_schemas.push(schema);
} }
}); });
const non_interactive_element_ax_object_schemas: ARIARoleRelationConcept[] = []; /**
* @type {ARIARoleRelationConcept[]}
*/
const non_interactive_element_ax_object_schemas = [];
elementAXObjects.entries().forEach(([schema, ax_object]) => { elementAXObjects.entries().forEach(([schema, ax_object]) => {
if ([...ax_object].every((role) => non_interactive_ax_objects.has(role))) { if ([...ax_object].every((role) => non_interactive_ax_objects.has(role))) {
non_interactive_element_ax_object_schemas.push(schema); non_interactive_element_ax_object_schemas.push(schema);
} }
}); });
function match_schema( /**
schema: ARIARoleRelationConcept, * @param {ARIARoleRelationConcept} schema
tag_name: string, * @param {string} tag_name
attribute_map: Map<string, Attribute> * @param {Map<string, Attribute>} attribute_map
) { */
if (schema.name !== tag_name) return false; function match_schema(schema, tag_name, attribute_map) {
if (!schema.attributes) return true; if (schema.name !== tag_name)
return false;
if (!schema.attributes)
return true;
return schema.attributes.every((schema_attribute) => { return schema.attributes.every((schema_attribute) => {
const attribute = attribute_map.get(schema_attribute.name); const attribute = attribute_map.get(schema_attribute.name);
if (!attribute) return false; if (!attribute)
return false;
if (schema_attribute.value && schema_attribute.value !== attribute.get_static_value()) { if (schema_attribute.value && schema_attribute.value !== attribute.get_static_value()) {
return false; return false;
} }
return true; return true;
}); });
} }
export var ElementInteractivity;
export enum ElementInteractivity { (function (ElementInteractivity) {
Interactive = 'interactive', ElementInteractivity["Interactive"] = "interactive";
NonInteractive = 'non-interactive', ElementInteractivity["NonInteractive"] = "non-interactive";
Static = 'static' ElementInteractivity["Static"] = "static";
} })(ElementInteractivity || (ElementInteractivity = {}));
export function element_interactivity( /**
tag_name: string, * @param {string} tag_name
attribute_map: Map<string, Attribute> * @param {Map<string, Attribute>} attribute_map
): ElementInteractivity { * @returns {import("C:/repos/svelte/svelte/a11y.ts-to-jsdoc").ElementInteractivity}
if ( */
interactive_element_role_schemas.some((schema) => match_schema(schema, tag_name, attribute_map)) export function element_interactivity(tag_name, attribute_map) {
) { if (interactive_element_role_schemas.some((schema) => match_schema(schema, tag_name, attribute_map))) {
return ElementInteractivity.Interactive; return ElementInteractivity.Interactive;
} }
if (tag_name !== 'header' &&
if ( non_interactive_element_role_schemas.some((schema) => match_schema(schema, tag_name, attribute_map))) {
tag_name !== 'header' &&
non_interactive_element_role_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map)
)
) {
return ElementInteractivity.NonInteractive; return ElementInteractivity.NonInteractive;
} }
if (interactive_element_ax_object_schemas.some((schema) => match_schema(schema, tag_name, attribute_map))) {
if (
interactive_element_ax_object_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map)
)
) {
return ElementInteractivity.Interactive; return ElementInteractivity.Interactive;
} }
if (non_interactive_element_ax_object_schemas.some((schema) => match_schema(schema, tag_name, attribute_map))) {
if (
non_interactive_element_ax_object_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map)
)
) {
return ElementInteractivity.NonInteractive; return ElementInteractivity.NonInteractive;
} }
return ElementInteractivity.Static; return ElementInteractivity.Static;
} }
export function is_interactive_element( /**
tag_name: string, * @param {string} tag_name
attribute_map: Map<string, Attribute> * @param {Map<string, Attribute>} attribute_map
): boolean { * @returns {boolean}
*/
export function is_interactive_element(tag_name, attribute_map) {
return element_interactivity(tag_name, attribute_map) === ElementInteractivity.Interactive; return element_interactivity(tag_name, attribute_map) === ElementInteractivity.Interactive;
} }
export function is_non_interactive_element( /**
tag_name: string, * @param {string} tag_name
attribute_map: Map<string, Attribute> * @param {Map<string, Attribute>} attribute_map
): boolean { * @returns {boolean}
*/
export function is_non_interactive_element(tag_name, attribute_map) {
return element_interactivity(tag_name, attribute_map) === ElementInteractivity.NonInteractive; return element_interactivity(tag_name, attribute_map) === ElementInteractivity.NonInteractive;
} }
export function is_static_element( /**
tag_name: string, * @param {string} tag_name
attribute_map: Map<string, Attribute> * @param {Map<string, Attribute>} attribute_map
): boolean { * @returns {boolean}
*/
export function is_static_element(tag_name, attribute_map) {
return element_interactivity(tag_name, attribute_map) === ElementInteractivity.Static; return element_interactivity(tag_name, attribute_map) === ElementInteractivity.Static;
} }
export function is_semantic_role_element( /**
role: ARIARoleDefinitionKey, * @param {ARIARoleDefinitionKey} role
tag_name: string, * @param {string} tag_name
attribute_map: Map<string, Attribute> * @param {Map<string, Attribute>} attribute_map
) { */
export function is_semantic_role_element(role, tag_name, attribute_map) {
for (const [schema, ax_object] of elementAXObjects.entries()) { for (const [schema, ax_object] of elementAXObjects.entries()) {
if ( if (schema.name === tag_name &&
schema.name === tag_name &&
(!schema.attributes || (!schema.attributes ||
schema.attributes.every( schema.attributes.every((attr) => attribute_map.has(attr.name) &&
(attr) => attribute_map.get(attr.name).get_static_value() === attr.value))) {
attribute_map.has(attr.name) &&
attribute_map.get(attr.name).get_static_value() === attr.value
))
) {
for (const name of ax_object) { for (const name of ax_object) {
const roles = AXObjectRoles.get(name); const roles = AXObjectRoles.get(name);
if (roles) { if (roles) {
@ -249,7 +234,6 @@ export function is_semantic_role_element(
} }
return false; return false;
} }
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofilling-form-controls:-the-autocomplete-attribute // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofilling-form-controls:-the-autocomplete-attribute
const address_type_tokens = new Set(['shipping', 'billing']); const address_type_tokens = new Set(['shipping', 'billing']);
const autofill_field_name_tokens = new Set([ const autofill_field_name_tokens = new Set([
@ -315,40 +299,43 @@ const autofill_contact_field_name_tokens = new Set([
'impp' 'impp'
]); ]);
export function is_valid_autocomplete(autocomplete: null | true | string) { /**
* @param {null | true | string} autocomplete
*/
export function is_valid_autocomplete(autocomplete) {
if (autocomplete === true) { if (autocomplete === true) {
return false; return false;
} else if (!autocomplete) { }
else if (!autocomplete) {
return true; // dynamic value return true; // dynamic value
} }
const tokens = autocomplete.trim().toLowerCase().split(regex_whitespaces); const tokens = autocomplete.trim().toLowerCase().split(regex_whitespaces);
if (typeof tokens[0] === 'string' && tokens[0].startsWith('section-')) { if (typeof tokens[0] === 'string' && tokens[0].startsWith('section-')) {
tokens.shift(); tokens.shift();
} }
if (address_type_tokens.has(tokens[0])) { if (address_type_tokens.has(tokens[0])) {
tokens.shift(); tokens.shift();
} }
if (autofill_field_name_tokens.has(tokens[0])) { if (autofill_field_name_tokens.has(tokens[0])) {
tokens.shift(); tokens.shift();
} else { }
else {
if (contact_type_tokens.has(tokens[0])) { if (contact_type_tokens.has(tokens[0])) {
tokens.shift(); tokens.shift();
} }
if (autofill_contact_field_name_tokens.has(tokens[0])) { if (autofill_contact_field_name_tokens.has(tokens[0])) {
tokens.shift(); tokens.shift();
} else { }
else {
return false; return false;
} }
} }
if (tokens[0] === 'webauthn') { if (tokens[0] === 'webauthn') {
tokens.shift(); tokens.shift();
} }
return tokens.length === 0; return tokens.length === 0;
} }

Loading…
Cancel
Save