Unify role type and element interactivity API

pull/8167/head
Nguyen Tran 4 years ago
parent 6c6d1d1113
commit 14a3131e35

@ -24,14 +24,13 @@ import { Literal } from 'estree';
import compiler_warnings from '../compiler_warnings'; import compiler_warnings from '../compiler_warnings';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query'; import { ARIARoleDefintionKey, roles, aria, ARIAPropertyDefinition, ARIAProperty } from 'aria-query';
import { is_interactive_element, is_non_interactive_element, is_non_interactive_roles, is_presentation_role, is_interactive_roles, is_hidden_from_screen_reader, is_semantic_role_element } from '../utils/a11y'; import { role_type, RoleType, element_interactivity, ElementInteractivity, is_presentation_role, is_hidden_from_screen_reader, is_semantic_role_element } from '../utils/a11y';
const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' '); const aria_attributes = 'activedescendant atomic autocomplete busy checked colcount colindex colspan controls current describedby description details disabled dropeffect errormessage expanded flowto grabbed haspopup hidden invalid keyshortcuts label labelledby level live modal multiline multiselectable orientation owns placeholder posinset pressed readonly relevant required roledescription rowcount rowindex rowspan selected setsize sort valuemax valuemin valuenow valuetext'.split(' ');
const aria_attribute_set = new Set(aria_attributes); const aria_attribute_set = new Set(aria_attributes);
const aria_roles = roles.keys(); const aria_roles = roles.keys();
const aria_role_set = new Set(aria_roles); const aria_role_set = new Set(aria_roles);
const aria_role_abstract_set = new Set(roles.keys().filter(role => roles.get(role).abstract));
const a11y_required_attributes = { const a11y_required_attributes = {
a: ['href'], a: ['href'],
@ -497,7 +496,7 @@ export default class Element extends Node {
if (typeof value === 'string') { if (typeof value === 'string') {
value.split(regex_any_repeated_whitespaces).forEach((current_role: ARIARoleDefintionKey) => { value.split(regex_any_repeated_whitespaces).forEach((current_role: ARIARoleDefintionKey) => {
if (current_role && aria_role_abstract_set.has(current_role)) { if (current_role && role_type(current_role) === RoleType.Abstract) {
component.warn(attribute, compiler_warnings.a11y_no_abstract_role(current_role)); component.warn(attribute, compiler_warnings.a11y_no_abstract_role(current_role));
} else if (current_role && !aria_role_set.has(current_role)) { } else if (current_role && !aria_role_set.has(current_role)) {
const match = fuzzymatch(current_role, aria_roles); const match = fuzzymatch(current_role, aria_roles);
@ -534,12 +533,14 @@ export default class Element extends Node {
} }
// no-interactive-element-to-noninteractive-role // no-interactive-element-to-noninteractive-role
if (is_interactive_element(this.name, attribute_map) && (is_non_interactive_roles(current_role) || is_presentation_role(current_role))) { if (element_interactivity(this.name, attribute_map) === ElementInteractivity.Interactive
&& (role_type(current_role) === RoleType.NonInteractive || is_presentation_role(current_role))) {
component.warn(this, compiler_warnings.a11y_no_interactive_element_to_noninteractive_role(current_role, this.name)); component.warn(this, compiler_warnings.a11y_no_interactive_element_to_noninteractive_role(current_role, this.name));
} }
// no-noninteractive-element-to-interactive-role // no-noninteractive-element-to-interactive-role
if (is_non_interactive_element(this.name, attribute_map) && is_interactive_roles(current_role)) { if (element_interactivity(this.name, attribute_map) === ElementInteractivity.NonInteractive
&& role_type(current_role) === RoleType.Interactive) {
component.warn(this, compiler_warnings.a11y_no_noninteractive_element_to_interactive_role(current_role, this.name)); component.warn(this, compiler_warnings.a11y_no_noninteractive_element_to_interactive_role(current_role, this.name));
} }
}); });
@ -579,7 +580,7 @@ export default class Element extends Node {
if ( if (
!is_hidden_from_screen_reader(this.name, attribute_map) && !is_hidden_from_screen_reader(this.name, attribute_map) &&
(!role || is_non_presentation_role) && (!role || is_non_presentation_role) &&
!is_interactive_element(this.name, attribute_map) && element_interactivity(this.name, attribute_map) !== ElementInteractivity.Interactive &&
!this.attributes.find(attr => attr.is_spread) !this.attributes.find(attr => attr.is_spread)
) { ) {
const has_key_event = const has_key_event =
@ -597,7 +598,10 @@ export default class Element extends Node {
} }
// no-noninteractive-tabindex // no-noninteractive-tabindex
if (!is_interactive_element(this.name, attribute_map) && !is_interactive_roles(attribute_map.get('role')?.get_static_value() as ARIARoleDefintionKey)) { if (element_interactivity(this.name, attribute_map) !== ElementInteractivity.Interactive
&& role_type(
attribute_map.get('role')?.get_static_value() as ARIARoleDefintionKey
) !== RoleType.Interactive) {
const tab_index = attribute_map.get('tabindex'); const tab_index = attribute_map.get('tabindex');
if (tab_index && (!tab_index.is_static || Number(tab_index.get_static_value()) >= 0)) { if (tab_index && (!tab_index.is_static || Number(tab_index.get_static_value()) >= 0)) {
component.warn(this, compiler_warnings.a11y_no_noninteractive_tabindex); component.warn(this, compiler_warnings.a11y_no_noninteractive_tabindex);

@ -7,7 +7,9 @@ import {
import { AXObjects, AXObjectRoles, elementAXObjects } from 'axobject-query'; import { AXObjects, AXObjectRoles, elementAXObjects } from 'axobject-query';
import Attribute from '../nodes/Attribute'; import Attribute from '../nodes/Attribute';
const non_abstract_roles = [...roles_map.keys()].filter((name) => !roles_map.get(name).abstract && name !== 'generic'); const aria_roles = roles_map.keys();
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_interactive_roles = new Set( const non_interactive_roles = new Set(
non_abstract_roles non_abstract_roles
@ -32,12 +34,23 @@ const interactive_roles = new Set(
non_abstract_roles.filter((name) => !non_interactive_roles.has(name)) non_abstract_roles.filter((name) => !non_interactive_roles.has(name))
); );
export function is_non_interactive_roles(role: ARIARoleDefintionKey) { export enum RoleType {
return non_interactive_roles.has(role); Interactive = 'interactive',
NonInteractive = 'non-interactive',
Abstract = 'abstract',
Invalid = 'invalid',
} }
export function is_interactive_roles(role: ARIARoleDefintionKey) { export function role_type(role: ARIARoleDefintionKey): RoleType {
return interactive_roles.has(role); if (interactive_roles.has(role)) {
return RoleType.Interactive;
} else if (non_interactive_roles.has(role)) {
return RoleType.NonInteractive;
} else if (abstract_roles.has(role)) {
return RoleType.Abstract;
} else {
return RoleType.Invalid;
}
} }
const presentation_roles = new Set(['presentation', 'none']); const presentation_roles = new Set(['presentation', 'none']);
@ -65,7 +78,7 @@ export function is_hidden_from_screen_reader(tag_name: string, attribute_map: Ma
const non_interactive_element_role_schemas: ARIARoleRelationConcept[] = []; const non_interactive_element_role_schemas: ARIARoleRelationConcept[] = [];
elementRoles.entries().forEach(([schema, roles]) => { elementRoles.entries().forEach(([schema, roles]) => {
if ([...roles].every((role) => 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);
} }
}); });
@ -102,7 +115,6 @@ elementAXObjects.entries().forEach(([schema, ax_object]) => {
} }
}); });
function match_schema( function match_schema(
schema: ARIARoleRelationConcept, schema: ARIARoleRelationConcept,
tag_name: string, tag_name: string,
@ -123,24 +135,31 @@ function match_schema(
}); });
} }
export function is_interactive_element( export enum ElementInteractivity {
Interactive = 'interactive',
NonInteractive = 'non-interactive',
Static = 'static',
}
export function element_interactivity(
tag_name: string, tag_name: string,
attribute_map: Map<string, Attribute> attribute_map: Map<string, Attribute>
): boolean { ): ElementInteractivity {
if ( if (
interactive_element_role_schemas.some((schema) => interactive_element_role_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map) match_schema(schema, tag_name, attribute_map)
) )
) { ) {
return true; return ElementInteractivity.Interactive;
} }
if ( if (
tag_name !== 'header' &&
non_interactive_element_role_schemas.some((schema) => non_interactive_element_role_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map) match_schema(schema, tag_name, attribute_map)
) )
) { ) {
return false; return ElementInteractivity.NonInteractive;
} }
if ( if (
@ -148,34 +167,7 @@ export function is_interactive_element(
match_schema(schema, tag_name, attribute_map) match_schema(schema, tag_name, attribute_map)
) )
) { ) {
return true; return ElementInteractivity.Interactive;
}
return false;
}
export function is_non_interactive_element(
tag_name: string,
attribute_map: Map<string, Attribute>
): boolean {
if (tag_name === 'header') {
return false;
}
if (
non_interactive_element_role_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map)
)
) {
return true;
}
if (
interactive_element_role_schemas.some((schema) =>
match_schema(schema, tag_name, attribute_map)
)
) {
return false;
} }
if ( if (
@ -183,10 +175,10 @@ export function is_non_interactive_element(
match_schema(schema, tag_name, attribute_map) match_schema(schema, tag_name, attribute_map)
) )
) { ) {
return true; return ElementInteractivity.NonInteractive;
} }
return false; return ElementInteractivity.Static;
} }
export function is_semantic_role_element(role: ARIARoleDefintionKey, tag_name: string, attribute_map: Map<string, Attribute>) { export function is_semantic_role_element(role: ARIARoleDefintionKey, tag_name: string, attribute_map: Map<string, Attribute>) {

Loading…
Cancel
Save