@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.
Files changed (50) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +4 -0
  3. package/lib/algorithm/aria/accname/aria-steps.d.ts +26 -9
  4. package/lib/algorithm/aria/accname/aria-steps.js +38 -9
  5. package/lib/algorithm/aria/accname/compute.d.ts +38 -6
  6. package/lib/algorithm/aria/accname/compute.js +60 -12
  7. package/lib/algorithm/aria/accname/element-names.d.ts +4 -4
  8. package/lib/algorithm/aria/accname/element-names.js +31 -30
  9. package/lib/algorithm/aria/accname/helpers.d.ts +52 -12
  10. package/lib/algorithm/aria/accname/helpers.js +78 -23
  11. package/lib/algorithm/aria/accname/index.d.ts +2 -2
  12. package/lib/algorithm/aria/accname/index.js +1 -1
  13. package/lib/algorithm/aria/accname/label-steps.d.ts +4 -4
  14. package/lib/algorithm/aria/accname/label-steps.js +14 -5
  15. package/lib/algorithm/aria/accname/result.d.ts +21 -0
  16. package/lib/algorithm/aria/accname/result.js +28 -0
  17. package/lib/algorithm/aria/accname/svg-helpers.d.ts +2 -2
  18. package/lib/algorithm/aria/accname/svg-helpers.js +2 -2
  19. package/lib/algorithm/aria/accname/types.d.ts +51 -0
  20. package/lib/algorithm/aria/accname-computation.d.ts +1 -1
  21. package/lib/algorithm/aria/accname-computation.js +12 -6
  22. package/lib/algorithm/aria/get-aria.js +2 -2
  23. package/lib/algorithm/aria/get-computed-role.js +18 -14
  24. package/lib/algorithm/aria/get-explicit-role.d.ts +6 -0
  25. package/lib/algorithm/aria/get-explicit-role.js +82 -9
  26. package/lib/algorithm/aria/get-non-presentational-ancestor.d.ts +2 -0
  27. package/lib/algorithm/aria/get-non-presentational-ancestor.js +5 -2
  28. package/lib/algorithm/aria/is-exposed.d.ts +3 -1
  29. package/lib/algorithm/aria/is-exposed.js +15 -7
  30. package/lib/algorithm/aria/is-presentational.d.ts +15 -4
  31. package/lib/algorithm/aria/is-presentational.js +16 -3
  32. package/lib/algorithm/aria/matches-context-role.d.ts +3 -1
  33. package/lib/algorithm/aria/matches-context-role.js +10 -7
  34. package/lib/algorithm/dom/tree-boundary.d.ts +93 -0
  35. package/lib/algorithm/dom/tree-boundary.js +103 -0
  36. package/lib/algorithm/html/content-model-category-to-tag-names.d.ts +7 -3
  37. package/lib/algorithm/html/content-model-category-to-tag-names.js +7 -3
  38. package/lib/algorithm/html/select-display-size.d.ts +29 -0
  39. package/lib/algorithm/html/select-display-size.js +38 -0
  40. package/lib/const/accname.d.ts +1 -1
  41. package/lib/const/accname.js +1 -1
  42. package/lib/index.d.ts +2 -0
  43. package/lib/index.js +3 -0
  44. package/lib/types/index.d.ts +67 -2
  45. package/lib/types/permitted-structures.d.ts +5 -1
  46. package/lib/utils/directive-resolver.d.ts +8 -0
  47. package/lib/utils/directive-resolver.js +1 -0
  48. package/lib/utils/schema-to-spec.d.ts +4 -0
  49. package/lib/utils/schema-to-spec.js +42 -0
  50. 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
