@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.
- package/ARCHITECTURE.ja.md +24 -10
- package/ARCHITECTURE.md +24 -10
- package/CHANGELOG.md +34 -0
- package/README.md +2 -2
- package/docs/aria-algorithms.ja.md +180 -33
- package/docs/aria-algorithms.md +183 -34
- package/docs/html-algorithms.ja.md +2 -2
- package/docs/html-algorithms.md +2 -2
- package/docs/maintenance.ja.md +13 -22
- package/docs/maintenance.md +13 -22
- package/docs/spec-resolution.ja.md +44 -9
- package/docs/spec-resolution.md +53 -16
- package/docs/type-definitions.ja.md +8 -4
- package/docs/type-definitions.md +8 -4
- package/lib/algorithm/aria/accname/__tests__/test-helpers.d.ts +45 -0
- package/lib/algorithm/aria/accname/__tests__/test-helpers.js +120 -0
- package/lib/algorithm/aria/accname/aria-steps.d.ts +51 -0
- package/lib/algorithm/aria/accname/aria-steps.js +104 -0
- package/lib/algorithm/aria/accname/compute.d.ts +51 -0
- package/lib/algorithm/aria/accname/compute.js +101 -0
- package/lib/algorithm/aria/accname/element-names.d.ts +36 -0
- package/lib/algorithm/aria/accname/element-names.js +342 -0
- package/lib/algorithm/aria/accname/helpers.d.ts +98 -0
- package/lib/algorithm/aria/accname/helpers.js +330 -0
- package/lib/algorithm/aria/accname/index.d.ts +4 -0
- package/lib/algorithm/aria/accname/index.js +3 -0
- package/lib/algorithm/aria/accname/label-steps.d.ts +25 -0
- package/lib/algorithm/aria/accname/label-steps.js +74 -0
- package/lib/algorithm/aria/accname/svg-helpers.d.ts +17 -0
- package/lib/algorithm/aria/accname/svg-helpers.js +30 -0
- package/lib/algorithm/aria/accname/types.d.ts +70 -0
- package/lib/algorithm/aria/accname/types.js +2 -0
- package/lib/algorithm/aria/accname-computation.d.ts +14 -3
- package/lib/algorithm/aria/accname-computation.js +131 -8
- package/lib/algorithm/aria/aria-specs.d.ts +1 -0
- package/lib/algorithm/aria/get-aria.js +30 -4
- package/lib/algorithm/aria/get-computed-role.js +106 -26
- package/lib/algorithm/aria/get-explicit-role.d.ts +12 -0
- package/lib/algorithm/aria/get-explicit-role.js +12 -0
- package/lib/algorithm/aria/get-non-presentational-ancestor.d.ts +12 -0
- package/lib/algorithm/aria/get-non-presentational-ancestor.js +14 -2
- package/lib/algorithm/aria/get-permitted-roles-spec.d.ts +8 -4
- package/lib/algorithm/aria/get-permitted-roles-spec.js +19 -6
- package/lib/algorithm/aria/get-role-spec.js +10 -3
- package/lib/algorithm/aria/has-required-owned-elements.d.ts +2 -1
- package/lib/algorithm/aria/has-required-owned-elements.js +18 -15
- package/lib/algorithm/aria/is-presentational.d.ts +24 -0
- package/lib/algorithm/aria/is-presentational.js +31 -0
- package/lib/algorithm/aria/matches-context-role.d.ts +12 -4
- package/lib/algorithm/aria/matches-context-role.js +39 -6
- package/lib/algorithm/html/content-model-category-to-tag-names.js +1 -1
- package/lib/algorithm/html/get-content-model.d.ts +4 -2
- package/lib/algorithm/html/get-content-model.js +6 -7
- package/lib/const/accname.d.ts +29 -0
- package/lib/const/accname.js +76 -0
- package/lib/const/dom.d.ts +8 -0
- package/lib/const/dom.js +8 -0
- package/lib/const/index.d.ts +2 -0
- package/lib/const/index.js +2 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +4 -0
- package/lib/types/aria.d.ts +3 -3
- package/lib/types/index.d.ts +65 -0
- package/lib/utils/aria-version.d.ts +1 -1
- package/lib/utils/aria-version.js +1 -1
- package/lib/utils/directive-resolver.d.ts +23 -0
- package/lib/utils/directive-resolver.js +50 -0
- package/lib/utils/get-attr-specs-spec.js +4 -3
- package/lib/utils/get-ns.d.ts +7 -0
- package/lib/utils/get-ns.js +7 -0
- package/lib/utils/get-spec-by-tag-name.d.ts +1 -1
- package/lib/utils/merge-array.d.ts +10 -0
- package/lib/utils/merge-array.js +10 -0
- package/lib/utils/resolve-version.d.ts +11 -0
- package/lib/utils/resolve-version.js +13 -1
- package/lib/utils/schema-to-spec.d.ts +2 -0
- package/lib/utils/schema-to-spec.js +15 -4
- 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;
|