@markuplint/ml-spec 4.10.1 → 5.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.ja.md +267 -0
- package/ARCHITECTURE.md +267 -0
- package/CHANGELOG.md +20 -2
- package/README.md +6 -188
- package/SKILL.md +116 -0
- package/docs/aria-algorithms.ja.md +798 -0
- package/docs/aria-algorithms.md +800 -0
- package/docs/html-algorithms.ja.md +469 -0
- package/docs/html-algorithms.md +469 -0
- package/docs/maintenance.ja.md +331 -0
- package/docs/maintenance.md +331 -0
- package/docs/spec-resolution.ja.md +568 -0
- package/docs/spec-resolution.md +580 -0
- package/docs/type-definitions.ja.md +565 -0
- package/docs/type-definitions.md +565 -0
- package/lib/algorithm/aria/accname/__tests__/test-helpers.d.ts +45 -0
- package/lib/algorithm/aria/accname/__tests__/test-helpers.js +120 -0
- package/lib/algorithm/aria/accname/aria-steps.d.ts +51 -0
- package/lib/algorithm/aria/accname/aria-steps.js +104 -0
- package/lib/algorithm/aria/accname/compute.d.ts +51 -0
- package/lib/algorithm/aria/accname/compute.js +101 -0
- package/lib/algorithm/aria/accname/element-names.d.ts +36 -0
- package/lib/algorithm/aria/accname/element-names.js +342 -0
- package/lib/algorithm/aria/accname/helpers.d.ts +98 -0
- package/lib/algorithm/aria/accname/helpers.js +330 -0
- package/lib/algorithm/aria/accname/index.d.ts +4 -0
- package/lib/algorithm/aria/accname/index.js +3 -0
- package/lib/algorithm/aria/accname/label-steps.d.ts +25 -0
- package/lib/algorithm/aria/accname/label-steps.js +66 -0
- package/lib/algorithm/aria/accname/svg-helpers.d.ts +17 -0
- package/lib/algorithm/aria/accname/svg-helpers.js +30 -0
- package/lib/algorithm/aria/accname/types.d.ts +70 -0
- package/lib/algorithm/aria/accname/types.js +2 -0
- package/lib/algorithm/aria/accname-computation.d.ts +19 -1
- package/lib/algorithm/aria/accname-computation.js +136 -6
- package/lib/algorithm/aria/aria-specs.d.ts +8 -0
- package/lib/algorithm/aria/aria-specs.js +7 -0
- package/lib/algorithm/aria/get-aria.d.ts +12 -0
- package/lib/algorithm/aria/get-aria.js +42 -4
- package/lib/algorithm/aria/get-computed-aria-props.d.ts +22 -0
- package/lib/algorithm/aria/get-computed-aria-props.js +10 -0
- package/lib/algorithm/aria/get-computed-role.d.ts +12 -0
- package/lib/algorithm/aria/get-computed-role.js +118 -26
- package/lib/algorithm/aria/get-explicit-role.d.ts +12 -0
- package/lib/algorithm/aria/get-explicit-role.js +12 -0
- package/lib/algorithm/aria/get-implicit-role.d.ts +18 -0
- package/lib/algorithm/aria/get-implicit-role.js +18 -0
- package/lib/algorithm/aria/get-non-presentational-ancestor.d.ts +12 -0
- package/lib/algorithm/aria/get-non-presentational-ancestor.js +14 -2
- package/lib/algorithm/aria/get-permitted-roles-spec.d.ts +8 -4
- package/lib/algorithm/aria/get-permitted-roles-spec.js +19 -6
- package/lib/algorithm/aria/get-permitted-roles.d.ts +9 -0
- package/lib/algorithm/aria/get-permitted-roles.js +9 -0
- package/lib/algorithm/aria/get-role-spec.d.ts +11 -0
- package/lib/algorithm/aria/get-role-spec.js +21 -3
- package/lib/algorithm/aria/has-required-owned-elements.d.ts +24 -0
- package/lib/algorithm/aria/has-required-owned-elements.js +40 -14
- package/lib/algorithm/aria/is-exposed.d.ts +7 -4
- package/lib/algorithm/aria/is-exposed.js +7 -4
- package/lib/algorithm/aria/is-presentational.d.ts +32 -0
- package/lib/algorithm/aria/is-presentational.js +39 -0
- package/lib/algorithm/aria/matches-context-role.d.ts +19 -0
- package/lib/algorithm/aria/matches-context-role.js +46 -2
- package/lib/algorithm/html/content-model-category-to-tag-names.d.ts +9 -0
- package/lib/algorithm/html/content-model-category-to-tag-names.js +10 -1
- package/lib/algorithm/html/get-content-model.d.ts +12 -1
- package/lib/algorithm/html/get-content-model.js +14 -6
- package/lib/algorithm/html/get-selectors-by-content-model-category.d.ts +8 -0
- package/lib/algorithm/html/get-selectors-by-content-model-category.js +8 -0
- package/lib/algorithm/html/is-nothing-content-model.d.ts +7 -0
- package/lib/algorithm/html/is-nothing-content-model.js +7 -0
- package/lib/algorithm/html/is-palpable-elements.d.ts +13 -0
- package/lib/algorithm/html/is-palpable-elements.js +13 -0
- package/lib/algorithm/html/is-void-element.d.ts +9 -0
- package/lib/algorithm/html/is-void-element.js +9 -0
- package/lib/algorithm/html/may-be-focusable.d.ts +10 -0
- package/lib/algorithm/html/may-be-focusable.js +10 -0
- package/lib/const/accname.d.ts +29 -0
- package/lib/const/accname.js +76 -0
- package/lib/const/dom.d.ts +8 -0
- package/lib/const/dom.js +8 -0
- package/lib/const/index.d.ts +2 -0
- package/lib/const/index.js +2 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +4 -0
- package/lib/types/index.d.ts +119 -0
- package/lib/utils/aria-version.d.ts +6 -0
- package/lib/utils/aria-version.js +6 -0
- package/lib/utils/directive-resolver.d.ts +23 -0
- package/lib/utils/directive-resolver.js +50 -0
- package/lib/utils/get-attr-specs-spec.d.ts +18 -0
- package/lib/utils/get-attr-specs-spec.js +22 -3
- package/lib/utils/get-attr-specs.d.ts +9 -0
- package/lib/utils/get-attr-specs.js +9 -0
- package/lib/utils/get-ns.d.ts +7 -0
- package/lib/utils/get-ns.js +7 -0
- package/lib/utils/get-spec-by-tag-name.d.ts +12 -1
- package/lib/utils/get-spec-by-tag-name.js +11 -0
- package/lib/utils/get-spec.d.ts +11 -1
- package/lib/utils/get-spec.js +10 -0
- package/lib/utils/merge-array.d.ts +10 -0
- package/lib/utils/merge-array.js +10 -0
- package/lib/utils/resolve-namespace.d.ts +13 -0
- package/lib/utils/resolve-namespace.js +10 -0
- package/lib/utils/resolve-version.d.ts +11 -0
- package/lib/utils/resolve-version.js +11 -0
- package/lib/utils/schema-to-spec.d.ts +7 -2
- package/lib/utils/schema-to-spec.js +20 -6
- package/lib/utils/validate-aria-version.d.ts +7 -0
- package/lib/utils/validate-aria-version.js +7 -0
- package/package.json +9 -7
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isTransparentForOwnership } from './is-presentational.js';
|
|
2
2
|
import { getComputedRole } from './get-computed-role.js';
|
|
3
|
+
/**
|
|
4
|
+
* Traverses the parent element chain to find the nearest ancestor with a
|
|
5
|
+
* non-presentational role, skipping elements that are transparent for
|
|
6
|
+
* ownership traversal (via `isTransparentForOwnership`).
|
|
7
|
+
*
|
|
8
|
+
* In ARIA 1.3, `generic` role elements are additionally transparent.
|
|
9
|
+
*
|
|
10
|
+
* @param el - The DOM element whose ancestors to traverse
|
|
11
|
+
* @param specs - The full markup language specification
|
|
12
|
+
* @param version - The ARIA specification version to use
|
|
13
|
+
* @returns The nearest non-presentational ancestor's `ComputedRole`, or `{ el: null, role: null }` if none exists
|
|
14
|
+
*/
|
|
3
15
|
export function getNonPresentationalAncestor(
|
|
4
16
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
5
17
|
el, specs, version) {
|
|
@@ -12,7 +24,7 @@ el, specs, version) {
|
|
|
12
24
|
const assumeSingleNode = version !== '1.1' && version !== '1.2';
|
|
13
25
|
while (ancestor) {
|
|
14
26
|
const ancestorRole = getComputedRole(specs, ancestor, version, assumeSingleNode);
|
|
15
|
-
if (!
|
|
27
|
+
if (!isTransparentForOwnership(ancestorRole.role?.name, version)) {
|
|
16
28
|
return ancestorRole;
|
|
17
29
|
}
|
|
18
30
|
ancestor = ancestor.parentElement;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { ARIAVersion, Matches, MLMLSpec } from '../../types/index.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Computes the list of permitted ARIA roles for an element at the spec level,
|
|
4
|
+
* operating on tag name and namespace rather than a DOM element.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
6
|
+
* @param specs - The full markup language specification
|
|
7
|
+
* @param localName - The element's local tag name
|
|
8
|
+
* @param namespace - The element's namespace URI
|
|
9
|
+
* @param version - The ARIA specification version to use
|
|
10
|
+
* @param matches - A function that tests CSS selector matches for conditional role resolution
|
|
11
|
+
* @returns A list of permitted roles, each with a `name` and optional `deprecated` flag
|
|
8
12
|
*/
|
|
9
13
|
export declare function getPermittedRoles(specs: MLMLSpec, localName: string, namespace: string | null, version: ARIAVersion, matches: Matches): readonly {
|
|
10
14
|
readonly name: string;
|
|
@@ -4,11 +4,15 @@ import { mergeArray } from '../../utils/merge-array.js';
|
|
|
4
4
|
import { ariaSpecs } from './aria-specs.js';
|
|
5
5
|
import { getARIA } from './get-aria.js';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Computes the list of permitted ARIA roles for an element at the spec level,
|
|
8
|
+
* operating on tag name and namespace rather than a DOM element.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
10
|
+
* @param specs - The full markup language specification
|
|
11
|
+
* @param localName - The element's local tag name
|
|
12
|
+
* @param namespace - The element's namespace URI
|
|
13
|
+
* @param version - The ARIA specification version to use
|
|
14
|
+
* @param matches - A function that tests CSS selector matches for conditional role resolution
|
|
15
|
+
* @returns A list of permitted roles, each with a `name` and optional `deprecated` flag
|
|
12
16
|
*/
|
|
13
17
|
export function getPermittedRoles(specs, localName, namespace, version, matches) {
|
|
14
18
|
const aria = getARIA(specs, localName, namespace, version, matches);
|
|
@@ -16,7 +20,7 @@ export function getPermittedRoles(specs, localName, namespace, version, matches)
|
|
|
16
20
|
return [];
|
|
17
21
|
}
|
|
18
22
|
const { implicitRole, permittedRoles } = aria;
|
|
19
|
-
const { roles, graphicsRoles } = ariaSpecs(specs, version);
|
|
23
|
+
const { roles, graphicsRoles, dpubRoles } = ariaSpecs(specs, version);
|
|
20
24
|
let permittedRoleList = [];
|
|
21
25
|
if (permittedRoles === true) {
|
|
22
26
|
permittedRoleList = mergeArray(permittedRoleList, roles
|
|
@@ -24,6 +28,11 @@ export function getPermittedRoles(specs, localName, namespace, version, matches)
|
|
|
24
28
|
.map(role => ({
|
|
25
29
|
name: role.name,
|
|
26
30
|
})));
|
|
31
|
+
permittedRoleList = mergeArray(permittedRoleList, dpubRoles
|
|
32
|
+
.filter(role => !role.isAbstract)
|
|
33
|
+
.map(role => ({
|
|
34
|
+
name: role.name,
|
|
35
|
+
})));
|
|
27
36
|
}
|
|
28
37
|
if (isAAMInfo(permittedRoles)) {
|
|
29
38
|
if (permittedRoles['core-aam']) {
|
|
@@ -54,7 +63,11 @@ export function getPermittedRoles(specs, localName, namespace, version, matches)
|
|
|
54
63
|
if (implicitRole === false) {
|
|
55
64
|
return permittedRoleList;
|
|
56
65
|
}
|
|
57
|
-
const implicitRoles = implicitRole === 'presentation' || implicitRole === 'none'
|
|
66
|
+
const implicitRoles = implicitRole === 'presentation' || implicitRole === 'none'
|
|
67
|
+
? ['none', 'presentation']
|
|
68
|
+
: version === '1.3' && (implicitRole === 'img' || implicitRole === 'image')
|
|
69
|
+
? ['image', 'img']
|
|
70
|
+
: [implicitRole];
|
|
58
71
|
return mergeArray(implicitRoles.map(r => ({ name: r })), permittedRoleList);
|
|
59
72
|
}
|
|
60
73
|
function isAAMInfo(permittedRoles) {
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { ARIAVersion, MLMLSpec } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Retrieves the list of permitted ARIA roles that may be explicitly assigned
|
|
4
|
+
* to an element, based on its tag name, namespace, and current attribute state.
|
|
5
|
+
*
|
|
6
|
+
* @param el - The DOM element to determine permitted roles for
|
|
7
|
+
* @param version - The ARIA specification version to use
|
|
8
|
+
* @param specs - The full markup language specification
|
|
9
|
+
* @returns The permitted roles specification, which may be a boolean, an array of role names, or an AAM reference
|
|
10
|
+
*/
|
|
2
11
|
export declare function getPermittedRoles(el: Element, version: ARIAVersion, specs: MLMLSpec): readonly {
|
|
3
12
|
readonly name: string;
|
|
4
13
|
readonly deprecated?: boolean;
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { getPermittedRoles as _getPermittedRoles } from './get-permitted-roles-spec.js';
|
|
2
|
+
/**
|
|
3
|
+
* Retrieves the list of permitted ARIA roles that may be explicitly assigned
|
|
4
|
+
* to an element, based on its tag name, namespace, and current attribute state.
|
|
5
|
+
*
|
|
6
|
+
* @param el - The DOM element to determine permitted roles for
|
|
7
|
+
* @param version - The ARIA specification version to use
|
|
8
|
+
* @param specs - The full markup language specification
|
|
9
|
+
* @returns The permitted roles specification, which may be a boolean, an array of role names, or an AAM reference
|
|
10
|
+
*/
|
|
2
11
|
export function getPermittedRoles(
|
|
3
12
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
4
13
|
el, version, specs) {
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { ARIAVersion, ARIARoleInSchema, MLMLSpec, ARIARole } from '../../types/index.js';
|
|
2
2
|
import type { NamespaceURI } from '@markuplint/ml-ast';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves the full ARIA role specification for a given role name, including
|
|
5
|
+
* its properties, requirements, and the complete chain of super-class roles.
|
|
6
|
+
* For SVG namespace elements, also searches graphics ARIA roles.
|
|
7
|
+
*
|
|
8
|
+
* @param specs - The full markup language specification
|
|
9
|
+
* @param roleName - The name of the ARIA role to look up
|
|
10
|
+
* @param namespace - The namespace URI of the element context
|
|
11
|
+
* @param version - The ARIA specification version to use
|
|
12
|
+
* @returns The role specification with super-class roles, or null if the role does not exist
|
|
13
|
+
*/
|
|
3
14
|
export declare function getRoleSpec(specs: MLMLSpec, roleName: string, namespace: NamespaceURI, version: ARIAVersion): (ARIARole & {
|
|
4
15
|
superClassRoles: ARIARoleInSchema[];
|
|
5
16
|
}) | null;
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import { ariaSpecs } from './aria-specs.js';
|
|
2
|
+
/**
|
|
3
|
+
* Retrieves the full ARIA role specification for a given role name, including
|
|
4
|
+
* its properties, requirements, and the complete chain of super-class roles.
|
|
5
|
+
* For SVG namespace elements, also searches graphics ARIA roles.
|
|
6
|
+
*
|
|
7
|
+
* @param specs - The full markup language specification
|
|
8
|
+
* @param roleName - The name of the ARIA role to look up
|
|
9
|
+
* @param namespace - The namespace URI of the element context
|
|
10
|
+
* @param version - The ARIA specification version to use
|
|
11
|
+
* @returns The role specification with super-class roles, or null if the role does not exist
|
|
12
|
+
*/
|
|
2
13
|
export function getRoleSpec(specs, roleName, namespace, version) {
|
|
3
14
|
const role = getRoleByName(specs, roleName, namespace, version);
|
|
4
15
|
if (!role) {
|
|
5
16
|
return null;
|
|
6
17
|
}
|
|
7
18
|
const superClassRoles = recursiveTraverseSuperClassRoles(specs, roleName, namespace, version);
|
|
19
|
+
const requiredAccessibilityParentRole = role.requiredContextRole ?? role.requiredAccessibilityParentRole ?? [];
|
|
20
|
+
const allowedAccessibilityChildRoles = role.requiredOwnedElements ?? role.allowedAccessibilityChildRoles ?? [];
|
|
8
21
|
return {
|
|
9
22
|
name: role.name,
|
|
10
23
|
isAbstract: !!role.isAbstract,
|
|
11
24
|
deprecated: !!role.deprecated,
|
|
12
|
-
|
|
13
|
-
|
|
25
|
+
requiredAccessibilityParentRole,
|
|
26
|
+
allowedAccessibilityChildRoles,
|
|
27
|
+
requiredContextRole: requiredAccessibilityParentRole,
|
|
28
|
+
requiredOwnedElements: allowedAccessibilityChildRoles,
|
|
14
29
|
accessibleNameRequired: !!role.accessibleNameRequired,
|
|
15
30
|
accessibleNameFromAuthor: !!role.accessibleNameFromAuthor,
|
|
16
31
|
accessibleNameFromContent: !!role.accessibleNameFromContent,
|
|
@@ -40,10 +55,13 @@ function getSuperClassRoles(specs, roleName, namespace, version) {
|
|
|
40
55
|
.filter((role) => !!role) ?? null);
|
|
41
56
|
}
|
|
42
57
|
function getRoleByName(specs, roleName, namespace, version) {
|
|
43
|
-
const { roles, graphicsRoles } = ariaSpecs(specs, version);
|
|
58
|
+
const { roles, graphicsRoles, dpubRoles } = ariaSpecs(specs, version);
|
|
44
59
|
let role = roles.find(r => r.name === roleName);
|
|
45
60
|
if (!role && namespace === 'http://www.w3.org/2000/svg') {
|
|
46
61
|
role = graphicsRoles.find(r => r.name === roleName);
|
|
47
62
|
}
|
|
63
|
+
if (!role) {
|
|
64
|
+
role = dpubRoles.find(r => r.name === roleName);
|
|
65
|
+
}
|
|
48
66
|
return role;
|
|
49
67
|
}
|
|
@@ -1,3 +1,27 @@
|
|
|
1
1
|
import type { ARIAVersion, ComputedRole, MLMLSpec } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Checks whether an element satisfies the "Allowed Accessibility Child Roles"
|
|
4
|
+
* constraint (called "Required Owned Elements" in ARIA 1.2)
|
|
5
|
+
* defined by its computed ARIA role. An element satisfies this constraint if it
|
|
6
|
+
* has `aria-owns`, or if any of its closest non-presentational descendants match
|
|
7
|
+
* the required owned element roles.
|
|
8
|
+
*
|
|
9
|
+
* @param el - The DOM element to check
|
|
10
|
+
* @param specs - The full markup language specification
|
|
11
|
+
* @param version - The ARIA specification version to use
|
|
12
|
+
* @returns `true` if the element has no required owned elements or the constraint is satisfied, `false` otherwise
|
|
13
|
+
*/
|
|
2
14
|
export declare function hasRequiredOwnedElement(el: Element, specs: MLMLSpec, version: ARIAVersion): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Determines whether an element with a given role matches a required owned element query.
|
|
17
|
+
* The query string may describe a single role or a parent-child relationship using `>` notation
|
|
18
|
+
* (e.g., `"listitem"` or `"group > listitem"`).
|
|
19
|
+
*
|
|
20
|
+
* @param el - The DOM element to test
|
|
21
|
+
* @param role - The computed role of the element
|
|
22
|
+
* @param query - The required owned element query string, optionally containing `>` for nested requirements
|
|
23
|
+
* @param specs - The full markup language specification
|
|
24
|
+
* @param version - The ARIA specification version to use
|
|
25
|
+
* @returns `true` if the element and its role match the required owned element query
|
|
26
|
+
*/
|
|
3
27
|
export declare function isRequiredOwnedElement(el: Element, role: ComputedRole['role'], query: string, specs: MLMLSpec, version: ARIAVersion): boolean;
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isTransparentForOwnership } from './is-presentational.js';
|
|
2
2
|
import { getComputedRole } from './get-computed-role.js';
|
|
3
3
|
import { getExplicitRole } from './get-explicit-role.js';
|
|
4
4
|
import { getImplicitRole } from './get-implicit-role.js';
|
|
5
|
+
/**
|
|
6
|
+
* Checks whether an element satisfies the "Allowed Accessibility Child Roles"
|
|
7
|
+
* constraint (called "Required Owned Elements" in ARIA 1.2)
|
|
8
|
+
* defined by its computed ARIA role. An element satisfies this constraint if it
|
|
9
|
+
* has `aria-owns`, or if any of its closest non-presentational descendants match
|
|
10
|
+
* the required owned element roles.
|
|
11
|
+
*
|
|
12
|
+
* @param el - The DOM element to check
|
|
13
|
+
* @param specs - The full markup language specification
|
|
14
|
+
* @param version - The ARIA specification version to use
|
|
15
|
+
* @returns `true` if the element has no required owned elements or the constraint is satisfied, `false` otherwise
|
|
16
|
+
*/
|
|
5
17
|
export function hasRequiredOwnedElement(
|
|
6
18
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
7
19
|
el, specs, version) {
|
|
@@ -18,10 +30,10 @@ el, specs, version) {
|
|
|
18
30
|
* Otherwise, traverses descendants to find owned elements.
|
|
19
31
|
*/
|
|
20
32
|
const computed = getComputedRole(specs, el, version);
|
|
21
|
-
if (!computed.role || computed.role.
|
|
33
|
+
if (!computed.role || computed.role.allowedAccessibilityChildRoles.length === 0) {
|
|
22
34
|
return true;
|
|
23
35
|
}
|
|
24
|
-
for (const expectRole of computed.role.
|
|
36
|
+
for (const expectRole of computed.role.allowedAccessibilityChildRoles) {
|
|
25
37
|
for (const owned of getClosestNonPresentationalDescendants(el, specs, version)) {
|
|
26
38
|
if (isRequiredOwnedElement(owned.el, owned.role, expectRole, specs, version)) {
|
|
27
39
|
return true;
|
|
@@ -30,6 +42,18 @@ el, specs, version) {
|
|
|
30
42
|
}
|
|
31
43
|
return false;
|
|
32
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Determines whether an element with a given role matches a required owned element query.
|
|
47
|
+
* The query string may describe a single role or a parent-child relationship using `>` notation
|
|
48
|
+
* (e.g., `"listitem"` or `"group > listitem"`).
|
|
49
|
+
*
|
|
50
|
+
* @param el - The DOM element to test
|
|
51
|
+
* @param role - The computed role of the element
|
|
52
|
+
* @param query - The required owned element query string, optionally containing `>` for nested requirements
|
|
53
|
+
* @param specs - The full markup language specification
|
|
54
|
+
* @param version - The ARIA specification version to use
|
|
55
|
+
* @returns `true` if the element and its role match the required owned element query
|
|
56
|
+
*/
|
|
33
57
|
export function isRequiredOwnedElement(
|
|
34
58
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
35
59
|
el, role, query, specs, version) {
|
|
@@ -49,18 +73,20 @@ el, role, query, specs, version) {
|
|
|
49
73
|
}
|
|
50
74
|
/**
|
|
51
75
|
* Gets the list of closest non-presentational descendants.
|
|
52
|
-
*
|
|
53
|
-
*
|
|
76
|
+
* This corresponds to the "Allowed Accessibility Child Roles" validation
|
|
77
|
+
* (called "Required Owned Elements" in ARIA 1.2).
|
|
54
78
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* @see https://github.com/w3c/aria/pull/1213
|
|
79
|
+
* In ARIA 1.1/1.2, the spec had not decided whether the owned element
|
|
80
|
+
* is a child or a descendant. This implementation interprets that as
|
|
81
|
+
* A CHILD, but `presentation`/`none` elements are treated as transparent.
|
|
59
82
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
83
|
+
* ARIA 1.3 formally resolves this with the definitions of
|
|
84
|
+
* "accessibility child" and "accessibility parent", and additionally
|
|
85
|
+
* makes `generic` elements transparent.
|
|
86
|
+
*
|
|
87
|
+
* @see https://w3c.github.io/aria/#mustContain
|
|
88
|
+
* @see https://github.com/w3c/aria/issues/1033
|
|
89
|
+
* @see https://github.com/w3c/aria/pull/1454
|
|
64
90
|
*/
|
|
65
91
|
function getClosestNonPresentationalDescendants(
|
|
66
92
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -69,7 +95,7 @@ el, specs, version) {
|
|
|
69
95
|
for (const child of el.children) {
|
|
70
96
|
const explicitRole = getExplicitRole(specs, child, version);
|
|
71
97
|
const computed = explicitRole.role ? explicitRole : getImplicitRole(specs, child, version);
|
|
72
|
-
if (
|
|
98
|
+
if (isTransparentForOwnership(computed.role?.name, version)) {
|
|
73
99
|
owned.push(...getClosestNonPresentationalDescendants(child, specs, version));
|
|
74
100
|
continue;
|
|
75
101
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { ARIAVersion, MLMLSpec } from '../../types/index.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Detects whether an element is included in (exposed to) the Accessibility Tree.
|
|
4
|
+
* Applies WAI-ARIA exclusion/inclusion rules, SVG rendering rules, and
|
|
5
|
+
* HTML metadata element filtering.
|
|
4
6
|
*
|
|
5
7
|
* @see https://www.w3.org/TR/wai-aria-1.2/#accessibility_tree
|
|
6
8
|
*
|
|
7
|
-
* @param
|
|
8
|
-
* @param
|
|
9
|
-
* @param version
|
|
9
|
+
* @param el - The DOM element to check for accessibility tree exposure
|
|
10
|
+
* @param specs - The full markup language specification
|
|
11
|
+
* @param version - The ARIA specification version to use
|
|
12
|
+
* @returns `true` if the element should be exposed in the accessibility tree
|
|
10
13
|
*/
|
|
11
14
|
export declare function isExposed(el: Element, specs: MLMLSpec, version: ARIAVersion): boolean;
|
|
@@ -4,13 +4,16 @@ import { isPresentational } from './is-presentational.js';
|
|
|
4
4
|
import { resolveNamespace } from '../../utils/resolve-namespace.js';
|
|
5
5
|
import { getComputedRole } from './get-computed-role.js';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Detects whether an element is included in (exposed to) the Accessibility Tree.
|
|
8
|
+
* Applies WAI-ARIA exclusion/inclusion rules, SVG rendering rules, and
|
|
9
|
+
* HTML metadata element filtering.
|
|
8
10
|
*
|
|
9
11
|
* @see https://www.w3.org/TR/wai-aria-1.2/#accessibility_tree
|
|
10
12
|
*
|
|
11
|
-
* @param
|
|
12
|
-
* @param
|
|
13
|
-
* @param version
|
|
13
|
+
* @param el - The DOM element to check for accessibility tree exposure
|
|
14
|
+
* @param specs - The full markup language specification
|
|
15
|
+
* @param version - The ARIA specification version to use
|
|
16
|
+
* @returns `true` if the element should be exposed in the accessibility tree
|
|
14
17
|
*/
|
|
15
18
|
export function isExposed(
|
|
16
19
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -1 +1,33 @@
|
|
|
1
|
+
import type { ARIAVersion } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Determines whether a given role name corresponds to a presentational role
|
|
4
|
+
* (`"presentation"` or `"none"`), which indicates the element should be
|
|
5
|
+
* excluded from the accessibility tree.
|
|
6
|
+
*
|
|
7
|
+
* @param roleName - The ARIA role name to check, or undefined
|
|
8
|
+
* @returns `true` if the role name is `"presentation"` or `"none"`, `false` otherwise
|
|
9
|
+
*/
|
|
1
10
|
export declare function isPresentational(roleName?: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Determines whether a given role is transparent for ownership traversal.
|
|
13
|
+
*
|
|
14
|
+
* **Version behavior:**
|
|
15
|
+
* - **ARIA 1.1/1.2:** Only `presentation`/`none` are transparent.
|
|
16
|
+
* This matches the pre-existing behavior of `getClosestNonPresentationalDescendants`
|
|
17
|
+
* and `getNonPresentationalAncestor`.
|
|
18
|
+
* - **ARIA 1.3:** `generic` is additionally transparent per the spec:
|
|
19
|
+
* "user agents MUST ignore any intervening elements with the role
|
|
20
|
+
* `generic` or `none`."
|
|
21
|
+
*
|
|
22
|
+
* **Note:** `matchesContextRole` calls this function unconditionally
|
|
23
|
+
* (without a version gate) so that `presentation`/`none` are always
|
|
24
|
+
* transparent, consistent with `getNonPresentationalAncestor`.
|
|
25
|
+
*
|
|
26
|
+
* @see https://w3c.github.io/aria/#mustContain — "Allowed Accessibility Child Roles" (called "Required Owned Elements" in ARIA 1.2)
|
|
27
|
+
* @see https://w3c.github.io/aria/#scope — "Required Accessibility Parent Role" (called "Required Context Role" in ARIA 1.2)
|
|
28
|
+
*
|
|
29
|
+
* @param roleName - The ARIA role name to check, or undefined
|
|
30
|
+
* @param version - The ARIA specification version
|
|
31
|
+
* @returns `true` if the role should be skipped during ownership traversal
|
|
32
|
+
*/
|
|
33
|
+
export declare function isTransparentForOwnership(roleName: string | undefined, version: ARIAVersion): boolean;
|
|
@@ -1,6 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines whether a given role name corresponds to a presentational role
|
|
3
|
+
* (`"presentation"` or `"none"`), which indicates the element should be
|
|
4
|
+
* excluded from the accessibility tree.
|
|
5
|
+
*
|
|
6
|
+
* @param roleName - The ARIA role name to check, or undefined
|
|
7
|
+
* @returns `true` if the role name is `"presentation"` or `"none"`, `false` otherwise
|
|
8
|
+
*/
|
|
1
9
|
export function isPresentational(roleName) {
|
|
2
10
|
if (!roleName) {
|
|
3
11
|
return false;
|
|
4
12
|
}
|
|
5
13
|
return ['presentation', 'none'].includes(roleName);
|
|
6
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Determines whether a given role is transparent for ownership traversal.
|
|
17
|
+
*
|
|
18
|
+
* **Version behavior:**
|
|
19
|
+
* - **ARIA 1.1/1.2:** Only `presentation`/`none` are transparent.
|
|
20
|
+
* This matches the pre-existing behavior of `getClosestNonPresentationalDescendants`
|
|
21
|
+
* and `getNonPresentationalAncestor`.
|
|
22
|
+
* - **ARIA 1.3:** `generic` is additionally transparent per the spec:
|
|
23
|
+
* "user agents MUST ignore any intervening elements with the role
|
|
24
|
+
* `generic` or `none`."
|
|
25
|
+
*
|
|
26
|
+
* **Note:** `matchesContextRole` calls this function unconditionally
|
|
27
|
+
* (without a version gate) so that `presentation`/`none` are always
|
|
28
|
+
* transparent, consistent with `getNonPresentationalAncestor`.
|
|
29
|
+
*
|
|
30
|
+
* @see https://w3c.github.io/aria/#mustContain — "Allowed Accessibility Child Roles" (called "Required Owned Elements" in ARIA 1.2)
|
|
31
|
+
* @see https://w3c.github.io/aria/#scope — "Required Accessibility Parent Role" (called "Required Context Role" in ARIA 1.2)
|
|
32
|
+
*
|
|
33
|
+
* @param roleName - The ARIA role name to check, or undefined
|
|
34
|
+
* @param version - The ARIA specification version
|
|
35
|
+
* @returns `true` if the role should be skipped during ownership traversal
|
|
36
|
+
*/
|
|
37
|
+
export function isTransparentForOwnership(roleName, version) {
|
|
38
|
+
if (isPresentational(roleName)) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (version === '1.3' && roleName === 'generic') {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
@@ -1,2 +1,21 @@
|
|
|
1
1
|
import type { ARIAVersion, MLMLSpec } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Checks whether an element's parent hierarchy satisfies the
|
|
4
|
+
* "Required Accessibility Parent Role" (called "Required Context Role" in ARIA 1.2).
|
|
5
|
+
* Each condition string may describe a chain of ancestor roles separated by
|
|
6
|
+
* ` > ` (e.g., `"list > group"`).
|
|
7
|
+
*
|
|
8
|
+
* TODO: This function only walks the DOM `parentElement` chain and does not
|
|
9
|
+
* consider `aria-owns` relationships. An element referenced by `aria-owns` on
|
|
10
|
+
* a remote ancestor should be treated as if it were an accessibility child of
|
|
11
|
+
* that ancestor. Implementing this requires a document-wide reverse lookup of
|
|
12
|
+
* `aria-owns` attributes, which is a separate architectural concern.
|
|
13
|
+
* See also: `has-required-owned-elements.ts` has a similar limitation.
|
|
14
|
+
*
|
|
15
|
+
* @param conditions - An array of required accessibility parent role condition strings to match against
|
|
16
|
+
* @param ownedEl - The owned DOM element whose parent context is being validated
|
|
17
|
+
* @param specs - The full markup language specification
|
|
18
|
+
* @param version - The ARIA specification version to use
|
|
19
|
+
* @returns `true` if any of the context role conditions are satisfied by the element's ancestors
|
|
20
|
+
*/
|
|
2
21
|
export declare function matchesContextRole(conditions: readonly string[], ownedEl: Element, specs: MLMLSpec, version: ARIAVersion): boolean;
|
|
@@ -1,4 +1,24 @@
|
|
|
1
|
+
import { isTransparentForOwnership } from './is-presentational.js';
|
|
1
2
|
import { getComputedRole } from './get-computed-role.js';
|
|
3
|
+
/**
|
|
4
|
+
* Checks whether an element's parent hierarchy satisfies the
|
|
5
|
+
* "Required Accessibility Parent Role" (called "Required Context Role" in ARIA 1.2).
|
|
6
|
+
* Each condition string may describe a chain of ancestor roles separated by
|
|
7
|
+
* ` > ` (e.g., `"list > group"`).
|
|
8
|
+
*
|
|
9
|
+
* TODO: This function only walks the DOM `parentElement` chain and does not
|
|
10
|
+
* consider `aria-owns` relationships. An element referenced by `aria-owns` on
|
|
11
|
+
* a remote ancestor should be treated as if it were an accessibility child of
|
|
12
|
+
* that ancestor. Implementing this requires a document-wide reverse lookup of
|
|
13
|
+
* `aria-owns` attributes, which is a separate architectural concern.
|
|
14
|
+
* See also: `has-required-owned-elements.ts` has a similar limitation.
|
|
15
|
+
*
|
|
16
|
+
* @param conditions - An array of required accessibility parent role condition strings to match against
|
|
17
|
+
* @param ownedEl - The owned DOM element whose parent context is being validated
|
|
18
|
+
* @param specs - The full markup language specification
|
|
19
|
+
* @param version - The ARIA specification version to use
|
|
20
|
+
* @returns `true` if any of the context role conditions are satisfied by the element's ancestors
|
|
21
|
+
*/
|
|
2
22
|
export function matchesContextRole(conditions,
|
|
3
23
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
4
24
|
ownedEl, specs, version) {
|
|
@@ -7,13 +27,37 @@ ownedEl, specs, version) {
|
|
|
7
27
|
function matchesCondition(condition,
|
|
8
28
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
9
29
|
parentEl, specs, version) {
|
|
10
|
-
const conditions = condition.split(/\s+>\s+/).
|
|
30
|
+
const conditions = condition.split(/\s+>\s+/).toReversed();
|
|
11
31
|
while (conditions.length > 0) {
|
|
12
32
|
if (!parentEl) {
|
|
13
33
|
return false;
|
|
14
34
|
}
|
|
15
|
-
const condition = conditions.shift();
|
|
16
35
|
const parentRole = getComputedRole(specs, parentEl, version, true).role;
|
|
36
|
+
/**
|
|
37
|
+
* ARIA 1.3 ("Required Accessibility Parent Role"): "To determine
|
|
38
|
+
* whether an element has a parent with the required role, user agents
|
|
39
|
+
* MUST ignore any elements with the role `generic` or `none`."
|
|
40
|
+
*
|
|
41
|
+
* `presentation`/`none` are transparent in all ARIA versions to stay
|
|
42
|
+
* consistent with `getNonPresentationalAncestor` (which already skips
|
|
43
|
+
* them unconditionally). `generic` is additionally transparent in 1.3+
|
|
44
|
+
* per the spec.
|
|
45
|
+
*
|
|
46
|
+
* While the ARIA 1.1/1.2 specification text does not explicitly define
|
|
47
|
+
* this transparency for the context role check, applying it is a
|
|
48
|
+
* pragmatic choice: `getNonPresentationalAncestor` and `classifyChildren`
|
|
49
|
+
* (for owned elements) already skip `presentation`/`none` in all versions.
|
|
50
|
+
* Treating the context role check differently would create inconsistent
|
|
51
|
+
* behavior where a structure is valid from the parent's perspective
|
|
52
|
+
* (owned elements) but invalid from the child's perspective (context role).
|
|
53
|
+
*
|
|
54
|
+
* @see https://w3c.github.io/aria/#scope
|
|
55
|
+
*/
|
|
56
|
+
if (isTransparentForOwnership(parentRole?.name, version)) {
|
|
57
|
+
parentEl = parentEl.parentElement;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const condition = conditions.shift();
|
|
17
61
|
if (condition !== parentRole?.name) {
|
|
18
62
|
return false;
|
|
19
63
|
}
|
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import type { MLMLSpec } from '../../types/index.js';
|
|
2
2
|
import type { Category } from '../../types/permitted-structures.js';
|
|
3
|
+
/**
|
|
4
|
+
* Converts a content model category (e.g., `#flow`, `#phrasing`) to a sorted,
|
|
5
|
+
* frozen array of HTML/SVG tag names that belong to that category.
|
|
6
|
+
* Results are cached for repeated lookups.
|
|
7
|
+
*
|
|
8
|
+
* @param contentModel - The content model category identifier
|
|
9
|
+
* @param def - The specification definitions containing content model mappings
|
|
10
|
+
* @returns A frozen, sorted array of tag name strings belonging to the category
|
|
11
|
+
*/
|
|
3
12
|
export declare function contentModelCategoryToTagNames(contentModel: Category, def: MLMLSpec['def']): ReadonlyArray<string>;
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
const cache = new Map();
|
|
2
|
+
/**
|
|
3
|
+
* Converts a content model category (e.g., `#flow`, `#phrasing`) to a sorted,
|
|
4
|
+
* frozen array of HTML/SVG tag names that belong to that category.
|
|
5
|
+
* Results are cached for repeated lookups.
|
|
6
|
+
*
|
|
7
|
+
* @param contentModel - The content model category identifier
|
|
8
|
+
* @param def - The specification definitions containing content model mappings
|
|
9
|
+
* @returns A frozen, sorted array of tag name strings belonging to the category
|
|
10
|
+
*/
|
|
2
11
|
export function contentModelCategoryToTagNames(contentModel, def) {
|
|
3
12
|
const cached = cache.get(contentModel);
|
|
4
13
|
if (cached) {
|
|
5
14
|
return cached;
|
|
6
15
|
}
|
|
7
16
|
const tags = def['#contentModels'][contentModel];
|
|
8
|
-
const sortedTag = Object.freeze(tags && Array.isArray(tags) ? tags.
|
|
17
|
+
const sortedTag = Object.freeze(tags && Array.isArray(tags) ? tags.toSorted() : []);
|
|
9
18
|
cache.set(contentModel, sortedTag);
|
|
10
19
|
return sortedTag;
|
|
11
20
|
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { ElementSpec } from '../../types/index.js';
|
|
2
|
+
import type { PermittedContentPattern } from '../../types/permitted-structures.js';
|
|
3
|
+
import type { ReadonlyDeep } from 'type-fest';
|
|
2
4
|
type Specs = readonly Pick<ElementSpec, 'name' | 'contentModel'>[];
|
|
3
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Retrieves the permitted content model for an element. Evaluates any conditional
|
|
7
|
+
* content models based on the element's current attributes (e.g., different content
|
|
8
|
+
* models for `<ol>` vs `<ol reversed>`). Results are cached per element.
|
|
9
|
+
*
|
|
10
|
+
* @param el - The DOM element to retrieve the content model for
|
|
11
|
+
* @param specs - The element specifications containing content model definitions
|
|
12
|
+
* @returns The permitted content patterns array, a boolean (true for any content, false for no content), or null if no spec exists
|
|
13
|
+
*/
|
|
14
|
+
export declare function getContentModel(el: Element, specs: Specs): ReadonlyDeep<PermittedContentPattern[]> | boolean | null;
|
|
4
15
|
export {};
|
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
import { getSpec } from '../../utils/get-spec.js';
|
|
2
|
-
const
|
|
2
|
+
const contentModelCache = new WeakMap();
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves the permitted content model for an element. Evaluates any conditional
|
|
5
|
+
* content models based on the element's current attributes (e.g., different content
|
|
6
|
+
* models for `<ol>` vs `<ol reversed>`). Results are cached per element.
|
|
7
|
+
*
|
|
8
|
+
* @param el - The DOM element to retrieve the content model for
|
|
9
|
+
* @param specs - The element specifications containing content model definitions
|
|
10
|
+
* @returns The permitted content patterns array, a boolean (true for any content, false for no content), or null if no spec exists
|
|
11
|
+
*/
|
|
3
12
|
export function getContentModel(
|
|
4
13
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
5
14
|
el, specs) {
|
|
6
|
-
const
|
|
7
|
-
const cached = cacheByEl.get(el);
|
|
15
|
+
const cached = contentModelCache.get(el);
|
|
8
16
|
if (cached !== undefined) {
|
|
9
17
|
return cached;
|
|
10
18
|
}
|
|
11
19
|
const spec = getSpec(el, specs);
|
|
12
20
|
if (!spec) {
|
|
13
|
-
|
|
21
|
+
contentModelCache.set(el, null);
|
|
14
22
|
return null;
|
|
15
23
|
}
|
|
16
24
|
const conditions = spec.contentModel.conditional ?? [];
|
|
17
25
|
for (const cond of conditions) {
|
|
18
26
|
if (el.matches(cond.condition)) {
|
|
19
|
-
|
|
27
|
+
contentModelCache.set(el, cond.contents);
|
|
20
28
|
return cond.contents;
|
|
21
29
|
}
|
|
22
30
|
}
|
|
23
|
-
|
|
31
|
+
contentModelCache.set(el, spec.contentModel.contents);
|
|
24
32
|
return spec.contentModel.contents;
|
|
25
33
|
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import type { MLMLSpec } from '../../types/index.js';
|
|
2
2
|
import type { Category } from '../../types/permitted-structures.js';
|
|
3
|
+
/**
|
|
4
|
+
* Retrieves the CSS selectors associated with a content model category from the spec definitions.
|
|
5
|
+
* These selectors can be used to match elements that belong to the given category.
|
|
6
|
+
*
|
|
7
|
+
* @param specs - The full markup language specification
|
|
8
|
+
* @param category - The content model category identifier (e.g., `#flow`, `#interactive`)
|
|
9
|
+
* @returns A readonly array of CSS selector strings for the category, or an empty array if the category is not defined
|
|
10
|
+
*/
|
|
3
11
|
export declare function getSelectorsByContentModelCategory(specs: MLMLSpec, category: Category): ReadonlyArray<string>;
|