@markuplint/ml-spec 3.0.0-alpha.82 → 3.0.0-dev.24

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 (91) hide show
  1. package/README.md +8 -3
  2. package/lib/dom-traverse/get-computed-aria-props.d.ts +12 -0
  3. package/lib/dom-traverse/get-computed-aria-props.js +106 -0
  4. package/lib/dom-traverse/get-computed-role.js +13 -25
  5. package/lib/dom-traverse/get-content-model.d.ts +3 -0
  6. package/lib/dom-traverse/get-content-model.js +28 -0
  7. package/lib/dom-traverse/get-explicit-role.js +1 -1
  8. package/lib/dom-traverse/get-spec.d.ts +2 -2
  9. package/lib/dom-traverse/get-spec.js +2 -2
  10. package/lib/dom-traverse/has-required-owned-elements.js +12 -0
  11. package/lib/dom-traverse/is-exposed.d.ts +11 -0
  12. package/lib/dom-traverse/is-exposed.js +181 -0
  13. package/lib/dom-traverse/may-be-focusable.d.ts +2 -0
  14. package/lib/dom-traverse/may-be-focusable.js +22 -0
  15. package/lib/index.d.ts +8 -1
  16. package/lib/index.js +8 -1
  17. package/lib/specs/content-model-category-to-tag-names.d.ts +2 -2
  18. package/lib/specs/content-model-category-to-tag-names.js +2 -2
  19. package/lib/specs/get-aria.js +3 -3
  20. package/lib/specs/get-attr-specs.d.ts +1 -1
  21. package/lib/specs/get-selectors-by-content-model-category.d.ts +1 -1
  22. package/lib/specs/get-spec-by-tag-name.d.ts +2 -2
  23. package/lib/specs/get-spec-by-tag-name.js +1 -1
  24. package/lib/specs/is-nothing-content-model.d.ts +5 -0
  25. package/lib/specs/is-nothing-content-model.js +12 -0
  26. package/lib/specs/is-palpable-elements.d.ts +5 -0
  27. package/lib/specs/is-palpable-elements.js +47 -0
  28. package/lib/specs/is-void-element.d.ts +1 -0
  29. package/lib/specs/is-void-element.js +25 -0
  30. package/lib/specs/schema-to-spec.d.ts +1 -1
  31. package/lib/specs/schema-to-spec.js +1 -1
  32. package/lib/types/aria.d.ts +10 -4
  33. package/lib/types/attributes.d.ts +94 -39
  34. package/lib/types/index.d.ts +27 -27
  35. package/lib/types/permitted-structres.d.ts +22 -28
  36. package/lib/types/permitted-structures.d.ts +54 -0
  37. package/lib/types/permitted-structures.js +8 -0
  38. package/lib/utils/get-ns.d.ts +2 -1
  39. package/lib/utils/get-ns.js +3 -0
  40. package/lib/utils/merge-array.d.ts +1 -1
  41. package/lib/utils/resolve-namespace.d.ts +4 -4
  42. package/lib/utils/resolve-namespace.js +18 -13
  43. package/package.json +7 -7
  44. package/schema.json +6 -6
  45. package/schemas/aria.schema.json +1 -1
  46. package/schemas/content-models.schema.json +33 -49
  47. package/src/dom-traverse/accname-computation.spec.ts +6 -6
  48. package/src/dom-traverse/get-computed-aria-props.spec.ts +368 -0
  49. package/src/dom-traverse/get-computed-aria-props.ts +130 -0
  50. package/src/dom-traverse/get-computed-role.spec.ts +1 -1
  51. package/src/dom-traverse/get-computed-role.ts +13 -25
  52. package/src/dom-traverse/get-content-model.ts +32 -0
  53. package/src/dom-traverse/get-explicit-role.ts +1 -1
  54. package/src/dom-traverse/get-spec.ts +3 -3
  55. package/src/dom-traverse/has-required-owned-elements.ts +13 -0
  56. package/src/dom-traverse/is-exposed.spec.ts +50 -0
  57. package/src/dom-traverse/is-exposed.ts +196 -0
  58. package/src/dom-traverse/may-be-focusable.ts +21 -0
  59. package/src/index.ts +8 -1
  60. package/src/specs/content-model-category-to-tag-names.ts +3 -3
  61. package/src/specs/get-aria.ts +3 -3
  62. package/src/specs/get-selectors-by-content-model-category.ts +1 -1
  63. package/src/specs/get-spec-by-tag-name.spec.ts +10 -10
  64. package/src/specs/get-spec-by-tag-name.ts +9 -5
  65. package/src/specs/is-nothing-content-model.ts +9 -0
  66. package/src/specs/is-palpable-elements.ts +55 -0
  67. package/src/specs/is-void-element.ts +22 -0
  68. package/src/specs/schema-to-spec.ts +1 -1
  69. package/src/types/aria.ts +7 -1
  70. package/src/types/attributes.ts +227 -121
  71. package/src/types/index.ts +8 -8
  72. package/src/types/permitted-structres.ts +24 -32
  73. package/src/types/permitted-structures.ts +96 -0
  74. package/src/utils/get-ns.ts +6 -1
  75. package/src/utils/resolve-namespace.spec.ts +37 -0
  76. package/src/utils/resolve-namespace.ts +22 -18
  77. package/tsconfig.tsbuildinfo +1 -1
  78. package/lib/dom-traverse/focusability-computation.d.ts +0 -1
  79. package/lib/dom-traverse/focusability-computation.js +0 -8
  80. package/lib/dom-traverse/get-aria.d.ts +0 -9
  81. package/lib/dom-traverse/get-aria.js +0 -54
  82. package/lib/specs/aria-spec.d.ts +0 -5
  83. package/lib/specs/aria-spec.js +0 -12
  84. package/lib/specs/get-spec.d.ts +0 -13
  85. package/lib/specs/get-spec.js +0 -85
  86. package/lib/specs/html-spec.d.ts +0 -6
  87. package/lib/specs/html-spec.js +0 -16
  88. package/lib/specs/resolve-namespace.d.ts +0 -9
  89. package/lib/specs/resolve-namespace.js +0 -26
  90. package/lib/utils.d.ts +0 -8
  91. package/lib/utils.js +0 -50
