@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
@@ -0,0 +1,50 @@
1
+ import type { ARIAVersion } from '../types';
2
+
3
+ import specs from '@markuplint/html-spec';
4
+ import { createSelector } from '@markuplint/selector';
5
+ import { createJSDOMElement } from '@markuplint/test-tools';
6
+
7
+ import { isExposed } from './is-exposed';
8
+
9
+ function _(html: string, selector?: string) {
10
+ return createJSDOMElement(html, selector, function (selector) {
11
+ // JSDOM supports no level 4 selectors yet.
12
+ return !!createSelector(selector, specs).match(this);
13
+ });
14
+ }
15
+
16
+ function x(html: string, selector?: string, version: ARIAVersion = '1.2') {
17
+ const el = _(html, selector);
18
+ return isExposed(el, specs, version);
19
+ }
20
+
21
+ describe('isExposed', () => {
22
+ test('Basic', () => {
23
+ expect(x('<div></div>')).toBe(true);
24
+ expect(x('<div aria-hidden="true"></div>')).toBe(false);
25
+ expect(x('<div role="presentation"></div>')).toBe(false);
26
+ expect(x('<div role="presentation" aria-hidden="true"></div>')).toBe(false);
27
+ expect(x('<div role="presentation"><span></span></div>', 'span')).toBe(true);
28
+ expect(x('<div role="presentation" aria-hidden="true"><span></span></div>', 'span')).toBe(false);
29
+ expect(x('<div style="display: none;"><span></span></div>', 'span')).toBe(false);
30
+ expect(x('<div hidden><span></span></div>', 'span')).toBe(false);
31
+ expect(x('<div hidden="until-found"><span></span></div>', 'span')).toBe(false);
32
+ expect(x('<html><body>content</body></html>', 'body')).toBe(true);
33
+ expect(x('<ul><li></li></ul>', 'li')).toBe(true);
34
+ });
35
+
36
+ test('Children Presentational: True', () => {
37
+ expect(x('<button></button>')).toBe(true);
38
+ expect(x('<button hidden></button>')).toBe(false);
39
+ expect(x('<button><span></span></button>', 'span')).toBe(false);
40
+ expect(x('<button hidden><span></span></button>', 'span')).toBe(false);
41
+ });
42
+
43
+ test('Hidden elements', () => {
44
+ expect(x('<input type="text">')).toBe(true);
45
+ expect(x('<input type="hidden">')).toBe(false);
46
+ expect(x('<meta>')).toBe(false);
47
+ expect(x('<style>a{}</style>')).toBe(false);
48
+ expect(x('<script>"";</script>')).toBe(false);
49
+ });
50
+ });
@@ -0,0 +1,196 @@
1
+ import type { ARIAVersion, MLMLSpec } from '../types';
2
+
3
+ import { ariaSpecs } from '../specs/aria-specs';
4
+ import { isPalpableElement } from '../specs/is-palpable-elements';
5
+ import { isPresentational } from '../specs/is-presentational';
6
+
7
+ import { getComputedRole } from './get-computed-role';
8
+
9
+ /**
10
+ * Detect including/excluding from the Accessibility Tree
11
+ *
12
+ * @see https://www.w3.org/TR/wai-aria-1.2/#accessibility_tree
13
+ *
14
+ * @param specs
15
+ * @param el
16
+ * @param version
17
+ */
18
+ export function isExposed(el: Element, specs: Readonly<MLMLSpec>, version: ARIAVersion): boolean {
19
+ // According to WAI-ARIA
20
+ if (isExcluding(el, specs, version)) {
21
+ return false;
22
+ }
23
+
24
+ // According to HTML and SVG Specs with **the author's interpretation**
25
+ {
26
+ if (!isPalpableElement(el, specs, { extendsSvg: true, extendsExposableElements: true })) {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ // According to WAI-ARIA
32
+ {
33
+ const exposable = isIncluding(el, specs, version);
34
+ if (exposable) {
35
+ return true;
36
+ }
37
+ }
38
+
39
+ // Default
40
+ return true;
41
+ }
42
+
43
+ /**
44
+ * Excluding Elements from the Accessibility Tree
45
+ *
46
+ * @see https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion
47
+ *
48
+ * @param specs
49
+ * @param el
50
+ * @param version
51
+ */
52
+ function isExcluding(el: Element, specs: Readonly<MLMLSpec>, version: ARIAVersion): boolean {
53
+ /**
54
+ * The following elements are not exposed via the accessibility API and
55
+ * user agents MUST NOT include them in the accessibility tree:
56
+ * - Elements, including their descendent elements,
57
+ * that have host language semantics specifying
58
+ * that the element is not displayed, such as CSS display:none,
59
+ * visibility:hidden, or the HTML hidden attribute.
60
+ */
61
+ {
62
+ let currentEl: Element | null = el;
63
+ while (currentEl) {
64
+ if (hasDisplayNodeOrVisibilityHidden(currentEl) || currentEl.hasAttribute('hidden')) {
65
+ return true;
66
+ }
67
+ currentEl = currentEl.parentElement;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * - Elements with none or presentation as the first role in the role attribute.
73
+ * However, their exclusion is conditional. In addition,
74
+ * the element's descendants and text content are generally included.
75
+ * These exceptions and conditions are documented in the presentation (role)
76
+ * section.
77
+ */
78
+ if (isPresentational((el.getAttribute('role') || '').split(/\s+/)[0])) {
79
+ return true;
80
+ }
81
+
82
+ /**
83
+ * If not already excluded from the accessibility tree per the above rules,
84
+ * user agents SHOULD NOT include the following elements
85
+ * in the accessibility tree:
86
+ * - Elements, including their descendants, that have aria-hidden set to true.
87
+ * In other words, aria-hidden="true" on a parent overrides aria-hidden="false"
88
+ * on descendants.
89
+ */
90
+ {
91
+ let currentEl: Element | null = el;
92
+ while (currentEl) {
93
+ if (currentEl.getAttribute('aria-hidden') === 'true') {
94
+ return true;
95
+ }
96
+ currentEl = currentEl.parentElement;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * - Any descendants of elements that have the characteristic
102
+ * "Children Presentational: True" unless the descendant
103
+ * is not allowed to be presentational because it meets one of
104
+ * the conditions for exception described in
105
+ * Presentational Roles Conflict Resolution.
106
+ * However, the text content of any excluded descendants is included.
107
+ */
108
+ {
109
+ let currentEl = el.parentElement;
110
+ while (currentEl) {
111
+ const { role } = getComputedRole(specs, currentEl, version);
112
+ if (role?.childrenPresentational) {
113
+ return true;
114
+ }
115
+ currentEl = currentEl.parentElement;
116
+ }
117
+ }
118
+
119
+ return false;
120
+ }
121
+
122
+ /**
123
+ * Including Elements in the Accessibility Tree
124
+ *
125
+ * If not excluded from or marked as hidden in the accessibility tree
126
+ * per the rules above in Excluding Elements in the Accessibility Tree,
127
+ * user agents MUST provide an accessible object in the accessibility tree
128
+ * for DOM elements that meet any of the following criteria:
129
+ *
130
+ * @see https://www.w3.org/TR/wai-aria-1.2/#tree_inclusion
131
+ *
132
+ * @param specs
133
+ * @param el
134
+ * @param version
135
+ */
136
+ function isIncluding(el: Element, specs: Readonly<MLMLSpec>, version: ARIAVersion): boolean {
137
+ /**
138
+ * > **meet any** of the following criteria:
139
+ */
140
+ const results: boolean[] = [];
141
+
142
+ /**
143
+ * Elements that are not hidden and may fire an accessibility API event,
144
+ * including:
145
+ * - Elements that are currently focused, even if the element or one of
146
+ * its ancestor elements has its aria-hidden attribute set to true.
147
+ */
148
+ // 🚫 Can't detect the element is focused.
149
+ // results.push(true);
150
+
151
+ /**
152
+ * - Elements that are a valid target of an aria-activedescendant attribute.
153
+ */
154
+ // TODO: Compute aria-activedescendant.
155
+ // results.push(true);
156
+
157
+ /**
158
+ * Elements that have an explicit role or a global WAI-ARIA attribute and
159
+ * do not have aria-hidden set to true.
160
+ * (See Excluding Elements in the Accessibility Tree for
161
+ * additional guidance on aria-hidden.)
162
+ */
163
+ if (el.getAttribute('aria-hidden') !== 'true') {
164
+ const globalAria = ariaSpecs(specs, version).props.filter(prop => prop.isGlobal);
165
+ const { role } = getComputedRole(specs, el, version);
166
+ // Has an explicit role
167
+ if (role && !role.isImplicit) {
168
+ results.push(true);
169
+ }
170
+ // Has a global WAI-ARIA attribute
171
+ for (const attr of Array.from(el.attributes)) {
172
+ if (globalAria.some(aria => aria.name === attr.localName)) {
173
+ results.push(true);
174
+ break;
175
+ }
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Elements that are not hidden and have an ID that is referenced
181
+ * by another element via a WAI-ARIA property.
182
+ */
183
+ // TODO: Compute refering ID.
184
+ // results.push(true);
185
+
186
+ return results.includes(true);
187
+ }
188
+
189
+ function hasDisplayNodeOrVisibilityHidden(el: Element) {
190
+ const style = el.getAttribute('style');
191
+ if (!style) {
192
+ return false;
193
+ }
194
+ // TODO: Improve accuracy
195
+ return /display\s*:\s*none|visibility\s*:\s*hidden/gi.test(style);
196
+ }
@@ -0,0 +1,21 @@
1
+ import type { MLMLSpec } from '../types';
2
+
3
+ import { getSelectorsByContentModelCategory } from '../specs/get-selectors-by-content-model-category';
4
+
5
+ export function mayBeFocusable(el: Element, specs: Readonly<MLMLSpec>): boolean {
6
+ return [
7
+ /**
8
+ * Interactive content
9
+ *
10
+ * @see https://html.spec.whatwg.org/multipage/dom.html#interactive-content
11
+ */
12
+ ...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
+ }
package/src/index.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';
@@ -1,14 +1,14 @@
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
 
4
4
  const cache = new Map<Category, ReadonlyArray<string>>();
5
5
 
6
- export function contentModelCategoryToTagNames(contentModel: Category, specs: MLMLSpec): ReadonlyArray<string> {
6
+ export function contentModelCategoryToTagNames(contentModel: Category, def: MLMLSpec['def']): ReadonlyArray<string> {
7
7
  const cached = cache.get(contentModel);
8
8
  if (cached) {
9
9
  return cached;
10
10
  }
11
- const tags: string[] | undefined = specs.def['#contentModels'][contentModel];
11
+ const tags: string[] | undefined = def['#contentModels'][contentModel];
12
12
  const sortedTag = Object.freeze(tags && Array.isArray(tags) ? tags.sort() : []);
13
13
  cache.set(contentModel, sortedTag);
14
14
  return sortedTag;
@@ -21,9 +21,9 @@ export function getARIA(
21
21
  if (!conditions) {
22
22
  return aria;
23
23
  }
24
- const conds = Object.keys(conditions);
24
+ const conditionKeys = Object.keys(conditions);
25
25
  let { implicitRole, permittedRoles, implicitProperties, properties, namingProhibited } = aria;
26
- for (const cond of conds) {
26
+ for (const cond of conditionKeys) {
27
27
  if (!matches(cond)) {
28
28
  continue;
29
29
  }
@@ -54,7 +54,7 @@ function getVersionResolvedARIA(
54
54
  if (aria !== undefined) {
55
55
  return aria;
56
56
  }
57
- const spec = getSpecByTagName(specs, localName, namespace)?.aria;
57
+ const spec = getSpecByTagName(specs.specs, localName, namespace)?.aria;
58
58
  if (!spec) {
59
59
  cache.set(key, null);
60
60
  return null;
@@ -1,5 +1,5 @@
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
 
4
4
  export function getSelectorsByContentModelCategory(
5
5
  specs: Readonly<MLMLSpec>,
@@ -1,4 +1,4 @@
1
- import specs from '@markuplint/html-spec';
1
+ import { specs } from '@markuplint/html-spec';
2
2
 
3
3
  import { getSpecByTagName } from './get-spec-by-tag-name';
4
4
 
@@ -8,26 +8,26 @@ const divSpec = {
8
8
  condition: 'dl > div',
9
9
  contents: [
10
10
  {
11
- zeroOrMore: '#script-supporting',
11
+ zeroOrMore: ':model(script-supporting)',
12
12
  },
13
13
  {
14
14
  oneOrMore: 'dt',
15
15
  },
16
16
  {
17
- zeroOrMore: '#script-supporting',
17
+ zeroOrMore: ':model(script-supporting)',
18
18
  },
19
19
  {
20
20
  oneOrMore: 'dd',
21
21
  },
22
22
  {
23
- zeroOrMore: '#script-supporting',
23
+ zeroOrMore: ':model(script-supporting)',
24
24
  },
25
25
  ],
26
26
  },
27
27
  ],
28
28
  contents: [
29
29
  {
30
- zeroOrMore: '#flow',
30
+ oneOrMore: ':model(flow)',
31
31
  },
32
32
  ],
33
33
  };
@@ -43,11 +43,11 @@ test('svg:svg', () => {
43
43
  contents: [
44
44
  {
45
45
  zeroOrMore: [
46
- '#SVGAnimation',
47
- '#SVGDescriptive',
48
- '#SVGPaintServer',
49
- '#SVGShape',
50
- '#SVGStructural',
46
+ ':model(SVGAnimation)',
47
+ ':model(SVGDescriptive)',
48
+ ':model(SVGPaintServer)',
49
+ ':model(SVGShape)',
50
+ ':model(SVGStructural)',
51
51
  'svg|a',
52
52
  'svg|clipPath',
53
53
  'svg|filter',
@@ -1,16 +1,20 @@
1
- import type { ElementSpec, MLMLSpec } from '../types';
1
+ import type { ElementSpec } from '../types';
2
2
 
3
3
  import { resolveNamespace } from '../utils/resolve-namespace';
4
4
 
5
- const cache = new Map<string, ElementSpec | null>();
5
+ const cache = new Map<string, any>();
6
6
 
7
- export function getSpecByTagName(specs: Readonly<MLMLSpec>, localName: string, namespace: string | null) {
7
+ export function getSpecByTagName<K extends keyof ElementSpec = keyof ElementSpec>(
8
+ specs: Readonly<Pick<ElementSpec, 'name' | K>[]>,
9
+ localName: string,
10
+ namespace: string | null,
11
+ ) {
8
12
  const { localNameWithNS } = resolveNamespace(localName, namespace || undefined);
9
- let spec = cache.get(localNameWithNS);
13
+ let spec: Pick<ElementSpec, 'name' | K> | null | undefined = cache.get(localNameWithNS);
10
14
  if (spec !== undefined) {
11
15
  return spec;
12
16
  }
13
- spec = specs.specs.find(spec => spec.name === localNameWithNS) || null;
17
+ spec = specs.find(spec => spec.name === localNameWithNS) || null;
14
18
  cache.set(localNameWithNS, spec);
15
19
  return spec;
16
20
  }
@@ -0,0 +1,9 @@
1
+ import { isVoidElement } from './is-void-element';
2
+
3
+ /**
4
+ * @see https://html.spec.whatwg.org/multipage/dom.html#the-nothing-content-model
5
+ * @see https://html.spec.whatwg.org/multipage/indices.html#elements-3
6
+ */
7
+ export function isNothingContentModel(el: Element) {
8
+ return isVoidElement(el) || ['iframe', 'template'].includes(el.localName);
9
+ }
@@ -0,0 +1,55 @@
1
+ import type { MLMLSpec } from '../types';
2
+
3
+ /**
4
+ * Exposable content models and elements
5
+ *
6
+ * **WARNING**:
7
+ * This implementation is through the author's interpretation.
8
+ * Want a issue request.
9
+ * https://github.com/markuplint/markuplint/issues/new
10
+ *
11
+ * @see https://html.spec.whatwg.org/multipage/indices.html#elements-3
12
+ */
13
+ const exposableElementsThatAreNoBelongingAModel: string[] = [
14
+ 'body',
15
+ 'dd',
16
+ 'dt',
17
+ 'figcaption',
18
+ 'html',
19
+ 'legend',
20
+ 'li',
21
+ 'optgroup',
22
+ 'option',
23
+ 'rp',
24
+ 'rt',
25
+ 'summary',
26
+ 'tbody',
27
+ 'td',
28
+ 'tfoot',
29
+ 'th',
30
+ 'thead',
31
+ 'tr',
32
+ ];
33
+
34
+ export function isPalpableElement(
35
+ el: Element,
36
+ specs: Readonly<MLMLSpec>,
37
+ options?: {
38
+ extendsSvg?: boolean;
39
+ extendsExposableElements?: boolean;
40
+ },
41
+ ) {
42
+ const conditions = [specs.def['#contentModels']['#palpable']?.join(',') || ''];
43
+
44
+ if (options?.extendsSvg !== false /* default true */) {
45
+ conditions.push(specs.def['#contentModels']['#SVGRenderable']?.join(',') || '');
46
+ }
47
+
48
+ if (options?.extendsExposableElements /* default false */) {
49
+ conditions.push(exposableElementsThatAreNoBelongingAModel.join(','));
50
+ }
51
+
52
+ return conditions.some(condition => {
53
+ return el.matches(condition);
54
+ });
55
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @see https://html.spec.whatwg.org/multipage/syntax.html#void-elements
3
+ */
4
+ const voidElements = [
5
+ 'area',
6
+ 'base',
7
+ 'br',
8
+ 'col',
9
+ 'embed',
10
+ 'hr',
11
+ 'img',
12
+ 'input',
13
+ 'link',
14
+ 'meta',
15
+ 'source',
16
+ 'track',
17
+ 'wbr',
18
+ ];
19
+
20
+ export function isVoidElement(el: Element) {
21
+ return voidElements.includes(el.localName);
22
+ }
@@ -5,7 +5,7 @@ import { mergeArray } from '../utils/merge-array';
5
5
  /**
6
6
  * Merging HTML-spec schema and extended spec schemas
7
7
  *
8
- * Ex: `@markuplint/html-spec` + `{ specs: ["@markuplint/vue-spec"] }` in cofigure files.
8
+ * Ex: `@markuplint/html-spec` + `{ specs: { "\\.vue$": "@markuplint/vue-spec" } }` in configure files.
9
9
  *
10
10
  * @param schemas `MLDocument.schemas`
11
11
  */
package/src/types/aria.ts CHANGED
@@ -38,6 +38,9 @@ export type PermittedARIAProperties =
38
38
  * Set true if the spec says "and any aria-* attributes applicable to the allowed roles."
39
39
  */
40
40
  role?: true | string | [string, ...string[]];
41
+ /**
42
+ * @minItems 1
43
+ */
41
44
  only?: [
42
45
  (
43
46
  | string
@@ -54,7 +57,10 @@ export type PermittedARIAProperties =
54
57
  }
55
58
  )[],
56
59
  ];
57
- whithout?: [
60
+ /**
61
+ * @minItems 1
62
+ */
63
+ without?: [
58
64
  {
59
65
  type: 'not-recommended' | 'should-not' | 'must-not';
60
66
  name: string;