@markuplint/ml-core 5.3.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/lib/ml-dom/helper/accname.d.ts +25 -1
- package/lib/ml-dom/helper/accname.js +85 -4
- package/lib/ml-dom/node/element.d.ts +39 -1
- package/lib/ml-dom/node/element.js +78 -1
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
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.4.0](https://github.com/markuplint/markuplint/compare/v5.3.0...v5.4.0) (2026-10-29)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **ml-core:** expose what the accessible name computation reaches ([2a29d84](https://github.com/markuplint/markuplint/commit/2a29d84ca196ad1808ede49e75796df7c599d2e8)), closes [#4408](https://github.com/markuplint/markuplint/issues/4408)
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# [5.3.0](https://github.com/markuplint/markuplint/compare/v5.2.0...v5.3.0) (2026-10-21)
|
|
7
14
|
|
|
8
15
|
### Bug Fixes
|
|
@@ -1,3 +1,27 @@
|
|
|
1
1
|
import type { MLElement } from '../node/element.js';
|
|
2
|
-
import type { ARIAVersion } from '@markuplint/ml-spec';
|
|
2
|
+
import type { AccnameSource, AccnameVisit, ARIAVersion } from '@markuplint/ml-spec';
|
|
3
3
|
export declare function getAccname(el: MLElement<any, any>, version: ARIAVersion): string;
|
|
4
|
+
/**
|
|
5
|
+
* The elements that the computation of the element's text alternative reaches,
|
|
6
|
+
* with how it reaches each (see `AccnameResolver.onVisit` of `@markuplint/ml-spec`).
|
|
7
|
+
* Errors are handled as in {@link getAccname}: the computation is taken to reach
|
|
8
|
+
* nothing, so a caller that exempts what the computation reads (such as
|
|
9
|
+
* `no-aria-on-presentational-children`) exempts nothing for that element.
|
|
10
|
+
*
|
|
11
|
+
* @param el - The element whose text alternative is computed
|
|
12
|
+
* @param version - The ARIA version
|
|
13
|
+
* @param asReferenced - Computes it as a node referenced by `aria-labelledby` or
|
|
14
|
+
* `aria-describedby` instead of its accessible name
|
|
15
|
+
*/
|
|
16
|
+
export declare function getAccnameVisits(el: MLElement<any, any>, version: ARIAVersion, asReferenced: boolean): ReadonlyMap<MLElement<any, any>, readonly AccnameVisit[]>;
|
|
17
|
+
/**
|
|
18
|
+
* The text alternative of the element as a node referenced by `aria-labelledby`
|
|
19
|
+
* or `aria-describedby`. Errors are handled as in {@link getAccname}.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getReferencedTextAlternative(el: MLElement<any, any>, version: ARIAVersion): string;
|
|
22
|
+
/**
|
|
23
|
+
* The source of the host language text alternative of the element (AccName
|
|
24
|
+
* Step 2E), whether or not the element is exposed as presentational. Errors are
|
|
25
|
+
* handled as in {@link getAccname}: the element is taken to have none.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getHostLanguageLabelSource(el: MLElement<any, any>, version: ARIAVersion): AccnameSource | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computeAccessibleName, escapeCSS, getComputedRole, EMBEDDED_CONTROL_ROLES, isNativeEmbeddedControl, } from '@markuplint/ml-spec';
|
|
1
|
+
import { computeAccessibleName, computeHostLanguageLabel, computeReferencedTextAlternative, escapeCSS, getComputedRole, EMBEDDED_CONTROL_ROLES, isExposedAsPresentational, isNativeEmbeddedControl, } from '@markuplint/ml-spec';
|
|
2
2
|
import { log } from '../../debug.js';
|
|
3
3
|
const accnameLog = log.extend('accname');
|
|
4
4
|
export function getAccname(
|
|
@@ -23,13 +23,89 @@ el, version) {
|
|
|
23
23
|
return result.name;
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
logFailure(el, error);
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The elements that the computation of the element's text alternative reaches,
|
|
32
|
+
* with how it reaches each (see `AccnameResolver.onVisit` of `@markuplint/ml-spec`).
|
|
33
|
+
* Errors are handled as in {@link getAccname}: the computation is taken to reach
|
|
34
|
+
* nothing, so a caller that exempts what the computation reads (such as
|
|
35
|
+
* `no-aria-on-presentational-children`) exempts nothing for that element.
|
|
36
|
+
*
|
|
37
|
+
* @param el - The element whose text alternative is computed
|
|
38
|
+
* @param version - The ARIA version
|
|
39
|
+
* @param asReferenced - Computes it as a node referenced by `aria-labelledby` or
|
|
40
|
+
* `aria-describedby` instead of its accessible name
|
|
41
|
+
*/
|
|
42
|
+
export function getAccnameVisits(
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
44
|
+
el, version, asReferenced) {
|
|
45
|
+
const visits = new Map();
|
|
46
|
+
try {
|
|
47
|
+
const resolver = {
|
|
48
|
+
...createMLCoreResolver(el, version),
|
|
49
|
+
onVisit(target, visit) {
|
|
50
|
+
// TODO: Remove cast when AccnameResolver is generic (#3178)
|
|
51
|
+
const targetEl = target;
|
|
52
|
+
const list = visits.get(targetEl) ?? [];
|
|
53
|
+
list.push(visit);
|
|
54
|
+
visits.set(targetEl, list);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
if (asReferenced) {
|
|
58
|
+
computeReferencedTextAlternative(el, resolver);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
computeAccessibleName(el, resolver);
|
|
29
62
|
}
|
|
63
|
+
return visits;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
logFailure(el, error);
|
|
67
|
+
return new Map();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The text alternative of the element as a node referenced by `aria-labelledby`
|
|
72
|
+
* or `aria-describedby`. Errors are handled as in {@link getAccname}.
|
|
73
|
+
*/
|
|
74
|
+
export function getReferencedTextAlternative(
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
76
|
+
el, version) {
|
|
77
|
+
try {
|
|
78
|
+
return computeReferencedTextAlternative(el, createMLCoreResolver(el, version)).name;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
logFailure(el, error);
|
|
30
82
|
return '';
|
|
31
83
|
}
|
|
32
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* The source of the host language text alternative of the element (AccName
|
|
87
|
+
* Step 2E), whether or not the element is exposed as presentational. Errors are
|
|
88
|
+
* handled as in {@link getAccname}: the element is taken to have none.
|
|
89
|
+
*/
|
|
90
|
+
export function getHostLanguageLabelSource(
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
92
|
+
el, version) {
|
|
93
|
+
try {
|
|
94
|
+
return computeHostLanguageLabel(el, createMLCoreResolver(el, version))?.source ?? null;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
logFailure(el, error);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function logFailure(
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
103
|
+
el, error) {
|
|
104
|
+
if (accnameLog.enabled) {
|
|
105
|
+
accnameLog('Raw: %s', el.raw);
|
|
106
|
+
accnameLog('Error: %O', error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
33
109
|
function createMLCoreResolver(
|
|
34
110
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
35
111
|
el, version) {
|
|
@@ -63,6 +139,11 @@ el, version) {
|
|
|
63
139
|
return isNativeEmbeddedControl(target);
|
|
64
140
|
},
|
|
65
141
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
142
|
+
isExposedAsPresentational(target) {
|
|
143
|
+
// TODO: Remove cast when getComputedRole accepts AccnameElement (#3178)
|
|
144
|
+
return isExposedAsPresentational(getComputedRole(doc.specs, target, version));
|
|
145
|
+
},
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
66
147
|
getPrecomputedName(target) {
|
|
67
148
|
// TODO: Remove cast when AccnameResolver is generic (#3178)
|
|
68
149
|
const targetEl = target;
|
|
@@ -5,7 +5,7 @@ import type { MLText } from './text.js';
|
|
|
5
5
|
import type { ElementNodeType, PretenderContext } from './types.js';
|
|
6
6
|
import type { ElementType, MLASTBlockBehavior, MLASTElement, NamespaceURI } from '@markuplint/ml-ast';
|
|
7
7
|
import type { PlainData, Pretender, RegexSelector, RuleConfigValue, RuleInfo } from '@markuplint/ml-config';
|
|
8
|
-
import type { ARIAVersion } from '@markuplint/ml-spec';
|
|
8
|
+
import type { AccnameSource, AccnameVisit, ARIAVersion } from '@markuplint/ml-spec';
|
|
9
9
|
import type { SelectorMatches } from '@markuplint/selector';
|
|
10
10
|
import { MLAttr } from './attr.js';
|
|
11
11
|
import { MLDomTokenList } from './dom-token-list.js';
|
|
@@ -1968,6 +1968,44 @@ export declare class MLElement<T extends RuleConfigValue, O extends PlainData =
|
|
|
1968
1968
|
* @returns The computed accessible name string (may be empty)
|
|
1969
1969
|
*/
|
|
1970
1970
|
getAccessibleName(version: ARIAVersion): string;
|
|
1971
|
+
/**
|
|
1972
|
+
* Returns the elements that the computation of this element's text
|
|
1973
|
+
* alternative reaches, with how it reaches each, memoized per ARIA version
|
|
1974
|
+
* and mode like {@link getAccessibleName}.
|
|
1975
|
+
*
|
|
1976
|
+
* A descendant missing from the map takes no part in the text alternative:
|
|
1977
|
+
* the computation ends before it (a hidden element, a name given before
|
|
1978
|
+
* content, a component that does not render its children). When the
|
|
1979
|
+
* computation fails, the map is empty.
|
|
1980
|
+
*
|
|
1981
|
+
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
1982
|
+
* @param version - The ARIA specification version to use for computation
|
|
1983
|
+
* @param asReferenced - Computes it as a node referenced by `aria-labelledby`
|
|
1984
|
+
* or `aria-describedby` instead of as this element's accessible name
|
|
1985
|
+
* @returns The reached elements and their visits
|
|
1986
|
+
*/
|
|
1987
|
+
getAccessibleNameVisits(version: ARIAVersion, asReferenced?: boolean): ReadonlyMap<MLElement<any, any>, readonly AccnameVisit[]>;
|
|
1988
|
+
/**
|
|
1989
|
+
* Returns the text alternative of this element as a node referenced by
|
|
1990
|
+
* `aria-labelledby` or `aria-describedby` (AccName Step 2B, LabelledBy
|
|
1991
|
+
* Traversal), memoized per ARIA version like {@link getAccessibleName}.
|
|
1992
|
+
*
|
|
1993
|
+
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
1994
|
+
* @param version - The ARIA specification version to use for computation
|
|
1995
|
+
* @returns The text alternative (may be empty)
|
|
1996
|
+
*/
|
|
1997
|
+
getReferencedTextAlternative(version: ARIAVersion): string;
|
|
1998
|
+
/**
|
|
1999
|
+
* Returns the source of this element's host language text alternative
|
|
2000
|
+
* (AccName Step 2E), such as `'alt'` or `'svg-title'`, whether or not an
|
|
2001
|
+
* explicit `none` or `presentation` role keeps it out of the name.
|
|
2002
|
+
* Memoized per ARIA version like {@link getAccessibleName}.
|
|
2003
|
+
*
|
|
2004
|
+
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
2005
|
+
* @param version - The ARIA specification version to use for computation
|
|
2006
|
+
* @returns The source, or `null` if the element has no host language text alternative
|
|
2007
|
+
*/
|
|
2008
|
+
getHostLanguageLabelSource(version: ARIAVersion): AccnameSource | null;
|
|
1971
2009
|
/**
|
|
1972
2010
|
* **IT THROWS AN ERROR WHEN CALLING THIS.**
|
|
1973
2011
|
*
|
|
@@ -4,7 +4,7 @@ import { getSpecByTagName, resolveNamespace } from '@markuplint/ml-spec';
|
|
|
4
4
|
import { pretenderNamespaceToURI } from '@markuplint/ml-config';
|
|
5
5
|
import { getChildNamespace } from '@markuplint/parser-utils';
|
|
6
6
|
import { matchSelector, typeNameMatches } from '@markuplint/selector';
|
|
7
|
-
import { getAccname } from '../helper/accname.js';
|
|
7
|
+
import { getAccname, getAccnameVisits, getHostLanguageLabelSource, getReferencedTextAlternative, } from '../helper/accname.js';
|
|
8
8
|
import { after, before, nextElementSibling, previousElementSibling, remove, replaceWith, } from '../manipulations/child-node-methods.js';
|
|
9
9
|
import { MLAttr } from './attr.js';
|
|
10
10
|
import { MLDomTokenList } from './dom-token-list.js';
|
|
@@ -227,6 +227,21 @@ export class MLElement extends MLParentNode {
|
|
|
227
227
|
* @see {@link getAccessibleName} — the public method that reads/writes this cache
|
|
228
228
|
*/
|
|
229
229
|
#accessibleNameCache = new Map();
|
|
230
|
+
/**
|
|
231
|
+
* Memoizes {@link getAccessibleNameVisits}, keyed by `${version}:${asReferenced}`.
|
|
232
|
+
* Safe for the same reason as {@link #accessibleNameCache}.
|
|
233
|
+
*/
|
|
234
|
+
#accessibleNameVisitsCache = new Map();
|
|
235
|
+
/**
|
|
236
|
+
* Memoizes {@link getHostLanguageLabelSource}. Safe for the same reason as
|
|
237
|
+
* {@link #accessibleNameCache}.
|
|
238
|
+
*/
|
|
239
|
+
#hostLanguageLabelSourceCache = new Map();
|
|
240
|
+
/**
|
|
241
|
+
* Memoizes {@link getReferencedTextAlternative}. Safe for the same reason as
|
|
242
|
+
* {@link #accessibleNameCache}.
|
|
243
|
+
*/
|
|
244
|
+
#referencedTextAlternativeCache = new Map();
|
|
230
245
|
#normalizedAttrs = new Map();
|
|
231
246
|
#normalizedString = null;
|
|
232
247
|
ontouchcancel;
|
|
@@ -2589,6 +2604,68 @@ export class MLElement extends MLParentNode {
|
|
|
2589
2604
|
this.#accessibleNameCache.set(version, name);
|
|
2590
2605
|
return name;
|
|
2591
2606
|
}
|
|
2607
|
+
/**
|
|
2608
|
+
* Returns the elements that the computation of this element's text
|
|
2609
|
+
* alternative reaches, with how it reaches each, memoized per ARIA version
|
|
2610
|
+
* and mode like {@link getAccessibleName}.
|
|
2611
|
+
*
|
|
2612
|
+
* A descendant missing from the map takes no part in the text alternative:
|
|
2613
|
+
* the computation ends before it (a hidden element, a name given before
|
|
2614
|
+
* content, a component that does not render its children). When the
|
|
2615
|
+
* computation fails, the map is empty.
|
|
2616
|
+
*
|
|
2617
|
+
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
2618
|
+
* @param version - The ARIA specification version to use for computation
|
|
2619
|
+
* @param asReferenced - Computes it as a node referenced by `aria-labelledby`
|
|
2620
|
+
* or `aria-describedby` instead of as this element's accessible name
|
|
2621
|
+
* @returns The reached elements and their visits
|
|
2622
|
+
*/
|
|
2623
|
+
getAccessibleNameVisits(version, asReferenced = false) {
|
|
2624
|
+
const key = `${version}:${asReferenced}`;
|
|
2625
|
+
const cached = this.#accessibleNameVisitsCache.get(key);
|
|
2626
|
+
if (cached != null) {
|
|
2627
|
+
return cached;
|
|
2628
|
+
}
|
|
2629
|
+
const visits = getAccnameVisits(this, version, asReferenced);
|
|
2630
|
+
this.#accessibleNameVisitsCache.set(key, visits);
|
|
2631
|
+
return visits;
|
|
2632
|
+
}
|
|
2633
|
+
/**
|
|
2634
|
+
* Returns the text alternative of this element as a node referenced by
|
|
2635
|
+
* `aria-labelledby` or `aria-describedby` (AccName Step 2B, LabelledBy
|
|
2636
|
+
* Traversal), memoized per ARIA version like {@link getAccessibleName}.
|
|
2637
|
+
*
|
|
2638
|
+
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
2639
|
+
* @param version - The ARIA specification version to use for computation
|
|
2640
|
+
* @returns The text alternative (may be empty)
|
|
2641
|
+
*/
|
|
2642
|
+
getReferencedTextAlternative(version) {
|
|
2643
|
+
const cached = this.#referencedTextAlternativeCache.get(version);
|
|
2644
|
+
if (cached != null) {
|
|
2645
|
+
return cached;
|
|
2646
|
+
}
|
|
2647
|
+
const text = getReferencedTextAlternative(this, version);
|
|
2648
|
+
this.#referencedTextAlternativeCache.set(version, text);
|
|
2649
|
+
return text;
|
|
2650
|
+
}
|
|
2651
|
+
/**
|
|
2652
|
+
* Returns the source of this element's host language text alternative
|
|
2653
|
+
* (AccName Step 2E), such as `'alt'` or `'svg-title'`, whether or not an
|
|
2654
|
+
* explicit `none` or `presentation` role keeps it out of the name.
|
|
2655
|
+
* Memoized per ARIA version like {@link getAccessibleName}.
|
|
2656
|
+
*
|
|
2657
|
+
* @implements `@markuplint/ml-core` API: `MLElement`
|
|
2658
|
+
* @param version - The ARIA specification version to use for computation
|
|
2659
|
+
* @returns The source, or `null` if the element has no host language text alternative
|
|
2660
|
+
*/
|
|
2661
|
+
getHostLanguageLabelSource(version) {
|
|
2662
|
+
if (this.#hostLanguageLabelSourceCache.has(version)) {
|
|
2663
|
+
return this.#hostLanguageLabelSourceCache.get(version) ?? null;
|
|
2664
|
+
}
|
|
2665
|
+
const source = getHostLanguageLabelSource(this, version);
|
|
2666
|
+
this.#hostLanguageLabelSourceCache.set(version, source);
|
|
2667
|
+
return source;
|
|
2668
|
+
}
|
|
2592
2669
|
/**
|
|
2593
2670
|
* **IT THROWS AN ERROR WHEN CALLING THIS.**
|
|
2594
2671
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/ml-core",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"description": "The core module of markuplint",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,20 +34,20 @@
|
|
|
34
34
|
"./lib/configs.js": "./lib/configs.browser.js"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@markuplint/config-presets": "5.
|
|
38
|
-
"@markuplint/html-parser": "5.
|
|
39
|
-
"@markuplint/html-spec": "5.
|
|
37
|
+
"@markuplint/config-presets": "5.4.0",
|
|
38
|
+
"@markuplint/html-parser": "5.4.0",
|
|
39
|
+
"@markuplint/html-spec": "5.4.0",
|
|
40
40
|
"@markuplint/i18n": "5.3.0",
|
|
41
41
|
"@markuplint/ml-ast": "5.3.0",
|
|
42
|
-
"@markuplint/ml-config": "5.
|
|
43
|
-
"@markuplint/ml-spec": "5.
|
|
44
|
-
"@markuplint/parser-utils": "5.
|
|
45
|
-
"@markuplint/selector": "5.
|
|
46
|
-
"@markuplint/shared": "5.
|
|
42
|
+
"@markuplint/ml-config": "5.4.0",
|
|
43
|
+
"@markuplint/ml-spec": "5.4.0",
|
|
44
|
+
"@markuplint/parser-utils": "5.4.0",
|
|
45
|
+
"@markuplint/selector": "5.4.0",
|
|
46
|
+
"@markuplint/shared": "5.4.0",
|
|
47
47
|
"@types/debug": "^4.1.13",
|
|
48
48
|
"debug": "^4.4.3",
|
|
49
49
|
"is-plain-object": "^5.1.0",
|
|
50
50
|
"type-fest": "^5.10.0"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "647f6fb5d5743f060678599c97e9c84b58970747"
|
|
53
53
|
}
|