-
Notifications
You must be signed in to change notification settings - Fork 105
Expand file tree
/
Copy pathcombobox_focusable_elements.ts
More file actions
102 lines (93 loc) · 4.55 KB
/
combobox_focusable_elements.ts
File metadata and controls
102 lines (93 loc) · 4.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/******************************************************************************
Copyright:: 2022- IBM, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*****************************************************************************/
import { Rule, RuleResult, RuleFail, RuleContext, RulePass, RuleContextHierarchy } from "../api/IRule";
import { eRulePolicy, eToolkitLevel } from "../api/IRule";
import { DOMWalker } from "../../v2/dom/DOMWalker";
import { AriaUtil } from "../util/AriaUtil";
import { CommonUtil } from "../util/CommonUtil";
import { CacheUtil } from "../util/CacheUtil";
import { VisUtil } from "../util/VisUtil";
export const combobox_focusable_elements: Rule = {
id: "combobox_focusable_elements",
context: "aria:combobox",
dependencies: ["combobox_popup_reference"],
help: {
"en-US": {
"Pass": "combobox_focusable_elements.html",
"Fail_not_tabbable": "combobox_focusable_elements.html",
"Fail_tabbable_child": "combobox_focusable_elements.html",
"group": "combobox_focusable_elements.html"
}
},
messages: {
"en-US": {
"Pass": "DOM focus is allowed only on the combobox element as required",
"Fail_not_tabbable": "The combobox element does not allow DOM focus as required",
"Fail_tabbable_child": "The popup of the combobox has DOM focus or has 'aria-activedescendant' defined, which is not allowed",
"group": "Tabbable focus for the combobox must be allowed only on the text input, except when using a dialog popup"
}
},
rulesets: [{
"id": ["IBM_Accessibility", "IBM_Accessibility_next", "WCAG_2_1", "WCAG_2_0", "WCAG_2_2"],
"num": ["4.1.2"],
"level": eRulePolicy.VIOLATION,
"toolkitLevel": eToolkitLevel.LEVEL_ONE
}],
act: [],
run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => {
const ruleContext = context["dom"].node as Element;
let cache = CacheUtil.getCache(ruleContext.ownerDocument, "combobox", {});
if (!cache) return null;
let cachedElem = cache[context["dom"].rolePath];
if (!cachedElem) return null;
const { popupElement, expanded } = cachedElem;
// If this isn't defined, the combobox is probably collapsed. A reference error is
// detected in combobox_popup_reference
if (!popupElement) return null;
const popupRole = AriaUtil.getRoles(popupElement, true)[0];
let retVal = []
if (!CommonUtil.isTabbable(ruleContext)) {
retVal.push(RuleFail("Fail_not_tabbable"));
}
// Only makes sense to check the popup when expanded
// this does not apply to dialogs, return pass since the main element was focusable above
if (expanded === false || popupRole === "dialog") {
return RulePass("Pass");
}
let passed = true;
// examine the children
if (popupElement && VisUtil.isNodeVisible(popupElement)) {
// if popupElement itself has "aria-activedescendant"
passed = !CommonUtil.isTabbable(popupElement) && !AriaUtil.getAriaAttribute(popupElement, "aria-activedescendant");;
// if any child of popupElement has "aria-autocomplete"
if (passed && popupElement.children && popupElement.children.length > 0) {
let nw = new DOMWalker(popupElement);
while (passed && nw.nextNode()) {
if (nw.node.nodeType === 1 && VisUtil.isNodeVisible(nw.node)) {
passed = !CommonUtil.isTabbable(nw.node) &&
!AriaUtil.getAriaAttribute(nw.node, "aria-activedescendant");
if (nw.bEndTag && nw.node === popupElement.lastElementChild) break;
}
}
}
}
if (!passed) {
retVal.push(RuleFail("Fail_tabbable_child"));
}
if (retVal.length === 0) {
return RulePass("Pass");
} else {
return retVal;
}
}
}