@markuplint/ml-spec 5.4.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 (46) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +4 -0
  3. package/lib/algorithm/aria/accname/aria-steps.d.ts +12 -7
  4. package/lib/algorithm/aria/accname/aria-steps.js +16 -7
  5. package/lib/algorithm/aria/accname/compute.d.ts +3 -3
  6. package/lib/algorithm/aria/accname/compute.js +3 -3
  7. package/lib/algorithm/aria/accname/element-names.d.ts +2 -2
  8. package/lib/algorithm/aria/accname/element-names.js +29 -29
  9. package/lib/algorithm/aria/accname/helpers.d.ts +30 -9
  10. package/lib/algorithm/aria/accname/helpers.js +34 -12
  11. package/lib/algorithm/aria/accname/label-steps.d.ts +2 -2
  12. package/lib/algorithm/aria/accname/label-steps.js +2 -2
  13. package/lib/algorithm/aria/accname/svg-helpers.d.ts +2 -2
  14. package/lib/algorithm/aria/accname/svg-helpers.js +2 -2
  15. package/lib/algorithm/aria/accname/types.d.ts +12 -0
  16. package/lib/algorithm/aria/accname-computation.d.ts +1 -1
  17. package/lib/algorithm/aria/accname-computation.js +6 -6
  18. package/lib/algorithm/aria/get-aria.js +2 -2
  19. package/lib/algorithm/aria/get-computed-role.js +14 -9
  20. package/lib/algorithm/aria/get-explicit-role.d.ts +2 -2
  21. package/lib/algorithm/aria/get-explicit-role.js +11 -4
  22. package/lib/algorithm/aria/get-non-presentational-ancestor.d.ts +2 -0
  23. package/lib/algorithm/aria/get-non-presentational-ancestor.js +5 -2
  24. package/lib/algorithm/aria/is-exposed.d.ts +3 -1
  25. package/lib/algorithm/aria/is-exposed.js +15 -7
  26. package/lib/algorithm/aria/is-presentational.d.ts +2 -3
  27. package/lib/algorithm/aria/is-presentational.js +2 -3
  28. package/lib/algorithm/aria/matches-context-role.d.ts +3 -1
  29. package/lib/algorithm/aria/matches-context-role.js +10 -7
  30. package/lib/algorithm/dom/tree-boundary.d.ts +93 -0
  31. package/lib/algorithm/dom/tree-boundary.js +103 -0
  32. package/lib/algorithm/html/content-model-category-to-tag-names.d.ts +7 -3
  33. package/lib/algorithm/html/content-model-category-to-tag-names.js +7 -3
  34. package/lib/algorithm/html/select-display-size.d.ts +29 -0
  35. package/lib/algorithm/html/select-display-size.js +38 -0
  36. package/lib/const/accname.d.ts +1 -1
  37. package/lib/const/accname.js +1 -1
  38. package/lib/index.d.ts +2 -0
  39. package/lib/index.js +3 -0
  40. package/lib/types/index.d.ts +63 -2
  41. package/lib/types/permitted-structures.d.ts +5 -1
  42. package/lib/utils/directive-resolver.d.ts +8 -0
  43. package/lib/utils/directive-resolver.js +1 -0
  44. package/lib/utils/schema-to-spec.d.ts +4 -0
  45. package/lib/utils/schema-to-spec.js +42 -0
  46. package/package.json +6 -5
package/CHANGELOG.md CHANGED
@@ -3,6 +3,23 @@
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
+
6
23
  # [5.4.0](https://github.com/markuplint/markuplint/compare/v5.3.0...v5.4.0) (2026-10-29)
7
24
 
8
25
  ### 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
