@markuplint/ml-spec 4.10.2 → 5.0.0-alpha.1

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 (78) hide show
  1. package/ARCHITECTURE.ja.md +24 -10
  2. package/ARCHITECTURE.md +24 -10
  3. package/CHANGELOG.md +34 -0
  4. package/README.md +2 -2
  5. package/docs/aria-algorithms.ja.md +180 -33
  6. package/docs/aria-algorithms.md +183 -34
  7. package/docs/html-algorithms.ja.md +2 -2
  8. package/docs/html-algorithms.md +2 -2
  9. package/docs/maintenance.ja.md +13 -22
  10. package/docs/maintenance.md +13 -22
  11. package/docs/spec-resolution.ja.md +44 -9
  12. package/docs/spec-resolution.md +53 -16
  13. package/docs/type-definitions.ja.md +8 -4
  14. package/docs/type-definitions.md +8 -4
  15. package/lib/algorithm/aria/accname/__tests__/test-helpers.d.ts +45 -0
  16. package/lib/algorithm/aria/accname/__tests__/test-helpers.js +120 -0
  17. package/lib/algorithm/aria/accname/aria-steps.d.ts +51 -0
  18. package/lib/algorithm/aria/accname/aria-steps.js +104 -0
  19. package/lib/algorithm/aria/accname/compute.d.ts +51 -0
  20. package/lib/algorithm/aria/accname/compute.js +101 -0
  21. package/lib/algorithm/aria/accname/element-names.d.ts +36 -0
  22. package/lib/algorithm/aria/accname/element-names.js +342 -0
  23. package/lib/algorithm/aria/accname/helpers.d.ts +98 -0
  24. package/lib/algorithm/aria/accname/helpers.js +330 -0
  25. package/lib/algorithm/aria/accname/index.d.ts +4 -0
  26. package/lib/algorithm/aria/accname/index.js +3 -0
  27. package/lib/algorithm/aria/accname/label-steps.d.ts +25 -0
  28. package/lib/algorithm/aria/accname/label-steps.js +74 -0
  29. package/lib/algorithm/aria/accname/svg-helpers.d.ts +17 -0
  30. package/lib/algorithm/aria/accname/svg-helpers.js +30 -0
  31. package/lib/algorithm/aria/accname/types.d.ts +70 -0
  32. package/lib/algorithm/aria/accname/types.js +2 -0
  33. package/lib/algorithm/aria/accname-computation.d.ts +14 -3
  34. package/lib/algorithm/aria/accname-computation.js +131 -8
  35. package/lib/algorithm/aria/aria-specs.d.ts +1 -0
  36. package/lib/algorithm/aria/get-aria.js +30 -4
  37. package/lib/algorithm/aria/get-computed-role.js +106 -26
  38. package/lib/algorithm/aria/get-explicit-role.d.ts +12 -0
  39. package/lib/algorithm/aria/get-explicit-role.js +12 -0
  40. package/lib/algorithm/aria/get-non-presentational-ancestor.d.ts +12 -0
  41. package/lib/algorithm/aria/get-non-presentational-ancestor.js +14 -2
  42. package/lib/algorithm/aria/get-permitted-roles-spec.d.ts +8 -4
  43. package/lib/algorithm/aria/get-permitted-roles-spec.js +19 -6
  44. package/lib/algorithm/aria/get-role-spec.js +10 -3
  45. package/lib/algorithm/aria/has-required-owned-elements.d.ts +2 -1
  46. package/lib/algorithm/aria/has-required-owned-elements.js +18 -15
  47. package/lib/algorithm/aria/is-presentational.d.ts +24 -0
  48. package/lib/algorithm/aria/is-presentational.js +31 -0
  49. package/lib/algorithm/aria/matches-context-role.d.ts +12 -4
  50. package/lib/algorithm/aria/matches-context-role.js +39 -6
  51. package/lib/algorithm/html/content-model-category-to-tag-names.js +1 -1
  52. package/lib/algorithm/html/get-content-model.d.ts +4 -2
  53. package/lib/algorithm/html/get-content-model.js +6 -7
  54. package/lib/const/accname.d.ts +29 -0
  55. package/lib/const/accname.js +76 -0
  56. package/lib/const/dom.d.ts +8 -0
  57. package/lib/const/dom.js +8 -0
  58. package/lib/const/index.d.ts +2 -0
  59. package/lib/const/index.js +2 -0
  60. package/lib/index.d.ts +3 -0
  61. package/lib/index.js +4 -0
  62. package/lib/types/aria.d.ts +3 -3
  63. package/lib/types/index.d.ts +65 -0
  64. package/lib/utils/aria-version.d.ts +1 -1
  65. package/lib/utils/aria-version.js +1 -1
  66. package/lib/utils/directive-resolver.d.ts +23 -0
  67. package/lib/utils/directive-resolver.js +50 -0
  68. package/lib/utils/get-attr-specs-spec.js +4 -3
  69. package/lib/utils/get-ns.d.ts +7 -0
  70. package/lib/utils/get-ns.js +7 -0
  71. package/lib/utils/get-spec-by-tag-name.d.ts +1 -1
  72. package/lib/utils/merge-array.d.ts +10 -0
  73. package/lib/utils/merge-array.js +10 -0
  74. package/lib/utils/resolve-version.d.ts +11 -0
  75. package/lib/utils/resolve-version.js +13 -1
  76. package/lib/utils/schema-to-spec.d.ts +2 -0
  77. package/lib/utils/schema-to-spec.js +15 -4
  78. package/package.json +9 -7
