@markuplint/ml-spec 5.4.0 → 5.6.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/CHANGELOG.md +25 -0
- package/README.md +4 -0
- package/lib/algorithm/aria/accname/aria-steps.d.ts +12 -7
- package/lib/algorithm/aria/accname/aria-steps.js +16 -7
- package/lib/algorithm/aria/accname/compute.d.ts +3 -3
- package/lib/algorithm/aria/accname/compute.js +3 -3
- package/lib/algorithm/aria/accname/element-names.d.ts +2 -2
- package/lib/algorithm/aria/accname/element-names.js +29 -29
- package/lib/algorithm/aria/accname/helpers.d.ts +30 -9
- package/lib/algorithm/aria/accname/helpers.js +34 -12
- package/lib/algorithm/aria/accname/label-steps.d.ts +2 -2
- package/lib/algorithm/aria/accname/label-steps.js +2 -2
- package/lib/algorithm/aria/accname/svg-helpers.d.ts +2 -2
- package/lib/algorithm/aria/accname/svg-helpers.js +2 -2
- package/lib/algorithm/aria/accname/types.d.ts +12 -0
- package/lib/algorithm/aria/accname-computation.d.ts +1 -1
- package/lib/algorithm/aria/accname-computation.js +6 -6
- package/lib/algorithm/aria/get-aria.js +2 -2
- package/lib/algorithm/aria/get-computed-role.js +14 -9
- package/lib/algorithm/aria/get-explicit-role.d.ts +2 -2
- package/lib/algorithm/aria/get-explicit-role.js +11 -4
- package/lib/algorithm/aria/get-non-presentational-ancestor.d.ts +2 -0
- package/lib/algorithm/aria/get-non-presentational-ancestor.js +5 -2
- package/lib/algorithm/aria/is-exposed.d.ts +3 -1
- package/lib/algorithm/aria/is-exposed.js +21 -8
- package/lib/algorithm/aria/is-presentational.d.ts +2 -3
- package/lib/algorithm/aria/is-presentational.js +2 -3
- package/lib/algorithm/aria/matches-context-role.d.ts +4 -1
- package/lib/algorithm/aria/matches-context-role.js +11 -7
- package/lib/algorithm/dom/tree-boundary.d.ts +108 -0
- package/lib/algorithm/dom/tree-boundary.js +172 -0
- package/lib/algorithm/html/content-model-category-to-tag-names.d.ts +7 -3
- package/lib/algorithm/html/content-model-category-to-tag-names.js +7 -3
- package/lib/algorithm/html/select-display-size.d.ts +29 -0
- package/lib/algorithm/html/select-display-size.js +38 -0
- package/lib/const/accname.d.ts +1 -1
- package/lib/const/accname.js +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +3 -0
- package/lib/types/index.d.ts +63 -2
- package/lib/types/permitted-structures.d.ts +12 -1
- package/lib/utils/directive-resolver.d.ts +8 -0
- package/lib/utils/directive-resolver.js +1 -0
- package/lib/utils/schema-to-spec.d.ts +4 -0
- package/lib/utils/schema-to-spec.js +42 -0
- package/package.json +6 -8
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,31 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [5.6.0](https://github.com/markuplint/markuplint/compare/v5.5.0...v5.6.0) (2026-11-12)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* do not report an area element that has no parent node in the file ([916a166](https://github.com/markuplint/markuplint/commit/916a166916e4efd5292402abcd293927605ff799)), closes [#4528](https://github.com/markuplint/markuplint/issues/4528)
|
|
11
|
+
* **ml-spec:** follow a light child to the slot that takes it ([5269f1f](https://github.com/markuplint/markuplint/commit/5269f1f3892df6e383a9f08de6ccb07f71b5cfb9))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [5.5.0](https://github.com/markuplint/markuplint/compare/v5.4.0...v5.5.0) (2026-11-10)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **html-spec:** allow script-supporting elements in table sections, colgroup and dl ([197176a](https://github.com/markuplint/markuplint/commit/197176a7aae805e2949f9b96c6a27f39d478d261)), closes [#3591](https://github.com/markuplint/markuplint/issues/3591) [#4492](https://github.com/markuplint/markuplint/issues/4492)
|
|
19
|
+
* **ml-spec:** skip aria-label on a slot element and cite the current accname and ARIA text ([57fe6a6](https://github.com/markuplint/markuplint/commit/57fe6a6f9ac2cdc7afda7595b67c4c9392a1c073))
|
|
20
|
+
* **ml-spec:** stop the ancestor walks at the boundary of template content ([37d66d9](https://github.com/markuplint/markuplint/commit/37d66d99ac4b6b85f12df6a0cd32aa39cf27d901))
|
|
21
|
+
* report a main element that is not hierarchically correct ([b44c936](https://github.com/markuplint/markuplint/commit/b44c936cf2e40c8142f88fe6b503af6f437cd341))
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* **ml-spec:** add getSelectDisplaySize ([9788707](https://github.com/markuplint/markuplint/commit/9788707dbefa096d15be09e6baf0657e940400eb))
|
|
26
|
+
* **ml-spec:** add movedToHead to the spec for elements a framework places in the head ([c78991c](https://github.com/markuplint/markuplint/commit/c78991c612ed038f6e597a3cc3c06e80d31c47f6))
|
|
27
|
+
* **ml-spec:** declare where a template keeps its children, and resolve aria-labelledby per token ([5534b91](https://github.com/markuplint/markuplint/commit/5534b9149db8f7297e50bb4afadab0749c46ff33)), closes [#104](https://github.com/markuplint/markuplint/issues/104)
|
|
28
|
+
* **ml-spec:** merge how a framework renders a literal, and skip attributes not output ([1f6b61c](https://github.com/markuplint/markuplint/commit/1f6b61cb5137f4409bd10522c44cd5bff7c6f688))
|
|
29
|
+
|
|
30
|
+
|
|
6
31
|
# [5.4.0](https://github.com/markuplint/markuplint/compare/v5.3.0...v5.4.0) (2026-10-29)
|
|
7
32
|
|
|
8
33
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -52,6 +52,10 @@ Generated TypeScript types (do not edit directly):
|
|
|
52
52
|
- Interactive Content classification
|
|
53
53
|
- Content Model validation (`src/algorithm/html/get-content-model.ts`)
|
|
54
54
|
|
|
55
|
+
**DOM Tree Algorithms**:
|
|
56
|
+
|
|
57
|
+
- Parent element in a tree and in the accessibility tree (`getTreeParentElement`, `getComposedParentElement`, `isShadowRoot` in `src/algorithm/dom/tree-boundary.ts`)
|
|
58
|
+
|
|
55
59
|
**WAI-ARIA Specification Algorithms**:
|
|
56
60
|
|
|
57
61
|
- **Accessible Name Computation** - W3C AccName 1.2 / HTML-AAM §4.1 compliant (`src/algorithm/aria/accname/`)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { AccnameComputeFn, AccnameElement, AccnameResolver, AccnameResult } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Implements AccName 1.2 §4.3.2 Step 2B:
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* an ongoing
|
|
4
|
+
*
|
|
5
|
+
* > Otherwise, if the current node has an aria-labelledby attribute that contains at least one valid IDREF,
|
|
6
|
+
* > and the current node is not already part of an ongoing aria-labelledby or aria-describedby traversal,
|
|
7
|
+
* > process its IDREFs in the order they occur:
|
|
7
8
|
*
|
|
8
9
|
* **Self-reference handling** (spec-defined, not a custom extension):
|
|
9
10
|
* An element may reference its own ID in `aria-labelledby` to include its
|
|
@@ -14,7 +15,7 @@ import type { AccnameComputeFn, AccnameElement, AccnameResolver, AccnameResult }
|
|
|
14
15
|
* visited set — `computeFn` is called with `inLabelledbyTraversal=true`,
|
|
15
16
|
* which causes `compute.ts` to skip Step 2B on the referenced element.
|
|
16
17
|
*
|
|
17
|
-
* @see https://
|
|
18
|
+
* @see https://w3c.github.io/accname/#comp_labelledby_traversal — AccName 1.2 §4.3.2 Step 2B
|
|
18
19
|
*/
|
|
19
20
|
export declare function resolveAriaLabelledby(el: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: AccnameComputeFn): AccnameResult | null;
|
|
20
21
|
/**
|
|
@@ -31,9 +32,13 @@ export declare function resolveAriaLabelledby(el: AccnameElement, resolver: Accn
|
|
|
31
32
|
export declare function computeReferenced(referenced: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: AccnameComputeFn): AccnameResult;
|
|
32
33
|
/**
|
|
33
34
|
* Implements AccName 1.2 §4.3.2 Step 2D:
|
|
34
|
-
* "If the current node has an `aria-label` attribute whose value is
|
|
35
|
-
* not undefined, not the empty string, and not a string of whitespace [...]"
|
|
36
35
|
*
|
|
37
|
-
*
|
|
36
|
+
* > Otherwise, if the current node is not a slot element and has an aria-label attribute whose value is not undefined,
|
|
37
|
+
* > not the empty string, nor, when trimmed of whitespace, is not the empty string:
|
|
38
|
+
*
|
|
39
|
+
* The `slot` element is the HTML one (the XHTML namespace): the `aria-label` on it is skipped and the
|
|
40
|
+
* computation goes on to the following steps. A `slot` of another namespace is not the HTML `slot` element.
|
|
41
|
+
*
|
|
42
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2 Step 2D
|
|
38
43
|
*/
|
|
39
44
|
export declare function resolveAriaLabel(el: AccnameElement): AccnameResult | null;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- AccnameElement wraps mutable DOM types */
|
|
2
|
+
import { XHTML_NAMESPACE } from '../../../const/index.js';
|
|
2
3
|
import { flattenText, makeResult } from './result.js';
|
|
3
4
|
/**
|
|
4
5
|
* Implements AccName 1.2 §4.3.2 Step 2B:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* an ongoing
|
|
6
|
+
*
|
|
7
|
+
* > Otherwise, if the current node has an aria-labelledby attribute that contains at least one valid IDREF,
|
|
8
|
+
* > and the current node is not already part of an ongoing aria-labelledby or aria-describedby traversal,
|
|
9
|
+
* > process its IDREFs in the order they occur:
|
|
8
10
|
*
|
|
9
11
|
* **Self-reference handling** (spec-defined, not a custom extension):
|
|
10
12
|
* An element may reference its own ID in `aria-labelledby` to include its
|
|
@@ -15,7 +17,7 @@ import { flattenText, makeResult } from './result.js';
|
|
|
15
17
|
* visited set — `computeFn` is called with `inLabelledbyTraversal=true`,
|
|
16
18
|
* which causes `compute.ts` to skip Step 2B on the referenced element.
|
|
17
19
|
*
|
|
18
|
-
* @see https://
|
|
20
|
+
* @see https://w3c.github.io/accname/#comp_labelledby_traversal — AccName 1.2 §4.3.2 Step 2B
|
|
19
21
|
*/
|
|
20
22
|
export function resolveAriaLabelledby(el, resolver, visited, computeFn) {
|
|
21
23
|
const labelledbyAttr = el.getAttribute('aria-labelledby');
|
|
@@ -85,12 +87,19 @@ export function computeReferenced(referenced, resolver, visited, computeFn) {
|
|
|
85
87
|
}
|
|
86
88
|
/**
|
|
87
89
|
* Implements AccName 1.2 §4.3.2 Step 2D:
|
|
88
|
-
* "If the current node has an `aria-label` attribute whose value is
|
|
89
|
-
* not undefined, not the empty string, and not a string of whitespace [...]"
|
|
90
90
|
*
|
|
91
|
-
*
|
|
91
|
+
* > Otherwise, if the current node is not a slot element and has an aria-label attribute whose value is not undefined,
|
|
92
|
+
* > not the empty string, nor, when trimmed of whitespace, is not the empty string:
|
|
93
|
+
*
|
|
94
|
+
* The `slot` element is the HTML one (the XHTML namespace): the `aria-label` on it is skipped and the
|
|
95
|
+
* computation goes on to the following steps. A `slot` of another namespace is not the HTML `slot` element.
|
|
96
|
+
*
|
|
97
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2 Step 2D
|
|
92
98
|
*/
|
|
93
99
|
export function resolveAriaLabel(el) {
|
|
100
|
+
if (el.localName === 'slot' && el.namespaceURI === XHTML_NAMESPACE) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
94
103
|
const label = el.getAttribute('aria-label');
|
|
95
104
|
if (label?.trim()) {
|
|
96
105
|
return makeResult(label, 'aria-label');
|
|
@@ -31,8 +31,8 @@ import type { AccnameElement, AccnameResolver, AccnameResult } from './types.js'
|
|
|
31
31
|
* @param el - The element to compute the accessible name for
|
|
32
32
|
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
33
33
|
* @returns The computed name and its source
|
|
34
|
-
* @see https://
|
|
35
|
-
* @see https://www.w3.org/TR/html-aam
|
|
34
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2
|
|
35
|
+
* @see https://www.w3.org/TR/html-aam/#accessible-name-and-description-computation — HTML-AAM §4
|
|
36
36
|
*/
|
|
37
37
|
export declare function computeAccessibleName(el: AccnameElement, resolver: AccnameResolver): AccnameResult;
|
|
38
38
|
/**
|
|
@@ -68,6 +68,6 @@ export declare function computeHostLanguageLabel(el: AccnameElement, resolver: A
|
|
|
68
68
|
* @param inLabelledbyTraversal - When true, Step 2B is skipped to prevent re-entry
|
|
69
69
|
* into `resolveAriaLabelledby`, and Step 2F name-from-content is enabled regardless
|
|
70
70
|
* of role. This is set by Step 2B when processing each IDREF.
|
|
71
|
-
* @see https://
|
|
71
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2
|
|
72
72
|
*/
|
|
73
73
|
export declare function computeAccessibleNameInternal(el: AccnameElement, resolver: AccnameResolver, inLabelledbyTraversal: boolean, visited: ReadonlySet<string>): AccnameResult;
|
|
@@ -36,8 +36,8 @@ const EMPTY_RESULT = { name: '', source: null };
|
|
|
36
36
|
* @param el - The element to compute the accessible name for
|
|
37
37
|
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
38
38
|
* @returns The computed name and its source
|
|
39
|
-
* @see https://
|
|
40
|
-
* @see https://www.w3.org/TR/html-aam
|
|
39
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2
|
|
40
|
+
* @see https://www.w3.org/TR/html-aam/#accessible-name-and-description-computation — HTML-AAM §4
|
|
41
41
|
*/
|
|
42
42
|
export function computeAccessibleName(el, resolver) {
|
|
43
43
|
return computeAccessibleNameInternal(el, resolver, false, new Set());
|
|
@@ -83,7 +83,7 @@ export function computeHostLanguageLabel(el, resolver) {
|
|
|
83
83
|
* @param inLabelledbyTraversal - When true, Step 2B is skipped to prevent re-entry
|
|
84
84
|
* into `resolveAriaLabelledby`, and Step 2F name-from-content is enabled regardless
|
|
85
85
|
* of role. This is set by Step 2B when processing each IDREF.
|
|
86
|
-
* @see https://
|
|
86
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2
|
|
87
87
|
*/
|
|
88
88
|
export function computeAccessibleNameInternal(el, resolver, inLabelledbyTraversal, visited) {
|
|
89
89
|
// AccName 1.2 §4.3.2 Step 2A: Hidden elements return empty. In an aria-labelledby
|
|
@@ -7,7 +7,7 @@ import type { AccnameComputeFn, AccnameElement, AccnameResolver, AccnameResult }
|
|
|
7
7
|
* Returns null if no element-specific rule applies, letting the caller
|
|
8
8
|
* fall through to name-from-content (Step 2F) or title fallback (Step 2I).
|
|
9
9
|
*
|
|
10
|
-
* @see https://
|
|
11
|
-
* @see https://www.w3.org/TR/html-aam
|
|
10
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2 Step 2E
|
|
11
|
+
* @see https://www.w3.org/TR/html-aam/#accessible-name-and-description-computation — HTML-AAM §4
|
|
12
12
|
*/
|
|
13
13
|
export declare function getElementSpecificName(el: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: AccnameComputeFn, inLabelledbyTraversal: boolean): AccnameResult | null;
|
|
@@ -11,8 +11,8 @@ import { makeResult } from './result.js';
|
|
|
11
11
|
* Returns null if no element-specific rule applies, letting the caller
|
|
12
12
|
* fall through to name-from-content (Step 2F) or title fallback (Step 2I).
|
|
13
13
|
*
|
|
14
|
-
* @see https://
|
|
15
|
-
* @see https://www.w3.org/TR/html-aam
|
|
14
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2 Step 2E
|
|
15
|
+
* @see https://www.w3.org/TR/html-aam/#accessible-name-and-description-computation — HTML-AAM §4
|
|
16
16
|
*/
|
|
17
17
|
export function getElementSpecificName(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
18
18
|
if (isSvgElement(el)) {
|
|
@@ -68,9 +68,9 @@ export function getElementSpecificName(el, resolver, visited, computeFn, inLabel
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
* HTML-AAM §
|
|
71
|
+
* HTML-AAM §3.5.76 (input[text-like], textarea, select, meter, progress, output).
|
|
72
72
|
* Name sources: label → title → placeholder.
|
|
73
|
-
* @see https://www.w3.org/TR/html-aam
|
|
73
|
+
* @see https://www.w3.org/TR/html-aam/#el-input-text
|
|
74
74
|
*/
|
|
75
75
|
function handleLabelableWithTitle(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
76
76
|
const labelResult = resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
@@ -88,13 +88,13 @@ function handleLabelableWithTitle(el, resolver, visited, computeFn, inLabelledby
|
|
|
88
88
|
return null;
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* HTML-AAM §
|
|
91
|
+
* HTML-AAM §3.5.76 (input — dispatches by type).
|
|
92
92
|
* Text-like types: label → title → placeholder.
|
|
93
93
|
* Button/submit/reset: label → value → default label.
|
|
94
94
|
* Image: label → alt → title → default label.
|
|
95
95
|
* Hidden: always empty.
|
|
96
96
|
* Checkbox/radio: label → title.
|
|
97
|
-
* @see https://www.w3.org/TR/html-aam
|
|
97
|
+
* @see https://www.w3.org/TR/html-aam/#el-input-text
|
|
98
98
|
*/
|
|
99
99
|
function handleInput(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
100
100
|
const type = getInputType(el);
|
|
@@ -114,9 +114,9 @@ function handleInput(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
|
114
114
|
return handleLabelableWithTitle(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
|
-
* HTML-AAM §
|
|
117
|
+
* HTML-AAM §3.5.58 (input[type=button/submit/reset]).
|
|
118
118
|
* Name sources: label → value → title → default label (submit="Submit", reset="Reset").
|
|
119
|
-
* @see https://www.w3.org/TR/html-aam
|
|
119
|
+
* @see https://www.w3.org/TR/html-aam/#el-input-button
|
|
120
120
|
*/
|
|
121
121
|
function handleInputButton(el, type, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
122
122
|
const labelResult = resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
@@ -141,9 +141,9 @@ function handleInputButton(el, type, resolver, visited, computeFn, inLabelledbyT
|
|
|
141
141
|
return null;
|
|
142
142
|
}
|
|
143
143
|
/**
|
|
144
|
-
* HTML-AAM §
|
|
144
|
+
* HTML-AAM §3.5.66 (input[type=image]).
|
|
145
145
|
* Name sources: label → alt → title → default ("Submit Query").
|
|
146
|
-
* @see https://www.w3.org/TR/html-aam
|
|
146
|
+
* @see https://www.w3.org/TR/html-aam/#el-input-image
|
|
147
147
|
*/
|
|
148
148
|
function handleInputImage(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
149
149
|
const labelResult = resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
@@ -161,9 +161,9 @@ function handleInputImage(el, resolver, visited, computeFn, inLabelledbyTraversa
|
|
|
161
161
|
return makeResult(DEFAULT_IMAGE_LABEL, 'default');
|
|
162
162
|
}
|
|
163
163
|
/**
|
|
164
|
-
* HTML-AAM §
|
|
164
|
+
* HTML-AAM §3.5.20 (button).
|
|
165
165
|
* Name sources: label → content → title.
|
|
166
|
-
* @see https://www.w3.org/TR/html-aam
|
|
166
|
+
* @see https://www.w3.org/TR/html-aam/#el-button
|
|
167
167
|
*/
|
|
168
168
|
function handleButton(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
169
169
|
const labelResult = resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
@@ -181,9 +181,9 @@ function handleButton(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
|
181
181
|
return null;
|
|
182
182
|
}
|
|
183
183
|
/**
|
|
184
|
-
* HTML-AAM §
|
|
184
|
+
* HTML-AAM §3.5.40 (fieldset).
|
|
185
185
|
* Name sources: legend (content) → title.
|
|
186
|
-
* @see https://www.w3.org/TR/html-aam
|
|
186
|
+
* @see https://www.w3.org/TR/html-aam/#el-fieldset
|
|
187
187
|
*/
|
|
188
188
|
function handleFieldset(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
189
189
|
const legend = findChildByLocalName(el, 'legend', resolver);
|
|
@@ -200,9 +200,9 @@ function handleFieldset(el, resolver, visited, computeFn, inLabelledbyTraversal)
|
|
|
200
200
|
return null;
|
|
201
201
|
}
|
|
202
202
|
/**
|
|
203
|
-
* HTML-AAM §
|
|
203
|
+
* HTML-AAM §3.5.127 (table).
|
|
204
204
|
* Name sources: caption (content) → title.
|
|
205
|
-
* @see https://www.w3.org/TR/html-aam
|
|
205
|
+
* @see https://www.w3.org/TR/html-aam/#el-table
|
|
206
206
|
*/
|
|
207
207
|
function handleTable(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
208
208
|
const caption = findChildByLocalName(el, 'caption', resolver);
|
|
@@ -219,10 +219,10 @@ function handleTable(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
|
219
219
|
return null;
|
|
220
220
|
}
|
|
221
221
|
/**
|
|
222
|
-
* HTML-AAM §
|
|
222
|
+
* HTML-AAM §3.5.56 (img).
|
|
223
223
|
* Name sources: alt → title (only when alt is not specified).
|
|
224
224
|
* When alt="" (empty), returns empty name with null source (decorative image).
|
|
225
|
-
* @see https://www.w3.org/TR/html-aam
|
|
225
|
+
* @see https://www.w3.org/TR/html-aam/#el-img
|
|
226
226
|
*/
|
|
227
227
|
function handleImg(el) {
|
|
228
228
|
if (el.hasAttribute('alt')) {
|
|
@@ -236,9 +236,9 @@ function handleImg(el) {
|
|
|
236
236
|
return null;
|
|
237
237
|
}
|
|
238
238
|
/**
|
|
239
|
-
* HTML-AAM §
|
|
239
|
+
* HTML-AAM §3.5.6 (area).
|
|
240
240
|
* Name sources: alt → title.
|
|
241
|
-
* @see https://www.w3.org/TR/html-aam
|
|
241
|
+
* @see https://www.w3.org/TR/html-aam/#el-area
|
|
242
242
|
*/
|
|
243
243
|
function handleArea(el) {
|
|
244
244
|
const alt = el.getAttribute('alt');
|
|
@@ -252,10 +252,10 @@ function handleArea(el) {
|
|
|
252
252
|
return null;
|
|
253
253
|
}
|
|
254
254
|
/**
|
|
255
|
-
* HTML-AAM §
|
|
255
|
+
* HTML-AAM §3.5.42 (figure).
|
|
256
256
|
* Name sources: title only. Note: figcaption provides the accessible
|
|
257
257
|
* *description*, not the name, per HTML-AAM.
|
|
258
|
-
* @see https://www.w3.org/TR/html-aam
|
|
258
|
+
* @see https://www.w3.org/TR/html-aam/#el-figure
|
|
259
259
|
*/
|
|
260
260
|
function handleFigure(el) {
|
|
261
261
|
const title = el.getAttribute('title');
|
|
@@ -265,9 +265,9 @@ function handleFigure(el) {
|
|
|
265
265
|
return null;
|
|
266
266
|
}
|
|
267
267
|
/**
|
|
268
|
-
* HTML-AAM §
|
|
268
|
+
* HTML-AAM §3.5.124 (summary).
|
|
269
269
|
* Name sources: content → title.
|
|
270
|
-
* @see https://www.w3.org/TR/html-aam
|
|
270
|
+
* @see https://www.w3.org/TR/html-aam/#el-summary
|
|
271
271
|
*/
|
|
272
272
|
function handleSummary(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
273
273
|
const content = resolveNameFromContent(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
@@ -281,9 +281,9 @@ function handleSummary(el, resolver, visited, computeFn, inLabelledbyTraversal)
|
|
|
281
281
|
return null;
|
|
282
282
|
}
|
|
283
283
|
/**
|
|
284
|
-
* HTML-AAM §
|
|
284
|
+
* HTML-AAM §3.5.2 (a[href]).
|
|
285
285
|
* Name sources: content → title.
|
|
286
|
-
* @see https://www.w3.org/TR/html-aam
|
|
286
|
+
* @see https://www.w3.org/TR/html-aam/#el-a
|
|
287
287
|
*/
|
|
288
288
|
function handleAnchor(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
289
289
|
const content = resolveNameFromContent(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
@@ -297,8 +297,8 @@ function handleAnchor(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
|
297
297
|
return null;
|
|
298
298
|
}
|
|
299
299
|
/**
|
|
300
|
-
* HTML-AAM §
|
|
301
|
-
* @see https://www.w3.org/TR/html-aam
|
|
300
|
+
* HTML-AAM §3.5.55 (iframe) — name from title attribute only.
|
|
301
|
+
* @see https://www.w3.org/TR/html-aam/#el-iframe
|
|
302
302
|
*/
|
|
303
303
|
function handleTitleOnly(el) {
|
|
304
304
|
const title = el.getAttribute('title');
|
|
@@ -309,7 +309,7 @@ function handleTitleOnly(el) {
|
|
|
309
309
|
}
|
|
310
310
|
/**
|
|
311
311
|
* SVG-AAM: SVG elements get name from `<title>` child element.
|
|
312
|
-
* @see https://www.w3.org/TR/svg-aam
|
|
312
|
+
* @see https://www.w3.org/TR/svg-aam/#mapping_additional_nd — SVG-AAM §7.1
|
|
313
313
|
*/
|
|
314
314
|
function handleSvgElement(el, resolver) {
|
|
315
315
|
const titleEl = findChildByLocalName(el, 'title', resolver);
|
|
@@ -3,12 +3,22 @@ export { EMBEDDED_CONTROL_ROLES } from '../../../const/index.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Implements AccName 1.2 §4.3.2 Steps 2F and 2C:
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* **Step 2F**, the name from content:
|
|
7
|
+
*
|
|
8
|
+
* > Otherwise, if the current node's role allows name from content, or if the current node is referenced by
|
|
9
|
+
* > aria-labelledby, aria-describedby, or is a native host language text alternative element (e.g. label in HTML),
|
|
10
|
+
* > or is a descendant of a native host language text alternative element:
|
|
11
|
+
*
|
|
12
|
+
* **Step 2C**, the embedded controls:
|
|
13
|
+
*
|
|
14
|
+
* > Otherwise, if the current node is a control embedded within the label (e.g. any element directly referenced by
|
|
15
|
+
* > aria-labelledby) for another widget, where the user can adjust the embedded control's value, then return the
|
|
16
|
+
* > embedded control as part of the text alternative in the following manner:
|
|
17
|
+
*
|
|
18
|
+
* `aria-label` is ignored for an embedded control during this traversal:
|
|
19
|
+
*
|
|
20
|
+
* > If traversal of the current node is due to Name From Content descendant node recursion and the current node is an
|
|
21
|
+
* > embedded control, ignore aria-label and skip to rule Embedded Control.
|
|
12
22
|
*
|
|
13
23
|
* **Limitation:** CSS-generated content (`::before`/`::after` with the `content`
|
|
14
24
|
* property) is not included. AccName 1.2 §4.3.2 Step 2G specifies that CSS
|
|
@@ -16,8 +26,8 @@ export { EMBEDDED_CONTROL_ROLES } from '../../../const/index.js';
|
|
|
16
26
|
* performs static HTML analysis without CSS processing, so this content is
|
|
17
27
|
* unavailable at lint time.
|
|
18
28
|
*
|
|
19
|
-
* @see https://
|
|
20
|
-
* @see https://
|
|
29
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2 Step 2F
|
|
30
|
+
* @see https://w3c.github.io/accname/#comp_embedded_control — AccName 1.2 §4.3.2 Step 2C
|
|
21
31
|
*/
|
|
22
32
|
export declare function resolveNameFromContent(el: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: AccnameComputeFn, inLabelledbyTraversal: boolean): string;
|
|
23
33
|
/**
|
|
@@ -44,6 +54,17 @@ export declare function getChildNodes(el: AccnameElement, resolver: AccnameResol
|
|
|
44
54
|
*/
|
|
45
55
|
export declare function getTextContent(el: AccnameElement, resolver: AccnameResolver): string;
|
|
46
56
|
export declare function findChildByLocalName(el: AccnameElement, localName: string, resolver?: AccnameResolver): AccnameElement | null;
|
|
57
|
+
/**
|
|
58
|
+
* The closest `label` element among the ancestors of the element, which names the element when it is
|
|
59
|
+
* its labeled control. The ancestors are those of its own tree, as the HTML Standard defines the
|
|
60
|
+
* relation between a `label` and its labeled control by ancestors in the tree, so a `label` that
|
|
61
|
+
* wraps a `template` element does not label a control in its content.
|
|
62
|
+
*
|
|
63
|
+
* @see https://html.spec.whatwg.org/multipage/forms.html#labeled-control
|
|
64
|
+
*
|
|
65
|
+
* @param el - The element to start from
|
|
66
|
+
* @returns The closest `label` ancestor, or `null`
|
|
67
|
+
*/
|
|
47
68
|
export declare function findAncestorLabel(el: AccnameElement): AccnameElement | null;
|
|
48
69
|
export declare function resolveLabel(el: AccnameElement, resolver: AccnameResolver): readonly AccnameElement[];
|
|
49
70
|
export declare function getInputType(el: AccnameElement): string;
|
|
@@ -54,7 +75,7 @@ export declare function isSvgElement(el: AccnameElement): boolean;
|
|
|
54
75
|
* through Step 2E of its parent (`handleSvgElement`), which an element exposed
|
|
55
76
|
* as presentational skips.
|
|
56
77
|
*
|
|
57
|
-
* @see https://
|
|
78
|
+
* @see https://svgwg.org/svg2-draft/render.html#TermNeverRenderedElement
|
|
58
79
|
*/
|
|
59
80
|
export declare function isSvgDescriptiveElement(el: AccnameElement): boolean;
|
|
60
81
|
/**
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- AccnameElement wraps mutable DOM types */
|
|
2
2
|
import { ELEMENT_NODE, SVG_NAMESPACE, TEXT_NODE, TEXT_LIKE_INPUT_TYPES } from '../../../const/index.js';
|
|
3
|
+
import { getTreeParentElement } from '../../dom/tree-boundary.js';
|
|
3
4
|
import { resolveAriaLabelledby } from './aria-steps.js';
|
|
4
5
|
export { EMBEDDED_CONTROL_ROLES } from '../../../const/index.js';
|
|
5
6
|
/**
|
|
6
7
|
* Implements AccName 1.2 §4.3.2 Steps 2F and 2C:
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* **Step 2F**, the name from content:
|
|
10
|
+
*
|
|
11
|
+
* > Otherwise, if the current node's role allows name from content, or if the current node is referenced by
|
|
12
|
+
* > aria-labelledby, aria-describedby, or is a native host language text alternative element (e.g. label in HTML),
|
|
13
|
+
* > or is a descendant of a native host language text alternative element:
|
|
14
|
+
*
|
|
15
|
+
* **Step 2C**, the embedded controls:
|
|
16
|
+
*
|
|
17
|
+
* > Otherwise, if the current node is a control embedded within the label (e.g. any element directly referenced by
|
|
18
|
+
* > aria-labelledby) for another widget, where the user can adjust the embedded control's value, then return the
|
|
19
|
+
* > embedded control as part of the text alternative in the following manner:
|
|
20
|
+
*
|
|
21
|
+
* `aria-label` is ignored for an embedded control during this traversal:
|
|
22
|
+
*
|
|
23
|
+
* > If traversal of the current node is due to Name From Content descendant node recursion and the current node is an
|
|
24
|
+
* > embedded control, ignore aria-label and skip to rule Embedded Control.
|
|
14
25
|
*
|
|
15
26
|
* **Limitation:** CSS-generated content (`::before`/`::after` with the `content`
|
|
16
27
|
* property) is not included. AccName 1.2 §4.3.2 Step 2G specifies that CSS
|
|
@@ -18,8 +29,8 @@ export { EMBEDDED_CONTROL_ROLES } from '../../../const/index.js';
|
|
|
18
29
|
* performs static HTML analysis without CSS processing, so this content is
|
|
19
30
|
* unavailable at lint time.
|
|
20
31
|
*
|
|
21
|
-
* @see https://
|
|
22
|
-
* @see https://
|
|
32
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2 Step 2F
|
|
33
|
+
* @see https://w3c.github.io/accname/#comp_embedded_control — AccName 1.2 §4.3.2 Step 2C
|
|
23
34
|
*/
|
|
24
35
|
export function resolveNameFromContent(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
25
36
|
const parts = [];
|
|
@@ -130,7 +141,7 @@ export function embeddedControlText(el, resolver, visited, computeFn, inLabelled
|
|
|
130
141
|
* value attr → textContent
|
|
131
142
|
* - **Listbox** (select): selected option text (static analysis approximation)
|
|
132
143
|
*
|
|
133
|
-
* @see https://
|
|
144
|
+
* @see https://w3c.github.io/accname/#comp_embedded_control — AccName 1.2 §4.3.2 Step 2C
|
|
134
145
|
*/
|
|
135
146
|
function getEmbeddedControlValue(el, resolver) {
|
|
136
147
|
const role = el.getAttribute('role')?.trim().split(/\s+/)[0];
|
|
@@ -276,13 +287,24 @@ export function findChildByLocalName(el, localName, resolver) {
|
|
|
276
287
|
}
|
|
277
288
|
return null;
|
|
278
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* The closest `label` element among the ancestors of the element, which names the element when it is
|
|
292
|
+
* its labeled control. The ancestors are those of its own tree, as the HTML Standard defines the
|
|
293
|
+
* relation between a `label` and its labeled control by ancestors in the tree, so a `label` that
|
|
294
|
+
* wraps a `template` element does not label a control in its content.
|
|
295
|
+
*
|
|
296
|
+
* @see https://html.spec.whatwg.org/multipage/forms.html#labeled-control
|
|
297
|
+
*
|
|
298
|
+
* @param el - The element to start from
|
|
299
|
+
* @returns The closest `label` ancestor, or `null`
|
|
300
|
+
*/
|
|
279
301
|
export function findAncestorLabel(el) {
|
|
280
|
-
let current = el
|
|
302
|
+
let current = getTreeParentElement(el);
|
|
281
303
|
while (current) {
|
|
282
304
|
if (current.localName === 'label') {
|
|
283
305
|
return current;
|
|
284
306
|
}
|
|
285
|
-
current = current
|
|
307
|
+
current = getTreeParentElement(current);
|
|
286
308
|
}
|
|
287
309
|
return null;
|
|
288
310
|
}
|
|
@@ -315,7 +337,7 @@ export function isSvgElement(el) {
|
|
|
315
337
|
* through Step 2E of its parent (`handleSvgElement`), which an element exposed
|
|
316
338
|
* as presentational skips.
|
|
317
339
|
*
|
|
318
|
-
* @see https://
|
|
340
|
+
* @see https://svgwg.org/svg2-draft/render.html#TermNeverRenderedElement
|
|
319
341
|
*/
|
|
320
342
|
export function isSvgDescriptiveElement(el) {
|
|
321
343
|
return isSvgElement(el) && (el.localName === 'title' || el.localName === 'desc');
|
|
@@ -3,7 +3,7 @@ import type { AccnameComputeFn, AccnameElement, AccnameResolver, AccnameResult }
|
|
|
3
3
|
* Part of AccName 1.2 §4.3.2 Step 2E — for labelable elements, the HTML label
|
|
4
4
|
* association is checked before other element-specific rules.
|
|
5
5
|
*
|
|
6
|
-
* @see https://
|
|
7
|
-
* @see https://www.w3.org/TR/html-aam
|
|
6
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2 Step 2E
|
|
7
|
+
* @see https://www.w3.org/TR/html-aam/#el-input-text — HTML-AAM label association
|
|
8
8
|
*/
|
|
9
9
|
export declare function resolveLabelText(el: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: AccnameComputeFn, inLabelledbyTraversal: boolean): AccnameResult | null;
|
|
@@ -6,8 +6,8 @@ import { flattenText, makeResult } from './result.js';
|
|
|
6
6
|
* Part of AccName 1.2 §4.3.2 Step 2E — for labelable elements, the HTML label
|
|
7
7
|
* association is checked before other element-specific rules.
|
|
8
8
|
*
|
|
9
|
-
* @see https://
|
|
10
|
-
* @see https://www.w3.org/TR/html-aam
|
|
9
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2 Step 2E
|
|
10
|
+
* @see https://www.w3.org/TR/html-aam/#el-input-text — HTML-AAM label association
|
|
11
11
|
*/
|
|
12
12
|
export function resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
13
13
|
const labels = resolveLabel(el, resolver);
|
|
@@ -4,7 +4,7 @@ import type { AccnameElement } from './types.js';
|
|
|
4
4
|
* the full name computation.
|
|
5
5
|
*
|
|
6
6
|
* Used by `get-computed-role.ts` for SVG accessibility tree inclusion decisions
|
|
7
|
-
* per SVG-AAM §
|
|
7
|
+
* per SVG-AAM §4.1.2. This is a lightweight check that avoids the full AccName
|
|
8
8
|
* algorithm, breaking the circular dependency between role computation and
|
|
9
9
|
* name computation.
|
|
10
10
|
*
|
|
@@ -19,6 +19,6 @@ import type { AccnameElement } from './types.js';
|
|
|
19
19
|
*
|
|
20
20
|
* @param el - The SVG element to check for accessible name sources
|
|
21
21
|
* @returns True if the element has aria-label, aria-labelledby, or a title/desc child
|
|
22
|
-
* @see https://www.w3.org/TR/svg-aam
|
|
22
|
+
* @see https://www.w3.org/TR/svg-aam/#include_elements — SVG-AAM §4.1.2
|
|
23
23
|
*/
|
|
24
24
|
export declare function hasSvgAccessibleNameSource(el: AccnameElement): boolean;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* the full name computation.
|
|
5
5
|
*
|
|
6
6
|
* Used by `get-computed-role.ts` for SVG accessibility tree inclusion decisions
|
|
7
|
-
* per SVG-AAM §
|
|
7
|
+
* per SVG-AAM §4.1.2. This is a lightweight check that avoids the full AccName
|
|
8
8
|
* algorithm, breaking the circular dependency between role computation and
|
|
9
9
|
* name computation.
|
|
10
10
|
*
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
* @param el - The SVG element to check for accessible name sources
|
|
21
21
|
* @returns True if the element has aria-label, aria-labelledby, or a title/desc child
|
|
22
|
-
* @see https://www.w3.org/TR/svg-aam
|
|
22
|
+
* @see https://www.w3.org/TR/svg-aam/#include_elements — SVG-AAM §4.1.2
|
|
23
23
|
*/
|
|
24
24
|
export function hasSvgAccessibleNameSource(el) {
|
|
25
25
|
if (el.getAttribute('aria-label')?.trim()) {
|
|
@@ -20,6 +20,13 @@ export interface AccnameElement extends AccnameNode {
|
|
|
20
20
|
getAttribute(name: string): string | null;
|
|
21
21
|
hasAttribute(name: string): boolean;
|
|
22
22
|
readonly parentElement: AccnameElement | null;
|
|
23
|
+
/**
|
|
24
|
+
* The root of the tree of the element. An element and its parent element are in the same tree,
|
|
25
|
+
* unless the parent element is only the syntactical parent, as `MLElement#parentElement` is for the
|
|
26
|
+
* top of the content of a `template` element: see `getTreeParentElement`. An implementation without
|
|
27
|
+
* it has no tree boundary.
|
|
28
|
+
*/
|
|
29
|
+
getRootNode?(): unknown;
|
|
23
30
|
readonly children: Iterable<AccnameElement>;
|
|
24
31
|
readonly childNodes: Iterable<AccnameNode>;
|
|
25
32
|
}
|
|
@@ -66,6 +73,11 @@ export interface AccnameResolver {
|
|
|
66
73
|
* Used by ml-core for a pretended component: it renders its own elements around
|
|
67
74
|
* the children given at the usage site (`contents` of the pretender), and those
|
|
68
75
|
* can carry the accessible name (`<button><img alt="Save"></button>`).
|
|
76
|
+
* Also the rendered children, when they are not the syntactical ones: the
|
|
77
|
+
* child nodes of an attached shadow root, and none for an ordinary
|
|
78
|
+
* `template` element, whose content is not rendered where it is written.
|
|
79
|
+
* An element whose shadow root is only maybe attached is given none as
|
|
80
|
+
* well, because that root is not there to read.
|
|
69
81
|
* Consulted by name-from-content, label text, the legend/caption/SVG `title`
|
|
70
82
|
* lookups, the `<select>` options, and wherever the computation reads an
|
|
71
83
|
* element's text (`getTextContent`): an option, an SVG `title`, the value of
|
|
@@ -14,6 +14,6 @@ import type { ARIAVersion, MLMLSpec } from '../../types/index.js';
|
|
|
14
14
|
* @param specs - The ML specification data for role resolution
|
|
15
15
|
* @param version - The ARIA version to use for role computation
|
|
16
16
|
* @returns The computed accessible name string, or an empty string if none is found
|
|
17
|
-
* @see https://
|
|
17
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2
|
|
18
18
|
*/
|
|
19
19
|
export declare function getAccname(el: Element, specs: MLMLSpec, version: ARIAVersion): string;
|