-
Notifications
You must be signed in to change notification settings - Fork 105
Expand file tree
/
Copy pathelement_tabbable_visible.ts
More file actions
127 lines (114 loc) · 6.26 KB
/
element_tabbable_visible.ts
File metadata and controls
127 lines (114 loc) · 6.26 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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/******************************************************************************
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 { CommonUtil } from "../util/CommonUtil";
import { CSSUtil } from "../util/CSSUtil";
import { Rule, RuleResult, RuleContext, RulePass, RuleContextHierarchy, RulePotential } from "../api/IRule";
import { eRulePolicy, eToolkitLevel } from "../api/IRule";
import { DOMMapper } from "../../v2/dom/DOMMapper";
export const element_tabbable_visible: Rule = {
id: "element_tabbable_visible",
context: "dom:*",
dependencies: [],
help: {
"en-US": {
"group": "element_tabbable_visible.html",
"pass": "element_tabbable_visible.html",
"potential_visible": "element_tabbable_visible.html"
}
},
messages: {
"en-US": {
"group": "A tabbable element should be visible on the screen when it has keyboard focus",
"pass": "The tabbable element is visible on the screen",
"potential_visible": "Confirm the element should be tabbable and if so, it becomes visible when it has keyboard focus"
}
},
rulesets: [{
id: ["IBM_Accessibility", "IBM_Accessibility_next", "WCAG_2_1", "WCAG_2_0", "WCAG_2_2"],
num: ["2.4.7"],
level: eRulePolicy.VIOLATION,
toolkitLevel: eToolkitLevel.LEVEL_ONE
}],
act: [],
run: (context: RuleContext, options?: {}, contextHierarchies?: RuleContextHierarchy): RuleResult | RuleResult[] => {
const ruleContext = context["dom"].node as HTMLElement;
if (!CommonUtil.isTabbable(ruleContext))
return null;
const nodeName = ruleContext.nodeName.toLocaleLowerCase();
const mapper : DOMMapper = new DOMMapper();
const bounds = mapper.getUnadjustedBounds(ruleContext);
//in case the bounds not available
if (!bounds) return null;
// Get all needed styles in a single pass through stylesheets
const allStyles = CSSUtil.getDefinedStylesMultiple(ruleContext, ["position", "clip"], ["", ":focus"]);
const defined_styles = allStyles[""];
const onfocus_styles = allStyles[":focus"];
if (bounds['height'] === 0 || bounds['width'] === 0)
return RulePotential("potential_visible", []);
if (defined_styles['position']==='absolute' && defined_styles['clip'] && defined_styles['clip'].replaceAll(' ', '')==='rect(0px,0px,0px,0px)'
&& !onfocus_styles['clip']) {
/**
* note that A user can select a checkbox and radio button by selecting the button or the label text.
* When a checkbox or radio button is clipped to 0 size, it is still available to a keyboard or a screen reader.
* The rule should be passed if the label text exists and the button on-focus style is defined by the user,
* which likely incurs the changes of the label style.
*/
if (nodeName === 'input' && (ruleContext.getAttribute('type')==='checkbox' || ruleContext.getAttribute('type')==='radio')) {
const label = CommonUtil.getLabelForElement(ruleContext);
if (label && !CommonUtil.isInnerTextEmpty(label)) {
// Get additional pseudo-class styles (reuse onfocus_styles from line 60)
const pseudoStyles = CSSUtil.getDefinedStylesMultiple(ruleContext, null, [":focus-visible", ":focus-within", ":checked", ":focus"]);
const focus_visible_styles = pseudoStyles[":focus-visible"];
const focus_within_styles = pseudoStyles[":focus-within"];
const checked_styles = pseudoStyles[":checked"];
const onfocus_styles = pseudoStyles[":focus"];
if (onfocus_styles || focus_visible_styles || focus_within_styles || checked_styles)
return RulePass("pass");
}
}
return RulePotential("potential_visible", []);
}
if (bounds['top'] >= 0 && bounds['left'] >= 0)
return RulePass("pass");
const default_styles = getComputedStyle(ruleContext);
let top = bounds['top'];
let left = bounds['left'];
if (Object.keys(onfocus_styles).length === 0 ) {
// no onfocus position change, but could be changed from js
return RulePotential("potential_visible", []);
} else {
// with onfocus position change
var positions = ['absolute', 'fixed'];
if (typeof onfocus_styles['top'] !== 'undefined') {
if (positions.includes(onfocus_styles['position']) || (typeof onfocus_styles['position'] === 'undefined' && positions.includes(default_styles['position']))) {
top = onfocus_styles['top'].replace(/\D/g,'');
} else {
// the position is undefined and the parent's position is 'relative'
top = Number.MIN_VALUE;
}
}
if (typeof onfocus_styles['left'] !== 'undefined') {
if (positions.includes(onfocus_styles['position']) || (typeof onfocus_styles['position'] === 'undefined' && positions.includes(default_styles['position']))) {
left = onfocus_styles['left'].replace(/\D/g,'');
} else {
// the position is undefined and the parent's position is 'relative'
left = Number.MIN_VALUE;
}
}
}
if (top >= 0 && left >= 0)
return RulePass("pass");
else
return RulePotential("potential_visible", []);
}
}