aria-ease 6.14.0 → 7.0.1

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.
Files changed (94) hide show
  1. package/README.md +1 -1
  2. package/dist/AccordionComponentStrategy-2SWMNUR6.js +1 -0
  3. package/dist/ComboboxComponentStrategy-YSYLR2U5.js +5 -0
  4. package/dist/MenuComponentStrategy-C22BZEBH.js +5 -0
  5. package/dist/RelativeTargetResolver-T4P25J2M.js +1 -0
  6. package/dist/TabsComponentStrategy-ADEEFJXM.js +1 -0
  7. package/dist/audit-WBKVW7H6.js +9 -0
  8. package/dist/badgeHelper-IB5RTMAG.js +11 -0
  9. package/dist/badgeHelper-JSROP5ML.js +1 -0
  10. package/dist/buildContracts-T4XQZBDU.js +13 -0
  11. package/dist/chunk-52I3INNG.js +11 -0
  12. package/dist/chunk-APUMBDOT.js +1 -0
  13. package/dist/chunk-BHNO4ZI3.js +1 -0
  14. package/dist/chunk-CNU4N4AY.js +1 -0
  15. package/dist/chunk-SM6ZKEDR.js +1 -0
  16. package/dist/chunk-ZNQ5BXVJ.js +1 -0
  17. package/dist/cli.cjs +132 -3560
  18. package/dist/cli.js +19 -161
  19. package/dist/configLoader-ZEJVXLX7.js +1 -0
  20. package/dist/configLoader-ZXTSCIP6.js +1 -0
  21. package/dist/contractTestRunnerPlaywright-FOCQTM4L.js +46 -0
  22. package/dist/contractTestRunnerPlaywright-QPU6HZXG.js +46 -0
  23. package/dist/formatters-H3CPDLG5.js +87 -0
  24. package/dist/index.cjs +64 -5103
  25. package/dist/index.d.cts +4 -6
  26. package/dist/index.d.ts +4 -6
  27. package/dist/index.js +17 -2703
  28. package/dist/src/accordion/index.cjs +1 -183
  29. package/dist/src/accordion/index.js +1 -181
  30. package/dist/src/block/index.cjs +1 -124
  31. package/dist/src/block/index.js +1 -122
  32. package/dist/src/checkbox/index.cjs +1 -109
  33. package/dist/src/checkbox/index.js +1 -107
  34. package/dist/src/combobox/index.cjs +1 -265
  35. package/dist/src/combobox/index.js +1 -263
  36. package/dist/src/menu/index.cjs +1 -339
  37. package/dist/src/menu/index.js +1 -337
  38. package/dist/src/radio/index.cjs +1 -117
  39. package/dist/src/radio/index.js +1 -115
  40. package/dist/src/tabs/index.cjs +1 -265
  41. package/dist/src/tabs/index.js +1 -263
  42. package/dist/src/toggle/index.cjs +1 -119
  43. package/dist/src/toggle/index.js +1 -117
  44. package/dist/src/utils/test/AccordionComponentStrategy-X2GSQ5KT.js +1 -0
  45. package/dist/src/utils/test/ComboboxComponentStrategy-SICWLI27.js +5 -0
  46. package/dist/src/utils/test/MenuComponentStrategy-R4VPAHDE.js +5 -0
  47. package/dist/src/utils/test/RelativeTargetResolver-UQQMZHI6.js +1 -0
  48. package/dist/src/utils/test/TabsComponentStrategy-L2PYNEW6.js +1 -0
  49. package/dist/src/utils/test/badgeHelper-ER5ZOHWF.js +11 -0
  50. package/dist/src/utils/test/chunk-APUMBDOT.js +1 -0
  51. package/dist/src/utils/test/chunk-BHNO4ZI3.js +1 -0
  52. package/dist/src/utils/test/configLoader-NCYRL2O6.js +1 -0
  53. package/dist/src/utils/test/contractTestRunnerPlaywright-YZCMF64Q.js +46 -0
  54. package/dist/src/utils/test/dsl/index.cjs +1 -838
  55. package/dist/src/utils/test/dsl/index.d.cts +2 -4
  56. package/dist/src/utils/test/dsl/index.d.ts +2 -4
  57. package/dist/src/utils/test/dsl/index.js +1 -836
  58. package/dist/src/utils/test/index.cjs +64 -2672
  59. package/dist/src/utils/test/index.d.cts +2 -2
  60. package/dist/src/utils/test/index.d.ts +2 -2
  61. package/dist/src/utils/test/index.js +16 -340
  62. package/dist/test-VXSCSKV5.js +19 -0
  63. package/package.json +8 -10
  64. package/dist/AccordionComponentStrategy-4ZEIQ2V6.js +0 -42
  65. package/dist/ComboboxComponentStrategy-DU342VMB.js +0 -64
  66. package/dist/MenuComponentStrategy-VYCC2XOM.js +0 -81
  67. package/dist/RelativeTargetResolver-DJAITO6D.js +0 -7
  68. package/dist/TabsComponentStrategy-3SQURPMX.js +0 -29
  69. package/dist/audit-JYEPKLHR.js +0 -63
  70. package/dist/badgeHelper-JOWO6RQG.js +0 -15
  71. package/dist/badgeHelper-RDOMCC6E.js +0 -108
  72. package/dist/buildContracts-VIV6GM56.js +0 -437
  73. package/dist/chunk-4DU5Z5BR.js +0 -340
  74. package/dist/chunk-GJGUY643.js +0 -182
  75. package/dist/chunk-GLT43UVH.js +0 -43
  76. package/dist/chunk-I2KLQ2HA.js +0 -22
  77. package/dist/chunk-JJEPLK7L.js +0 -107
  78. package/dist/chunk-PK5L2SAF.js +0 -17
  79. package/dist/configLoader-Q7N5XV4P.js +0 -183
  80. package/dist/configLoader-REHK3S3Q.js +0 -7
  81. package/dist/contractTestRunnerPlaywright-B2HLZKKK.js +0 -1394
  82. package/dist/contractTestRunnerPlaywright-RWK52C7S.js +0 -1394
  83. package/dist/formatters-32KQIIYS.js +0 -183
  84. package/dist/src/utils/test/AccordionComponentStrategy-WRHZOEN6.js +0 -38
  85. package/dist/src/utils/test/ComboboxComponentStrategy-XKQ72RFD.js +0 -60
  86. package/dist/src/utils/test/MenuComponentStrategy-6XWU5KLW.js +0 -77
  87. package/dist/src/utils/test/RelativeTargetResolver-G2XDN2VV.js +0 -1
  88. package/dist/src/utils/test/TabsComponentStrategy-BKG53SEV.js +0 -26
  89. package/dist/src/utils/test/badgeHelper-HZKGOPB4.js +0 -102
  90. package/dist/src/utils/test/chunk-4DU5Z5BR.js +0 -332
  91. package/dist/src/utils/test/chunk-GLT43UVH.js +0 -41
  92. package/dist/src/utils/test/configLoader-NA7IBCS3.js +0 -181
  93. package/dist/src/utils/test/contractTestRunnerPlaywright-5FIGA5G4.js +0 -1372
  94. package/dist/test-WDBS5JWO.js +0 -358
