|
|
|
@ -4,52 +4,61 @@ import { regex_whitespaces } from '../../utils/patterns.js';
|
|
|
|
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(
|
|
|
|
.filter((name) => {
|
|
|
|
non_abstract_roles
|
|
|
|
const role = roles_map.get(name);
|
|
|
|
.filter((name) => {
|
|
|
|
return (
|
|
|
|
const role = roles_map.get(name);
|
|
|
|
// 'toolbar' does not descend from widget, but it does support
|
|
|
|
return (
|
|
|
|
// aria-activedescendant, thus in practice we treat it as a widget.
|
|
|
|
// 'toolbar' does not descend from widget, but it does support
|
|
|
|
// focusable tabpanel elements are recommended if any panels in a set contain content where the first element in the panel is not focusable.
|
|
|
|
// aria-activedescendant, thus in practice we treat it as a widget.
|
|
|
|
// 'generic' is meant to have no semantic meaning.
|
|
|
|
// focusable tabpanel elements are recommended if any panels in a set contain content where the first element in the panel is not focusable.
|
|
|
|
!['toolbar', 'tabpanel', 'generic'].includes(name) &&
|
|
|
|
// 'generic' is meant to have no semantic meaning.
|
|
|
|
!role.superClass.some((classes) => classes.includes('widget')));
|
|
|
|
!['toolbar', 'tabpanel', 'generic'].includes(name) &&
|
|
|
|
})
|
|
|
|
!role.superClass.some((classes) => classes.includes('widget'))
|
|
|
|
.concat(
|
|
|
|
);
|
|
|
|
// The `progressbar` is descended from `widget`, but in practice, its
|
|
|
|
})
|
|
|
|
// value is always `readonly`, so we treat it as a non-interactive role.
|
|
|
|
.concat(
|
|
|
|
'progressbar'));
|
|
|
|
// The `progressbar` is descended from `widget`, but in practice, its
|
|
|
|
const interactive_roles = new Set(non_abstract_roles.filter((name) => !non_interactive_roles.has(name) &&
|
|
|
|
// value is always `readonly`, so we treat it as a non-interactive role.
|
|
|
|
// 'generic' is meant to have no semantic meaning.
|
|
|
|
'progressbar'
|
|
|
|
name !== 'generic'));
|
|
|
|
)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
const interactive_roles = new Set(
|
|
|
|
|
|
|
|
non_abstract_roles.filter(
|
|
|
|
|
|
|
|
(name) =>
|
|
|
|
|
|
|
|
!non_interactive_roles.has(name) &&
|
|
|
|
|
|
|
|
// 'generic' is meant to have no semantic meaning.
|
|
|
|
|
|
|
|
name !== 'generic'
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @param {ARIARoleDefinitionKey} role
|
|
|
|
* @param {ARIARoleDefinitionKey} role
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function is_non_interactive_roles(role) {
|
|
|
|
export function is_non_interactive_roles(role) {
|
|
|
|
return non_interactive_roles.has(role);
|
|
|
|
return non_interactive_roles.has(role);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @param {ARIARoleDefinitionKey} role
|
|
|
|
* @param {ARIARoleDefinitionKey} role
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function is_interactive_roles(role) {
|
|
|
|
export function is_interactive_roles(role) {
|
|
|
|
return interactive_roles.has(role);
|
|
|
|
return interactive_roles.has(role);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @param {ARIARoleDefinitionKey} role
|
|
|
|
* @param {ARIARoleDefinitionKey} role
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function is_abstract_role(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']);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @param {ARIARoleDefinitionKey} role
|
|
|
|
* @param {ARIARoleDefinitionKey} role
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function is_presentation_role(role) {
|
|
|
|
export function is_presentation_role(role) {
|
|
|
|
return presentation_roles.has(role);
|
|
|
|
return presentation_roles.has(role);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
@ -57,80 +66,84 @@ export function is_presentation_role(role) {
|
|
|
|
* @param {Map<string, Attribute>} attribute_map
|
|
|
|
* @param {Map<string, Attribute>} attribute_map
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function is_hidden_from_screen_reader(tag_name, 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)
|
|
|
|
if (!aria_hidden) return false;
|
|
|
|
return false;
|
|
|
|
if (!aria_hidden.is_static) return true;
|
|
|
|
if (!aria_hidden.is_static)
|
|
|
|
const aria_hidden_value = aria_hidden.get_static_value();
|
|
|
|
return true;
|
|
|
|
return aria_hidden_value === true || aria_hidden_value === 'true';
|
|
|
|
const aria_hidden_value = aria_hidden.get_static_value();
|
|
|
|
|
|
|
|
return aria_hidden_value === true || aria_hidden_value === 'true';
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @param {Map<string, Attribute>} attribute_map
|
|
|
|
* @param {Map<string, Attribute>} attribute_map
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function has_disabled_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();
|
|
|
|
if (aria_disabled_attr_value === true) {
|
|
|
|
if (aria_disabled_attr_value === true) {
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @type {ARIARoleRelationConcept[]}
|
|
|
|
* @type {ARIARoleRelationConcept[]}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
const non_interactive_element_role_schemas = [];
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @type {ARIARoleRelationConcept[]}
|
|
|
|
* @type {ARIARoleRelationConcept[]}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
const interactive_element_role_schemas = [];
|
|
|
|
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 interactive_ax_objects = new Set(
|
|
|
|
const non_interactive_ax_objects = new Set([...AXObjects.keys()].filter((name) => ['windows', 'structure'].includes(AXObjects.get(name).type)));
|
|
|
|
[...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)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @type {ARIARoleRelationConcept[]}
|
|
|
|
* @type {ARIARoleRelationConcept[]}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
const interactive_element_ax_object_schemas = [];
|
|
|
|
const interactive_element_ax_object_schemas = [];
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @type {ARIARoleRelationConcept[]}
|
|
|
|
* @type {ARIARoleRelationConcept[]}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
const non_interactive_element_ax_object_schemas = [];
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
@ -139,25 +152,22 @@ elementAXObjects.entries().forEach(([schema, ax_object]) => {
|
|
|
|
* @param {Map<string, Attribute>} attribute_map
|
|
|
|
* @param {Map<string, Attribute>} attribute_map
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
function match_schema(schema, tag_name, attribute_map) {
|
|
|
|
function match_schema(schema, tag_name, attribute_map) {
|
|
|
|
if (schema.name !== tag_name)
|
|
|
|
if (schema.name !== tag_name) return false;
|
|
|
|
return false;
|
|
|
|
if (!schema.attributes) return true;
|
|
|
|
if (!schema.attributes)
|
|
|
|
return schema.attributes.every((schema_attribute) => {
|
|
|
|
return true;
|
|
|
|
const attribute = attribute_map.get(schema_attribute.name);
|
|
|
|
return schema.attributes.every((schema_attribute) => {
|
|
|
|
if (!attribute) return false;
|
|
|
|
const attribute = attribute_map.get(schema_attribute.name);
|
|
|
|
if (schema_attribute.value && schema_attribute.value !== attribute.get_static_value()) {
|
|
|
|
if (!attribute)
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (schema_attribute.value && schema_attribute.value !== attribute.get_static_value()) {
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
export var ElementInteractivity;
|
|
|
|
export var ElementInteractivity;
|
|
|
|
(function (ElementInteractivity) {
|
|
|
|
(function (ElementInteractivity) {
|
|
|
|
ElementInteractivity["Interactive"] = "interactive";
|
|
|
|
ElementInteractivity['Interactive'] = 'interactive';
|
|
|
|
ElementInteractivity["NonInteractive"] = "non-interactive";
|
|
|
|
ElementInteractivity['NonInteractive'] = 'non-interactive';
|
|
|
|
ElementInteractivity["Static"] = "static";
|
|
|
|
ElementInteractivity['Static'] = 'static';
|
|
|
|
})(ElementInteractivity || (ElementInteractivity = {}));
|
|
|
|
})(ElementInteractivity || (ElementInteractivity = {}));
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
@ -166,20 +176,34 @@ export var ElementInteractivity;
|
|
|
|
* @returns {import("C:/repos/svelte/svelte/a11y.ts-to-jsdoc").ElementInteractivity}
|
|
|
|
* @returns {import("C:/repos/svelte/svelte/a11y.ts-to-jsdoc").ElementInteractivity}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function element_interactivity(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))) {
|
|
|
|
if (
|
|
|
|
return ElementInteractivity.Interactive;
|
|
|
|
interactive_element_role_schemas.some((schema) => match_schema(schema, tag_name, attribute_map))
|
|
|
|
}
|
|
|
|
) {
|
|
|
|
if (tag_name !== 'header' &&
|
|
|
|
return ElementInteractivity.Interactive;
|
|
|
|
non_interactive_element_role_schemas.some((schema) => match_schema(schema, tag_name, attribute_map))) {
|
|
|
|
}
|
|
|
|
return ElementInteractivity.NonInteractive;
|
|
|
|
if (
|
|
|
|
}
|
|
|
|
tag_name !== 'header' &&
|
|
|
|
if (interactive_element_ax_object_schemas.some((schema) => match_schema(schema, tag_name, attribute_map))) {
|
|
|
|
non_interactive_element_role_schemas.some((schema) =>
|
|
|
|
return ElementInteractivity.Interactive;
|
|
|
|
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;
|
|
|
|
if (
|
|
|
|
|
|
|
|
interactive_element_ax_object_schemas.some((schema) =>
|
|
|
|
|
|
|
|
match_schema(schema, tag_name, attribute_map)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
return ElementInteractivity.Interactive;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
|
|
non_interactive_element_ax_object_schemas.some((schema) =>
|
|
|
|
|
|
|
|
match_schema(schema, tag_name, attribute_map)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
return ElementInteractivity.NonInteractive;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return ElementInteractivity.Static;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
@ -188,7 +212,7 @@ export function element_interactivity(tag_name, attribute_map) {
|
|
|
|
* @returns {boolean}
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function is_interactive_element(tag_name, attribute_map) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
@ -197,7 +221,7 @@ export function is_interactive_element(tag_name, attribute_map) {
|
|
|
|
* @returns {boolean}
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function is_non_interactive_element(tag_name, attribute_map) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
@ -206,7 +230,7 @@ export function is_non_interactive_element(tag_name, attribute_map) {
|
|
|
|
* @returns {boolean}
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function is_static_element(tag_name, attribute_map) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
@ -215,127 +239,125 @@ export function is_static_element(tag_name, attribute_map) {
|
|
|
|
* @param {Map<string, Attribute>} attribute_map
|
|
|
|
* @param {Map<string, Attribute>} attribute_map
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function is_semantic_role_element(role, tag_name, 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 (schema.name === tag_name &&
|
|
|
|
if (
|
|
|
|
(!schema.attributes ||
|
|
|
|
schema.name === tag_name &&
|
|
|
|
schema.attributes.every((attr) => attribute_map.has(attr.name) &&
|
|
|
|
(!schema.attributes ||
|
|
|
|
attribute_map.get(attr.name).get_static_value() === attr.value))) {
|
|
|
|
schema.attributes.every(
|
|
|
|
for (const name of ax_object) {
|
|
|
|
(attr) =>
|
|
|
|
const roles = AXObjectRoles.get(name);
|
|
|
|
attribute_map.has(attr.name) &&
|
|
|
|
if (roles) {
|
|
|
|
attribute_map.get(attr.name).get_static_value() === attr.value
|
|
|
|
for (const { name } of roles) {
|
|
|
|
))
|
|
|
|
if (name === role) {
|
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
for (const name of ax_object) {
|
|
|
|
}
|
|
|
|
const roles = AXObjectRoles.get(name);
|
|
|
|
}
|
|
|
|
if (roles) {
|
|
|
|
}
|
|
|
|
for (const { name } of roles) {
|
|
|
|
}
|
|
|
|
if (name === role) {
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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([
|
|
|
|
'',
|
|
|
|
'',
|
|
|
|
'on',
|
|
|
|
'on',
|
|
|
|
'off',
|
|
|
|
'off',
|
|
|
|
'name',
|
|
|
|
'name',
|
|
|
|
'honorific-prefix',
|
|
|
|
'honorific-prefix',
|
|
|
|
'given-name',
|
|
|
|
'given-name',
|
|
|
|
'additional-name',
|
|
|
|
'additional-name',
|
|
|
|
'family-name',
|
|
|
|
'family-name',
|
|
|
|
'honorific-suffix',
|
|
|
|
'honorific-suffix',
|
|
|
|
'nickname',
|
|
|
|
'nickname',
|
|
|
|
'username',
|
|
|
|
'username',
|
|
|
|
'new-password',
|
|
|
|
'new-password',
|
|
|
|
'current-password',
|
|
|
|
'current-password',
|
|
|
|
'one-time-code',
|
|
|
|
'one-time-code',
|
|
|
|
'organization-title',
|
|
|
|
'organization-title',
|
|
|
|
'organization',
|
|
|
|
'organization',
|
|
|
|
'street-address',
|
|
|
|
'street-address',
|
|
|
|
'address-line1',
|
|
|
|
'address-line1',
|
|
|
|
'address-line2',
|
|
|
|
'address-line2',
|
|
|
|
'address-line3',
|
|
|
|
'address-line3',
|
|
|
|
'address-level4',
|
|
|
|
'address-level4',
|
|
|
|
'address-level3',
|
|
|
|
'address-level3',
|
|
|
|
'address-level2',
|
|
|
|
'address-level2',
|
|
|
|
'address-level1',
|
|
|
|
'address-level1',
|
|
|
|
'country',
|
|
|
|
'country',
|
|
|
|
'country-name',
|
|
|
|
'country-name',
|
|
|
|
'postal-code',
|
|
|
|
'postal-code',
|
|
|
|
'cc-name',
|
|
|
|
'cc-name',
|
|
|
|
'cc-given-name',
|
|
|
|
'cc-given-name',
|
|
|
|
'cc-additional-name',
|
|
|
|
'cc-additional-name',
|
|
|
|
'cc-family-name',
|
|
|
|
'cc-family-name',
|
|
|
|
'cc-number',
|
|
|
|
'cc-number',
|
|
|
|
'cc-exp',
|
|
|
|
'cc-exp',
|
|
|
|
'cc-exp-month',
|
|
|
|
'cc-exp-month',
|
|
|
|
'cc-exp-year',
|
|
|
|
'cc-exp-year',
|
|
|
|
'cc-csc',
|
|
|
|
'cc-csc',
|
|
|
|
'cc-type',
|
|
|
|
'cc-type',
|
|
|
|
'transaction-currency',
|
|
|
|
'transaction-currency',
|
|
|
|
'transaction-amount',
|
|
|
|
'transaction-amount',
|
|
|
|
'language',
|
|
|
|
'language',
|
|
|
|
'bday',
|
|
|
|
'bday',
|
|
|
|
'bday-day',
|
|
|
|
'bday-day',
|
|
|
|
'bday-month',
|
|
|
|
'bday-month',
|
|
|
|
'bday-year',
|
|
|
|
'bday-year',
|
|
|
|
'sex',
|
|
|
|
'sex',
|
|
|
|
'url',
|
|
|
|
'url',
|
|
|
|
'photo'
|
|
|
|
'photo'
|
|
|
|
]);
|
|
|
|
]);
|
|
|
|
const contact_type_tokens = new Set(['home', 'work', 'mobile', 'fax', 'pager']);
|
|
|
|
const contact_type_tokens = new Set(['home', 'work', 'mobile', 'fax', 'pager']);
|
|
|
|
const autofill_contact_field_name_tokens = new Set([
|
|
|
|
const autofill_contact_field_name_tokens = new Set([
|
|
|
|
'tel',
|
|
|
|
'tel',
|
|
|
|
'tel-country-code',
|
|
|
|
'tel-country-code',
|
|
|
|
'tel-national',
|
|
|
|
'tel-national',
|
|
|
|
'tel-area-code',
|
|
|
|
'tel-area-code',
|
|
|
|
'tel-local',
|
|
|
|
'tel-local',
|
|
|
|
'tel-local-prefix',
|
|
|
|
'tel-local-prefix',
|
|
|
|
'tel-local-suffix',
|
|
|
|
'tel-local-suffix',
|
|
|
|
'tel-extension',
|
|
|
|
'tel-extension',
|
|
|
|
'email',
|
|
|
|
'email',
|
|
|
|
'impp'
|
|
|
|
'impp'
|
|
|
|
]);
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* @param {null | true | string} autocomplete
|
|
|
|
* @param {null | true | string} autocomplete
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
export function is_valid_autocomplete(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 {
|
|
|
|
}
|
|
|
|
if (contact_type_tokens.has(tokens[0])) {
|
|
|
|
else {
|
|
|
|
tokens.shift();
|
|
|
|
if (contact_type_tokens.has(tokens[0])) {
|
|
|
|
}
|
|
|
|
tokens.shift();
|
|
|
|
if (autofill_contact_field_name_tokens.has(tokens[0])) {
|
|
|
|
}
|
|
|
|
tokens.shift();
|
|
|
|
if (autofill_contact_field_name_tokens.has(tokens[0])) {
|
|
|
|
} else {
|
|
|
|
tokens.shift();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
if (tokens[0] === 'webauthn') {
|
|
|
|
}
|
|
|
|
tokens.shift();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (tokens[0] === 'webauthn') {
|
|
|
|
return tokens.length === 0;
|
|
|
|
tokens.shift();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return tokens.length === 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|