- * "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,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://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
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
- * @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
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
- * "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 './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://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');
@@ -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
- * @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
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://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2
35
- * @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
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://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
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://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2
40
- * @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
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://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
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://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
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://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2E
15
- * @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
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 §4.1 (input[text-like], textarea, select, meter, progress, output).
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-1.0/#el-input-text
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 §4.1 (input — dispatches by type).
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-1.0/#el-input-text
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 §4.1 (input[type=button/submit/reset]).
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-1.0/#el-input-button
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 §4.1 (input[type=image]).
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-1.0/#el-input-image
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 §4.1 (button).
164
+ * HTML-AAM §3.5.20 (button).
165
165
  * Name sources: label → content → title.
166
- * @see https://www.w3.org/TR/html-aam-1.0/#el-button
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 §4.1 (fieldset).
184
+ * HTML-AAM §3.5.40 (fieldset).
185
185
  * Name sources: legend (content) → title.
186
- * @see https://www.w3.org/TR/html-aam-1.0/#el-fieldset
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 §4.1 (table).
203
+ * HTML-AAM §3.5.127 (table).
204
204
  * Name sources: caption (content) → title.
205
- * @see https://www.w3.org/TR/html-aam-1.0/#el-table
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 §4.1 (img).
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-1.0/#el-img
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 §4.1 (area).
239
+ * HTML-AAM §3.5.6 (area).
240
240
  * Name sources: alt → title.
241
- * @see https://www.w3.org/TR/html-aam-1.0/#el-area
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 §4.1 (figure).
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-1.0/#el-figure
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 §4.1 (summary).
268
+ * HTML-AAM §3.5.124 (summary).
269
269
  * Name sources: content → title.
270
- * @see https://www.w3.org/TR/html-aam-1.0/#el-summary
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 §4.1 (a[href]).
284
+ * HTML-AAM §3.5.2 (a[href]).
285
285
  * Name sources: content → title.
286
- * @see https://www.w3.org/TR/html-aam-1.0/#el-a
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 §4.1 (iframe) — name from title attribute only.
301
- * @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
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-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
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
- * - **Step 2F**: "If the current node's role allows name from content [...],
7
- * return the accumulated text of the current node's descendant nodes."
8
- * - **Step 2C (Embedded Controls)**: "If the current node is a descendant of
9
- * an `aria-labelledby` or `aria-label` reference AND the current node is
10
- * an embedded control, return the embedded control's value." Per spec,
11
- * `aria-label` is ignored for embedded controls during this traversal.
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://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2F
20
- * @see https://www.w3.org/TR/accname-1.2/#comp_embedded_control — AccName 1.2 §4.3.2 Step 2C
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://w3c.github.io/svgwg/svg2-draft/struct.html#TermNeverRenderedElement
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
- * - **Step 2F**: "If the current node's role allows name from content [...],
9
- * return the accumulated text of the current node's descendant nodes."
10
- * - **Step 2C (Embedded Controls)**: "If the current node is a descendant of
11
- * an `aria-labelledby` or `aria-label` reference AND the current node is
12
- * an embedded control, return the embedded control's value." Per spec,
13
- * `aria-label` is ignored for embedded controls during this traversal.
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://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2F
22
- * @see https://www.w3.org/TR/accname-1.2/#comp_embedded_control — AccName 1.2 §4.3.2 Step 2C
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://www.w3.org/TR/accname-1.2/#comp_embedded_control — AccName 1.2 §4.3.2 Step 2C
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.parentElement;
302
+ let current = getTreeParentElement(el);
281
303
  while (current) {
282
304
  if (current.localName === 'label') {
283
305
  return current;
284
306
  }
285
- current = current.parentElement;
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://w3c.github.io/svgwg/svg2-draft/struct.html#TermNeverRenderedElement
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://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2E
7
- * @see https://www.w3.org/TR/html-aam-1.0/#el-input-text — HTML-AAM label association
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://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2E
10
- * @see https://www.w3.org/TR/html-aam-1.0/#el-input-text — HTML-AAM label association
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 §5.1.1. This is a lightweight check that avoids the full AccName
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-1.0/#include_elements — SVG-AAM §5.1.1
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 §5.1.1. This is a lightweight check that avoids the full AccName
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-1.0/#include_elements — SVG-AAM §5.1.1
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://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2
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;