- * "If the current node has an `aria-labelledby` attribute that contains
5
- * at least one valid IDREF, and the current node is not already part of
6
- * an ongoing `aria-labelledby` traversal, process its IDREFs [...]"
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://www.w3.org/TR/accname-1.2/#comp_labelledby_traversal — AccName 1.2 §4.3.2 Step 2B
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 resolveAriaLabelledby(el: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: (el: AccnameElement, resolver: AccnameResolver, inLabelledbyTraversal: boolean, visited: ReadonlySet<string>) => AccnameResult): AccnameResult | null;
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
- * @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2D
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 { flattenText, makeResult } from './helpers.js';
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
- * "If the current node has an `aria-labelledby` attribute that contains
6
- * at least one valid IDREF, and the current node is not already part of
7
- * an ongoing `aria-labelledby` traversal, process its IDREFs [...]"
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://www.w3.org/TR/accname-1.2/#comp_labelledby_traversal — AccName 1.2 §4.3.2 Step 2B
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 = computeFn(referenced, resolver, true, innerVisited);
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
- * @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2D
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`) and NOT
12
- * referenced by `aria-labelledby`, return empty immediately.
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://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
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://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2
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 { makeResult, resolveNameFromContent } from './helpers.js';
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`) and NOT
16
- * referenced by `aria-labelledby`, return empty immediately.
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://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
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://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2
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 unless referenced by aria-labelledby
49
- if (!inLabelledbyTraversal && resolver.isHidden(el)) {
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
- const elementResult = getElementSpecificName(el, resolver, visited, computeAccessibleNameInternal, inLabelledbyTraversal);
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://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2E
11
- * @see https://www.w3.org/TR/html-aam-1.0/#accessible-name-and-description-computation — HTML-AAM §4.1
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: (el: AccnameElement, resolver: AccnameResolver, inLabelledbyTraversal: boolean, visited: ReadonlySet<string>) => AccnameResult, inLabelledbyTraversal: boolean): AccnameResult | null;
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, makeResult, resolveNameFromContent, } from './helpers.js';
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://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2E
14
- * @see https://www.w3.org/TR/html-aam-1.0/#accessible-name-and-description-computation — HTML-AAM §4.1
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 §4.1 (input[text-like], textarea, select, meter, progress, output).
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-1.0/#el-input-text
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 §4.1 (input — dispatches by type).
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-1.0/#el-input-text
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 §4.1 (input[type=button/submit/reset]).
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-1.0/#el-input-button
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 §4.1 (input[type=image]).
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-1.0/#el-input-image
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 §4.1 (button).
164
+ * HTML-AAM §3.5.20 (button).
164
165
  * Name sources: label → content → title.
165
- * @see https://www.w3.org/TR/html-aam-1.0/#el-button
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 §4.1 (fieldset).
184
+ * HTML-AAM §3.5.40 (fieldset).
184
185
  * Name sources: legend (content) → title.
185
- * @see https://www.w3.org/TR/html-aam-1.0/#el-fieldset
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 §4.1 (table).
203
+ * HTML-AAM §3.5.127 (table).
203
204
  * Name sources: caption (content) → title.
204
- * @see https://www.w3.org/TR/html-aam-1.0/#el-table
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 §4.1 (img).
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-1.0/#el-img
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 §4.1 (area).
239
+ * HTML-AAM §3.5.6 (area).
239
240
  * Name sources: alt → title.
240
- * @see https://www.w3.org/TR/html-aam-1.0/#el-area
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 §4.1 (figure).
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-1.0/#el-figure
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 §4.1 (summary).
268
+ * HTML-AAM §3.5.124 (summary).
268
269
  * Name sources: content → title.
269
- * @see https://www.w3.org/TR/html-aam-1.0/#el-summary
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 §4.1 (a[href]).
284
+ * HTML-AAM §3.5.2 (a[href]).
284
285
  * Name sources: content → title.
285
- * @see https://www.w3.org/TR/html-aam-1.0/#el-a
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 §4.1 (iframe) — name from title attribute only.
300
- * @see https://www.w3.org/TR/html-aam-1.0/#el-iframe
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-1.0/#mapping_additional_nd — SVG-AAM §8.1
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);