@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
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- AccnameElement wraps mutable DOM types */
|
|
2
|
+
import { ELEMENT_NODE, TEXT_NODE, XHTML_NAMESPACE, EMBEDDED_CONTROL_ROLES, isNativeEmbeddedControl, } from '../../../../const/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a test AccnameElement (plain object).
|
|
5
|
+
*
|
|
6
|
+
* **Note:** `textContent` is computed eagerly at creation time from the initial
|
|
7
|
+
* `children`. Adding children after creation will NOT update `textContent`.
|
|
8
|
+
* Build the full child tree before calling this function.
|
|
9
|
+
*
|
|
10
|
+
* @param localName - The local tag name of the element
|
|
11
|
+
* @param options - Configuration for attributes, children, parent, and namespace
|
|
12
|
+
* @returns A plain object implementing the AccnameElement interface
|
|
13
|
+
*/
|
|
14
|
+
export function element(localName, options = {}) {
|
|
15
|
+
const attrs = options.attrs ?? {};
|
|
16
|
+
const childNodes = options.children ?? [];
|
|
17
|
+
const elementChildren = [];
|
|
18
|
+
for (const child of childNodes) {
|
|
19
|
+
if (child.nodeType === ELEMENT_NODE) {
|
|
20
|
+
elementChildren.push(child);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const el = {
|
|
24
|
+
nodeType: ELEMENT_NODE,
|
|
25
|
+
localName,
|
|
26
|
+
id: attrs['id'] ?? '',
|
|
27
|
+
namespaceURI: options.namespaceURI ?? XHTML_NAMESPACE,
|
|
28
|
+
textContent: computeTextContent(childNodes),
|
|
29
|
+
parentElement: options.parentElement ?? null,
|
|
30
|
+
children: elementChildren,
|
|
31
|
+
childNodes,
|
|
32
|
+
getAttribute(name) {
|
|
33
|
+
return attrs[name] ?? null;
|
|
34
|
+
},
|
|
35
|
+
hasAttribute(name) {
|
|
36
|
+
return name in attrs;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
return el;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Creates a test text node.
|
|
43
|
+
*
|
|
44
|
+
* @param text - The text content of the node
|
|
45
|
+
* @returns A plain object implementing the AccnameNode interface
|
|
46
|
+
*/
|
|
47
|
+
export function textNode(text) {
|
|
48
|
+
return {
|
|
49
|
+
nodeType: TEXT_NODE,
|
|
50
|
+
textContent: text,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function computeTextContent(childNodes) {
|
|
54
|
+
return childNodes
|
|
55
|
+
.map(child => {
|
|
56
|
+
if (child.nodeType === TEXT_NODE) {
|
|
57
|
+
return child.textContent ?? '';
|
|
58
|
+
}
|
|
59
|
+
if (child.nodeType === ELEMENT_NODE) {
|
|
60
|
+
return child.textContent ?? '';
|
|
61
|
+
}
|
|
62
|
+
return '';
|
|
63
|
+
})
|
|
64
|
+
.join('');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Creates a test AccnameResolver.
|
|
68
|
+
*
|
|
69
|
+
* @param options - Configuration for element lookups, label associations, and behavior overrides
|
|
70
|
+
* @returns A resolver implementing the AccnameResolver interface for testing
|
|
71
|
+
*/
|
|
72
|
+
export function createTestResolver(options = {}) {
|
|
73
|
+
const elements = options.elements ?? new Map();
|
|
74
|
+
const labels = options.labels ?? new Map();
|
|
75
|
+
const nameFromContent = options.nameFromContent ?? new Set();
|
|
76
|
+
const hiddenIds = options.hiddenIds ?? new Set();
|
|
77
|
+
const embeddedControlRoles = options.embeddedControlRoles ?? EMBEDDED_CONTROL_ROLES;
|
|
78
|
+
return {
|
|
79
|
+
getElementById(id) {
|
|
80
|
+
return elements.get(id) ?? null;
|
|
81
|
+
},
|
|
82
|
+
getLabelsForId(id) {
|
|
83
|
+
return labels.get(id) ?? [];
|
|
84
|
+
},
|
|
85
|
+
allowsNameFromContent(el) {
|
|
86
|
+
if (options.allowsNameFromContentFn) {
|
|
87
|
+
return options.allowsNameFromContentFn(el);
|
|
88
|
+
}
|
|
89
|
+
return nameFromContent.has(el.localName);
|
|
90
|
+
},
|
|
91
|
+
isHidden(el) {
|
|
92
|
+
if (options.isHiddenFn) {
|
|
93
|
+
return options.isHiddenFn(el);
|
|
94
|
+
}
|
|
95
|
+
if (hiddenIds.has(el.id)) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
if (el.getAttribute('aria-hidden') === 'true') {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
if (el.hasAttribute('hidden')) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
},
|
|
106
|
+
isEmbeddedControl(el) {
|
|
107
|
+
if (options.isEmbeddedControlFn) {
|
|
108
|
+
return options.isEmbeddedControlFn(el);
|
|
109
|
+
}
|
|
110
|
+
// Check explicit role
|
|
111
|
+
const role = el.getAttribute('role')?.trim().split(/\s+/)[0];
|
|
112
|
+
if (role && embeddedControlRoles.has(role)) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
// Check native HTML elements
|
|
116
|
+
return isNativeEmbeddedControl(el);
|
|
117
|
+
},
|
|
118
|
+
...(options.getPrecomputedNameFn ? { getPrecomputedName: options.getPrecomputedNameFn } : {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { AccnameElement, AccnameResolver, AccnameResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves accessible name via `aria-labelledby` attribute.
|
|
4
|
+
*
|
|
5
|
+
* Implements AccName 1.2 §4.3.2 Step 2B:
|
|
6
|
+
* "If the current node has an `aria-labelledby` attribute that contains
|
|
7
|
+
* at least one valid IDREF, and the current node is not already part of
|
|
8
|
+
* an ongoing `aria-labelledby` traversal, process its IDREFs [...]"
|
|
9
|
+
*
|
|
10
|
+
* Control flow:
|
|
11
|
+
* 1. Read `aria-labelledby` attribute; return null if absent or empty.
|
|
12
|
+
* 2. Split the attribute value by whitespace into IDREF tokens.
|
|
13
|
+
* 3. Mark the current element's ID as visited (cycle prevention for A→B→A).
|
|
14
|
+
* 4. For each IDREF:
|
|
15
|
+
* a. Skip if already visited — **except** self-references (see below).
|
|
16
|
+
* b. Resolve the referenced element via `resolver.getElementById`.
|
|
17
|
+
* c. Recursively compute the referenced element's name with
|
|
18
|
+
* `inLabelledbyTraversal=true` (prevents Step 2B re-entry in `compute.ts`).
|
|
19
|
+
* d. Each IDREF branch gets its own copy of the visited set so that
|
|
20
|
+
* one branch's traversal does not block later branches.
|
|
21
|
+
* 5. Join all resolved parts with a space separator and flatten whitespace.
|
|
22
|
+
*
|
|
23
|
+
* **Self-reference handling** (spec-defined, not a custom extension):
|
|
24
|
+
* An element may reference its own ID in `aria-labelledby` to include its
|
|
25
|
+
* own content alongside other referenced elements. This is explicitly
|
|
26
|
+
* permitted by the spec (AccName 1.2 §4.3.2 Example 2):
|
|
27
|
+
* `<h2 id="h" aria-labelledby="h foo">Meeting</h2>`
|
|
28
|
+
* Infinite recursion is prevented by `inLabelledbyTraversal`, not by the
|
|
29
|
+
* visited set — `computeFn` is called with `inLabelledbyTraversal=true`,
|
|
30
|
+
* which causes `compute.ts` to skip Step 2B on the referenced element.
|
|
31
|
+
*
|
|
32
|
+
* @param el - The element with a potential aria-labelledby attribute
|
|
33
|
+
* @param resolver - Environment-dependent resolver for element lookups
|
|
34
|
+
* @param visited - Set of element IDs already visited (cycle prevention)
|
|
35
|
+
* @param computeFn - The recursive accessible name computation function
|
|
36
|
+
* @returns The resolved name result, or null if aria-labelledby is not present or yields no name
|
|
37
|
+
* @see https://www.w3.org/TR/accname-1.2/#comp_labelledby_traversal — AccName 1.2 §4.3.2 Step 2B
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveAriaLabelledby(el: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: (el: AccnameElement, resolver: AccnameResolver, inLabelledbyTraversal: boolean, visited: ReadonlySet<string>) => AccnameResult): AccnameResult | null;
|
|
40
|
+
/**
|
|
41
|
+
* Resolves accessible name via `aria-label` attribute.
|
|
42
|
+
*
|
|
43
|
+
* Implements AccName 1.2 §4.3.2 Step 2D:
|
|
44
|
+
* "If the current node has an `aria-label` attribute whose value is
|
|
45
|
+
* not undefined, not the empty string, and not a string of whitespace [...]"
|
|
46
|
+
*
|
|
47
|
+
* @param el - The element with a potential aria-label attribute
|
|
48
|
+
* @returns The resolved name result, or null if aria-label is not present or empty
|
|
49
|
+
* @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2D
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveAriaLabel(el: AccnameElement): AccnameResult | null;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- AccnameElement wraps mutable DOM types */
|
|
2
|
+
import { flattenText, makeResult } from './helpers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves accessible name via `aria-labelledby` attribute.
|
|
5
|
+
*
|
|
6
|
+
* Implements AccName 1.2 §4.3.2 Step 2B:
|
|
7
|
+
* "If the current node has an `aria-labelledby` attribute that contains
|
|
8
|
+
* at least one valid IDREF, and the current node is not already part of
|
|
9
|
+
* an ongoing `aria-labelledby` traversal, process its IDREFs [...]"
|
|
10
|
+
*
|
|
11
|
+
* Control flow:
|
|
12
|
+
* 1. Read `aria-labelledby` attribute; return null if absent or empty.
|
|
13
|
+
* 2. Split the attribute value by whitespace into IDREF tokens.
|
|
14
|
+
* 3. Mark the current element's ID as visited (cycle prevention for A→B→A).
|
|
15
|
+
* 4. For each IDREF:
|
|
16
|
+
* a. Skip if already visited — **except** self-references (see below).
|
|
17
|
+
* b. Resolve the referenced element via `resolver.getElementById`.
|
|
18
|
+
* c. Recursively compute the referenced element's name with
|
|
19
|
+
* `inLabelledbyTraversal=true` (prevents Step 2B re-entry in `compute.ts`).
|
|
20
|
+
* d. Each IDREF branch gets its own copy of the visited set so that
|
|
21
|
+
* one branch's traversal does not block later branches.
|
|
22
|
+
* 5. Join all resolved parts with a space separator and flatten whitespace.
|
|
23
|
+
*
|
|
24
|
+
* **Self-reference handling** (spec-defined, not a custom extension):
|
|
25
|
+
* An element may reference its own ID in `aria-labelledby` to include its
|
|
26
|
+
* own content alongside other referenced elements. This is explicitly
|
|
27
|
+
* permitted by the spec (AccName 1.2 §4.3.2 Example 2):
|
|
28
|
+
* `<h2 id="h" aria-labelledby="h foo">Meeting</h2>`
|
|
29
|
+
* Infinite recursion is prevented by `inLabelledbyTraversal`, not by the
|
|
30
|
+
* visited set — `computeFn` is called with `inLabelledbyTraversal=true`,
|
|
31
|
+
* which causes `compute.ts` to skip Step 2B on the referenced element.
|
|
32
|
+
*
|
|
33
|
+
* @param el - The element with a potential aria-labelledby attribute
|
|
34
|
+
* @param resolver - Environment-dependent resolver for element lookups
|
|
35
|
+
* @param visited - Set of element IDs already visited (cycle prevention)
|
|
36
|
+
* @param computeFn - The recursive accessible name computation function
|
|
37
|
+
* @returns The resolved name result, or null if aria-labelledby is not present or yields no name
|
|
38
|
+
* @see https://www.w3.org/TR/accname-1.2/#comp_labelledby_traversal — AccName 1.2 §4.3.2 Step 2B
|
|
39
|
+
*/
|
|
40
|
+
export function resolveAriaLabelledby(el, resolver, visited, computeFn) {
|
|
41
|
+
const labelledbyAttr = el.getAttribute('aria-labelledby');
|
|
42
|
+
if (!labelledbyAttr?.trim()) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const ids = labelledbyAttr.trim().split(/\s+/);
|
|
46
|
+
const parts = [];
|
|
47
|
+
const newVisited = new Set(visited);
|
|
48
|
+
// Mark the current element as visited to prevent other elements from
|
|
49
|
+
// circling back to it (e.g., A → B → A).
|
|
50
|
+
if (el.id) {
|
|
51
|
+
newVisited.add(el.id);
|
|
52
|
+
}
|
|
53
|
+
for (const id of ids) {
|
|
54
|
+
// Skip IDs already visited — but allow self-references (id === el.id).
|
|
55
|
+
// The spec requires that an element CAN reference itself in
|
|
56
|
+
// aria-labelledby to include its own content alongside other IDs.
|
|
57
|
+
// Example (AccName 1.2 §4.3.2 Example 2):
|
|
58
|
+
// <h2 id="h" aria-labelledby="h foo">Meeting</h2>
|
|
59
|
+
// Here "h" references itself, contributing "Meeting" to the result.
|
|
60
|
+
//
|
|
61
|
+
// Infinite recursion is prevented not by the visited set, but by
|
|
62
|
+
// inLabelledbyTraversal: computeFn is called with true below,
|
|
63
|
+
// which causes compute.ts to skip Step 2B (aria-labelledby) on
|
|
64
|
+
// the referenced element, so it never re-enters this function.
|
|
65
|
+
if (newVisited.has(id) && id !== el.id) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const referenced = resolver.getElementById(id);
|
|
69
|
+
if (!referenced) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// Each branch gets its own visited set so that one IDREF's
|
|
73
|
+
// traversal doesn't block resolution of a later IDREF.
|
|
74
|
+
const innerVisited = new Set(newVisited);
|
|
75
|
+
innerVisited.add(id);
|
|
76
|
+
const result = computeFn(referenced, resolver, true, innerVisited);
|
|
77
|
+
if (result.name) {
|
|
78
|
+
parts.push(result.name);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const name = flattenText(parts.join(' '));
|
|
82
|
+
if (name) {
|
|
83
|
+
return makeResult(name, 'aria-labelledby');
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolves accessible name via `aria-label` attribute.
|
|
89
|
+
*
|
|
90
|
+
* Implements AccName 1.2 §4.3.2 Step 2D:
|
|
91
|
+
* "If the current node has an `aria-label` attribute whose value is
|
|
92
|
+
* not undefined, not the empty string, and not a string of whitespace [...]"
|
|
93
|
+
*
|
|
94
|
+
* @param el - The element with a potential aria-label attribute
|
|
95
|
+
* @returns The resolved name result, or null if aria-label is not present or empty
|
|
96
|
+
* @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2D
|
|
97
|
+
*/
|
|
98
|
+
export function resolveAriaLabel(el) {
|
|
99
|
+
const label = el.getAttribute('aria-label');
|
|
100
|
+
if (label?.trim()) {
|
|
101
|
+
return makeResult(label, 'aria-label');
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { AccnameElement, AccnameResolver, AccnameResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Computes the accessible name for an element.
|
|
4
|
+
*
|
|
5
|
+
* Implements the Accessible Name and Description Computation algorithm
|
|
6
|
+
* per AccName 1.2 §4.3.2 ("Computation Steps") and HTML-AAM §4.1
|
|
7
|
+
* ("Accessible Name and Description Computation").
|
|
8
|
+
*
|
|
9
|
+
* Control flow (Steps map to AccName 1.2 §4.3.2):
|
|
10
|
+
*
|
|
11
|
+
* 1. **Step 2A — Hidden check** — If the element is hidden (per `isHidden`) and NOT
|
|
12
|
+
* referenced by `aria-labelledby`, return empty immediately.
|
|
13
|
+
* 2. **Pre-computed name** — (Implementation-specific extension) If the
|
|
14
|
+
* resolver provides `getPrecomputedName`, check it before standard steps.
|
|
15
|
+
* Used by ml-core's Pretender integration for framework components.
|
|
16
|
+
* 3. **Step 2B — `aria-labelledby`** — Resolve referenced elements and
|
|
17
|
+
* recursively compute their names. Skipped when already inside a
|
|
18
|
+
* labelledby traversal (`inLabelledbyTraversal`) to prevent re-entry.
|
|
19
|
+
* 4. **Step 2D — `aria-label`** — Use the `aria-label` attribute value.
|
|
20
|
+
* 5. **Step 2E — Element-specific name** — Dispatch to HTML-AAM §4.1
|
|
21
|
+
* element-specific rules (label association, alt, value, legend, caption, SVG title).
|
|
22
|
+
* 6. **Step 2F — Name from content** — If the element's role allows
|
|
23
|
+
* `nameFrom: ["content"]`, or the element is referenced by `aria-labelledby`
|
|
24
|
+
* (`inLabelledbyTraversal`), recursively collect child text (including
|
|
25
|
+
* embedded control values per Step 2C).
|
|
26
|
+
* 7. **Step 2I — Title fallback** — Use the `title` attribute value.
|
|
27
|
+
*
|
|
28
|
+
* @param el - The element to compute the accessible name for
|
|
29
|
+
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
30
|
+
* @returns The computed name and its source
|
|
31
|
+
* @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2
|
|
32
|
+
* @see https://www.w3.org/TR/html-aam-1.0/#accessible-name-and-description-computation — HTML-AAM §4.1
|
|
33
|
+
*/
|
|
34
|
+
export declare function computeAccessibleName(el: AccnameElement, resolver: AccnameResolver): AccnameResult;
|
|
35
|
+
/**
|
|
36
|
+
* Internal recursive entry point for accessible name computation.
|
|
37
|
+
*
|
|
38
|
+
* This function is the recursive core called by `resolveAriaLabelledby` (Step 2B),
|
|
39
|
+
* `resolveLabelText` (Step 2E), and `resolveNameFromContent` (Step 2F) when they
|
|
40
|
+
* need to compute a child or referenced element's name.
|
|
41
|
+
*
|
|
42
|
+
* @param el - The element to compute the accessible name for
|
|
43
|
+
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
44
|
+
* @param inLabelledbyTraversal - When true, Step 2B is skipped to prevent re-entry
|
|
45
|
+
* into `resolveAriaLabelledby`, and Step 2F name-from-content is enabled regardless
|
|
46
|
+
* of role. This is set by Step 2B when processing each IDREF.
|
|
47
|
+
* @param visited - Set of element IDs already visited (cycle prevention for aria-labelledby)
|
|
48
|
+
* @returns The computed name and its source
|
|
49
|
+
* @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2
|
|
50
|
+
*/
|
|
51
|
+
export declare function computeAccessibleNameInternal(el: AccnameElement, resolver: AccnameResolver, inLabelledbyTraversal: boolean, visited: ReadonlySet<string>): AccnameResult;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- AccnameElement wraps mutable DOM types */
|
|
2
|
+
import { resolveAriaLabel, resolveAriaLabelledby } from './aria-steps.js';
|
|
3
|
+
import { getElementSpecificName } from './element-names.js';
|
|
4
|
+
import { makeResult, resolveNameFromContent } from './helpers.js';
|
|
5
|
+
const EMPTY_RESULT = { name: '', source: null };
|
|
6
|
+
/**
|
|
7
|
+
* Computes the accessible name for an element.
|
|
8
|
+
*
|
|
9
|
+
* Implements the Accessible Name and Description Computation algorithm
|
|
10
|
+
* per AccName 1.2 §4.3.2 ("Computation Steps") and HTML-AAM §4.1
|
|
11
|
+
* ("Accessible Name and Description Computation").
|
|
12
|
+
*
|
|
13
|
+
* Control flow (Steps map to AccName 1.2 §4.3.2):
|
|
14
|
+
*
|
|
15
|
+
* 1. **Step 2A — Hidden check** — If the element is hidden (per `isHidden`) and NOT
|
|
16
|
+
* referenced by `aria-labelledby`, return empty immediately.
|
|
17
|
+
* 2. **Pre-computed name** — (Implementation-specific extension) If the
|
|
18
|
+
* resolver provides `getPrecomputedName`, check it before standard steps.
|
|
19
|
+
* Used by ml-core's Pretender integration for framework components.
|
|
20
|
+
* 3. **Step 2B — `aria-labelledby`** — Resolve referenced elements and
|
|
21
|
+
* recursively compute their names. Skipped when already inside a
|
|
22
|
+
* labelledby traversal (`inLabelledbyTraversal`) to prevent re-entry.
|
|
23
|
+
* 4. **Step 2D — `aria-label`** — Use the `aria-label` attribute value.
|
|
24
|
+
* 5. **Step 2E — Element-specific name** — Dispatch to HTML-AAM §4.1
|
|
25
|
+
* element-specific rules (label association, alt, value, legend, caption, SVG title).
|
|
26
|
+
* 6. **Step 2F — Name from content** — If the element's role allows
|
|
27
|
+
* `nameFrom: ["content"]`, or the element is referenced by `aria-labelledby`
|
|
28
|
+
* (`inLabelledbyTraversal`), recursively collect child text (including
|
|
29
|
+
* embedded control values per Step 2C).
|
|
30
|
+
* 7. **Step 2I — Title fallback** — Use the `title` attribute value.
|
|
31
|
+
*
|
|
32
|
+
* @param el - The element to compute the accessible name for
|
|
33
|
+
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
34
|
+
* @returns The computed name and its source
|
|
35
|
+
* @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2
|
|
36
|
+
* @see https://www.w3.org/TR/html-aam-1.0/#accessible-name-and-description-computation — HTML-AAM §4.1
|
|
37
|
+
*/
|
|
38
|
+
export function computeAccessibleName(el, resolver) {
|
|
39
|
+
return computeAccessibleNameInternal(el, resolver, false, new Set());
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Internal recursive entry point for accessible name computation.
|
|
43
|
+
*
|
|
44
|
+
* This function is the recursive core called by `resolveAriaLabelledby` (Step 2B),
|
|
45
|
+
* `resolveLabelText` (Step 2E), and `resolveNameFromContent` (Step 2F) when they
|
|
46
|
+
* need to compute a child or referenced element's name.
|
|
47
|
+
*
|
|
48
|
+
* @param el - The element to compute the accessible name for
|
|
49
|
+
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
50
|
+
* @param inLabelledbyTraversal - When true, Step 2B is skipped to prevent re-entry
|
|
51
|
+
* into `resolveAriaLabelledby`, and Step 2F name-from-content is enabled regardless
|
|
52
|
+
* of role. This is set by Step 2B when processing each IDREF.
|
|
53
|
+
* @param visited - Set of element IDs already visited (cycle prevention for aria-labelledby)
|
|
54
|
+
* @returns The computed name and its source
|
|
55
|
+
* @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2
|
|
56
|
+
*/
|
|
57
|
+
export function computeAccessibleNameInternal(el, resolver, inLabelledbyTraversal, visited) {
|
|
58
|
+
// AccName 1.2 §4.3.2 Step 2A: Hidden elements return empty unless referenced by aria-labelledby
|
|
59
|
+
if (!inLabelledbyTraversal && resolver.isHidden(el)) {
|
|
60
|
+
return EMPTY_RESULT;
|
|
61
|
+
}
|
|
62
|
+
// [Implementation-specific] Pre-computed name (e.g., Pretender integration) — checked after hidden check
|
|
63
|
+
if (resolver.getPrecomputedName) {
|
|
64
|
+
const precomputed = resolver.getPrecomputedName(el);
|
|
65
|
+
if (precomputed != null) {
|
|
66
|
+
return makeResult(precomputed, 'content');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// AccName 1.2 §4.3.2 Step 2B: aria-labelledby (skipped when inside labelledby traversal)
|
|
70
|
+
if (!inLabelledbyTraversal) {
|
|
71
|
+
const labelledbyResult = resolveAriaLabelledby(el, resolver, visited, computeAccessibleNameInternal);
|
|
72
|
+
if (labelledbyResult) {
|
|
73
|
+
return labelledbyResult;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// AccName 1.2 §4.3.2 Step 2D: aria-label
|
|
77
|
+
const ariaLabelResult = resolveAriaLabel(el);
|
|
78
|
+
if (ariaLabelResult) {
|
|
79
|
+
return ariaLabelResult;
|
|
80
|
+
}
|
|
81
|
+
// AccName 1.2 §4.3.2 Step 2E: Element-specific name (HTML-AAM §4.1)
|
|
82
|
+
const elementResult = getElementSpecificName(el, resolver, visited, computeAccessibleNameInternal, inLabelledbyTraversal);
|
|
83
|
+
if (elementResult) {
|
|
84
|
+
return elementResult;
|
|
85
|
+
}
|
|
86
|
+
// AccName 1.2 §4.3.2 Step 2F/2C: Name from content
|
|
87
|
+
// Applies when the role allows nameFrom: ["content"] OR when the element is
|
|
88
|
+
// directly referenced by aria-labelledby (inLabelledbyTraversal).
|
|
89
|
+
if (resolver.allowsNameFromContent(el) || inLabelledbyTraversal) {
|
|
90
|
+
const content = resolveNameFromContent(el, resolver, visited, computeAccessibleNameInternal, inLabelledbyTraversal);
|
|
91
|
+
if (content.trim()) {
|
|
92
|
+
return makeResult(content, 'content');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// AccName 1.2 §4.3.2 Step 2I: Title attribute fallback
|
|
96
|
+
const title = el.getAttribute('title');
|
|
97
|
+
if (title?.trim()) {
|
|
98
|
+
return makeResult(title, 'title');
|
|
99
|
+
}
|
|
100
|
+
return EMPTY_RESULT;
|
|
101
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AccnameElement, AccnameResolver, AccnameResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Computes element-specific accessible name per HTML-AAM §4.1.
|
|
4
|
+
*
|
|
5
|
+
* Implements AccName 1.2 §4.3.2 Step 2E: for elements that have a native
|
|
6
|
+
* host language text alternative, use that alternative. The specific rules
|
|
7
|
+
* for each HTML element are defined in HTML-AAM §4.1.
|
|
8
|
+
*
|
|
9
|
+
* Returns null if no element-specific rule applies, letting the caller
|
|
10
|
+
* fall through to name-from-content (Step 2F) or title fallback (Step 2I).
|
|
11
|
+
*
|
|
12
|
+
* Control flow (dispatches by `localName`):
|
|
13
|
+
* - SVG elements → `handleSvgElement` (SVG-AAM: `<title>` child)
|
|
14
|
+
* - `<input>` → `handleInput` (branches by type: text-like, button, image, hidden)
|
|
15
|
+
* - `<textarea>`, `<select>`, `<meter>`, `<progress>`, `<output>` → `handleLabelableWithTitle`
|
|
16
|
+
* - `<button>` → `handleButton` (label → content → title)
|
|
17
|
+
* - `<fieldset>` → `handleFieldset` (legend → title)
|
|
18
|
+
* - `<table>` → `handleTable` (caption → title)
|
|
19
|
+
* - `<img>` → `handleImg` (alt → title)
|
|
20
|
+
* - `<area>` → `handleArea` (alt → title)
|
|
21
|
+
* - `<figure>` → `handleFigure` (title only)
|
|
22
|
+
* - `<summary>` → `handleSummary` (content → title)
|
|
23
|
+
* - `<a href>` → `handleAnchor` (content → title)
|
|
24
|
+
* - `<iframe>` → `handleTitleOnly` (title only)
|
|
25
|
+
* - All others → null (rely on caller's generic Steps 2F/2I)
|
|
26
|
+
*
|
|
27
|
+
* @param el - The element to compute the name for
|
|
28
|
+
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
29
|
+
* @param visited - Set of element IDs already visited (cycle prevention)
|
|
30
|
+
* @param computeFn - The recursive accessible name computation function
|
|
31
|
+
* @param inLabelledbyTraversal - Whether this computation is part of an aria-labelledby traversal
|
|
32
|
+
* @returns The computed name result, or null if no element-specific rule applies
|
|
33
|
+
* @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2E
|
|
34
|
+
* @see https://www.w3.org/TR/html-aam-1.0/#accessible-name-and-description-computation — HTML-AAM §4.1
|
|
35
|
+
*/
|
|
36
|
+
export declare function getElementSpecificName(el: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: (el: AccnameElement, resolver: AccnameResolver, inLabelledbyTraversal: boolean, visited: ReadonlySet<string>) => AccnameResult, inLabelledbyTraversal: boolean): AccnameResult | null;
|