testaro 1.0.0
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/LICENSE +21 -0
- package/README.md +502 -0
- package/aceconfig.js +7 -0
- package/commands.js +249 -0
- package/index.js +1248 -0
- package/package.json +39 -0
- package/procs/score/asp09.js +555 -0
- package/procs/test/allText.js +76 -0
- package/procs/test/allVis.js +17 -0
- package/procs/test/linksByType.js +90 -0
- package/procs/test/textOf.txt +73 -0
- package/scoring/correlation.js +74 -0
- package/scoring/correlations.json +327 -0
- package/scoring/data.json +26021 -0
- package/scoring/dupCounts.js +39 -0
- package/scoring/dupCounts.json +112 -0
- package/scoring/duplications.json +253 -0
- package/scoring/issues.json +304 -0
- package/scoring/packageData.js +171 -0
- package/scoring/packageIssues.js +34 -0
- package/scoring/rulesetData.json +15 -0
- package/tests/aatt.js +64 -0
- package/tests/alfa.js +107 -0
- package/tests/axe.js +109 -0
- package/tests/bulk.js +21 -0
- package/tests/embAc.js +36 -0
- package/tests/focAll.js +62 -0
- package/tests/focInd.js +99 -0
- package/tests/focOp.js +132 -0
- package/tests/hover.js +195 -0
- package/tests/ibm.js +89 -0
- package/tests/labClash.js +157 -0
- package/tests/linkUl.js +65 -0
- package/tests/menuNav.js +254 -0
- package/tests/motion.js +115 -0
- package/tests/radioSet.js +87 -0
- package/tests/role.js +164 -0
- package/tests/styleDiff.js +146 -0
- package/tests/tabNav.js +282 -0
- package/tests/wave.js +44 -0
- package/tests/zIndex.js +49 -0
- package/validation/batches/sample.json +13 -0
- package/validation/executors/sample.js +11 -0
- package/validation/scripts/app/sample.json +21 -0
- package/validation/scripts/test/bulk.json +39 -0
- package/validation/scripts/test/embAc.json +45 -0
- package/validation/scripts/test/focAll.json +59 -0
- package/validation/scripts/test/focInd.json +55 -0
- package/validation/scripts/test/focOp.json +53 -0
- package/validation/scripts/test/hover.json +47 -0
- package/validation/scripts/test/labClash.json +43 -0
- package/validation/scripts/test/linkUl.json +62 -0
- package/validation/scripts/test/menuNav.json +97 -0
- package/validation/scripts/test/motion.json +53 -0
- package/validation/scripts/test/radioSet.json +43 -0
- package/validation/scripts/test/role.json +42 -0
- package/validation/scripts/test/styleDiff.json +61 -0
- package/validation/scripts/test/tabNav.json +97 -0
- package/validation/scripts/test/zIndex.json +40 -0
- package/validation/targets/bulk/bad.html +48 -0
- package/validation/targets/bulk/good.html +15 -0
- package/validation/targets/embAc/bad.html +21 -0
- package/validation/targets/embAc/good.html +15 -0
- package/validation/targets/focAll/good.html +15 -0
- package/validation/targets/focAll/less.html +15 -0
- package/validation/targets/focAll/more.html +16 -0
- package/validation/targets/focInd/bad.html +31 -0
- package/validation/targets/focInd/good.html +22 -0
- package/validation/targets/focOp/bad.html +18 -0
- package/validation/targets/focOp/good.html +15 -0
- package/validation/targets/hover/bad.html +19 -0
- package/validation/targets/hover/good.html +15 -0
- package/validation/targets/labClash/bad.html +20 -0
- package/validation/targets/labClash/good.html +18 -0
- package/validation/targets/linkUl/bad.html +16 -0
- package/validation/targets/linkUl/good.html +30 -0
- package/validation/targets/linkUl/na.html +20 -0
- package/validation/targets/menuNav/bad.html +106 -0
- package/validation/targets/menuNav/bad.js +348 -0
- package/validation/targets/menuNav/good.html +106 -0
- package/validation/targets/menuNav/good.js +365 -0
- package/validation/targets/menuNav/style.css +22 -0
- package/validation/targets/motion/bad.css +15 -0
- package/validation/targets/motion/bad.html +16 -0
- package/validation/targets/motion/good.html +15 -0
- package/validation/targets/radioSet/bad.html +34 -0
- package/validation/targets/radioSet/good.html +27 -0
- package/validation/targets/role/bad.html +26 -0
- package/validation/targets/role/good.html +22 -0
- package/validation/targets/styleDiff/bad.html +35 -0
- package/validation/targets/styleDiff/good.html +36 -0
- package/validation/targets/tabNav/bad.html +51 -0
- package/validation/targets/tabNav/bad.js +35 -0
- package/validation/targets/tabNav/good.html +53 -0
- package/validation/targets/tabNav/good.js +83 -0
- package/validation/targets/tabNav/goodMoz.js +206 -0
- package/validation/targets/tabNav/style.css +34 -0
- package/validation/targets/zIndex/bad.html +17 -0
- package/validation/targets/zIndex/good.html +15 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// UTILITIES FOR EVENT HANDLERS
|
|
2
|
+
|
|
3
|
+
// Returns the menu controlled by a menu button.
|
|
4
|
+
const controlledMenu = button => document.getElementById(button.getAttribute('aria-controls'));
|
|
5
|
+
// Returns the button controlling a menu.
|
|
6
|
+
const controller = menu => document.body.querySelector(`button[aria-controls=${menu.id}`);
|
|
7
|
+
// Returns whether an element is a menu item.
|
|
8
|
+
const isMenuItem = element => element.getAttribute('role') === 'menuitem';
|
|
9
|
+
// Returns the focus type of a menu.
|
|
10
|
+
const focusTypeOf = menu => menu.hasAttribute('aria-activedescendant') ? 'fake' : 'true';
|
|
11
|
+
// Closes a menu controlled by a button.
|
|
12
|
+
const closeMenu = button => {
|
|
13
|
+
// If the menu is open:
|
|
14
|
+
if (button.ariaExpanded === 'true') {
|
|
15
|
+
// Close it.
|
|
16
|
+
button.setAttribute('aria-expanded', 'false');
|
|
17
|
+
const menu = controlledMenu(button);
|
|
18
|
+
menu.className = 'shut';
|
|
19
|
+
menu.tabIndex = -1;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
// Returns the menu items of a menu.
|
|
23
|
+
const menuItemsOf = menu => {
|
|
24
|
+
const items = [];
|
|
25
|
+
const children = Array.from(menu.children);
|
|
26
|
+
children.forEach(child => {
|
|
27
|
+
if (isMenuItem(child)) {
|
|
28
|
+
items.push(child);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const grandchild = child.firstElementChild;
|
|
32
|
+
if (grandchild.getAttribute('role')) {
|
|
33
|
+
items.push(grandchild);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return items;
|
|
38
|
+
};
|
|
39
|
+
// Returns the menu that a menu item is an item of.
|
|
40
|
+
const owningMenuOf = menuItem => {
|
|
41
|
+
const parent = menuItem.parentElement;
|
|
42
|
+
const parentRole = parent.getAttribute('role');
|
|
43
|
+
if (parentRole.startsWith('menu')){
|
|
44
|
+
return parent;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const grandparent = parent.parentElement;
|
|
48
|
+
const grandparentRole = grandparent.getAttribute('role');
|
|
49
|
+
if (grandparentRole.startsWith('menu')) {
|
|
50
|
+
return grandparent;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
// Returns the index of the active menu item of a menu, or -1 if none.
|
|
58
|
+
const activeIndexOf = (isButton, buttonOrMenu) => {
|
|
59
|
+
const menu = isButton ? controlledMenu(buttonOrMenu) : buttonOrMenu;
|
|
60
|
+
const items = menuItemsOf(menu);
|
|
61
|
+
// If the menu is a fake focus manager, return the index.
|
|
62
|
+
if (menu.hasAttribute('aria-activedescendant')) {
|
|
63
|
+
const activeID = menu.getAttribute('aria-activedescendant');
|
|
64
|
+
if (activeID) {
|
|
65
|
+
const itemIDs = items.map(item => item.id);
|
|
66
|
+
return itemIDs.indexOf(activeID);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
return -1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Otherwise, i.e. if the menu is a true focus manager, return the index.
|
|
73
|
+
else {
|
|
74
|
+
const tabIndexes = items.map(item => item.tabIndex);
|
|
75
|
+
return tabIndexes.indexOf(0);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
// Makes the specified (or the last if -1) menu item active and close any sibling menus.
|
|
79
|
+
const setActive = (focusType, menu, itemIndex) => {
|
|
80
|
+
// Identify the menu items.
|
|
81
|
+
const menuItems = menuItemsOf(menu);
|
|
82
|
+
// Identify the specified index.
|
|
83
|
+
const newIndex = itemIndex === -1 ? menuItems.length - 1 : itemIndex;
|
|
84
|
+
// For each menu item in the menu:
|
|
85
|
+
menuItems.forEach((item, index) => {
|
|
86
|
+
// If it has the specified index:
|
|
87
|
+
if (index === newIndex) {
|
|
88
|
+
// If the menu is a pseudofocus manager:
|
|
89
|
+
if (focusType === 'fake') {
|
|
90
|
+
// Mark the item as focal.
|
|
91
|
+
item.className = 'focal';
|
|
92
|
+
menu.setAttribute('aria-activedescendant', item.id);
|
|
93
|
+
// Ensure that the menu is in focus.
|
|
94
|
+
menu.tabIndex = 0;
|
|
95
|
+
menu.focus();
|
|
96
|
+
}
|
|
97
|
+
// Otherwise, if the menu is a true focus manager:
|
|
98
|
+
else if (focusType === 'true') {
|
|
99
|
+
// Make the item focusable.
|
|
100
|
+
item.tabIndex = 0;
|
|
101
|
+
// Focus it.
|
|
102
|
+
item.focus();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Otherwise, i.e. if the menu item does not have the specified index:
|
|
106
|
+
else {
|
|
107
|
+
// If the menu is a pseudofocus manager:
|
|
108
|
+
if (focusType === 'fake') {
|
|
109
|
+
// Mark the item as nonfocal.
|
|
110
|
+
item.className = 'blurred';
|
|
111
|
+
}
|
|
112
|
+
// Otherwise, if the menu is a true focus manager:
|
|
113
|
+
else if (focusType === 'true') {
|
|
114
|
+
// Make the item nonfocusable.
|
|
115
|
+
item.tabIndex = -1;
|
|
116
|
+
}
|
|
117
|
+
// Close the menu controlled by the item if the item is a menu button.
|
|
118
|
+
closeMenu(item);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
// Opens a menu controlled by a button and makes an item active.
|
|
123
|
+
const openMenu = (button, newIndex) => {
|
|
124
|
+
// Open the menu (Chrome fails to support settable ariaExpanded property).
|
|
125
|
+
button.setAttribute('aria-expanded', 'true');
|
|
126
|
+
const menu = controlledMenu(button);
|
|
127
|
+
menu.className = 'open';
|
|
128
|
+
const focusType = focusTypeOf(menu);
|
|
129
|
+
// If an active index was specified:
|
|
130
|
+
if (newIndex > -2) {
|
|
131
|
+
// Make the specified menu item active.
|
|
132
|
+
setActive(focusType, menu, newIndex);
|
|
133
|
+
}
|
|
134
|
+
// Otherwise, i.e. if no active index was specified:
|
|
135
|
+
else {
|
|
136
|
+
// Make the currently active menu item, or if none the first one, active.
|
|
137
|
+
const oldIndex = activeIndexOf(true, button);
|
|
138
|
+
setActive(focusType, menu, oldIndex > -1 ? oldIndex : 0);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
// Navigates within a menu according to a key press and returns the newly active index.
|
|
142
|
+
const keyNav = (isBar, menu, key, focusType) => {
|
|
143
|
+
const oldIndex = activeIndexOf(false, menu);
|
|
144
|
+
const menuItems = menuItemsOf(menu);
|
|
145
|
+
const menuItemCount = menuItems.length;
|
|
146
|
+
// Initialize the chosen index as the current index.
|
|
147
|
+
let newIndex = oldIndex;
|
|
148
|
+
// If the request is for the next menu item:
|
|
149
|
+
if (key === (isBar ? 'ArrowRight' : 'ArrowDown')) {
|
|
150
|
+
// Change the index to the next menu item’s, wrapping.
|
|
151
|
+
newIndex = (oldIndex + 1) % menuItemCount;
|
|
152
|
+
}
|
|
153
|
+
// Otherwise, if the request is for the previous menu item:
|
|
154
|
+
else if (key === (isBar ? 'ArrowLeft' : 'ArrowUp')) {
|
|
155
|
+
// Change the index to the previous menu item’s, wrapping.
|
|
156
|
+
newIndex = (menuItemCount + oldIndex - 1) % menuItemCount;
|
|
157
|
+
}
|
|
158
|
+
else if (key === 'Home') {
|
|
159
|
+
newIndex = 0;
|
|
160
|
+
}
|
|
161
|
+
else if (key === 'End') {
|
|
162
|
+
newIndex = menuItemCount - 1;
|
|
163
|
+
}
|
|
164
|
+
// Otherwise, if the request is for a menu item with an initial letter:
|
|
165
|
+
else if (/^[a-zA-Z]$/.test(key)) {
|
|
166
|
+
// Change the index to the next matching menu item’s, if there is one.
|
|
167
|
+
const matches = menuItems.map((item, index) =>
|
|
168
|
+
item.textContent.toLowerCase().trim().startsWith(key.toLowerCase()) ? index : -1
|
|
169
|
+
);
|
|
170
|
+
const laterMatches = matches.filter(index => index > -1 && index > oldIndex);
|
|
171
|
+
if (laterMatches.length) {
|
|
172
|
+
newIndex = laterMatches[0];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Make the menu item with the new index active.
|
|
176
|
+
if (newIndex > -1) {
|
|
177
|
+
setActive(focusType, menu, newIndex);
|
|
178
|
+
}
|
|
179
|
+
// Return the new index.
|
|
180
|
+
return newIndex;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// EVENT LISTENERS
|
|
184
|
+
|
|
185
|
+
// Handle clicks.
|
|
186
|
+
document.body.addEventListener('click', event => {
|
|
187
|
+
// Identify the click target.
|
|
188
|
+
let target = event.target;
|
|
189
|
+
// If it is a menu item:
|
|
190
|
+
if (isMenuItem(target)) {
|
|
191
|
+
// Identify the menu that owns it.
|
|
192
|
+
const menu = owningMenuOf(target);
|
|
193
|
+
// If the owning menu exists:
|
|
194
|
+
if (menu) {
|
|
195
|
+
// Make the menu item active.
|
|
196
|
+
const itemIndex = menuItemsOf(menu).indexOf(target);
|
|
197
|
+
setActive(focusTypeOf(menu), menu, itemIndex);
|
|
198
|
+
// If the menu item is also a menu button:
|
|
199
|
+
if (target.tagName === 'BUTTON' && ['menu', 'true'].includes(target.ariaHasPopup)) {
|
|
200
|
+
// If the menu it controls is closed:
|
|
201
|
+
if (target.ariaExpanded === 'false') {
|
|
202
|
+
// Open it with the first item active.
|
|
203
|
+
openMenu(target, 0);
|
|
204
|
+
}
|
|
205
|
+
// Otherwise, if the menu it controls is open:
|
|
206
|
+
else if (target.ariaExpanded === 'true') {
|
|
207
|
+
// Close it.
|
|
208
|
+
closeMenu(target);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Otherwise, if the menu item is also a link:
|
|
212
|
+
else if (target.tagName === 'A') {
|
|
213
|
+
// Identify the button controlling the menu, if any.
|
|
214
|
+
const ownerButton = controller(menu);
|
|
215
|
+
// If the menu has a controlling button:
|
|
216
|
+
if (ownerButton) {
|
|
217
|
+
// If the button is also a menu item:
|
|
218
|
+
if (isMenuItem(ownerButton)) {
|
|
219
|
+
// Identify the menu that it is an item of.
|
|
220
|
+
const ownerMenu = ownerMenu(ownerButton);
|
|
221
|
+
// If it exists:
|
|
222
|
+
if (ownerMenu) {
|
|
223
|
+
// Identify the menu button’s index as a menu item.
|
|
224
|
+
const ownerIndex = menuItemsOf(ownerMenu).indexOf(ownerButton);
|
|
225
|
+
// Make it active.
|
|
226
|
+
setActive(focusTypeOf(ownerMenu), ownerMenu, ownerIndex);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
// Handle key presses.
|
|
235
|
+
window.addEventListener('keydown', event => {
|
|
236
|
+
const key = event.key;
|
|
237
|
+
// If no ineligible modifier key was in effect when the key was depressed:
|
|
238
|
+
if (! (event.altKey || event.ctrlKey || event.metaKey)) {
|
|
239
|
+
// Identify whether the shift key was in effect.
|
|
240
|
+
const shift = event.shiftKey;
|
|
241
|
+
// Initialize the focused element.
|
|
242
|
+
let focus = document.activeElement;
|
|
243
|
+
// If it exists and is within the body:
|
|
244
|
+
if (focus && focus !== document.body) {
|
|
245
|
+
// Change it to the effectively focused element if different.
|
|
246
|
+
if (focus.hasAttribute('aria-activedescendant')) {
|
|
247
|
+
focus = document.getElementById(focus.getAttribute('aria-activedescendant'));
|
|
248
|
+
}
|
|
249
|
+
// If it is a menu item:
|
|
250
|
+
if (focus.getAttribute('role') === 'menuitem') {
|
|
251
|
+
// Identify the menu that the item is in.
|
|
252
|
+
const menu = owningMenuOf(focus);
|
|
253
|
+
// If it exists:
|
|
254
|
+
if (menu) {
|
|
255
|
+
// Identify the role of the menu.
|
|
256
|
+
const menuRole = menu.getAttribute('role');
|
|
257
|
+
// Identify whether the menu manages pseudofocus or true focus.
|
|
258
|
+
const focusType = focusTypeOf(menu);
|
|
259
|
+
// Identify the menu button controlling the menu, if any.
|
|
260
|
+
const ownerButton = controller(menu);
|
|
261
|
+
// Initialize the type of the focused element as inoperable.
|
|
262
|
+
let itemType = 'plain';
|
|
263
|
+
// Revise the type if it is operable.
|
|
264
|
+
const tagName = focus.tagName;
|
|
265
|
+
if (tagName === 'A') {
|
|
266
|
+
itemType = 'link';
|
|
267
|
+
}
|
|
268
|
+
else if (tagName === 'BUTTON') {
|
|
269
|
+
itemType = focus.ariaExpanded ? 'menuButton' : 'button';
|
|
270
|
+
}
|
|
271
|
+
// If the key is Enter:
|
|
272
|
+
if (key === 'Enter' && ! shift) {
|
|
273
|
+
// If the menu item is a link or ordinary button:
|
|
274
|
+
if (['link', 'button'].includes(itemType)) {
|
|
275
|
+
// Simulate a click on it.
|
|
276
|
+
focus.click();
|
|
277
|
+
}
|
|
278
|
+
// Otherwise, if the menu item is a menu button:
|
|
279
|
+
else if (itemType === 'menuButton') {
|
|
280
|
+
// Its menu must be closed, so open it, with the first item active.
|
|
281
|
+
openMenu(focus, 0);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Otherwise, if the key is Space:
|
|
285
|
+
else if (key === ' ' && ! shift) {
|
|
286
|
+
// If the menu item is an ordinary button:
|
|
287
|
+
if (itemType === 'button') {
|
|
288
|
+
// Prevent default scrolling.
|
|
289
|
+
event.preventDefault();
|
|
290
|
+
// Simulate a click on the button.
|
|
291
|
+
focus.click();
|
|
292
|
+
}
|
|
293
|
+
// Otherwise, if the menu item is a menu button:
|
|
294
|
+
else if (itemType === 'menuButton') {
|
|
295
|
+
// Prevent default scrolling.
|
|
296
|
+
event.preventDefault();
|
|
297
|
+
// Its menu must be closed, so open it, with the first item active.
|
|
298
|
+
openMenu(focus, 0);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Otherwise, if the key is Tab and the menu is closable:
|
|
302
|
+
else if (key === 'Tab' && ownerButton) {
|
|
303
|
+
// Close the menu.
|
|
304
|
+
closeMenu(ownerButton);
|
|
305
|
+
}
|
|
306
|
+
// Otherwise, if the key is Escape and the menu is closable:
|
|
307
|
+
else if (key === 'Escape' && ! shift && ownerButton) {
|
|
308
|
+
// Close the menu.
|
|
309
|
+
closeMenu(ownerButton);
|
|
310
|
+
// Focus its menu button.
|
|
311
|
+
ownerButton.focus();
|
|
312
|
+
}
|
|
313
|
+
// Otherwise, if the key is a letter:
|
|
314
|
+
else if (/^[a-zA-Z]$/.test(key)) {
|
|
315
|
+
// Navigate within the menu according to the key.
|
|
316
|
+
keyNav(true, menu, key, focusType);
|
|
317
|
+
}
|
|
318
|
+
// Otherwise, if the key navigates among parent menu items in a menu bar:
|
|
319
|
+
else if (['ArrowLeft', 'ArrowRight'].includes(key)) {
|
|
320
|
+
// If the focused element is a menu button in a menu bar:
|
|
321
|
+
if (itemType === 'menuButton' && menuRole === 'menubar') {
|
|
322
|
+
// Navigate within the menu bar according to the key.
|
|
323
|
+
keyNav(true, menu, key, focusType);
|
|
324
|
+
}
|
|
325
|
+
// Otherwise, if the focused element is a button-controlled menu item:
|
|
326
|
+
else if (menuRole === 'menu' && ownerButton) {
|
|
327
|
+
// Close the menu.
|
|
328
|
+
closeMenu(ownerButton);
|
|
329
|
+
// Identify the menu bar.
|
|
330
|
+
const bar = owningMenuOf(ownerButton);
|
|
331
|
+
// Obey the key.
|
|
332
|
+
const newBarIndex = keyNav(true, bar, key, focusTypeOf(bar));
|
|
333
|
+
// Open the menu controlled by the newly focused menu button and focus its first item.
|
|
334
|
+
openMenu(menuItemsOf(bar)[newBarIndex], 0);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Otherwise, if the key specially navigates within the menu:
|
|
338
|
+
else if (['Home', 'End'].includes(key) && ! shift) {
|
|
339
|
+
// Prevent default page scrolling.
|
|
340
|
+
event.preventDefault();
|
|
341
|
+
// Obey the key.
|
|
342
|
+
keyNav(true, menu, key, focusType);
|
|
343
|
+
}
|
|
344
|
+
// Otherwise, if the key conditionally navigates or opens a menu:
|
|
345
|
+
else if (['ArrowUp', 'ArrowDown'].includes(key) && ! shift) {
|
|
346
|
+
// If the focused element is a menu button in a menu bar:
|
|
347
|
+
if (menuRole === 'menubar' && itemType === 'menuButton') {
|
|
348
|
+
// Prevent default scrolling.
|
|
349
|
+
event.preventDefault();
|
|
350
|
+
// Open the menu button’s menu.
|
|
351
|
+
openMenu(focus, key === 'ArrowUp' ? -1 : 0);
|
|
352
|
+
}
|
|
353
|
+
// Otherwise, if the active item is not a menu button and is in a menu:
|
|
354
|
+
else if (menuRole === 'menu' && itemType !== 'menuButton') {
|
|
355
|
+
// Prevent default scrolling.
|
|
356
|
+
event.preventDefault();
|
|
357
|
+
// Obey the key.
|
|
358
|
+
keyNav(false, menu, key, focusType);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
:focus:not(ul), .focal {
|
|
2
|
+
outline: 2px #00f solid;
|
|
3
|
+
outline-offset: 2px;
|
|
4
|
+
}
|
|
5
|
+
.menubar {
|
|
6
|
+
min-height: 7rem;
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: flex-start;
|
|
9
|
+
justify-content: space-evenly;
|
|
10
|
+
}
|
|
11
|
+
.menubar li {
|
|
12
|
+
list-style-type: none;
|
|
13
|
+
}
|
|
14
|
+
.open {
|
|
15
|
+
display: block;
|
|
16
|
+
}
|
|
17
|
+
.shut {
|
|
18
|
+
display: none;
|
|
19
|
+
}
|
|
20
|
+
ul:focus {
|
|
21
|
+
outline: none;
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Page with motion</title>
|
|
6
|
+
<meta name="description" content="tester">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
<link rel="stylesheet" href="bad.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<main>
|
|
12
|
+
<h1>Page with motion</h1>
|
|
13
|
+
<p class="moving">This page has motion.</p>
|
|
14
|
+
</main>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Page without motion</title>
|
|
6
|
+
<meta name="description" content="tester">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main>
|
|
11
|
+
<h1>Page without motion</h1>
|
|
12
|
+
<p>This page has no motion.</p>
|
|
13
|
+
</main>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Page with deviant radio-button grouping</title>
|
|
6
|
+
<meta name="description" content="tester">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main>
|
|
11
|
+
<h1>Page with deviant radio-button grouping</h1>
|
|
12
|
+
<p>This page contains radio buttons in nonstandard groups.</p>
|
|
13
|
+
<form>
|
|
14
|
+
<div><fieldset>
|
|
15
|
+
<legend>Choose a drink and a programming language</legend>
|
|
16
|
+
<p><label><input type="radio" name="drink" value="water">water</label></p>
|
|
17
|
+
<p><label><input type="radio" name="drink" value="juice">juice</label></p>
|
|
18
|
+
<p><label><input type="radio" name="lang" value="fortran">Fortran</label></p>
|
|
19
|
+
<p><label><input type="radio" name="lang" value="ada">Ada</label></p>
|
|
20
|
+
</fieldset></div>
|
|
21
|
+
<div><fieldset>
|
|
22
|
+
<p>Choose a food</p>
|
|
23
|
+
<p><label><input type="radio" name="food" value="rice">rice</label></p>
|
|
24
|
+
<p><label><input type="radio" name="food" value="apple">apple</label></p>
|
|
25
|
+
</fieldset></div>
|
|
26
|
+
<div>
|
|
27
|
+
<p>Choose a gas</p>
|
|
28
|
+
<p><label><input type="radio" name="gas" value="methane">methane</label></p>
|
|
29
|
+
<p><label><input type="radio" name="gas" value="nitrogen">nitrogen</label></p>
|
|
30
|
+
</div>
|
|
31
|
+
</form>
|
|
32
|
+
</main>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Page with standard radio-button grouping</title>
|
|
6
|
+
<meta name="description" content="tester">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main>
|
|
11
|
+
<h1>Page with standard radio-button grouping</h1>
|
|
12
|
+
<p>This page contains radio buttons in standard groups.</p>
|
|
13
|
+
<form>
|
|
14
|
+
<p><fieldset>
|
|
15
|
+
<legend>Choose a drink</legend>
|
|
16
|
+
<p><label><input type="radio" name="drink" value="water">water</label></p>
|
|
17
|
+
<p><label><input type="radio" name="drink" value="juice">juice</label></p>
|
|
18
|
+
</fieldset></p>
|
|
19
|
+
<p><fieldset>
|
|
20
|
+
<legend>Choose a programming language</legend>
|
|
21
|
+
<p><label><input type="radio" name="lang" value="fortran">Fortran</label></p>
|
|
22
|
+
<p><label><input type="radio" name="lang" value="ada">Ada</label></p>
|
|
23
|
+
</fieldset></p>
|
|
24
|
+
</form>
|
|
25
|
+
</main>
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Page with deviant role elements</title>
|
|
6
|
+
<meta name="description" content="tester">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<section role="main">
|
|
11
|
+
<h1>Page with deviant role elements</h1>
|
|
12
|
+
<section role="section">
|
|
13
|
+
<h2>Abstraction</h2>
|
|
14
|
+
<p>This section has an abstract role.</p>
|
|
15
|
+
</section>
|
|
16
|
+
<section>
|
|
17
|
+
<h2 role="heading">Redundancy</h2>
|
|
18
|
+
<p>This section has a redundant role.</p>
|
|
19
|
+
</section>
|
|
20
|
+
<section>
|
|
21
|
+
<h2>Conflict</h2>
|
|
22
|
+
<p>The parent section of these sections has an unnecessary explicit role.</p>
|
|
23
|
+
</section>
|
|
24
|
+
</section>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Page with standard role elements</title>
|
|
6
|
+
<meta name="description" content="tester">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main>
|
|
11
|
+
<h1>Page with standard role elements</h1>
|
|
12
|
+
<section role="note">
|
|
13
|
+
<h2>Note</h2>
|
|
14
|
+
<p>This section has parenthetical content and a standard role indicating that.</p>
|
|
15
|
+
</section>
|
|
16
|
+
<section>
|
|
17
|
+
<h2>Basic content</h2>
|
|
18
|
+
<p>This section is non-parenthetical.</p>
|
|
19
|
+
</section>
|
|
20
|
+
</main>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Page with inconsistent styles</title>
|
|
6
|
+
<meta name="description" content="tester">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
<style>
|
|
9
|
+
button {
|
|
10
|
+
min-height: 44px;
|
|
11
|
+
min-width: 44px;
|
|
12
|
+
}
|
|
13
|
+
.italic {
|
|
14
|
+
font-style: italic;
|
|
15
|
+
}
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<main>
|
|
20
|
+
<h1>Page with inconsistent styles</h1>
|
|
21
|
+
<h2>Inline links</h2>
|
|
22
|
+
<p>This paragraph contains inline links to <a href="https://en.wikipedia.org">English information</a> and <a style="text-decoration: underline; text-decoration-style: double" href="https://fr.wikipedia.org">French information</a>. They have different styles.</p>
|
|
23
|
+
<h2>Block links</h2>
|
|
24
|
+
<p>The following links are not inline. They have a different styles.</p>
|
|
25
|
+
<ul>
|
|
26
|
+
<li><a style="font-weight: 700" href="https://sp.wikipedia.org">Spanish information</a></li>
|
|
27
|
+
<li><a href="https://eo.wikipedia.org">Esperanto information</a></li>
|
|
28
|
+
</ul>
|
|
29
|
+
<h2>Buttons</h2>
|
|
30
|
+
<p>This paragraph contains two buttons with a different custom styles. <button type="button">Wikipedia</button> <button type="button" style="border-width: 3px">Wiktionary</button></p>
|
|
31
|
+
<h2 class="italic">Headings</h2>
|
|
32
|
+
<p>This page contains 4 <code>h2</code> headings. This one has a deviant style.</p>
|
|
33
|
+
</main>
|
|
34
|
+
</body>
|
|
35
|
+
</html>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Page with consistent styles</title>
|
|
6
|
+
<meta name="description" content="tester">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
<style>
|
|
9
|
+
button {
|
|
10
|
+
min-height: 44px;
|
|
11
|
+
min-width: 44px;
|
|
12
|
+
}
|
|
13
|
+
.dotted > li > a {
|
|
14
|
+
text-decoration-line: underline;
|
|
15
|
+
text-decoration-style: dotted;
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<main>
|
|
21
|
+
<h1>Page with consistent styles</h1>
|
|
22
|
+
<h2>Inline links</h2>
|
|
23
|
+
<p>This paragraph contains inline links to <a href="https://en.wikipedia.org">English information</a> and <a href="https://fr.wikipedia.org">French information</a>. They both have a default style.</p>
|
|
24
|
+
<h2>Block links</h2>
|
|
25
|
+
<p>The following links are not inline. They have a uniform custom underline.</p>
|
|
26
|
+
<ul class="dotted">
|
|
27
|
+
<li><a href="https://sp.wikipedia.org">Spanish information</a></li>
|
|
28
|
+
<li><a href="https://eo.wikipedia.org">Esperanto information</a></li>
|
|
29
|
+
</ul>
|
|
30
|
+
<h2>Buttons</h2>
|
|
31
|
+
<p>This paragraph contains two buttons with a uniform custom style. <button type="button">Wikipedia</button> <button type="button">Wiktionary</button></p>
|
|
32
|
+
<h2>Headings</h2>
|
|
33
|
+
<p>This page contains 4 <code>h2</code> headings. They all have a default style.</p>
|
|
34
|
+
</main>
|
|
35
|
+
</body>
|
|
36
|
+
</html>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Page with deviant tab-list navigation</title>
|
|
6
|
+
<meta name="description" content="tester">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
<link rel="stylesheet" href="style.css">
|
|
9
|
+
<script src="bad.js" defer></script>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<main>
|
|
13
|
+
<h1>Page with deviant tab-list navigation</h1>
|
|
14
|
+
<div class="tabs">
|
|
15
|
+
<p id="tabListLabel">What kind of HTML element do you want to study?</p>
|
|
16
|
+
<div role="tablist" aria-labelledby="tabListLabel">
|
|
17
|
+
<button role="tab" aria-selected="true" aria-controls="ulDoc" id="ul">
|
|
18
|
+
<code>ul</code>
|
|
19
|
+
</button>
|
|
20
|
+
<button
|
|
21
|
+
role="tab"
|
|
22
|
+
aria-selected="false"
|
|
23
|
+
aria-controls="sectionDoc"
|
|
24
|
+
id="section"
|
|
25
|
+
>
|
|
26
|
+
<code>section</code>
|
|
27
|
+
</button>
|
|
28
|
+
<button
|
|
29
|
+
role="tab"
|
|
30
|
+
aria-selected="false"
|
|
31
|
+
aria-controls="mainDoc"
|
|
32
|
+
id="main"
|
|
33
|
+
>
|
|
34
|
+
<code>main</code>
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="tabPanels">
|
|
38
|
+
<div tabindex="0" role="tabpanel" id="ulDoc" aria-labelledby="ul" hidden>
|
|
39
|
+
<p>A <code>ul</code> element encodes an unordered list.</p>
|
|
40
|
+
</div>
|
|
41
|
+
<div tabindex="0" role="tabpanel" id="sectionDoc" aria-labelledby="section" hidden>
|
|
42
|
+
<p>A <code>section</code> element encodes a generic part of a document.</p>
|
|
43
|
+
</div>
|
|
44
|
+
<div tabindex="0" role="tabpanel" id="mainDoc" aria-labelledby="main" hidden>
|
|
45
|
+
<p>A <code>main</code> element encodes the dominant content of a document.</p>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</main>
|
|
50
|
+
</body>
|
|
51
|
+
</html>
|