@markuplint/ml-spec 3.0.0-canary.5 → 3.0.0-dev.176
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/README.md +1 -1
- package/lib/constant/aria-version.d.ts +2 -0
- package/lib/constant/aria-version.js +5 -0
- package/lib/dom-traverse/accname-computation.js +3 -1
- package/lib/dom-traverse/get-attr-specs.d.ts +1 -1
- package/lib/dom-traverse/get-attr-specs.js +3 -1
- package/lib/dom-traverse/get-computed-aria-props.d.ts +6 -6
- package/lib/dom-traverse/get-computed-aria-props.js +4 -2
- package/lib/dom-traverse/get-computed-role.d.ts +6 -1
- package/lib/dom-traverse/get-computed-role.js +41 -10
- package/lib/dom-traverse/get-content-model.d.ts +28 -2
- package/lib/dom-traverse/get-content-model.js +4 -2
- package/lib/dom-traverse/get-explicit-role.js +9 -4
- package/lib/dom-traverse/get-implicit-role.d.ts +6 -2
- package/lib/dom-traverse/get-implicit-role.js +6 -2
- package/lib/dom-traverse/get-non-presentational-ancestor.d.ts +10 -4
- package/lib/dom-traverse/get-non-presentational-ancestor.js +10 -2
- package/lib/dom-traverse/get-permitted-roles.d.ts +7 -3
- package/lib/dom-traverse/get-permitted-roles.js +3 -1
- package/lib/dom-traverse/get-spec.d.ts +4 -1
- package/lib/dom-traverse/get-spec.js +3 -1
- package/lib/dom-traverse/has-required-owned-elements.d.ts +8 -2
- package/lib/dom-traverse/has-required-owned-elements.js +14 -8
- package/lib/dom-traverse/is-exposed.d.ts +1 -1
- package/lib/dom-traverse/is-exposed.js +43 -7
- package/lib/dom-traverse/matches-context-role.d.ts +7 -0
- package/lib/dom-traverse/matches-context-role.js +27 -0
- package/lib/dom-traverse/may-be-focusable.d.ts +1 -1
- package/lib/dom-traverse/may-be-focusable.js +3 -1
- package/lib/index.d.ts +4 -0
- package/lib/index.js +4 -0
- package/lib/specs/aria-specs.d.ts +7 -4
- package/lib/specs/content-model-category-to-tag-names.d.ts +4 -1
- package/lib/specs/get-aria.d.ts +8 -1
- package/lib/specs/get-aria.js +8 -2
- package/lib/specs/get-attr-specs.d.ts +6 -2
- package/lib/specs/get-attr-specs.js +2 -14
- package/lib/specs/get-implicit-role.d.ts +7 -1
- package/lib/specs/get-permitted-roles.d.ts +9 -3
- package/lib/specs/get-permitted-roles.js +19 -15
- package/lib/specs/get-role-spec.d.ts +10 -3
- package/lib/specs/get-role-spec.js +2 -2
- package/lib/specs/get-selectors-by-content-model-category.d.ts +1 -1
- package/lib/specs/get-selectors-by-content-model-category.js +1 -1
- package/lib/specs/get-spec-by-tag-name.d.ts +5 -1
- package/lib/specs/get-spec-by-tag-name.js +3 -2
- package/lib/specs/is-nothing-content-model.d.ts +5 -0
- package/lib/specs/is-nothing-content-model.js +14 -0
- package/lib/specs/is-palpable-elements.d.ts +8 -4
- package/lib/specs/is-palpable-elements.js +6 -4
- package/lib/specs/is-void-element.js +3 -1
- package/lib/specs/resolve-version.d.ts +5 -1
- package/lib/specs/resolve-version.js +1 -1
- package/lib/specs/schema-to-spec.d.ts +4 -4
- package/lib/specs/schema-to-spec.js +29 -18
- package/lib/types/aria.d.ts +149 -114
- package/lib/types/attributes.d.ts +1490 -111
- package/lib/types/index.d.ts +178 -149
- package/lib/types/permitted-structures.d.ts +74 -31
- package/lib/utils/merge-array.d.ts +9 -4
- package/lib/utils/resolve-namespace.d.ts +4 -4
- package/lib/utils/resolve-namespace.js +3 -2
- package/lib/utils/validateAriaVersion.d.ts +2 -0
- package/lib/utils/validateAriaVersion.js +8 -0
- package/package.json +13 -8
- package/{src/dom-traverse/accname-computation.spec.ts → test/dom-traverse/accname-computation.spec.js} +4 -4
- package/test/dom-traverse/get-computed-aria-props.spec.js +758 -0
- package/test/dom-traverse/get-computed-role.spec.js +294 -0
- package/test/dom-traverse/get-implicit-role.spec.js +40 -0
- package/{src/dom-traverse/has-required-owned-elements.spec.ts → test/dom-traverse/has-required-owned-elements.spec.js} +7 -9
- package/{src/dom-traverse/is-exposed.spec.ts → test/dom-traverse/is-exposed.spec.js} +29 -9
- package/test/dom-traverse/matches-context-role.spec.js +60 -0
- package/{src/specs/get-attr-specs.spec.ts → test/specs/get-attr-specs.spec.js} +2 -8
- package/{src/specs/get-implicit-role.spec.ts → test/specs/get-implicit-role.spec.js} +9 -11
- package/{src/specs/get-permitted-roles.spec.ts → test/specs/get-permitted-roles.spec.js} +8 -10
- package/{src/specs/get-role-spec.spec.ts → test/specs/get-role-spec.spec.js} +4 -4
- package/{src/specs/get-spec-by-tag-name.spec.ts → test/specs/get-spec-by-tag-name.spec.js} +2 -2
- package/test/specs/resolve-version.spec.js +34 -0
- package/test/specs/schema-to-spec.spec.js +61 -0
- package/{src/utils/resolve-namespace.spec.ts → test/utils/resolve-namespace.spec.js} +1 -1
- package/lib/types/permitted-structres.d.ts +0 -58
- package/lib/types/permitted-structres.js +0 -8
- package/schemas/aria.schema.json +0 -258
- package/schemas/attributes.schema.json +0 -204
- package/schemas/content-models.schema.json +0 -199
- package/schemas/element.schema.json +0 -11
- package/schemas/global-attributes.schema.json +0 -761
- package/src/dom-traverse/accname-computation.ts +0 -5
- package/src/dom-traverse/get-attr-specs.ts +0 -8
- package/src/dom-traverse/get-computed-aria-props.spec.ts +0 -368
- package/src/dom-traverse/get-computed-aria-props.ts +0 -130
- package/src/dom-traverse/get-computed-role.spec.ts +0 -134
- package/src/dom-traverse/get-computed-role.ts +0 -181
- package/src/dom-traverse/get-content-model.ts +0 -32
- package/src/dom-traverse/get-explicit-role.ts +0 -112
- package/src/dom-traverse/get-implicit-role.ts +0 -36
- package/src/dom-traverse/get-non-presentational-ancestor.ts +0 -20
- package/src/dom-traverse/get-permitted-roles.ts +0 -7
- package/src/dom-traverse/get-spec.ts +0 -7
- package/src/dom-traverse/has-required-owned-elements.ts +0 -85
- package/src/dom-traverse/is-exposed.ts +0 -196
- package/src/dom-traverse/may-be-focusable.ts +0 -21
- package/src/index.ts +0 -23
- package/src/specs/aria-specs.ts +0 -6
- package/src/specs/content-model-category-to-tag-names.ts +0 -15
- package/src/specs/get-aria.ts +0 -85
- package/src/specs/get-attr-specs.ts +0 -116
- package/src/specs/get-implicit-role.ts +0 -17
- package/src/specs/get-permitted-roles.ts +0 -87
- package/src/specs/get-role-spec.ts +0 -72
- package/src/specs/get-selectors-by-content-model-category.ts +0 -10
- package/src/specs/get-spec-by-tag-name.ts +0 -20
- package/src/specs/is-palpable-elements.ts +0 -55
- package/src/specs/is-presentational.ts +0 -6
- package/src/specs/is-void-element.ts +0 -22
- package/src/specs/resolve-version.ts +0 -20
- package/src/specs/schema-to-spec.spec.ts +0 -39
- package/src/specs/schema-to-spec.ts +0 -103
- package/src/types/aria.ts +0 -153
- package/src/types/attributes.ts +0 -1473
- package/src/types/index.ts +0 -255
- package/src/types/permitted-structres.ts +0 -100
- package/src/types/permitted-structures.ts +0 -96
- package/src/utils/get-ns.ts +0 -18
- package/src/utils/merge-array.ts +0 -35
- package/src/utils/resolve-namespace.ts +0 -40
- package/tsconfig.test.json +0 -3
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import type { ARIAVersion, ComputedRole, MLMLSpec } from '../types';
|
|
2
|
-
|
|
3
|
-
import { ariaSpecs } from '../specs/aria-specs';
|
|
4
|
-
import { isPresentational } from '../specs/is-presentational';
|
|
5
|
-
|
|
6
|
-
import { getAttrSpecs } from './get-attr-specs';
|
|
7
|
-
import { getExplicitRole } from './get-explicit-role';
|
|
8
|
-
import { getImplicitRole } from './get-implicit-role';
|
|
9
|
-
import { getNonPresentationalAncestor } from './get-non-presentational-ancestor';
|
|
10
|
-
import { isRequiredOwnedElement } from './has-required-owned-elements';
|
|
11
|
-
import { mayBeFocusable } from './may-be-focusable';
|
|
12
|
-
|
|
13
|
-
export function getComputedRole(specs: Readonly<MLMLSpec>, el: Element, version: ARIAVersion): ComputedRole {
|
|
14
|
-
const implicitRole = getImplicitRole(specs, el, version);
|
|
15
|
-
const explicitRole = getExplicitRole(specs, el, version);
|
|
16
|
-
const computedRole = explicitRole.role ? explicitRole : implicitRole;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Presentational Roles Conflict Resolution
|
|
20
|
-
*
|
|
21
|
-
* @see https://www.w3.org/TR/wai-aria/#conflict_resolution_presentation_none
|
|
22
|
-
* @see https://w3c.github.io/aria/#conflict_resolution_presentation_none
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* > If the action of exposing the explicit role
|
|
27
|
-
* > causes the accessibility tree to be malformed,
|
|
28
|
-
* > the expected results are undefined.
|
|
29
|
-
*
|
|
30
|
-
* Determines whether the context is valid.
|
|
31
|
-
* ⚠ THE SPECIFICATION HAS AN ISSUE
|
|
32
|
-
* that has not decided whether the context is a parent or an ancestor.
|
|
33
|
-
*
|
|
34
|
-
* @see https://github.com/w3c/aria/issues/1033
|
|
35
|
-
* @see https://github.com/w3c/aria/issues/748
|
|
36
|
-
* @see https://github.com/w3c/aria/pull/1162
|
|
37
|
-
* @see https://github.com/w3c/aria/pull/1213
|
|
38
|
-
*
|
|
39
|
-
* Currently, this process interprets that as A PARENT
|
|
40
|
-
* because it wants to be near to HTML semantics.
|
|
41
|
-
* However, the presentational role behaves transparently
|
|
42
|
-
* according to the sample code in WAI-ARIA specification.
|
|
43
|
-
*/
|
|
44
|
-
if (computedRole.role?.requiredContextRole.length) {
|
|
45
|
-
const nonPresentationalAncestor = getNonPresentationalAncestor(el, specs, version);
|
|
46
|
-
if (!nonPresentationalAncestor.role) {
|
|
47
|
-
return {
|
|
48
|
-
el,
|
|
49
|
-
role: null,
|
|
50
|
-
errorType: 'NO_OWNER',
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
if (!computedRole.role?.requiredContextRole.includes(nonPresentationalAncestor.role.name)) {
|
|
54
|
-
return {
|
|
55
|
-
el,
|
|
56
|
-
role: null,
|
|
57
|
-
errorType: 'INVALID_REQUIRED_CONTEXT_ROLE',
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (computedRole.role && !isPresentational(computedRole.role.name)) {
|
|
63
|
-
return computedRole;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* > If an element is focusable,
|
|
68
|
-
* > user agents MUST ignore the presentation/none role
|
|
69
|
-
* > and expose the element with its implicit role,
|
|
70
|
-
* > in order to ensure that the element is operable.
|
|
71
|
-
*
|
|
72
|
-
* With the issue: What does focusable or otherwise interactive mean](https://github.com/w3c/aria/issues/1192)
|
|
73
|
-
*
|
|
74
|
-
* - Focus: https://html.spec.whatwg.org/multipage/interaction.html#focus
|
|
75
|
-
* - Interactive content: https://html.spec.whatwg.org/multipage/dom.html#interactive-content
|
|
76
|
-
*/
|
|
77
|
-
if (
|
|
78
|
-
/**
|
|
79
|
-
* If interactive element
|
|
80
|
-
*
|
|
81
|
-
* 1. It may be focusable
|
|
82
|
-
*
|
|
83
|
-
* THIS CONDITION IS ALMOST MEANINGLESS.
|
|
84
|
-
* Because it has already been determined that
|
|
85
|
-
* the interactive elements can not specify the presentational role
|
|
86
|
-
* in the previous processing that computes the permitted role (`getExplicitRole`).
|
|
87
|
-
*/
|
|
88
|
-
mayBeFocusable(el, specs) &&
|
|
89
|
-
/**
|
|
90
|
-
* 2. No disabled
|
|
91
|
-
*/
|
|
92
|
-
!someAncestors(el, p => isEnabledAttr(p, specs, 'disabled')) &&
|
|
93
|
-
/**
|
|
94
|
-
* 3. No inert
|
|
95
|
-
*/
|
|
96
|
-
!someAncestors(el, p => isEnabledAttr(p, specs, 'inert')) &&
|
|
97
|
-
/**
|
|
98
|
-
* 4. No hidden
|
|
99
|
-
*/
|
|
100
|
-
!someAncestors(el, p => isEnabledAttr(p, specs, 'hidden'))
|
|
101
|
-
) {
|
|
102
|
-
return {
|
|
103
|
-
...implicitRole,
|
|
104
|
-
errorType: 'INTERACTIVE_ELEMENT_MUST_NOT_BE_PRESENTATIONAL',
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* > If a required owned element has an explicit non-presentational role,
|
|
110
|
-
* > user agents MUST ignore an inherited presentational role
|
|
111
|
-
* > and expose the element with its explicit role.
|
|
112
|
-
*
|
|
113
|
-
* In other words,
|
|
114
|
-
* if the element has `role=presentation` and
|
|
115
|
-
* the role of the parent element has a required owned element,
|
|
116
|
-
* `role=presentation` is to be ignored absolutely.
|
|
117
|
-
*/
|
|
118
|
-
if (explicitRole.role) {
|
|
119
|
-
const nonPresentationalAncestor = getNonPresentationalAncestor(el, specs, version);
|
|
120
|
-
if (nonPresentationalAncestor.role?.requiredOwnedElements.length) {
|
|
121
|
-
if (
|
|
122
|
-
nonPresentationalAncestor.role.requiredOwnedElements.some(expected => {
|
|
123
|
-
// const ancestor = nonPresentationalAncestor.el;
|
|
124
|
-
// const ancestorImplicitRole = getImplicitRole(specs, ancestor, version);
|
|
125
|
-
// console.log({ nonPresentationalAncestor, ancestorImplicitRole });
|
|
126
|
-
return isRequiredOwnedElement(implicitRole, expected, specs, version);
|
|
127
|
-
})
|
|
128
|
-
) {
|
|
129
|
-
return {
|
|
130
|
-
...implicitRole,
|
|
131
|
-
errorType: 'REQUIRED_OWNED_ELEMENT_MUST_NOT_BE_PRESENTATIONAL',
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* > If an element has global WAI-ARIA states or properties,
|
|
139
|
-
* > user agents MUST ignore the presentation role
|
|
140
|
-
* > and instead expose the element's implicit role.
|
|
141
|
-
* > However, if an element has only non-global,
|
|
142
|
-
* > role-specific WAI-ARIA states or properties,
|
|
143
|
-
* > the element MUST NOT be exposed
|
|
144
|
-
* > unless the presentational role is inherited
|
|
145
|
-
* > and an explicit non-presentational role is applied.
|
|
146
|
-
*/
|
|
147
|
-
const { props } = ariaSpecs(specs, version);
|
|
148
|
-
for (const attr of Array.from(el.attributes)) {
|
|
149
|
-
if (props.find(p => p.name === attr.name)?.isGlobal) {
|
|
150
|
-
return {
|
|
151
|
-
...implicitRole,
|
|
152
|
-
errorType: 'GLOBAL_PROP_MUST_NOT_BE_PRESENTATIONAL',
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return computedRole;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* The attribute is available in its owner element,
|
|
162
|
-
* it has the attribute.
|
|
163
|
-
*/
|
|
164
|
-
function isEnabledAttr(el: Element, specs: MLMLSpec, attrName: string) {
|
|
165
|
-
const attrs = getAttrSpecs(el, specs);
|
|
166
|
-
const attr = attrs?.find(attr => attr.name === attrName);
|
|
167
|
-
return !!attr && el.hasAttribute(attrName);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Determines whether some ancestors match the condition that the specified callback function.
|
|
172
|
-
*/
|
|
173
|
-
function someAncestors(el: Element, predicate: (ancestor: Element) => boolean) {
|
|
174
|
-
const list: Element[] = [];
|
|
175
|
-
let current: Element | null = el;
|
|
176
|
-
while (current) {
|
|
177
|
-
list.push(current);
|
|
178
|
-
current = current.parentElement;
|
|
179
|
-
}
|
|
180
|
-
return list.some(predicate);
|
|
181
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { ElementSpec } from '../types';
|
|
2
|
-
import type { PermittedContentPattern } from '../types/permitted-structures';
|
|
3
|
-
|
|
4
|
-
import { getSpec } from './get-spec';
|
|
5
|
-
|
|
6
|
-
type Specs = Pick<ElementSpec, 'name' | 'contentModel'>[];
|
|
7
|
-
|
|
8
|
-
const cachesBySpecs = new Map<Specs, Map<Element, PermittedContentPattern[] | boolean | null>>();
|
|
9
|
-
export function getContentModel(el: Element, specs: Pick<ElementSpec, 'name' | 'contentModel'>[]) {
|
|
10
|
-
const cacheByEl = cachesBySpecs.get(specs) ?? new Map<Element, PermittedContentPattern[] | boolean>();
|
|
11
|
-
const cached = cacheByEl.get(el);
|
|
12
|
-
if (cached) {
|
|
13
|
-
return cached;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const spec = getSpec<'contentModel'>(el, specs);
|
|
17
|
-
if (!spec) {
|
|
18
|
-
cacheByEl.set(el, null);
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const conditions = spec.contentModel.conditional ?? [];
|
|
23
|
-
for (const cond of conditions) {
|
|
24
|
-
if (el.matches(cond.condition)) {
|
|
25
|
-
cacheByEl.set(el, cond.contents);
|
|
26
|
-
return cond.contents;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
cacheByEl.set(el, spec.contentModel.contents);
|
|
31
|
-
return spec.contentModel.contents;
|
|
32
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import type { ARIAVersion, ComputedRole, MLMLSpec, RoleComputationError } from '../types';
|
|
2
|
-
|
|
3
|
-
import { getRoleSpec } from '../specs/get-role-spec';
|
|
4
|
-
import { resolveNamespace } from '../utils/resolve-namespace';
|
|
5
|
-
|
|
6
|
-
import { getPermittedRoles } from './get-permitted-roles';
|
|
7
|
-
|
|
8
|
-
export function getExplicitRole(specs: MLMLSpec, el: Element, version: ARIAVersion): ComputedRole {
|
|
9
|
-
const roleValue = el.getAttribute('role');
|
|
10
|
-
const roleNames = roleValue?.toLowerCase().trim().split(/\s+/g) || [];
|
|
11
|
-
const permittedRoles = getPermittedRoles(el, version, specs);
|
|
12
|
-
const { namespaceURI } = resolveNamespace(el.localName, el.namespaceURI);
|
|
13
|
-
|
|
14
|
-
let error: RoleComputationError = 'NO_EXPLICIT';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Resolve from values and **Handling Author Errors**
|
|
18
|
-
*
|
|
19
|
-
* @see https://w3c.github.io/aria/#document-handling_author-errors_roles
|
|
20
|
-
*/
|
|
21
|
-
for (const roleName of roleNames) {
|
|
22
|
-
const spec = getRoleSpec(specs, roleName, namespaceURI, version);
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* If `spec` is null, the role DOES NOT EXIST.
|
|
26
|
-
*
|
|
27
|
-
* > If the role attribute contains no tokens matching the name
|
|
28
|
-
* > of a non-abstract WAI-ARIA role,
|
|
29
|
-
* > the user agent MUST treat the element as
|
|
30
|
-
* > if no role had been provided. For example,
|
|
31
|
-
* > <table role="foo"> should be exposed
|
|
32
|
-
* > in the same way as <table> and <input type="text" role="structure">
|
|
33
|
-
* > in the same way as <input type="text">.
|
|
34
|
-
*/
|
|
35
|
-
if (!spec) {
|
|
36
|
-
error = 'ROLE_NO_EXISTS';
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* > As stated in the Definition of Roles section,
|
|
42
|
-
* > it is considered an authoring error to use abstract roles in content.
|
|
43
|
-
* > User agents MUST NOT map abstract roles
|
|
44
|
-
* > via the standard role mechanism of the accessibility API.
|
|
45
|
-
*/
|
|
46
|
-
if (spec.isAbstract) {
|
|
47
|
-
error = 'ABSTRACT';
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Whether the role is the permitted role according to ARIA in HTML.
|
|
53
|
-
*/
|
|
54
|
-
if (!permittedRoles.some(r => r.name === roleName)) {
|
|
55
|
-
error = 'NO_PERMITTED';
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* > Certain landmark roles require names from authors.
|
|
61
|
-
* > In situations where an author has not specified names for these landmarks,
|
|
62
|
-
* > it is considered an authoring error.
|
|
63
|
-
* > The user agent MUST treat such elements as if no role had been provided.
|
|
64
|
-
* > If a valid fallback role had been specified,
|
|
65
|
-
* > or if the element had an implicit ARIA role,
|
|
66
|
-
* > then user agents would continue to expose that role, instead.
|
|
67
|
-
*/
|
|
68
|
-
if (isLandmarkRole(spec) && !isValidLandmarkRole(el, spec)) {
|
|
69
|
-
error = 'INVALID_LANDMARK';
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Otherwise
|
|
75
|
-
*/
|
|
76
|
-
return {
|
|
77
|
-
el,
|
|
78
|
-
role: {
|
|
79
|
-
...spec,
|
|
80
|
-
isImplicit: false,
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
el,
|
|
87
|
-
role: null,
|
|
88
|
-
errorType: error,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function isLandmarkRole(role: NonNullable<ReturnType<typeof getRoleSpec>>) {
|
|
93
|
-
return role?.superClassRoles.some(su => su.name === 'landmark');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function isValidLandmarkRole(el: Element, role: NonNullable<ReturnType<typeof getRoleSpec>>) {
|
|
97
|
-
if (!role.accessibleNameRequired) {
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
if (role.accessibleNameFromAuthor) {
|
|
101
|
-
if (el.getAttribute('aria-label')) {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
const id = el.getAttribute('aria-labelledby');
|
|
105
|
-
if (id && el.ownerDocument.getElementById(id)) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
// The landmark role does not require names from the content
|
|
110
|
-
// if (role.accessibleNameFromContent) {}
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { ARIAVersion, ComputedRole, MLMLSpec } from '../types';
|
|
2
|
-
|
|
3
|
-
import { getImplicitRole as _getImplicitRole } from '../specs/get-implicit-role';
|
|
4
|
-
import { getRoleSpec } from '../specs/get-role-spec';
|
|
5
|
-
import { resolveNamespace } from '../utils/resolve-namespace';
|
|
6
|
-
|
|
7
|
-
export function getImplicitRole(specs: Readonly<MLMLSpec>, el: Element, version: ARIAVersion): ComputedRole {
|
|
8
|
-
const implicitRole = getImplicitRoleName(el, version, specs);
|
|
9
|
-
if (implicitRole === false) {
|
|
10
|
-
// No Corresponding Role
|
|
11
|
-
return {
|
|
12
|
-
el,
|
|
13
|
-
role: null,
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
const { namespaceURI } = resolveNamespace(el.localName, el.namespaceURI);
|
|
17
|
-
const spec = getRoleSpec(specs, implicitRole, namespaceURI, version);
|
|
18
|
-
if (!spec) {
|
|
19
|
-
return {
|
|
20
|
-
el,
|
|
21
|
-
role: null,
|
|
22
|
-
errorType: 'IMPLICIT_ROLE_NAMESPACE_ERROR',
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
return {
|
|
26
|
-
el,
|
|
27
|
-
role: {
|
|
28
|
-
...spec,
|
|
29
|
-
isImplicit: true,
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function getImplicitRoleName(el: Element, version: ARIAVersion, specs: Readonly<MLMLSpec>) {
|
|
35
|
-
return _getImplicitRole(specs, el.localName, el.namespaceURI, version, el.matches.bind(el));
|
|
36
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { ARIAVersion, MLMLSpec } from '../types';
|
|
2
|
-
|
|
3
|
-
import { isPresentational } from '../specs/is-presentational';
|
|
4
|
-
|
|
5
|
-
import { getComputedRole } from './get-computed-role';
|
|
6
|
-
|
|
7
|
-
export function getNonPresentationalAncestor(el: Element, specs: MLMLSpec, version: ARIAVersion) {
|
|
8
|
-
let ancestor: Element | null = el.parentElement;
|
|
9
|
-
while (ancestor) {
|
|
10
|
-
const ancestorRole = getComputedRole(specs, ancestor, version);
|
|
11
|
-
if (!isPresentational(ancestorRole.role?.name)) {
|
|
12
|
-
return ancestorRole;
|
|
13
|
-
}
|
|
14
|
-
ancestor = ancestor.parentElement;
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
el: ancestor,
|
|
18
|
-
role: null,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { ARIAVersion, MLMLSpec } from '../types';
|
|
2
|
-
|
|
3
|
-
import { getPermittedRoles as _getPermittedRoles } from '../specs/get-permitted-roles';
|
|
4
|
-
|
|
5
|
-
export function getPermittedRoles(el: Element, version: ARIAVersion, specs: Readonly<MLMLSpec>) {
|
|
6
|
-
return _getPermittedRoles(specs, el.localName, el.namespaceURI, version, el.matches.bind(el));
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { ElementSpec } from '../types';
|
|
2
|
-
|
|
3
|
-
import { getSpecByTagName } from '../specs/get-spec-by-tag-name';
|
|
4
|
-
|
|
5
|
-
export function getSpec<K extends keyof ElementSpec>(el: Element, specs: Pick<ElementSpec, 'name' | K>[]) {
|
|
6
|
-
return getSpecByTagName(specs, el.localName, el.namespaceURI);
|
|
7
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import type { ARIAVersion, ComputedRole, MLMLSpec } from '../types';
|
|
2
|
-
|
|
3
|
-
import { isPresentational } from '../specs/is-presentational';
|
|
4
|
-
|
|
5
|
-
import { getComputedRole } from './get-computed-role';
|
|
6
|
-
import { getExplicitRole } from './get-explicit-role';
|
|
7
|
-
import { getImplicitRole } from './get-implicit-role';
|
|
8
|
-
|
|
9
|
-
export function hasRequiredOwnedElement(el: Element, specs: Readonly<MLMLSpec>, version: ARIAVersion): boolean {
|
|
10
|
-
/**
|
|
11
|
-
* The element has aria-owns which means it may have owned elements.
|
|
12
|
-
*
|
|
13
|
-
* THIS CONDITION IS PARTIAL SUPPORT.
|
|
14
|
-
*/
|
|
15
|
-
if (el.hasAttribute('aria-owns')) {
|
|
16
|
-
// FIXME
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Otherwise, traverses descendants to find owned elements.
|
|
22
|
-
*/
|
|
23
|
-
const computed = getComputedRole(specs, el, version);
|
|
24
|
-
if (!computed.role || computed.role.requiredOwnedElements.length === 0) {
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
for (const expectRole of computed.role.requiredOwnedElements) {
|
|
28
|
-
for (const owned of getClosestNonPresentationalDescendants(el, specs, version)) {
|
|
29
|
-
if (isRequiredOwnedElement(owned, expectRole, specs, version)) {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function isRequiredOwnedElement(owned: ComputedRole, query: string, specs: MLMLSpec, version: ARIAVersion) {
|
|
39
|
-
const [baseRole, owningRole] = query.split(' > ') as [string, string | undefined];
|
|
40
|
-
if (owned.role?.name !== baseRole) {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (!owningRole) {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
for (const owning of getClosestNonPresentationalDescendants(owned.el, specs, version)) {
|
|
49
|
-
if (owning.role?.name === owningRole) {
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Gets the list of closest non-presentational descendants.
|
|
59
|
-
* ⚠ THE SPECIFICATION HAS AN ISSUE
|
|
60
|
-
* that has not decided whether the owned element is a child or a descendant.
|
|
61
|
-
*
|
|
62
|
-
* @see https://github.com/w3c/aria/issues/1033
|
|
63
|
-
* @see https://github.com/w3c/aria/issues/748
|
|
64
|
-
* @see https://github.com/w3c/aria/pull/1162
|
|
65
|
-
* @see https://github.com/w3c/aria/pull/1213
|
|
66
|
-
*
|
|
67
|
-
* Currently, this process interprets that as A CHILD
|
|
68
|
-
* because it wants to be near to HTML semantics.
|
|
69
|
-
* However, the presentational role behaves transparently
|
|
70
|
-
* according to the sample code in WAI-ARIA specification.
|
|
71
|
-
*/
|
|
72
|
-
function getClosestNonPresentationalDescendants(el: Element, specs: MLMLSpec, version: ARIAVersion): ComputedRole[] {
|
|
73
|
-
const owned: ComputedRole[] = [];
|
|
74
|
-
for (const child of Array.from(el.children)) {
|
|
75
|
-
const implicitRole = getImplicitRole(specs, child, version);
|
|
76
|
-
const explicitRole = getExplicitRole(specs, child, version);
|
|
77
|
-
const computed = explicitRole.role ? explicitRole : implicitRole;
|
|
78
|
-
if (isPresentational(computed.role?.name)) {
|
|
79
|
-
owned.push(...getClosestNonPresentationalDescendants(child, specs, version));
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
owned.push({ ...computed, el: child });
|
|
83
|
-
}
|
|
84
|
-
return owned;
|
|
85
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
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
|
-
}
|