package/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # @markuplint/ml-spec
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/%40markuplint%2Fml-spec.svg)](https://www.npmjs.com/package/@markuplint/ml-spec)
4
- [![Build Status](https://travis-ci.org/markuplint/markuplint.svg?branch=main)](https://travis-ci.org/markuplint/markuplint)
5
- [![Coverage Status](https://coveralls.io/repos/github/markuplint/markuplint/badge.svg?branch=main)](https://coveralls.io/github/markuplint/markuplint?branch=main)
6
4
 
7
5
  ## Install
8
6
 
9
- ```sh
7
+ [`markuplint`](https://www.npmjs.com/package/markuplint) package includes this package.
8
+
9
+ <details>
10
+ <summary>If you are installing purposely, how below:</summary>
11
+
12
+ ```shell
10
13
  $ npm install @markuplint/ml-spec
11
14
 
12
15
  $ yarn add @markuplint/ml-spec
13
16
  ```
17
+
18
+ </details>
@@ -0,0 +1,12 @@
1
+ import type { ARIAVersion, MLMLSpec } from '../types';
2
+ type ARIAProps = Record<string, ARIAProp>;
3
+ type ARIAProp = {
4
+ name: string;
5
+ value: string | undefined;
6
+ required: boolean;
7
+ deprecated: boolean;
8
+ from: ARIAPropReferenceType;
9
+ };
10
+ type ARIAPropReferenceType = 'default' | 'html-attr' | 'aria-attr';
11
+ export declare function getComputedAriaProps(specs: Readonly<MLMLSpec>, el: Element, version: ARIAVersion): ARIAProps;
12
+ export {};
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getComputedAriaProps = void 0;
4
+ const aria_specs_1 = require("../specs/aria-specs");
5
+ const get_computed_role_1 = require("./get-computed-role");
6
+ function getComputedAriaProps(specs, el, version) {
7
+ const ariaSpecs = (0, aria_specs_1.ariaSpecs)(specs, version);
8
+ const { role } = (0, get_computed_role_1.getComputedRole)(specs, el, version);
9
+ if (!role) {
10
+ return {};
11
+ }
12
+ const props = {};
13
+ role.ownedProperties.forEach(ownedProp => {
14
+ var _a;
15
+ const spec = ariaSpecs.props.find(propSpec => propSpec.name === ownedProp.name);
16
+ if (!spec) {
17
+ return;
18
+ }
19
+ if (el.hasAttribute(spec.name)) {
20
+ const attrValue = el.getAttribute(spec.name);
21
+ if (attrValue && isValidAriaValue(spec, role.name, attrValue, spec.enum)) {
22
+ props[ownedProp.name] = {
23
+ name: ownedProp.name,
24
+ value: attrValue,
25
+ deprecated: !!spec.deprecated,
26
+ required: !!ownedProp.required,
27
+ from: 'aria-attr',
28
+ };
29
+ return;
30
+ }
31
+ }
32
+ const equivalentHtmlAttr = (_a = spec.equivalentHtmlAttrs) === null || _a === void 0 ? void 0 : _a[0];
33
+ if (equivalentHtmlAttr && el.hasAttribute(equivalentHtmlAttr.htmlAttrName)) {
34
+ const value = equivalentHtmlAttr.value === null
35
+ ? el.getAttribute(equivalentHtmlAttr.htmlAttrName)
36
+ : equivalentHtmlAttr.value;
37
+ if (value != null) {
38
+ props[ownedProp.name] = {
39
+ name: ownedProp.name,
40
+ value,
41
+ deprecated: !!spec.deprecated,
42
+ required: !!ownedProp.required,
43
+ from: 'html-attr',
44
+ };
45
+ return;
46
+ }
47
+ }
48
+ let defaultValue = spec.defaultValue;
49
+ /**
50
+ * @see https://www.w3.org/TR/html-aria/#el-h1-h6
51
+ */
52
+ if (ownedProp.name === 'aria-level' && /^H[1-6]$/.test(el.nodeName)) {
53
+ defaultValue = el.nodeName.replace('H', '');
54
+ }
55
+ props[ownedProp.name] = {
56
+ name: ownedProp.name,
57
+ value: defaultValue,
58
+ deprecated: !!spec.deprecated,
59
+ required: !!ownedProp.required,
60
+ from: 'default',
61
+ };
62
+ });
63
+ return props;
64
+ }
65
+ exports.getComputedAriaProps = getComputedAriaProps;
66
+ function isValidAriaValue(spec, role, value, enumList) {
67
+ let type = spec.value;
68
+ if (spec.conditionalValue) {
69
+ for (const cond of spec.conditionalValue) {
70
+ if (cond.role.includes(role)) {
71
+ type = cond.value;
72
+ }
73
+ }
74
+ }
75
+ value = (value !== null && value !== void 0 ? value : '').trim();
76
+ switch (type) {
77
+ case 'string': {
78
+ return true;
79
+ }
80
+ case 'ID reference':
81
+ case 'ID reference list':
82
+ case 'URI': {
83
+ return !!value;
84
+ }
85
+ case 'integer':
86
+ case 'number': {
87
+ return !isNaN(parseFloat(value));
88
+ }
89
+ case 'token':
90
+ case 'token list': {
91
+ if (!enumList.length) {
92
+ throw new Error('Need an enum list in token and token list types');
93
+ }
94
+ return enumList.includes(value.toLowerCase());
95
+ }
96
+ case 'tristate': {
97
+ return ['true', 'false', 'mixed'].includes(value.toLowerCase());
98
+ }
99
+ case 'true/false': {
100
+ return ['true', 'false'].includes(value.toLowerCase());
101
+ }
102
+ case 'true/false/undefined': {
103
+ return ['true', 'false', 'undefined'].includes(value.toLowerCase());
104
+ }
105
+ }
106
+ }
@@ -2,13 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getComputedRole = void 0;
4
4
  const aria_specs_1 = require("../specs/aria-specs");
5
- const get_selectors_by_content_model_category_1 = require("../specs/get-selectors-by-content-model-category");
6
5
  const is_presentational_1 = require("../specs/is-presentational");
7
6
  const get_attr_specs_1 = require("./get-attr-specs");
8
7
  const get_explicit_role_1 = require("./get-explicit-role");
9
8
  const get_implicit_role_1 = require("./get-implicit-role");
10
9
  const get_non_presentational_ancestor_1 = require("./get-non-presentational-ancestor");
11
10
  const has_required_owned_elements_1 = require("./has-required-owned-elements");
11
+ const may_be_focusable_1 = require("./may-be-focusable");
12
12
  function getComputedRole(specs, el, version) {
13
13
  var _a, _b, _c, _d;
14
14
  const implicitRole = (0, get_implicit_role_1.getImplicitRole)(specs, el, version);
@@ -72,38 +72,26 @@ function getComputedRole(specs, el, version) {
72
72
  */
73
73
  if (
74
74
  /**
75
- * Interactive element
75
+ * If interactive element
76
+ *
77
+ * 1. It may be focusable
78
+ *
79
+ * THIS CONDITION IS ALMOST MEANINGLESS.
80
+ * Because it has already been determined that
81
+ * the interactive elements can not specify the presentational role
82
+ * in the previous processing that computes the permitted role (`getExplicitRole`).
76
83
  */
77
- [
78
- /**
79
- * Interactive content
80
- *
81
- * THIS CONDITION(SELECTORS) IS ALMOST MEANINGLESS.
82
- * Because it has already been determined that
83
- * the interactive elements can not specify the presentational role
84
- * in the previous processing that computes the permitted role (`getExplicitRole`).
85
- *
86
- * @see https://html.spec.whatwg.org/multipage/dom.html#interactive-content
87
- */
88
- ...(0, get_selectors_by_content_model_category_1.getSelectorsByContentModelCategory)(specs, '#interactive'),
89
- /**
90
- * Interaction
91
- *
92
- * @see https://html.spec.whatwg.org/multipage/interaction.html
93
- */
94
- '[tabindex]',
95
- '[contenteditable]:not([contenteditable="false" i])',
96
- ].some(selector => el.matches(selector)) &&
84
+ (0, may_be_focusable_1.mayBeFocusable)(el, specs) &&
97
85
  /**
98
- * No disabled
86
+ * 2. No disabled
99
87
  */
100
88
  !someAncestors(el, p => isEnabledAttr(p, specs, 'disabled')) &&
101
89
  /**
102
- * No inert
90
+ * 3. No inert
103
91
  */
104
92
  !someAncestors(el, p => isEnabledAttr(p, specs, 'inert')) &&
105
93
  /**
106
- * No hidden
94
+ * 4. No hidden
107
95
  */
108
96
  !someAncestors(el, p => isEnabledAttr(p, specs, 'hidden'))) {
109
97
  return {
@@ -0,0 +1,3 @@
1
+ import type { ElementSpec } from '../types';
2
+ import type { PermittedContentPattern } from '../types/permitted-structures';
3
+ export declare function getContentModel(el: Element, specs: Pick<ElementSpec, 'name' | 'contentModel'>[]): boolean | PermittedContentPattern[] | null;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getContentModel = void 0;
4
+ const get_spec_1 = require("./get-spec");
5
+ const cachesBySpecs = new Map();
6
+ function getContentModel(el, specs) {
7
+ var _a, _b;
8
+ const cacheByEl = (_a = cachesBySpecs.get(specs)) !== null && _a !== void 0 ? _a : new Map();
9
+ const cached = cacheByEl.get(el);
10
+ if (cached) {
11
+ return cached;
12
+ }
13
+ const spec = (0, get_spec_1.getSpec)(el, specs);
14
+ if (!spec) {
15
+ cacheByEl.set(el, null);
16
+ return null;
17
+ }
18
+ const conditions = (_b = spec.contentModel.conditional) !== null && _b !== void 0 ? _b : [];
19
+ for (const cond of conditions) {
20
+ if (el.matches(cond.condition)) {
21
+ cacheByEl.set(el, cond.contents);
22
+ return cond.contents;
23
+ }
24
+ }
25
+ cacheByEl.set(el, spec.contentModel.contents);
26
+ return spec.contentModel.contents;
27
+ }
28
+ exports.getContentModel = getContentModel;
@@ -13,7 +13,7 @@ function getExplicitRole(specs, el, version) {
13
13
  /**
14
14
  * Resolve from values and **Handling Author Errors**
15
15
  *
16
- * @see https://w3c.github.io/aria/#document-handling_author-errors_roles
16
+ * @see https://w3c.github.io/aria/#document-handling_author-errors
17
17
  */
18
18
  for (const roleName of roleNames) {
19
19
  const spec = (0, get_role_spec_1.getRoleSpec)(specs, roleName, namespaceURI, version);
@@ -1,2 +1,2 @@
1
- import type { MLMLSpec } from '../types';
2
- export declare function getSpec(el: Element, schema: MLMLSpec): import("../types").ElementSpec | null;
1
+ import type { ElementSpec } from '../types';
2
+ export declare function getSpec<K extends keyof ElementSpec>(el: Element, specs: Pick<ElementSpec, 'name' | K>[]): Pick<ElementSpec, "name" | K> | null;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getSpec = void 0;
4
4
  const get_spec_by_tag_name_1 = require("../specs/get-spec-by-tag-name");
5
- function getSpec(el, schema) {
6
- return (0, get_spec_by_tag_name_1.getSpecByTagName)(schema, el.localName, el.namespaceURI);
5
+ function getSpec(el, specs) {
6
+ return (0, get_spec_by_tag_name_1.getSpecByTagName)(specs, el.localName, el.namespaceURI);
7
7
  }
8
8
  exports.getSpec = getSpec;
@@ -6,6 +6,18 @@ const get_computed_role_1 = require("./get-computed-role");
6
6
  const get_explicit_role_1 = require("./get-explicit-role");
7
7
  const get_implicit_role_1 = require("./get-implicit-role");
8
8
  function hasRequiredOwnedElement(el, specs, version) {
9
+ /**
10
+ * The element has aria-owns which means it may have owned elements.
11
+ *
12
+ * THIS CONDITION IS PARTIAL SUPPORT.
13
+ */
14
+ if (el.hasAttribute('aria-owns')) {
15
+ // FIXME
16
+ return true;
17
+ }
18
+ /**
19
+ * Otherwise, traverses descendants to find owned elements.
20
+ */
9
21
  const computed = (0, get_computed_role_1.getComputedRole)(specs, el, version);
10
22
  if (!computed.role || computed.role.requiredOwnedElements.length === 0) {
11
23
  return true;
@@ -0,0 +1,11 @@
1
+ import type { ARIAVersion, MLMLSpec } from '../types';
2
+ /**
3
+ * Detect including/excluding from the Accessibility Tree
4
+ *
5
+ * @see https://www.w3.org/TR/wai-aria-1.2/#accessibility_tree
6
+ *
7
+ * @param specs
8
+ * @param el
9
+ * @param version
10
+ */
11
+ export declare function isExposed(el: Element, specs: Readonly<MLMLSpec>, version: ARIAVersion): boolean;
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isExposed = void 0;
4
+ const aria_specs_1 = require("../specs/aria-specs");
5
+ const is_palpable_elements_1 = require("../specs/is-palpable-elements");
6
+ const is_presentational_1 = require("../specs/is-presentational");
7
+ const get_computed_role_1 = require("./get-computed-role");
8
+ /**
9
+ * Detect including/excluding from the Accessibility Tree
10
+ *
11
+ * @see https://www.w3.org/TR/wai-aria-1.2/#accessibility_tree
12
+ *
13
+ * @param specs
14
+ * @param el
15
+ * @param version
16
+ */
17
+ function isExposed(el, specs, version) {
18
+ // According to WAI-ARIA
19
+ if (isExcluding(el, specs, version)) {
20
+ return false;
21
+ }
22
+ // According to HTML and SVG Specs with **the author's interpretation**
23
+ {
24
+ if (!(0, is_palpable_elements_1.isPalpableElement)(el, specs, { extendsSvg: true, extendsExposableElements: true })) {
25
+ return false;
26
+ }
27
+ }
28
+ // According to WAI-ARIA
29
+ {
30
+ const exposable = isIncluding(el, specs, version);
31
+ if (exposable) {
32
+ return true;
33
+ }
34
+ }
35
+ // Default
36
+ return true;
37
+ }
38
+ exports.isExposed = isExposed;
39
+ /**
40
+ * Excluding Elements from the Accessibility Tree
41
+ *
42
+ * @see https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion
43
+ *
44
+ * @param specs
45
+ * @param el
46
+ * @param version
47
+ */
48
+ function isExcluding(el, specs, version) {
49
+ /**
50
+ * The following elements are not exposed via the accessibility API and
51
+ * user agents MUST NOT include them in the accessibility tree:
52
+ * - Elements, including their descendent elements,
53
+ * that have host language semantics specifying
54
+ * that the element is not displayed, such as CSS display:none,
55
+ * visibility:hidden, or the HTML hidden attribute.
56
+ */
57
+ {
58
+ let currentEl = el;
59
+ while (currentEl) {
60
+ if (hasDisplayNodeOrVisibilityHidden(currentEl) || currentEl.hasAttribute('hidden')) {
61
+ return true;
62
+ }
63
+ currentEl = currentEl.parentElement;
64
+ }
65
+ }
66
+ /**
67
+ * - Elements with none or presentation as the first role in the role attribute.
68
+ * However, their exclusion is conditional. In addition,
69
+ * the element's descendants and text content are generally included.
70
+ * These exceptions and conditions are documented in the presentation (role)
71
+ * section.
72
+ */
73
+ if ((0, is_presentational_1.isPresentational)((el.getAttribute('role') || '').split(/\s+/)[0])) {
74
+ return true;
75
+ }
76
+ /**
77
+ * If not already excluded from the accessibility tree per the above rules,
78
+ * user agents SHOULD NOT include the following elements
79
+ * in the accessibility tree:
80
+ * - Elements, including their descendants, that have aria-hidden set to true.
81
+ * In other words, aria-hidden="true" on a parent overrides aria-hidden="false"
82
+ * on descendants.
83
+ */
84
+ {
85
+ let currentEl = el;
86
+ while (currentEl) {
87
+ if (currentEl.getAttribute('aria-hidden') === 'true') {
88
+ return true;
89
+ }
90
+ currentEl = currentEl.parentElement;
91
+ }
92
+ }
93
+ /**
94
+ * - Any descendants of elements that have the characteristic
95
+ * "Children Presentational: True" unless the descendant
96
+ * is not allowed to be presentational because it meets one of
97
+ * the conditions for exception described in
98
+ * Presentational Roles Conflict Resolution.
99
+ * However, the text content of any excluded descendants is included.
100
+ */
101
+ {
102
+ let currentEl = el.parentElement;
103
+ while (currentEl) {
104
+ const { role } = (0, get_computed_role_1.getComputedRole)(specs, currentEl, version);
105
+ if (role === null || role === void 0 ? void 0 : role.childrenPresentational) {
106
+ return true;
107
+ }
108
+ currentEl = currentEl.parentElement;
109
+ }
110
+ }
111
+ return false;
112
+ }
113
+ /**
114
+ * Including Elements in the Accessibility Tree
115
+ *
116
+ * If not excluded from or marked as hidden in the accessibility tree
117
+ * per the rules above in Excluding Elements in the Accessibility Tree,
118
+ * user agents MUST provide an accessible object in the accessibility tree
119
+ * for DOM elements that meet any of the following criteria:
120
+ *
121
+ * @see https://www.w3.org/TR/wai-aria-1.2/#tree_inclusion
122
+ *
123
+ * @param specs
124
+ * @param el
125
+ * @param version
126
+ */
127
+ function isIncluding(el, specs, version) {
128
+ /**
129
+ * > **meet any** of the following criteria:
130
+ */
131
+ const results = [];
132
+ /**
133
+ * Elements that are not hidden and may fire an accessibility API event,
134
+ * including:
135
+ * - Elements that are currently focused, even if the element or one of
136
+ * its ancestor elements has its aria-hidden attribute set to true.
137
+ */
138
+ // 🚫 Can't detect the element is focused.
139
+ // results.push(true);
140
+ /**
141
+ * - Elements that are a valid target of an aria-activedescendant attribute.
142
+ */
143
+ // TODO: Compute aria-activedescendant.
144
+ // results.push(true);
145
+ /**
146
+ * Elements that have an explicit role or a global WAI-ARIA attribute and
147
+ * do not have aria-hidden set to true.
148
+ * (See Excluding Elements in the Accessibility Tree for
149
+ * additional guidance on aria-hidden.)
150
+ */
151
+ if (el.getAttribute('aria-hidden') !== 'true') {
152
+ const globalAria = (0, aria_specs_1.ariaSpecs)(specs, version).props.filter(prop => prop.isGlobal);
153
+ const { role } = (0, get_computed_role_1.getComputedRole)(specs, el, version);
154
+ // Has an explicit role
155
+ if (role && !role.isImplicit) {
156
+ results.push(true);
157
+ }
158
+ // Has a global WAI-ARIA attribute
159
+ for (const attr of Array.from(el.attributes)) {
160
+ if (globalAria.some(aria => aria.name === attr.localName)) {
161
+ results.push(true);
162
+ break;
163
+ }
164
+ }
165
+ }
166
+ /**
167
+ * Elements that are not hidden and have an ID that is referenced
168
+ * by another element via a WAI-ARIA property.
169
+ */
170
+ // TODO: Compute refering ID.
171
+ // results.push(true);
172
+ return results.includes(true);
173
+ }
174
+ function hasDisplayNodeOrVisibilityHidden(el) {
175
+ const style = el.getAttribute('style');
176
+ if (!style) {
177
+ return false;
178
+ }
179
+ // TODO: Improve accuracy
180
+ return /display\s*:\s*none|visibility\s*:\s*hidden/gi.test(style);
181
+ }
@@ -0,0 +1,2 @@
1
+ import type { MLMLSpec } from '../types';
2
+ export declare function mayBeFocusable(el: Element, specs: Readonly<MLMLSpec>): boolean;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mayBeFocusable = void 0;
4
+ const get_selectors_by_content_model_category_1 = require("../specs/get-selectors-by-content-model-category");
5
+ function mayBeFocusable(el, specs) {
6
+ return [
7
+ /**
8
+ * Interactive content
9
+ *
10
+ * @see https://html.spec.whatwg.org/multipage/dom.html#interactive-content
11
+ */
12
+ ...(0, get_selectors_by_content_model_category_1.getSelectorsByContentModelCategory)(specs, '#interactive'),
13
+ /**
14
+ * Interaction
15
+ *
16
+ * @see https://html.spec.whatwg.org/multipage/interaction.html
17
+ */
18
+ '[tabindex]',
19
+ '[contenteditable]:not([contenteditable="false" i])',
20
+ ].some(selector => el.matches(selector));
21
+ }
22
+ exports.mayBeFocusable = mayBeFocusable;
package/lib/index.d.ts CHANGED
@@ -1,17 +1,24 @@
1
1
  export * from './dom-traverse/accname-computation';
2
2
  export * from './dom-traverse/get-attr-specs';
3
+ export * from './dom-traverse/get-computed-aria-props';
3
4
  export * from './dom-traverse/get-computed-role';
5
+ export * from './dom-traverse/get-content-model';
4
6
  export * from './dom-traverse/get-implicit-role';
5
7
  export * from './dom-traverse/get-permitted-roles';
6
8
  export * from './dom-traverse/get-spec';
7
9
  export * from './dom-traverse/has-required-owned-elements';
10
+ export * from './dom-traverse/is-exposed';
11
+ export * from './dom-traverse/may-be-focusable';
8
12
  export * from './specs/aria-specs';
9
13
  export * from './specs/content-model-category-to-tag-names';
10
14
  export * from './specs/get-role-spec';
11
15
  export * from './specs/get-spec-by-tag-name';
16
+ export * from './specs/is-nothing-content-model';
17
+ export * from './specs/is-palpable-elements';
18
+ export * from './specs/is-void-element';
12
19
  export * from './specs/schema-to-spec';
13
20
  export * from './types';
14
21
  export * from './types/aria';
15
22
  export * from './types/attributes';
16
- export * from './types/permitted-structres';
23
+ export * from './types/permitted-structures';
17
24
  export * from './utils/resolve-namespace';
package/lib/index.js CHANGED
@@ -3,18 +3,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  tslib_1.__exportStar(require("./dom-traverse/accname-computation"), exports);
5
5
  tslib_1.__exportStar(require("./dom-traverse/get-attr-specs"), exports);
6
+ tslib_1.__exportStar(require("./dom-traverse/get-computed-aria-props"), exports);
6
7
  tslib_1.__exportStar(require("./dom-traverse/get-computed-role"), exports);
8
+ tslib_1.__exportStar(require("./dom-traverse/get-content-model"), exports);
7
9
  tslib_1.__exportStar(require("./dom-traverse/get-implicit-role"), exports);
8
10
  tslib_1.__exportStar(require("./dom-traverse/get-permitted-roles"), exports);
9
11
  tslib_1.__exportStar(require("./dom-traverse/get-spec"), exports);
10
12
  tslib_1.__exportStar(require("./dom-traverse/has-required-owned-elements"), exports);
13
+ tslib_1.__exportStar(require("./dom-traverse/is-exposed"), exports);
14
+ tslib_1.__exportStar(require("./dom-traverse/may-be-focusable"), exports);
11
15
  tslib_1.__exportStar(require("./specs/aria-specs"), exports);
12
16
  tslib_1.__exportStar(require("./specs/content-model-category-to-tag-names"), exports);
13
17
  tslib_1.__exportStar(require("./specs/get-role-spec"), exports);
14
18
  tslib_1.__exportStar(require("./specs/get-spec-by-tag-name"), exports);
19
+ tslib_1.__exportStar(require("./specs/is-nothing-content-model"), exports);
20
+ tslib_1.__exportStar(require("./specs/is-palpable-elements"), exports);
21
+ tslib_1.__exportStar(require("./specs/is-void-element"), exports);
15
22
  tslib_1.__exportStar(require("./specs/schema-to-spec"), exports);
16
23
  tslib_1.__exportStar(require("./types"), exports);
17
24
  tslib_1.__exportStar(require("./types/aria"), exports);
18
25
  tslib_1.__exportStar(require("./types/attributes"), exports);
19
- tslib_1.__exportStar(require("./types/permitted-structres"), exports);
26
+ tslib_1.__exportStar(require("./types/permitted-structures"), exports);
20
27
  tslib_1.__exportStar(require("./utils/resolve-namespace"), exports);
@@ -1,3 +1,3 @@
1
1
  import type { MLMLSpec } from '../types';
2
- import type { Category } from '../types/permitted-structres';
3
- export declare function contentModelCategoryToTagNames(contentModel: Category, specs: MLMLSpec): ReadonlyArray<string>;
2
+ import type { Category } from '../types/permitted-structures';
3
+ export declare function contentModelCategoryToTagNames(contentModel: Category, def: MLMLSpec['def']): ReadonlyArray<string>;
@@ -2,12 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.contentModelCategoryToTagNames = void 0;
4
4
  const cache = new Map();
5
- function contentModelCategoryToTagNames(contentModel, specs) {
5
+ function contentModelCategoryToTagNames(contentModel, def) {
6
6
  const cached = cache.get(contentModel);
7
7
  if (cached) {
8
8
  return cached;
9
9
  }
10
- const tags = specs.def['#contentModels'][contentModel];
10
+ const tags = def['#contentModels'][contentModel];
11
11
  const sortedTag = Object.freeze(tags && Array.isArray(tags) ? tags.sort() : []);
12
12
  cache.set(contentModel, sortedTag);
13
13
  return sortedTag;
@@ -14,9 +14,9 @@ function getARIA(specs, localName, namespace, version, matches) {
14
14
  if (!conditions) {
15
15
  return aria;
16
16
  }
17
- const conds = Object.keys(conditions);
17
+ const conditionKeys = Object.keys(conditions);
18
18
  let { implicitRole, permittedRoles, implicitProperties, properties, namingProhibited } = aria;
19
- for (const cond of conds) {
19
+ for (const cond of conditionKeys) {
20
20
  if (!matches(cond)) {
21
21
  continue;
22
22
  }
@@ -43,7 +43,7 @@ function getVersionResolvedARIA(specs, localName, namespace, version) {
43
43
  if (aria !== undefined) {
44
44
  return aria;
45
45
  }
46
- const spec = (_a = (0, get_spec_by_tag_name_1.getSpecByTagName)(specs, localName, namespace)) === null || _a === void 0 ? void 0 : _a.aria;
46
+ const spec = (_a = (0, get_spec_by_tag_name_1.getSpecByTagName)(specs.specs, localName, namespace)) === null || _a === void 0 ? void 0 : _a.aria;
47
47
  if (!spec) {
48
48
  cache.set(key, null);
49
49
  return null;
@@ -1,7 +1,7 @@
1
1
  import type { MLMLSpec, Attribute } from '../types';
2
2
  import type { NamespaceURI } from '@markuplint/ml-ast';
3
3
  export declare function getAttrSpecs(localName: string, namespace: NamespaceURI | null, schema: MLMLSpec): Attribute[] | null;
4
- declare type HasName = {
4
+ type HasName = {
5
5
  name: string;
6
6
  };
7
7
  export declare function nameCompare(a: HasName | string, b: HasName | string): 1 | -1 | 0;
@@ -1,3 +1,3 @@
1
1
  import type { MLMLSpec } from '../types';
2
- import type { Category } from '../types/permitted-structres';
2
+ import type { Category } from '../types/permitted-structures';
3
3
  export declare function getSelectorsByContentModelCategory(specs: Readonly<MLMLSpec>, category: Category): ReadonlyArray<string>;
@@ -1,2 +1,2 @@
1
- import type { ElementSpec, MLMLSpec } from '../types';
2
- export declare function getSpecByTagName(specs: Readonly<MLMLSpec>, localName: string, namespace: string | null): ElementSpec | null;
1
+ import type { ElementSpec } from '../types';
2
+ export declare function getSpecByTagName<K extends keyof ElementSpec = keyof ElementSpec>(specs: Readonly<Pick<ElementSpec, 'name' | K>[]>, localName: string, namespace: string | null): Pick<ElementSpec, "name" | K> | null;
@@ -9,7 +9,7 @@ function getSpecByTagName(specs, localName, namespace) {
9
9
  if (spec !== undefined) {
10
10
  return spec;
11
11
  }
12
- spec = specs.specs.find(spec => spec.name === localNameWithNS) || null;
12
+ spec = specs.find(spec => spec.name === localNameWithNS) || null;
13
13
  cache.set(localNameWithNS, spec);
14
14
  return spec;
15
15
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @see https://html.spec.whatwg.org/multipage/dom.html#the-nothing-content-model
3
+ * @see https://html.spec.whatwg.org/multipage/indices.html#elements-3
4
+ */
5
+ export declare function isNothingContentModel(el: Element): boolean;