aria-ease 6.14.0 → 7.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/dist/AccordionComponentStrategy-2SWMNUR6.js +1 -0
- package/dist/ComboboxComponentStrategy-YSYLR2U5.js +5 -0
- package/dist/MenuComponentStrategy-C22BZEBH.js +5 -0
- package/dist/RelativeTargetResolver-T4P25J2M.js +1 -0
- package/dist/TabsComponentStrategy-ADEEFJXM.js +1 -0
- package/dist/audit-APAPHXRO.js +9 -0
- package/dist/badgeHelper-IB5RTMAG.js +11 -0
- package/dist/badgeHelper-JSROP5ML.js +1 -0
- package/dist/buildContracts-T4XQZBDU.js +13 -0
- package/dist/chunk-52I3INNG.js +11 -0
- package/dist/chunk-APUMBDOT.js +1 -0
- package/dist/chunk-BHNO4ZI3.js +1 -0
- package/dist/chunk-CNU4N4AY.js +1 -0
- package/dist/chunk-SM6ZKEDR.js +1 -0
- package/dist/chunk-ZNQ5BXVJ.js +1 -0
- package/dist/cli.cjs +132 -3560
- package/dist/cli.js +19 -161
- package/dist/configLoader-ZEJVXLX7.js +1 -0
- package/dist/configLoader-ZXTSCIP6.js +1 -0
- package/dist/contractTestRunnerPlaywright-FOCQTM4L.js +46 -0
- package/dist/contractTestRunnerPlaywright-QPU6HZXG.js +46 -0
- package/dist/formatters-H3CPDLG5.js +87 -0
- package/dist/index.cjs +64 -5103
- package/dist/index.d.cts +4 -6
- package/dist/index.d.ts +4 -6
- package/dist/index.js +17 -2703
- package/dist/src/accordion/index.cjs +1 -183
- package/dist/src/accordion/index.js +1 -181
- package/dist/src/block/index.cjs +1 -124
- package/dist/src/block/index.js +1 -122
- package/dist/src/checkbox/index.cjs +1 -109
- package/dist/src/checkbox/index.js +1 -107
- package/dist/src/combobox/index.cjs +1 -265
- package/dist/src/combobox/index.js +1 -263
- package/dist/src/menu/index.cjs +1 -339
- package/dist/src/menu/index.js +1 -337
- package/dist/src/radio/index.cjs +1 -117
- package/dist/src/radio/index.js +1 -115
- package/dist/src/tabs/index.cjs +1 -265
- package/dist/src/tabs/index.js +1 -263
- package/dist/src/toggle/index.cjs +1 -119
- package/dist/src/toggle/index.js +1 -117
- package/dist/src/utils/test/AccordionComponentStrategy-X2GSQ5KT.js +1 -0
- package/dist/src/utils/test/ComboboxComponentStrategy-SICWLI27.js +5 -0
- package/dist/src/utils/test/MenuComponentStrategy-R4VPAHDE.js +5 -0
- package/dist/src/utils/test/RelativeTargetResolver-UQQMZHI6.js +1 -0
- package/dist/src/utils/test/TabsComponentStrategy-L2PYNEW6.js +1 -0
- package/dist/src/utils/test/badgeHelper-ER5ZOHWF.js +11 -0
- package/dist/src/utils/test/chunk-APUMBDOT.js +1 -0
- package/dist/src/utils/test/chunk-BHNO4ZI3.js +1 -0
- package/dist/src/utils/test/configLoader-NCYRL2O6.js +1 -0
- package/dist/src/utils/test/contractTestRunnerPlaywright-YZCMF64Q.js +46 -0
- package/dist/src/utils/test/dsl/index.cjs +1 -838
- package/dist/src/utils/test/dsl/index.d.cts +2 -4
- package/dist/src/utils/test/dsl/index.d.ts +2 -4
- package/dist/src/utils/test/dsl/index.js +1 -836
- package/dist/src/utils/test/index.cjs +64 -2672
- package/dist/src/utils/test/index.d.cts +2 -2
- package/dist/src/utils/test/index.d.ts +2 -2
- package/dist/src/utils/test/index.js +16 -340
- package/dist/test-VXSCSKV5.js +19 -0
- package/package.json +7 -9
- package/dist/AccordionComponentStrategy-4ZEIQ2V6.js +0 -42
- package/dist/ComboboxComponentStrategy-DU342VMB.js +0 -64
- package/dist/MenuComponentStrategy-VYCC2XOM.js +0 -81
- package/dist/RelativeTargetResolver-DJAITO6D.js +0 -7
- package/dist/TabsComponentStrategy-3SQURPMX.js +0 -29
- package/dist/audit-JYEPKLHR.js +0 -63
- package/dist/badgeHelper-JOWO6RQG.js +0 -15
- package/dist/badgeHelper-RDOMCC6E.js +0 -108
- package/dist/buildContracts-VIV6GM56.js +0 -437
- package/dist/chunk-4DU5Z5BR.js +0 -340
- package/dist/chunk-GJGUY643.js +0 -182
- package/dist/chunk-GLT43UVH.js +0 -43
- package/dist/chunk-I2KLQ2HA.js +0 -22
- package/dist/chunk-JJEPLK7L.js +0 -107
- package/dist/chunk-PK5L2SAF.js +0 -17
- package/dist/configLoader-Q7N5XV4P.js +0 -183
- package/dist/configLoader-REHK3S3Q.js +0 -7
- package/dist/contractTestRunnerPlaywright-B2HLZKKK.js +0 -1394
- package/dist/contractTestRunnerPlaywright-RWK52C7S.js +0 -1394
- package/dist/formatters-32KQIIYS.js +0 -183
- package/dist/src/utils/test/AccordionComponentStrategy-WRHZOEN6.js +0 -38
- package/dist/src/utils/test/ComboboxComponentStrategy-XKQ72RFD.js +0 -60
- package/dist/src/utils/test/MenuComponentStrategy-6XWU5KLW.js +0 -77
- package/dist/src/utils/test/RelativeTargetResolver-G2XDN2VV.js +0 -1
- package/dist/src/utils/test/TabsComponentStrategy-BKG53SEV.js +0 -26
- package/dist/src/utils/test/badgeHelper-HZKGOPB4.js +0 -102
- package/dist/src/utils/test/chunk-4DU5Z5BR.js +0 -332
- package/dist/src/utils/test/chunk-GLT43UVH.js +0 -41
- package/dist/src/utils/test/configLoader-NA7IBCS3.js +0 -181
- package/dist/src/utils/test/contractTestRunnerPlaywright-5FIGA5G4.js +0 -1372
- package/dist/test-WDBS5JWO.js +0 -358
package/dist/index.js
CHANGED
|
@@ -1,2705 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
normalizeStrictness,
|
|
6
|
-
resolveEnforcement
|
|
7
|
-
} from "./chunk-4DU5Z5BR.js";
|
|
8
|
-
import "./chunk-I2KLQ2HA.js";
|
|
9
|
-
|
|
10
|
-
// src/accordion/src/makeAccordionAccessible/makeAccordionAccessible.ts
|
|
11
|
-
function makeAccordionAccessible({ accordionId, triggersClass, panelsClass, allowMultipleOpen = false, callback }) {
|
|
12
|
-
const accordionContainer = document.querySelector(`#${accordionId}`);
|
|
13
|
-
if (!accordionContainer) {
|
|
14
|
-
console.error(`[aria-ease] Element with id="${accordionId}" not found. Make sure the accordion container exists before calling makeAccordionAccessible.`);
|
|
15
|
-
return { cleanup: () => {
|
|
16
|
-
} };
|
|
17
|
-
}
|
|
18
|
-
const triggers = Array.from(accordionContainer.querySelectorAll(`.${triggersClass}`));
|
|
19
|
-
if (triggers.length === 0) {
|
|
20
|
-
console.error(`[aria-ease] No elements with class="${triggersClass}" found. Make sure accordion triggers exist before calling makeAccordionAccessible.`);
|
|
21
|
-
return { cleanup: () => {
|
|
22
|
-
} };
|
|
23
|
-
}
|
|
24
|
-
const panels = Array.from(accordionContainer.querySelectorAll(`.${panelsClass}`));
|
|
25
|
-
if (panels.length === 0) {
|
|
26
|
-
console.error(`[aria-ease] No elements with class="${panelsClass}" found. Make sure accordion panels exist before calling makeAccordionAccessible.`);
|
|
27
|
-
return { cleanup: () => {
|
|
28
|
-
} };
|
|
29
|
-
}
|
|
30
|
-
if (triggers.length !== panels.length) {
|
|
31
|
-
console.error(`[aria-ease] Accordion trigger/panel mismatch: found ${triggers.length} triggers but ${panels.length} panels.`);
|
|
32
|
-
return { cleanup: () => {
|
|
33
|
-
} };
|
|
34
|
-
}
|
|
35
|
-
const handlerMap = /* @__PURE__ */ new WeakMap();
|
|
36
|
-
const clickHandlerMap = /* @__PURE__ */ new WeakMap();
|
|
37
|
-
function initialize() {
|
|
38
|
-
triggers.forEach((trigger, index) => {
|
|
39
|
-
const panel = panels[index];
|
|
40
|
-
if (!trigger.id) {
|
|
41
|
-
trigger.id = `${accordionId}-trigger-${index}`;
|
|
42
|
-
}
|
|
43
|
-
if (!panel.id) {
|
|
44
|
-
panel.id = `${accordionId}-panel-${index}`;
|
|
45
|
-
}
|
|
46
|
-
trigger.setAttribute("aria-controls", panel.id);
|
|
47
|
-
trigger.setAttribute("aria-expanded", "false");
|
|
48
|
-
if (!allowMultipleOpen || triggers.length <= 6) {
|
|
49
|
-
panel.setAttribute("role", "region");
|
|
50
|
-
}
|
|
51
|
-
panel.setAttribute("aria-labelledby", trigger.id);
|
|
52
|
-
panel.style.display = "none";
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function expandItem(index) {
|
|
56
|
-
if (index < 0 || index >= triggers.length) {
|
|
57
|
-
console.error(`[aria-ease] Invalid accordion index: ${index}`);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const trigger = triggers[index];
|
|
61
|
-
const panel = panels[index];
|
|
62
|
-
trigger.setAttribute("aria-expanded", "true");
|
|
63
|
-
panel.style.display = "block";
|
|
64
|
-
if (callback?.onExpand) {
|
|
65
|
-
try {
|
|
66
|
-
callback.onExpand(index);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error("[aria-ease] Error in accordion onExpand callback:", error);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function collapseItem(index) {
|
|
73
|
-
if (index < 0 || index >= triggers.length) {
|
|
74
|
-
console.error(`[aria-ease] Invalid accordion index: ${index}`);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const trigger = triggers[index];
|
|
78
|
-
const panel = panels[index];
|
|
79
|
-
trigger.setAttribute("aria-expanded", "false");
|
|
80
|
-
panel.style.display = "none";
|
|
81
|
-
if (callback?.onCollapse) {
|
|
82
|
-
try {
|
|
83
|
-
callback.onCollapse(index);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.error("[aria-ease] Error in accordion onCollapse callback:", error);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
function toggleItem(index) {
|
|
90
|
-
const trigger = triggers[index];
|
|
91
|
-
const isExpanded = trigger.getAttribute("aria-expanded") === "true";
|
|
92
|
-
if (isExpanded) {
|
|
93
|
-
collapseItem(index);
|
|
94
|
-
} else {
|
|
95
|
-
if (!allowMultipleOpen) {
|
|
96
|
-
triggers.forEach((_, i) => {
|
|
97
|
-
if (i !== index) {
|
|
98
|
-
collapseItem(i);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
expandItem(index);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function handleTriggerClick(index) {
|
|
106
|
-
return () => {
|
|
107
|
-
toggleItem(index);
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function handleTriggerKeydown(index) {
|
|
111
|
-
return (event) => {
|
|
112
|
-
const { key } = event;
|
|
113
|
-
switch (key) {
|
|
114
|
-
case "Enter":
|
|
115
|
-
case " ":
|
|
116
|
-
event.preventDefault();
|
|
117
|
-
toggleItem(index);
|
|
118
|
-
break;
|
|
119
|
-
case "ArrowDown":
|
|
120
|
-
event.preventDefault();
|
|
121
|
-
{
|
|
122
|
-
const nextIndex = (index + 1) % triggers.length;
|
|
123
|
-
triggers[nextIndex].focus();
|
|
124
|
-
}
|
|
125
|
-
break;
|
|
126
|
-
case "ArrowUp":
|
|
127
|
-
event.preventDefault();
|
|
128
|
-
{
|
|
129
|
-
const prevIndex = (index - 1 + triggers.length) % triggers.length;
|
|
130
|
-
triggers[prevIndex].focus();
|
|
131
|
-
}
|
|
132
|
-
break;
|
|
133
|
-
case "Home":
|
|
134
|
-
event.preventDefault();
|
|
135
|
-
triggers[0].focus();
|
|
136
|
-
break;
|
|
137
|
-
case "End":
|
|
138
|
-
event.preventDefault();
|
|
139
|
-
triggers[triggers.length - 1].focus();
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
function addListeners() {
|
|
145
|
-
triggers.forEach((trigger, index) => {
|
|
146
|
-
const clickHandler = handleTriggerClick(index);
|
|
147
|
-
const keydownHandler = handleTriggerKeydown(index);
|
|
148
|
-
trigger.addEventListener("click", clickHandler);
|
|
149
|
-
trigger.addEventListener("keydown", keydownHandler);
|
|
150
|
-
handlerMap.set(trigger, keydownHandler);
|
|
151
|
-
clickHandlerMap.set(trigger, clickHandler);
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
function removeListeners() {
|
|
155
|
-
triggers.forEach((trigger) => {
|
|
156
|
-
const keydownHandler = handlerMap.get(trigger);
|
|
157
|
-
const clickHandler = clickHandlerMap.get(trigger);
|
|
158
|
-
if (keydownHandler) {
|
|
159
|
-
trigger.removeEventListener("keydown", keydownHandler);
|
|
160
|
-
handlerMap.delete(trigger);
|
|
161
|
-
}
|
|
162
|
-
if (clickHandler) {
|
|
163
|
-
trigger.removeEventListener("click", clickHandler);
|
|
164
|
-
clickHandlerMap.delete(trigger);
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
function cleanup() {
|
|
169
|
-
removeListeners();
|
|
170
|
-
triggers.forEach((_, index) => {
|
|
171
|
-
collapseItem(index);
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
function refresh() {
|
|
175
|
-
removeListeners();
|
|
176
|
-
const newTriggers = Array.from(accordionContainer.querySelectorAll(`.${triggersClass}`));
|
|
177
|
-
const newPanels = Array.from(accordionContainer.querySelectorAll(`.${panelsClass}`));
|
|
178
|
-
triggers.length = 0;
|
|
179
|
-
triggers.push(...newTriggers);
|
|
180
|
-
panels.length = 0;
|
|
181
|
-
panels.push(...newPanels);
|
|
182
|
-
initialize();
|
|
183
|
-
addListeners();
|
|
184
|
-
}
|
|
185
|
-
initialize();
|
|
186
|
-
addListeners();
|
|
187
|
-
return { expandItem, collapseItem, toggleItem, cleanup, refresh };
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// src/utils/handleKeyPress/handleKeyPress.ts
|
|
191
|
-
function isTextInput(el) {
|
|
192
|
-
if (el.tagName !== "INPUT") return false;
|
|
193
|
-
const type = el.type;
|
|
194
|
-
return ["text", "email", "password", "tel", "number"].includes(type);
|
|
195
|
-
}
|
|
196
|
-
function isTextArea(el) {
|
|
197
|
-
return el.tagName === "TEXTAREA";
|
|
198
|
-
}
|
|
199
|
-
function isNativeButton(el) {
|
|
200
|
-
return el.tagName === "BUTTON" || el.tagName === "INPUT" && ["button", "submit", "reset"].includes(el.type);
|
|
201
|
-
}
|
|
202
|
-
function isLink(el) {
|
|
203
|
-
return el.tagName === "A";
|
|
204
|
-
}
|
|
205
|
-
function moveFocus(elementItems, currentIndex, direction) {
|
|
206
|
-
const len = elementItems.length;
|
|
207
|
-
const nextIndex = (currentIndex + direction + len) % len;
|
|
208
|
-
elementItems.item(nextIndex).focus();
|
|
209
|
-
}
|
|
210
|
-
function isClickableButNotSemantic(el) {
|
|
211
|
-
return el.getAttribute("data-custom-click") !== null && el.getAttribute("data-custom-click") !== void 0;
|
|
212
|
-
}
|
|
213
|
-
function handleKeyPress(event, elementItems, elementItemIndex) {
|
|
214
|
-
const currentEl = elementItems.item(elementItemIndex);
|
|
215
|
-
switch (event.key) {
|
|
216
|
-
case "ArrowUp":
|
|
217
|
-
case "ArrowLeft": {
|
|
218
|
-
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
219
|
-
event.preventDefault();
|
|
220
|
-
moveFocus(elementItems, elementItemIndex, -1);
|
|
221
|
-
} else if (isTextInput(currentEl) || isTextArea(currentEl)) {
|
|
222
|
-
const cursorStart = currentEl.selectionStart;
|
|
223
|
-
if (cursorStart === 0) {
|
|
224
|
-
event.preventDefault();
|
|
225
|
-
moveFocus(elementItems, elementItemIndex, -1);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
case "ArrowDown":
|
|
231
|
-
case "ArrowRight": {
|
|
232
|
-
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
233
|
-
event.preventDefault();
|
|
234
|
-
moveFocus(elementItems, elementItemIndex, 1);
|
|
235
|
-
} else if (isTextInput(currentEl) || isTextArea(currentEl)) {
|
|
236
|
-
const value = currentEl.value;
|
|
237
|
-
const cursorEnd = currentEl.selectionStart;
|
|
238
|
-
if (cursorEnd === value.length) {
|
|
239
|
-
event.preventDefault();
|
|
240
|
-
moveFocus(elementItems, elementItemIndex, 1);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
case "Escape": {
|
|
246
|
-
event.preventDefault();
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
case "Enter":
|
|
250
|
-
case " ": {
|
|
251
|
-
if (!isNativeButton(currentEl) && !isLink(currentEl) && isClickableButNotSemantic(currentEl)) {
|
|
252
|
-
event.preventDefault();
|
|
253
|
-
currentEl.click();
|
|
254
|
-
} else if (isNativeButton(currentEl)) {
|
|
255
|
-
event.preventDefault();
|
|
256
|
-
currentEl.click();
|
|
257
|
-
}
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
case "Tab": {
|
|
261
|
-
break;
|
|
262
|
-
}
|
|
263
|
-
default:
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// src/block/src/makeBlockAccessible/makeBlockAccessible.ts
|
|
269
|
-
function makeBlockAccessible({ blockId, blockItemsClass }) {
|
|
270
|
-
const blockDiv = document.querySelector(`#${blockId}`);
|
|
271
|
-
if (!blockDiv) {
|
|
272
|
-
console.error(`[aria-ease] Element with id="${blockId}" not found. Make sure the block element exists before calling makeBlockAccessible.`);
|
|
273
|
-
return { cleanup: () => {
|
|
274
|
-
} };
|
|
275
|
-
}
|
|
276
|
-
let cachedItems = null;
|
|
277
|
-
function getItems() {
|
|
278
|
-
if (!cachedItems) {
|
|
279
|
-
cachedItems = blockDiv.querySelectorAll(`.${blockItemsClass}`);
|
|
280
|
-
}
|
|
281
|
-
return cachedItems;
|
|
282
|
-
}
|
|
283
|
-
const blockItems = getItems();
|
|
284
|
-
if (!blockItems || blockItems.length === 0) {
|
|
285
|
-
console.error(`[aria-ease] Element with class="${blockItemsClass}" not found. Make sure the block items exist before calling makeBlockAccessible.`);
|
|
286
|
-
return { cleanup: () => {
|
|
287
|
-
} };
|
|
288
|
-
}
|
|
289
|
-
const eventListenersMap = /* @__PURE__ */ new Map();
|
|
290
|
-
blockItems.forEach((blockItem) => {
|
|
291
|
-
if (!eventListenersMap.has(blockItem)) {
|
|
292
|
-
const handler = (event) => {
|
|
293
|
-
const items = blockDiv.querySelectorAll(`.${blockItemsClass}`);
|
|
294
|
-
const index = Array.prototype.indexOf.call(items, blockItem);
|
|
295
|
-
handleKeyPress(event, items, index);
|
|
296
|
-
};
|
|
297
|
-
blockItem.addEventListener("keydown", handler);
|
|
298
|
-
eventListenersMap.set(blockItem, handler);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
function cleanup() {
|
|
302
|
-
blockItems.forEach((blockItem) => {
|
|
303
|
-
const handler = eventListenersMap.get(blockItem);
|
|
304
|
-
if (handler) {
|
|
305
|
-
blockItem.removeEventListener("keydown", handler);
|
|
306
|
-
eventListenersMap.delete(blockItem);
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
function refresh() {
|
|
311
|
-
cachedItems = null;
|
|
312
|
-
}
|
|
313
|
-
return { cleanup, refresh };
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// src/checkbox/src/makeCheckboxAccessible/makeCheckboxAccessible.ts
|
|
317
|
-
function makeCheckboxAccessible({ checkboxGroupId, checkboxesClass }) {
|
|
318
|
-
const checkboxGroup = document.querySelector(`#${checkboxGroupId}`);
|
|
319
|
-
if (!checkboxGroup) {
|
|
320
|
-
console.error(`[aria-ease] Element with id="${checkboxGroupId}" not found. Make sure the checkbox group container exists before calling makeCheckboxAccessible.`);
|
|
321
|
-
return { cleanup: () => {
|
|
322
|
-
} };
|
|
323
|
-
}
|
|
324
|
-
const checkboxes = Array.from(checkboxGroup.querySelectorAll(`.${checkboxesClass}`));
|
|
325
|
-
if (checkboxes.length === 0) {
|
|
326
|
-
console.error(`[aria-ease] No elements with class="${checkboxesClass}" found. Make sure checkboxes exist before calling makeCheckboxAccessible.`);
|
|
327
|
-
return { cleanup: () => {
|
|
328
|
-
} };
|
|
329
|
-
}
|
|
330
|
-
const handlerMap = /* @__PURE__ */ new WeakMap();
|
|
331
|
-
const clickHandlerMap = /* @__PURE__ */ new WeakMap();
|
|
332
|
-
function initialize() {
|
|
333
|
-
if (!checkboxGroup.getAttribute("role")) {
|
|
334
|
-
checkboxGroup.setAttribute("role", "group");
|
|
335
|
-
}
|
|
336
|
-
checkboxes.forEach((checkbox) => {
|
|
337
|
-
checkbox.setAttribute("role", "checkbox");
|
|
338
|
-
if (!checkbox.hasAttribute("aria-checked")) {
|
|
339
|
-
checkbox.setAttribute("aria-checked", "false");
|
|
340
|
-
}
|
|
341
|
-
if (!checkbox.hasAttribute("tabindex")) {
|
|
342
|
-
checkbox.setAttribute("tabindex", "0");
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
function toggleCheckbox(index) {
|
|
347
|
-
if (index < 0 || index >= checkboxes.length) {
|
|
348
|
-
console.error(`[aria-ease] Invalid checkbox index: ${index}`);
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
const checkbox = checkboxes[index];
|
|
352
|
-
const isChecked = checkbox.getAttribute("aria-checked") === "true";
|
|
353
|
-
checkbox.setAttribute("aria-checked", isChecked ? "false" : "true");
|
|
354
|
-
}
|
|
355
|
-
function setCheckboxState(index, checked) {
|
|
356
|
-
if (index < 0 || index >= checkboxes.length) {
|
|
357
|
-
console.error(`[aria-ease] Invalid checkbox index: ${index}`);
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
checkboxes[index].setAttribute("aria-checked", checked ? "true" : "false");
|
|
361
|
-
}
|
|
362
|
-
function handleCheckboxClick(index) {
|
|
363
|
-
return () => {
|
|
364
|
-
toggleCheckbox(index);
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
function handleCheckboxKeydown(index) {
|
|
368
|
-
return (event) => {
|
|
369
|
-
const { key } = event;
|
|
370
|
-
switch (key) {
|
|
371
|
-
case " ":
|
|
372
|
-
event.preventDefault();
|
|
373
|
-
toggleCheckbox(index);
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
function addListeners() {
|
|
379
|
-
checkboxes.forEach((checkbox, index) => {
|
|
380
|
-
const clickHandler = handleCheckboxClick(index);
|
|
381
|
-
const keydownHandler = handleCheckboxKeydown(index);
|
|
382
|
-
checkbox.addEventListener("click", clickHandler);
|
|
383
|
-
checkbox.addEventListener("keydown", keydownHandler);
|
|
384
|
-
handlerMap.set(checkbox, keydownHandler);
|
|
385
|
-
clickHandlerMap.set(checkbox, clickHandler);
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
function removeListeners() {
|
|
389
|
-
checkboxes.forEach((checkbox) => {
|
|
390
|
-
const keydownHandler = handlerMap.get(checkbox);
|
|
391
|
-
const clickHandler = clickHandlerMap.get(checkbox);
|
|
392
|
-
if (keydownHandler) {
|
|
393
|
-
checkbox.removeEventListener("keydown", keydownHandler);
|
|
394
|
-
handlerMap.delete(checkbox);
|
|
395
|
-
}
|
|
396
|
-
if (clickHandler) {
|
|
397
|
-
checkbox.removeEventListener("click", clickHandler);
|
|
398
|
-
clickHandlerMap.delete(checkbox);
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
function cleanup() {
|
|
403
|
-
removeListeners();
|
|
404
|
-
}
|
|
405
|
-
function getCheckedStates() {
|
|
406
|
-
return checkboxes.map((checkbox) => checkbox.getAttribute("aria-checked") === "true");
|
|
407
|
-
}
|
|
408
|
-
function getCheckedIndices() {
|
|
409
|
-
return checkboxes.map((checkbox, index) => checkbox.getAttribute("aria-checked") === "true" ? index : -1).filter((index) => index !== -1);
|
|
410
|
-
}
|
|
411
|
-
initialize();
|
|
412
|
-
addListeners();
|
|
413
|
-
return {
|
|
414
|
-
toggleCheckbox,
|
|
415
|
-
setCheckboxState,
|
|
416
|
-
getCheckedStates,
|
|
417
|
-
getCheckedIndices,
|
|
418
|
-
cleanup
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// src/menu/src/makeMenuAccessible/makeMenuAccessible.ts
|
|
423
|
-
function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
424
|
-
const menuDiv = document.querySelector(`#${menuId}`);
|
|
425
|
-
if (!menuDiv) {
|
|
426
|
-
console.error(`[aria-ease] Element with id="${menuId}" not found. Make sure the menu element exists before calling makeMenuAccessible.`);
|
|
427
|
-
return { openMenu: () => {
|
|
428
|
-
}, closeMenu: () => {
|
|
429
|
-
}, cleanup: () => {
|
|
430
|
-
} };
|
|
431
|
-
}
|
|
432
|
-
const triggerButton = document.querySelector(`#${triggerId}`);
|
|
433
|
-
if (!triggerButton) {
|
|
434
|
-
console.error(`[aria-ease] Element with id="${triggerId}" not found. Make sure the trigger button element exists before calling makeMenuAccessible.`);
|
|
435
|
-
return { openMenu: () => {
|
|
436
|
-
}, closeMenu: () => {
|
|
437
|
-
}, cleanup: () => {
|
|
438
|
-
} };
|
|
439
|
-
}
|
|
440
|
-
if (!/^[\w-]+$/.test(menuId)) {
|
|
441
|
-
console.error("[aria-ease] Invalid menuId: must be alphanumeric");
|
|
442
|
-
return { openMenu: () => {
|
|
443
|
-
}, closeMenu: () => {
|
|
444
|
-
}, cleanup: () => {
|
|
445
|
-
} };
|
|
446
|
-
}
|
|
447
|
-
triggerButton.setAttribute("aria-haspopup", "true");
|
|
448
|
-
triggerButton.setAttribute("aria-controls", menuId);
|
|
449
|
-
triggerButton.setAttribute("aria-expanded", "false");
|
|
450
|
-
menuDiv.setAttribute("role", "menu");
|
|
451
|
-
menuDiv.setAttribute("aria-labelledby", triggerId);
|
|
452
|
-
const handlerMap = /* @__PURE__ */ new WeakMap();
|
|
453
|
-
const submenuInstances = /* @__PURE__ */ new Map();
|
|
454
|
-
let cachedItems = null;
|
|
455
|
-
let filteredItems = null;
|
|
456
|
-
function getItems() {
|
|
457
|
-
if (!cachedItems) {
|
|
458
|
-
cachedItems = menuDiv.querySelectorAll(`.${menuItemsClass}`);
|
|
459
|
-
}
|
|
460
|
-
return cachedItems;
|
|
461
|
-
}
|
|
462
|
-
function getFilteredItems() {
|
|
463
|
-
if (!filteredItems) {
|
|
464
|
-
const allItems = getItems();
|
|
465
|
-
filteredItems = [];
|
|
466
|
-
for (let i = 0; i < allItems.length; i++) {
|
|
467
|
-
const item = allItems.item(i);
|
|
468
|
-
const isNested = isItemInNestedSubmenu(item);
|
|
469
|
-
const isDisabled = item.getAttribute("aria-disabled") === "true";
|
|
470
|
-
if (!isNested) {
|
|
471
|
-
if (!item.hasAttribute("tabindex")) {
|
|
472
|
-
item.setAttribute("tabindex", "-1");
|
|
473
|
-
}
|
|
474
|
-
if (!isDisabled) {
|
|
475
|
-
filteredItems.push(item);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
return filteredItems;
|
|
481
|
-
}
|
|
482
|
-
function toNodeListLike(items) {
|
|
483
|
-
const nodeListLike = {
|
|
484
|
-
length: items.length,
|
|
485
|
-
item: (index) => items[index],
|
|
486
|
-
forEach: (callback2) => {
|
|
487
|
-
items.forEach(callback2);
|
|
488
|
-
},
|
|
489
|
-
[Symbol.iterator]: function* () {
|
|
490
|
-
for (const item of items) {
|
|
491
|
-
yield item;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
};
|
|
495
|
-
return nodeListLike;
|
|
496
|
-
}
|
|
497
|
-
function intializeMenuItems() {
|
|
498
|
-
const items = getItems();
|
|
499
|
-
items.forEach((item) => {
|
|
500
|
-
item.setAttribute("role", "menuitem");
|
|
501
|
-
const submenuId = item.getAttribute("data-submenu-id") ?? item.getAttribute("aria-controls");
|
|
502
|
-
const hasSubmenuTriggerAttributes = item.hasAttribute("aria-haspopup") && submenuId;
|
|
503
|
-
if (submenuId && (item.hasAttribute("data-submenu-id") || hasSubmenuTriggerAttributes)) {
|
|
504
|
-
item.setAttribute("aria-haspopup", "menu");
|
|
505
|
-
item.setAttribute("aria-controls", submenuId);
|
|
506
|
-
if (!item.hasAttribute("aria-expanded")) {
|
|
507
|
-
item.setAttribute("aria-expanded", "false");
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
function moveFocus2(elementItems, currentIndex, direction) {
|
|
513
|
-
const len = elementItems.length;
|
|
514
|
-
const nextIndex = (currentIndex + direction + len) % len;
|
|
515
|
-
elementItems.item(nextIndex).focus();
|
|
516
|
-
}
|
|
517
|
-
function focusItemAtIndex(items, index) {
|
|
518
|
-
if (items.length === 0) return;
|
|
519
|
-
items[index]?.focus();
|
|
520
|
-
}
|
|
521
|
-
function hasSubmenu(menuItem) {
|
|
522
|
-
return menuItem.hasAttribute("aria-controls") && menuItem.hasAttribute("aria-haspopup") && menuItem.getAttribute("role") === "menuitem";
|
|
523
|
-
}
|
|
524
|
-
function closeAncestorMenusFromTrigger(triggerEl) {
|
|
525
|
-
let currentTrigger = triggerEl;
|
|
526
|
-
while (currentTrigger && currentTrigger.getAttribute("role") === "menuitem") {
|
|
527
|
-
const parentMenu = currentTrigger.closest('[role="menu"]');
|
|
528
|
-
if (!parentMenu) break;
|
|
529
|
-
parentMenu.style.display = "none";
|
|
530
|
-
currentTrigger.setAttribute("aria-expanded", "false");
|
|
531
|
-
const parentTriggerId = parentMenu.getAttribute("aria-labelledby");
|
|
532
|
-
if (!parentTriggerId) break;
|
|
533
|
-
const nextTrigger = document.getElementById(parentTriggerId);
|
|
534
|
-
if (!nextTrigger) break;
|
|
535
|
-
currentTrigger = nextTrigger;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
intializeMenuItems();
|
|
539
|
-
function handleItemsKeydown(event, menuItem, menuItemIndex) {
|
|
540
|
-
switch (event.key) {
|
|
541
|
-
case "ArrowLeft": {
|
|
542
|
-
if (event.key === "ArrowLeft" && triggerButton.getAttribute("role") === "menuitem") {
|
|
543
|
-
event.preventDefault();
|
|
544
|
-
closeMenu();
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
break;
|
|
548
|
-
}
|
|
549
|
-
case "ArrowUp": {
|
|
550
|
-
event.preventDefault();
|
|
551
|
-
moveFocus2(toNodeListLike(getFilteredItems()), menuItemIndex, -1);
|
|
552
|
-
break;
|
|
553
|
-
}
|
|
554
|
-
case "ArrowRight": {
|
|
555
|
-
if (event.key === "ArrowRight" && hasSubmenu(menuItem)) {
|
|
556
|
-
event.preventDefault();
|
|
557
|
-
const submenuId = menuItem.getAttribute("aria-controls");
|
|
558
|
-
if (submenuId) {
|
|
559
|
-
openSubmenu(submenuId);
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
break;
|
|
564
|
-
}
|
|
565
|
-
case "ArrowDown": {
|
|
566
|
-
event.preventDefault();
|
|
567
|
-
moveFocus2(toNodeListLike(getFilteredItems()), menuItemIndex, 1);
|
|
568
|
-
break;
|
|
569
|
-
}
|
|
570
|
-
case "Home": {
|
|
571
|
-
event.preventDefault();
|
|
572
|
-
focusItemAtIndex(getFilteredItems(), 0);
|
|
573
|
-
break;
|
|
574
|
-
}
|
|
575
|
-
case "End": {
|
|
576
|
-
event.preventDefault();
|
|
577
|
-
const items = getFilteredItems();
|
|
578
|
-
focusItemAtIndex(items, items.length - 1);
|
|
579
|
-
break;
|
|
580
|
-
}
|
|
581
|
-
case "Escape": {
|
|
582
|
-
event.preventDefault();
|
|
583
|
-
closeMenu();
|
|
584
|
-
triggerButton.focus();
|
|
585
|
-
if (onOpenChange) {
|
|
586
|
-
onOpenChange(false);
|
|
587
|
-
}
|
|
588
|
-
break;
|
|
589
|
-
}
|
|
590
|
-
case "Enter":
|
|
591
|
-
case " ": {
|
|
592
|
-
event.preventDefault();
|
|
593
|
-
if (hasSubmenu(menuItem)) {
|
|
594
|
-
const submenuId = menuItem.getAttribute("aria-controls");
|
|
595
|
-
if (submenuId) {
|
|
596
|
-
openSubmenu(submenuId);
|
|
597
|
-
return;
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
menuItem.click();
|
|
601
|
-
closeMenu();
|
|
602
|
-
if (onOpenChange) {
|
|
603
|
-
onOpenChange(false);
|
|
604
|
-
}
|
|
605
|
-
break;
|
|
606
|
-
}
|
|
607
|
-
case "Tab": {
|
|
608
|
-
closeMenu();
|
|
609
|
-
closeAncestorMenusFromTrigger(triggerButton);
|
|
610
|
-
if (onOpenChange) {
|
|
611
|
-
onOpenChange(false);
|
|
612
|
-
}
|
|
613
|
-
break;
|
|
614
|
-
}
|
|
615
|
-
default:
|
|
616
|
-
break;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
function isItemInNestedSubmenu(item) {
|
|
620
|
-
let parent = item.parentElement;
|
|
621
|
-
while (parent && parent !== menuDiv) {
|
|
622
|
-
if (parent.getAttribute("role") === "menu") {
|
|
623
|
-
return true;
|
|
624
|
-
}
|
|
625
|
-
if (parent.id) {
|
|
626
|
-
const parentMenuTrigger = menuDiv.querySelector(`[aria-controls="${parent.id}"]`);
|
|
627
|
-
if (parentMenuTrigger) {
|
|
628
|
-
return true;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
parent = parent.parentElement;
|
|
632
|
-
}
|
|
633
|
-
return false;
|
|
634
|
-
}
|
|
635
|
-
function setAria(isOpen) {
|
|
636
|
-
triggerButton.setAttribute("aria-expanded", isOpen ? "true" : "false");
|
|
637
|
-
}
|
|
638
|
-
function openSubmenu(submenuId) {
|
|
639
|
-
let submenuInstance = submenuInstances.get(submenuId);
|
|
640
|
-
if (!submenuInstance) {
|
|
641
|
-
const submenuTrigger = menuDiv.querySelector(`[aria-controls="${submenuId}"]`);
|
|
642
|
-
if (!submenuTrigger) {
|
|
643
|
-
console.error(`[aria-ease] Submenu trigger with aria-controls="${submenuId}" not found in menu "${menuId}".`);
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
if (!submenuTrigger.id) {
|
|
647
|
-
const generatedId = `trigger-${submenuId}`;
|
|
648
|
-
submenuTrigger.id = generatedId;
|
|
649
|
-
console.warn(`[aria-ease] Submenu trigger for "${submenuId}" had no ID. Auto-generated ID: "${generatedId}".`);
|
|
650
|
-
}
|
|
651
|
-
const submenuElement = document.querySelector(`#${submenuId}`);
|
|
652
|
-
if (!submenuElement) {
|
|
653
|
-
console.error(`[aria-ease] Submenu element with id="${submenuId}" not found. Cannot create submenu instance.`);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
submenuInstance = makeMenuAccessible({
|
|
657
|
-
menuId: submenuId,
|
|
658
|
-
menuItemsClass,
|
|
659
|
-
triggerId: submenuTrigger.id,
|
|
660
|
-
callback
|
|
661
|
-
});
|
|
662
|
-
submenuInstances.set(submenuId, submenuInstance);
|
|
663
|
-
}
|
|
664
|
-
submenuInstance.openMenu();
|
|
665
|
-
}
|
|
666
|
-
function onOpenChange(isOpen) {
|
|
667
|
-
if (callback?.onOpenChange) {
|
|
668
|
-
try {
|
|
669
|
-
callback.onOpenChange(isOpen);
|
|
670
|
-
} catch (error) {
|
|
671
|
-
console.error("[aria-ease] Error in menu onOpenChange callback:", error);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
function addListeners() {
|
|
676
|
-
const items = getFilteredItems();
|
|
677
|
-
items.forEach((menuItem, index) => {
|
|
678
|
-
if (!handlerMap.has(menuItem)) {
|
|
679
|
-
const handler = (event) => handleItemsKeydown(event, menuItem, index);
|
|
680
|
-
menuItem.addEventListener("keydown", handler);
|
|
681
|
-
handlerMap.set(menuItem, handler);
|
|
682
|
-
}
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
function removeListeners() {
|
|
686
|
-
const items = getFilteredItems();
|
|
687
|
-
items.forEach((menuItem) => {
|
|
688
|
-
const handler = handlerMap.get(menuItem);
|
|
689
|
-
if (handler) {
|
|
690
|
-
menuItem.removeEventListener("keydown", handler);
|
|
691
|
-
handlerMap.delete(menuItem);
|
|
692
|
-
}
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
function openMenu() {
|
|
696
|
-
setAria(true);
|
|
697
|
-
menuDiv.style.display = "block";
|
|
698
|
-
const items = getFilteredItems();
|
|
699
|
-
addListeners();
|
|
700
|
-
if (items && items.length > 0) {
|
|
701
|
-
items[0].focus();
|
|
702
|
-
}
|
|
703
|
-
if (callback?.onOpenChange) {
|
|
704
|
-
try {
|
|
705
|
-
callback.onOpenChange(true);
|
|
706
|
-
} catch (error) {
|
|
707
|
-
console.error("[aria-ease] Error in menu onOpenChange callback:", error);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
function closeMenu() {
|
|
712
|
-
submenuInstances.forEach((instance) => instance.closeMenu());
|
|
713
|
-
setAria(false);
|
|
714
|
-
menuDiv.style.display = "none";
|
|
715
|
-
removeListeners();
|
|
716
|
-
triggerButton.focus();
|
|
717
|
-
if (callback?.onOpenChange) {
|
|
718
|
-
try {
|
|
719
|
-
callback.onOpenChange(false);
|
|
720
|
-
} catch (error) {
|
|
721
|
-
console.error("[aria-ease] Error in menu onOpenChange callback:", error);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
function handleTriggerClick() {
|
|
726
|
-
const isOpen = triggerButton.getAttribute("aria-expanded") === "true";
|
|
727
|
-
if (isOpen) {
|
|
728
|
-
closeMenu();
|
|
729
|
-
} else {
|
|
730
|
-
openMenu();
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
function handleClickOutside(event) {
|
|
734
|
-
const isMenuOpen = triggerButton.getAttribute("aria-expanded") === "true";
|
|
735
|
-
if (!isMenuOpen) return;
|
|
736
|
-
const clickedTrigger = triggerButton.contains(event.target);
|
|
737
|
-
const clickedMenu = menuDiv.contains(event.target);
|
|
738
|
-
if (!clickedTrigger && !clickedMenu) {
|
|
739
|
-
closeMenu();
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
triggerButton.addEventListener("click", handleTriggerClick);
|
|
743
|
-
document.addEventListener("click", handleClickOutside);
|
|
744
|
-
function cleanup() {
|
|
745
|
-
removeListeners();
|
|
746
|
-
triggerButton.removeEventListener("click", handleTriggerClick);
|
|
747
|
-
document.removeEventListener("click", handleClickOutside);
|
|
748
|
-
menuDiv.style.display = "none";
|
|
749
|
-
setAria(false);
|
|
750
|
-
submenuInstances.forEach((instance) => instance.cleanup());
|
|
751
|
-
submenuInstances.clear();
|
|
752
|
-
}
|
|
753
|
-
function refresh() {
|
|
754
|
-
cachedItems = null;
|
|
755
|
-
filteredItems = null;
|
|
756
|
-
}
|
|
757
|
-
return { openMenu, closeMenu, cleanup, refresh };
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// src/radio/src/makeRadioAccessible/makeRadioAccessible.ts
|
|
761
|
-
function makeRadioAccessible({ radioGroupId, radiosClass, defaultSelectedIndex = 0 }) {
|
|
762
|
-
const radioGroup = document.querySelector(`#${radioGroupId}`);
|
|
763
|
-
if (!radioGroup) {
|
|
764
|
-
console.error(`[aria-ease] Element with id="${radioGroupId}" not found. Make sure the radio group container exists before calling makeRadioAccessible.`);
|
|
765
|
-
return { cleanup: () => {
|
|
766
|
-
} };
|
|
767
|
-
}
|
|
768
|
-
const radios = Array.from(radioGroup.querySelectorAll(`.${radiosClass}`));
|
|
769
|
-
if (radios.length === 0) {
|
|
770
|
-
console.error(`[aria-ease] No elements with class="${radiosClass}" found. Make sure radio buttons exist before calling makeRadioAccessible.`);
|
|
771
|
-
return { cleanup: () => {
|
|
772
|
-
} };
|
|
773
|
-
}
|
|
774
|
-
const handlerMap = /* @__PURE__ */ new WeakMap();
|
|
775
|
-
const clickHandlerMap = /* @__PURE__ */ new WeakMap();
|
|
776
|
-
let currentSelectedIndex = defaultSelectedIndex;
|
|
777
|
-
function initialize() {
|
|
778
|
-
if (!radioGroup.getAttribute("role")) {
|
|
779
|
-
radioGroup.setAttribute("role", "radiogroup");
|
|
780
|
-
}
|
|
781
|
-
radios.forEach((radio, index) => {
|
|
782
|
-
radio.setAttribute("role", "radio");
|
|
783
|
-
radio.setAttribute("tabindex", index === currentSelectedIndex ? "0" : "-1");
|
|
784
|
-
if (index === currentSelectedIndex) {
|
|
785
|
-
radio.setAttribute("aria-checked", "true");
|
|
786
|
-
} else {
|
|
787
|
-
radio.setAttribute("aria-checked", "false");
|
|
788
|
-
}
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
function selectRadio(index) {
|
|
792
|
-
if (index < 0 || index >= radios.length) {
|
|
793
|
-
console.error(`[aria-ease] Invalid radio index: ${index}`);
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
if (currentSelectedIndex >= 0 && currentSelectedIndex < radios.length) {
|
|
797
|
-
radios[currentSelectedIndex].setAttribute("aria-checked", "false");
|
|
798
|
-
radios[currentSelectedIndex].setAttribute("tabindex", "-1");
|
|
799
|
-
}
|
|
800
|
-
radios[index].setAttribute("aria-checked", "true");
|
|
801
|
-
radios[index].setAttribute("tabindex", "0");
|
|
802
|
-
radios[index].focus();
|
|
803
|
-
currentSelectedIndex = index;
|
|
804
|
-
}
|
|
805
|
-
function handleRadioClick(index) {
|
|
806
|
-
return () => {
|
|
807
|
-
selectRadio(index);
|
|
808
|
-
};
|
|
809
|
-
}
|
|
810
|
-
function handleRadioKeydown(index) {
|
|
811
|
-
return (event) => {
|
|
812
|
-
const { key } = event;
|
|
813
|
-
let nextIndex = index;
|
|
814
|
-
switch (key) {
|
|
815
|
-
case "ArrowDown":
|
|
816
|
-
case "ArrowRight":
|
|
817
|
-
event.preventDefault();
|
|
818
|
-
nextIndex = (index + 1) % radios.length;
|
|
819
|
-
selectRadio(nextIndex);
|
|
820
|
-
break;
|
|
821
|
-
case "ArrowUp":
|
|
822
|
-
case "ArrowLeft":
|
|
823
|
-
event.preventDefault();
|
|
824
|
-
nextIndex = (index - 1 + radios.length) % radios.length;
|
|
825
|
-
selectRadio(nextIndex);
|
|
826
|
-
break;
|
|
827
|
-
case " ":
|
|
828
|
-
case "Enter":
|
|
829
|
-
event.preventDefault();
|
|
830
|
-
selectRadio(index);
|
|
831
|
-
break;
|
|
832
|
-
}
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
function addListeners() {
|
|
836
|
-
radios.forEach((radio, index) => {
|
|
837
|
-
const clickHandler = handleRadioClick(index);
|
|
838
|
-
const keydownHandler = handleRadioKeydown(index);
|
|
839
|
-
radio.addEventListener("click", clickHandler);
|
|
840
|
-
radio.addEventListener("keydown", keydownHandler);
|
|
841
|
-
handlerMap.set(radio, keydownHandler);
|
|
842
|
-
clickHandlerMap.set(radio, clickHandler);
|
|
843
|
-
});
|
|
844
|
-
}
|
|
845
|
-
function removeListeners() {
|
|
846
|
-
radios.forEach((radio) => {
|
|
847
|
-
const keydownHandler = handlerMap.get(radio);
|
|
848
|
-
const clickHandler = clickHandlerMap.get(radio);
|
|
849
|
-
if (keydownHandler) {
|
|
850
|
-
radio.removeEventListener("keydown", keydownHandler);
|
|
851
|
-
handlerMap.delete(radio);
|
|
852
|
-
}
|
|
853
|
-
if (clickHandler) {
|
|
854
|
-
radio.removeEventListener("click", clickHandler);
|
|
855
|
-
clickHandlerMap.delete(radio);
|
|
856
|
-
}
|
|
857
|
-
});
|
|
858
|
-
}
|
|
859
|
-
function cleanup() {
|
|
860
|
-
removeListeners();
|
|
861
|
-
}
|
|
862
|
-
function getSelectedIndex() {
|
|
863
|
-
return currentSelectedIndex;
|
|
864
|
-
}
|
|
865
|
-
initialize();
|
|
866
|
-
addListeners();
|
|
867
|
-
return {
|
|
868
|
-
selectRadio,
|
|
869
|
-
getSelectedIndex,
|
|
870
|
-
cleanup
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
// src/toggle/src/makeTogggleAccessible/makeToggleAccessible.ts
|
|
875
|
-
function makeToggleAccessible({ toggleId, togglesClass, isSingleToggle = true }) {
|
|
876
|
-
const toggleContainer = document.querySelector(`#${toggleId}`);
|
|
877
|
-
if (!toggleContainer) {
|
|
878
|
-
console.error(`[aria-ease] Element with id="${toggleId}" not found. Make sure the toggle element exists before calling makeToggleAccessible.`);
|
|
879
|
-
return { cleanup: () => {
|
|
880
|
-
} };
|
|
881
|
-
}
|
|
882
|
-
let toggles;
|
|
883
|
-
if (isSingleToggle) {
|
|
884
|
-
toggles = [toggleContainer];
|
|
885
|
-
} else {
|
|
886
|
-
if (!togglesClass) {
|
|
887
|
-
console.error(`[aria-ease] togglesClass is required when isSingleToggle is false.`);
|
|
888
|
-
return { cleanup: () => {
|
|
889
|
-
} };
|
|
890
|
-
}
|
|
891
|
-
toggles = Array.from(toggleContainer.querySelectorAll(`.${togglesClass}`));
|
|
892
|
-
if (toggles.length === 0) {
|
|
893
|
-
console.error(`[aria-ease] No elements with class="${togglesClass}" found. Make sure toggle buttons exist before calling makeToggleAccessible.`);
|
|
894
|
-
return { cleanup: () => {
|
|
895
|
-
} };
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
const handlerMap = /* @__PURE__ */ new WeakMap();
|
|
899
|
-
const clickHandlerMap = /* @__PURE__ */ new WeakMap();
|
|
900
|
-
function initialize() {
|
|
901
|
-
toggles.forEach((toggle) => {
|
|
902
|
-
if (toggle.tagName.toLowerCase() !== "button" && !toggle.getAttribute("role")) {
|
|
903
|
-
toggle.setAttribute("role", "button");
|
|
904
|
-
}
|
|
905
|
-
if (!toggle.hasAttribute("aria-pressed")) {
|
|
906
|
-
toggle.setAttribute("aria-pressed", "false");
|
|
907
|
-
}
|
|
908
|
-
if (!toggle.hasAttribute("tabindex")) {
|
|
909
|
-
toggle.setAttribute("tabindex", "0");
|
|
910
|
-
}
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
function toggleButton(index) {
|
|
914
|
-
if (index < 0 || index >= toggles.length) {
|
|
915
|
-
console.error(`[aria-ease] Invalid toggle index: ${index}`);
|
|
916
|
-
return;
|
|
917
|
-
}
|
|
918
|
-
const toggle = toggles[index];
|
|
919
|
-
const isPressed = toggle.getAttribute("aria-pressed") === "true";
|
|
920
|
-
toggle.setAttribute("aria-pressed", isPressed ? "false" : "true");
|
|
921
|
-
}
|
|
922
|
-
function setPressed(index, pressed) {
|
|
923
|
-
if (index < 0 || index >= toggles.length) {
|
|
924
|
-
console.error(`[aria-ease] Invalid toggle index: ${index}`);
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
toggles[index].setAttribute("aria-pressed", pressed ? "true" : "false");
|
|
928
|
-
}
|
|
929
|
-
function handleToggleClick(index) {
|
|
930
|
-
return () => {
|
|
931
|
-
toggleButton(index);
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
function handleToggleKeydown(index) {
|
|
935
|
-
return (event) => {
|
|
936
|
-
const { key } = event;
|
|
937
|
-
switch (key) {
|
|
938
|
-
case "Enter":
|
|
939
|
-
case " ":
|
|
940
|
-
event.preventDefault();
|
|
941
|
-
toggleButton(index);
|
|
942
|
-
break;
|
|
943
|
-
}
|
|
944
|
-
};
|
|
945
|
-
}
|
|
946
|
-
function addListeners() {
|
|
947
|
-
toggles.forEach((toggle, index) => {
|
|
948
|
-
const clickHandler = handleToggleClick(index);
|
|
949
|
-
const keydownHandler = handleToggleKeydown(index);
|
|
950
|
-
toggle.addEventListener("click", clickHandler);
|
|
951
|
-
toggle.addEventListener("keydown", keydownHandler);
|
|
952
|
-
handlerMap.set(toggle, keydownHandler);
|
|
953
|
-
clickHandlerMap.set(toggle, clickHandler);
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
function removeListeners() {
|
|
957
|
-
toggles.forEach((toggle) => {
|
|
958
|
-
const keydownHandler = handlerMap.get(toggle);
|
|
959
|
-
const clickHandler = clickHandlerMap.get(toggle);
|
|
960
|
-
if (keydownHandler) {
|
|
961
|
-
toggle.removeEventListener("keydown", keydownHandler);
|
|
962
|
-
handlerMap.delete(toggle);
|
|
963
|
-
}
|
|
964
|
-
if (clickHandler) {
|
|
965
|
-
toggle.removeEventListener("click", clickHandler);
|
|
966
|
-
clickHandlerMap.delete(toggle);
|
|
967
|
-
}
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
function cleanup() {
|
|
971
|
-
removeListeners();
|
|
972
|
-
}
|
|
973
|
-
function getPressedStates() {
|
|
974
|
-
return toggles.map((toggle) => toggle.getAttribute("aria-pressed") === "true");
|
|
975
|
-
}
|
|
976
|
-
function getPressedIndices() {
|
|
977
|
-
return toggles.map((toggle, index) => toggle.getAttribute("aria-pressed") === "true" ? index : -1).filter((index) => index !== -1);
|
|
978
|
-
}
|
|
979
|
-
initialize();
|
|
980
|
-
addListeners();
|
|
981
|
-
return {
|
|
982
|
-
toggleButton,
|
|
983
|
-
setPressed,
|
|
984
|
-
getPressedStates,
|
|
985
|
-
getPressedIndices,
|
|
986
|
-
cleanup
|
|
987
|
-
};
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
// src/combobox/src/makeComboBoxAccessible/makeComboBoxAccessible.ts
|
|
991
|
-
function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId, listBoxItemsClass, callback }) {
|
|
992
|
-
const comboboxInput = document.getElementById(`${comboboxInputId}`);
|
|
993
|
-
if (!comboboxInput) {
|
|
994
|
-
console.error(`[aria-ease] Element with id="${comboboxInputId}" not found. Make sure the combobox input element exists before calling makeComboboxAccessible.`);
|
|
995
|
-
return { cleanup: () => {
|
|
996
|
-
} };
|
|
997
|
-
}
|
|
998
|
-
const listBox = document.getElementById(`${listBoxId}`);
|
|
999
|
-
if (!listBox) {
|
|
1000
|
-
console.error(`[aria-ease] Element with id="${listBoxId}" not found. Make sure the combobox listbox element exists before calling makeComboboxAccessible.`);
|
|
1001
|
-
return { cleanup: () => {
|
|
1002
|
-
} };
|
|
1003
|
-
}
|
|
1004
|
-
const listButton = comboboxButtonId ? document.getElementById(`${comboboxButtonId}`) : null;
|
|
1005
|
-
let activeIndex = -1;
|
|
1006
|
-
comboboxInput.setAttribute("role", "combobox");
|
|
1007
|
-
comboboxInput.setAttribute("aria-autocomplete", "list");
|
|
1008
|
-
comboboxInput.setAttribute("aria-controls", listBoxId);
|
|
1009
|
-
comboboxInput.setAttribute("aria-expanded", "false");
|
|
1010
|
-
comboboxInput.setAttribute("aria-haspopup", "listbox");
|
|
1011
|
-
listBox.setAttribute("role", "listbox");
|
|
1012
|
-
let cachedItems = null;
|
|
1013
|
-
function getVisibleItems() {
|
|
1014
|
-
if (!cachedItems) {
|
|
1015
|
-
cachedItems = listBox.querySelectorAll(`.${listBoxItemsClass}`);
|
|
1016
|
-
}
|
|
1017
|
-
return Array.from(cachedItems).filter((item) => !item.hidden && item.style.display !== "none");
|
|
1018
|
-
}
|
|
1019
|
-
function isListboxOpen() {
|
|
1020
|
-
return comboboxInput.getAttribute("aria-expanded") === "true";
|
|
1021
|
-
}
|
|
1022
|
-
function setActiveDescendant(index) {
|
|
1023
|
-
const visibleItems = getVisibleItems();
|
|
1024
|
-
if (index >= 0 && index < visibleItems.length) {
|
|
1025
|
-
const activeItem = visibleItems[index];
|
|
1026
|
-
const itemId = activeItem.id || `${listBoxId}-option-${index}`;
|
|
1027
|
-
if (!activeItem.id) {
|
|
1028
|
-
activeItem.id = itemId;
|
|
1029
|
-
}
|
|
1030
|
-
comboboxInput.setAttribute("aria-activedescendant", itemId);
|
|
1031
|
-
if (typeof activeItem.scrollIntoView === "function") {
|
|
1032
|
-
activeItem.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
1033
|
-
}
|
|
1034
|
-
if (callback?.onActiveDescendantChange) {
|
|
1035
|
-
try {
|
|
1036
|
-
callback.onActiveDescendantChange(itemId, activeItem);
|
|
1037
|
-
} catch (error) {
|
|
1038
|
-
console.error("[aria-ease] Error in combobox onActiveDescendantChange callback:", error);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
} else {
|
|
1042
|
-
comboboxInput.setAttribute("aria-activedescendant", "");
|
|
1043
|
-
}
|
|
1044
|
-
activeIndex = index;
|
|
1045
|
-
}
|
|
1046
|
-
function openListbox() {
|
|
1047
|
-
comboboxInput.setAttribute("aria-expanded", "true");
|
|
1048
|
-
listBox.style.display = "block";
|
|
1049
|
-
if (callback?.onOpenChange) {
|
|
1050
|
-
try {
|
|
1051
|
-
callback.onOpenChange(true);
|
|
1052
|
-
} catch (error) {
|
|
1053
|
-
console.error("[aria-ease] Error in combobox onOpenChange callback:", error);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
function closeListbox() {
|
|
1058
|
-
comboboxInput.setAttribute("aria-expanded", "false");
|
|
1059
|
-
comboboxInput.setAttribute("aria-activedescendant", "");
|
|
1060
|
-
listBox.style.display = "none";
|
|
1061
|
-
activeIndex = -1;
|
|
1062
|
-
if (callback?.onOpenChange) {
|
|
1063
|
-
try {
|
|
1064
|
-
callback.onOpenChange(false);
|
|
1065
|
-
} catch (error) {
|
|
1066
|
-
console.error("[aria-ease] Error in combobox onOpenChange callback:", error);
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
function selectOption(item) {
|
|
1071
|
-
const value = item.textContent?.trim() || "";
|
|
1072
|
-
comboboxInput.value = value;
|
|
1073
|
-
item.setAttribute("aria-selected", "true");
|
|
1074
|
-
closeListbox();
|
|
1075
|
-
if (callback?.onSelect) {
|
|
1076
|
-
try {
|
|
1077
|
-
callback.onSelect(item);
|
|
1078
|
-
} catch (error) {
|
|
1079
|
-
console.error("[aria-ease] Error in combobox onSelect callback:", error);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
function handleInputKeyDown(event) {
|
|
1084
|
-
const visibleItems = getVisibleItems();
|
|
1085
|
-
const isOpen = isListboxOpen();
|
|
1086
|
-
switch (event.key) {
|
|
1087
|
-
case "ArrowDown":
|
|
1088
|
-
event.preventDefault();
|
|
1089
|
-
if (!isOpen) {
|
|
1090
|
-
openListbox();
|
|
1091
|
-
return;
|
|
1092
|
-
}
|
|
1093
|
-
if (visibleItems.length === 0) return;
|
|
1094
|
-
{
|
|
1095
|
-
const newIndex = activeIndex >= visibleItems.length - 1 ? 0 : activeIndex + 1;
|
|
1096
|
-
setActiveDescendant(newIndex);
|
|
1097
|
-
}
|
|
1098
|
-
break;
|
|
1099
|
-
case "ArrowUp":
|
|
1100
|
-
event.preventDefault();
|
|
1101
|
-
if (!isOpen) return;
|
|
1102
|
-
if (visibleItems.length > 0) {
|
|
1103
|
-
const newIndex = activeIndex <= 0 ? visibleItems.length - 1 : activeIndex - 1;
|
|
1104
|
-
setActiveDescendant(newIndex);
|
|
1105
|
-
}
|
|
1106
|
-
break;
|
|
1107
|
-
case "Enter":
|
|
1108
|
-
if (isOpen && activeIndex >= 0 && activeIndex < visibleItems.length) {
|
|
1109
|
-
event.preventDefault();
|
|
1110
|
-
selectOption(visibleItems[activeIndex]);
|
|
1111
|
-
}
|
|
1112
|
-
break;
|
|
1113
|
-
case "Escape":
|
|
1114
|
-
if (isOpen) {
|
|
1115
|
-
event.preventDefault();
|
|
1116
|
-
closeListbox();
|
|
1117
|
-
} else if (comboboxInput.value) {
|
|
1118
|
-
event.preventDefault();
|
|
1119
|
-
comboboxInput.value = "";
|
|
1120
|
-
comboboxInput.setAttribute("aria-activedescendant", "");
|
|
1121
|
-
const visibleItems2 = getVisibleItems();
|
|
1122
|
-
visibleItems2.forEach((item) => {
|
|
1123
|
-
if (item.getAttribute("aria-selected") === "true") item.setAttribute("aria-selected", "false");
|
|
1124
|
-
});
|
|
1125
|
-
if (callback?.onClear) {
|
|
1126
|
-
try {
|
|
1127
|
-
callback.onClear();
|
|
1128
|
-
} catch (error) {
|
|
1129
|
-
console.error("[aria-ease] Error in combobox onClear callback:", error);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
break;
|
|
1134
|
-
case "Home":
|
|
1135
|
-
if (isOpen && visibleItems.length > 0) {
|
|
1136
|
-
event.preventDefault();
|
|
1137
|
-
setActiveDescendant(0);
|
|
1138
|
-
}
|
|
1139
|
-
break;
|
|
1140
|
-
case "End":
|
|
1141
|
-
if (isOpen && visibleItems.length > 0) {
|
|
1142
|
-
event.preventDefault();
|
|
1143
|
-
setActiveDescendant(visibleItems.length - 1);
|
|
1144
|
-
}
|
|
1145
|
-
break;
|
|
1146
|
-
case "Tab":
|
|
1147
|
-
if (isOpen && activeIndex >= 0 && activeIndex < visibleItems.length) {
|
|
1148
|
-
selectOption(visibleItems[activeIndex]);
|
|
1149
|
-
}
|
|
1150
|
-
if (isOpen) {
|
|
1151
|
-
closeListbox();
|
|
1152
|
-
}
|
|
1153
|
-
break;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
function handleMouseMove(event) {
|
|
1157
|
-
const target = event.target;
|
|
1158
|
-
if (target.classList.contains(listBoxItemsClass)) {
|
|
1159
|
-
const visibleItems = getVisibleItems();
|
|
1160
|
-
const index = visibleItems.indexOf(target);
|
|
1161
|
-
if (index >= 0) {
|
|
1162
|
-
setActiveDescendant(index);
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
function handleMouseDown(event) {
|
|
1167
|
-
const target = event.target;
|
|
1168
|
-
if (target.classList.contains(listBoxItemsClass)) {
|
|
1169
|
-
event.preventDefault();
|
|
1170
|
-
selectOption(target);
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
function handleClickOutside(event) {
|
|
1174
|
-
const target = event.target;
|
|
1175
|
-
if (!comboboxInput.contains(target) && !listBox.contains(target) && (!listButton || !listButton.contains(target))) {
|
|
1176
|
-
closeListbox();
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
function handleListButtonClick() {
|
|
1180
|
-
if (isListboxOpen()) {
|
|
1181
|
-
closeListbox();
|
|
1182
|
-
} else {
|
|
1183
|
-
openListbox();
|
|
1184
|
-
comboboxInput.focus();
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
function handleListButtonKeyDown(event) {
|
|
1188
|
-
if (event.key === "Enter" || event.key === " ") {
|
|
1189
|
-
event.preventDefault();
|
|
1190
|
-
handleListButtonClick();
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
comboboxInput.addEventListener("keydown", handleInputKeyDown);
|
|
1194
|
-
listBox.addEventListener("mousemove", handleMouseMove);
|
|
1195
|
-
listBox.addEventListener("mousedown", handleMouseDown);
|
|
1196
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
1197
|
-
if (listButton) {
|
|
1198
|
-
listButton.setAttribute("tabindex", "-1");
|
|
1199
|
-
listButton.setAttribute("aria-label", "Toggle options");
|
|
1200
|
-
listButton.addEventListener("click", handleListButtonClick);
|
|
1201
|
-
listButton.addEventListener("keydown", handleListButtonKeyDown);
|
|
1202
|
-
}
|
|
1203
|
-
function initializeOptions() {
|
|
1204
|
-
const items = listBox.querySelectorAll(`.${listBoxItemsClass}`);
|
|
1205
|
-
if (items.length === 0) return;
|
|
1206
|
-
let selectedValue = null;
|
|
1207
|
-
for (const item of items) {
|
|
1208
|
-
if (item.getAttribute("aria-selected") === "true") {
|
|
1209
|
-
selectedValue = item.textContent?.trim() || null;
|
|
1210
|
-
break;
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
if (!selectedValue && comboboxInput.value) {
|
|
1214
|
-
selectedValue = comboboxInput.value.trim();
|
|
1215
|
-
}
|
|
1216
|
-
items.forEach((item, index) => {
|
|
1217
|
-
item.setAttribute("role", "option");
|
|
1218
|
-
const itemValue = item.textContent?.trim() || "";
|
|
1219
|
-
if (selectedValue && itemValue === selectedValue) {
|
|
1220
|
-
item.setAttribute("aria-selected", "true");
|
|
1221
|
-
} else {
|
|
1222
|
-
item.setAttribute("aria-selected", "false");
|
|
1223
|
-
}
|
|
1224
|
-
const currentId = item.getAttribute("id");
|
|
1225
|
-
if (!currentId || currentId === "") {
|
|
1226
|
-
const itemId = `${listBoxId}-option-${index}`;
|
|
1227
|
-
item.id = itemId;
|
|
1228
|
-
item.setAttribute("id", itemId);
|
|
1229
|
-
}
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
initializeOptions();
|
|
1233
|
-
function cleanup() {
|
|
1234
|
-
comboboxInput.removeEventListener("keydown", handleInputKeyDown);
|
|
1235
|
-
listBox.removeEventListener("mousemove", handleMouseMove);
|
|
1236
|
-
listBox.removeEventListener("mousedown", handleMouseDown);
|
|
1237
|
-
document.removeEventListener("mousedown", handleClickOutside);
|
|
1238
|
-
if (listButton) {
|
|
1239
|
-
listButton.removeEventListener("click", handleListButtonClick);
|
|
1240
|
-
listButton.removeEventListener("keydown", handleListButtonKeyDown);
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
function refresh() {
|
|
1244
|
-
cachedItems = null;
|
|
1245
|
-
initializeOptions();
|
|
1246
|
-
activeIndex = -1;
|
|
1247
|
-
setActiveDescendant(-1);
|
|
1248
|
-
}
|
|
1249
|
-
return { cleanup, refresh, openListbox, closeListbox };
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
// src/tabs/src/makeTabsAccessible/makeTabsAccessible.ts
|
|
1253
|
-
function makeTabsAccessible({ tabListId, tabsClass, tabPanelsClass, orientation = "horizontal", activateOnFocus = true, callback }) {
|
|
1254
|
-
const tabList = document.querySelector(`#${tabListId}`);
|
|
1255
|
-
if (!tabList) {
|
|
1256
|
-
console.error(`[aria-ease] Element with id="${tabListId}" not found. Make sure the tab list container exists before calling makeTabsAccessible.`);
|
|
1257
|
-
return { cleanup: () => {
|
|
1258
|
-
} };
|
|
1259
|
-
}
|
|
1260
|
-
const tabs = Array.from(tabList.querySelectorAll(`.${tabsClass}`));
|
|
1261
|
-
if (tabs.length === 0) {
|
|
1262
|
-
console.error(`[aria-ease] No elements with class="${tabsClass}" found. Make sure tab buttons exist before calling makeTabsAccessible.`);
|
|
1263
|
-
return { cleanup: () => {
|
|
1264
|
-
} };
|
|
1265
|
-
}
|
|
1266
|
-
const tabPanels = Array.from(document.querySelectorAll(`.${tabPanelsClass}`));
|
|
1267
|
-
if (tabPanels.length === 0) {
|
|
1268
|
-
console.error(`[aria-ease] No elements with class="${tabPanelsClass}" found. Make sure tab panels exist before calling makeTabsAccessible.`);
|
|
1269
|
-
return { cleanup: () => {
|
|
1270
|
-
} };
|
|
1271
|
-
}
|
|
1272
|
-
if (tabs.length !== tabPanels.length) {
|
|
1273
|
-
console.error(`[aria-ease] Tab/panel mismatch: found ${tabs.length} tabs but ${tabPanels.length} panels.`);
|
|
1274
|
-
return { cleanup: () => {
|
|
1275
|
-
} };
|
|
1276
|
-
}
|
|
1277
|
-
const handlerMap = /* @__PURE__ */ new WeakMap();
|
|
1278
|
-
const clickHandlerMap = /* @__PURE__ */ new WeakMap();
|
|
1279
|
-
const contextMenuHandlerMap = /* @__PURE__ */ new WeakMap();
|
|
1280
|
-
let activeTabIndex = 0;
|
|
1281
|
-
function initialize() {
|
|
1282
|
-
tabList.setAttribute("role", "tablist");
|
|
1283
|
-
tabList.setAttribute("aria-orientation", orientation);
|
|
1284
|
-
tabs.forEach((tab, index) => {
|
|
1285
|
-
const panel = tabPanels[index];
|
|
1286
|
-
if (!tab.id) {
|
|
1287
|
-
tab.id = `${tabListId}-tab-${index}`;
|
|
1288
|
-
}
|
|
1289
|
-
if (!panel.id) {
|
|
1290
|
-
panel.id = `${tabListId}-panel-${index}`;
|
|
1291
|
-
}
|
|
1292
|
-
tab.setAttribute("role", "tab");
|
|
1293
|
-
tab.setAttribute("aria-controls", panel.id);
|
|
1294
|
-
tab.setAttribute("aria-selected", "false");
|
|
1295
|
-
tab.setAttribute("tabindex", "-1");
|
|
1296
|
-
panel.setAttribute("role", "tabpanel");
|
|
1297
|
-
panel.setAttribute("aria-labelledby", tab.id);
|
|
1298
|
-
panel.hidden = true;
|
|
1299
|
-
const hasFocusableContent = panel.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
1300
|
-
if (!hasFocusableContent) {
|
|
1301
|
-
panel.setAttribute("tabindex", "0");
|
|
1302
|
-
}
|
|
1303
|
-
});
|
|
1304
|
-
activateTab(0, false);
|
|
1305
|
-
}
|
|
1306
|
-
function activateTab(index, shouldFocus = true) {
|
|
1307
|
-
if (index < 0 || index >= tabs.length) {
|
|
1308
|
-
console.error(`[aria-ease] Invalid tab index: ${index}`);
|
|
1309
|
-
return;
|
|
1310
|
-
}
|
|
1311
|
-
const previousIndex = activeTabIndex;
|
|
1312
|
-
tabs.forEach((tab, i) => {
|
|
1313
|
-
const panel = tabPanels[i];
|
|
1314
|
-
tab.setAttribute("aria-selected", "false");
|
|
1315
|
-
tab.setAttribute("tabindex", "-1");
|
|
1316
|
-
panel.hidden = true;
|
|
1317
|
-
});
|
|
1318
|
-
const activeTab = tabs[index];
|
|
1319
|
-
const activePanel = tabPanels[index];
|
|
1320
|
-
activeTab.setAttribute("aria-selected", "true");
|
|
1321
|
-
activeTab.setAttribute("tabindex", "0");
|
|
1322
|
-
activePanel.hidden = false;
|
|
1323
|
-
if (shouldFocus) {
|
|
1324
|
-
activeTab.focus();
|
|
1325
|
-
}
|
|
1326
|
-
activeTabIndex = index;
|
|
1327
|
-
if (callback?.onTabChange && previousIndex !== index) {
|
|
1328
|
-
try {
|
|
1329
|
-
callback.onTabChange(index, previousIndex);
|
|
1330
|
-
} catch (error) {
|
|
1331
|
-
console.error("[aria-ease] Error in tabs onTabChange callback:", error);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
function moveFocus2(direction) {
|
|
1336
|
-
const currentFocusedIndex = tabs.findIndex((tab) => tab === document.activeElement);
|
|
1337
|
-
const currentIndex = currentFocusedIndex !== -1 ? currentFocusedIndex : activeTabIndex;
|
|
1338
|
-
let newIndex = currentIndex;
|
|
1339
|
-
switch (direction) {
|
|
1340
|
-
case "first":
|
|
1341
|
-
newIndex = 0;
|
|
1342
|
-
break;
|
|
1343
|
-
case "last":
|
|
1344
|
-
newIndex = tabs.length - 1;
|
|
1345
|
-
break;
|
|
1346
|
-
case "next":
|
|
1347
|
-
newIndex = (currentIndex + 1) % tabs.length;
|
|
1348
|
-
break;
|
|
1349
|
-
case "prev":
|
|
1350
|
-
newIndex = (currentIndex - 1 + tabs.length) % tabs.length;
|
|
1351
|
-
break;
|
|
1352
|
-
}
|
|
1353
|
-
tabs[newIndex].focus();
|
|
1354
|
-
tabs[newIndex].setAttribute("tabindex", "0");
|
|
1355
|
-
tabs[activeTabIndex].setAttribute("tabindex", "-1");
|
|
1356
|
-
if (activateOnFocus) {
|
|
1357
|
-
activateTab(newIndex, false);
|
|
1358
|
-
} else {
|
|
1359
|
-
const currentActive = activeTabIndex;
|
|
1360
|
-
tabs.forEach((tab, i) => {
|
|
1361
|
-
if (i === newIndex) {
|
|
1362
|
-
tab.setAttribute("tabindex", "0");
|
|
1363
|
-
} else if (i !== currentActive) {
|
|
1364
|
-
tab.setAttribute("tabindex", "-1");
|
|
1365
|
-
}
|
|
1366
|
-
});
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
function handleTabClick(index) {
|
|
1370
|
-
return () => {
|
|
1371
|
-
activateTab(index);
|
|
1372
|
-
};
|
|
1373
|
-
}
|
|
1374
|
-
function handleTabKeydown(index) {
|
|
1375
|
-
return (event) => {
|
|
1376
|
-
const { key } = event;
|
|
1377
|
-
let handled = false;
|
|
1378
|
-
if (orientation === "horizontal") {
|
|
1379
|
-
switch (key) {
|
|
1380
|
-
case "ArrowLeft":
|
|
1381
|
-
event.preventDefault();
|
|
1382
|
-
moveFocus2("prev");
|
|
1383
|
-
handled = true;
|
|
1384
|
-
break;
|
|
1385
|
-
case "ArrowRight":
|
|
1386
|
-
event.preventDefault();
|
|
1387
|
-
moveFocus2("next");
|
|
1388
|
-
handled = true;
|
|
1389
|
-
break;
|
|
1390
|
-
}
|
|
1391
|
-
} else {
|
|
1392
|
-
switch (key) {
|
|
1393
|
-
case "ArrowUp":
|
|
1394
|
-
event.preventDefault();
|
|
1395
|
-
moveFocus2("prev");
|
|
1396
|
-
handled = true;
|
|
1397
|
-
break;
|
|
1398
|
-
case "ArrowDown":
|
|
1399
|
-
event.preventDefault();
|
|
1400
|
-
moveFocus2("next");
|
|
1401
|
-
handled = true;
|
|
1402
|
-
break;
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
if (!handled) {
|
|
1406
|
-
switch (key) {
|
|
1407
|
-
case "Home":
|
|
1408
|
-
event.preventDefault();
|
|
1409
|
-
moveFocus2("first");
|
|
1410
|
-
break;
|
|
1411
|
-
case "End":
|
|
1412
|
-
event.preventDefault();
|
|
1413
|
-
moveFocus2("last");
|
|
1414
|
-
break;
|
|
1415
|
-
case " ":
|
|
1416
|
-
case "Enter":
|
|
1417
|
-
if (!activateOnFocus) {
|
|
1418
|
-
event.preventDefault();
|
|
1419
|
-
activateTab(index);
|
|
1420
|
-
}
|
|
1421
|
-
break;
|
|
1422
|
-
case "F10":
|
|
1423
|
-
if (event.shiftKey && callback?.onContextMenu) {
|
|
1424
|
-
event.preventDefault();
|
|
1425
|
-
try {
|
|
1426
|
-
callback.onContextMenu(index, tabs[index]);
|
|
1427
|
-
} catch (error) {
|
|
1428
|
-
console.error("[aria-ease] Error in tabs onContextMenu callback:", error);
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
break;
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
|
-
function handleTabContextMenu(index) {
|
|
1437
|
-
return (event) => {
|
|
1438
|
-
if (callback?.onContextMenu) {
|
|
1439
|
-
event.preventDefault();
|
|
1440
|
-
try {
|
|
1441
|
-
callback.onContextMenu(index, tabs[index]);
|
|
1442
|
-
} catch (error) {
|
|
1443
|
-
console.error("[aria-ease] Error in tabs onContextMenu callback:", error);
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
};
|
|
1447
|
-
}
|
|
1448
|
-
function addListeners() {
|
|
1449
|
-
tabs.forEach((tab, index) => {
|
|
1450
|
-
const clickHandler = handleTabClick(index);
|
|
1451
|
-
const keydownHandler = handleTabKeydown(index);
|
|
1452
|
-
const contextMenuHandler = handleTabContextMenu(index);
|
|
1453
|
-
tab.addEventListener("click", clickHandler);
|
|
1454
|
-
tab.addEventListener("keydown", keydownHandler);
|
|
1455
|
-
if (callback?.onContextMenu) {
|
|
1456
|
-
tab.addEventListener("contextmenu", contextMenuHandler);
|
|
1457
|
-
contextMenuHandlerMap.set(tab, contextMenuHandler);
|
|
1458
|
-
}
|
|
1459
|
-
handlerMap.set(tab, keydownHandler);
|
|
1460
|
-
clickHandlerMap.set(tab, clickHandler);
|
|
1461
|
-
});
|
|
1462
|
-
}
|
|
1463
|
-
function removeListeners() {
|
|
1464
|
-
tabs.forEach((tab) => {
|
|
1465
|
-
const keydownHandler = handlerMap.get(tab);
|
|
1466
|
-
const clickHandler = clickHandlerMap.get(tab);
|
|
1467
|
-
const contextMenuHandler = contextMenuHandlerMap.get(tab);
|
|
1468
|
-
if (keydownHandler) {
|
|
1469
|
-
tab.removeEventListener("keydown", keydownHandler);
|
|
1470
|
-
handlerMap.delete(tab);
|
|
1471
|
-
}
|
|
1472
|
-
if (clickHandler) {
|
|
1473
|
-
tab.removeEventListener("click", clickHandler);
|
|
1474
|
-
clickHandlerMap.delete(tab);
|
|
1475
|
-
}
|
|
1476
|
-
if (contextMenuHandler) {
|
|
1477
|
-
tab.removeEventListener("contextmenu", contextMenuHandler);
|
|
1478
|
-
contextMenuHandlerMap.delete(tab);
|
|
1479
|
-
}
|
|
1480
|
-
});
|
|
1481
|
-
}
|
|
1482
|
-
function cleanup() {
|
|
1483
|
-
removeListeners();
|
|
1484
|
-
tabs.forEach((tab, index) => {
|
|
1485
|
-
const panel = tabPanels[index];
|
|
1486
|
-
tab.removeAttribute("role");
|
|
1487
|
-
tab.removeAttribute("aria-selected");
|
|
1488
|
-
tab.removeAttribute("aria-controls");
|
|
1489
|
-
tab.removeAttribute("tabindex");
|
|
1490
|
-
panel.removeAttribute("role");
|
|
1491
|
-
panel.removeAttribute("aria-labelledby");
|
|
1492
|
-
panel.removeAttribute("tabindex");
|
|
1493
|
-
panel.hidden = false;
|
|
1494
|
-
});
|
|
1495
|
-
tabList.removeAttribute("role");
|
|
1496
|
-
tabList.removeAttribute("aria-orientation");
|
|
1497
|
-
}
|
|
1498
|
-
function refresh() {
|
|
1499
|
-
removeListeners();
|
|
1500
|
-
const newTabs = Array.from(tabList.querySelectorAll(`.${tabsClass}`));
|
|
1501
|
-
const newPanels = Array.from(document.querySelectorAll(`.${tabPanelsClass}`));
|
|
1502
|
-
tabs.length = 0;
|
|
1503
|
-
tabs.push(...newTabs);
|
|
1504
|
-
tabPanels.length = 0;
|
|
1505
|
-
tabPanels.push(...newPanels);
|
|
1506
|
-
initialize();
|
|
1507
|
-
addListeners();
|
|
1508
|
-
}
|
|
1509
|
-
initialize();
|
|
1510
|
-
addListeners();
|
|
1511
|
-
return { activateTab, cleanup, refresh };
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
// src/utils/test/dsl/src/state-packs/combobox/comboboxStatePack.ts
|
|
1515
|
-
var COMBOBOX_STATES = {
|
|
1516
|
-
"popup.open": {
|
|
1517
|
-
setup: [
|
|
1518
|
-
{
|
|
1519
|
-
when: ["keyboard", "textInput"],
|
|
1520
|
-
steps: () => [
|
|
1521
|
-
{ type: "keypress", target: "input", key: "ArrowDown" }
|
|
1522
|
-
]
|
|
1523
|
-
},
|
|
1524
|
-
{
|
|
1525
|
-
when: ["pointer"],
|
|
1526
|
-
steps: () => [
|
|
1527
|
-
{ type: "click", target: "button" }
|
|
1528
|
-
]
|
|
1529
|
-
}
|
|
1530
|
-
],
|
|
1531
|
-
assertion: isComboboxOpen
|
|
1532
|
-
},
|
|
1533
|
-
"popup.closed": {
|
|
1534
|
-
setup: [
|
|
1535
|
-
{
|
|
1536
|
-
when: ["keyboard"],
|
|
1537
|
-
steps: () => [
|
|
1538
|
-
/* { type: "keypress", target: "input", key: "Escape" } */
|
|
1539
|
-
]
|
|
1540
|
-
},
|
|
1541
|
-
{
|
|
1542
|
-
when: ["pointer"],
|
|
1543
|
-
steps: () => [
|
|
1544
|
-
/* { type: "click", target: "button" } */
|
|
1545
|
-
]
|
|
1546
|
-
}
|
|
1547
|
-
],
|
|
1548
|
-
assertion: [...isComboboxClosed(), ...isActiveDescendantEmpty()]
|
|
1549
|
-
},
|
|
1550
|
-
"main.focused": {
|
|
1551
|
-
setup: [
|
|
1552
|
-
{
|
|
1553
|
-
when: ["keyboard"],
|
|
1554
|
-
steps: () => [
|
|
1555
|
-
{ type: "focus", target: "main" }
|
|
1556
|
-
]
|
|
1557
|
-
}
|
|
1558
|
-
],
|
|
1559
|
-
assertion: isMainFocused
|
|
1560
|
-
},
|
|
1561
|
-
"main.notFocused": {
|
|
1562
|
-
setup: [
|
|
1563
|
-
{
|
|
1564
|
-
when: ["keyboard"],
|
|
1565
|
-
steps: () => [
|
|
1566
|
-
//what to do here?
|
|
1567
|
-
]
|
|
1568
|
-
}
|
|
1569
|
-
],
|
|
1570
|
-
assertion: isMainNotFocused
|
|
1571
|
-
},
|
|
1572
|
-
"input.filled": {
|
|
1573
|
-
setup: [
|
|
1574
|
-
{
|
|
1575
|
-
when: ["keyboard", "textInput"],
|
|
1576
|
-
steps: () => [
|
|
1577
|
-
{ type: "type", target: "input", value: "test" }
|
|
1578
|
-
]
|
|
1579
|
-
}
|
|
1580
|
-
],
|
|
1581
|
-
assertion: isInputFilled
|
|
1582
|
-
},
|
|
1583
|
-
"input.notFilled": {
|
|
1584
|
-
setup: [
|
|
1585
|
-
{
|
|
1586
|
-
when: ["keyboard", "textInput"],
|
|
1587
|
-
steps: () => [
|
|
1588
|
-
{ type: "type", target: "input", value: "" }
|
|
1589
|
-
]
|
|
1590
|
-
}
|
|
1591
|
-
],
|
|
1592
|
-
assertion: isInputNotFilled
|
|
1593
|
-
},
|
|
1594
|
-
"activeOption.first": {
|
|
1595
|
-
requires: ["popup.open"],
|
|
1596
|
-
setup: [
|
|
1597
|
-
{
|
|
1598
|
-
when: ["keyboard"],
|
|
1599
|
-
steps: () => [
|
|
1600
|
-
{ type: "keypress", target: "input", key: "ArrowDown" }
|
|
1601
|
-
]
|
|
1602
|
-
}
|
|
1603
|
-
],
|
|
1604
|
-
assertion: isActiveDescendantFirst
|
|
1605
|
-
},
|
|
1606
|
-
"activeOption.last": {
|
|
1607
|
-
requires: ["activeOption.first"],
|
|
1608
|
-
setup: [
|
|
1609
|
-
{
|
|
1610
|
-
when: ["keyboard"],
|
|
1611
|
-
steps: () => [
|
|
1612
|
-
{ type: "keypress", target: "input", key: "ArrowUp" }
|
|
1613
|
-
]
|
|
1614
|
-
}
|
|
1615
|
-
],
|
|
1616
|
-
assertion: isActiveDescendantLast
|
|
1617
|
-
},
|
|
1618
|
-
"activeDescendant.notEmpty": {
|
|
1619
|
-
requires: [],
|
|
1620
|
-
setup: [
|
|
1621
|
-
{
|
|
1622
|
-
when: ["keyboard"],
|
|
1623
|
-
steps: () => []
|
|
1624
|
-
}
|
|
1625
|
-
],
|
|
1626
|
-
assertion: isActiveDescendantNotEmpty
|
|
1627
|
-
},
|
|
1628
|
-
"activeDescendant.Empty": {
|
|
1629
|
-
requires: [],
|
|
1630
|
-
setup: [
|
|
1631
|
-
{
|
|
1632
|
-
when: ["keyboard"],
|
|
1633
|
-
steps: () => []
|
|
1634
|
-
}
|
|
1635
|
-
],
|
|
1636
|
-
assertion: isActiveDescendantEmpty
|
|
1637
|
-
},
|
|
1638
|
-
"selectedOption.first": {
|
|
1639
|
-
requires: ["popup.open"],
|
|
1640
|
-
setup: [
|
|
1641
|
-
{
|
|
1642
|
-
when: ["pointer"],
|
|
1643
|
-
steps: () => [
|
|
1644
|
-
{ type: "click", target: "relative", relativeTarget: "first" }
|
|
1645
|
-
]
|
|
1646
|
-
}
|
|
1647
|
-
],
|
|
1648
|
-
assertion: () => isAriaSelected("first")
|
|
1649
|
-
},
|
|
1650
|
-
"selectedOption.last": {
|
|
1651
|
-
requires: ["popup.open"],
|
|
1652
|
-
setup: [
|
|
1653
|
-
{
|
|
1654
|
-
when: ["pointer"],
|
|
1655
|
-
steps: () => [
|
|
1656
|
-
{ type: "click", target: "relative", relativeTarget: "last" }
|
|
1657
|
-
]
|
|
1658
|
-
}
|
|
1659
|
-
],
|
|
1660
|
-
assertion: () => isAriaSelected("last")
|
|
1661
|
-
}
|
|
1662
|
-
};
|
|
1663
|
-
function isComboboxOpen() {
|
|
1664
|
-
return [
|
|
1665
|
-
{
|
|
1666
|
-
target: "popup",
|
|
1667
|
-
assertion: "toBeVisible",
|
|
1668
|
-
failureMessage: "Expected popup to be visible"
|
|
1669
|
-
},
|
|
1670
|
-
{
|
|
1671
|
-
target: "main",
|
|
1672
|
-
assertion: "toHaveAttribute",
|
|
1673
|
-
attribute: "aria-expanded",
|
|
1674
|
-
expectedValue: "true",
|
|
1675
|
-
failureMessage: "Expect combobox main to have aria-expanded='true'."
|
|
1676
|
-
}
|
|
1677
|
-
];
|
|
1678
|
-
}
|
|
1679
|
-
function isComboboxClosed() {
|
|
1680
|
-
return [
|
|
1681
|
-
{
|
|
1682
|
-
target: "popup",
|
|
1683
|
-
assertion: "notToBeVisible",
|
|
1684
|
-
failureMessage: "Expected popup to be closed"
|
|
1685
|
-
},
|
|
1686
|
-
{
|
|
1687
|
-
target: "main",
|
|
1688
|
-
assertion: "toHaveAttribute",
|
|
1689
|
-
attribute: "aria-expanded",
|
|
1690
|
-
expectedValue: "false",
|
|
1691
|
-
failureMessage: "Expect combobox main to have aria-expanded='false'."
|
|
1692
|
-
}
|
|
1693
|
-
];
|
|
1694
|
-
}
|
|
1695
|
-
function isActiveDescendantFirst() {
|
|
1696
|
-
return [
|
|
1697
|
-
{
|
|
1698
|
-
target: "main",
|
|
1699
|
-
assertion: "toHaveAttribute",
|
|
1700
|
-
attribute: "aria-activedescendant",
|
|
1701
|
-
expectedValue: { ref: "relative", relativeTarget: "first", property: "id" },
|
|
1702
|
-
failureMessage: "Expected aria-activedescendant on main to match the id of the first option."
|
|
1703
|
-
}
|
|
1704
|
-
];
|
|
1705
|
-
}
|
|
1706
|
-
function isActiveDescendantLast() {
|
|
1707
|
-
return [
|
|
1708
|
-
{
|
|
1709
|
-
target: "main",
|
|
1710
|
-
assertion: "toHaveAttribute",
|
|
1711
|
-
attribute: "aria-activedescendant",
|
|
1712
|
-
expectedValue: { ref: "relative", relativeTarget: "last", property: "id" },
|
|
1713
|
-
failureMessage: "Expected aria-activedescendant on main to match the id of the last option."
|
|
1714
|
-
}
|
|
1715
|
-
];
|
|
1716
|
-
}
|
|
1717
|
-
function isActiveDescendantNotEmpty() {
|
|
1718
|
-
return [
|
|
1719
|
-
{
|
|
1720
|
-
target: "main",
|
|
1721
|
-
assertion: "toHaveAttribute",
|
|
1722
|
-
attribute: "aria-activedescendant",
|
|
1723
|
-
expectedValue: "!empty",
|
|
1724
|
-
failureMessage: "Expected aria-activedescendant on main to not be empty."
|
|
1725
|
-
}
|
|
1726
|
-
];
|
|
1727
|
-
}
|
|
1728
|
-
function isActiveDescendantEmpty() {
|
|
1729
|
-
return [
|
|
1730
|
-
{
|
|
1731
|
-
target: "main",
|
|
1732
|
-
assertion: "toHaveAttribute",
|
|
1733
|
-
attribute: "aria-activedescendant",
|
|
1734
|
-
expectedValue: "",
|
|
1735
|
-
failureMessage: "Expected aria-activedescendant on main to be empty."
|
|
1736
|
-
}
|
|
1737
|
-
];
|
|
1738
|
-
}
|
|
1739
|
-
function isAriaSelected(index) {
|
|
1740
|
-
return [
|
|
1741
|
-
{
|
|
1742
|
-
target: "relative",
|
|
1743
|
-
relativeTarget: index,
|
|
1744
|
-
assertion: "toHaveAttribute",
|
|
1745
|
-
attribute: "aria-selected",
|
|
1746
|
-
expectedValue: "true",
|
|
1747
|
-
failureMessage: `Expected ${index} option to have aria-selected='true'.`
|
|
1748
|
-
}
|
|
1749
|
-
];
|
|
1750
|
-
}
|
|
1751
|
-
function isMainFocused() {
|
|
1752
|
-
return [
|
|
1753
|
-
{
|
|
1754
|
-
target: "main",
|
|
1755
|
-
assertion: "toHaveFocus",
|
|
1756
|
-
failureMessage: "Expected main to be focused."
|
|
1757
|
-
}
|
|
1758
|
-
];
|
|
1759
|
-
}
|
|
1760
|
-
function isMainNotFocused() {
|
|
1761
|
-
return [
|
|
1762
|
-
{
|
|
1763
|
-
target: "main",
|
|
1764
|
-
assertion: "notToHaveFocus",
|
|
1765
|
-
failureMessage: "Expected main to not have focused."
|
|
1766
|
-
}
|
|
1767
|
-
];
|
|
1768
|
-
}
|
|
1769
|
-
function isInputFilled() {
|
|
1770
|
-
return [
|
|
1771
|
-
{
|
|
1772
|
-
target: "input",
|
|
1773
|
-
assertion: "toHaveValue",
|
|
1774
|
-
expectedValue: "test",
|
|
1775
|
-
failureMessage: "Expected input to have the value 'test'."
|
|
1776
|
-
}
|
|
1777
|
-
];
|
|
1778
|
-
}
|
|
1779
|
-
function isInputNotFilled() {
|
|
1780
|
-
return [
|
|
1781
|
-
{
|
|
1782
|
-
target: "input",
|
|
1783
|
-
assertion: "toHaveValue",
|
|
1784
|
-
expectedValue: "",
|
|
1785
|
-
failureMessage: "Expected input to have the value ''."
|
|
1786
|
-
}
|
|
1787
|
-
];
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
// src/utils/test/dsl/src/state-packs/menu/menuStatePack.ts
|
|
1791
|
-
var MENU_STATES = {
|
|
1792
|
-
"popup.open": {
|
|
1793
|
-
setup: [
|
|
1794
|
-
{
|
|
1795
|
-
when: ["keyboard"],
|
|
1796
|
-
steps: () => [
|
|
1797
|
-
{ type: "keypress", target: "main", key: "Enter" }
|
|
1798
|
-
]
|
|
1799
|
-
},
|
|
1800
|
-
{
|
|
1801
|
-
when: ["pointer"],
|
|
1802
|
-
steps: () => [
|
|
1803
|
-
{ type: "click", target: "main" }
|
|
1804
|
-
]
|
|
1805
|
-
}
|
|
1806
|
-
],
|
|
1807
|
-
assertion: isMenuPopupOpen
|
|
1808
|
-
},
|
|
1809
|
-
"popup.closed": {
|
|
1810
|
-
setup: [
|
|
1811
|
-
{
|
|
1812
|
-
when: ["keyboard"],
|
|
1813
|
-
steps: () => [
|
|
1814
|
-
// component resets after each test so popup is closed
|
|
1815
|
-
]
|
|
1816
|
-
},
|
|
1817
|
-
{
|
|
1818
|
-
when: ["pointer"],
|
|
1819
|
-
steps: () => []
|
|
1820
|
-
}
|
|
1821
|
-
],
|
|
1822
|
-
assertion: isMenuPopupClosed
|
|
1823
|
-
},
|
|
1824
|
-
"main.focused": {
|
|
1825
|
-
setup: [
|
|
1826
|
-
{
|
|
1827
|
-
when: ["keyboard"],
|
|
1828
|
-
steps: () => [
|
|
1829
|
-
{ type: "focus", target: "main" }
|
|
1830
|
-
]
|
|
1831
|
-
}
|
|
1832
|
-
],
|
|
1833
|
-
assertion: isMainFocused2
|
|
1834
|
-
},
|
|
1835
|
-
"main.notFocused": {
|
|
1836
|
-
setup: [
|
|
1837
|
-
{
|
|
1838
|
-
when: ["keyboard"],
|
|
1839
|
-
steps: () => [
|
|
1840
|
-
//what to do here?
|
|
1841
|
-
]
|
|
1842
|
-
}
|
|
1843
|
-
],
|
|
1844
|
-
assertion: isMainNotFocused2
|
|
1845
|
-
},
|
|
1846
|
-
"activeItem.first": {
|
|
1847
|
-
requires: ["popup.open"],
|
|
1848
|
-
setup: [
|
|
1849
|
-
{
|
|
1850
|
-
when: ["keyboard"],
|
|
1851
|
-
steps: () => [
|
|
1852
|
-
// By default, the first item should be active when the menu opens, so no action is needed to set this state
|
|
1853
|
-
]
|
|
1854
|
-
}
|
|
1855
|
-
],
|
|
1856
|
-
assertion: isActiveItemFirst
|
|
1857
|
-
},
|
|
1858
|
-
"activeItem.last": {
|
|
1859
|
-
requires: ["popup.open"],
|
|
1860
|
-
setup: [
|
|
1861
|
-
{
|
|
1862
|
-
when: ["keyboard"],
|
|
1863
|
-
steps: () => [
|
|
1864
|
-
{ type: "keypress", target: "main", key: "ArrowUp" }
|
|
1865
|
-
]
|
|
1866
|
-
}
|
|
1867
|
-
],
|
|
1868
|
-
assertion: isActiveItemLast
|
|
1869
|
-
},
|
|
1870
|
-
"submenu.open": {
|
|
1871
|
-
requires: ["popup.open"],
|
|
1872
|
-
setup: [
|
|
1873
|
-
{
|
|
1874
|
-
when: ["keyboard"],
|
|
1875
|
-
steps: () => [
|
|
1876
|
-
{ type: "keypress", target: "submenuTrigger", key: "ArrowRight" }
|
|
1877
|
-
]
|
|
1878
|
-
},
|
|
1879
|
-
{
|
|
1880
|
-
when: ["pointer"],
|
|
1881
|
-
steps: () => [
|
|
1882
|
-
{ type: "click", target: "submenuTrigger" }
|
|
1883
|
-
]
|
|
1884
|
-
}
|
|
1885
|
-
],
|
|
1886
|
-
assertion: isSubmenuPopupOpen
|
|
1887
|
-
},
|
|
1888
|
-
"submenu.closed": {
|
|
1889
|
-
requires: ["submenu.open"],
|
|
1890
|
-
setup: [
|
|
1891
|
-
{
|
|
1892
|
-
when: ["keyboard"],
|
|
1893
|
-
steps: () => [
|
|
1894
|
-
{ type: "keypress", target: "submenuTrigger", key: "ArrowLeft" }
|
|
1895
|
-
]
|
|
1896
|
-
},
|
|
1897
|
-
{
|
|
1898
|
-
when: ["pointer"],
|
|
1899
|
-
steps: () => [
|
|
1900
|
-
{ type: "click", target: "submenuTrigger" }
|
|
1901
|
-
]
|
|
1902
|
-
}
|
|
1903
|
-
],
|
|
1904
|
-
assertion: isSubmenuPopupClosed
|
|
1905
|
-
},
|
|
1906
|
-
"submenuTrigger.focused": {
|
|
1907
|
-
setup: [
|
|
1908
|
-
{
|
|
1909
|
-
when: ["keyboard"],
|
|
1910
|
-
steps: () => [
|
|
1911
|
-
{ type: "focus", target: "submenuTrigger" }
|
|
1912
|
-
]
|
|
1913
|
-
}
|
|
1914
|
-
],
|
|
1915
|
-
assertion: isSubmenuTriggerFocused
|
|
1916
|
-
},
|
|
1917
|
-
"submenuTrigger.notFocused": {
|
|
1918
|
-
setup: [
|
|
1919
|
-
{
|
|
1920
|
-
when: ["keyboard"],
|
|
1921
|
-
steps: () => [
|
|
1922
|
-
//what to do here?
|
|
1923
|
-
]
|
|
1924
|
-
}
|
|
1925
|
-
],
|
|
1926
|
-
assertion: isSubmenuTriggerNotFocused
|
|
1927
|
-
},
|
|
1928
|
-
"submenuActiveItem.first": {
|
|
1929
|
-
requires: ["submenu.open"],
|
|
1930
|
-
setup: [
|
|
1931
|
-
{
|
|
1932
|
-
when: ["keyboard"],
|
|
1933
|
-
steps: () => [
|
|
1934
|
-
// By default, the first item should be active when the submenu opens, so no action is needed to set this state
|
|
1935
|
-
]
|
|
1936
|
-
},
|
|
1937
|
-
{
|
|
1938
|
-
when: ["pointer"],
|
|
1939
|
-
steps: () => []
|
|
1940
|
-
}
|
|
1941
|
-
],
|
|
1942
|
-
assertion: isSubmenuActiveItemFirst
|
|
1943
|
-
}
|
|
1944
|
-
};
|
|
1945
|
-
function isMenuPopupOpen() {
|
|
1946
|
-
return [
|
|
1947
|
-
{
|
|
1948
|
-
target: "popup",
|
|
1949
|
-
assertion: "toBeVisible",
|
|
1950
|
-
failureMessage: "Expected popup to be visible"
|
|
1951
|
-
},
|
|
1952
|
-
{
|
|
1953
|
-
target: "main",
|
|
1954
|
-
assertion: "toHaveAttribute",
|
|
1955
|
-
attribute: "aria-expanded",
|
|
1956
|
-
expectedValue: "true",
|
|
1957
|
-
failureMessage: "Expect menu main to have aria-expanded='true'."
|
|
1958
|
-
}
|
|
1959
|
-
];
|
|
1960
|
-
}
|
|
1961
|
-
function isMenuPopupClosed() {
|
|
1962
|
-
return [
|
|
1963
|
-
{
|
|
1964
|
-
target: "popup",
|
|
1965
|
-
assertion: "notToBeVisible",
|
|
1966
|
-
failureMessage: "Expected popup to be closed"
|
|
1967
|
-
},
|
|
1968
|
-
{
|
|
1969
|
-
target: "main",
|
|
1970
|
-
assertion: "toHaveAttribute",
|
|
1971
|
-
attribute: "aria-expanded",
|
|
1972
|
-
expectedValue: "false",
|
|
1973
|
-
failureMessage: "Expect menu main to have aria-expanded='false'."
|
|
1974
|
-
}
|
|
1975
|
-
];
|
|
1976
|
-
}
|
|
1977
|
-
function isMainFocused2() {
|
|
1978
|
-
return [
|
|
1979
|
-
{
|
|
1980
|
-
target: "main",
|
|
1981
|
-
assertion: "toHaveFocus",
|
|
1982
|
-
failureMessage: "Expected menu main to be focused."
|
|
1983
|
-
}
|
|
1984
|
-
];
|
|
1985
|
-
}
|
|
1986
|
-
function isMainNotFocused2() {
|
|
1987
|
-
return [
|
|
1988
|
-
{
|
|
1989
|
-
target: "main",
|
|
1990
|
-
assertion: "notToHaveFocus",
|
|
1991
|
-
failureMessage: "Expected menu main to not have focused."
|
|
1992
|
-
}
|
|
1993
|
-
];
|
|
1994
|
-
}
|
|
1995
|
-
function isActiveItemFirst() {
|
|
1996
|
-
return [
|
|
1997
|
-
{
|
|
1998
|
-
target: "relative",
|
|
1999
|
-
assertion: "toHaveFocus",
|
|
2000
|
-
expectedValue: "first",
|
|
2001
|
-
failureMessage: "First menu item should have focus."
|
|
2002
|
-
}
|
|
2003
|
-
];
|
|
2004
|
-
}
|
|
2005
|
-
function isActiveItemLast() {
|
|
2006
|
-
return [
|
|
2007
|
-
{
|
|
2008
|
-
target: "relative",
|
|
2009
|
-
assertion: "toHaveFocus",
|
|
2010
|
-
expectedValue: "last",
|
|
2011
|
-
failureMessage: "Last menu item should have focus."
|
|
2012
|
-
}
|
|
2013
|
-
];
|
|
2014
|
-
}
|
|
2015
|
-
function isSubmenuPopupOpen() {
|
|
2016
|
-
return [
|
|
2017
|
-
{
|
|
2018
|
-
target: "submenu",
|
|
2019
|
-
assertion: "toBeVisible",
|
|
2020
|
-
failureMessage: "Expected submenu to be visible"
|
|
2021
|
-
},
|
|
2022
|
-
{
|
|
2023
|
-
target: "submenuTrigger",
|
|
2024
|
-
assertion: "toHaveAttribute",
|
|
2025
|
-
attribute: "aria-expanded",
|
|
2026
|
-
expectedValue: "true",
|
|
2027
|
-
failureMessage: "Expect submenu trigger to have aria-expanded='true'."
|
|
2028
|
-
}
|
|
2029
|
-
];
|
|
2030
|
-
}
|
|
2031
|
-
function isSubmenuPopupClosed() {
|
|
2032
|
-
return [
|
|
2033
|
-
{
|
|
2034
|
-
target: "submenu",
|
|
2035
|
-
assertion: "notToBeVisible",
|
|
2036
|
-
failureMessage: "Expected submenu to be closed"
|
|
2037
|
-
},
|
|
2038
|
-
{
|
|
2039
|
-
target: "submenuTrigger",
|
|
2040
|
-
assertion: "toHaveAttribute",
|
|
2041
|
-
attribute: "aria-expanded",
|
|
2042
|
-
expectedValue: "false",
|
|
2043
|
-
failureMessage: "Expect submenu trigger to have aria-expanded='false'."
|
|
2044
|
-
}
|
|
2045
|
-
];
|
|
2046
|
-
}
|
|
2047
|
-
function isSubmenuTriggerFocused() {
|
|
2048
|
-
return [
|
|
2049
|
-
{
|
|
2050
|
-
target: "submenuTrigger",
|
|
2051
|
-
assertion: "toHaveFocus",
|
|
2052
|
-
failureMessage: "Expected submenu trigger to be focused."
|
|
2053
|
-
}
|
|
2054
|
-
];
|
|
2055
|
-
}
|
|
2056
|
-
function isSubmenuTriggerNotFocused() {
|
|
2057
|
-
return [
|
|
2058
|
-
{
|
|
2059
|
-
target: "submenuTrigger",
|
|
2060
|
-
assertion: "notToHaveFocus",
|
|
2061
|
-
failureMessage: "Expected submenu trigger to not have focused."
|
|
2062
|
-
}
|
|
2063
|
-
];
|
|
2064
|
-
}
|
|
2065
|
-
function isSubmenuActiveItemFirst() {
|
|
2066
|
-
return [
|
|
2067
|
-
{
|
|
2068
|
-
target: "submenuItems",
|
|
2069
|
-
assertion: "toHaveFocus",
|
|
2070
|
-
failureMessage: "First interactive item in the submenu should have focus after Right Arrow open the submenu."
|
|
2071
|
-
}
|
|
2072
|
-
];
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
// src/utils/test/dsl/src/state-packs/Capability.ts
|
|
2076
|
-
function hasCapabilities(ctx, requiredCaps) {
|
|
2077
|
-
return requiredCaps.some((cap) => ctx.capabilities.includes(cap));
|
|
2078
|
-
}
|
|
2079
|
-
function resolveSetup(setup, ctx) {
|
|
2080
|
-
if (Array.isArray(setup) && setup.length && !setup[0].when) {
|
|
2081
|
-
setup = [{ when: ["keyboard"], steps: () => setup }];
|
|
2082
|
-
}
|
|
2083
|
-
for (const strat of setup) {
|
|
2084
|
-
if (hasCapabilities(ctx, strat.when)) {
|
|
2085
|
-
return strat.steps(ctx);
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
|
-
throw new Error(
|
|
2089
|
-
`No setup strategy matches capabilities: ${ctx.capabilities.join(", ")}`
|
|
2090
|
-
);
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
// src/utils/test/dsl/src/contractBuilder.ts
|
|
2094
|
-
var STATE_PACKS = {
|
|
2095
|
-
"combobox": COMBOBOX_STATES,
|
|
2096
|
-
"menu": MENU_STATES
|
|
2097
|
-
// Add more mappings as needed
|
|
2098
|
-
};
|
|
2099
|
-
var FluentContract = class {
|
|
2100
|
-
constructor(jsonContract) {
|
|
2101
|
-
this.jsonContract = jsonContract;
|
|
2102
|
-
}
|
|
2103
|
-
toJSON() {
|
|
2104
|
-
return this.jsonContract;
|
|
2105
|
-
}
|
|
2106
|
-
};
|
|
2107
|
-
var ContractBuilder = class {
|
|
2108
|
-
constructor(componentName) {
|
|
2109
|
-
this.componentName = componentName;
|
|
2110
|
-
this.statePack = STATE_PACKS[componentName] || {};
|
|
2111
|
-
}
|
|
2112
|
-
metaValue = {};
|
|
2113
|
-
selectorsValue = {};
|
|
2114
|
-
relationshipInvariants = [];
|
|
2115
|
-
staticAssertions = [];
|
|
2116
|
-
dynamicTests = [];
|
|
2117
|
-
statePack;
|
|
2118
|
-
meta(meta) {
|
|
2119
|
-
this.metaValue = meta;
|
|
2120
|
-
return this;
|
|
2121
|
-
}
|
|
2122
|
-
selectors(selectors) {
|
|
2123
|
-
this.selectorsValue = selectors;
|
|
2124
|
-
return this;
|
|
2125
|
-
}
|
|
2126
|
-
relationships(fn) {
|
|
2127
|
-
const statePack = this.statePack;
|
|
2128
|
-
const ctx = { capabilities: ["keyboard"] };
|
|
2129
|
-
const resolveAllSetups = (stateName, visited = /* @__PURE__ */ new Set()) => {
|
|
2130
|
-
if (visited.has(stateName)) return [];
|
|
2131
|
-
visited.add(stateName);
|
|
2132
|
-
const s = statePack[stateName];
|
|
2133
|
-
if (!s) return [];
|
|
2134
|
-
let actions = [];
|
|
2135
|
-
if (Array.isArray(s.requires)) {
|
|
2136
|
-
for (const req of s.requires) {
|
|
2137
|
-
actions = actions.concat(resolveAllSetups(req, visited));
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
if (s.setup) actions = actions.concat(resolveSetup(s.setup, ctx));
|
|
2141
|
-
return actions;
|
|
2142
|
-
};
|
|
2143
|
-
const api = {
|
|
2144
|
-
ariaReference: (from, attribute, to) => {
|
|
2145
|
-
return {
|
|
2146
|
-
requires: (state) => {
|
|
2147
|
-
const setupActions = resolveAllSetups(state, /* @__PURE__ */ new Set());
|
|
2148
|
-
return {
|
|
2149
|
-
required: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "required", setup: setupActions }),
|
|
2150
|
-
optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional", setup: setupActions }),
|
|
2151
|
-
recommended: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "recommended", setup: setupActions })
|
|
2152
|
-
};
|
|
2153
|
-
},
|
|
2154
|
-
required: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "required" }),
|
|
2155
|
-
optional: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "optional" }),
|
|
2156
|
-
recommended: () => this.relationshipInvariants.push({ type: "aria-reference", from, attribute, to, level: "recommended" })
|
|
2157
|
-
};
|
|
2158
|
-
},
|
|
2159
|
-
contains: (parent, child) => {
|
|
2160
|
-
return {
|
|
2161
|
-
requires: (state) => {
|
|
2162
|
-
const setupActions = resolveAllSetups(state, /* @__PURE__ */ new Set());
|
|
2163
|
-
return {
|
|
2164
|
-
required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required", setup: setupActions }),
|
|
2165
|
-
optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional", setup: setupActions }),
|
|
2166
|
-
recommended: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "recommended", setup: setupActions })
|
|
2167
|
-
};
|
|
2168
|
-
},
|
|
2169
|
-
required: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "required" }),
|
|
2170
|
-
optional: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "optional" }),
|
|
2171
|
-
recommended: () => this.relationshipInvariants.push({ type: "contains", parent, child, level: "recommended" })
|
|
2172
|
-
};
|
|
2173
|
-
}
|
|
2174
|
-
};
|
|
2175
|
-
fn(api);
|
|
2176
|
-
return this;
|
|
2177
|
-
}
|
|
2178
|
-
static(fn) {
|
|
2179
|
-
const api = {
|
|
2180
|
-
target: (target) => {
|
|
2181
|
-
return {
|
|
2182
|
-
requires: (state) => {
|
|
2183
|
-
const statePack = this.statePack;
|
|
2184
|
-
const ctx = { capabilities: ["keyboard"] };
|
|
2185
|
-
const resolveAllSetups = (stateName, visited = /* @__PURE__ */ new Set()) => {
|
|
2186
|
-
if (visited.has(stateName)) return [];
|
|
2187
|
-
visited.add(stateName);
|
|
2188
|
-
const s = statePack[stateName];
|
|
2189
|
-
if (!s) return [];
|
|
2190
|
-
let actions = [];
|
|
2191
|
-
if (Array.isArray(s.requires)) {
|
|
2192
|
-
for (const req of s.requires) {
|
|
2193
|
-
actions = actions.concat(resolveAllSetups(req, visited));
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
if (s.setup) actions = actions.concat(resolveSetup(s.setup, ctx));
|
|
2197
|
-
return actions;
|
|
2198
|
-
};
|
|
2199
|
-
const setupActions = resolveAllSetups(state, /* @__PURE__ */ new Set());
|
|
2200
|
-
return {
|
|
2201
|
-
has: (attribute, expectedValue) => ({
|
|
2202
|
-
required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required", setup: setupActions }),
|
|
2203
|
-
optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional", setup: setupActions }),
|
|
2204
|
-
recommended: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "recommended", setup: setupActions })
|
|
2205
|
-
})
|
|
2206
|
-
};
|
|
2207
|
-
},
|
|
2208
|
-
has: (attribute, expectedValue) => ({
|
|
2209
|
-
required: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "required" }),
|
|
2210
|
-
optional: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "optional" }),
|
|
2211
|
-
recommended: () => this.staticAssertions.push({ target, attribute, expectedValue, failureMessage: "", level: "recommended" })
|
|
2212
|
-
})
|
|
2213
|
-
};
|
|
2214
|
-
}
|
|
2215
|
-
};
|
|
2216
|
-
fn(api);
|
|
2217
|
-
return this;
|
|
2218
|
-
}
|
|
2219
|
-
when(event) {
|
|
2220
|
-
return new DynamicTestBuilder(this, this.statePack, event);
|
|
2221
|
-
}
|
|
2222
|
-
addDynamicTest(test) {
|
|
2223
|
-
this.dynamicTests.push(test);
|
|
2224
|
-
}
|
|
2225
|
-
build() {
|
|
2226
|
-
return {
|
|
2227
|
-
meta: this.metaValue,
|
|
2228
|
-
selectors: this.selectorsValue,
|
|
2229
|
-
relationships: this.relationshipInvariants.length ? this.relationshipInvariants : void 0,
|
|
2230
|
-
static: this.staticAssertions.length ? [{ assertions: this.staticAssertions }] : [],
|
|
2231
|
-
dynamic: this.dynamicTests
|
|
2232
|
-
};
|
|
2233
|
-
}
|
|
2234
|
-
};
|
|
2235
|
-
var DynamicTestBuilder = class {
|
|
2236
|
-
constructor(parent, statePack, event) {
|
|
2237
|
-
this.parent = parent;
|
|
2238
|
-
this.statePack = statePack;
|
|
2239
|
-
this.event = event;
|
|
2240
|
-
}
|
|
2241
|
-
_as;
|
|
2242
|
-
_on;
|
|
2243
|
-
_given = [];
|
|
2244
|
-
_then = [];
|
|
2245
|
-
_desc = "";
|
|
2246
|
-
_level = "required";
|
|
2247
|
-
as(actionType) {
|
|
2248
|
-
this._as = actionType;
|
|
2249
|
-
return this;
|
|
2250
|
-
}
|
|
2251
|
-
on(target) {
|
|
2252
|
-
this._on = target;
|
|
2253
|
-
return this;
|
|
2254
|
-
}
|
|
2255
|
-
given(states) {
|
|
2256
|
-
this._given = Array.isArray(states) ? states : [states];
|
|
2257
|
-
return this;
|
|
2258
|
-
}
|
|
2259
|
-
then(states) {
|
|
2260
|
-
this._then = Array.isArray(states) ? states : [states];
|
|
2261
|
-
return this;
|
|
2262
|
-
}
|
|
2263
|
-
describe(desc) {
|
|
2264
|
-
this._desc = desc;
|
|
2265
|
-
return this;
|
|
2266
|
-
}
|
|
2267
|
-
required() {
|
|
2268
|
-
this._level = "required";
|
|
2269
|
-
this._finalize();
|
|
2270
|
-
return this.parent;
|
|
2271
|
-
}
|
|
2272
|
-
optional() {
|
|
2273
|
-
this._level = "optional";
|
|
2274
|
-
this._finalize();
|
|
2275
|
-
return this.parent;
|
|
2276
|
-
}
|
|
2277
|
-
recommended() {
|
|
2278
|
-
this._level = "recommended";
|
|
2279
|
-
this._finalize();
|
|
2280
|
-
return this.parent;
|
|
2281
|
-
}
|
|
2282
|
-
_finalize() {
|
|
2283
|
-
const capabilityMap = {
|
|
2284
|
-
keypress: "keyboard",
|
|
2285
|
-
click: "pointer",
|
|
2286
|
-
type: "textInput",
|
|
2287
|
-
focus: "keyboard",
|
|
2288
|
-
hover: "pointer"
|
|
2289
|
-
// add more mappings as needed
|
|
2290
|
-
};
|
|
2291
|
-
const capability = capabilityMap[this._as || "keyboard"] || (this._as || "keyboard");
|
|
2292
|
-
const ctx = { capabilities: [capability] };
|
|
2293
|
-
const resolveAllSetups = (stateName, visited = /* @__PURE__ */ new Set()) => {
|
|
2294
|
-
if (visited.has(stateName)) return [];
|
|
2295
|
-
visited.add(stateName);
|
|
2296
|
-
const s = this.statePack[stateName];
|
|
2297
|
-
if (!s) return [];
|
|
2298
|
-
let actions = [];
|
|
2299
|
-
if (Array.isArray(s.requires)) {
|
|
2300
|
-
for (const req of s.requires) {
|
|
2301
|
-
actions = actions.concat(resolveAllSetups(req, visited));
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
if (s.setup) actions = actions.concat(resolveSetup(s.setup, ctx));
|
|
2305
|
-
return actions;
|
|
2306
|
-
};
|
|
2307
|
-
const setup = [];
|
|
2308
|
-
for (const state of this._given) {
|
|
2309
|
-
setup.push(...resolveAllSetups(state));
|
|
2310
|
-
}
|
|
2311
|
-
const assertions = [];
|
|
2312
|
-
for (const state of this._then) {
|
|
2313
|
-
const s = this.statePack[state];
|
|
2314
|
-
if (s && s.assertion !== void 0) {
|
|
2315
|
-
let value = s.assertion;
|
|
2316
|
-
if (typeof value === "function") {
|
|
2317
|
-
try {
|
|
2318
|
-
value = value();
|
|
2319
|
-
} catch (e) {
|
|
2320
|
-
throw new Error(`Error calling assertion function for state '${state}': ${e.message}`);
|
|
2321
|
-
}
|
|
2322
|
-
}
|
|
2323
|
-
if (Array.isArray(value)) assertions.push(...value);
|
|
2324
|
-
else assertions.push(value);
|
|
2325
|
-
}
|
|
2326
|
-
}
|
|
2327
|
-
const action = [
|
|
2328
|
-
{
|
|
2329
|
-
type: this._as,
|
|
2330
|
-
target: this._on,
|
|
2331
|
-
key: this._as === "keypress" ? this.event : void 0
|
|
2332
|
-
}
|
|
2333
|
-
];
|
|
2334
|
-
this.parent.addDynamicTest({
|
|
2335
|
-
description: this._desc || "",
|
|
2336
|
-
level: this._level,
|
|
2337
|
-
action,
|
|
2338
|
-
assertions,
|
|
2339
|
-
...setup.length ? { setup } : {}
|
|
2340
|
-
});
|
|
2341
|
-
}
|
|
2342
|
-
};
|
|
2343
|
-
function createContract(componentName, define) {
|
|
2344
|
-
const builder = new ContractBuilder(componentName);
|
|
2345
|
-
define(builder);
|
|
2346
|
-
return new FluentContract(builder.build());
|
|
2347
|
-
}
|
|
2348
|
-
|
|
2349
|
-
// src/utils/test/src/test.ts
|
|
2350
|
-
import { axe } from "jest-axe";
|
|
2351
|
-
|
|
2352
|
-
// src/utils/test/src/contractTestRunner.ts
|
|
2353
|
-
import fs from "fs/promises";
|
|
2354
|
-
async function runContractTests(contractPath, componentName, component, strictness) {
|
|
2355
|
-
const reporter = new ContractReporter(false);
|
|
2356
|
-
const strictnessMode = normalizeStrictness(strictness);
|
|
2357
|
-
if (!contractPath) {
|
|
2358
|
-
throw new Error(`No contract path provided for component: ${componentName}`);
|
|
2359
|
-
}
|
|
2360
|
-
const contractData = await fs.readFile(contractPath, "utf-8");
|
|
2361
|
-
const componentContract = JSON.parse(contractData);
|
|
2362
|
-
const totalTests = (componentContract.relationships?.length || 0) + (componentContract.static[0]?.assertions.length || 0) + componentContract.dynamic.length;
|
|
2363
|
-
reporter.start(componentName, totalTests);
|
|
2364
|
-
const failures = [];
|
|
2365
|
-
const passes = [];
|
|
2366
|
-
const skipped = [];
|
|
2367
|
-
const warnings = [];
|
|
2368
|
-
const classifyFailure = (message, levelRaw) => {
|
|
2369
|
-
const level = normalizeLevel(levelRaw);
|
|
2370
|
-
const enforcement = resolveEnforcement(level, strictnessMode);
|
|
2371
|
-
if (enforcement === "error") {
|
|
2372
|
-
failures.push(message);
|
|
2373
|
-
return { status: "fail", level, detail: message };
|
|
2374
|
-
}
|
|
2375
|
-
if (enforcement === "warning") {
|
|
2376
|
-
warnings.push(message);
|
|
2377
|
-
return { status: "warn", level, detail: message };
|
|
2378
|
-
}
|
|
2379
|
-
const ignoredMessage = `${message} (ignored by strictness=${strictnessMode}, level=${level})`;
|
|
2380
|
-
skipped.push(ignoredMessage);
|
|
2381
|
-
return { status: "skip", level, detail: ignoredMessage };
|
|
2382
|
-
};
|
|
2383
|
-
let staticPassed = 0;
|
|
2384
|
-
let staticFailed = 0;
|
|
2385
|
-
let staticWarnings = 0;
|
|
2386
|
-
for (const rel of componentContract.relationships || []) {
|
|
2387
|
-
const relationshipLevel = normalizeLevel(rel.level);
|
|
2388
|
-
if (rel.type === "aria-reference") {
|
|
2389
|
-
const fromSelector = componentContract.selectors[rel.from];
|
|
2390
|
-
const toSelector = componentContract.selectors[rel.to];
|
|
2391
|
-
const relDescription = `${rel.from}.${rel.attribute} references ${rel.to}`;
|
|
2392
|
-
if (!fromSelector || !toSelector) {
|
|
2393
|
-
const outcome = classifyFailure(`Relationship selector missing: from="${rel.from}" or to="${rel.to}" not found in selectors.`, rel.level);
|
|
2394
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
2395
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
2396
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2397
|
-
continue;
|
|
2398
|
-
}
|
|
2399
|
-
const fromTarget = component.querySelector(fromSelector);
|
|
2400
|
-
const toTarget = component.querySelector(toSelector);
|
|
2401
|
-
if (!fromTarget || !toTarget) {
|
|
2402
|
-
const outcome = classifyFailure(`Relationship target not found: ${!fromTarget ? rel.from : rel.to}.`, rel.level);
|
|
2403
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
2404
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
2405
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2406
|
-
continue;
|
|
2407
|
-
}
|
|
2408
|
-
const toId = toTarget.getAttribute("id");
|
|
2409
|
-
const attrValue = fromTarget.getAttribute(rel.attribute) || "";
|
|
2410
|
-
if (!toId) {
|
|
2411
|
-
const outcome = classifyFailure(`Relationship target "${rel.to}" must have an id for ${rel.attribute} validation.`, rel.level);
|
|
2412
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
2413
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
2414
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2415
|
-
continue;
|
|
2416
|
-
}
|
|
2417
|
-
const references = attrValue.split(/\s+/).filter(Boolean);
|
|
2418
|
-
if (!references.includes(toId)) {
|
|
2419
|
-
const outcome = classifyFailure(`Expected ${rel.from} ${rel.attribute} to reference id "${toId}", found "${attrValue}".`, rel.level);
|
|
2420
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
2421
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
2422
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2423
|
-
continue;
|
|
2424
|
-
}
|
|
2425
|
-
passes.push(`Relationship valid: ${rel.from}.${rel.attribute} -> ${rel.to} (id=${toId}).`);
|
|
2426
|
-
staticPassed += 1;
|
|
2427
|
-
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
2428
|
-
continue;
|
|
2429
|
-
}
|
|
2430
|
-
if (rel.type === "contains") {
|
|
2431
|
-
const parentSelector = componentContract.selectors[rel.parent];
|
|
2432
|
-
const childSelector = componentContract.selectors[rel.child];
|
|
2433
|
-
const relDescription = `${rel.parent} contains ${rel.child}`;
|
|
2434
|
-
if (!parentSelector || !childSelector) {
|
|
2435
|
-
const outcome = classifyFailure(`Relationship selector missing: parent="${rel.parent}" or child="${rel.child}" not found in selectors.`, rel.level);
|
|
2436
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
2437
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
2438
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2439
|
-
continue;
|
|
2440
|
-
}
|
|
2441
|
-
const parentTarget = component.querySelector(parentSelector);
|
|
2442
|
-
if (!parentTarget) {
|
|
2443
|
-
const outcome = classifyFailure(`Relationship parent target not found: ${rel.parent}.`, rel.level);
|
|
2444
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
2445
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
2446
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2447
|
-
continue;
|
|
2448
|
-
}
|
|
2449
|
-
const nestedChild = parentTarget.querySelector(childSelector);
|
|
2450
|
-
if (!nestedChild) {
|
|
2451
|
-
const outcome = classifyFailure(`Expected ${rel.parent} to contain descendant matching selector for ${rel.child}.`, rel.level);
|
|
2452
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
2453
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
2454
|
-
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
2455
|
-
continue;
|
|
2456
|
-
}
|
|
2457
|
-
passes.push(`Relationship valid: ${rel.parent} contains ${rel.child}.`);
|
|
2458
|
-
staticPassed += 1;
|
|
2459
|
-
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2462
|
-
for (const test of componentContract.static[0].assertions) {
|
|
2463
|
-
if (test.target !== "relative") {
|
|
2464
|
-
const staticLevel = normalizeLevel(test.level);
|
|
2465
|
-
const selector = componentContract.selectors[test.target];
|
|
2466
|
-
if (!selector) {
|
|
2467
|
-
const outcome = classifyFailure(`Selector for target ${test.target} not found.`, test.level);
|
|
2468
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
2469
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
2470
|
-
reporter.reportStaticTest(`${test.target} has required ARIA attributes`, outcome.status, outcome.detail, outcome.level);
|
|
2471
|
-
continue;
|
|
2472
|
-
}
|
|
2473
|
-
const target = component.querySelector(selector);
|
|
2474
|
-
if (!target) {
|
|
2475
|
-
const outcome = classifyFailure(`Target ${test.target} not found.`, test.level);
|
|
2476
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
2477
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
2478
|
-
reporter.reportStaticTest(`${test.target} has required ARIA attributes`, outcome.status, outcome.detail, outcome.level);
|
|
2479
|
-
continue;
|
|
2480
|
-
}
|
|
2481
|
-
const attributeValue = target.getAttribute(test.attribute);
|
|
2482
|
-
if (!test.expectedValue) {
|
|
2483
|
-
const attributes = test.attribute.split(" | ");
|
|
2484
|
-
const hasAnyAttribute = attributes.some((attr) => target.hasAttribute(attr));
|
|
2485
|
-
if (!hasAnyAttribute) {
|
|
2486
|
-
const outcome = classifyFailure(test.failureMessage + ` None of the attributes "${test.attribute}" are present.`, test.level);
|
|
2487
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
2488
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
2489
|
-
reporter.reportStaticTest(`${test.target} has ${test.attribute}`, outcome.status, outcome.detail, outcome.level);
|
|
2490
|
-
} else {
|
|
2491
|
-
passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
|
|
2492
|
-
staticPassed += 1;
|
|
2493
|
-
reporter.reportStaticTest(`${test.target} has ${test.attribute}`, "pass", void 0, staticLevel);
|
|
2494
|
-
}
|
|
2495
|
-
} else if (!attributeValue || typeof test.expectedValue === "string" && !test.expectedValue.split(" | ").includes(attributeValue)) {
|
|
2496
|
-
const outcome = classifyFailure(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`, test.level);
|
|
2497
|
-
if (outcome.status === "fail") staticFailed += 1;
|
|
2498
|
-
if (outcome.status === "warn") staticWarnings += 1;
|
|
2499
|
-
reporter.reportStaticTest(`${test.target} has ${test.attribute}="${test.expectedValue}"`, outcome.status, outcome.detail, outcome.level);
|
|
2500
|
-
} else {
|
|
2501
|
-
passes.push(`Attribute value matches expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
|
|
2502
|
-
staticPassed += 1;
|
|
2503
|
-
reporter.reportStaticTest(`${test.target} has ${test.attribute}="${attributeValue}"`, "pass", void 0, staticLevel);
|
|
2504
|
-
}
|
|
2505
|
-
}
|
|
2506
|
-
}
|
|
2507
|
-
for (const dynamicTest of componentContract.dynamic) {
|
|
2508
|
-
skipped.push(dynamicTest.description);
|
|
2509
|
-
reporter.reportTest({ description: dynamicTest.description, level: dynamicTest.level }, "skip");
|
|
2510
|
-
}
|
|
2511
|
-
reporter.reportStatic(staticPassed, staticFailed, staticWarnings);
|
|
2512
|
-
reporter.summary(failures);
|
|
2513
|
-
return { passes, failures, skipped, warnings };
|
|
2514
|
-
}
|
|
2515
|
-
|
|
2516
|
-
// src/utils/test/src/test.ts
|
|
2517
|
-
import path from "path";
|
|
2518
|
-
async function testUiComponent(componentName, component, url, options = {}) {
|
|
2519
|
-
if (!componentName || typeof componentName !== "string") {
|
|
2520
|
-
throw new Error("\u274C testUiComponent requires a valid componentName (string)");
|
|
2521
|
-
}
|
|
2522
|
-
if (!url && (!component || !(component instanceof HTMLElement))) {
|
|
2523
|
-
throw new Error("\u274C testUiComponent requires either a valid component (HTMLElement) or a URL");
|
|
2524
|
-
}
|
|
2525
|
-
if (url && typeof url !== "string") {
|
|
2526
|
-
throw new Error("\u274C testUiComponent url parameter must be a string");
|
|
2527
|
-
}
|
|
2528
|
-
let results;
|
|
2529
|
-
if (component) {
|
|
2530
|
-
try {
|
|
2531
|
-
results = await axe(component);
|
|
2532
|
-
} catch (error) {
|
|
2533
|
-
throw new Error(
|
|
2534
|
-
`\u274C Axe accessibility scan failed
|
|
2535
|
-
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
2536
|
-
);
|
|
2537
|
-
}
|
|
2538
|
-
} else {
|
|
2539
|
-
results = { violations: [] };
|
|
2540
|
-
}
|
|
2541
|
-
async function checkDevServer(url2) {
|
|
2542
|
-
try {
|
|
2543
|
-
const response = await fetch(url2, {
|
|
2544
|
-
method: "HEAD",
|
|
2545
|
-
signal: AbortSignal.timeout(1e3)
|
|
2546
|
-
});
|
|
2547
|
-
if (response.ok || response.status === 304) {
|
|
2548
|
-
return url2;
|
|
2549
|
-
}
|
|
2550
|
-
} catch {
|
|
2551
|
-
return null;
|
|
2552
|
-
}
|
|
2553
|
-
return null;
|
|
2554
|
-
}
|
|
2555
|
-
let strictness = normalizeStrictness(options.strictness);
|
|
2556
|
-
let config = {};
|
|
2557
|
-
let configBaseDir = typeof process !== "undefined" ? process.cwd() : "";
|
|
2558
|
-
if (typeof process !== "undefined" && typeof process.cwd === "function") {
|
|
2559
|
-
try {
|
|
2560
|
-
const { loadConfig } = await import("./configLoader-Q7N5XV4P.js");
|
|
2561
|
-
const result2 = await loadConfig(process.cwd());
|
|
2562
|
-
config = result2.config;
|
|
2563
|
-
if (result2.configPath) {
|
|
2564
|
-
configBaseDir = path.dirname(result2.configPath);
|
|
2565
|
-
}
|
|
2566
|
-
if (options.strictness === void 0) {
|
|
2567
|
-
const componentStrictness = config.test?.components?.find((comp) => comp?.name === componentName)?.strictness;
|
|
2568
|
-
strictness = normalizeStrictness(componentStrictness ?? config.test?.strictness);
|
|
2569
|
-
}
|
|
2570
|
-
} catch {
|
|
2571
|
-
if (options.strictness === void 0) {
|
|
2572
|
-
strictness = "balanced";
|
|
2573
|
-
}
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
let contract;
|
|
2577
|
-
try {
|
|
2578
|
-
if (url) {
|
|
2579
|
-
const devServerUrl = await checkDevServer(url);
|
|
2580
|
-
if (devServerUrl) {
|
|
2581
|
-
console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
|
|
2582
|
-
const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-RWK52C7S.js");
|
|
2583
|
-
contract = await runContractTestsPlaywright(componentName, devServerUrl, strictness, config, configBaseDir);
|
|
2584
|
-
} else {
|
|
2585
|
-
throw new Error(
|
|
2586
|
-
`\u274C Dev server not running at ${url}
|
|
2587
|
-
Please start your dev server and try again.`
|
|
2588
|
-
);
|
|
2589
|
-
}
|
|
2590
|
-
} else if (component) {
|
|
2591
|
-
console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
|
|
2592
|
-
const contractPath = config.test?.components?.find((comp) => comp?.name === componentName)?.contractPath;
|
|
2593
|
-
if (!contractPath) {
|
|
2594
|
-
throw new Error(`\u274C No contract path found for component: ${componentName}`);
|
|
2595
|
-
}
|
|
2596
|
-
contract = await runContractTests(
|
|
2597
|
-
path.resolve(configBaseDir, contractPath),
|
|
2598
|
-
componentName,
|
|
2599
|
-
component,
|
|
2600
|
-
strictness
|
|
2601
|
-
);
|
|
2602
|
-
} else {
|
|
2603
|
-
throw new Error("\u274C Either component or URL must be provided");
|
|
2604
|
-
}
|
|
2605
|
-
} catch (error) {
|
|
2606
|
-
if (error instanceof Error) {
|
|
2607
|
-
throw error;
|
|
2608
|
-
}
|
|
2609
|
-
throw new Error(`\u274C Contract test execution failed: ${String(error)}`);
|
|
2610
|
-
}
|
|
2611
|
-
const result = {
|
|
2612
|
-
violations: results.violations,
|
|
2613
|
-
raw: results,
|
|
2614
|
-
contract
|
|
2615
|
-
};
|
|
2616
|
-
if (contract.failures.length > 0 && url === "Playwright") {
|
|
2617
|
-
throw new Error(
|
|
2618
|
-
`
|
|
2619
|
-
\u274C ${contract.failures.length} accessibility contract test${contract.failures.length > 1 ? "s" : ""} failed (Playwright mode)
|
|
2620
|
-
\u2705 ${contract.passes.length} test${contract.passes.length > 1 ? "s" : ""} passed
|
|
1
|
+
import{b as U,d as V}from"./chunk-APUMBDOT.js";import"./chunk-CNU4N4AY.js";function Le({accordionId:c,triggersClass:n,panelsClass:h,allowMultipleOpen:o=!1,callback:s}){let i=document.querySelector(`#${c}`);if(!i)return console.error(`[aria-ease] Element with id="${c}" not found. Make sure the accordion container exists before calling makeAccordionAccessible.`),{cleanup:()=>{}};let e=Array.from(i.querySelectorAll(`.${n}`));if(e.length===0)return console.error(`[aria-ease] No elements with class="${n}" found. Make sure accordion triggers exist before calling makeAccordionAccessible.`),{cleanup:()=>{}};let t=Array.from(i.querySelectorAll(`.${h}`));if(t.length===0)return console.error(`[aria-ease] No elements with class="${h}" found. Make sure accordion panels exist before calling makeAccordionAccessible.`),{cleanup:()=>{}};if(e.length!==t.length)return console.error(`[aria-ease] Accordion trigger/panel mismatch: found ${e.length} triggers but ${t.length} panels.`),{cleanup:()=>{}};let r=new WeakMap,m=new WeakMap;function p(){e.forEach((g,T)=>{let L=t[T];g.id||(g.id=`${c}-trigger-${T}`),L.id||(L.id=`${c}-panel-${T}`),g.setAttribute("aria-controls",L.id),g.setAttribute("aria-expanded","false"),(!o||e.length<=6)&&L.setAttribute("role","region"),L.setAttribute("aria-labelledby",g.id),L.style.display="none"})}function v(g){if(g<0||g>=e.length){console.error(`[aria-ease] Invalid accordion index: ${g}`);return}let T=e[g],L=t[g];if(T.setAttribute("aria-expanded","true"),L.style.display="block",s?.onExpand)try{s.onExpand(g)}catch(d){console.error("[aria-ease] Error in accordion onExpand callback:",d)}}function A(g){if(g<0||g>=e.length){console.error(`[aria-ease] Invalid accordion index: ${g}`);return}let T=e[g],L=t[g];if(T.setAttribute("aria-expanded","false"),L.style.display="none",s?.onCollapse)try{s.onCollapse(g)}catch(d){console.error("[aria-ease] Error in accordion onCollapse callback:",d)}}function H(g){e[g].getAttribute("aria-expanded")==="true"?A(g):(o||e.forEach((d,w)=>{w!==g&&A(w)}),v(g))}function C(g){return()=>{H(g)}}function $(g){return T=>{let{key:L}=T;switch(L){case"Enter":case" ":T.preventDefault(),H(g);break;case"ArrowDown":T.preventDefault();{let d=(g+1)%e.length;e[d].focus()}break;case"ArrowUp":T.preventDefault();{let d=(g-1+e.length)%e.length;e[d].focus()}break;case"Home":T.preventDefault(),e[0].focus();break;case"End":T.preventDefault(),e[e.length-1].focus();break}}}function a(){e.forEach((g,T)=>{let L=C(T),d=$(T);g.addEventListener("click",L),g.addEventListener("keydown",d),r.set(g,d),m.set(g,L)})}function l(){e.forEach(g=>{let T=r.get(g),L=m.get(g);T&&(g.removeEventListener("keydown",T),r.delete(g)),L&&(g.removeEventListener("click",L),m.delete(g))})}function k(){l(),e.forEach((g,T)=>{A(T)})}function x(){l();let g=Array.from(i.querySelectorAll(`.${n}`)),T=Array.from(i.querySelectorAll(`.${h}`));e.length=0,e.push(...g),t.length=0,t.push(...T),p(),a()}return p(),a(),{expandItem:v,collapseItem:A,toggleItem:H,cleanup:k,refresh:x}}function P(c){if(c.tagName!=="INPUT")return!1;let n=c.type;return["text","email","password","tel","number"].includes(n)}function N(c){return c.tagName==="TEXTAREA"}function z(c){return c.tagName==="BUTTON"||c.tagName==="INPUT"&&["button","submit","reset"].includes(c.type)}function ee(c){return c.tagName==="A"}function F(c,n,h){let o=c.length,s=(n+h+o)%o;c.item(s).focus()}function te(c){return c.getAttribute("data-custom-click")!==null&&c.getAttribute("data-custom-click")!==void 0}function W(c,n,h){let o=n.item(h);switch(c.key){case"ArrowUp":case"ArrowLeft":{(!P(o)&&!N(o)||(P(o)||N(o))&&o.selectionStart===0)&&(c.preventDefault(),F(n,h,-1));break}case"ArrowDown":case"ArrowRight":{if(!P(o)&&!N(o))c.preventDefault(),F(n,h,1);else if(P(o)||N(o)){let s=o.value;o.selectionStart===s.length&&(c.preventDefault(),F(n,h,1))}break}case"Escape":{c.preventDefault();break}case"Enter":case" ":{(!z(o)&&!ee(o)&&te(o)||z(o))&&(c.preventDefault(),o.click());break}case"Tab":break;default:break}}function Se({blockId:c,blockItemsClass:n}){let h=document.querySelector(`#${c}`);if(!h)return console.error(`[aria-ease] Element with id="${c}" not found. Make sure the block element exists before calling makeBlockAccessible.`),{cleanup:()=>{}};let o=null;function s(){return o||(o=h.querySelectorAll(`.${n}`)),o}let i=s();if(!i||i.length===0)return console.error(`[aria-ease] Element with class="${n}" not found. Make sure the block items exist before calling makeBlockAccessible.`),{cleanup:()=>{}};let e=new Map;i.forEach(m=>{if(!e.has(m)){let p=v=>{let A=h.querySelectorAll(`.${n}`),H=Array.prototype.indexOf.call(A,m);W(v,A,H)};m.addEventListener("keydown",p),e.set(m,p)}});function t(){i.forEach(m=>{let p=e.get(m);p&&(m.removeEventListener("keydown",p),e.delete(m))})}function r(){o=null}return{cleanup:t,refresh:r}}function $e({checkboxGroupId:c,checkboxesClass:n}){let h=document.querySelector(`#${c}`);if(!h)return console.error(`[aria-ease] Element with id="${c}" not found. Make sure the checkbox group container exists before calling makeCheckboxAccessible.`),{cleanup:()=>{}};let o=Array.from(h.querySelectorAll(`.${n}`));if(o.length===0)return console.error(`[aria-ease] No elements with class="${n}" found. Make sure checkboxes exist before calling makeCheckboxAccessible.`),{cleanup:()=>{}};let s=new WeakMap,i=new WeakMap;function e(){h.getAttribute("role")||h.setAttribute("role","group"),o.forEach(a=>{a.setAttribute("role","checkbox"),a.hasAttribute("aria-checked")||a.setAttribute("aria-checked","false"),a.hasAttribute("tabindex")||a.setAttribute("tabindex","0")})}function t(a){if(a<0||a>=o.length){console.error(`[aria-ease] Invalid checkbox index: ${a}`);return}let l=o[a],k=l.getAttribute("aria-checked")==="true";l.setAttribute("aria-checked",k?"false":"true")}function r(a,l){if(a<0||a>=o.length){console.error(`[aria-ease] Invalid checkbox index: ${a}`);return}o[a].setAttribute("aria-checked",l?"true":"false")}function m(a){return()=>{t(a)}}function p(a){return l=>{let{key:k}=l;switch(k){case" ":l.preventDefault(),t(a);break}}}function v(){o.forEach((a,l)=>{let k=m(l),x=p(l);a.addEventListener("click",k),a.addEventListener("keydown",x),s.set(a,x),i.set(a,k)})}function A(){o.forEach(a=>{let l=s.get(a),k=i.get(a);l&&(a.removeEventListener("keydown",l),s.delete(a)),k&&(a.removeEventListener("click",k),i.delete(a))})}function H(){A()}function C(){return o.map(a=>a.getAttribute("aria-checked")==="true")}function $(){return o.map((a,l)=>a.getAttribute("aria-checked")==="true"?l:-1).filter(a=>a!==-1)}return e(),v(),{toggleCheckbox:t,setCheckboxState:r,getCheckedStates:C,getCheckedIndices:$,cleanup:H}}function ne({menuId:c,menuItemsClass:n,triggerId:h,callback:o}){let s=document.querySelector(`#${c}`);if(!s)return console.error(`[aria-ease] Element with id="${c}" not found. Make sure the menu element exists before calling makeMenuAccessible.`),{openMenu:()=>{},closeMenu:()=>{},cleanup:()=>{}};let i=document.querySelector(`#${h}`);if(!i)return console.error(`[aria-ease] Element with id="${h}" not found. Make sure the trigger button element exists before calling makeMenuAccessible.`),{openMenu:()=>{},closeMenu:()=>{},cleanup:()=>{}};if(!/^[\w-]+$/.test(c))return console.error("[aria-ease] Invalid menuId: must be alphanumeric"),{openMenu:()=>{},closeMenu:()=>{},cleanup:()=>{}};i.setAttribute("aria-haspopup","true"),i.setAttribute("aria-controls",c),i.setAttribute("aria-expanded","false"),s.setAttribute("role","menu"),s.setAttribute("aria-labelledby",h);let e=new WeakMap,t=new Map,r=null,m=null;function p(){return r||(r=s.querySelectorAll(`.${n}`)),r}function v(){if(!m){let f=p();m=[];for(let y=0;y<f.length;y++){let M=f.item(y),D=x(M),I=M.getAttribute("aria-disabled")==="true";D||(M.hasAttribute("tabindex")||M.setAttribute("tabindex","-1"),I||m.push(M))}}return m}function A(f){return{length:f.length,item:M=>f[M],forEach:M=>{f.forEach(M)},[Symbol.iterator]:function*(){for(let M of f)yield M}}}function H(){p().forEach(y=>{y.setAttribute("role","menuitem");let M=y.getAttribute("data-submenu-id")??y.getAttribute("aria-controls"),D=y.hasAttribute("aria-haspopup")&&M;M&&(y.hasAttribute("data-submenu-id")||D)&&(y.setAttribute("aria-haspopup","menu"),y.setAttribute("aria-controls",M),y.hasAttribute("aria-expanded")||y.setAttribute("aria-expanded","false"))})}function C(f,y,M){let D=f.length,I=(y+M+D)%D;f.item(I).focus()}function $(f,y){f.length!==0&&f[y]?.focus()}function a(f){return f.hasAttribute("aria-controls")&&f.hasAttribute("aria-haspopup")&&f.getAttribute("role")==="menuitem"}function l(f){let y=f;for(;y&&y.getAttribute("role")==="menuitem";){let M=y.closest('[role="menu"]');if(!M)break;M.style.display="none",y.setAttribute("aria-expanded","false");let D=M.getAttribute("aria-labelledby");if(!D)break;let I=document.getElementById(D);if(!I)break;y=I}}H();function k(f,y,M){switch(f.key){case"ArrowLeft":{if(f.key==="ArrowLeft"&&i.getAttribute("role")==="menuitem"){f.preventDefault(),b();return}break}case"ArrowUp":{f.preventDefault(),C(A(v()),M,-1);break}case"ArrowRight":{if(f.key==="ArrowRight"&&a(y)){f.preventDefault();let D=y.getAttribute("aria-controls");if(D){T(D);return}}break}case"ArrowDown":{f.preventDefault(),C(A(v()),M,1);break}case"Home":{f.preventDefault(),$(v(),0);break}case"End":{f.preventDefault();let D=v();$(D,D.length-1);break}case"Escape":{f.preventDefault(),b(),i.focus(),L&&L(!1);break}case"Enter":case" ":{if(f.preventDefault(),a(y)){let D=y.getAttribute("aria-controls");if(D){T(D);return}}y.click(),b(),L&&L(!1);break}case"Tab":{b(),l(i),L&&L(!1);break}default:break}}function x(f){let y=f.parentElement;for(;y&&y!==s;){if(y.getAttribute("role")==="menu"||y.id&&s.querySelector(`[aria-controls="${y.id}"]`))return!0;y=y.parentElement}return!1}function g(f){i.setAttribute("aria-expanded",f?"true":"false")}function T(f){let y=t.get(f);if(!y){let M=s.querySelector(`[aria-controls="${f}"]`);if(!M){console.error(`[aria-ease] Submenu trigger with aria-controls="${f}" not found in menu "${c}".`);return}if(!M.id){let I=`trigger-${f}`;M.id=I,console.warn(`[aria-ease] Submenu trigger for "${f}" had no ID. Auto-generated ID: "${I}".`)}if(!document.querySelector(`#${f}`)){console.error(`[aria-ease] Submenu element with id="${f}" not found. Cannot create submenu instance.`);return}y=ne({menuId:f,menuItemsClass:n,triggerId:M.id,callback:o}),t.set(f,y)}y.openMenu()}function L(f){if(o?.onOpenChange)try{o.onOpenChange(f)}catch(y){console.error("[aria-ease] Error in menu onOpenChange callback:",y)}}function d(){v().forEach((y,M)=>{if(!e.has(y)){let D=I=>k(I,y,M);y.addEventListener("keydown",D),e.set(y,D)}})}function w(){v().forEach(y=>{let M=e.get(y);M&&(y.removeEventListener("keydown",M),e.delete(y))})}function u(){g(!0),s.style.display="block";let f=v();if(d(),f&&f.length>0&&f[0].focus(),o?.onOpenChange)try{o.onOpenChange(!0)}catch(y){console.error("[aria-ease] Error in menu onOpenChange callback:",y)}}function b(){if(t.forEach(f=>f.closeMenu()),g(!1),s.style.display="none",w(),i.focus(),o?.onOpenChange)try{o.onOpenChange(!1)}catch(f){console.error("[aria-ease] Error in menu onOpenChange callback:",f)}}function E(){i.getAttribute("aria-expanded")==="true"?b():u()}function S(f){if(!(i.getAttribute("aria-expanded")==="true"))return;let M=i.contains(f.target),D=s.contains(f.target);!M&&!D&&b()}i.addEventListener("click",E),document.addEventListener("click",S);function q(){w(),i.removeEventListener("click",E),document.removeEventListener("click",S),s.style.display="none",g(!1),t.forEach(f=>f.cleanup()),t.clear()}function O(){r=null,m=null}return{openMenu:u,closeMenu:b,cleanup:q,refresh:O}}function Oe({radioGroupId:c,radiosClass:n,defaultSelectedIndex:h=0}){let o=document.querySelector(`#${c}`);if(!o)return console.error(`[aria-ease] Element with id="${c}" not found. Make sure the radio group container exists before calling makeRadioAccessible.`),{cleanup:()=>{}};let s=Array.from(o.querySelectorAll(`.${n}`));if(s.length===0)return console.error(`[aria-ease] No elements with class="${n}" found. Make sure radio buttons exist before calling makeRadioAccessible.`),{cleanup:()=>{}};let i=new WeakMap,e=new WeakMap,t=h;function r(){o.getAttribute("role")||o.setAttribute("role","radiogroup"),s.forEach((a,l)=>{a.setAttribute("role","radio"),a.setAttribute("tabindex",l===t?"0":"-1"),l===t?a.setAttribute("aria-checked","true"):a.setAttribute("aria-checked","false")})}function m(a){if(a<0||a>=s.length){console.error(`[aria-ease] Invalid radio index: ${a}`);return}t>=0&&t<s.length&&(s[t].setAttribute("aria-checked","false"),s[t].setAttribute("tabindex","-1")),s[a].setAttribute("aria-checked","true"),s[a].setAttribute("tabindex","0"),s[a].focus(),t=a}function p(a){return()=>{m(a)}}function v(a){return l=>{let{key:k}=l,x=a;switch(k){case"ArrowDown":case"ArrowRight":l.preventDefault(),x=(a+1)%s.length,m(x);break;case"ArrowUp":case"ArrowLeft":l.preventDefault(),x=(a-1+s.length)%s.length,m(x);break;case" ":case"Enter":l.preventDefault(),m(a);break}}}function A(){s.forEach((a,l)=>{let k=p(l),x=v(l);a.addEventListener("click",k),a.addEventListener("keydown",x),i.set(a,x),e.set(a,k)})}function H(){s.forEach(a=>{let l=i.get(a),k=e.get(a);l&&(a.removeEventListener("keydown",l),i.delete(a)),k&&(a.removeEventListener("click",k),e.delete(a))})}function C(){H()}function $(){return t}return r(),A(),{selectRadio:m,getSelectedIndex:$,cleanup:C}}function Ne({toggleId:c,togglesClass:n,isSingleToggle:h=!0}){let o=document.querySelector(`#${c}`);if(!o)return console.error(`[aria-ease] Element with id="${c}" not found. Make sure the toggle element exists before calling makeToggleAccessible.`),{cleanup:()=>{}};let s;if(h)s=[o];else{if(!n)return console.error("[aria-ease] togglesClass is required when isSingleToggle is false."),{cleanup:()=>{}};if(s=Array.from(o.querySelectorAll(`.${n}`)),s.length===0)return console.error(`[aria-ease] No elements with class="${n}" found. Make sure toggle buttons exist before calling makeToggleAccessible.`),{cleanup:()=>{}}}let i=new WeakMap,e=new WeakMap;function t(){s.forEach(l=>{l.tagName.toLowerCase()!=="button"&&!l.getAttribute("role")&&l.setAttribute("role","button"),l.hasAttribute("aria-pressed")||l.setAttribute("aria-pressed","false"),l.hasAttribute("tabindex")||l.setAttribute("tabindex","0")})}function r(l){if(l<0||l>=s.length){console.error(`[aria-ease] Invalid toggle index: ${l}`);return}let k=s[l],x=k.getAttribute("aria-pressed")==="true";k.setAttribute("aria-pressed",x?"false":"true")}function m(l,k){if(l<0||l>=s.length){console.error(`[aria-ease] Invalid toggle index: ${l}`);return}s[l].setAttribute("aria-pressed",k?"true":"false")}function p(l){return()=>{r(l)}}function v(l){return k=>{let{key:x}=k;switch(x){case"Enter":case" ":k.preventDefault(),r(l);break}}}function A(){s.forEach((l,k)=>{let x=p(k),g=v(k);l.addEventListener("click",x),l.addEventListener("keydown",g),i.set(l,g),e.set(l,x)})}function H(){s.forEach(l=>{let k=i.get(l),x=e.get(l);k&&(l.removeEventListener("keydown",k),i.delete(l)),x&&(l.removeEventListener("click",x),e.delete(l))})}function C(){H()}function $(){return s.map(l=>l.getAttribute("aria-pressed")==="true")}function a(){return s.map((l,k)=>l.getAttribute("aria-pressed")==="true"?k:-1).filter(l=>l!==-1)}return t(),A(),{toggleButton:r,setPressed:m,getPressedStates:$,getPressedIndices:a,cleanup:C}}function Re({comboboxInputId:c,comboboxButtonId:n,listBoxId:h,listBoxItemsClass:o,callback:s}){let i=document.getElementById(`${c}`);if(!i)return console.error(`[aria-ease] Element with id="${c}" not found. Make sure the combobox input element exists before calling makeComboboxAccessible.`),{cleanup:()=>{}};let e=document.getElementById(`${h}`);if(!e)return console.error(`[aria-ease] Element with id="${h}" not found. Make sure the combobox listbox element exists before calling makeComboboxAccessible.`),{cleanup:()=>{}};let t=n?document.getElementById(`${n}`):null,r=-1;i.setAttribute("role","combobox"),i.setAttribute("aria-autocomplete","list"),i.setAttribute("aria-controls",h),i.setAttribute("aria-expanded","false"),i.setAttribute("aria-haspopup","listbox"),e.setAttribute("role","listbox");let m=null;function p(){return m||(m=e.querySelectorAll(`.${o}`)),Array.from(m).filter(u=>!u.hidden&&u.style.display!=="none")}function v(){return i.getAttribute("aria-expanded")==="true"}function A(u){let b=p();if(u>=0&&u<b.length){let E=b[u],S=E.id||`${h}-option-${u}`;if(E.id||(E.id=S),i.setAttribute("aria-activedescendant",S),typeof E.scrollIntoView=="function"&&E.scrollIntoView({block:"nearest",behavior:"smooth"}),s?.onActiveDescendantChange)try{s.onActiveDescendantChange(S,E)}catch(q){console.error("[aria-ease] Error in combobox onActiveDescendantChange callback:",q)}}else i.setAttribute("aria-activedescendant","");r=u}function H(){if(i.setAttribute("aria-expanded","true"),e.style.display="block",s?.onOpenChange)try{s.onOpenChange(!0)}catch(u){console.error("[aria-ease] Error in combobox onOpenChange callback:",u)}}function C(){if(i.setAttribute("aria-expanded","false"),i.setAttribute("aria-activedescendant",""),e.style.display="none",r=-1,s?.onOpenChange)try{s.onOpenChange(!1)}catch(u){console.error("[aria-ease] Error in combobox onOpenChange callback:",u)}}function $(u){let b=u.textContent?.trim()||"";if(i.value=b,u.setAttribute("aria-selected","true"),C(),s?.onSelect)try{s.onSelect(u)}catch(E){console.error("[aria-ease] Error in combobox onSelect callback:",E)}}function a(u){let b=p(),E=v();switch(u.key){case"ArrowDown":if(u.preventDefault(),!E){H();return}if(b.length===0)return;{let S=r>=b.length-1?0:r+1;A(S)}break;case"ArrowUp":if(u.preventDefault(),!E)return;if(b.length>0){let S=r<=0?b.length-1:r-1;A(S)}break;case"Enter":E&&r>=0&&r<b.length&&(u.preventDefault(),$(b[r]));break;case"Escape":if(E)u.preventDefault(),C();else if(i.value&&(u.preventDefault(),i.value="",i.setAttribute("aria-activedescendant",""),p().forEach(q=>{q.getAttribute("aria-selected")==="true"&&q.setAttribute("aria-selected","false")}),s?.onClear))try{s.onClear()}catch(q){console.error("[aria-ease] Error in combobox onClear callback:",q)}break;case"Home":E&&b.length>0&&(u.preventDefault(),A(0));break;case"End":E&&b.length>0&&(u.preventDefault(),A(b.length-1));break;case"Tab":E&&r>=0&&r<b.length&&$(b[r]),E&&C();break}}function l(u){let b=u.target;if(b.classList.contains(o)){let S=p().indexOf(b);S>=0&&A(S)}}function k(u){let b=u.target;b.classList.contains(o)&&(u.preventDefault(),$(b))}function x(u){let b=u.target;!i.contains(b)&&!e.contains(b)&&(!t||!t.contains(b))&&C()}function g(){v()?C():(H(),i.focus())}function T(u){(u.key==="Enter"||u.key===" ")&&(u.preventDefault(),g())}i.addEventListener("keydown",a),e.addEventListener("mousemove",l),e.addEventListener("mousedown",k),document.addEventListener("mousedown",x),t&&(t.setAttribute("tabindex","-1"),t.setAttribute("aria-label","Toggle options"),t.addEventListener("click",g),t.addEventListener("keydown",T));function L(){let u=e.querySelectorAll(`.${o}`);if(u.length===0)return;let b=null;for(let E of u)if(E.getAttribute("aria-selected")==="true"){b=E.textContent?.trim()||null;break}!b&&i.value&&(b=i.value.trim()),u.forEach((E,S)=>{E.setAttribute("role","option");let q=E.textContent?.trim()||"";b&&q===b?E.setAttribute("aria-selected","true"):E.setAttribute("aria-selected","false");let O=E.getAttribute("id");if(!O||O===""){let f=`${h}-option-${S}`;E.id=f,E.setAttribute("id",f)}})}L();function d(){i.removeEventListener("keydown",a),e.removeEventListener("mousemove",l),e.removeEventListener("mousedown",k),document.removeEventListener("mousedown",x),t&&(t.removeEventListener("click",g),t.removeEventListener("keydown",T))}function w(){m=null,L(),r=-1,A(-1)}return{cleanup:d,refresh:w,openListbox:H,closeListbox:C}}function _e({tabListId:c,tabsClass:n,tabPanelsClass:h,orientation:o="horizontal",activateOnFocus:s=!0,callback:i}){let e=document.querySelector(`#${c}`);if(!e)return console.error(`[aria-ease] Element with id="${c}" not found. Make sure the tab list container exists before calling makeTabsAccessible.`),{cleanup:()=>{}};let t=Array.from(e.querySelectorAll(`.${n}`));if(t.length===0)return console.error(`[aria-ease] No elements with class="${n}" found. Make sure tab buttons exist before calling makeTabsAccessible.`),{cleanup:()=>{}};let r=Array.from(document.querySelectorAll(`.${h}`));if(r.length===0)return console.error(`[aria-ease] No elements with class="${h}" found. Make sure tab panels exist before calling makeTabsAccessible.`),{cleanup:()=>{}};if(t.length!==r.length)return console.error(`[aria-ease] Tab/panel mismatch: found ${t.length} tabs but ${r.length} panels.`),{cleanup:()=>{}};let m=new WeakMap,p=new WeakMap,v=new WeakMap,A=0;function H(){e.setAttribute("role","tablist"),e.setAttribute("aria-orientation",o),t.forEach((d,w)=>{let u=r[w];d.id||(d.id=`${c}-tab-${w}`),u.id||(u.id=`${c}-panel-${w}`),d.setAttribute("role","tab"),d.setAttribute("aria-controls",u.id),d.setAttribute("aria-selected","false"),d.setAttribute("tabindex","-1"),u.setAttribute("role","tabpanel"),u.setAttribute("aria-labelledby",d.id),u.hidden=!0,u.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')||u.setAttribute("tabindex","0")}),C(0,!1)}function C(d,w=!0){if(d<0||d>=t.length){console.error(`[aria-ease] Invalid tab index: ${d}`);return}let u=A;t.forEach((S,q)=>{let O=r[q];S.setAttribute("aria-selected","false"),S.setAttribute("tabindex","-1"),O.hidden=!0});let b=t[d],E=r[d];if(b.setAttribute("aria-selected","true"),b.setAttribute("tabindex","0"),E.hidden=!1,w&&b.focus(),A=d,i?.onTabChange&&u!==d)try{i.onTabChange(d,u)}catch(S){console.error("[aria-ease] Error in tabs onTabChange callback:",S)}}function $(d){let w=t.findIndex(E=>E===document.activeElement),u=w!==-1?w:A,b=u;switch(d){case"first":b=0;break;case"last":b=t.length-1;break;case"next":b=(u+1)%t.length;break;case"prev":b=(u-1+t.length)%t.length;break}if(t[b].focus(),t[b].setAttribute("tabindex","0"),t[A].setAttribute("tabindex","-1"),s)C(b,!1);else{let E=A;t.forEach((S,q)=>{q===b?S.setAttribute("tabindex","0"):q!==E&&S.setAttribute("tabindex","-1")})}}function a(d){return()=>{C(d)}}function l(d){return w=>{let{key:u}=w,b=!1;if(o==="horizontal")switch(u){case"ArrowLeft":w.preventDefault(),$("prev"),b=!0;break;case"ArrowRight":w.preventDefault(),$("next"),b=!0;break}else switch(u){case"ArrowUp":w.preventDefault(),$("prev"),b=!0;break;case"ArrowDown":w.preventDefault(),$("next"),b=!0;break}if(!b)switch(u){case"Home":w.preventDefault(),$("first");break;case"End":w.preventDefault(),$("last");break;case" ":case"Enter":s||(w.preventDefault(),C(d));break;case"F10":if(w.shiftKey&&i?.onContextMenu){w.preventDefault();try{i.onContextMenu(d,t[d])}catch(E){console.error("[aria-ease] Error in tabs onContextMenu callback:",E)}}break}}}function k(d){return w=>{if(i?.onContextMenu){w.preventDefault();try{i.onContextMenu(d,t[d])}catch(u){console.error("[aria-ease] Error in tabs onContextMenu callback:",u)}}}}function x(){t.forEach((d,w)=>{let u=a(w),b=l(w),E=k(w);d.addEventListener("click",u),d.addEventListener("keydown",b),i?.onContextMenu&&(d.addEventListener("contextmenu",E),v.set(d,E)),m.set(d,b),p.set(d,u)})}function g(){t.forEach(d=>{let w=m.get(d),u=p.get(d),b=v.get(d);w&&(d.removeEventListener("keydown",w),m.delete(d)),u&&(d.removeEventListener("click",u),p.delete(d)),b&&(d.removeEventListener("contextmenu",b),v.delete(d))})}function T(){g(),t.forEach((d,w)=>{let u=r[w];d.removeAttribute("role"),d.removeAttribute("aria-selected"),d.removeAttribute("aria-controls"),d.removeAttribute("tabindex"),u.removeAttribute("role"),u.removeAttribute("aria-labelledby"),u.removeAttribute("tabindex"),u.hidden=!1}),e.removeAttribute("role"),e.removeAttribute("aria-orientation")}function L(){g();let d=Array.from(e.querySelectorAll(`.${n}`)),w=Array.from(document.querySelectorAll(`.${h}`));t.length=0,t.push(...d),r.length=0,r.push(...w),H(),x()}return H(),x(),{activateTab:C,cleanup:T,refresh:L}}var G={"popup.open":{setup:[{when:["keyboard","textInput"],steps:()=>[{type:"keypress",target:"input",key:"ArrowDown"}]},{when:["pointer"],steps:()=>[{type:"click",target:"button"}]}],assertion:re},"popup.closed":{setup:[{when:["keyboard"],steps:()=>[]},{when:["pointer"],steps:()=>[]}],assertion:[...se(),...J()]},"main.focused":{setup:[{when:["keyboard"],steps:()=>[{type:"focus",target:"main"}]}],assertion:ce},"main.notFocused":{setup:[{when:["keyboard"],steps:()=>[]}],assertion:le},"input.filled":{setup:[{when:["keyboard","textInput"],steps:()=>[{type:"type",target:"input",value:"test"}]}],assertion:ue},"input.notFilled":{setup:[{when:["keyboard","textInput"],steps:()=>[{type:"type",target:"input",value:""}]}],assertion:pe},"activeOption.first":{requires:["popup.open"],setup:[{when:["keyboard"],steps:()=>[{type:"keypress",target:"input",key:"ArrowDown"}]}],assertion:ie},"activeOption.last":{requires:["activeOption.first"],setup:[{when:["keyboard"],steps:()=>[{type:"keypress",target:"input",key:"ArrowUp"}]}],assertion:ae},"activeDescendant.notEmpty":{requires:[],setup:[{when:["keyboard"],steps:()=>[]}],assertion:oe},"activeDescendant.Empty":{requires:[],setup:[{when:["keyboard"],steps:()=>[]}],assertion:J},"selectedOption.first":{requires:["popup.open"],setup:[{when:["pointer"],steps:()=>[{type:"click",target:"relative",relativeTarget:"first"}]}],assertion:()=>j("first")},"selectedOption.last":{requires:["popup.open"],setup:[{when:["pointer"],steps:()=>[{type:"click",target:"relative",relativeTarget:"last"}]}],assertion:()=>j("last")}};function re(){return[{target:"popup",assertion:"toBeVisible",failureMessage:"Expected popup to be visible"},{target:"main",assertion:"toHaveAttribute",attribute:"aria-expanded",expectedValue:"true",failureMessage:"Expect combobox main to have aria-expanded='true'."}]}function se(){return[{target:"popup",assertion:"notToBeVisible",failureMessage:"Expected popup to be closed"},{target:"main",assertion:"toHaveAttribute",attribute:"aria-expanded",expectedValue:"false",failureMessage:"Expect combobox main to have aria-expanded='false'."}]}function ie(){return[{target:"main",assertion:"toHaveAttribute",attribute:"aria-activedescendant",expectedValue:{ref:"relative",relativeTarget:"first",property:"id"},failureMessage:"Expected aria-activedescendant on main to match the id of the first option."}]}function ae(){return[{target:"main",assertion:"toHaveAttribute",attribute:"aria-activedescendant",expectedValue:{ref:"relative",relativeTarget:"last",property:"id"},failureMessage:"Expected aria-activedescendant on main to match the id of the last option."}]}function oe(){return[{target:"main",assertion:"toHaveAttribute",attribute:"aria-activedescendant",expectedValue:"!empty",failureMessage:"Expected aria-activedescendant on main to not be empty."}]}function J(){return[{target:"main",assertion:"toHaveAttribute",attribute:"aria-activedescendant",expectedValue:"",failureMessage:"Expected aria-activedescendant on main to be empty."}]}function j(c){return[{target:"relative",relativeTarget:c,assertion:"toHaveAttribute",attribute:"aria-selected",expectedValue:"true",failureMessage:`Expected ${c} option to have aria-selected='true'.`}]}function ce(){return[{target:"main",assertion:"toHaveFocus",failureMessage:"Expected main to be focused."}]}function le(){return[{target:"main",assertion:"notToHaveFocus",failureMessage:"Expected main to not have focused."}]}function ue(){return[{target:"input",assertion:"toHaveValue",expectedValue:"test",failureMessage:"Expected input to have the value 'test'."}]}function pe(){return[{target:"input",assertion:"toHaveValue",expectedValue:"",failureMessage:"Expected input to have the value ''."}]}var X={"popup.open":{setup:[{when:["keyboard"],steps:()=>[{type:"keypress",target:"main",key:"Enter"}]},{when:["pointer"],steps:()=>[{type:"click",target:"main"}]}],assertion:de},"popup.closed":{setup:[{when:["keyboard"],steps:()=>[]},{when:["pointer"],steps:()=>[]}],assertion:fe},"main.focused":{setup:[{when:["keyboard"],steps:()=>[{type:"focus",target:"main"}]}],assertion:me},"main.notFocused":{setup:[{when:["keyboard"],steps:()=>[]}],assertion:be},"activeItem.first":{requires:["popup.open"],setup:[{when:["keyboard"],steps:()=>[]}],assertion:ge},"activeItem.last":{requires:["popup.open"],setup:[{when:["keyboard"],steps:()=>[{type:"keypress",target:"main",key:"ArrowUp"}]}],assertion:he},"submenu.open":{requires:["popup.open"],setup:[{when:["keyboard"],steps:()=>[{type:"keypress",target:"submenuTrigger",key:"ArrowRight"}]},{when:["pointer"],steps:()=>[{type:"click",target:"submenuTrigger"}]}],assertion:ye},"submenu.closed":{requires:["submenu.open"],setup:[{when:["keyboard"],steps:()=>[{type:"keypress",target:"submenuTrigger",key:"ArrowLeft"}]},{when:["pointer"],steps:()=>[{type:"click",target:"submenuTrigger"}]}],assertion:ve},"submenuTrigger.focused":{setup:[{when:["keyboard"],steps:()=>[{type:"focus",target:"submenuTrigger"}]}],assertion:Ae},"submenuTrigger.notFocused":{setup:[{when:["keyboard"],steps:()=>[]}],assertion:Ee},"submenuActiveItem.first":{requires:["submenu.open"],setup:[{when:["keyboard"],steps:()=>[]},{when:["pointer"],steps:()=>[]}],assertion:ke}};function de(){return[{target:"popup",assertion:"toBeVisible",failureMessage:"Expected popup to be visible"},{target:"main",assertion:"toHaveAttribute",attribute:"aria-expanded",expectedValue:"true",failureMessage:"Expect menu main to have aria-expanded='true'."}]}function fe(){return[{target:"popup",assertion:"notToBeVisible",failureMessage:"Expected popup to be closed"},{target:"main",assertion:"toHaveAttribute",attribute:"aria-expanded",expectedValue:"false",failureMessage:"Expect menu main to have aria-expanded='false'."}]}function me(){return[{target:"main",assertion:"toHaveFocus",failureMessage:"Expected menu main to be focused."}]}function be(){return[{target:"main",assertion:"notToHaveFocus",failureMessage:"Expected menu main to not have focused."}]}function ge(){return[{target:"relative",assertion:"toHaveFocus",expectedValue:"first",failureMessage:"First menu item should have focus."}]}function he(){return[{target:"relative",assertion:"toHaveFocus",expectedValue:"last",failureMessage:"Last menu item should have focus."}]}function ye(){return[{target:"submenu",assertion:"toBeVisible",failureMessage:"Expected submenu to be visible"},{target:"submenuTrigger",assertion:"toHaveAttribute",attribute:"aria-expanded",expectedValue:"true",failureMessage:"Expect submenu trigger to have aria-expanded='true'."}]}function ve(){return[{target:"submenu",assertion:"notToBeVisible",failureMessage:"Expected submenu to be closed"},{target:"submenuTrigger",assertion:"toHaveAttribute",attribute:"aria-expanded",expectedValue:"false",failureMessage:"Expect submenu trigger to have aria-expanded='false'."}]}function Ae(){return[{target:"submenuTrigger",assertion:"toHaveFocus",failureMessage:"Expected submenu trigger to be focused."}]}function Ee(){return[{target:"submenuTrigger",assertion:"notToHaveFocus",failureMessage:"Expected submenu trigger to not have focused."}]}function ke(){return[{target:"submenuItems",assertion:"toHaveFocus",failureMessage:"First interactive item in the submenu should have focus after Right Arrow open the submenu."}]}function we(c,n){return n.some(h=>c.capabilities.includes(h))}function R(c,n){Array.isArray(c)&&c.length&&!c[0].when&&(c=[{when:["keyboard"],steps:()=>c}]);for(let h of c)if(we(n,h.when))return h.steps(n);throw new Error(`No setup strategy matches capabilities: ${n.capabilities.join(", ")}`)}var Me={combobox:G,menu:X},_=class{constructor(n){this.jsonContract=n}toJSON(){return this.jsonContract}},K=class{constructor(n){this.componentName=n;this.statePack=Me[n]||{}}metaValue={};selectorsValue={};relationshipInvariants=[];staticAssertions=[];dynamicTests=[];statePack;meta(n){return this.metaValue=n,this}selectors(n){return this.selectorsValue=n,this}relationships(n){let h=this.statePack,o={capabilities:["keyboard"]},s=(e,t=new Set)=>{if(t.has(e))return[];t.add(e);let r=h[e];if(!r)return[];let m=[];if(Array.isArray(r.requires))for(let p of r.requires)m=m.concat(s(p,t));return r.setup&&(m=m.concat(R(r.setup,o))),m};return n({ariaReference:(e,t,r)=>({requires:m=>{let p=s(m,new Set);return{required:()=>this.relationshipInvariants.push({type:"aria-reference",from:e,attribute:t,to:r,level:"required",setup:p}),optional:()=>this.relationshipInvariants.push({type:"aria-reference",from:e,attribute:t,to:r,level:"optional",setup:p}),recommended:()=>this.relationshipInvariants.push({type:"aria-reference",from:e,attribute:t,to:r,level:"recommended",setup:p})}},required:()=>this.relationshipInvariants.push({type:"aria-reference",from:e,attribute:t,to:r,level:"required"}),optional:()=>this.relationshipInvariants.push({type:"aria-reference",from:e,attribute:t,to:r,level:"optional"}),recommended:()=>this.relationshipInvariants.push({type:"aria-reference",from:e,attribute:t,to:r,level:"recommended"})}),contains:(e,t)=>({requires:r=>{let m=s(r,new Set);return{required:()=>this.relationshipInvariants.push({type:"contains",parent:e,child:t,level:"required",setup:m}),optional:()=>this.relationshipInvariants.push({type:"contains",parent:e,child:t,level:"optional",setup:m}),recommended:()=>this.relationshipInvariants.push({type:"contains",parent:e,child:t,level:"recommended",setup:m})}},required:()=>this.relationshipInvariants.push({type:"contains",parent:e,child:t,level:"required"}),optional:()=>this.relationshipInvariants.push({type:"contains",parent:e,child:t,level:"optional"}),recommended:()=>this.relationshipInvariants.push({type:"contains",parent:e,child:t,level:"recommended"})})}),this}static(n){return n({target:o=>{let s=e=>{let t=this.statePack,r={capabilities:["keyboard"]},m=(p,v=new Set)=>{if(v.has(p))return[];v.add(p);let A=t[p];if(!A)return[];let H=[];if(Array.isArray(A.requires))for(let C of A.requires)H=H.concat(m(C,v));return A.setup&&(H=H.concat(R(A.setup,r))),H};return m(e,new Set)},i=(e,t,r)=>({required:()=>this.staticAssertions.push({target:o,attribute:e,expectedValue:t,failureMessage:"",level:"required",setup:r}),optional:()=>this.staticAssertions.push({target:o,attribute:e,expectedValue:t,failureMessage:"",level:"optional",setup:r}),recommended:()=>this.staticAssertions.push({target:o,attribute:e,expectedValue:t,failureMessage:"",level:"recommended",setup:r})});return{has:(e,t)=>({...i(e,t),requires:m=>{let p=s(m);return i(e,t,p)}})}}}),this}when(n){return new B(this,this.statePack,n)}addDynamicTest(n){this.dynamicTests.push(n)}build(){return{meta:this.metaValue,selectors:this.selectorsValue,relationships:this.relationshipInvariants.length?this.relationshipInvariants:void 0,static:this.staticAssertions.length?[{assertions:this.staticAssertions}]:[],dynamic:this.dynamicTests}}},B=class{constructor(n,h,o){this.parent=n;this.statePack=h;this.event=o}_as;_on;_given=[];_then=[];_desc="";_level="required";as(n){return this._as=n,this}on(n){return this._on=n,this}given(n){return this._given=Array.isArray(n)?n:[n],this}then(n){return this._then=Array.isArray(n)?n:[n],this}describe(n){return this._desc=n,this}required(){return this._level="required",this._finalize(),this.parent}optional(){return this._level="optional",this._finalize(),this.parent}recommended(){return this._level="recommended",this._finalize(),this.parent}_finalize(){let o={capabilities:[{keypress:"keyboard",click:"pointer",type:"textInput",focus:"keyboard",hover:"pointer"}[this._as||"keyboard"]||this._as||"keyboard"]},s=(r,m=new Set)=>{if(m.has(r))return[];m.add(r);let p=this.statePack[r];if(!p)return[];let v=[];if(Array.isArray(p.requires))for(let A of p.requires)v=v.concat(s(A,m));return p.setup&&(v=v.concat(R(p.setup,o))),v},i=[];for(let r of this._given)i.push(...s(r));let e=[];for(let r of this._then){let m=this.statePack[r];if(m&&m.assertion!==void 0){let p=m.assertion;if(typeof p=="function")try{p=p()}catch(v){throw new Error(`Error calling assertion function for state '${r}': ${v.message}`)}Array.isArray(p)?e.push(...p):e.push(p)}}let t=[{type:this._as,target:this._on,key:this._as==="keypress"?this.event:void 0}];this.parent.addDynamicTest({description:this._desc||"",level:this._level,action:t,assertions:e,...i.length?{setup:i}:{}})}};function Xe(c,n){let h=new K(c);return n(h),new _(h.build())}import Te from"path";async function Q(c,n,h={}){if(!c||typeof c!="string")throw new Error("\u274C testUiComponent requires a valid componentName (string)");if(!n)throw new Error("\u274C testUiComponent requires a URL");if(n&&typeof n!="string")throw new Error("\u274C testUiComponent url parameter must be a string");let o={violations:[]};async function s(p){try{let v=await fetch(p,{method:"HEAD",signal:AbortSignal.timeout(1e3)});if(v.ok||v.status===304)return p}catch{return null}return null}let i=V(h.strictness),e={},t=typeof process<"u"?process.cwd():"";if(typeof process<"u"&&typeof process.cwd=="function")try{let{loadConfig:p}=await import("./configLoader-ZEJVXLX7.js"),v=await p(process.cwd());if(e=v.config,v.configPath&&(t=Te.dirname(v.configPath)),h.strictness===void 0){let A=e.test?.components?.find(H=>H?.name===c)?.strictness;i=V(A??e.test?.strictness)}}catch{h.strictness===void 0&&(i="balanced")}let r;try{if(n){let p=await s(n);if(p){console.log(`\u{1F3AD} Running Playwright tests on ${p}`);let{runContractTestsPlaywright:v}=await import("./contractTestRunnerPlaywright-QPU6HZXG.js");r=await v(c,p,i,e,t)}else throw new Error(`\u274C Dev server not running at ${n}
|
|
2
|
+
Please start your dev server and try again.`)}else throw new Error("\u274C URL is required for component testing. Please provide a URL to run full accessibility tests with testUiComponent.")}catch(p){throw p instanceof Error?p:new Error(`\u274C Contract test execution failed: ${String(p)}`)}let m={violations:o.violations,raw:o,contract:r};if(r.failures.length>0&&n==="Playwright")throw new Error(`
|
|
3
|
+
\u274C ${r.failures.length} accessibility contract test${r.failures.length>1?"s":""} failed (Playwright mode)
|
|
4
|
+
\u2705 ${r.passes.length} test${r.passes.length>1?"s":""} passed
|
|
2621
5
|
|
|
2622
6
|
\u{1F4CB} Review the detailed test report above for specific failures.
|
|
2623
|
-
\u{1F4A1} Contract tests validate ARIA attributes and keyboard interactions per W3C APG guidelines.`
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
`
|
|
2637
|
-
\u274C ${violationCount} axe accessibility violation${violationCount > 1 ? "s" : ""} detected
|
|
2638
|
-
${violationDetails}
|
|
2639
|
-
|
|
2640
|
-
\u{1F4CB} Full details available in result.violations`
|
|
2641
|
-
);
|
|
2642
|
-
}
|
|
2643
|
-
return result;
|
|
2644
|
-
}
|
|
2645
|
-
var runTest = async () => {
|
|
2646
|
-
return {
|
|
2647
|
-
passes: [],
|
|
2648
|
-
failures: [],
|
|
2649
|
-
skipped: []
|
|
2650
|
-
};
|
|
2651
|
-
};
|
|
2652
|
-
if (typeof window === "undefined") {
|
|
2653
|
-
runTest = async () => {
|
|
2654
|
-
console.log(`\u{1F680} Running component accessibility tests...
|
|
2655
|
-
`);
|
|
2656
|
-
const { exec } = await import("child_process");
|
|
2657
|
-
const chalk = (await import("chalk")).default;
|
|
2658
|
-
return new Promise((resolve, reject) => {
|
|
2659
|
-
exec(
|
|
2660
|
-
`npx vitest --run --reporter verbose`,
|
|
2661
|
-
async (error, stdout, stderr) => {
|
|
2662
|
-
console.log(stdout);
|
|
2663
|
-
if (stderr) console.error(stderr);
|
|
2664
|
-
const testsPassed = !error || error.code === 0;
|
|
2665
|
-
if (testsPassed) {
|
|
2666
|
-
try {
|
|
2667
|
-
const { displayBadgeInfo, promptAddBadge } = await import("./badgeHelper-RDOMCC6E.js");
|
|
2668
|
-
displayBadgeInfo("component");
|
|
2669
|
-
await promptAddBadge("component", process.cwd());
|
|
2670
|
-
console.log(chalk.dim("\n" + "\u2500".repeat(60)));
|
|
2671
|
-
console.log(chalk.cyan("\u{1F499} Found aria-ease helpful?"));
|
|
2672
|
-
console.log(chalk.white(" \u2022 Star us on GitHub: ") + chalk.blue.underline("https://github.com/aria-ease/aria-ease"));
|
|
2673
|
-
console.log(chalk.white(" \u2022 Share feedback: ") + chalk.blue.underline("https://github.com/aria-ease/aria-ease/discussions"));
|
|
2674
|
-
console.log(chalk.dim("\u2500".repeat(60) + "\n"));
|
|
2675
|
-
} catch (badgeError) {
|
|
2676
|
-
console.error("Warning: Could not display badge prompt:", badgeError);
|
|
2677
|
-
}
|
|
2678
|
-
resolve({ passes: [], failures: [], skipped: [] });
|
|
2679
|
-
process.exit(0);
|
|
2680
|
-
} else {
|
|
2681
|
-
const exitCode = error?.code || 1;
|
|
2682
|
-
reject(new Error(`Tests failed with code ${exitCode}`));
|
|
2683
|
-
process.exit(exitCode);
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
);
|
|
2687
|
-
});
|
|
2688
|
-
};
|
|
2689
|
-
}
|
|
2690
|
-
async function cleanupTests() {
|
|
2691
|
-
await closeSharedBrowser();
|
|
2692
|
-
}
|
|
2693
|
-
export {
|
|
2694
|
-
cleanupTests,
|
|
2695
|
-
createContract,
|
|
2696
|
-
makeAccordionAccessible,
|
|
2697
|
-
makeBlockAccessible,
|
|
2698
|
-
makeCheckboxAccessible,
|
|
2699
|
-
makeComboboxAccessible,
|
|
2700
|
-
makeMenuAccessible,
|
|
2701
|
-
makeRadioAccessible,
|
|
2702
|
-
makeTabsAccessible,
|
|
2703
|
-
makeToggleAccessible,
|
|
2704
|
-
testUiComponent
|
|
2705
|
-
};
|
|
7
|
+
\u{1F4A1} Contract tests validate ARIA attributes and keyboard interactions per W3C APG guidelines.`);if(o.violations.length>0){let p=o.violations.length,v=o.violations.map(A=>`
|
|
8
|
+
- ${A.id}: ${A.description}
|
|
9
|
+
Impact: ${A.impact}
|
|
10
|
+
Affected elements: ${A.nodes.length}
|
|
11
|
+
Help: ${A.helpUrl}`).join(`
|
|
12
|
+
`);throw new Error(`
|
|
13
|
+
\u274C ${p} axe accessibility violation${p>1?"s":""} detected
|
|
14
|
+
${v}
|
|
15
|
+
|
|
16
|
+
\u{1F4CB} Full details available in result.violations`)}return m}var Y=async()=>({passes:[],failures:[],skipped:[]});typeof window>"u"&&(Y=async()=>{console.log(`\u{1F680} Running component accessibility tests...
|
|
17
|
+
`);let{exec:c}=await import("child_process"),n=(await import("chalk")).default;return new Promise((h,o)=>{c("npx vitest --run --reporter verbose",async(s,i,e)=>{if(console.log(i),e&&console.error(e),!s||s.code===0){try{let{displayBadgeInfo:r,promptAddBadge:m}=await import("./badgeHelper-IB5RTMAG.js");r("component"),await m("component",process.cwd()),console.log(n.dim(`
|
|
18
|
+
`+"\u2500".repeat(60))),console.log(n.cyan("\u{1F499} Found aria-ease helpful?")),console.log(n.white(" \u2022 Star us on GitHub: ")+n.blue.underline("https://github.com/aria-ease/aria-ease")),console.log(n.white(" \u2022 Share feedback: ")+n.blue.underline("https://github.com/aria-ease/aria-ease/discussions")),console.log(n.dim("\u2500".repeat(60)+`
|
|
19
|
+
`))}catch(r){console.error("Warning: Could not display badge prompt:",r)}h({passes:[],failures:[],skipped:[]}),process.exit(0)}else{let r=s?.code||1;o(new Error(`Tests failed with code ${r}`)),process.exit(r)}})})});async function Z(){await U()}export{Z as cleanupTests,Xe as createContract,Le as makeAccordionAccessible,Se as makeBlockAccessible,$e as makeCheckboxAccessible,Re as makeComboboxAccessible,ne as makeMenuAccessible,Oe as makeRadioAccessible,_e as makeTabsAccessible,Ne as makeToggleAccessible,Q as testUiComponent};
|