@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 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
- if (accnameLog.enabled) {
27
- accnameLog('Raw: %s', el.raw);
28
- accnameLog('Error: %O', error);
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.0",
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.3.0",
38
- "@markuplint/html-parser": "5.3.0",
39
- "@markuplint/html-spec": "5.3.0",
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.3.0",
43
- "@markuplint/ml-spec": "5.3.0",
44
- "@markuplint/parser-utils": "5.3.0",
45
- "@markuplint/selector": "5.3.0",
46
- "@markuplint/shared": "5.3.0",
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": "f587cf0206c8426e57369a48d6d5fb22306eafde"
52
+ "gitHead": "647f6fb5d5743f060678599c97e9c84b58970747"
53
53
  }