@@ -0,0 +1,101 @@
1
+ /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- AccnameElement wraps mutable DOM types */
2
+ import { resolveAriaLabel, resolveAriaLabelledby } from './aria-steps.js';
3
+ import { getElementSpecificName } from './element-names.js';
4
+ import { makeResult, resolveNameFromContent } from './helpers.js';
5
+ const EMPTY_RESULT = { name: '', source: null };
6
+ /**
7
+ * Computes the accessible name for an element.
8
+ *
9
+ * Implements the Accessible Name and Description Computation algorithm
10
+ * per AccName 1.2 §4.3.2 ("Computation Steps") and HTML-AAM §4.1
11
+ * ("Accessible Name and Description Computation").
12
+ *
13
+ * Control flow (Steps map to AccName 1.2 §4.3.2):
14
+ *
15
+ * 1. **Step 2A — Hidden check** — If the element is hidden (per `isHidden`) and NOT
16
+ * referenced by `aria-labelledby`, return empty immediately.
17
+ * 2. **Pre-computed name** — (Implementation-specific extension) If the
18
+ * resolver provides `getPrecomputedName`, check it before standard steps.
19
+ * Used by ml-core's Pretender integration for framework components.
20
+ * 3. **Step 2B — `aria-labelledby`** — Resolve referenced elements and
21
+ * recursively compute their names. Skipped when already inside a
22
+ * labelledby traversal (`inLabelledbyTraversal`) to prevent re-entry.
23
+ * 4. **Step 2D — `aria-label`** — Use the `aria-label` attribute value.
24
+ * 5. **Step 2E — Element-specific name** — Dispatch to HTML-AAM §4.1
25
+ * element-specific rules (label association, alt, value, legend, caption, SVG title).
26
+ * 6. **Step 2F — Name from content** — If the element's role allows
27
+ * `nameFrom: ["content"]`, or the element is referenced by `aria-labelledby`
28
+ * (`inLabelledbyTraversal`), recursively collect child text (including
29
+ * embedded control values per Step 2C).
30
+ * 7. **Step 2I — Title fallback** — Use the `title` attribute value.
31
+ *
32
+ * @param el - The element to compute the accessible name for
33
+ * @param resolver - Environment-dependent resolver for DOM traversal and role queries
34
+ * @returns The computed name and its source
35
+ * @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2
36
+ * @see https://www.w3.org/TR/html-aam-1.0/#accessible-name-and-description-computation — HTML-AAM §4.1
37
+ */
38
+ export function computeAccessibleName(el, resolver) {
39
+ return computeAccessibleNameInternal(el, resolver, false, new Set());
40
+ }
41
+ /**
42
+ * Internal recursive entry point for accessible name computation.
43
+ *
44
+ * This function is the recursive core called by `resolveAriaLabelledby` (Step 2B),
45
+ * `resolveLabelText` (Step 2E), and `resolveNameFromContent` (Step 2F) when they
46
+ * need to compute a child or referenced element's name.
47
+ *
48
+ * @param el - The element to compute the accessible name for
49
+ * @param resolver - Environment-dependent resolver for DOM traversal and role queries
50
+ * @param inLabelledbyTraversal - When true, Step 2B is skipped to prevent re-entry
51
+ * into `resolveAriaLabelledby`, and Step 2F name-from-content is enabled regardless
52
+ * of role. This is set by Step 2B when processing each IDREF.
53
+ * @param visited - Set of element IDs already visited (cycle prevention for aria-labelledby)
54
+ * @returns The computed name and its source
55
+ * @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2
56
+ */
57
+ export function computeAccessibleNameInternal(el, resolver, inLabelledbyTraversal, visited) {
58
+ // AccName 1.2 §4.3.2 Step 2A: Hidden elements return empty unless referenced by aria-labelledby
59
+ if (!inLabelledbyTraversal && resolver.isHidden(el)) {
60
+ return EMPTY_RESULT;
61
+ }
62
+ // [Implementation-specific] Pre-computed name (e.g., Pretender integration) — checked after hidden check
63
+ if (resolver.getPrecomputedName) {
64
+ const precomputed = resolver.getPrecomputedName(el);
65
+ if (precomputed != null) {
66
+ return makeResult(precomputed, 'content');
67
+ }
68
+ }
69
+ // AccName 1.2 §4.3.2 Step 2B: aria-labelledby (skipped when inside labelledby traversal)
70
+ if (!inLabelledbyTraversal) {
71
+ const labelledbyResult = resolveAriaLabelledby(el, resolver, visited, computeAccessibleNameInternal);
72
+ if (labelledbyResult) {
73
+ return labelledbyResult;
74
+ }
75
+ }
76
+ // AccName 1.2 §4.3.2 Step 2D: aria-label
77
+ const ariaLabelResult = resolveAriaLabel(el);
78
+ if (ariaLabelResult) {
79
+ return ariaLabelResult;
80
+ }
81
+ // AccName 1.2 §4.3.2 Step 2E: Element-specific name (HTML-AAM §4.1)
82
+ const elementResult = getElementSpecificName(el, resolver, visited, computeAccessibleNameInternal, inLabelledbyTraversal);
83
+ if (elementResult) {
84
+ return elementResult;
85
+ }
86
+ // AccName 1.2 §4.3.2 Step 2F/2C: Name from content
87
+ // Applies when the role allows nameFrom: ["content"] OR when the element is
88
+ // directly referenced by aria-labelledby (inLabelledbyTraversal).
89
+ if (resolver.allowsNameFromContent(el) || inLabelledbyTraversal) {
90
+ const content = resolveNameFromContent(el, resolver, visited, computeAccessibleNameInternal, inLabelledbyTraversal);
91
+ if (content.trim()) {
92
+ return makeResult(content, 'content');
93
+ }
94
+ }
95
+ // AccName 1.2 §4.3.2 Step 2I: Title attribute fallback
96
+ const title = el.getAttribute('title');
97
+ if (title?.trim()) {
98
+ return makeResult(title, 'title');
99
+ }
100
+ return EMPTY_RESULT;
101
+ }
@@ -0,0 +1,36 @@
1
+ import type { AccnameElement, AccnameResolver, AccnameResult } from './types.js';
2
+ /**
3
+ * Computes element-specific accessible name per HTML-AAM §4.1.
4
+ *
5
+ * Implements AccName 1.2 §4.3.2 Step 2E: for elements that have a native
6
+ * host language text alternative, use that alternative. The specific rules
7
+ * for each HTML element are defined in HTML-AAM §4.1.
8
+ *
9
+ * Returns null if no element-specific rule applies, letting the caller
10
+ * fall through to name-from-content (Step 2F) or title fallback (Step 2I).
11
+ *
12
+ * Control flow (dispatches by `localName`):
13
+ * - SVG elements → `handleSvgElement` (SVG-AAM: `<title>` child)
14
+ * - `<input>` → `handleInput` (branches by type: text-like, button, image, hidden)
15
+ * - `<textarea>`, `<select>`, `<meter>`, `<progress>`, `<output>` → `handleLabelableWithTitle`
16
+ * - `<button>` → `handleButton` (label → content → title)
17
+ * - `<fieldset>` → `handleFieldset` (legend → title)
18
+ * - `<table>` → `handleTable` (caption → title)
19
+ * - `<img>` → `handleImg` (alt → title)
20
+ * - `<area>` → `handleArea` (alt → title)
21
+ * - `<figure>` → `handleFigure` (title only)
22
+ * - `<summary>` → `handleSummary` (content → title)
23
+ * - `<a href>` → `handleAnchor` (content → title)
24
+ * - `<iframe>` → `handleTitleOnly` (title only)
25
+ * - All others → null (rely on caller's generic Steps 2F/2I)
26
+ *
27
+ * @param el - The element to compute the name for
28
+ * @param resolver - Environment-dependent resolver for DOM traversal and role queries
29
+ * @param visited - Set of element IDs already visited (cycle prevention)
30
+ * @param computeFn - The recursive accessible name computation function
31
+ * @param inLabelledbyTraversal - Whether this computation is part of an aria-labelledby traversal
32
+ * @returns The computed name result, or null if no element-specific rule applies
33
+ * @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2E
34
+ * @see https://www.w3.org/TR/html-aam-1.0/#accessible-name-and-description-computation — HTML-AAM §4.1
35
+ */
36
+ export declare function getElementSpecificName(el: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: (el: AccnameElement, resolver: AccnameResolver, inLabelledbyTraversal: boolean, visited: ReadonlySet<string>) => AccnameResult, inLabelledbyTraversal: boolean): AccnameResult | null;
@@ -0,0 +1,342 @@
1
+ /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types -- AccnameElement wraps mutable DOM types */
2
+ import { DEFAULT_IMAGE_LABEL, DEFAULT_RESET_LABEL, DEFAULT_SUBMIT_LABEL, TEXT_INPUT_TYPES, } from '../../../const/index.js';
3
+ import { findChildByLocalName, getInputType, isSvgElement, makeResult, resolveNameFromContent } from './helpers.js';
4
+ import { resolveLabelText } from './label-steps.js';
5
+ /**
6
+ * Computes element-specific accessible name per HTML-AAM §4.1.
7
+ *
8
+ * Implements AccName 1.2 §4.3.2 Step 2E: for elements that have a native
9
+ * host language text alternative, use that alternative. The specific rules
10
+ * for each HTML element are defined in HTML-AAM §4.1.
11
+ *
12
+ * Returns null if no element-specific rule applies, letting the caller
13
+ * fall through to name-from-content (Step 2F) or title fallback (Step 2I).
14
+ *
15
+ * Control flow (dispatches by `localName`):
16
+ * - SVG elements → `handleSvgElement` (SVG-AAM: `<title>` child)
17
+ * - `<input>` → `handleInput` (branches by type: text-like, button, image, hidden)
18
+ * - `<textarea>`, `<select>`, `<meter>`, `<progress>`, `<output>` → `handleLabelableWithTitle`
19
+ * - `<button>` → `handleButton` (label → content → title)
20
+ * - `<fieldset>` → `handleFieldset` (legend → title)
21
+ * - `<table>` → `handleTable` (caption → title)
22
+ * - `<img>` → `handleImg` (alt → title)
23
+ * - `<area>` → `handleArea` (alt → title)
24
+ * - `<figure>` → `handleFigure` (title only)
25
+ * - `<summary>` → `handleSummary` (content → title)
26
+ * - `<a href>` → `handleAnchor` (content → title)
27
+ * - `<iframe>` → `handleTitleOnly` (title only)
28
+ * - All others → null (rely on caller's generic Steps 2F/2I)
29
+ *
30
+ * @param el - The element to compute the name for
31
+ * @param resolver - Environment-dependent resolver for DOM traversal and role queries
32
+ * @param visited - Set of element IDs already visited (cycle prevention)
33
+ * @param computeFn - The recursive accessible name computation function
34
+ * @param inLabelledbyTraversal - Whether this computation is part of an aria-labelledby traversal
35
+ * @returns The computed name result, or null if no element-specific rule applies
36
+ * @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2E
37
+ * @see https://www.w3.org/TR/html-aam-1.0/#accessible-name-and-description-computation — HTML-AAM §4.1
38
+ */
39
+ export function getElementSpecificName(el, resolver, visited, computeFn, inLabelledbyTraversal) {
40
+ if (isSvgElement(el)) {
41
+ return handleSvgElement(el);
42
+ }
43
+ const { localName } = el;
44
+ switch (localName) {
45
+ case 'input': {
46
+ return handleInput(el, resolver, visited, computeFn, inLabelledbyTraversal);
47
+ }
48
+ case 'textarea':
49
+ case 'select':
50
+ case 'meter':
51
+ case 'progress':
52
+ case 'output': {
53
+ return handleLabelableWithTitle(el, resolver, visited, computeFn, inLabelledbyTraversal);
54
+ }
55
+ case 'button': {
56
+ return handleButton(el, resolver, visited, computeFn, inLabelledbyTraversal);
57
+ }
58
+ case 'fieldset': {
59
+ return handleFieldset(el, resolver, visited, computeFn, inLabelledbyTraversal);
60
+ }
61
+ case 'table': {
62
+ return handleTable(el, resolver, visited, computeFn, inLabelledbyTraversal);
63
+ }
64
+ case 'img': {
65
+ return handleImg(el);
66
+ }
67
+ case 'area': {
68
+ return handleArea(el);
69
+ }
70
+ case 'figure': {
71
+ return handleFigure(el);
72
+ }
73
+ case 'summary': {
74
+ return handleSummary(el, resolver, visited, computeFn, inLabelledbyTraversal);
75
+ }
76
+ case 'a': {
77
+ if (el.hasAttribute('href')) {
78
+ return handleAnchor(el, resolver, visited, computeFn, inLabelledbyTraversal);
79
+ }
80
+ return null;
81
+ }
82
+ case 'iframe': {
83
+ return handleTitleOnly(el);
84
+ }
85
+ default: {
86
+ // All other elements (tr, td, th, section, div, span, p, h1-h6, etc.)
87
+ // only get name from title (handled by caller's title fallback)
88
+ return null;
89
+ }
90
+ }
91
+ }
92
+ /**
93
+ * HTML-AAM §4.1 (input[text-like], textarea, select, meter, progress, output).
94
+ * Name sources: label → title → placeholder.
95
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-input-text
96
+ */
97
+ function handleLabelableWithTitle(el, resolver, visited, computeFn, inLabelledbyTraversal) {
98
+ const labelResult = resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal);
99
+ if (labelResult) {
100
+ return labelResult;
101
+ }
102
+ const title = el.getAttribute('title');
103
+ if (title?.trim()) {
104
+ return makeResult(title, 'title');
105
+ }
106
+ const placeholder = el.getAttribute('placeholder');
107
+ if (placeholder?.trim()) {
108
+ return makeResult(placeholder, 'placeholder');
109
+ }
110
+ return null;
111
+ }
112
+ /**
113
+ * HTML-AAM §4.1 (input — dispatches by type).
114
+ * Text-like types: label → title → placeholder.
115
+ * Button/submit/reset: label → value → default label.
116
+ * Image: label → alt → title → default label.
117
+ * Hidden: always empty.
118
+ * Checkbox/radio: label → title.
119
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-input-text
120
+ */
121
+ function handleInput(el, resolver, visited, computeFn, inLabelledbyTraversal) {
122
+ const type = getInputType(el);
123
+ if (TEXT_INPUT_TYPES.has(type)) {
124
+ return handleLabelableWithTitle(el, resolver, visited, computeFn, inLabelledbyTraversal);
125
+ }
126
+ if (type === 'button' || type === 'submit' || type === 'reset') {
127
+ return handleInputButton(el, type, resolver, visited, computeFn, inLabelledbyTraversal);
128
+ }
129
+ if (type === 'image') {
130
+ return handleInputImage(el, resolver, visited, computeFn, inLabelledbyTraversal);
131
+ }
132
+ if (type === 'hidden') {
133
+ return makeResult('', null);
134
+ }
135
+ // checkbox, radio: label -> title
136
+ return handleLabelableWithTitle(el, resolver, visited, computeFn, inLabelledbyTraversal);
137
+ }
138
+ /**
139
+ * HTML-AAM §4.1 (input[type=button/submit/reset]).
140
+ * Name sources: label → value → title → default label (submit="Submit", reset="Reset").
141
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-input-button
142
+ */
143
+ function handleInputButton(el, type, resolver, visited, computeFn, inLabelledbyTraversal) {
144
+ const labelResult = resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal);
145
+ if (labelResult) {
146
+ return labelResult;
147
+ }
148
+ const value = el.getAttribute('value');
149
+ if (value?.trim()) {
150
+ return makeResult(value, 'value');
151
+ }
152
+ const title = el.getAttribute('title');
153
+ if (title?.trim()) {
154
+ return makeResult(title, 'title');
155
+ }
156
+ // Default label for submit/reset buttons
157
+ if (type === 'submit') {
158
+ return makeResult(DEFAULT_SUBMIT_LABEL, 'default');
159
+ }
160
+ if (type === 'reset') {
161
+ return makeResult(DEFAULT_RESET_LABEL, 'default');
162
+ }
163
+ return null;
164
+ }
165
+ /**
166
+ * HTML-AAM §4.1 (input[type=image]).
167
+ * Name sources: label → alt → title → default ("Submit Query").
168
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-input-image
169
+ */
170
+ function handleInputImage(el, resolver, visited, computeFn, inLabelledbyTraversal) {
171
+ const labelResult = resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal);
172
+ if (labelResult) {
173
+ return labelResult;
174
+ }
175
+ const alt = el.getAttribute('alt');
176
+ if (alt?.trim()) {
177
+ return makeResult(alt, 'alt');
178
+ }
179
+ const title = el.getAttribute('title');
180
+ if (title?.trim()) {
181
+ return makeResult(title, 'title');
182
+ }
183
+ return makeResult(DEFAULT_IMAGE_LABEL, 'default');
184
+ }
185
+ /**
186
+ * HTML-AAM §4.1 (button).
187
+ * Name sources: label → content → title.
188
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-button
189
+ */
190
+ function handleButton(el, resolver, visited, computeFn, inLabelledbyTraversal) {
191
+ const labelResult = resolveLabelText(el, resolver, visited, computeFn, inLabelledbyTraversal);
192
+ if (labelResult) {
193
+ return labelResult;
194
+ }
195
+ const content = resolveNameFromContent(el, resolver, visited, computeFn, inLabelledbyTraversal);
196
+ if (content.trim()) {
197
+ return makeResult(content, 'content');
198
+ }
199
+ const title = el.getAttribute('title');
200
+ if (title?.trim()) {
201
+ return makeResult(title, 'title');
202
+ }
203
+ return null;
204
+ }
205
+ /**
206
+ * HTML-AAM §4.1 (fieldset).
207
+ * Name sources: legend (content) → title.
208
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-fieldset
209
+ */
210
+ function handleFieldset(el, resolver, visited, computeFn, inLabelledbyTraversal) {
211
+ const legend = findChildByLocalName(el, 'legend');
212
+ if (legend) {
213
+ const content = resolveNameFromContent(legend, resolver, visited, computeFn, inLabelledbyTraversal);
214
+ if (content.trim()) {
215
+ return makeResult(content, 'legend');
216
+ }
217
+ }
218
+ const title = el.getAttribute('title');
219
+ if (title?.trim()) {
220
+ return makeResult(title, 'title');
221
+ }
222
+ return null;
223
+ }
224
+ /**
225
+ * HTML-AAM §4.1 (table).
226
+ * Name sources: caption (content) → title.
227
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-table
228
+ */
229
+ function handleTable(el, resolver, visited, computeFn, inLabelledbyTraversal) {
230
+ const caption = findChildByLocalName(el, 'caption');
231
+ if (caption) {
232
+ const content = resolveNameFromContent(caption, resolver, visited, computeFn, inLabelledbyTraversal);
233
+ if (content.trim()) {
234
+ return makeResult(content, 'caption');
235
+ }
236
+ }
237
+ const title = el.getAttribute('title');
238
+ if (title?.trim()) {
239
+ return makeResult(title, 'title');
240
+ }
241
+ return null;
242
+ }
243
+ /**
244
+ * HTML-AAM §4.1 (img).
245
+ * Name sources: alt → title (only when alt is not specified).
246
+ * When alt="" (empty), returns empty name with null source (decorative image).
247
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-img
248
+ */
249
+ function handleImg(el) {
250
+ if (el.hasAttribute('alt')) {
251
+ const alt = el.getAttribute('alt') ?? '';
252
+ return makeResult(alt, alt.trim() ? 'alt' : null);
253
+ }
254
+ const title = el.getAttribute('title');
255
+ if (title?.trim()) {
256
+ return makeResult(title, 'title');
257
+ }
258
+ return null;
259
+ }
260
+ /**
261
+ * HTML-AAM §4.1 (area).
262
+ * Name sources: alt → title.
263
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-area
264
+ */
265
+ function handleArea(el) {
266
+ const alt = el.getAttribute('alt');
267
+ if (alt?.trim()) {
268
+ return makeResult(alt, 'alt');
269
+ }
270
+ const title = el.getAttribute('title');
271
+ if (title?.trim()) {
272
+ return makeResult(title, 'title');
273
+ }
274
+ return null;
275
+ }
276
+ /**
277
+ * HTML-AAM §4.1 (figure).
278
+ * Name sources: title only. Note: figcaption provides the accessible
279
+ * *description*, not the name, per HTML-AAM.
280
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-figure
281
+ */
282
+ function handleFigure(el) {
283
+ const title = el.getAttribute('title');
284
+ if (title?.trim()) {
285
+ return makeResult(title, 'title');
286
+ }
287
+ return null;
288
+ }
289
+ /**
290
+ * HTML-AAM §4.1 (summary).
291
+ * Name sources: content → title.
292
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-summary
293
+ */
294
+ function handleSummary(el, resolver, visited, computeFn, inLabelledbyTraversal) {
295
+ const content = resolveNameFromContent(el, resolver, visited, computeFn, inLabelledbyTraversal);
296
+ if (content.trim()) {
297
+ return makeResult(content, 'content');
298
+ }
299
+ const title = el.getAttribute('title');
300
+ if (title?.trim()) {
301
+ return makeResult(title, 'title');
302
+ }
303
+ return null;
304
+ }
305
+ /**
306
+ * HTML-AAM §4.1 (a[href]).
307
+ * Name sources: content → title.
308
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-a
309
+ */
310
+ function handleAnchor(el, resolver, visited, computeFn, inLabelledbyTraversal) {
311
+ const content = resolveNameFromContent(el, resolver, visited, computeFn, inLabelledbyTraversal);
312
+ if (content.trim()) {
313
+ return makeResult(content, 'content');
314
+ }
315
+ const title = el.getAttribute('title');
316
+ if (title?.trim()) {
317
+ return makeResult(title, 'title');
318
+ }
319
+ return null;
320
+ }
321
+ /**
322
+ * HTML-AAM §4.1 (iframe) — name from title attribute only.
323
+ * @see https://www.w3.org/TR/html-aam-1.0/#el-iframe
324
+ */
325
+ function handleTitleOnly(el) {
326
+ const title = el.getAttribute('title');
327
+ if (title?.trim()) {
328
+ return makeResult(title, 'title');
329
+ }
330
+ return null;
331
+ }
332
+ /**
333
+ * SVG-AAM: SVG elements get name from `<title>` child element.
334
+ * @see https://www.w3.org/TR/svg-aam-1.0/#mapping_additional_nd — SVG-AAM §8.1
335
+ */
336
+ function handleSvgElement(el) {
337
+ const titleEl = findChildByLocalName(el, 'title');
338
+ if (titleEl?.textContent?.trim()) {
339
+ return makeResult(titleEl.textContent, 'svg-title');
340
+ }
341
+ return null;
342
+ }
@@ -0,0 +1,98 @@
1
+ import type { AccnameElement, AccnameResolver, AccnameResult, AccnameSource } from './types.js';
2
+ export { EMBEDDED_CONTROL_ROLES } from '../../../const/index.js';
3
+ /**
4
+ * Creates an AccnameResult with a trimmed, whitespace-collapsed name.
5
+ *
6
+ * @param name - The raw name string to normalize
7
+ * @param source - The source that provided the name, or null if the name is empty
8
+ * @returns A result with the collapsed name and its source (source is null when name is empty)
9
+ */
10
+ export declare function makeResult(name: string, source: AccnameSource | null): AccnameResult;
11
+ /**
12
+ * Collapses internal whitespace and trims a string.
13
+ *
14
+ * @param text - The string to normalize
15
+ * @returns The string with collapsed whitespace and trimmed
16
+ */
17
+ export declare function flattenText(text: string): string;
18
+ /**
19
+ * Collects text content from child nodes recursively for name-from-content computation.
20
+ *
21
+ * Implements AccName 1.2 §4.3.2 Steps 2F and 2C:
22
+ *
23
+ * - **Step 2F**: "If the current node's role allows name from content [...],
24
+ * return the accumulated text of the current node's descendant nodes."
25
+ * - **Step 2C (Embedded Controls)**: "If the current node is a descendant of
26
+ * an `aria-labelledby` or `aria-label` reference AND the current node is
27
+ * an embedded control, return the embedded control's value."
28
+ *
29
+ * Control flow for each child node:
30
+ * 1. **Text node** → contribute text directly.
31
+ * 2. **Embedded control** (textbox, combobox, listbox, slider, spinbutton, searchbox)
32
+ * → use value via `getEmbeddedControlValue`. Per spec, `aria-label` is ignored
33
+ * for embedded controls during name-from-content traversal.
34
+ * 3. **Other element** → first try its full accessible name (Steps 2B–2I via `computeFn`).
35
+ * If no name, fall through to `collectTextContent` for transparent traversal.
36
+ *
37
+ * Parts are joined with a space separator per AccName 1.2 §4.3.2 Step 2C.
38
+ *
39
+ * **Limitation:** CSS-generated content (`::before`/`::after` with the `content`
40
+ * property) is not included. AccName 1.2 §4.3.2 Step 2G specifies that CSS
41
+ * generated textual content should be part of the accumulated text, but markuplint
42
+ * performs static HTML analysis without CSS processing, so this content is
43
+ * unavailable at lint time.
44
+ *
45
+ * @param el - The element whose child nodes to collect text from
46
+ * @param resolver - Environment-dependent resolver for DOM traversal and role queries
47
+ * @param visited - Set of element IDs already visited (cycle prevention)
48
+ * @param computeFn - The recursive accessible name computation function
49
+ * @param inLabelledbyTraversal - Whether this computation is part of an aria-labelledby traversal
50
+ * @returns The concatenated text content from child nodes
51
+ * @see https://www.w3.org/TR/accname-1.2/#computation-steps — AccName 1.2 §4.3.2 Step 2F
52
+ * @see https://www.w3.org/TR/accname-1.2/#comp_embedded_control — AccName 1.2 §4.3.2 Step 2C
53
+ */
54
+ export declare function resolveNameFromContent(el: AccnameElement, resolver: AccnameResolver, visited: ReadonlySet<string>, computeFn: (el: AccnameElement, resolver: AccnameResolver, inLabelledbyTraversal: boolean, visited: ReadonlySet<string>) => AccnameResult, inLabelledbyTraversal: boolean): string;
55
+ /**
56
+ * Finds the first child element with a matching localName.
57
+ *
58
+ * @param el - The parent element to search within
59
+ * @param localName - The local tag name to match
60
+ * @returns The first matching child element, or null if none found
61
+ */
62
+ export declare function findChildByLocalName(el: AccnameElement, localName: string): AccnameElement | null;
63
+ /**
64
+ * Finds the nearest ancestor label element (implicit label association).
65
+ *
66
+ * @param el - The element to search from
67
+ * @returns The nearest ancestor label element, or null if none found
68
+ */
69
+ export declare function findAncestorLabel(el: AccnameElement): AccnameElement | null;
70
+ /**
71
+ * Resolves a label for an element via explicit (for=id) or implicit (ancestor) association.
72
+ *
73
+ * @param el - The element to find labels for
74
+ * @param resolver - Environment-dependent resolver for label lookups
75
+ * @returns An array of label elements associated with the element
76
+ */
77
+ export declare function resolveLabel(el: AccnameElement, resolver: AccnameResolver): readonly AccnameElement[];
78
+ /**
79
+ * Gets the input type, defaulting to 'text' for inputs without a type attribute.
80
+ *
81
+ * @param el - The element to get the input type for
82
+ * @returns The lowercase input type, or an empty string if not an input element
83
+ */
84
+ export declare function getInputType(el: AccnameElement): string;
85
+ /**
86
+ * Checks if an element is in the SVG namespace.
87
+ *
88
+ * @param el - The element to check
89
+ * @returns True if the element is in the SVG namespace
90
+ */
91
+ export declare function isSvgElement(el: AccnameElement): boolean;
92
+ /**
93
+ * Escapes a string for safe use inside a CSS selector.
94
+ *
95
+ * @param value - The raw string value (typically an element ID)
96
+ * @returns The escaped string with CSS special characters backslash-escaped
97
+ */
98
+ export declare function escapeCSS(value: string): string;