@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
@@ -1,9 +1,10 @@
1
- import { isPresentational } from './is-presentational.js';
1
+ import { isTransparentForOwnership } from './is-presentational.js';
2
2
  import { getComputedRole } from './get-computed-role.js';
3
3
  import { getExplicitRole } from './get-explicit-role.js';
4
4
  import { getImplicitRole } from './get-implicit-role.js';
5
5
  /**
6
- * Checks whether an element satisfies the "Required Owned Elements" constraint
6
+ * Checks whether an element satisfies the "Allowed Accessibility Child Roles"
7
+ * constraint (called "Required Owned Elements" in ARIA 1.2)
7
8
  * defined by its computed ARIA role. An element satisfies this constraint if it
8
9
  * has `aria-owns`, or if any of its closest non-presentational descendants match
9
10
  * the required owned element roles.
@@ -29,10 +30,10 @@ el, specs, version) {
29
30
  * Otherwise, traverses descendants to find owned elements.
30
31
  */
31
32
  const computed = getComputedRole(specs, el, version);
32
- if (!computed.role || computed.role.requiredOwnedElements.length === 0) {
33
+ if (!computed.role || computed.role.allowedAccessibilityChildRoles.length === 0) {
33
34
  return true;
34
35
  }
35
- for (const expectRole of computed.role.requiredOwnedElements) {
36
+ for (const expectRole of computed.role.allowedAccessibilityChildRoles) {
36
37
  for (const owned of getClosestNonPresentationalDescendants(el, specs, version)) {
37
38
  if (isRequiredOwnedElement(owned.el, owned.role, expectRole, specs, version)) {
38
39
  return true;
@@ -72,18 +73,20 @@ el, role, query, specs, version) {
72
73
  }
73
74
  /**
74
75
  * Gets the list of closest non-presentational descendants.
75
- * THE SPECIFICATION HAS AN ISSUE
76
- * that has not decided whether the owned element is a child or a descendant.
76
+ * This corresponds to the "Allowed Accessibility Child Roles" validation
77
+ * (called "Required Owned Elements" in ARIA 1.2).
77
78
  *
78
- * @see https://github.com/w3c/aria/issues/1033
79
- * @see https://github.com/w3c/aria/issues/748
80
- * @see https://github.com/w3c/aria/pull/1162
81
- * @see https://github.com/w3c/aria/pull/1213
79
+ * In ARIA 1.1/1.2, the spec had not decided whether the owned element
80
+ * is a child or a descendant. This implementation interprets that as
81
+ * A CHILD, but `presentation`/`none` elements are treated as transparent.
82
+ *
83
+ * ARIA 1.3 formally resolves this with the definitions of
84
+ * "accessibility child" and "accessibility parent", and additionally
85
+ * makes `generic` elements transparent.
82
86
  *
83
- * Currently, this process interprets that as A CHILD
84
- * because it wants to be near to HTML semantics.
85
- * However, the presentational role behaves transparently
86
- * according to the sample code in WAI-ARIA specification.
87
+ * @see https://w3c.github.io/aria/#mustContain
88
+ * @see https://github.com/w3c/aria/issues/1033
89
+ * @see https://github.com/w3c/aria/pull/1454
87
90
  */
88
91
  function getClosestNonPresentationalDescendants(
89
92
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
@@ -92,7 +95,7 @@ el, specs, version) {
92
95
  for (const child of el.children) {
93
96
  const explicitRole = getExplicitRole(specs, child, version);
94
97
  const computed = explicitRole.role ? explicitRole : getImplicitRole(specs, child, version);
95
- if (isPresentational(computed.role?.name)) {
98
+ if (isTransparentForOwnership(computed.role?.name, version)) {
96
99
  owned.push(...getClosestNonPresentationalDescendants(child, specs, version));
97
100
  continue;
98
101
  }
@@ -1,3 +1,4 @@
1
+ import type { ARIAVersion } from '../../types/index.js';
1
2
  /**
2
3
  * Determines whether a given role name corresponds to a presentational role
3
4
  * (`"presentation"` or `"none"`), which indicates the element should be
@@ -7,3 +8,26 @@
7
8
  * @returns `true` if the role name is `"presentation"` or `"none"`, `false` otherwise
8
9
  */
9
10
  export declare function isPresentational(roleName?: string): boolean;
11
+ /**
12
+ * Determines whether a given role is transparent for ownership traversal.
13
+ *
14
+ * **Version behavior:**
15
+ * - **ARIA 1.1/1.2:** Only `presentation`/`none` are transparent.
16
+ * This matches the pre-existing behavior of `getClosestNonPresentationalDescendants`
17
+ * and `getNonPresentationalAncestor`.
18
+ * - **ARIA 1.3:** `generic` is additionally transparent per the spec:
19
+ * "user agents MUST ignore any intervening elements with the role
20
+ * `generic` or `none`."
21
+ *
22
+ * **Note:** `matchesContextRole` calls this function unconditionally
23
+ * (without a version gate) so that `presentation`/`none` are always
24
+ * transparent, consistent with `getNonPresentationalAncestor`.
25
+ *
26
+ * @see https://w3c.github.io/aria/#mustContain — "Allowed Accessibility Child Roles" (called "Required Owned Elements" in ARIA 1.2)
27
+ * @see https://w3c.github.io/aria/#scope — "Required Accessibility Parent Role" (called "Required Context Role" in ARIA 1.2)
28
+ *
29
+ * @param roleName - The ARIA role name to check, or undefined
30
+ * @param version - The ARIA specification version
31
+ * @returns `true` if the role should be skipped during ownership traversal
32
+ */
33
+ export declare function isTransparentForOwnership(roleName: string | undefined, version: ARIAVersion): boolean;
@@ -12,3 +12,34 @@ export function isPresentational(roleName) {
12
12
  }
13
13
  return ['presentation', 'none'].includes(roleName);
14
14
  }
15
+ /**
16
+ * Determines whether a given role is transparent for ownership traversal.
17
+ *
18
+ * **Version behavior:**
19
+ * - **ARIA 1.1/1.2:** Only `presentation`/`none` are transparent.
20
+ * This matches the pre-existing behavior of `getClosestNonPresentationalDescendants`
21
+ * and `getNonPresentationalAncestor`.
22
+ * - **ARIA 1.3:** `generic` is additionally transparent per the spec:
23
+ * "user agents MUST ignore any intervening elements with the role
24
+ * `generic` or `none`."
25
+ *
26
+ * **Note:** `matchesContextRole` calls this function unconditionally
27
+ * (without a version gate) so that `presentation`/`none` are always
28
+ * transparent, consistent with `getNonPresentationalAncestor`.
29
+ *
30
+ * @see https://w3c.github.io/aria/#mustContain — "Allowed Accessibility Child Roles" (called "Required Owned Elements" in ARIA 1.2)
31
+ * @see https://w3c.github.io/aria/#scope — "Required Accessibility Parent Role" (called "Required Context Role" in ARIA 1.2)
32
+ *
33
+ * @param roleName - The ARIA role name to check, or undefined
34
+ * @param version - The ARIA specification version
35
+ * @returns `true` if the role should be skipped during ownership traversal
36
+ */
37
+ export function isTransparentForOwnership(roleName, version) {
38
+ if (isPresentational(roleName)) {
39
+ return true;
40
+ }
41
+ if (version === '1.3' && roleName === 'generic') {
42
+ return true;
43
+ }
44
+ return false;
45
+ }
@@ -1,10 +1,18 @@
1
1
  import type { ARIAVersion, MLMLSpec } from '../../types/index.js';
2
2
  /**
3
- * Checks whether an element's parent hierarchy satisfies at least one of the
4
- * required context role conditions. Each condition string may describe a chain
5
- * of ancestor roles separated by ` > ` (e.g., `"list > group"`).
3
+ * Checks whether an element's parent hierarchy satisfies the
4
+ * "Required Accessibility Parent Role" (called "Required Context Role" in ARIA 1.2).
5
+ * Each condition string may describe a chain of ancestor roles separated by
6
+ * ` > ` (e.g., `"list > group"`).
6
7
  *
7
- * @param conditions - An array of required context role condition strings to match against
8
+ * TODO: This function only walks the DOM `parentElement` chain and does not
9
+ * consider `aria-owns` relationships. An element referenced by `aria-owns` on
10
+ * a remote ancestor should be treated as if it were an accessibility child of
11
+ * that ancestor. Implementing this requires a document-wide reverse lookup of
12
+ * `aria-owns` attributes, which is a separate architectural concern.
13
+ * See also: `has-required-owned-elements.ts` has a similar limitation.
14
+ *
15
+ * @param conditions - An array of required accessibility parent role condition strings to match against
8
16
  * @param ownedEl - The owned DOM element whose parent context is being validated
9
17
  * @param specs - The full markup language specification
10
18
  * @param version - The ARIA specification version to use
@@ -1,10 +1,19 @@
1
+ import { isTransparentForOwnership } from './is-presentational.js';
1
2
  import { getComputedRole } from './get-computed-role.js';
2
3
  /**
3
- * Checks whether an element's parent hierarchy satisfies at least one of the
4
- * required context role conditions. Each condition string may describe a chain
5
- * of ancestor roles separated by ` > ` (e.g., `"list > group"`).
4
+ * Checks whether an element's parent hierarchy satisfies the
5
+ * "Required Accessibility Parent Role" (called "Required Context Role" in ARIA 1.2).
6
+ * Each condition string may describe a chain of ancestor roles separated by
7
+ * ` > ` (e.g., `"list > group"`).
6
8
  *
7
- * @param conditions - An array of required context role condition strings to match against
9
+ * TODO: This function only walks the DOM `parentElement` chain and does not
10
+ * consider `aria-owns` relationships. An element referenced by `aria-owns` on
11
+ * a remote ancestor should be treated as if it were an accessibility child of
12
+ * that ancestor. Implementing this requires a document-wide reverse lookup of
13
+ * `aria-owns` attributes, which is a separate architectural concern.
14
+ * See also: `has-required-owned-elements.ts` has a similar limitation.
15
+ *
16
+ * @param conditions - An array of required accessibility parent role condition strings to match against
8
17
  * @param ownedEl - The owned DOM element whose parent context is being validated
9
18
  * @param specs - The full markup language specification
10
19
  * @param version - The ARIA specification version to use
@@ -18,13 +27,37 @@ ownedEl, specs, version) {
18
27
  function matchesCondition(condition,
19
28
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
20
29
  parentEl, specs, version) {
21
- const conditions = condition.split(/\s+>\s+/).reverse();
30
+ const conditions = condition.split(/\s+>\s+/).toReversed();
22
31
  while (conditions.length > 0) {
23
32
  if (!parentEl) {
24
33
  return false;
25
34
  }
26
- const condition = conditions.shift();
27
35
  const parentRole = getComputedRole(specs, parentEl, version, true).role;
36
+ /**
37
+ * ARIA 1.3 ("Required Accessibility Parent Role"): "To determine
38
+ * whether an element has a parent with the required role, user agents
39
+ * MUST ignore any elements with the role `generic` or `none`."
40
+ *
41
+ * `presentation`/`none` are transparent in all ARIA versions to stay
42
+ * consistent with `getNonPresentationalAncestor` (which already skips
43
+ * them unconditionally). `generic` is additionally transparent in 1.3+
44
+ * per the spec.
45
+ *
46
+ * While the ARIA 1.1/1.2 specification text does not explicitly define
47
+ * this transparency for the context role check, applying it is a
48
+ * pragmatic choice: `getNonPresentationalAncestor` and `classifyChildren`
49
+ * (for owned elements) already skip `presentation`/`none` in all versions.
50
+ * Treating the context role check differently would create inconsistent
51
+ * behavior where a structure is valid from the parent's perspective
52
+ * (owned elements) but invalid from the child's perspective (context role).
53
+ *
54
+ * @see https://w3c.github.io/aria/#scope
55
+ */
56
+ if (isTransparentForOwnership(parentRole?.name, version)) {
57
+ parentEl = parentEl.parentElement;
58
+ continue;
59
+ }
60
+ const condition = conditions.shift();
28
61
  if (condition !== parentRole?.name) {
29
62
  return false;
30
63
  }
@@ -14,7 +14,7 @@ export function contentModelCategoryToTagNames(contentModel, def) {
14
14
  return cached;
15
15
  }
16
16
  const tags = def['#contentModels'][contentModel];
17
- const sortedTag = Object.freeze(tags && Array.isArray(tags) ? tags.sort() : []);
17
+ const sortedTag = Object.freeze(tags && Array.isArray(tags) ? tags.toSorted() : []);
18
18
  cache.set(contentModel, sortedTag);
19
19
  return sortedTag;
20
20
  }
@@ -1,13 +1,15 @@
1
1
  import type { ElementSpec } from '../../types/index.js';
2
+ import type { PermittedContentPattern } from '../../types/permitted-structures.js';
3
+ import type { ReadonlyDeep } from 'type-fest';
2
4
  type Specs = readonly Pick<ElementSpec, 'name' | 'contentModel'>[];
3
5
  /**
4
6
  * Retrieves the permitted content model for an element. Evaluates any conditional
5
7
  * content models based on the element's current attributes (e.g., different content
6
- * models for `<ol>` vs `<ol reversed>`). Results are cached per element and spec set.
8
+ * models for `<ol>` vs `<ol reversed>`). Results are cached per element.
7
9
  *
8
10
  * @param el - The DOM element to retrieve the content model for
9
11
  * @param specs - The element specifications containing content model definitions
10
12
  * @returns The permitted content patterns array, a boolean (true for any content, false for no content), or null if no spec exists
11
13
  */
12
- export declare function getContentModel(el: Element, specs: Specs): boolean | readonly (import("type-fest/source/readonly-deep.js").ReadonlyObjectDeep<import("../../types/permitted-structures.js").PermittedContentRequire> | import("type-fest/source/readonly-deep.js").ReadonlyObjectDeep<import("../../types/permitted-structures.js").PermittedContentOptional> | import("type-fest/source/readonly-deep.js").ReadonlyObjectDeep<import("../../types/permitted-structures.js").PermittedContentOneOrMore> | import("type-fest/source/readonly-deep.js").ReadonlyObjectDeep<import("../../types/permitted-structures.js").PermittedContentZeroOrMore> | import("type-fest/source/readonly-deep.js").ReadonlyObjectDeep<import("../../types/permitted-structures.js").PermittedContentChoice> | import("type-fest/source/readonly-deep.js").ReadonlyObjectDeep<import("../../types/permitted-structures.js").PermittedContentTransparent>)[] | null;
14
+ export declare function getContentModel(el: Element, specs: Specs): ReadonlyDeep<PermittedContentPattern[]> | boolean | null;
13
15
  export {};
@@ -1,9 +1,9 @@
1
1
  import { getSpec } from '../../utils/get-spec.js';
2
- const cachesBySpecs = new Map();
2
+ const contentModelCache = new WeakMap();
3
3
  /**
4
4
  * Retrieves the permitted content model for an element. Evaluates any conditional
5
5
  * content models based on the element's current attributes (e.g., different content
6
- * models for `<ol>` vs `<ol reversed>`). Results are cached per element and spec set.
6
+ * models for `<ol>` vs `<ol reversed>`). Results are cached per element.
7
7
  *
8
8
  * @param el - The DOM element to retrieve the content model for
9
9
  * @param specs - The element specifications containing content model definitions
@@ -12,23 +12,22 @@ const cachesBySpecs = new Map();
12
12
  export function getContentModel(
13
13
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
14
14
  el, specs) {
15
- const cacheByEl = cachesBySpecs.get(specs) ?? new Map();
16
- const cached = cacheByEl.get(el);
15
+ const cached = contentModelCache.get(el);
17
16
  if (cached !== undefined) {
18
17
  return cached;
19
18
  }
20
19
  const spec = getSpec(el, specs);
21
20
  if (!spec) {
22
- cacheByEl.set(el, null);
21
+ contentModelCache.set(el, null);
23
22
  return null;
24
23
  }
25
24
  const conditions = spec.contentModel.conditional ?? [];
26
25
  for (const cond of conditions) {
27
26
  if (el.matches(cond.condition)) {
28
- cacheByEl.set(el, cond.contents);
27
+ contentModelCache.set(el, cond.contents);
29
28
  return cond.contents;
30
29
  }
31
30
  }
32
- cacheByEl.set(el, spec.contentModel.contents);
31
+ contentModelCache.set(el, spec.contentModel.contents);
33
32
  return spec.contentModel.contents;
34
33
  }
@@ -0,0 +1,29 @@
1
+ import type { AccnameElement } from '../algorithm/aria/accname/types.js';
2
+ /**
3
+ * ARIA roles that are embedded controls per AccName Step 2C.
4
+ * @see https://www.w3.org/TR/accname-1.2/#comp_embedded_control
5
+ */
6
+ export declare const EMBEDDED_CONTROL_ROLES: ReadonlySet<string>;
7
+ /**
8
+ * Input types using label → title → placeholder name computation (HTML-AAM §4.1).
9
+ */
10
+ export declare const TEXT_INPUT_TYPES: ReadonlySet<string>;
11
+ /**
12
+ * Input types that behave as text-like controls (value attr → textContent).
13
+ * Subset of TEXT_INPUT_TYPES used for embedded control value extraction.
14
+ */
15
+ export declare const TEXT_LIKE_INPUT_TYPES: ReadonlySet<string>;
16
+ /** Default accessible name for input[type=submit] per HTML-AAM §4.1 */
17
+ export declare const DEFAULT_SUBMIT_LABEL = "Submit";
18
+ /** Default accessible name for input[type=reset] per HTML-AAM §4.1 */
19
+ export declare const DEFAULT_RESET_LABEL = "Reset";
20
+ /** Default accessible name for input[type=image] per HTML-AAM §4.1 */
21
+ export declare const DEFAULT_IMAGE_LABEL = "Submit Query";
22
+ /**
23
+ * Checks if a native HTML element is an embedded control (without role resolution).
24
+ * Centralizes the logic duplicated across accname-computation.ts, ml-core/accname.ts, test-helpers.ts.
25
+ *
26
+ * @param el - The element to check
27
+ * @returns True if the element is a native embedded control (textarea, select, or input with a value-producing type)
28
+ */
29
+ export declare function isNativeEmbeddedControl(el: AccnameElement): boolean;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * ARIA roles that are embedded controls per AccName Step 2C.
3
+ * @see https://www.w3.org/TR/accname-1.2/#comp_embedded_control
4
+ */
5
+ export const EMBEDDED_CONTROL_ROLES = new Set([
6
+ 'textbox',
7
+ 'combobox',
8
+ 'listbox',
9
+ 'spinbutton',
10
+ 'slider',
11
+ 'searchbox',
12
+ ]);
13
+ /**
14
+ * Input types using label → title → placeholder name computation (HTML-AAM §4.1).
15
+ */
16
+ export const TEXT_INPUT_TYPES = new Set([
17
+ 'text',
18
+ 'password',
19
+ 'search',
20
+ 'tel',
21
+ 'url',
22
+ 'email',
23
+ 'number',
24
+ 'date',
25
+ 'month',
26
+ 'week',
27
+ 'time',
28
+ 'datetime-local',
29
+ 'color',
30
+ 'range',
31
+ 'file',
32
+ ]);
33
+ /**
34
+ * Input types that behave as text-like controls (value attr → textContent).
35
+ * Subset of TEXT_INPUT_TYPES used for embedded control value extraction.
36
+ */
37
+ export const TEXT_LIKE_INPUT_TYPES = new Set([
38
+ 'text',
39
+ 'search',
40
+ 'tel',
41
+ 'url',
42
+ 'email',
43
+ 'password',
44
+ 'number',
45
+ ]);
46
+ /** Default accessible name for input[type=submit] per HTML-AAM §4.1 */
47
+ export const DEFAULT_SUBMIT_LABEL = 'Submit';
48
+ /** Default accessible name for input[type=reset] per HTML-AAM §4.1 */
49
+ export const DEFAULT_RESET_LABEL = 'Reset';
50
+ /** Default accessible name for input[type=image] per HTML-AAM §4.1 */
51
+ export const DEFAULT_IMAGE_LABEL = 'Submit Query';
52
+ /**
53
+ * Checks if a native HTML element is an embedded control (without role resolution).
54
+ * Centralizes the logic duplicated across accname-computation.ts, ml-core/accname.ts, test-helpers.ts.
55
+ *
56
+ * @param el - The element to check
57
+ * @returns True if the element is a native embedded control (textarea, select, or input with a value-producing type)
58
+ */
59
+ // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
60
+ export function isNativeEmbeddedControl(el) {
61
+ const { localName } = el;
62
+ if (localName === 'textarea' || localName === 'select') {
63
+ return true;
64
+ }
65
+ if (localName === 'input') {
66
+ const type = (el.getAttribute('type') ?? 'text').toLowerCase();
67
+ return (type !== 'hidden' &&
68
+ type !== 'button' &&
69
+ type !== 'submit' &&
70
+ type !== 'reset' &&
71
+ type !== 'image' &&
72
+ type !== 'checkbox' &&
73
+ type !== 'radio');
74
+ }
75
+ return false;
76
+ }
@@ -0,0 +1,8 @@
1
+ /** DOM Node.ELEMENT_NODE */
2
+ export declare const ELEMENT_NODE = 1;
3
+ /** DOM Node.TEXT_NODE */
4
+ export declare const TEXT_NODE = 3;
5
+ /** SVG namespace URI */
6
+ export declare const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
7
+ /** XHTML namespace URI */
8
+ export declare const XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
@@ -0,0 +1,8 @@
1
+ /** DOM Node.ELEMENT_NODE */
2
+ export const ELEMENT_NODE = 1;
3
+ /** DOM Node.TEXT_NODE */
4
+ export const TEXT_NODE = 3;
5
+ /** SVG namespace URI */
6
+ export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
7
+ /** XHTML namespace URI */
8
+ export const XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
@@ -0,0 +1,2 @@
1
+ export { ELEMENT_NODE, TEXT_NODE, SVG_NAMESPACE, XHTML_NAMESPACE } from './dom.js';
2
+ export { EMBEDDED_CONTROL_ROLES, TEXT_INPUT_TYPES, TEXT_LIKE_INPUT_TYPES, DEFAULT_SUBMIT_LABEL, DEFAULT_RESET_LABEL, DEFAULT_IMAGE_LABEL, isNativeEmbeddedControl, } from './accname.js';
@@ -0,0 +1,2 @@
1
+ export { ELEMENT_NODE, TEXT_NODE, SVG_NAMESPACE, XHTML_NAMESPACE } from './dom.js';
2
+ export { EMBEDDED_CONTROL_ROLES, TEXT_INPUT_TYPES, TEXT_LIKE_INPUT_TYPES, DEFAULT_SUBMIT_LABEL, DEFAULT_RESET_LABEL, DEFAULT_IMAGE_LABEL, isNativeEmbeddedControl, } from './accname.js';
package/lib/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { getAttrSpecs as getAttrSpecsByNames } from './utils/get-attr-specs-spec.js';
2
2
  export * from './utils/aria-version.js';
3
+ export { EMBEDDED_CONTROL_ROLES, isNativeEmbeddedControl } from './const/index.js';
4
+ export * from './algorithm/aria/accname/index.js';
3
5
  export * from './algorithm/aria/accname-computation.js';
4
6
  export * from './algorithm/aria/get-computed-aria-props.js';
5
7
  export * from './algorithm/aria/get-computed-role.js';
@@ -24,6 +26,7 @@ export * from './utils/get-spec-by-tag-name.js';
24
26
  export * from './utils/schema-to-spec.js';
25
27
  export * from './utils/resolve-namespace.js';
26
28
  export * from './utils/validate-aria-version.js';
29
+ export * from './utils/directive-resolver.js';
27
30
  export * from './types/index.js';
28
31
  export * from './types/aria.js';
29
32
  export * from './types/attributes.js';
package/lib/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  export { getAttrSpecs as getAttrSpecsByNames } from './utils/get-attr-specs-spec.js';
2
2
  export * from './utils/aria-version.js';
3
+ // Constants
4
+ export { EMBEDDED_CONTROL_ROLES, isNativeEmbeddedControl } from './const/index.js';
3
5
  // ARIA algorithms
6
+ export * from './algorithm/aria/accname/index.js';
4
7
  export * from './algorithm/aria/accname-computation.js';
5
8
  export * from './algorithm/aria/get-computed-aria-props.js';
6
9
  export * from './algorithm/aria/get-computed-role.js';
@@ -27,6 +30,7 @@ export * from './utils/get-spec-by-tag-name.js';
27
30
  export * from './utils/schema-to-spec.js';
28
31
  export * from './utils/resolve-namespace.js';
29
32
  export * from './utils/validate-aria-version.js';
33
+ export * from './utils/directive-resolver.js';
30
34
  // Type definitions
31
35
  export * from './types/index.js';
32
36
  export * from './types/aria.js';
@@ -91,7 +91,7 @@ export interface ARIA {
91
91
  namingProhibited?: true;
92
92
  implicitProperties?: ImplicitProperties;
93
93
  properties?: PermittedARIAProperties;
94
- conditions?: {
94
+ conditions?: false | {
95
95
  /**
96
96
  * This interface was referenced by `undefined`'s JSON-Schema definition
97
97
  * via the `patternProperty` ".+".
@@ -111,7 +111,7 @@ export interface ARIA {
111
111
  namingProhibited?: true;
112
112
  implicitProperties?: ImplicitProperties;
113
113
  properties?: PermittedARIAProperties;
114
- conditions?: {
114
+ conditions?: false | {
115
115
  /**
116
116
  * This interface was referenced by `undefined`'s JSON-Schema definition
117
117
  * via the `patternProperty` ".+".
@@ -130,7 +130,7 @@ export interface ARIA {
130
130
  permittedRoles?: PermittedRoles;
131
131
  implicitProperties?: ImplicitProperties;
132
132
  properties?: PermittedARIAProperties;
133
- conditions?: {
133
+ conditions?: false | {
134
134
  /**
135
135
  * This interface was referenced by `undefined`'s JSON-Schema definition
136
136
  * via the `patternProperty` ".+".
@@ -11,6 +11,8 @@ export interface MLMLSpec {
11
11
  readonly cites: Cites;
12
12
  readonly def: SpecDefs;
13
13
  readonly specs: readonly ElementSpec[];
14
+ readonly directivePatterns?: readonly DirectivePattern[];
15
+ readonly acceptedAttrNames?: 'idl' | 'both';
14
16
  }
15
17
  /**
16
18
  * An element specification for extending or overriding parts of the base spec.
@@ -29,6 +31,56 @@ export type ExtendedSpec = {
29
31
  readonly cites?: Cites;
30
32
  readonly def?: Partial<SpecDefs>;
31
33
  readonly specs?: readonly ExtendedElementSpec[];
34
+ readonly directivePatterns?: readonly DirectivePattern[];
35
+ readonly acceptedAttrNames?: 'idl' | 'both';
36
+ };
37
+ /**
38
+ * A declarative pattern for resolving framework-specific directive
39
+ * attributes to their canonical names and metadata. Stored as part
40
+ * of the spec, enabling "parser-less" framework support.
41
+ *
42
+ * The `pattern` is a regex string matched against the raw attribute name.
43
+ * Capture groups can be referenced in `potentialName` using `$1`, `$2`, etc.
44
+ */
45
+ export type DirectivePattern = {
46
+ /**
47
+ * A regex string (without delimiters) matched against the raw attribute name.
48
+ * Must be a valid JavaScript RegExp pattern. Capture groups can be used
49
+ * for potentialName templates.
50
+ * @example "^(?:x-bind:|:)([^.]+)(?:\\.[^.]+)?$"
51
+ */
52
+ readonly pattern: string;
53
+ /**
54
+ * Optional regex flags (e.g., "i" for case-insensitive).
55
+ * @default "i"
56
+ */
57
+ readonly flags?: string;
58
+ /**
59
+ * Template for the resolved potentialName. References capture groups
60
+ * with $1, $2, etc. If omitted, the attribute is treated as a directive
61
+ * with no potentialName change.
62
+ * @example "on$1" -- for `@click` -> `onclick`
63
+ * @example "$1" -- for `:href` -> `href`
64
+ */
65
+ readonly potentialName?: string;
66
+ /**
67
+ * Whether this attribute is a framework directive.
68
+ */
69
+ readonly isDirective?: true;
70
+ /**
71
+ * Whether this attribute has a dynamic value.
72
+ */
73
+ readonly isDynamicValue?: true;
74
+ /**
75
+ * The semantic value type for matched attributes.
76
+ */
77
+ readonly valueType?: 'string' | 'number' | 'boolean' | 'code';
78
+ /**
79
+ * If true, the attribute can appear multiple times on the same element.
80
+ * Can also be a string array of potentialName values for which
81
+ * duplication is allowed (e.g., ['class', 'style']).
82
+ */
83
+ readonly isDuplicatable?: true | readonly string[];
32
84
  };
33
85
  /**
34
86
  * Reference URLs
@@ -54,6 +106,7 @@ export type SpecDefs = {
54
106
  type ARIASpec = {
55
107
  readonly roles: readonly ARIARoleInSchema[];
56
108
  readonly graphicsRoles: readonly ARIARoleInSchema[];
109
+ readonly dpubRoles: readonly ARIARoleInSchema[];
57
110
  readonly props: readonly ARIAProperty[];
58
111
  };
59
112
  /**
@@ -158,7 +211,19 @@ export type ARIARole = {
158
211
  readonly name: string;
159
212
  readonly isAbstract: boolean;
160
213
  readonly deprecated: boolean;
214
+ /** Required Accessibility Parent Role (ARIA 1.3 name) */
215
+ readonly requiredAccessibilityParentRole: readonly string[];
216
+ /** Allowed Accessibility Child Roles (ARIA 1.3 name) */
217
+ readonly allowedAccessibilityChildRoles: readonly string[];
218
+ /**
219
+ * @deprecated Use {@link ARIARole.requiredAccessibilityParentRole} instead.
220
+ * Retained for ARIA 1.2 backward compatibility.
221
+ */
161
222
  readonly requiredContextRole: readonly string[];
223
+ /**
224
+ * @deprecated Use {@link ARIARole.allowedAccessibilityChildRoles} instead.
225
+ * Retained for ARIA 1.2 backward compatibility.
226
+ */
162
227
  readonly requiredOwnedElements: readonly string[];
163
228
  readonly accessibleNameRequired: boolean;
164
229
  readonly accessibleNameFromAuthor: boolean;
@@ -5,4 +5,4 @@ export declare const ariaVersions: readonly ["1.1", "1.2", "1.3"];
5
5
  /**
6
6
  * The recommended default ARIA specification version to use when none is explicitly specified.
7
7
  */
8
- export declare const ARIA_RECOMMENDED_VERSION = "1.2";
8
+ export declare const ARIA_RECOMMENDED_VERSION = "1.3";
@@ -5,4 +5,4 @@ export const ariaVersions = ['1.1', '1.2', '1.3'];
5
5
  /**
6
6
  * The recommended default ARIA specification version to use when none is explicitly specified.
7
7
  */
8
- export const ARIA_RECOMMENDED_VERSION = '1.2';
8
+ export const ARIA_RECOMMENDED_VERSION = '1.3';
@@ -0,0 +1,23 @@
1
+ import type { DirectivePattern } from '../types/index.js';
2
+ type CompiledDirectivePattern = {
3
+ readonly regex: RegExp;
4
+ readonly pattern: DirectivePattern;
5
+ };
6
+ /**
7
+ * Compiles directive patterns into RegExp objects, caching them
8
+ * by the patterns array reference for efficiency.
9
+ */
10
+ export declare function compileDirectivePatterns(patterns: readonly DirectivePattern[]): readonly CompiledDirectivePattern[];
11
+ export type DirectiveResolution = {
12
+ readonly potentialName?: string;
13
+ readonly isDirective?: true;
14
+ readonly isDynamicValue?: true;
15
+ readonly valueType?: 'string' | 'number' | 'boolean' | 'code';
16
+ readonly isDuplicatable?: boolean;
17
+ };
18
+ /**
19
+ * Resolves an attribute name against a list of compiled directive patterns.
20
+ * Returns the first match's resolution, or null if no pattern matches.
21
+ */
22
+ export declare function resolveDirective(attrName: string, compiledPatterns: readonly CompiledDirectivePattern[]): DirectiveResolution | null;
23
+ export {};