@markuplint/ml-spec 4.10.2 → 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 +24 -10
- package/ARCHITECTURE.md +24 -10
- package/CHANGELOG.md +18 -0
- package/README.md +2 -2
- package/docs/aria-algorithms.ja.md +180 -33
- package/docs/aria-algorithms.md +183 -34
- package/docs/html-algorithms.ja.md +2 -2
- package/docs/html-algorithms.md +2 -2
- package/docs/maintenance.ja.md +13 -22
- package/docs/maintenance.md +13 -22
- package/docs/spec-resolution.ja.md +37 -9
- package/docs/spec-resolution.md +45 -16
- package/docs/type-definitions.ja.md +8 -4
- package/docs/type-definitions.md +8 -4
- 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 +14 -3
- package/lib/algorithm/aria/accname-computation.js +131 -8
- package/lib/algorithm/aria/aria-specs.d.ts +1 -0
- package/lib/algorithm/aria/get-aria.js +30 -4
- package/lib/algorithm/aria/get-computed-role.js +106 -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-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-role-spec.js +10 -3
- package/lib/algorithm/aria/has-required-owned-elements.d.ts +2 -1
- package/lib/algorithm/aria/has-required-owned-elements.js +18 -15
- package/lib/algorithm/aria/is-presentational.d.ts +24 -0
- package/lib/algorithm/aria/is-presentational.js +31 -0
- package/lib/algorithm/aria/matches-context-role.d.ts +12 -4
- package/lib/algorithm/aria/matches-context-role.js +39 -6
- package/lib/algorithm/html/content-model-category-to-tag-names.js +1 -1
- package/lib/algorithm/html/get-content-model.d.ts +4 -2
- package/lib/algorithm/html/get-content-model.js +6 -7
- 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 +65 -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.js +4 -3
- 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 +1 -1
- package/lib/utils/merge-array.d.ts +10 -0
- package/lib/utils/merge-array.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 +2 -0
- package/lib/utils/schema-to-spec.js +15 -4
- package/package.json +9 -7
|
@@ -1,9 +1,10 @@
|
|
|
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
5
|
/**
|
|
6
|
-
* Checks whether an element satisfies the "
|
|
6
|
+
* Checks whether an element satisfies the "Allowed Accessibility Child Roles"
|
|
7
|
+
* constraint (called "Required Owned Elements" in ARIA 1.2)
|
|
7
8
|
* defined by its computed ARIA role. An element satisfies this constraint if it
|
|
8
9
|
* has `aria-owns`, or if any of its closest non-presentational descendants match
|
|
9
10
|
* the required owned element roles.
|
|
@@ -29,10 +30,10 @@ el, specs, version) {
|
|
|
29
30
|
* Otherwise, traverses descendants to find owned elements.
|
|
30
31
|
*/
|
|
31
32
|
const computed = getComputedRole(specs, el, version);
|
|
32
|
-
if (!computed.role || computed.role.
|
|
33
|
+
if (!computed.role || computed.role.allowedAccessibilityChildRoles.length === 0) {
|
|
33
34
|
return true;
|
|
34
35
|
}
|
|
35
|
-
for (const expectRole of computed.role.
|
|
36
|
+
for (const expectRole of computed.role.allowedAccessibilityChildRoles) {
|
|
36
37
|
for (const owned of getClosestNonPresentationalDescendants(el, specs, version)) {
|
|
37
38
|
if (isRequiredOwnedElement(owned.el, owned.role, expectRole, specs, version)) {
|
|
38
39
|
return true;
|
|
@@ -72,18 +73,20 @@ el, role, query, specs, version) {
|
|
|
72
73
|
}
|
|
73
74
|
/**
|
|
74
75
|
* Gets the list of closest non-presentational descendants.
|
|
75
|
-
*
|
|
76
|
-
*
|
|
76
|
+
* This corresponds to the "Allowed Accessibility Child Roles" validation
|
|
77
|
+
* (called "Required Owned Elements" in ARIA 1.2).
|
|
77
78
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
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.
|
|
82
|
+
*
|
|
83
|
+
* ARIA 1.3 formally resolves this with the definitions of
|
|
84
|
+
* "accessibility child" and "accessibility parent", and additionally
|
|
85
|
+
* makes `generic` elements transparent.
|
|
82
86
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* according to the sample code in WAI-ARIA specification.
|
|
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
|
|
87
90
|
*/
|
|
88
91
|
function getClosestNonPresentationalDescendants(
|
|
89
92
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
@@ -92,7 +95,7 @@ el, specs, version) {
|
|
|
92
95
|
for (const child of el.children) {
|
|
93
96
|
const explicitRole = getExplicitRole(specs, child, version);
|
|
94
97
|
const computed = explicitRole.role ? explicitRole : getImplicitRole(specs, child, version);
|
|
95
|
-
if (
|
|
98
|
+
if (isTransparentForOwnership(computed.role?.name, version)) {
|
|
96
99
|
owned.push(...getClosestNonPresentationalDescendants(child, specs, version));
|
|
97
100
|
continue;
|
|
98
101
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ARIAVersion } from '../../types/index.js';
|
|
1
2
|
/**
|
|
2
3
|
* Determines whether a given role name corresponds to a presentational role
|
|
3
4
|
* (`"presentation"` or `"none"`), which indicates the element should be
|
|
@@ -7,3 +8,26 @@
|
|
|
7
8
|
* @returns `true` if the role name is `"presentation"` or `"none"`, `false` otherwise
|
|
8
9
|
*/
|
|
9
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;
|
|
@@ -12,3 +12,34 @@ export function isPresentational(roleName) {
|
|
|
12
12
|
}
|
|
13
13
|
return ['presentation', 'none'].includes(roleName);
|
|
14
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,10 +1,18 @@
|
|
|
1
1
|
import type { ARIAVersion, MLMLSpec } from '../../types/index.js';
|
|
2
2
|
/**
|
|
3
|
-
* Checks whether an element's parent hierarchy satisfies
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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"`).
|
|
6
7
|
*
|
|
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
|
|
8
16
|
* @param ownedEl - The owned DOM element whose parent context is being validated
|
|
9
17
|
* @param specs - The full markup language specification
|
|
10
18
|
* @param version - The ARIA specification version to use
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import { isTransparentForOwnership } from './is-presentational.js';
|
|
1
2
|
import { getComputedRole } from './get-computed-role.js';
|
|
2
3
|
/**
|
|
3
|
-
* Checks whether an element's parent hierarchy satisfies
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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"`).
|
|
6
8
|
*
|
|
7
|
-
*
|
|
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
|
|
8
17
|
* @param ownedEl - The owned DOM element whose parent context is being validated
|
|
9
18
|
* @param specs - The full markup language specification
|
|
10
19
|
* @param version - The ARIA specification version to use
|
|
@@ -18,13 +27,37 @@ ownedEl, specs, version) {
|
|
|
18
27
|
function matchesCondition(condition,
|
|
19
28
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
20
29
|
parentEl, specs, version) {
|
|
21
|
-
const conditions = condition.split(/\s+>\s+/).
|
|
30
|
+
const conditions = condition.split(/\s+>\s+/).toReversed();
|
|
22
31
|
while (conditions.length > 0) {
|
|
23
32
|
if (!parentEl) {
|
|
24
33
|
return false;
|
|
25
34
|
}
|
|
26
|
-
const condition = conditions.shift();
|
|
27
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();
|
|
28
61
|
if (condition !== parentRole?.name) {
|
|
29
62
|
return false;
|
|
30
63
|
}
|
|
@@ -14,7 +14,7 @@ export function contentModelCategoryToTagNames(contentModel, def) {
|
|
|
14
14
|
return cached;
|
|
15
15
|
}
|
|
16
16
|
const tags = def['#contentModels'][contentModel];
|
|
17
|
-
const sortedTag = Object.freeze(tags && Array.isArray(tags) ? tags.
|
|
17
|
+
const sortedTag = Object.freeze(tags && Array.isArray(tags) ? tags.toSorted() : []);
|
|
18
18
|
cache.set(contentModel, sortedTag);
|
|
19
19
|
return sortedTag;
|
|
20
20
|
}
|
|
@@ -1,13 +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
|
/**
|
|
4
6
|
* Retrieves the permitted content model for an element. Evaluates any conditional
|
|
5
7
|
* 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
|
|
8
|
+
* models for `<ol>` vs `<ol reversed>`). Results are cached per element.
|
|
7
9
|
*
|
|
8
10
|
* @param el - The DOM element to retrieve the content model for
|
|
9
11
|
* @param specs - The element specifications containing content model definitions
|
|
10
12
|
* @returns The permitted content patterns array, a boolean (true for any content, false for no content), or null if no spec exists
|
|
11
13
|
*/
|
|
12
|
-
export declare function getContentModel(el: Element, specs: Specs):
|
|
14
|
+
export declare function getContentModel(el: Element, specs: Specs): ReadonlyDeep<PermittedContentPattern[]> | boolean | null;
|
|
13
15
|
export {};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { getSpec } from '../../utils/get-spec.js';
|
|
2
|
-
const
|
|
2
|
+
const contentModelCache = new WeakMap();
|
|
3
3
|
/**
|
|
4
4
|
* Retrieves the permitted content model for an element. Evaluates any conditional
|
|
5
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
|
|
6
|
+
* models for `<ol>` vs `<ol reversed>`). Results are cached per element.
|
|
7
7
|
*
|
|
8
8
|
* @param el - The DOM element to retrieve the content model for
|
|
9
9
|
* @param specs - The element specifications containing content model definitions
|
|
@@ -12,23 +12,22 @@ const cachesBySpecs = new Map();
|
|
|
12
12
|
export function getContentModel(
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
14
14
|
el, specs) {
|
|
15
|
-
const
|
|
16
|
-
const cached = cacheByEl.get(el);
|
|
15
|
+
const cached = contentModelCache.get(el);
|
|
17
16
|
if (cached !== undefined) {
|
|
18
17
|
return cached;
|
|
19
18
|
}
|
|
20
19
|
const spec = getSpec(el, specs);
|
|
21
20
|
if (!spec) {
|
|
22
|
-
|
|
21
|
+
contentModelCache.set(el, null);
|
|
23
22
|
return null;
|
|
24
23
|
}
|
|
25
24
|
const conditions = spec.contentModel.conditional ?? [];
|
|
26
25
|
for (const cond of conditions) {
|
|
27
26
|
if (el.matches(cond.condition)) {
|
|
28
|
-
|
|
27
|
+
contentModelCache.set(el, cond.contents);
|
|
29
28
|
return cond.contents;
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
|
-
|
|
31
|
+
contentModelCache.set(el, spec.contentModel.contents);
|
|
33
32
|
return spec.contentModel.contents;
|
|
34
33
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AccnameElement } from '../algorithm/aria/accname/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* ARIA roles that are embedded controls per AccName Step 2C.
|
|
4
|
+
* @see https://www.w3.org/TR/accname-1.2/#comp_embedded_control
|
|
5
|
+
*/
|
|
6
|
+
export declare const EMBEDDED_CONTROL_ROLES: ReadonlySet<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Input types using label → title → placeholder name computation (HTML-AAM §4.1).
|
|
9
|
+
*/
|
|
10
|
+
export declare const TEXT_INPUT_TYPES: ReadonlySet<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Input types that behave as text-like controls (value attr → textContent).
|
|
13
|
+
* Subset of TEXT_INPUT_TYPES used for embedded control value extraction.
|
|
14
|
+
*/
|
|
15
|
+
export declare const TEXT_LIKE_INPUT_TYPES: ReadonlySet<string>;
|
|
16
|
+
/** Default accessible name for input[type=submit] per HTML-AAM §4.1 */
|
|
17
|
+
export declare const DEFAULT_SUBMIT_LABEL = "Submit";
|
|
18
|
+
/** Default accessible name for input[type=reset] per HTML-AAM §4.1 */
|
|
19
|
+
export declare const DEFAULT_RESET_LABEL = "Reset";
|
|
20
|
+
/** Default accessible name for input[type=image] per HTML-AAM §4.1 */
|
|
21
|
+
export declare const DEFAULT_IMAGE_LABEL = "Submit Query";
|
|
22
|
+
/**
|
|
23
|
+
* Checks if a native HTML element is an embedded control (without role resolution).
|
|
24
|
+
* Centralizes the logic duplicated across accname-computation.ts, ml-core/accname.ts, test-helpers.ts.
|
|
25
|
+
*
|
|
26
|
+
* @param el - The element to check
|
|
27
|
+
* @returns True if the element is a native embedded control (textarea, select, or input with a value-producing type)
|
|
28
|
+
*/
|
|
29
|
+
export declare function isNativeEmbeddedControl(el: AccnameElement): boolean;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARIA roles that are embedded controls per AccName Step 2C.
|
|
3
|
+
* @see https://www.w3.org/TR/accname-1.2/#comp_embedded_control
|
|
4
|
+
*/
|
|
5
|
+
export const EMBEDDED_CONTROL_ROLES = new Set([
|
|
6
|
+
'textbox',
|
|
7
|
+
'combobox',
|
|
8
|
+
'listbox',
|
|
9
|
+
'spinbutton',
|
|
10
|
+
'slider',
|
|
11
|
+
'searchbox',
|
|
12
|
+
]);
|
|
13
|
+
/**
|
|
14
|
+
* Input types using label → title → placeholder name computation (HTML-AAM §4.1).
|
|
15
|
+
*/
|
|
16
|
+
export const TEXT_INPUT_TYPES = new Set([
|
|
17
|
+
'text',
|
|
18
|
+
'password',
|
|
19
|
+
'search',
|
|
20
|
+
'tel',
|
|
21
|
+
'url',
|
|
22
|
+
'email',
|
|
23
|
+
'number',
|
|
24
|
+
'date',
|
|
25
|
+
'month',
|
|
26
|
+
'week',
|
|
27
|
+
'time',
|
|
28
|
+
'datetime-local',
|
|
29
|
+
'color',
|
|
30
|
+
'range',
|
|
31
|
+
'file',
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* Input types that behave as text-like controls (value attr → textContent).
|
|
35
|
+
* Subset of TEXT_INPUT_TYPES used for embedded control value extraction.
|
|
36
|
+
*/
|
|
37
|
+
export const TEXT_LIKE_INPUT_TYPES = new Set([
|
|
38
|
+
'text',
|
|
39
|
+
'search',
|
|
40
|
+
'tel',
|
|
41
|
+
'url',
|
|
42
|
+
'email',
|
|
43
|
+
'password',
|
|
44
|
+
'number',
|
|
45
|
+
]);
|
|
46
|
+
/** Default accessible name for input[type=submit] per HTML-AAM §4.1 */
|
|
47
|
+
export const DEFAULT_SUBMIT_LABEL = 'Submit';
|
|
48
|
+
/** Default accessible name for input[type=reset] per HTML-AAM §4.1 */
|
|
49
|
+
export const DEFAULT_RESET_LABEL = 'Reset';
|
|
50
|
+
/** Default accessible name for input[type=image] per HTML-AAM §4.1 */
|
|
51
|
+
export const DEFAULT_IMAGE_LABEL = 'Submit Query';
|
|
52
|
+
/**
|
|
53
|
+
* Checks if a native HTML element is an embedded control (without role resolution).
|
|
54
|
+
* Centralizes the logic duplicated across accname-computation.ts, ml-core/accname.ts, test-helpers.ts.
|
|
55
|
+
*
|
|
56
|
+
* @param el - The element to check
|
|
57
|
+
* @returns True if the element is a native embedded control (textarea, select, or input with a value-producing type)
|
|
58
|
+
*/
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
60
|
+
export function isNativeEmbeddedControl(el) {
|
|
61
|
+
const { localName } = el;
|
|
62
|
+
if (localName === 'textarea' || localName === 'select') {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (localName === 'input') {
|
|
66
|
+
const type = (el.getAttribute('type') ?? 'text').toLowerCase();
|
|
67
|
+
return (type !== 'hidden' &&
|
|
68
|
+
type !== 'button' &&
|
|
69
|
+
type !== 'submit' &&
|
|
70
|
+
type !== 'reset' &&
|
|
71
|
+
type !== 'image' &&
|
|
72
|
+
type !== 'checkbox' &&
|
|
73
|
+
type !== 'radio');
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** DOM Node.ELEMENT_NODE */
|
|
2
|
+
export declare const ELEMENT_NODE = 1;
|
|
3
|
+
/** DOM Node.TEXT_NODE */
|
|
4
|
+
export declare const TEXT_NODE = 3;
|
|
5
|
+
/** SVG namespace URI */
|
|
6
|
+
export declare const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
7
|
+
/** XHTML namespace URI */
|
|
8
|
+
export declare const XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
package/lib/const/dom.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** DOM Node.ELEMENT_NODE */
|
|
2
|
+
export const ELEMENT_NODE = 1;
|
|
3
|
+
/** DOM Node.TEXT_NODE */
|
|
4
|
+
export const TEXT_NODE = 3;
|
|
5
|
+
/** SVG namespace URI */
|
|
6
|
+
export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
|
7
|
+
/** XHTML namespace URI */
|
|
8
|
+
export const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { getAttrSpecs as getAttrSpecsByNames } from './utils/get-attr-specs-spec.js';
|
|
2
2
|
export * from './utils/aria-version.js';
|
|
3
|
+
export { EMBEDDED_CONTROL_ROLES, isNativeEmbeddedControl } from './const/index.js';
|
|
4
|
+
export * from './algorithm/aria/accname/index.js';
|
|
3
5
|
export * from './algorithm/aria/accname-computation.js';
|
|
4
6
|
export * from './algorithm/aria/get-computed-aria-props.js';
|
|
5
7
|
export * from './algorithm/aria/get-computed-role.js';
|
|
@@ -24,6 +26,7 @@ export * from './utils/get-spec-by-tag-name.js';
|
|
|
24
26
|
export * from './utils/schema-to-spec.js';
|
|
25
27
|
export * from './utils/resolve-namespace.js';
|
|
26
28
|
export * from './utils/validate-aria-version.js';
|
|
29
|
+
export * from './utils/directive-resolver.js';
|
|
27
30
|
export * from './types/index.js';
|
|
28
31
|
export * from './types/aria.js';
|
|
29
32
|
export * from './types/attributes.js';
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { getAttrSpecs as getAttrSpecsByNames } from './utils/get-attr-specs-spec.js';
|
|
2
2
|
export * from './utils/aria-version.js';
|
|
3
|
+
// Constants
|
|
4
|
+
export { EMBEDDED_CONTROL_ROLES, isNativeEmbeddedControl } from './const/index.js';
|
|
3
5
|
// ARIA algorithms
|
|
6
|
+
export * from './algorithm/aria/accname/index.js';
|
|
4
7
|
export * from './algorithm/aria/accname-computation.js';
|
|
5
8
|
export * from './algorithm/aria/get-computed-aria-props.js';
|
|
6
9
|
export * from './algorithm/aria/get-computed-role.js';
|
|
@@ -27,6 +30,7 @@ export * from './utils/get-spec-by-tag-name.js';
|
|
|
27
30
|
export * from './utils/schema-to-spec.js';
|
|
28
31
|
export * from './utils/resolve-namespace.js';
|
|
29
32
|
export * from './utils/validate-aria-version.js';
|
|
33
|
+
export * from './utils/directive-resolver.js';
|
|
30
34
|
// Type definitions
|
|
31
35
|
export * from './types/index.js';
|
|
32
36
|
export * from './types/aria.js';
|
package/lib/types/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface MLMLSpec {
|
|
|
11
11
|
readonly cites: Cites;
|
|
12
12
|
readonly def: SpecDefs;
|
|
13
13
|
readonly specs: readonly ElementSpec[];
|
|
14
|
+
readonly directivePatterns?: readonly DirectivePattern[];
|
|
15
|
+
readonly useIDLAttributeNames?: boolean;
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* An element specification for extending or overriding parts of the base spec.
|
|
@@ -29,6 +31,56 @@ export type ExtendedSpec = {
|
|
|
29
31
|
readonly cites?: Cites;
|
|
30
32
|
readonly def?: Partial<SpecDefs>;
|
|
31
33
|
readonly specs?: readonly ExtendedElementSpec[];
|
|
34
|
+
readonly directivePatterns?: readonly DirectivePattern[];
|
|
35
|
+
readonly useIDLAttributeNames?: boolean;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* A declarative pattern for resolving framework-specific directive
|
|
39
|
+
* attributes to their canonical names and metadata. Stored as part
|
|
40
|
+
* of the spec, enabling "parser-less" framework support.
|
|
41
|
+
*
|
|
42
|
+
* The `pattern` is a regex string matched against the raw attribute name.
|
|
43
|
+
* Capture groups can be referenced in `potentialName` using `$1`, `$2`, etc.
|
|
44
|
+
*/
|
|
45
|
+
export type DirectivePattern = {
|
|
46
|
+
/**
|
|
47
|
+
* A regex string (without delimiters) matched against the raw attribute name.
|
|
48
|
+
* Must be a valid JavaScript RegExp pattern. Capture groups can be used
|
|
49
|
+
* for potentialName templates.
|
|
50
|
+
* @example "^(?:x-bind:|:)([^.]+)(?:\\.[^.]+)?$"
|
|
51
|
+
*/
|
|
52
|
+
readonly pattern: string;
|
|
53
|
+
/**
|
|
54
|
+
* Optional regex flags (e.g., "i" for case-insensitive).
|
|
55
|
+
* @default "i"
|
|
56
|
+
*/
|
|
57
|
+
readonly flags?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Template for the resolved potentialName. References capture groups
|
|
60
|
+
* with $1, $2, etc. If omitted, the attribute is treated as a directive
|
|
61
|
+
* with no potentialName change.
|
|
62
|
+
* @example "on$1" -- for `@click` -> `onclick`
|
|
63
|
+
* @example "$1" -- for `:href` -> `href`
|
|
64
|
+
*/
|
|
65
|
+
readonly potentialName?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Whether this attribute is a framework directive.
|
|
68
|
+
*/
|
|
69
|
+
readonly isDirective?: true;
|
|
70
|
+
/**
|
|
71
|
+
* Whether this attribute has a dynamic value.
|
|
72
|
+
*/
|
|
73
|
+
readonly isDynamicValue?: true;
|
|
74
|
+
/**
|
|
75
|
+
* The semantic value type for matched attributes.
|
|
76
|
+
*/
|
|
77
|
+
readonly valueType?: 'string' | 'number' | 'boolean' | 'code';
|
|
78
|
+
/**
|
|
79
|
+
* If true, the attribute can appear multiple times on the same element.
|
|
80
|
+
* Can also be a string array of potentialName values for which
|
|
81
|
+
* duplication is allowed (e.g., ['class', 'style']).
|
|
82
|
+
*/
|
|
83
|
+
readonly isDuplicatable?: true | readonly string[];
|
|
32
84
|
};
|
|
33
85
|
/**
|
|
34
86
|
* Reference URLs
|
|
@@ -54,6 +106,7 @@ export type SpecDefs = {
|
|
|
54
106
|
type ARIASpec = {
|
|
55
107
|
readonly roles: readonly ARIARoleInSchema[];
|
|
56
108
|
readonly graphicsRoles: readonly ARIARoleInSchema[];
|
|
109
|
+
readonly dpubRoles: readonly ARIARoleInSchema[];
|
|
57
110
|
readonly props: readonly ARIAProperty[];
|
|
58
111
|
};
|
|
59
112
|
/**
|
|
@@ -158,7 +211,19 @@ export type ARIARole = {
|
|
|
158
211
|
readonly name: string;
|
|
159
212
|
readonly isAbstract: boolean;
|
|
160
213
|
readonly deprecated: boolean;
|
|
214
|
+
/** Required Accessibility Parent Role (ARIA 1.3 name) */
|
|
215
|
+
readonly requiredAccessibilityParentRole: readonly string[];
|
|
216
|
+
/** Allowed Accessibility Child Roles (ARIA 1.3 name) */
|
|
217
|
+
readonly allowedAccessibilityChildRoles: readonly string[];
|
|
218
|
+
/**
|
|
219
|
+
* @deprecated Use {@link ARIARole.requiredAccessibilityParentRole} instead.
|
|
220
|
+
* Retained for ARIA 1.2 backward compatibility.
|
|
221
|
+
*/
|
|
161
222
|
readonly requiredContextRole: readonly string[];
|
|
223
|
+
/**
|
|
224
|
+
* @deprecated Use {@link ARIARole.allowedAccessibilityChildRoles} instead.
|
|
225
|
+
* Retained for ARIA 1.2 backward compatibility.
|
|
226
|
+
*/
|
|
162
227
|
readonly requiredOwnedElements: readonly string[];
|
|
163
228
|
readonly accessibleNameRequired: boolean;
|
|
164
229
|
readonly accessibleNameFromAuthor: boolean;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DirectivePattern } from '../types/index.js';
|
|
2
|
+
type CompiledDirectivePattern = {
|
|
3
|
+
readonly regex: RegExp;
|
|
4
|
+
readonly pattern: DirectivePattern;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Compiles directive patterns into RegExp objects, caching them
|
|
8
|
+
* by the patterns array reference for efficiency.
|
|
9
|
+
*/
|
|
10
|
+
export declare function compileDirectivePatterns(patterns: readonly DirectivePattern[]): readonly CompiledDirectivePattern[];
|
|
11
|
+
export type DirectiveResolution = {
|
|
12
|
+
readonly potentialName?: string;
|
|
13
|
+
readonly isDirective?: true;
|
|
14
|
+
readonly isDynamicValue?: true;
|
|
15
|
+
readonly valueType?: 'string' | 'number' | 'boolean' | 'code';
|
|
16
|
+
readonly isDuplicatable?: boolean;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Resolves an attribute name against a list of compiled directive patterns.
|
|
20
|
+
* Returns the first match's resolution, or null if no pattern matches.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveDirective(attrName: string, compiledPatterns: readonly CompiledDirectivePattern[]): DirectiveResolution | null;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const cache = new WeakMap();
|
|
2
|
+
/**
|
|
3
|
+
* Compiles directive patterns into RegExp objects, caching them
|
|
4
|
+
* by the patterns array reference for efficiency.
|
|
5
|
+
*/
|
|
6
|
+
export function compileDirectivePatterns(patterns) {
|
|
7
|
+
let compiled = cache.get(patterns);
|
|
8
|
+
if (!compiled) {
|
|
9
|
+
compiled = patterns.map(p => ({
|
|
10
|
+
regex: new RegExp(p.pattern, p.flags ?? 'i'),
|
|
11
|
+
pattern: p,
|
|
12
|
+
}));
|
|
13
|
+
cache.set(patterns, compiled);
|
|
14
|
+
}
|
|
15
|
+
return compiled;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolves an attribute name against a list of compiled directive patterns.
|
|
19
|
+
* Returns the first match's resolution, or null if no pattern matches.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveDirective(attrName,
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
23
|
+
compiledPatterns) {
|
|
24
|
+
for (const { regex, pattern } of compiledPatterns) {
|
|
25
|
+
regex.lastIndex = 0;
|
|
26
|
+
const match = regex.exec(attrName);
|
|
27
|
+
if (!match) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
let potentialName;
|
|
31
|
+
if (pattern.potentialName != null) {
|
|
32
|
+
potentialName = pattern.potentialName.replaceAll(/\$(\d+)/g, (_, idx) => (match[Number(idx)] ?? '').toLowerCase());
|
|
33
|
+
}
|
|
34
|
+
let isDuplicatable;
|
|
35
|
+
if (pattern.isDuplicatable === true) {
|
|
36
|
+
isDuplicatable = true;
|
|
37
|
+
}
|
|
38
|
+
else if (Array.isArray(pattern.isDuplicatable) && potentialName) {
|
|
39
|
+
isDuplicatable = pattern.isDuplicatable.includes(potentialName);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
potentialName,
|
|
43
|
+
isDirective: pattern.isDirective,
|
|
44
|
+
isDynamicValue: pattern.isDynamicValue,
|
|
45
|
+
valueType: pattern.valueType,
|
|
46
|
+
isDuplicatable,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
@@ -69,11 +69,12 @@ export function getAttrSpecs(localName, namespace, schema) {
|
|
|
69
69
|
...attr,
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
-
const attrList = Object.keys(attrs)
|
|
72
|
+
const attrList = Object.keys(attrs)
|
|
73
|
+
.map(name => {
|
|
73
74
|
const attr = attrs[name];
|
|
74
75
|
return { name, type: 'Any', ...attr };
|
|
75
|
-
})
|
|
76
|
-
|
|
76
|
+
})
|
|
77
|
+
.toSorted(nameCompare);
|
|
77
78
|
cacheMap.set(localNameWithNS, attrList);
|
|
78
79
|
return attrList;
|
|
79
80
|
}
|