@@ -1,263 +1 @@
1
- // src/combobox/src/makeComboBoxAccessible/makeComboBoxAccessible.ts
2
- function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId, listBoxItemsClass, callback }) {
3
- const comboboxInput = document.getElementById(`${comboboxInputId}`);
4
- if (!comboboxInput) {
5
- console.error(`[aria-ease] Element with id="${comboboxInputId}" not found. Make sure the combobox input element exists before calling makeComboboxAccessible.`);
6
- return { cleanup: () => {
7
- } };
8
- }
9
- const listBox = document.getElementById(`${listBoxId}`);
10
- if (!listBox) {
11
- console.error(`[aria-ease] Element with id="${listBoxId}" not found. Make sure the combobox listbox element exists before calling makeComboboxAccessible.`);
12
- return { cleanup: () => {
13
- } };
14
- }
15
- const listButton = comboboxButtonId ? document.getElementById(`${comboboxButtonId}`) : null;
16
- let activeIndex = -1;
17
- comboboxInput.setAttribute("role", "combobox");
18
- comboboxInput.setAttribute("aria-autocomplete", "list");
19
- comboboxInput.setAttribute("aria-controls", listBoxId);
20
- comboboxInput.setAttribute("aria-expanded", "false");
21
- comboboxInput.setAttribute("aria-haspopup", "listbox");
22
- listBox.setAttribute("role", "listbox");
23
- let cachedItems = null;
24
- function getVisibleItems() {
25
- if (!cachedItems) {
26
- cachedItems = listBox.querySelectorAll(`.${listBoxItemsClass}`);
27
- }
28
- return Array.from(cachedItems).filter((item) => !item.hidden && item.style.display !== "none");
29
- }
30
- function isListboxOpen() {
31
- return comboboxInput.getAttribute("aria-expanded") === "true";
32
- }
33
- function setActiveDescendant(index) {
34
- const visibleItems = getVisibleItems();
35
- if (index >= 0 && index < visibleItems.length) {
36
- const activeItem = visibleItems[index];
37
- const itemId = activeItem.id || `${listBoxId}-option-${index}`;
38
- if (!activeItem.id) {
39
- activeItem.id = itemId;
40
- }
41
- comboboxInput.setAttribute("aria-activedescendant", itemId);
42
- if (typeof activeItem.scrollIntoView === "function") {
43
- activeItem.scrollIntoView({ block: "nearest", behavior: "smooth" });
44
- }
45
- if (callback?.onActiveDescendantChange) {
46
- try {
47
- callback.onActiveDescendantChange(itemId, activeItem);
48
- } catch (error) {
49
- console.error("[aria-ease] Error in combobox onActiveDescendantChange callback:", error);
50
- }
51
- }
52
- } else {
53
- comboboxInput.setAttribute("aria-activedescendant", "");
54
- }
55
- activeIndex = index;
56
- }
57
- function openListbox() {
58
- comboboxInput.setAttribute("aria-expanded", "true");
59
- listBox.style.display = "block";
60
- if (callback?.onOpenChange) {
61
- try {
62
- callback.onOpenChange(true);
63
- } catch (error) {
64
- console.error("[aria-ease] Error in combobox onOpenChange callback:", error);
65
- }
66
- }
67
- }
68
- function closeListbox() {
69
- comboboxInput.setAttribute("aria-expanded", "false");
70
- comboboxInput.setAttribute("aria-activedescendant", "");
71
- listBox.style.display = "none";
72
- activeIndex = -1;
73
- if (callback?.onOpenChange) {
74
- try {
75
- callback.onOpenChange(false);
76
- } catch (error) {
77
- console.error("[aria-ease] Error in combobox onOpenChange callback:", error);
78
- }
79
- }
80
- }
81
- function selectOption(item) {
82
- const value = item.textContent?.trim() || "";
83
- comboboxInput.value = value;
84
- item.setAttribute("aria-selected", "true");
85
- closeListbox();
86
- if (callback?.onSelect) {
87
- try {
88
- callback.onSelect(item);
89
- } catch (error) {
90
- console.error("[aria-ease] Error in combobox onSelect callback:", error);
91
- }
92
- }
93
- }
94
- function handleInputKeyDown(event) {
95
- const visibleItems = getVisibleItems();
96
- const isOpen = isListboxOpen();
97
- switch (event.key) {
98
- case "ArrowDown":
99
- event.preventDefault();
100
- if (!isOpen) {
101
- openListbox();
102
- return;
103
- }
104
- if (visibleItems.length === 0) return;
105
- {
106
- const newIndex = activeIndex >= visibleItems.length - 1 ? 0 : activeIndex + 1;
107
- setActiveDescendant(newIndex);
108
- }
109
- break;
110
- case "ArrowUp":
111
- event.preventDefault();
112
- if (!isOpen) return;
113
- if (visibleItems.length > 0) {
114
- const newIndex = activeIndex <= 0 ? visibleItems.length - 1 : activeIndex - 1;
115
- setActiveDescendant(newIndex);
116
- }
117
- break;
118
- case "Enter":
119
- if (isOpen && activeIndex >= 0 && activeIndex < visibleItems.length) {
120
- event.preventDefault();
121
- selectOption(visibleItems[activeIndex]);
122
- }
123
- break;
124
- case "Escape":
125
- if (isOpen) {
126
- event.preventDefault();
127
- closeListbox();
128
- } else if (comboboxInput.value) {
129
- event.preventDefault();
130
- comboboxInput.value = "";
131
- comboboxInput.setAttribute("aria-activedescendant", "");
132
- const visibleItems2 = getVisibleItems();
133
- visibleItems2.forEach((item) => {
134
- if (item.getAttribute("aria-selected") === "true") item.setAttribute("aria-selected", "false");
135
- });
136
- if (callback?.onClear) {
137
- try {
138
- callback.onClear();
139
- } catch (error) {
140
- console.error("[aria-ease] Error in combobox onClear callback:", error);
141
- }
142
- }
143
- }
144
- break;
145
- case "Home":
146
- if (isOpen && visibleItems.length > 0) {
147
- event.preventDefault();
148
- setActiveDescendant(0);
149
- }
150
- break;
151
- case "End":
152
- if (isOpen && visibleItems.length > 0) {
153
- event.preventDefault();
154
- setActiveDescendant(visibleItems.length - 1);
155
- }
156
- break;
157
- case "Tab":
158
- if (isOpen && activeIndex >= 0 && activeIndex < visibleItems.length) {
159
- selectOption(visibleItems[activeIndex]);
160
- }
161
- if (isOpen) {
162
- closeListbox();
163
- }
164
- break;
165
- }
166
- }
167
- function handleMouseMove(event) {
168
- const target = event.target;
169
- if (target.classList.contains(listBoxItemsClass)) {
170
- const visibleItems = getVisibleItems();
171
- const index = visibleItems.indexOf(target);
172
- if (index >= 0) {
173
- setActiveDescendant(index);
174
- }
175
- }
176
- }
177
- function handleMouseDown(event) {
178
- const target = event.target;
179
- if (target.classList.contains(listBoxItemsClass)) {
180
- event.preventDefault();
181
- selectOption(target);
182
- }
183
- }
184
- function handleClickOutside(event) {
185
- const target = event.target;
186
- if (!comboboxInput.contains(target) && !listBox.contains(target) && (!listButton || !listButton.contains(target))) {
187
- closeListbox();
188
- }
189
- }
190
- function handleListButtonClick() {
191
- if (isListboxOpen()) {
192
- closeListbox();
193
- } else {
194
- openListbox();
195
- comboboxInput.focus();
196
- }
197
- }
198
- function handleListButtonKeyDown(event) {
199
- if (event.key === "Enter" || event.key === " ") {
200
- event.preventDefault();
201
- handleListButtonClick();
202
- }
203
- }
204
- comboboxInput.addEventListener("keydown", handleInputKeyDown);
205
- listBox.addEventListener("mousemove", handleMouseMove);
206
- listBox.addEventListener("mousedown", handleMouseDown);
207
- document.addEventListener("mousedown", handleClickOutside);
208
- if (listButton) {
209
- listButton.setAttribute("tabindex", "-1");
210
- listButton.setAttribute("aria-label", "Toggle options");
211
- listButton.addEventListener("click", handleListButtonClick);
212
- listButton.addEventListener("keydown", handleListButtonKeyDown);
213
- }
214
- function initializeOptions() {
215
- const items = listBox.querySelectorAll(`.${listBoxItemsClass}`);
216
- if (items.length === 0) return;
217
- let selectedValue = null;
218
- for (const item of items) {
219
- if (item.getAttribute("aria-selected") === "true") {
220
- selectedValue = item.textContent?.trim() || null;
221
- break;
222
- }
223
- }
224
- if (!selectedValue && comboboxInput.value) {
225
- selectedValue = comboboxInput.value.trim();
226
- }
227
- items.forEach((item, index) => {
228
- item.setAttribute("role", "option");
229
- const itemValue = item.textContent?.trim() || "";
230
- if (selectedValue && itemValue === selectedValue) {
231
- item.setAttribute("aria-selected", "true");
232
- } else {
233
- item.setAttribute("aria-selected", "false");
234
- }
235
- const currentId = item.getAttribute("id");
236
- if (!currentId || currentId === "") {
237
- const itemId = `${listBoxId}-option-${index}`;
238
- item.id = itemId;
239
- item.setAttribute("id", itemId);
240
- }
241
- });
242
- }
243
- initializeOptions();
244
- function cleanup() {
245
- comboboxInput.removeEventListener("keydown", handleInputKeyDown);
246
- listBox.removeEventListener("mousemove", handleMouseMove);
247
- listBox.removeEventListener("mousedown", handleMouseDown);
248
- document.removeEventListener("mousedown", handleClickOutside);
249
- if (listButton) {
250
- listButton.removeEventListener("click", handleListButtonClick);
251
- listButton.removeEventListener("keydown", handleListButtonKeyDown);
252
- }
253
- }
254
- function refresh() {
255
- cachedItems = null;
256
- initializeOptions();
257
- activeIndex = -1;
258
- setActiveDescendant(-1);
259
- }
260
- return { cleanup, refresh, openListbox, closeListbox };
261
- }
262
-
263
- export { makeComboboxAccessible };
1
+ function $({comboboxInputId:h,comboboxButtonId:A,listBoxId:b,listBoxItemsClass:u,callback:a}){if(h==="")return console.error("[aria-ease] 'comboboxInputId' should not be an empty string. Provide an id to the combobox input element that exists before calling makeComboboxAccessible."),{cleanup:()=>{}};let o=document.getElementById(`${h}`);if(!o)return console.error(`[aria-ease] Element with id="${h}" not found. Make sure the combobox input element exists before calling makeComboboxAccessible.`),{cleanup:()=>{}};if(b==="")return console.error("[aria-ease] 'listBoxId' should not be an empty string. Provide an id to the combobox listbox element that exists before calling makeComboboxAccessible."),{cleanup:()=>{}};let s=document.getElementById(`${b}`);if(!s)return console.error(`[aria-ease] Element with id="${b}" not found. Make sure the combobox listbox element exists before calling makeComboboxAccessible.`),{cleanup:()=>{}};if(u==="")return console.error("[aria-ease] 'listboxItemsClass' class should not be an empty string. Provide a class name to at least a listbox option that exists before calling makeComboboxAccessible."),{cleanup:()=>{}};if(!document.querySelectorAll(u))return console.error(`[aria-ease] Listbox option(s) with class="${u}" not found. Make sure at least a combobox listbox option exists before calling makeComboboxAccessible.`),{cleanup:()=>{}};let l=A?document.getElementById(`${A}`):null,r=-1;o.setAttribute("role","combobox"),o.setAttribute("aria-autocomplete","list"),o.setAttribute("aria-controls",b),o.setAttribute("aria-expanded","false"),o.setAttribute("aria-haspopup","listbox"),s.setAttribute("role","listbox");let m=null;function p(){return m||(m=s.querySelectorAll(`.${u}`)),Array.from(m).filter(e=>!e.hidden&&e.style.display!=="none")}function x(){return o.getAttribute("aria-expanded")==="true"}function f(e){let t=p();if(e>=0&&e<t.length){let n=t[e],i=n.id||`${b}-option-${e}`;if(n.id||(n.id=i),o.setAttribute("aria-activedescendant",i),typeof n.scrollIntoView=="function"&&n.scrollIntoView({block:"nearest",behavior:"smooth"}),a?.onActiveDescendantChange)try{a.onActiveDescendantChange(i,n);}catch(c){console.error("[aria-ease] Error in combobox onActiveDescendantChange callback:",c);}}else o.setAttribute("aria-activedescendant","");r=e;}function v(){if(o.setAttribute("aria-expanded","true"),s.style.display="block",a?.onOpenChange)try{a.onOpenChange(!0);}catch(e){console.error("[aria-ease] Error in combobox onOpenChange callback:",e);}}function d(){if(o.setAttribute("aria-expanded","false"),o.setAttribute("aria-activedescendant",""),s.style.display="none",r=-1,a?.onOpenChange)try{a.onOpenChange(!1);}catch(e){console.error("[aria-ease] Error in combobox onOpenChange callback:",e);}}function g(e){let t=e.textContent?.trim()||"";if(o.value=t,e.setAttribute("aria-selected","true"),d(),a?.onSelect)try{a.onSelect(e);}catch(n){console.error("[aria-ease] Error in combobox onSelect callback:",n);}}function L(e){let t=p(),n=x();switch(e.key){case "ArrowDown":if(e.preventDefault(),!n){v();return}if(t.length===0)return;{let i=r>=t.length-1?0:r+1;f(i);}break;case "ArrowUp":if(e.preventDefault(),!n)return;if(t.length>0){let i=r<=0?t.length-1:r-1;f(i);}break;case "Enter":n&&r>=0&&r<t.length&&(e.preventDefault(),g(t[r]));break;case "Escape":if(n)e.preventDefault(),d();else if(o.value&&(e.preventDefault(),o.value="",o.setAttribute("aria-activedescendant",""),p().forEach(c=>{c.getAttribute("aria-selected")==="true"&&c.setAttribute("aria-selected","false");}),a?.onClear))try{a.onClear();}catch(c){console.error("[aria-ease] Error in combobox onClear callback:",c);}break;case "Home":n&&t.length>0&&(e.preventDefault(),f(0));break;case "End":n&&t.length>0&&(e.preventDefault(),f(t.length-1));break;case "Tab":n&&r>=0&&r<t.length&&g(t[r]),n&&d();break}}function y(e){let t=e.target;if(t.classList.contains(u)){let i=p().indexOf(t);i>=0&&f(i);}}function k(e){let t=e.target;t.classList.contains(u)&&(e.preventDefault(),g(t));}function C(e){let t=e.target;!o.contains(t)&&!s.contains(t)&&(!l||!l.contains(t))&&d();}function E(){x()?d():(v(),o.focus());}function w(e){(e.key==="Enter"||e.key===" ")&&(e.preventDefault(),E());}o.addEventListener("keydown",L),s.addEventListener("mousemove",y),s.addEventListener("mousedown",k),document.addEventListener("mousedown",C),l&&(l.setAttribute("tabindex","-1"),l.setAttribute("aria-label","Toggle options"),l.addEventListener("click",E),l.addEventListener("keydown",w));function M(){let e=s.querySelectorAll(`.${u}`);if(e.length===0)return;let t=null;for(let n of e)if(n.getAttribute("aria-selected")==="true"){t=n.textContent?.trim()||null;break}!t&&o.value&&(t=o.value.trim()),e.forEach((n,i)=>{n.setAttribute("role","option");let c=n.textContent?.trim()||"";t&&c===t?n.setAttribute("aria-selected","true"):n.setAttribute("aria-selected","false");let O=n.getAttribute("id");if(!O||O===""){let D=`${b}-option-${i}`;n.id=D,n.setAttribute("id",D);}});}M();function T(){o.removeEventListener("keydown",L),s.removeEventListener("mousemove",y),s.removeEventListener("mousedown",k),document.removeEventListener("mousedown",C),l&&(l.removeEventListener("click",E),l.removeEventListener("keydown",w));}function H(){m=null,M(),r=-1,f(-1);}return {cleanup:T,refresh:H,openListbox:v,closeListbox:d}}export{$ as makeComboboxAccessible};
@@ -1,339 +1 @@
1
- 'use strict';
2
-
3
- // src/menu/src/makeMenuAccessible/makeMenuAccessible.ts
4
- function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
5
- const menuDiv = document.querySelector(`#${menuId}`);
6
- if (!menuDiv) {
7
- console.error(`[aria-ease] Element with id="${menuId}" not found. Make sure the menu element exists before calling makeMenuAccessible.`);
8
- return { openMenu: () => {
9
- }, closeMenu: () => {
10
- }, cleanup: () => {
11
- } };
12
- }
13
- const triggerButton = document.querySelector(`#${triggerId}`);
14
- if (!triggerButton) {
15
- console.error(`[aria-ease] Element with id="${triggerId}" not found. Make sure the trigger button element exists before calling makeMenuAccessible.`);
16
- return { openMenu: () => {
17
- }, closeMenu: () => {
18
- }, cleanup: () => {
19
- } };
20
- }
21
- if (!/^[\w-]+$/.test(menuId)) {
22
- console.error("[aria-ease] Invalid menuId: must be alphanumeric");
23
- return { openMenu: () => {
24
- }, closeMenu: () => {
25
- }, cleanup: () => {
26
- } };
27
- }
28
- triggerButton.setAttribute("aria-haspopup", "true");
29
- triggerButton.setAttribute("aria-controls", menuId);
30
- triggerButton.setAttribute("aria-expanded", "false");
31
- menuDiv.setAttribute("role", "menu");
32
- menuDiv.setAttribute("aria-labelledby", triggerId);
33
- const handlerMap = /* @__PURE__ */ new WeakMap();
34
- const submenuInstances = /* @__PURE__ */ new Map();
35
- let cachedItems = null;
36
- let filteredItems = null;
37
- function getItems() {
38
- if (!cachedItems) {
39
- cachedItems = menuDiv.querySelectorAll(`.${menuItemsClass}`);
40
- }
41
- return cachedItems;
42
- }
43
- function getFilteredItems() {
44
- if (!filteredItems) {
45
- const allItems = getItems();
46
- filteredItems = [];
47
- for (let i = 0; i < allItems.length; i++) {
48
- const item = allItems.item(i);
49
- const isNested = isItemInNestedSubmenu(item);
50
- const isDisabled = item.getAttribute("aria-disabled") === "true";
51
- if (!isNested) {
52
- if (!item.hasAttribute("tabindex")) {
53
- item.setAttribute("tabindex", "-1");
54
- }
55
- if (!isDisabled) {
56
- filteredItems.push(item);
57
- }
58
- }
59
- }
60
- }
61
- return filteredItems;
62
- }
63
- function toNodeListLike(items) {
64
- const nodeListLike = {
65
- length: items.length,
66
- item: (index) => items[index],
67
- forEach: (callback2) => {
68
- items.forEach(callback2);
69
- },
70
- [Symbol.iterator]: function* () {
71
- for (const item of items) {
72
- yield item;
73
- }
74
- }
75
- };
76
- return nodeListLike;
77
- }
78
- function intializeMenuItems() {
79
- const items = getItems();
80
- items.forEach((item) => {
81
- item.setAttribute("role", "menuitem");
82
- const submenuId = item.getAttribute("data-submenu-id") ?? item.getAttribute("aria-controls");
83
- const hasSubmenuTriggerAttributes = item.hasAttribute("aria-haspopup") && submenuId;
84
- if (submenuId && (item.hasAttribute("data-submenu-id") || hasSubmenuTriggerAttributes)) {
85
- item.setAttribute("aria-haspopup", "menu");
86
- item.setAttribute("aria-controls", submenuId);
87
- if (!item.hasAttribute("aria-expanded")) {
88
- item.setAttribute("aria-expanded", "false");
89
- }
90
- }
91
- });
92
- }
93
- function moveFocus(elementItems, currentIndex, direction) {
94
- const len = elementItems.length;
95
- const nextIndex = (currentIndex + direction + len) % len;
96
- elementItems.item(nextIndex).focus();
97
- }
98
- function focusItemAtIndex(items, index) {
99
- if (items.length === 0) return;
100
- items[index]?.focus();
101
- }
102
- function hasSubmenu(menuItem) {
103
- return menuItem.hasAttribute("aria-controls") && menuItem.hasAttribute("aria-haspopup") && menuItem.getAttribute("role") === "menuitem";
104
- }
105
- function closeAncestorMenusFromTrigger(triggerEl) {
106
- let currentTrigger = triggerEl;
107
- while (currentTrigger && currentTrigger.getAttribute("role") === "menuitem") {
108
- const parentMenu = currentTrigger.closest('[role="menu"]');
109
- if (!parentMenu) break;
110
- parentMenu.style.display = "none";
111
- currentTrigger.setAttribute("aria-expanded", "false");
112
- const parentTriggerId = parentMenu.getAttribute("aria-labelledby");
113
- if (!parentTriggerId) break;
114
- const nextTrigger = document.getElementById(parentTriggerId);
115
- if (!nextTrigger) break;
116
- currentTrigger = nextTrigger;
117
- }
118
- }
119
- intializeMenuItems();
120
- function handleItemsKeydown(event, menuItem, menuItemIndex) {
121
- switch (event.key) {
122
- case "ArrowLeft": {
123
- if (event.key === "ArrowLeft" && triggerButton.getAttribute("role") === "menuitem") {
124
- event.preventDefault();
125
- closeMenu();
126
- return;
127
- }
128
- break;
129
- }
130
- case "ArrowUp": {
131
- event.preventDefault();
132
- moveFocus(toNodeListLike(getFilteredItems()), menuItemIndex, -1);
133
- break;
134
- }
135
- case "ArrowRight": {
136
- if (event.key === "ArrowRight" && hasSubmenu(menuItem)) {
137
- event.preventDefault();
138
- const submenuId = menuItem.getAttribute("aria-controls");
139
- if (submenuId) {
140
- openSubmenu(submenuId);
141
- return;
142
- }
143
- }
144
- break;
145
- }
146
- case "ArrowDown": {
147
- event.preventDefault();
148
- moveFocus(toNodeListLike(getFilteredItems()), menuItemIndex, 1);
149
- break;
150
- }
151
- case "Home": {
152
- event.preventDefault();
153
- focusItemAtIndex(getFilteredItems(), 0);
154
- break;
155
- }
156
- case "End": {
157
- event.preventDefault();
158
- const items = getFilteredItems();
159
- focusItemAtIndex(items, items.length - 1);
160
- break;
161
- }
162
- case "Escape": {
163
- event.preventDefault();
164
- closeMenu();
165
- triggerButton.focus();
166
- if (onOpenChange) {
167
- onOpenChange(false);
168
- }
169
- break;
170
- }
171
- case "Enter":
172
- case " ": {
173
- event.preventDefault();
174
- if (hasSubmenu(menuItem)) {
175
- const submenuId = menuItem.getAttribute("aria-controls");
176
- if (submenuId) {
177
- openSubmenu(submenuId);
178
- return;
179
- }
180
- }
181
- menuItem.click();
182
- closeMenu();
183
- if (onOpenChange) {
184
- onOpenChange(false);
185
- }
186
- break;
187
- }
188
- case "Tab": {
189
- closeMenu();
190
- closeAncestorMenusFromTrigger(triggerButton);
191
- if (onOpenChange) {
192
- onOpenChange(false);
193
- }
194
- break;
195
- }
196
- }
197
- }
198
- function isItemInNestedSubmenu(item) {
199
- let parent = item.parentElement;
200
- while (parent && parent !== menuDiv) {
201
- if (parent.getAttribute("role") === "menu") {
202
- return true;
203
- }
204
- if (parent.id) {
205
- const parentMenuTrigger = menuDiv.querySelector(`[aria-controls="${parent.id}"]`);
206
- if (parentMenuTrigger) {
207
- return true;
208
- }
209
- }
210
- parent = parent.parentElement;
211
- }
212
- return false;
213
- }
214
- function setAria(isOpen) {
215
- triggerButton.setAttribute("aria-expanded", isOpen ? "true" : "false");
216
- }
217
- function openSubmenu(submenuId) {
218
- let submenuInstance = submenuInstances.get(submenuId);
219
- if (!submenuInstance) {
220
- const submenuTrigger = menuDiv.querySelector(`[aria-controls="${submenuId}"]`);
221
- if (!submenuTrigger) {
222
- console.error(`[aria-ease] Submenu trigger with aria-controls="${submenuId}" not found in menu "${menuId}".`);
223
- return;
224
- }
225
- if (!submenuTrigger.id) {
226
- const generatedId = `trigger-${submenuId}`;
227
- submenuTrigger.id = generatedId;
228
- console.warn(`[aria-ease] Submenu trigger for "${submenuId}" had no ID. Auto-generated ID: "${generatedId}".`);
229
- }
230
- const submenuElement = document.querySelector(`#${submenuId}`);
231
- if (!submenuElement) {
232
- console.error(`[aria-ease] Submenu element with id="${submenuId}" not found. Cannot create submenu instance.`);
233
- return;
234
- }
235
- submenuInstance = makeMenuAccessible({
236
- menuId: submenuId,
237
- menuItemsClass,
238
- triggerId: submenuTrigger.id,
239
- callback
240
- });
241
- submenuInstances.set(submenuId, submenuInstance);
242
- }
243
- submenuInstance.openMenu();
244
- }
245
- function onOpenChange(isOpen) {
246
- if (callback?.onOpenChange) {
247
- try {
248
- callback.onOpenChange(isOpen);
249
- } catch (error) {
250
- console.error("[aria-ease] Error in menu onOpenChange callback:", error);
251
- }
252
- }
253
- }
254
- function addListeners() {
255
- const items = getFilteredItems();
256
- items.forEach((menuItem, index) => {
257
- if (!handlerMap.has(menuItem)) {
258
- const handler = (event) => handleItemsKeydown(event, menuItem, index);
259
- menuItem.addEventListener("keydown", handler);
260
- handlerMap.set(menuItem, handler);
261
- }
262
- });
263
- }
264
- function removeListeners() {
265
- const items = getFilteredItems();
266
- items.forEach((menuItem) => {
267
- const handler = handlerMap.get(menuItem);
268
- if (handler) {
269
- menuItem.removeEventListener("keydown", handler);
270
- handlerMap.delete(menuItem);
271
- }
272
- });
273
- }
274
- function openMenu() {
275
- setAria(true);
276
- menuDiv.style.display = "block";
277
- const items = getFilteredItems();
278
- addListeners();
279
- if (items && items.length > 0) {
280
- items[0].focus();
281
- }
282
- if (callback?.onOpenChange) {
283
- try {
284
- callback.onOpenChange(true);
285
- } catch (error) {
286
- console.error("[aria-ease] Error in menu onOpenChange callback:", error);
287
- }
288
- }
289
- }
290
- function closeMenu() {
291
- submenuInstances.forEach((instance) => instance.closeMenu());
292
- setAria(false);
293
- menuDiv.style.display = "none";
294
- removeListeners();
295
- triggerButton.focus();
296
- if (callback?.onOpenChange) {
297
- try {
298
- callback.onOpenChange(false);
299
- } catch (error) {
300
- console.error("[aria-ease] Error in menu onOpenChange callback:", error);
301
- }
302
- }
303
- }
304
- function handleTriggerClick() {
305
- const isOpen = triggerButton.getAttribute("aria-expanded") === "true";
306
- if (isOpen) {
307
- closeMenu();
308
- } else {
309
- openMenu();
310
- }
311
- }
312
- function handleClickOutside(event) {
313
- const isMenuOpen = triggerButton.getAttribute("aria-expanded") === "true";
314
- if (!isMenuOpen) return;
315
- const clickedTrigger = triggerButton.contains(event.target);
316
- const clickedMenu = menuDiv.contains(event.target);
317
- if (!clickedTrigger && !clickedMenu) {
318
- closeMenu();
319
- }
320
- }
321
- triggerButton.addEventListener("click", handleTriggerClick);
322
- document.addEventListener("click", handleClickOutside);
323
- function cleanup() {
324
- removeListeners();
325
- triggerButton.removeEventListener("click", handleTriggerClick);
326
- document.removeEventListener("click", handleClickOutside);
327
- menuDiv.style.display = "none";
328
- setAria(false);
329
- submenuInstances.forEach((instance) => instance.cleanup());
330
- submenuInstances.clear();
331
- }
332
- function refresh() {
333
- cachedItems = null;
334
- filteredItems = null;
335
- }
336
- return { openMenu, closeMenu, cleanup, refresh };
337
- }
338
-
339
- exports.makeMenuAccessible = makeMenuAccessible;
1
+ 'use strict';function F({menuId:c,menuItemsClass:h,triggerId:m,callback:s}){if(c==="")return console.error("[aria-ease] 'menuId' should not be an empty string. Provide an id of the menu element before calling makeMenuAccessible."),{openMenu:()=>{},closeMenu:()=>{},cleanup:()=>{}};let a=document.querySelector(`#${c}`);if(!a)return console.error(`[aria-ease] Element with id="${c}" not found. Make sure the menu element exists before calling makeMenuAccessible.`),{openMenu:()=>{},closeMenu:()=>{},cleanup:()=>{}};if(m==="")return console.error("[aria-ease] 'triggerId' should not be an empty string. Provide an id of the trigger button element before calling makeMenuAccessible."),{openMenu:()=>{},closeMenu:()=>{},cleanup:()=>{}};let o=document.querySelector(`#${m}`);if(!o)return console.error(`[aria-ease] Element with id="${m}" not found. Make sure the trigger button element exists before calling makeMenuAccessible.`),{openMenu:()=>{},closeMenu:()=>{},cleanup:()=>{}};if(h==="")return console.error("[aria-ease] 'menuItemsClass' should not be an empty string. Provide a class name to at least a menu item that exists before calling makeMenuAccessible."),{openMenu:()=>{},closeMenu:()=>{},cleanup:()=>{}};if(!/^[\w-]+$/.test(c))return console.error("[aria-ease] Invalid menuId: must be alphanumeric"),{openMenu:()=>{},closeMenu:()=>{},cleanup:()=>{}};o.setAttribute("aria-haspopup","true"),o.setAttribute("aria-controls",c),o.setAttribute("aria-expanded","false"),a.setAttribute("role","menu"),a.setAttribute("aria-labelledby",m);let p=new WeakMap,d=new Map,g=null,b=null;function E(){return g||(g=a.querySelectorAll(`.${h}`)),g}function u(){if(!b){let e=E();b=[];for(let t=0;t<e.length;t++){let n=e.item(t),r=S(n),i=n.getAttribute("aria-disabled")==="true";r||(n.hasAttribute("tabindex")||n.setAttribute("tabindex","-1"),i||b.push(n));}}return b}function A(e){return {length:e.length,item:n=>e[n],forEach:n=>{e.forEach(n);},[Symbol.iterator]:function*(){for(let n of e)yield n;}}}function v(){E().forEach(t=>{t.setAttribute("role","menuitem");let n=t.getAttribute("data-submenu-id")??t.getAttribute("aria-controls"),r=t.hasAttribute("aria-haspopup")&&n;n&&(t.hasAttribute("data-submenu-id")||r)&&(t.setAttribute("aria-haspopup","menu"),t.setAttribute("aria-controls",n),t.hasAttribute("aria-expanded")||t.setAttribute("aria-expanded","false"));});}function L(e,t,n){let r=e.length,i=(t+n+r)%r;e.item(i).focus();}function k(e,t){e.length!==0&&e[t]?.focus();}function y(e){return e.hasAttribute("aria-controls")&&e.hasAttribute("aria-haspopup")&&e.getAttribute("role")==="menuitem"}function C(e){let t=e;for(;t&&t.getAttribute("role")==="menuitem";){let n=t.closest('[role="menu"]');if(!n)break;n.style.display="none",t.setAttribute("aria-expanded","false");let r=n.getAttribute("aria-labelledby");if(!r)break;let i=document.getElementById(r);if(!i)break;t=i;}}v();function $(e,t,n){switch(e.key){case "ArrowLeft":{if(e.key==="ArrowLeft"&&o.getAttribute("role")==="menuitem"){e.preventDefault(),l();return}break}case "ArrowUp":{e.preventDefault(),L(A(u()),n,-1);break}case "ArrowRight":{if(e.key==="ArrowRight"&&y(t)){e.preventDefault();let r=t.getAttribute("aria-controls");if(r){T(r);return}}break}case "ArrowDown":{e.preventDefault(),L(A(u()),n,1);break}case "Home":{e.preventDefault(),k(u(),0);break}case "End":{e.preventDefault();let r=u();k(r,r.length-1);break}case "Escape":{e.preventDefault(),l(),o.focus(),f&&f(false);break}case "Enter":case " ":{if(e.preventDefault(),y(t)){let r=t.getAttribute("aria-controls");if(r){T(r);return}}t.click(),l(),f&&f(false);break}case "Tab":{l(),C(o),f&&f(false);break}}}function S(e){let t=e.parentElement;for(;t&&t!==a;){if(t.getAttribute("role")==="menu"||t.id&&a.querySelector(`[aria-controls="${t.id}"]`))return true;t=t.parentElement;}return false}function M(e){o.setAttribute("aria-expanded",e?"true":"false");}function T(e){let t=d.get(e);if(!t){let n=a.querySelector(`[aria-controls="${e}"]`);if(!n){console.error(`[aria-ease] Submenu trigger with aria-controls="${e}" not found in menu "${c}".`);return}if(!n.id){let i=`trigger-${e}`;n.id=i,console.warn(`[aria-ease] Submenu trigger for "${e}" had no ID. Auto-generated ID: "${i}".`);}if(!document.querySelector(`#${e}`)){console.error(`[aria-ease] Submenu element with id="${e}" not found. Cannot create submenu instance.`);return}t=F({menuId:e,menuItemsClass:h,triggerId:n.id,callback:s}),d.set(e,t);}t.openMenu();}function f(e){if(s?.onOpenChange)try{s.onOpenChange(e);}catch(t){console.error("[aria-ease] Error in menu onOpenChange callback:",t);}}function D(){u().forEach((t,n)=>{if(!p.has(t)){let r=i=>$(i,t,n);t.addEventListener("keydown",r),p.set(t,r);}});}function H(){u().forEach(t=>{let n=p.get(t);n&&(t.removeEventListener("keydown",n),p.delete(t));});}function w(){M(true),a.style.display="block";let e=u();if(D(),e&&e.length>0&&e[0].focus(),s?.onOpenChange)try{s.onOpenChange(!0);}catch(t){console.error("[aria-ease] Error in menu onOpenChange callback:",t);}}function l(){if(d.forEach(e=>e.closeMenu()),M(false),a.style.display="none",H(),o.focus(),s?.onOpenChange)try{s.onOpenChange(!1);}catch(e){console.error("[aria-ease] Error in menu onOpenChange callback:",e);}}function O(){o.getAttribute("aria-expanded")==="true"?l():w();}function x(e){if(!(o.getAttribute("aria-expanded")==="true"))return;let n=o.contains(e.target),r=a.contains(e.target);!n&&!r&&l();}o.addEventListener("click",O),document.addEventListener("click",x);function N(){H(),o.removeEventListener("click",O),document.removeEventListener("click",x),a.style.display="none",M(false),d.forEach(e=>e.cleanup()),d.clear();}function q(){g=null,b=null;}return {openMenu:w,closeMenu:l,cleanup:N,refresh:q}}exports.makeMenuAccessible=F;