@markuplint/ml-spec 5.3.0 → 5.5.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 +24 -0
- package/README.md +4 -0
- package/lib/algorithm/aria/accname/aria-steps.d.ts +26 -9
- package/lib/algorithm/aria/accname/aria-steps.js +38 -9
- package/lib/algorithm/aria/accname/compute.d.ts +38 -6
- package/lib/algorithm/aria/accname/compute.js +60 -12
- package/lib/algorithm/aria/accname/element-names.d.ts +4 -4
- package/lib/algorithm/aria/accname/element-names.js +31 -30
- package/lib/algorithm/aria/accname/helpers.d.ts +52 -12
- package/lib/algorithm/aria/accname/helpers.js +78 -23
- package/lib/algorithm/aria/accname/index.d.ts +2 -2
- package/lib/algorithm/aria/accname/index.js +1 -1
- package/lib/algorithm/aria/accname/label-steps.d.ts +4 -4
- package/lib/algorithm/aria/accname/label-steps.js +14 -5
- package/lib/algorithm/aria/accname/result.d.ts +21 -0
- package/lib/algorithm/aria/accname/result.js +28 -0
- 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 +51 -0
- package/lib/algorithm/aria/accname-computation.d.ts +1 -1
- package/lib/algorithm/aria/accname-computation.js +12 -6
- package/lib/algorithm/aria/get-aria.js +2 -2
- package/lib/algorithm/aria/get-computed-role.js +18 -14
- package/lib/algorithm/aria/get-explicit-role.d.ts +6 -0
- package/lib/algorithm/aria/get-explicit-role.js +82 -9
- 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 +15 -7
- package/lib/algorithm/aria/is-presentational.d.ts +15 -4
- package/lib/algorithm/aria/is-presentational.js +16 -3
- package/lib/algorithm/aria/matches-context-role.d.ts +3 -1
- package/lib/algorithm/aria/matches-context-role.js +10 -7
- package/lib/algorithm/dom/tree-boundary.d.ts +93 -0
- package/lib/algorithm/dom/tree-boundary.js +103 -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 +67 -2
- package/lib/types/permitted-structures.d.ts +5 -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 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,30 @@
|
|
|
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.5.0](https://github.com/markuplint/markuplint/compare/v5.4.0...v5.5.0) (2026-11-10)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **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)
|
|
11
|
+
* **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))
|
|
12
|
+
* **ml-spec:** stop the ancestor walks at the boundary of template content ([37d66d9](https://github.com/markuplint/markuplint/commit/37d66d99ac4b6b85f12df6a0cd32aa39cf27d901))
|
|
13
|
+
* report a main element that is not hierarchically correct ([b44c936](https://github.com/markuplint/markuplint/commit/b44c936cf2e40c8142f88fe6b503af6f437cd341))
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **ml-spec:** add getSelectDisplaySize ([9788707](https://github.com/markuplint/markuplint/commit/9788707dbefa096d15be09e6baf0657e940400eb))
|
|
18
|
+
* **ml-spec:** add movedToHead to the spec for elements a framework places in the head ([c78991c](https://github.com/markuplint/markuplint/commit/c78991c612ed038f6e597a3cc3c06e80d31c47f6))
|
|
19
|
+
* **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)
|
|
20
|
+
* **ml-spec:** merge how a framework renders a literal, and skip attributes not output ([1f6b61c](https://github.com/markuplint/markuplint/commit/1f6b61cb5137f4409bd10522c44cd5bff7c6f688))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# [5.4.0](https://github.com/markuplint/markuplint/compare/v5.3.0...v5.4.0) (2026-10-29)
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* **ml-spec:** keep roles that are not permitted and align the accessible name with AccName ([6c38c60](https://github.com/markuplint/markuplint/commit/6c38c60971690edec8b6453e50c76146eb10deb2)), closes [#4408](https://github.com/markuplint/markuplint/issues/4408)
|
|
28
|
+
|
|
29
|
+
|
|
6
30
|
# [5.3.0](https://github.com/markuplint/markuplint/compare/v5.2.0...v5.3.0) (2026-10-21)
|
|
7
31
|
|
|
8
32
|
### 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
|
-
import type { AccnameElement, AccnameResolver, AccnameResult } from './types.js';
|
|
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,14 +15,30 @@ import type { AccnameElement, AccnameResolver, AccnameResult } from './types.js'
|
|
|
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
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveAriaLabelledby(el: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: AccnameComputeFn): AccnameResult | null;
|
|
21
|
+
/**
|
|
22
|
+
* Computes the text alternative of a node directly referenced by
|
|
23
|
+
* `aria-labelledby` (Step 2B, LabelledBy Traversal).
|
|
24
|
+
*
|
|
25
|
+
* Step 2A is skipped for the whole subtree only when the referenced node is
|
|
26
|
+
* hidden; otherwise its hidden descendants contribute nothing.
|
|
27
|
+
*
|
|
28
|
+
* @see https://w3c.github.io/accname/#comp_hidden_not_referenced
|
|
29
|
+
*
|
|
30
|
+
* > If the current node is hidden and is: Not part of an aria-labelledby or aria-describedby traversal, where the node directly referenced by that relation was hidden.
|
|
18
31
|
*/
|
|
19
|
-
export declare function
|
|
32
|
+
export declare function computeReferenced(referenced: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: AccnameComputeFn): AccnameResult;
|
|
20
33
|
/**
|
|
21
34
|
* Implements AccName 1.2 §4.3.2 Step 2D:
|
|
22
|
-
* "If the current node has an `aria-label` attribute whose value is
|
|
23
|
-
* not undefined, not the empty string, and not a string of whitespace [...]"
|
|
24
35
|
*
|
|
25
|
-
*
|
|
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
|
|
26
43
|
*/
|
|
27
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 {
|
|
2
|
+
import { XHTML_NAMESPACE } from '../../../const/index.js';
|
|
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 './helpers.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');
|
|
@@ -52,7 +54,7 @@ export function resolveAriaLabelledby(el, resolver, visited, computeFn) {
|
|
|
52
54
|
// Each branch gets its own visited set so that one IDREF's
|
|
53
55
|
// traversal doesn't block resolution of a later IDREF.
|
|
54
56
|
const innerVisited = new Set([...newVisited, id]);
|
|
55
|
-
const result =
|
|
57
|
+
const result = computeReferenced(referenced, resolver, innerVisited, computeFn);
|
|
56
58
|
if (result.name) {
|
|
57
59
|
parts.push(result.name);
|
|
58
60
|
}
|
|
@@ -63,14 +65,41 @@ export function resolveAriaLabelledby(el, resolver, visited, computeFn) {
|
|
|
63
65
|
}
|
|
64
66
|
return null;
|
|
65
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Computes the text alternative of a node directly referenced by
|
|
70
|
+
* `aria-labelledby` (Step 2B, LabelledBy Traversal).
|
|
71
|
+
*
|
|
72
|
+
* Step 2A is skipped for the whole subtree only when the referenced node is
|
|
73
|
+
* hidden; otherwise its hidden descendants contribute nothing.
|
|
74
|
+
*
|
|
75
|
+
* @see https://w3c.github.io/accname/#comp_hidden_not_referenced
|
|
76
|
+
*
|
|
77
|
+
* > If the current node is hidden and is: Not part of an aria-labelledby or aria-describedby traversal, where the node directly referenced by that relation was hidden.
|
|
78
|
+
*/
|
|
79
|
+
export function computeReferenced(referenced, resolver, visited, computeFn) {
|
|
80
|
+
const traversalResolver = resolver.isHidden(referenced)
|
|
81
|
+
? {
|
|
82
|
+
...resolver,
|
|
83
|
+
isHidden: () => false,
|
|
84
|
+
}
|
|
85
|
+
: resolver;
|
|
86
|
+
return computeFn(referenced, traversalResolver, true, visited);
|
|
87
|
+
}
|
|
66
88
|
/**
|
|
67
89
|
* Implements AccName 1.2 §4.3.2 Step 2D:
|
|
68
|
-
* "If the current node has an `aria-label` attribute whose value is
|
|
69
|
-
* not undefined, not the empty string, and not a string of whitespace [...]"
|
|
70
90
|
*
|
|
71
|
-
*
|
|
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
|
|
72
98
|
*/
|
|
73
99
|
export function resolveAriaLabel(el) {
|
|
100
|
+
if (el.localName === 'slot' && el.namespaceURI === XHTML_NAMESPACE) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
74
103
|
const label = el.getAttribute('aria-label');
|
|
75
104
|
if (label?.trim()) {
|
|
76
105
|
return makeResult(label, 'aria-label');
|
|
@@ -8,8 +8,9 @@ import type { AccnameElement, AccnameResolver, AccnameResult } from './types.js'
|
|
|
8
8
|
*
|
|
9
9
|
* Control flow (Steps map to AccName 1.2 §4.3.2):
|
|
10
10
|
*
|
|
11
|
-
* 1. **Step 2A — Hidden check** — If the element is hidden (per `isHidden`)
|
|
12
|
-
*
|
|
11
|
+
* 1. **Step 2A — Hidden check** — If the element is hidden (per `isHidden`), return
|
|
12
|
+
* empty immediately, unless it is in an `aria-labelledby` traversal whose
|
|
13
|
+
* directly referenced node is hidden.
|
|
13
14
|
* 2. **Pre-computed name** — (Implementation-specific extension) If the
|
|
14
15
|
* resolver provides `getPrecomputedName`, check it before standard steps.
|
|
15
16
|
* Used by ml-core's Pretender integration for framework components.
|
|
@@ -18,7 +19,9 @@ import type { AccnameElement, AccnameResolver, AccnameResult } from './types.js'
|
|
|
18
19
|
* labelledby traversal (`inLabelledbyTraversal`) to prevent re-entry.
|
|
19
20
|
* 4. **Step 2D — `aria-label`** — Use the `aria-label` attribute value.
|
|
20
21
|
* 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
|
+
* element-specific rules (label association, alt, value, legend, caption, SVG title),
|
|
23
|
+
* skipped for an element the resolver reports as exposed as presentational
|
|
24
|
+
* (`isExposedAsPresentational`).
|
|
22
25
|
* 6. **Step 2F — Name from content** — If the element's role allows
|
|
23
26
|
* `nameFrom: ["content"]`, or the element is referenced by `aria-labelledby`
|
|
24
27
|
* (`inLabelledbyTraversal`), recursively collect child text (including
|
|
@@ -28,14 +31,43 @@ import type { AccnameElement, AccnameResolver, AccnameResult } from './types.js'
|
|
|
28
31
|
* @param el - The element to compute the accessible name for
|
|
29
32
|
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
30
33
|
* @returns The computed name and its source
|
|
31
|
-
* @see https://
|
|
32
|
-
* @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
|
|
33
36
|
*/
|
|
34
37
|
export declare function computeAccessibleName(el: AccnameElement, resolver: AccnameResolver): AccnameResult;
|
|
38
|
+
/**
|
|
39
|
+
* Computes the text alternative of an element as a node referenced by
|
|
40
|
+
* `aria-labelledby` or `aria-describedby` (the LabelledBy Traversal of Step 2B,
|
|
41
|
+
* which the description computation also uses).
|
|
42
|
+
*
|
|
43
|
+
* @param el - The referenced element
|
|
44
|
+
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
45
|
+
* @returns The computed text alternative and its source
|
|
46
|
+
* @see https://w3c.github.io/accname/#comp_labelledby_traversal
|
|
47
|
+
*/
|
|
48
|
+
export declare function computeReferencedTextAlternative(el: AccnameElement, resolver: AccnameResolver): AccnameResult;
|
|
49
|
+
/**
|
|
50
|
+
* Computes the host language text alternative of an element (AccName §4.3.2
|
|
51
|
+
* Step 2E, HTML-AAM §4.1), such as the `alt` of an image or the `title` of an
|
|
52
|
+
* SVG element, whether or not the element is exposed as presentational.
|
|
53
|
+
*
|
|
54
|
+
* Tells what an explicit `none` or `presentation` role drops from the name:
|
|
55
|
+
* every source the HTML-AAM rules of Step 2E give (`label`, `alt`, `value`,
|
|
56
|
+
* `placeholder`, `legend`, `caption`, `svg-title`, and the `default` label of a
|
|
57
|
+
* submit, reset or image button). The sources that Step 2E shares with other
|
|
58
|
+
* steps (`content`, the `title` attribute) are not dropped by the role, and give
|
|
59
|
+
* `null`.
|
|
60
|
+
*
|
|
61
|
+
* @param el - The element
|
|
62
|
+
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
63
|
+
* @returns The text alternative and its source, or `null` if the element has none
|
|
64
|
+
* @see https://w3c.github.io/accname/#comp_host_language_label
|
|
65
|
+
*/
|
|
66
|
+
export declare function computeHostLanguageLabel(el: AccnameElement, resolver: AccnameResolver): AccnameResult | null;
|
|
35
67
|
/**
|
|
36
68
|
* @param inLabelledbyTraversal - When true, Step 2B is skipped to prevent re-entry
|
|
37
69
|
* into `resolveAriaLabelledby`, and Step 2F name-from-content is enabled regardless
|
|
38
70
|
* of role. This is set by Step 2B when processing each IDREF.
|
|
39
|
-
* @see https://
|
|
71
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2
|
|
40
72
|
*/
|
|
41
73
|
export declare function computeAccessibleNameInternal(el: AccnameElement, resolver: AccnameResolver, inLabelledbyTraversal: boolean, visited: ReadonlySet<string>): AccnameResult;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- AccnameElement wraps mutable DOM types */
|
|
2
|
-
import { resolveAriaLabel, resolveAriaLabelledby } from './aria-steps.js';
|
|
2
|
+
import { computeReferenced, resolveAriaLabel, resolveAriaLabelledby } from './aria-steps.js';
|
|
3
3
|
import { getElementSpecificName } from './element-names.js';
|
|
4
|
-
import {
|
|
4
|
+
import { resolveNameFromContent } from './helpers.js';
|
|
5
|
+
import { makeResult } from './result.js';
|
|
5
6
|
const EMPTY_RESULT = { name: '', source: null };
|
|
6
7
|
/**
|
|
7
8
|
* Computes the accessible name for an element.
|
|
@@ -12,8 +13,9 @@ const EMPTY_RESULT = { name: '', source: null };
|
|
|
12
13
|
*
|
|
13
14
|
* Control flow (Steps map to AccName 1.2 §4.3.2):
|
|
14
15
|
*
|
|
15
|
-
* 1. **Step 2A — Hidden check** — If the element is hidden (per `isHidden`)
|
|
16
|
-
*
|
|
16
|
+
* 1. **Step 2A — Hidden check** — If the element is hidden (per `isHidden`), return
|
|
17
|
+
* empty immediately, unless it is in an `aria-labelledby` traversal whose
|
|
18
|
+
* directly referenced node is hidden.
|
|
17
19
|
* 2. **Pre-computed name** — (Implementation-specific extension) If the
|
|
18
20
|
* resolver provides `getPrecomputedName`, check it before standard steps.
|
|
19
21
|
* Used by ml-core's Pretender integration for framework components.
|
|
@@ -22,7 +24,9 @@ const EMPTY_RESULT = { name: '', source: null };
|
|
|
22
24
|
* labelledby traversal (`inLabelledbyTraversal`) to prevent re-entry.
|
|
23
25
|
* 4. **Step 2D — `aria-label`** — Use the `aria-label` attribute value.
|
|
24
26
|
* 5. **Step 2E — Element-specific name** — Dispatch to HTML-AAM §4.1
|
|
25
|
-
* element-specific rules (label association, alt, value, legend, caption, SVG title)
|
|
27
|
+
* element-specific rules (label association, alt, value, legend, caption, SVG title),
|
|
28
|
+
* skipped for an element the resolver reports as exposed as presentational
|
|
29
|
+
* (`isExposedAsPresentational`).
|
|
26
30
|
* 6. **Step 2F — Name from content** — If the element's role allows
|
|
27
31
|
* `nameFrom: ["content"]`, or the element is referenced by `aria-labelledby`
|
|
28
32
|
* (`inLabelledbyTraversal`), recursively collect child text (including
|
|
@@ -32,21 +36,62 @@ const EMPTY_RESULT = { name: '', source: null };
|
|
|
32
36
|
* @param el - The element to compute the accessible name for
|
|
33
37
|
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
34
38
|
* @returns The computed name and its source
|
|
35
|
-
* @see https://
|
|
36
|
-
* @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
|
|
37
41
|
*/
|
|
38
42
|
export function computeAccessibleName(el, resolver) {
|
|
39
43
|
return computeAccessibleNameInternal(el, resolver, false, new Set());
|
|
40
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Computes the text alternative of an element as a node referenced by
|
|
47
|
+
* `aria-labelledby` or `aria-describedby` (the LabelledBy Traversal of Step 2B,
|
|
48
|
+
* which the description computation also uses).
|
|
49
|
+
*
|
|
50
|
+
* @param el - The referenced element
|
|
51
|
+
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
52
|
+
* @returns The computed text alternative and its source
|
|
53
|
+
* @see https://w3c.github.io/accname/#comp_labelledby_traversal
|
|
54
|
+
*/
|
|
55
|
+
export function computeReferencedTextAlternative(el, resolver) {
|
|
56
|
+
return computeReferenced(el, resolver, new Set(el.id ? [el.id] : []), computeAccessibleNameInternal);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Computes the host language text alternative of an element (AccName §4.3.2
|
|
60
|
+
* Step 2E, HTML-AAM §4.1), such as the `alt` of an image or the `title` of an
|
|
61
|
+
* SVG element, whether or not the element is exposed as presentational.
|
|
62
|
+
*
|
|
63
|
+
* Tells what an explicit `none` or `presentation` role drops from the name:
|
|
64
|
+
* every source the HTML-AAM rules of Step 2E give (`label`, `alt`, `value`,
|
|
65
|
+
* `placeholder`, `legend`, `caption`, `svg-title`, and the `default` label of a
|
|
66
|
+
* submit, reset or image button). The sources that Step 2E shares with other
|
|
67
|
+
* steps (`content`, the `title` attribute) are not dropped by the role, and give
|
|
68
|
+
* `null`.
|
|
69
|
+
*
|
|
70
|
+
* @param el - The element
|
|
71
|
+
* @param resolver - Environment-dependent resolver for DOM traversal and role queries
|
|
72
|
+
* @returns The text alternative and its source, or `null` if the element has none
|
|
73
|
+
* @see https://w3c.github.io/accname/#comp_host_language_label
|
|
74
|
+
*/
|
|
75
|
+
export function computeHostLanguageLabel(el, resolver) {
|
|
76
|
+
const result = getElementSpecificName(el, resolver, new Set(), computeAccessibleNameInternal, false);
|
|
77
|
+
if (!result?.source || result.source === 'content' || result.source === 'title') {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
41
82
|
/**
|
|
42
83
|
* @param inLabelledbyTraversal - When true, Step 2B is skipped to prevent re-entry
|
|
43
84
|
* into `resolveAriaLabelledby`, and Step 2F name-from-content is enabled regardless
|
|
44
85
|
* of role. This is set by Step 2B when processing each IDREF.
|
|
45
|
-
* @see https://
|
|
86
|
+
* @see https://w3c.github.io/accname/#computation-steps — AccName 1.2 §4.3.2
|
|
46
87
|
*/
|
|
47
88
|
export function computeAccessibleNameInternal(el, resolver, inLabelledbyTraversal, visited) {
|
|
48
|
-
// AccName 1.2 §4.3.2 Step 2A: Hidden elements return empty
|
|
49
|
-
|
|
89
|
+
// AccName 1.2 §4.3.2 Step 2A: Hidden elements return empty. In an aria-labelledby
|
|
90
|
+
// traversal whose referenced node is hidden, `computeReferenced` passes a
|
|
91
|
+
// resolver that finds nothing hidden.
|
|
92
|
+
const hidden = resolver.isHidden(el);
|
|
93
|
+
resolver.onVisit?.(el, { hidden, embeddedControl: false, inLabelledbyTraversal });
|
|
94
|
+
if (hidden) {
|
|
50
95
|
return EMPTY_RESULT;
|
|
51
96
|
}
|
|
52
97
|
// [Implementation-specific] Pre-computed name (e.g., Pretender integration) — checked after hidden check
|
|
@@ -68,8 +113,11 @@ export function computeAccessibleNameInternal(el, resolver, inLabelledbyTraversa
|
|
|
68
113
|
if (ariaLabelResult) {
|
|
69
114
|
return ariaLabelResult;
|
|
70
115
|
}
|
|
71
|
-
// AccName 1.2 §4.3.2 Step 2E: Element-specific name (HTML-AAM §4.1)
|
|
72
|
-
|
|
116
|
+
// AccName 1.2 §4.3.2 Step 2E: Element-specific name (HTML-AAM §4.1),
|
|
117
|
+
// "unless the current node is exposed as presentational"
|
|
118
|
+
const elementResult = resolver.isExposedAsPresentational?.(el)
|
|
119
|
+
? null
|
|
120
|
+
: getElementSpecificName(el, resolver, visited, computeAccessibleNameInternal, inLabelledbyTraversal);
|
|
73
121
|
if (elementResult) {
|
|
74
122
|
return elementResult;
|
|
75
123
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AccnameElement, AccnameResolver, AccnameResult } from './types.js';
|
|
1
|
+
import type { AccnameComputeFn, AccnameElement, AccnameResolver, AccnameResult } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Implements AccName 1.2 §4.3.2 Step 2E: for elements that have a native
|
|
4
4
|
* host language text alternative, use that alternative. The specific rules
|
|
@@ -7,7 +7,7 @@ import type { AccnameElement, AccnameResolver, AccnameResult } from './types.js'
|
|
|
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
|
-
export declare function getElementSpecificName(el: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn:
|
|
13
|
+
export declare function getElementSpecificName(el: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: AccnameComputeFn, inLabelledbyTraversal: boolean): AccnameResult | null;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- AccnameElement wraps mutable DOM types */
|
|
2
2
|
import { DEFAULT_IMAGE_LABEL, DEFAULT_RESET_LABEL, DEFAULT_SUBMIT_LABEL, TEXT_INPUT_TYPES, } from '../../../const/index.js';
|
|
3
|
-
import { findChildByLocalName, getInputType, getTextContent, isSvgElement,
|
|
3
|
+
import { findChildByLocalName, getInputType, getTextContent, isSvgElement, resolveNameFromContent } from './helpers.js';
|
|
4
4
|
import { resolveLabelText } from './label-steps.js';
|
|
5
|
+
import { makeResult } from './result.js';
|
|
5
6
|
/**
|
|
6
7
|
* Implements AccName 1.2 §4.3.2 Step 2E: for elements that have a native
|
|
7
8
|
* host language text alternative, use that alternative. The specific rules
|
|
@@ -10,8 +11,8 @@ import { resolveLabelText } from './label-steps.js';
|
|
|
10
11
|
* Returns null if no element-specific rule applies, letting the caller
|
|
11
12
|
* fall through to name-from-content (Step 2F) or title fallback (Step 2I).
|
|
12
13
|
*
|
|
13
|
-
* @see https://
|
|
14
|
-
* @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
|
|
15
16
|
*/
|
|
16
17
|
export function getElementSpecificName(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
17
18
|
if (isSvgElement(el)) {
|
|
@@ -67,9 +68,9 @@ export function getElementSpecificName(el, resolver, visited, computeFn, inLabel
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
/**
|
|
70
|
-
* HTML-AAM §
|
|
71
|
+
* HTML-AAM §3.5.76 (input[text-like], textarea, select, meter, progress, output).
|
|
71
72
|
* Name sources: label → title → placeholder.
|
|
72
|
-
* @see https://www.w3.org/TR/html-aam
|
|
73
|
+
* @see https://www.w3.org/TR/html-aam/#el-input-text
|
|
73
74
|
*/
|
|
74
75
|
function handleLabelableWithTitle(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
75
76
|
const labelResult = resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
@@ -87,13 +88,13 @@ function handleLabelableWithTitle(el, resolver, visited, computeFn, inLabelledby
|
|
|
87
88
|
return null;
|
|
88
89
|
}
|
|
89
90
|
/**
|
|
90
|
-
* HTML-AAM §
|
|
91
|
+
* HTML-AAM §3.5.76 (input — dispatches by type).
|
|
91
92
|
* Text-like types: label → title → placeholder.
|
|
92
93
|
* Button/submit/reset: label → value → default label.
|
|
93
94
|
* Image: label → alt → title → default label.
|
|
94
95
|
* Hidden: always empty.
|
|
95
96
|
* Checkbox/radio: label → title.
|
|
96
|
-
* @see https://www.w3.org/TR/html-aam
|
|
97
|
+
* @see https://www.w3.org/TR/html-aam/#el-input-text
|
|
97
98
|
*/
|
|
98
99
|
function handleInput(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
99
100
|
const type = getInputType(el);
|
|
@@ -113,9 +114,9 @@ function handleInput(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
|
113
114
|
return handleLabelableWithTitle(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
114
115
|
}
|
|
115
116
|
/**
|
|
116
|
-
* HTML-AAM §
|
|
117
|
+
* HTML-AAM §3.5.58 (input[type=button/submit/reset]).
|
|
117
118
|
* Name sources: label → value → title → default label (submit="Submit", reset="Reset").
|
|
118
|
-
* @see https://www.w3.org/TR/html-aam
|
|
119
|
+
* @see https://www.w3.org/TR/html-aam/#el-input-button
|
|
119
120
|
*/
|
|
120
121
|
function handleInputButton(el, type, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
121
122
|
const labelResult = resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
@@ -140,9 +141,9 @@ function handleInputButton(el, type, resolver, visited, computeFn, inLabelledbyT
|
|
|
140
141
|
return null;
|
|
141
142
|
}
|
|
142
143
|
/**
|
|
143
|
-
* HTML-AAM §
|
|
144
|
+
* HTML-AAM §3.5.66 (input[type=image]).
|
|
144
145
|
* Name sources: label → alt → title → default ("Submit Query").
|
|
145
|
-
* @see https://www.w3.org/TR/html-aam
|
|
146
|
+
* @see https://www.w3.org/TR/html-aam/#el-input-image
|
|
146
147
|
*/
|
|
147
148
|
function handleInputImage(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
148
149
|
const labelResult = resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
@@ -160,9 +161,9 @@ function handleInputImage(el, resolver, visited, computeFn, inLabelledbyTraversa
|
|
|
160
161
|
return makeResult(DEFAULT_IMAGE_LABEL, 'default');
|
|
161
162
|
}
|
|
162
163
|
/**
|
|
163
|
-
* HTML-AAM §
|
|
164
|
+
* HTML-AAM §3.5.20 (button).
|
|
164
165
|
* Name sources: label → content → title.
|
|
165
|
-
* @see https://www.w3.org/TR/html-aam
|
|
166
|
+
* @see https://www.w3.org/TR/html-aam/#el-button
|
|
166
167
|
*/
|
|
167
168
|
function handleButton(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
168
169
|
const labelResult = resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
@@ -180,9 +181,9 @@ function handleButton(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
|
180
181
|
return null;
|
|
181
182
|
}
|
|
182
183
|
/**
|
|
183
|
-
* HTML-AAM §
|
|
184
|
+
* HTML-AAM §3.5.40 (fieldset).
|
|
184
185
|
* Name sources: legend (content) → title.
|
|
185
|
-
* @see https://www.w3.org/TR/html-aam
|
|
186
|
+
* @see https://www.w3.org/TR/html-aam/#el-fieldset
|
|
186
187
|
*/
|
|
187
188
|
function handleFieldset(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
188
189
|
const legend = findChildByLocalName(el, 'legend', resolver);
|
|
@@ -199,9 +200,9 @@ function handleFieldset(el, resolver, visited, computeFn, inLabelledbyTraversal)
|
|
|
199
200
|
return null;
|
|
200
201
|
}
|
|
201
202
|
/**
|
|
202
|
-
* HTML-AAM §
|
|
203
|
+
* HTML-AAM §3.5.127 (table).
|
|
203
204
|
* Name sources: caption (content) → title.
|
|
204
|
-
* @see https://www.w3.org/TR/html-aam
|
|
205
|
+
* @see https://www.w3.org/TR/html-aam/#el-table
|
|
205
206
|
*/
|
|
206
207
|
function handleTable(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
207
208
|
const caption = findChildByLocalName(el, 'caption', resolver);
|
|
@@ -218,10 +219,10 @@ function handleTable(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
|
218
219
|
return null;
|
|
219
220
|
}
|
|
220
221
|
/**
|
|
221
|
-
* HTML-AAM §
|
|
222
|
+
* HTML-AAM §3.5.56 (img).
|
|
222
223
|
* Name sources: alt → title (only when alt is not specified).
|
|
223
224
|
* When alt="" (empty), returns empty name with null source (decorative image).
|
|
224
|
-
* @see https://www.w3.org/TR/html-aam
|
|
225
|
+
* @see https://www.w3.org/TR/html-aam/#el-img
|
|
225
226
|
*/
|
|
226
227
|
function handleImg(el) {
|
|
227
228
|
if (el.hasAttribute('alt')) {
|
|
@@ -235,9 +236,9 @@ function handleImg(el) {
|
|
|
235
236
|
return null;
|
|
236
237
|
}
|
|
237
238
|
/**
|
|
238
|
-
* HTML-AAM §
|
|
239
|
+
* HTML-AAM §3.5.6 (area).
|
|
239
240
|
* Name sources: alt → title.
|
|
240
|
-
* @see https://www.w3.org/TR/html-aam
|
|
241
|
+
* @see https://www.w3.org/TR/html-aam/#el-area
|
|
241
242
|
*/
|
|
242
243
|
function handleArea(el) {
|
|
243
244
|
const alt = el.getAttribute('alt');
|
|
@@ -251,10 +252,10 @@ function handleArea(el) {
|
|
|
251
252
|
return null;
|
|
252
253
|
}
|
|
253
254
|
/**
|
|
254
|
-
* HTML-AAM §
|
|
255
|
+
* HTML-AAM §3.5.42 (figure).
|
|
255
256
|
* Name sources: title only. Note: figcaption provides the accessible
|
|
256
257
|
* *description*, not the name, per HTML-AAM.
|
|
257
|
-
* @see https://www.w3.org/TR/html-aam
|
|
258
|
+
* @see https://www.w3.org/TR/html-aam/#el-figure
|
|
258
259
|
*/
|
|
259
260
|
function handleFigure(el) {
|
|
260
261
|
const title = el.getAttribute('title');
|
|
@@ -264,9 +265,9 @@ function handleFigure(el) {
|
|
|
264
265
|
return null;
|
|
265
266
|
}
|
|
266
267
|
/**
|
|
267
|
-
* HTML-AAM §
|
|
268
|
+
* HTML-AAM §3.5.124 (summary).
|
|
268
269
|
* Name sources: content → title.
|
|
269
|
-
* @see https://www.w3.org/TR/html-aam
|
|
270
|
+
* @see https://www.w3.org/TR/html-aam/#el-summary
|
|
270
271
|
*/
|
|
271
272
|
function handleSummary(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
272
273
|
const content = resolveNameFromContent(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
@@ -280,9 +281,9 @@ function handleSummary(el, resolver, visited, computeFn, inLabelledbyTraversal)
|
|
|
280
281
|
return null;
|
|
281
282
|
}
|
|
282
283
|
/**
|
|
283
|
-
* HTML-AAM §
|
|
284
|
+
* HTML-AAM §3.5.2 (a[href]).
|
|
284
285
|
* Name sources: content → title.
|
|
285
|
-
* @see https://www.w3.org/TR/html-aam
|
|
286
|
+
* @see https://www.w3.org/TR/html-aam/#el-a
|
|
286
287
|
*/
|
|
287
288
|
function handleAnchor(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
288
289
|
const content = resolveNameFromContent(el, resolver, visited, computeFn, inLabelledbyTraversal);
|
|
@@ -296,8 +297,8 @@ function handleAnchor(el, resolver, visited, computeFn, inLabelledbyTraversal) {
|
|
|
296
297
|
return null;
|
|
297
298
|
}
|
|
298
299
|
/**
|
|
299
|
-
* HTML-AAM §
|
|
300
|
-
* @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
|
|
301
302
|
*/
|
|
302
303
|
function handleTitleOnly(el) {
|
|
303
304
|
const title = el.getAttribute('title');
|
|
@@ -308,7 +309,7 @@ function handleTitleOnly(el) {
|
|
|
308
309
|
}
|
|
309
310
|
/**
|
|
310
311
|
* SVG-AAM: SVG elements get name from `<title>` child element.
|
|
311
|
-
* @see https://www.w3.org/TR/svg-aam
|
|
312
|
+
* @see https://www.w3.org/TR/svg-aam/#mapping_additional_nd — SVG-AAM §7.1
|
|
312
313
|
*/
|
|
313
314
|
function handleSvgElement(el, resolver) {
|
|
314
315
|
const titleEl = findChildByLocalName(el, 'title', resolver);
|