aria-ease 6.5.0 → 6.5.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.
- package/dist/index.cjs +75 -65
- package/dist/index.js +75 -65
- package/dist/src/{Types.d-yGC2bBaB.d.cts → Types.d-DYfYR3Vc.d.cts} +1 -1
- package/dist/src/{Types.d-yGC2bBaB.d.ts → Types.d-DYfYR3Vc.d.ts} +1 -1
- package/dist/src/accordion/index.d.cts +1 -1
- package/dist/src/accordion/index.d.ts +1 -1
- package/dist/src/block/index.cjs +1 -6
- package/dist/src/block/index.d.cts +1 -1
- package/dist/src/block/index.d.ts +1 -1
- package/dist/src/block/index.js +72 -1
- package/dist/src/checkbox/index.d.cts +1 -1
- package/dist/src/checkbox/index.d.ts +1 -1
- package/dist/src/combobox/index.d.cts +1 -1
- package/dist/src/combobox/index.d.ts +1 -1
- package/dist/src/menu/index.cjs +72 -140
- package/dist/src/menu/index.d.cts +1 -1
- package/dist/src/menu/index.d.ts +1 -1
- package/dist/src/menu/index.js +72 -16
- package/dist/src/radio/index.d.cts +1 -1
- package/dist/src/radio/index.d.ts +1 -1
- package/dist/src/tabs/index.d.cts +1 -1
- package/dist/src/tabs/index.d.ts +1 -1
- package/dist/src/toggle/index.d.cts +1 -1
- package/dist/src/toggle/index.d.ts +1 -1
- package/dist/src/utils/test/aria-contracts/accordion/accordion.contract.json +1 -1
- package/dist/src/utils/test/aria-contracts/combobox/combobox.listbox.contract.json +1 -1
- package/dist/src/utils/test/aria-contracts/menu/menu.contract.json +2 -2
- package/dist/src/utils/test/aria-contracts/tabs/tabs.contract.json +1 -1
- package/package.json +1 -1
- package/dist/src/chunk-ZJXZZDUR.js +0 -127
package/dist/index.cjs
CHANGED
|
@@ -1734,38 +1734,11 @@ function moveFocus(elementItems, currentIndex, direction) {
|
|
|
1734
1734
|
function isClickableButNotSemantic(el) {
|
|
1735
1735
|
return el.getAttribute("data-custom-click") !== null && el.getAttribute("data-custom-click") !== void 0;
|
|
1736
1736
|
}
|
|
1737
|
-
function
|
|
1738
|
-
menuElement.style.display = "none";
|
|
1739
|
-
const menuTriggerButtonId = menuTriggerButton.getAttribute("id");
|
|
1740
|
-
if (!menuTriggerButtonId) {
|
|
1741
|
-
console.error("[aria-ease] Menu trigger button must have an id attribute to properly set aria attributes.");
|
|
1742
|
-
return;
|
|
1743
|
-
}
|
|
1744
|
-
menuTriggerButton.setAttribute("aria-expanded", "false");
|
|
1745
|
-
}
|
|
1746
|
-
function hasSubmenu(menuItem) {
|
|
1747
|
-
return menuItem.getAttribute("aria-haspopup") === "true" || menuItem.getAttribute("aria-haspopup") === "menu";
|
|
1748
|
-
}
|
|
1749
|
-
function getSubmenuId(menuItem) {
|
|
1750
|
-
return menuItem.getAttribute("aria-controls");
|
|
1751
|
-
}
|
|
1752
|
-
function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, triggerButton, openSubmenu, closeSubmenu, onOpenChange) {
|
|
1737
|
+
function handleKeyPress(event, elementItems, elementItemIndex) {
|
|
1753
1738
|
const currentEl = elementItems.item(elementItemIndex);
|
|
1754
1739
|
switch (event.key) {
|
|
1755
1740
|
case "ArrowUp":
|
|
1756
1741
|
case "ArrowLeft": {
|
|
1757
|
-
if (event.key === "ArrowLeft" && menuElementDiv && closeSubmenu) {
|
|
1758
|
-
const labelledBy = menuElementDiv.getAttribute("aria-labelledby");
|
|
1759
|
-
if (labelledBy) {
|
|
1760
|
-
const parentTrigger = document.getElementById(labelledBy);
|
|
1761
|
-
if (parentTrigger && parentTrigger.getAttribute("role") === "menuitem") {
|
|
1762
|
-
event.preventDefault();
|
|
1763
|
-
closeSubmenu();
|
|
1764
|
-
parentTrigger.focus();
|
|
1765
|
-
return;
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
1742
|
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
1770
1743
|
event.preventDefault();
|
|
1771
1744
|
moveFocus(elementItems, elementItemIndex, -1);
|
|
@@ -1780,14 +1753,6 @@ function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, t
|
|
|
1780
1753
|
}
|
|
1781
1754
|
case "ArrowDown":
|
|
1782
1755
|
case "ArrowRight": {
|
|
1783
|
-
if (event.key === "ArrowRight" && hasSubmenu(currentEl) && openSubmenu) {
|
|
1784
|
-
event.preventDefault();
|
|
1785
|
-
const submenuId = getSubmenuId(currentEl);
|
|
1786
|
-
if (submenuId) {
|
|
1787
|
-
openSubmenu(submenuId);
|
|
1788
|
-
return;
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
1756
|
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
1792
1757
|
event.preventDefault();
|
|
1793
1758
|
moveFocus(elementItems, elementItemIndex, 1);
|
|
@@ -1803,15 +1768,6 @@ function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, t
|
|
|
1803
1768
|
}
|
|
1804
1769
|
case "Escape": {
|
|
1805
1770
|
event.preventDefault();
|
|
1806
|
-
if (menuElementDiv && triggerButton) {
|
|
1807
|
-
if (getComputedStyle(menuElementDiv).display === "block") {
|
|
1808
|
-
handleMenuClose(menuElementDiv, triggerButton);
|
|
1809
|
-
if (onOpenChange) {
|
|
1810
|
-
onOpenChange(false);
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
triggerButton.focus();
|
|
1814
|
-
}
|
|
1815
1771
|
break;
|
|
1816
1772
|
}
|
|
1817
1773
|
case "Enter":
|
|
@@ -1826,12 +1782,6 @@ function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, t
|
|
|
1826
1782
|
break;
|
|
1827
1783
|
}
|
|
1828
1784
|
case "Tab": {
|
|
1829
|
-
if (menuElementDiv && triggerButton && (!event.shiftKey || event.shiftKey)) {
|
|
1830
|
-
handleMenuClose(menuElementDiv, triggerButton);
|
|
1831
|
-
if (onOpenChange) {
|
|
1832
|
-
onOpenChange(false);
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
1785
|
break;
|
|
1836
1786
|
}
|
|
1837
1787
|
default:
|
|
@@ -2069,15 +2019,88 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
2069
2019
|
const items = getItems();
|
|
2070
2020
|
items.forEach((item) => {
|
|
2071
2021
|
item.setAttribute("role", "menuitem");
|
|
2022
|
+
if (item.hasAttribute("data-submenu-id")) {
|
|
2023
|
+
item.setAttribute("aria-haspopup", "menu");
|
|
2024
|
+
item.setAttribute("aria-controls", item.getAttribute("data-submenu-id"));
|
|
2025
|
+
}
|
|
2072
2026
|
});
|
|
2073
2027
|
}
|
|
2028
|
+
function moveFocus2(elementItems, currentIndex, direction) {
|
|
2029
|
+
const len = elementItems.length;
|
|
2030
|
+
const nextIndex = (currentIndex + direction + len) % len;
|
|
2031
|
+
elementItems.item(nextIndex).focus();
|
|
2032
|
+
}
|
|
2033
|
+
function hasSubmenu(menuItem) {
|
|
2034
|
+
return menuItem.hasAttribute("aria-controls") && menuItem.hasAttribute("aria-haspopup") && menuItem.getAttribute("role") === "menuitem";
|
|
2035
|
+
}
|
|
2074
2036
|
intializeMenuItems();
|
|
2037
|
+
function handleItemsKeydown(event, menuItem, menuItemIndex) {
|
|
2038
|
+
switch (event.key) {
|
|
2039
|
+
case "ArrowUp":
|
|
2040
|
+
case "ArrowLeft": {
|
|
2041
|
+
if (event.key === "ArrowLeft" && triggerButton.getAttribute("role") === "menuitem") {
|
|
2042
|
+
event.preventDefault();
|
|
2043
|
+
closeMenu();
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
event.preventDefault();
|
|
2047
|
+
moveFocus2(toNodeListLike(getFilteredItems()), menuItemIndex, -1);
|
|
2048
|
+
break;
|
|
2049
|
+
}
|
|
2050
|
+
case "ArrowDown":
|
|
2051
|
+
case "ArrowRight": {
|
|
2052
|
+
if (event.key === "ArrowRight" && hasSubmenu(menuItem)) {
|
|
2053
|
+
event.preventDefault();
|
|
2054
|
+
const submenuId = menuItem.getAttribute("aria-controls");
|
|
2055
|
+
if (submenuId) {
|
|
2056
|
+
openSubmenu(submenuId);
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
event.preventDefault();
|
|
2061
|
+
moveFocus2(toNodeListLike(getFilteredItems()), menuItemIndex, 1);
|
|
2062
|
+
break;
|
|
2063
|
+
}
|
|
2064
|
+
case "Escape": {
|
|
2065
|
+
event.preventDefault();
|
|
2066
|
+
closeMenu();
|
|
2067
|
+
triggerButton.focus();
|
|
2068
|
+
if (onOpenChange) {
|
|
2069
|
+
onOpenChange(false);
|
|
2070
|
+
}
|
|
2071
|
+
break;
|
|
2072
|
+
}
|
|
2073
|
+
case "Enter":
|
|
2074
|
+
case " ": {
|
|
2075
|
+
event.preventDefault();
|
|
2076
|
+
menuItem.click();
|
|
2077
|
+
break;
|
|
2078
|
+
}
|
|
2079
|
+
case "Tab": {
|
|
2080
|
+
if (!event.shiftKey || event.shiftKey) {
|
|
2081
|
+
closeMenu();
|
|
2082
|
+
if (onOpenChange) {
|
|
2083
|
+
onOpenChange(false);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
break;
|
|
2087
|
+
}
|
|
2088
|
+
default:
|
|
2089
|
+
break;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2075
2092
|
function isItemInNestedSubmenu(item) {
|
|
2076
2093
|
let parent = item.parentElement;
|
|
2077
2094
|
while (parent && parent !== menuDiv) {
|
|
2078
2095
|
if (parent.getAttribute("role") === "menu") {
|
|
2079
2096
|
return true;
|
|
2080
2097
|
}
|
|
2098
|
+
if (parent.id) {
|
|
2099
|
+
const parentMenuTrigger = menuDiv.querySelector(`[aria-controls="${parent.id}"]`);
|
|
2100
|
+
if (parentMenuTrigger) {
|
|
2101
|
+
return true;
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2081
2104
|
parent = parent.parentElement;
|
|
2082
2105
|
}
|
|
2083
2106
|
return false;
|
|
@@ -2113,9 +2136,6 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
2113
2136
|
}
|
|
2114
2137
|
submenuInstance.openMenu();
|
|
2115
2138
|
}
|
|
2116
|
-
function closeSubmenu() {
|
|
2117
|
-
closeMenu();
|
|
2118
|
-
}
|
|
2119
2139
|
function onOpenChange(isOpen) {
|
|
2120
2140
|
if (callback?.onOpenChange) {
|
|
2121
2141
|
try {
|
|
@@ -2127,19 +2147,9 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
2127
2147
|
}
|
|
2128
2148
|
function addListeners() {
|
|
2129
2149
|
const items = getFilteredItems();
|
|
2130
|
-
const nodeListLike = toNodeListLike(items);
|
|
2131
2150
|
items.forEach((menuItem, index) => {
|
|
2132
2151
|
if (!handlerMap.has(menuItem)) {
|
|
2133
|
-
const handler = (event) =>
|
|
2134
|
-
event,
|
|
2135
|
-
nodeListLike,
|
|
2136
|
-
index,
|
|
2137
|
-
menuDiv,
|
|
2138
|
-
triggerButton,
|
|
2139
|
-
openSubmenu,
|
|
2140
|
-
closeSubmenu,
|
|
2141
|
-
onOpenChange
|
|
2142
|
-
);
|
|
2152
|
+
const handler = (event) => handleItemsKeydown(event, menuItem, index);
|
|
2143
2153
|
menuItem.addEventListener("keydown", handler);
|
|
2144
2154
|
handlerMap.set(menuItem, handler);
|
|
2145
2155
|
}
|
package/dist/index.js
CHANGED
|
@@ -208,38 +208,11 @@ function moveFocus(elementItems, currentIndex, direction) {
|
|
|
208
208
|
function isClickableButNotSemantic(el) {
|
|
209
209
|
return el.getAttribute("data-custom-click") !== null && el.getAttribute("data-custom-click") !== void 0;
|
|
210
210
|
}
|
|
211
|
-
function
|
|
212
|
-
menuElement.style.display = "none";
|
|
213
|
-
const menuTriggerButtonId = menuTriggerButton.getAttribute("id");
|
|
214
|
-
if (!menuTriggerButtonId) {
|
|
215
|
-
console.error("[aria-ease] Menu trigger button must have an id attribute to properly set aria attributes.");
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
menuTriggerButton.setAttribute("aria-expanded", "false");
|
|
219
|
-
}
|
|
220
|
-
function hasSubmenu(menuItem) {
|
|
221
|
-
return menuItem.getAttribute("aria-haspopup") === "true" || menuItem.getAttribute("aria-haspopup") === "menu";
|
|
222
|
-
}
|
|
223
|
-
function getSubmenuId(menuItem) {
|
|
224
|
-
return menuItem.getAttribute("aria-controls");
|
|
225
|
-
}
|
|
226
|
-
function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, triggerButton, openSubmenu, closeSubmenu, onOpenChange) {
|
|
211
|
+
function handleKeyPress(event, elementItems, elementItemIndex) {
|
|
227
212
|
const currentEl = elementItems.item(elementItemIndex);
|
|
228
213
|
switch (event.key) {
|
|
229
214
|
case "ArrowUp":
|
|
230
215
|
case "ArrowLeft": {
|
|
231
|
-
if (event.key === "ArrowLeft" && menuElementDiv && closeSubmenu) {
|
|
232
|
-
const labelledBy = menuElementDiv.getAttribute("aria-labelledby");
|
|
233
|
-
if (labelledBy) {
|
|
234
|
-
const parentTrigger = document.getElementById(labelledBy);
|
|
235
|
-
if (parentTrigger && parentTrigger.getAttribute("role") === "menuitem") {
|
|
236
|
-
event.preventDefault();
|
|
237
|
-
closeSubmenu();
|
|
238
|
-
parentTrigger.focus();
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
216
|
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
244
217
|
event.preventDefault();
|
|
245
218
|
moveFocus(elementItems, elementItemIndex, -1);
|
|
@@ -254,14 +227,6 @@ function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, t
|
|
|
254
227
|
}
|
|
255
228
|
case "ArrowDown":
|
|
256
229
|
case "ArrowRight": {
|
|
257
|
-
if (event.key === "ArrowRight" && hasSubmenu(currentEl) && openSubmenu) {
|
|
258
|
-
event.preventDefault();
|
|
259
|
-
const submenuId = getSubmenuId(currentEl);
|
|
260
|
-
if (submenuId) {
|
|
261
|
-
openSubmenu(submenuId);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
230
|
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
266
231
|
event.preventDefault();
|
|
267
232
|
moveFocus(elementItems, elementItemIndex, 1);
|
|
@@ -277,15 +242,6 @@ function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, t
|
|
|
277
242
|
}
|
|
278
243
|
case "Escape": {
|
|
279
244
|
event.preventDefault();
|
|
280
|
-
if (menuElementDiv && triggerButton) {
|
|
281
|
-
if (getComputedStyle(menuElementDiv).display === "block") {
|
|
282
|
-
handleMenuClose(menuElementDiv, triggerButton);
|
|
283
|
-
if (onOpenChange) {
|
|
284
|
-
onOpenChange(false);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
triggerButton.focus();
|
|
288
|
-
}
|
|
289
245
|
break;
|
|
290
246
|
}
|
|
291
247
|
case "Enter":
|
|
@@ -300,12 +256,6 @@ function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, t
|
|
|
300
256
|
break;
|
|
301
257
|
}
|
|
302
258
|
case "Tab": {
|
|
303
|
-
if (menuElementDiv && triggerButton && (!event.shiftKey || event.shiftKey)) {
|
|
304
|
-
handleMenuClose(menuElementDiv, triggerButton);
|
|
305
|
-
if (onOpenChange) {
|
|
306
|
-
onOpenChange(false);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
259
|
break;
|
|
310
260
|
}
|
|
311
261
|
default:
|
|
@@ -543,15 +493,88 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
543
493
|
const items = getItems();
|
|
544
494
|
items.forEach((item) => {
|
|
545
495
|
item.setAttribute("role", "menuitem");
|
|
496
|
+
if (item.hasAttribute("data-submenu-id")) {
|
|
497
|
+
item.setAttribute("aria-haspopup", "menu");
|
|
498
|
+
item.setAttribute("aria-controls", item.getAttribute("data-submenu-id"));
|
|
499
|
+
}
|
|
546
500
|
});
|
|
547
501
|
}
|
|
502
|
+
function moveFocus2(elementItems, currentIndex, direction) {
|
|
503
|
+
const len = elementItems.length;
|
|
504
|
+
const nextIndex = (currentIndex + direction + len) % len;
|
|
505
|
+
elementItems.item(nextIndex).focus();
|
|
506
|
+
}
|
|
507
|
+
function hasSubmenu(menuItem) {
|
|
508
|
+
return menuItem.hasAttribute("aria-controls") && menuItem.hasAttribute("aria-haspopup") && menuItem.getAttribute("role") === "menuitem";
|
|
509
|
+
}
|
|
548
510
|
intializeMenuItems();
|
|
511
|
+
function handleItemsKeydown(event, menuItem, menuItemIndex) {
|
|
512
|
+
switch (event.key) {
|
|
513
|
+
case "ArrowUp":
|
|
514
|
+
case "ArrowLeft": {
|
|
515
|
+
if (event.key === "ArrowLeft" && triggerButton.getAttribute("role") === "menuitem") {
|
|
516
|
+
event.preventDefault();
|
|
517
|
+
closeMenu();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
event.preventDefault();
|
|
521
|
+
moveFocus2(toNodeListLike(getFilteredItems()), menuItemIndex, -1);
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
case "ArrowDown":
|
|
525
|
+
case "ArrowRight": {
|
|
526
|
+
if (event.key === "ArrowRight" && hasSubmenu(menuItem)) {
|
|
527
|
+
event.preventDefault();
|
|
528
|
+
const submenuId = menuItem.getAttribute("aria-controls");
|
|
529
|
+
if (submenuId) {
|
|
530
|
+
openSubmenu(submenuId);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
event.preventDefault();
|
|
535
|
+
moveFocus2(toNodeListLike(getFilteredItems()), menuItemIndex, 1);
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
case "Escape": {
|
|
539
|
+
event.preventDefault();
|
|
540
|
+
closeMenu();
|
|
541
|
+
triggerButton.focus();
|
|
542
|
+
if (onOpenChange) {
|
|
543
|
+
onOpenChange(false);
|
|
544
|
+
}
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
case "Enter":
|
|
548
|
+
case " ": {
|
|
549
|
+
event.preventDefault();
|
|
550
|
+
menuItem.click();
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
case "Tab": {
|
|
554
|
+
if (!event.shiftKey || event.shiftKey) {
|
|
555
|
+
closeMenu();
|
|
556
|
+
if (onOpenChange) {
|
|
557
|
+
onOpenChange(false);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
default:
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
549
566
|
function isItemInNestedSubmenu(item) {
|
|
550
567
|
let parent = item.parentElement;
|
|
551
568
|
while (parent && parent !== menuDiv) {
|
|
552
569
|
if (parent.getAttribute("role") === "menu") {
|
|
553
570
|
return true;
|
|
554
571
|
}
|
|
572
|
+
if (parent.id) {
|
|
573
|
+
const parentMenuTrigger = menuDiv.querySelector(`[aria-controls="${parent.id}"]`);
|
|
574
|
+
if (parentMenuTrigger) {
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
555
578
|
parent = parent.parentElement;
|
|
556
579
|
}
|
|
557
580
|
return false;
|
|
@@ -587,9 +610,6 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
587
610
|
}
|
|
588
611
|
submenuInstance.openMenu();
|
|
589
612
|
}
|
|
590
|
-
function closeSubmenu() {
|
|
591
|
-
closeMenu();
|
|
592
|
-
}
|
|
593
613
|
function onOpenChange(isOpen) {
|
|
594
614
|
if (callback?.onOpenChange) {
|
|
595
615
|
try {
|
|
@@ -601,19 +621,9 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
601
621
|
}
|
|
602
622
|
function addListeners() {
|
|
603
623
|
const items = getFilteredItems();
|
|
604
|
-
const nodeListLike = toNodeListLike(items);
|
|
605
624
|
items.forEach((menuItem, index) => {
|
|
606
625
|
if (!handlerMap.has(menuItem)) {
|
|
607
|
-
const handler = (event) =>
|
|
608
|
-
event,
|
|
609
|
-
nodeListLike,
|
|
610
|
-
index,
|
|
611
|
-
menuDiv,
|
|
612
|
-
triggerButton,
|
|
613
|
-
openSubmenu,
|
|
614
|
-
closeSubmenu,
|
|
615
|
-
onOpenChange
|
|
616
|
-
);
|
|
626
|
+
const handler = (event) => handleItemsKeydown(event, menuItem, index);
|
|
617
627
|
menuItem.addEventListener("keydown", handler);
|
|
618
628
|
handlerMap.set(menuItem, handler);
|
|
619
629
|
}
|
|
@@ -88,4 +88,4 @@ interface MenuCallback {
|
|
|
88
88
|
onOpenChange?: (isOpen: boolean) => void;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
export type {
|
|
91
|
+
export type { AccordionConfig as A, ComboboxConfig as C, MenuConfig as M, TabsConfig as T, AccessibilityInstance as a };
|
|
@@ -88,4 +88,4 @@ interface MenuCallback {
|
|
|
88
88
|
onOpenChange?: (isOpen: boolean) => void;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
export type {
|
|
91
|
+
export type { AccordionConfig as A, ComboboxConfig as C, MenuConfig as M, TabsConfig as T, AccessibilityInstance as a };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as AccordionConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Makes an accordion accessible by managing ARIA attributes, keyboard interaction, and state.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as AccordionConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Makes an accordion accessible by managing ARIA attributes, keyboard interaction, and state.
|
package/dist/src/block/index.cjs
CHANGED
|
@@ -23,15 +23,11 @@ function moveFocus(elementItems, currentIndex, direction) {
|
|
|
23
23
|
function isClickableButNotSemantic(el) {
|
|
24
24
|
return el.getAttribute("data-custom-click") !== null && el.getAttribute("data-custom-click") !== void 0;
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
return menuItem.getAttribute("aria-haspopup") === "true" || menuItem.getAttribute("aria-haspopup") === "menu";
|
|
28
|
-
}
|
|
29
|
-
function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, triggerButton, openSubmenu, closeSubmenu, onOpenChange) {
|
|
26
|
+
function handleKeyPress(event, elementItems, elementItemIndex) {
|
|
30
27
|
const currentEl = elementItems.item(elementItemIndex);
|
|
31
28
|
switch (event.key) {
|
|
32
29
|
case "ArrowUp":
|
|
33
30
|
case "ArrowLeft": {
|
|
34
|
-
if (event.key === "ArrowLeft" && menuElementDiv) ;
|
|
35
31
|
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
36
32
|
event.preventDefault();
|
|
37
33
|
moveFocus(elementItems, elementItemIndex, -1);
|
|
@@ -46,7 +42,6 @@ function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, t
|
|
|
46
42
|
}
|
|
47
43
|
case "ArrowDown":
|
|
48
44
|
case "ArrowRight": {
|
|
49
|
-
if (event.key === "ArrowRight" && hasSubmenu(currentEl) && openSubmenu) ;
|
|
50
45
|
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
51
46
|
event.preventDefault();
|
|
52
47
|
moveFocus(elementItems, elementItemIndex, 1);
|
package/dist/src/block/index.js
CHANGED
|
@@ -1,4 +1,75 @@
|
|
|
1
|
-
|
|
1
|
+
// src/utils/handleKeyPress/handleKeyPress.ts
|
|
2
|
+
function isTextInput(el) {
|
|
3
|
+
if (el.tagName !== "INPUT") return false;
|
|
4
|
+
const type = el.type;
|
|
5
|
+
return ["text", "email", "password", "tel", "number"].includes(type);
|
|
6
|
+
}
|
|
7
|
+
function isTextArea(el) {
|
|
8
|
+
return el.tagName === "TEXTAREA";
|
|
9
|
+
}
|
|
10
|
+
function isNativeButton(el) {
|
|
11
|
+
return el.tagName === "BUTTON" || el.tagName === "INPUT" && ["button", "submit", "reset"].includes(el.type);
|
|
12
|
+
}
|
|
13
|
+
function isLink(el) {
|
|
14
|
+
return el.tagName === "A";
|
|
15
|
+
}
|
|
16
|
+
function moveFocus(elementItems, currentIndex, direction) {
|
|
17
|
+
const len = elementItems.length;
|
|
18
|
+
const nextIndex = (currentIndex + direction + len) % len;
|
|
19
|
+
elementItems.item(nextIndex).focus();
|
|
20
|
+
}
|
|
21
|
+
function isClickableButNotSemantic(el) {
|
|
22
|
+
return el.getAttribute("data-custom-click") !== null && el.getAttribute("data-custom-click") !== void 0;
|
|
23
|
+
}
|
|
24
|
+
function handleKeyPress(event, elementItems, elementItemIndex) {
|
|
25
|
+
const currentEl = elementItems.item(elementItemIndex);
|
|
26
|
+
switch (event.key) {
|
|
27
|
+
case "ArrowUp":
|
|
28
|
+
case "ArrowLeft": {
|
|
29
|
+
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
moveFocus(elementItems, elementItemIndex, -1);
|
|
32
|
+
} else if (isTextInput(currentEl) || isTextArea(currentEl)) {
|
|
33
|
+
const cursorStart = currentEl.selectionStart;
|
|
34
|
+
if (cursorStart === 0) {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
moveFocus(elementItems, elementItemIndex, -1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
case "ArrowDown":
|
|
42
|
+
case "ArrowRight": {
|
|
43
|
+
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
moveFocus(elementItems, elementItemIndex, 1);
|
|
46
|
+
} else if (isTextInput(currentEl) || isTextArea(currentEl)) {
|
|
47
|
+
const value = currentEl.value;
|
|
48
|
+
const cursorEnd = currentEl.selectionStart;
|
|
49
|
+
if (cursorEnd === value.length) {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
moveFocus(elementItems, elementItemIndex, 1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case "Escape": {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case "Enter":
|
|
61
|
+
case " ": {
|
|
62
|
+
if (!isNativeButton(currentEl) && !isLink(currentEl) && isClickableButNotSemantic(currentEl)) {
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
currentEl.click();
|
|
65
|
+
} else if (isNativeButton(currentEl)) {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
currentEl.click();
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
2
73
|
|
|
3
74
|
// src/block/src/makeBlockAccessible/makeBlockAccessible.ts
|
|
4
75
|
function makeBlockAccessible({ blockId, blockItemsClass }) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ComboboxConfig,
|
|
1
|
+
import { C as ComboboxConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Makes a Combobox accessible by adding appropriate ARIA attributes, keyboard interactions and focus management.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ComboboxConfig,
|
|
1
|
+
import { C as ComboboxConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Makes a Combobox accessible by adding appropriate ARIA attributes, keyboard interactions and focus management.
|
package/dist/src/menu/index.cjs
CHANGED
|
@@ -1,131 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// src/utils/handleKeyPress/handleKeyPress.ts
|
|
4
|
-
function isTextInput(el) {
|
|
5
|
-
if (el.tagName !== "INPUT") return false;
|
|
6
|
-
const type = el.type;
|
|
7
|
-
return ["text", "email", "password", "tel", "number"].includes(type);
|
|
8
|
-
}
|
|
9
|
-
function isTextArea(el) {
|
|
10
|
-
return el.tagName === "TEXTAREA";
|
|
11
|
-
}
|
|
12
|
-
function isNativeButton(el) {
|
|
13
|
-
return el.tagName === "BUTTON" || el.tagName === "INPUT" && ["button", "submit", "reset"].includes(el.type);
|
|
14
|
-
}
|
|
15
|
-
function isLink(el) {
|
|
16
|
-
return el.tagName === "A";
|
|
17
|
-
}
|
|
18
|
-
function moveFocus(elementItems, currentIndex, direction) {
|
|
19
|
-
const len = elementItems.length;
|
|
20
|
-
const nextIndex = (currentIndex + direction + len) % len;
|
|
21
|
-
elementItems.item(nextIndex).focus();
|
|
22
|
-
}
|
|
23
|
-
function isClickableButNotSemantic(el) {
|
|
24
|
-
return el.getAttribute("data-custom-click") !== null && el.getAttribute("data-custom-click") !== void 0;
|
|
25
|
-
}
|
|
26
|
-
function handleMenuClose(menuElement, menuTriggerButton) {
|
|
27
|
-
menuElement.style.display = "none";
|
|
28
|
-
const menuTriggerButtonId = menuTriggerButton.getAttribute("id");
|
|
29
|
-
if (!menuTriggerButtonId) {
|
|
30
|
-
console.error("[aria-ease] Menu trigger button must have an id attribute to properly set aria attributes.");
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
menuTriggerButton.setAttribute("aria-expanded", "false");
|
|
34
|
-
}
|
|
35
|
-
function hasSubmenu(menuItem) {
|
|
36
|
-
return menuItem.getAttribute("aria-haspopup") === "true" || menuItem.getAttribute("aria-haspopup") === "menu";
|
|
37
|
-
}
|
|
38
|
-
function getSubmenuId(menuItem) {
|
|
39
|
-
return menuItem.getAttribute("aria-controls");
|
|
40
|
-
}
|
|
41
|
-
function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, triggerButton, openSubmenu, closeSubmenu, onOpenChange) {
|
|
42
|
-
const currentEl = elementItems.item(elementItemIndex);
|
|
43
|
-
switch (event.key) {
|
|
44
|
-
case "ArrowUp":
|
|
45
|
-
case "ArrowLeft": {
|
|
46
|
-
if (event.key === "ArrowLeft" && menuElementDiv && closeSubmenu) {
|
|
47
|
-
const labelledBy = menuElementDiv.getAttribute("aria-labelledby");
|
|
48
|
-
if (labelledBy) {
|
|
49
|
-
const parentTrigger = document.getElementById(labelledBy);
|
|
50
|
-
if (parentTrigger && parentTrigger.getAttribute("role") === "menuitem") {
|
|
51
|
-
event.preventDefault();
|
|
52
|
-
closeSubmenu();
|
|
53
|
-
parentTrigger.focus();
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
59
|
-
event.preventDefault();
|
|
60
|
-
moveFocus(elementItems, elementItemIndex, -1);
|
|
61
|
-
} else if (isTextInput(currentEl) || isTextArea(currentEl)) {
|
|
62
|
-
const cursorStart = currentEl.selectionStart;
|
|
63
|
-
if (cursorStart === 0) {
|
|
64
|
-
event.preventDefault();
|
|
65
|
-
moveFocus(elementItems, elementItemIndex, -1);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
case "ArrowDown":
|
|
71
|
-
case "ArrowRight": {
|
|
72
|
-
if (event.key === "ArrowRight" && hasSubmenu(currentEl) && openSubmenu) {
|
|
73
|
-
event.preventDefault();
|
|
74
|
-
const submenuId = getSubmenuId(currentEl);
|
|
75
|
-
if (submenuId) {
|
|
76
|
-
openSubmenu(submenuId);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
81
|
-
event.preventDefault();
|
|
82
|
-
moveFocus(elementItems, elementItemIndex, 1);
|
|
83
|
-
} else if (isTextInput(currentEl) || isTextArea(currentEl)) {
|
|
84
|
-
const value = currentEl.value;
|
|
85
|
-
const cursorEnd = currentEl.selectionStart;
|
|
86
|
-
if (cursorEnd === value.length) {
|
|
87
|
-
event.preventDefault();
|
|
88
|
-
moveFocus(elementItems, elementItemIndex, 1);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
case "Escape": {
|
|
94
|
-
event.preventDefault();
|
|
95
|
-
if (menuElementDiv && triggerButton) {
|
|
96
|
-
if (getComputedStyle(menuElementDiv).display === "block") {
|
|
97
|
-
handleMenuClose(menuElementDiv, triggerButton);
|
|
98
|
-
if (onOpenChange) {
|
|
99
|
-
onOpenChange(false);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
triggerButton.focus();
|
|
103
|
-
}
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
case "Enter":
|
|
107
|
-
case " ": {
|
|
108
|
-
if (!isNativeButton(currentEl) && !isLink(currentEl) && isClickableButNotSemantic(currentEl)) {
|
|
109
|
-
event.preventDefault();
|
|
110
|
-
currentEl.click();
|
|
111
|
-
} else if (isNativeButton(currentEl)) {
|
|
112
|
-
event.preventDefault();
|
|
113
|
-
currentEl.click();
|
|
114
|
-
}
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
case "Tab": {
|
|
118
|
-
if (menuElementDiv && triggerButton && (!event.shiftKey || event.shiftKey)) {
|
|
119
|
-
handleMenuClose(menuElementDiv, triggerButton);
|
|
120
|
-
if (onOpenChange) {
|
|
121
|
-
onOpenChange(false);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
3
|
// src/menu/src/makeMenuAccessible/makeMenuAccessible.ts
|
|
130
4
|
function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
131
5
|
const menuDiv = document.querySelector(`#${menuId}`);
|
|
@@ -202,15 +76,86 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
202
76
|
const items = getItems();
|
|
203
77
|
items.forEach((item) => {
|
|
204
78
|
item.setAttribute("role", "menuitem");
|
|
79
|
+
if (item.hasAttribute("data-submenu-id")) {
|
|
80
|
+
item.setAttribute("aria-haspopup", "menu");
|
|
81
|
+
item.setAttribute("aria-controls", item.getAttribute("data-submenu-id"));
|
|
82
|
+
}
|
|
205
83
|
});
|
|
206
84
|
}
|
|
85
|
+
function moveFocus(elementItems, currentIndex, direction) {
|
|
86
|
+
const len = elementItems.length;
|
|
87
|
+
const nextIndex = (currentIndex + direction + len) % len;
|
|
88
|
+
elementItems.item(nextIndex).focus();
|
|
89
|
+
}
|
|
90
|
+
function hasSubmenu(menuItem) {
|
|
91
|
+
return menuItem.hasAttribute("aria-controls") && menuItem.hasAttribute("aria-haspopup") && menuItem.getAttribute("role") === "menuitem";
|
|
92
|
+
}
|
|
207
93
|
intializeMenuItems();
|
|
94
|
+
function handleItemsKeydown(event, menuItem, menuItemIndex) {
|
|
95
|
+
switch (event.key) {
|
|
96
|
+
case "ArrowUp":
|
|
97
|
+
case "ArrowLeft": {
|
|
98
|
+
if (event.key === "ArrowLeft" && triggerButton.getAttribute("role") === "menuitem") {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
closeMenu();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
moveFocus(toNodeListLike(getFilteredItems()), menuItemIndex, -1);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "ArrowDown":
|
|
108
|
+
case "ArrowRight": {
|
|
109
|
+
if (event.key === "ArrowRight" && hasSubmenu(menuItem)) {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
const submenuId = menuItem.getAttribute("aria-controls");
|
|
112
|
+
if (submenuId) {
|
|
113
|
+
openSubmenu(submenuId);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
moveFocus(toNodeListLike(getFilteredItems()), menuItemIndex, 1);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case "Escape": {
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
closeMenu();
|
|
124
|
+
triggerButton.focus();
|
|
125
|
+
if (onOpenChange) {
|
|
126
|
+
onOpenChange(false);
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "Enter":
|
|
131
|
+
case " ": {
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
menuItem.click();
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "Tab": {
|
|
137
|
+
if (!event.shiftKey || event.shiftKey) {
|
|
138
|
+
closeMenu();
|
|
139
|
+
if (onOpenChange) {
|
|
140
|
+
onOpenChange(false);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
208
147
|
function isItemInNestedSubmenu(item) {
|
|
209
148
|
let parent = item.parentElement;
|
|
210
149
|
while (parent && parent !== menuDiv) {
|
|
211
150
|
if (parent.getAttribute("role") === "menu") {
|
|
212
151
|
return true;
|
|
213
152
|
}
|
|
153
|
+
if (parent.id) {
|
|
154
|
+
const parentMenuTrigger = menuDiv.querySelector(`[aria-controls="${parent.id}"]`);
|
|
155
|
+
if (parentMenuTrigger) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
214
159
|
parent = parent.parentElement;
|
|
215
160
|
}
|
|
216
161
|
return false;
|
|
@@ -246,9 +191,6 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
246
191
|
}
|
|
247
192
|
submenuInstance.openMenu();
|
|
248
193
|
}
|
|
249
|
-
function closeSubmenu() {
|
|
250
|
-
closeMenu();
|
|
251
|
-
}
|
|
252
194
|
function onOpenChange(isOpen) {
|
|
253
195
|
if (callback?.onOpenChange) {
|
|
254
196
|
try {
|
|
@@ -260,19 +202,9 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
260
202
|
}
|
|
261
203
|
function addListeners() {
|
|
262
204
|
const items = getFilteredItems();
|
|
263
|
-
const nodeListLike = toNodeListLike(items);
|
|
264
205
|
items.forEach((menuItem, index) => {
|
|
265
206
|
if (!handlerMap.has(menuItem)) {
|
|
266
|
-
const handler = (event) =>
|
|
267
|
-
event,
|
|
268
|
-
nodeListLike,
|
|
269
|
-
index,
|
|
270
|
-
menuDiv,
|
|
271
|
-
triggerButton,
|
|
272
|
-
openSubmenu,
|
|
273
|
-
closeSubmenu,
|
|
274
|
-
onOpenChange
|
|
275
|
-
);
|
|
207
|
+
const handler = (event) => handleItemsKeydown(event, menuItem, index);
|
|
276
208
|
menuItem.addEventListener("keydown", handler);
|
|
277
209
|
handlerMap.set(menuItem, handler);
|
|
278
210
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as MenuConfig,
|
|
1
|
+
import { M as MenuConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adds keyboard interaction to toggle menu. The menu traps focus and can be interacted with using the keyboard. The first interactive item of the menu has focus when menu open.
|
package/dist/src/menu/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as MenuConfig,
|
|
1
|
+
import { M as MenuConfig, a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adds keyboard interaction to toggle menu. The menu traps focus and can be interacted with using the keyboard. The first interactive item of the menu has focus when menu open.
|
package/dist/src/menu/index.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { handleKeyPress } from '../chunk-ZJXZZDUR.js';
|
|
2
|
-
|
|
3
1
|
// src/menu/src/makeMenuAccessible/makeMenuAccessible.ts
|
|
4
2
|
function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
5
3
|
const menuDiv = document.querySelector(`#${menuId}`);
|
|
@@ -76,15 +74,86 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
76
74
|
const items = getItems();
|
|
77
75
|
items.forEach((item) => {
|
|
78
76
|
item.setAttribute("role", "menuitem");
|
|
77
|
+
if (item.hasAttribute("data-submenu-id")) {
|
|
78
|
+
item.setAttribute("aria-haspopup", "menu");
|
|
79
|
+
item.setAttribute("aria-controls", item.getAttribute("data-submenu-id"));
|
|
80
|
+
}
|
|
79
81
|
});
|
|
80
82
|
}
|
|
83
|
+
function moveFocus(elementItems, currentIndex, direction) {
|
|
84
|
+
const len = elementItems.length;
|
|
85
|
+
const nextIndex = (currentIndex + direction + len) % len;
|
|
86
|
+
elementItems.item(nextIndex).focus();
|
|
87
|
+
}
|
|
88
|
+
function hasSubmenu(menuItem) {
|
|
89
|
+
return menuItem.hasAttribute("aria-controls") && menuItem.hasAttribute("aria-haspopup") && menuItem.getAttribute("role") === "menuitem";
|
|
90
|
+
}
|
|
81
91
|
intializeMenuItems();
|
|
92
|
+
function handleItemsKeydown(event, menuItem, menuItemIndex) {
|
|
93
|
+
switch (event.key) {
|
|
94
|
+
case "ArrowUp":
|
|
95
|
+
case "ArrowLeft": {
|
|
96
|
+
if (event.key === "ArrowLeft" && triggerButton.getAttribute("role") === "menuitem") {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
closeMenu();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
moveFocus(toNodeListLike(getFilteredItems()), menuItemIndex, -1);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case "ArrowDown":
|
|
106
|
+
case "ArrowRight": {
|
|
107
|
+
if (event.key === "ArrowRight" && hasSubmenu(menuItem)) {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
const submenuId = menuItem.getAttribute("aria-controls");
|
|
110
|
+
if (submenuId) {
|
|
111
|
+
openSubmenu(submenuId);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
moveFocus(toNodeListLike(getFilteredItems()), menuItemIndex, 1);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case "Escape": {
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
closeMenu();
|
|
122
|
+
triggerButton.focus();
|
|
123
|
+
if (onOpenChange) {
|
|
124
|
+
onOpenChange(false);
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case "Enter":
|
|
129
|
+
case " ": {
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
menuItem.click();
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case "Tab": {
|
|
135
|
+
if (!event.shiftKey || event.shiftKey) {
|
|
136
|
+
closeMenu();
|
|
137
|
+
if (onOpenChange) {
|
|
138
|
+
onOpenChange(false);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
82
145
|
function isItemInNestedSubmenu(item) {
|
|
83
146
|
let parent = item.parentElement;
|
|
84
147
|
while (parent && parent !== menuDiv) {
|
|
85
148
|
if (parent.getAttribute("role") === "menu") {
|
|
86
149
|
return true;
|
|
87
150
|
}
|
|
151
|
+
if (parent.id) {
|
|
152
|
+
const parentMenuTrigger = menuDiv.querySelector(`[aria-controls="${parent.id}"]`);
|
|
153
|
+
if (parentMenuTrigger) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
88
157
|
parent = parent.parentElement;
|
|
89
158
|
}
|
|
90
159
|
return false;
|
|
@@ -120,9 +189,6 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
120
189
|
}
|
|
121
190
|
submenuInstance.openMenu();
|
|
122
191
|
}
|
|
123
|
-
function closeSubmenu() {
|
|
124
|
-
closeMenu();
|
|
125
|
-
}
|
|
126
192
|
function onOpenChange(isOpen) {
|
|
127
193
|
if (callback?.onOpenChange) {
|
|
128
194
|
try {
|
|
@@ -134,19 +200,9 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
134
200
|
}
|
|
135
201
|
function addListeners() {
|
|
136
202
|
const items = getFilteredItems();
|
|
137
|
-
const nodeListLike = toNodeListLike(items);
|
|
138
203
|
items.forEach((menuItem, index) => {
|
|
139
204
|
if (!handlerMap.has(menuItem)) {
|
|
140
|
-
const handler = (event) =>
|
|
141
|
-
event,
|
|
142
|
-
nodeListLike,
|
|
143
|
-
index,
|
|
144
|
-
menuDiv,
|
|
145
|
-
triggerButton,
|
|
146
|
-
openSubmenu,
|
|
147
|
-
closeSubmenu,
|
|
148
|
-
onOpenChange
|
|
149
|
-
);
|
|
205
|
+
const handler = (event) => handleItemsKeydown(event, menuItem, index);
|
|
150
206
|
menuItem.addEventListener("keydown", handler);
|
|
151
207
|
handlerMap.set(menuItem, handler);
|
|
152
208
|
}
|
package/dist/src/tabs/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "aria-ease.accordion",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"created": "09-02-2026",
|
|
6
|
-
"lastUpdated": "
|
|
6
|
+
"lastUpdated": "15-03-2026",
|
|
7
7
|
"description": "ARIA Accordion interaction contract. Validates the ARIA and interaction contract for a custom accordion component following the ARIA Authoring Practices Guide accordion with show/hide pattern.",
|
|
8
8
|
"source": {
|
|
9
9
|
"apg": "https://www.w3.org/WAI/ARIA/apg/patterns/accordion/",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "aria-ease.combobox.listbox",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"created": "11-02-2026",
|
|
6
|
-
"lastUpdated": "
|
|
6
|
+
"lastUpdated": "15-03-2026",
|
|
7
7
|
"description": "ARIA Combobox with Listbox popup interaction contract. Validates the ARIA and interaction contract for a custom combobox with listbox component following the ARIA Authoring Practices Guide combobox with listbox popup pattern.",
|
|
8
8
|
"source": {
|
|
9
9
|
"apg": "https://www.w3.org/WAI/ARIA/apg/patterns/combobox/",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "aria-ease.menu",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"created": "11-02-2026",
|
|
6
|
-
"lastUpdated": "
|
|
6
|
+
"lastUpdated": "15-03-2026",
|
|
7
7
|
"description": "ARIA Menu interaction contract. Validates the ARIA and interaction contract for a custom menu component following the ARIA Authoring Practices Guide menu with popup pattern.",
|
|
8
8
|
"source": {
|
|
9
9
|
"apg": "https://www.w3.org/WAI/ARIA/apg/patterns/menubar/",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"trigger": "[data-test-id=menu-trigger]",
|
|
17
17
|
"container": "[role=menu]",
|
|
18
18
|
"items": "[role=menuitem], [role=menuitemradio], [role=menuitemcheckbox]",
|
|
19
|
-
"submenuTrigger": "[role=menu] [role=menuitem][aria-haspopup=
|
|
19
|
+
"submenuTrigger": "[role=menu] [role=menuitem][aria-haspopup=menu][aria-controls], [role=menu] [role=menuitemradio][aria-haspopup=menu][aria-controls], [role=menu] [role=menuitemcheckbox][aria-haspopup=menu][aria-controls], [data-submenu-id]",
|
|
20
20
|
"submenu": "[role=menu][aria-labelledby]",
|
|
21
21
|
"focusable": "[role=menuitem], [role=menuitemradio], [role=menuitemcheckbox]",
|
|
22
22
|
"relative": "[role=menuitem], [role=menuitemradio], [role=menuitemcheckbox]",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"id": "aria-ease.tabs",
|
|
4
4
|
"version": "1.0.0",
|
|
5
5
|
"created": "28-02-2026",
|
|
6
|
-
"lastUpdated": "
|
|
6
|
+
"lastUpdated": "15-03-2026",
|
|
7
7
|
"description": "ARIA tabs interaction contract. Validates the ARIA and interaction contract for a custom tabs component following the ARIA Authoring Practices Guide pattern.",
|
|
8
8
|
"source": {
|
|
9
9
|
"apg": "https://www.w3.org/WAI/ARIA/apg/patterns/tabs/",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aria-ease",
|
|
3
|
-
"version": "6.5.
|
|
3
|
+
"version": "6.5.1",
|
|
4
4
|
"description": "Accessibility infrastructure for the entire frontend engineering lifecycle. Build accessible patterns, run automated audits, verify component interactions, and gate deployments — all in one system.",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
// src/utils/handleKeyPress/handleKeyPress.ts
|
|
2
|
-
function isTextInput(el) {
|
|
3
|
-
if (el.tagName !== "INPUT") return false;
|
|
4
|
-
const type = el.type;
|
|
5
|
-
return ["text", "email", "password", "tel", "number"].includes(type);
|
|
6
|
-
}
|
|
7
|
-
function isTextArea(el) {
|
|
8
|
-
return el.tagName === "TEXTAREA";
|
|
9
|
-
}
|
|
10
|
-
function isNativeButton(el) {
|
|
11
|
-
return el.tagName === "BUTTON" || el.tagName === "INPUT" && ["button", "submit", "reset"].includes(el.type);
|
|
12
|
-
}
|
|
13
|
-
function isLink(el) {
|
|
14
|
-
return el.tagName === "A";
|
|
15
|
-
}
|
|
16
|
-
function moveFocus(elementItems, currentIndex, direction) {
|
|
17
|
-
const len = elementItems.length;
|
|
18
|
-
const nextIndex = (currentIndex + direction + len) % len;
|
|
19
|
-
elementItems.item(nextIndex).focus();
|
|
20
|
-
}
|
|
21
|
-
function isClickableButNotSemantic(el) {
|
|
22
|
-
return el.getAttribute("data-custom-click") !== null && el.getAttribute("data-custom-click") !== void 0;
|
|
23
|
-
}
|
|
24
|
-
function handleMenuClose(menuElement, menuTriggerButton) {
|
|
25
|
-
menuElement.style.display = "none";
|
|
26
|
-
const menuTriggerButtonId = menuTriggerButton.getAttribute("id");
|
|
27
|
-
if (!menuTriggerButtonId) {
|
|
28
|
-
console.error("[aria-ease] Menu trigger button must have an id attribute to properly set aria attributes.");
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
menuTriggerButton.setAttribute("aria-expanded", "false");
|
|
32
|
-
}
|
|
33
|
-
function hasSubmenu(menuItem) {
|
|
34
|
-
return menuItem.getAttribute("aria-haspopup") === "true" || menuItem.getAttribute("aria-haspopup") === "menu";
|
|
35
|
-
}
|
|
36
|
-
function getSubmenuId(menuItem) {
|
|
37
|
-
return menuItem.getAttribute("aria-controls");
|
|
38
|
-
}
|
|
39
|
-
function handleKeyPress(event, elementItems, elementItemIndex, menuElementDiv, triggerButton, openSubmenu, closeSubmenu, onOpenChange) {
|
|
40
|
-
const currentEl = elementItems.item(elementItemIndex);
|
|
41
|
-
switch (event.key) {
|
|
42
|
-
case "ArrowUp":
|
|
43
|
-
case "ArrowLeft": {
|
|
44
|
-
if (event.key === "ArrowLeft" && menuElementDiv && closeSubmenu) {
|
|
45
|
-
const labelledBy = menuElementDiv.getAttribute("aria-labelledby");
|
|
46
|
-
if (labelledBy) {
|
|
47
|
-
const parentTrigger = document.getElementById(labelledBy);
|
|
48
|
-
if (parentTrigger && parentTrigger.getAttribute("role") === "menuitem") {
|
|
49
|
-
event.preventDefault();
|
|
50
|
-
closeSubmenu();
|
|
51
|
-
parentTrigger.focus();
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
57
|
-
event.preventDefault();
|
|
58
|
-
moveFocus(elementItems, elementItemIndex, -1);
|
|
59
|
-
} else if (isTextInput(currentEl) || isTextArea(currentEl)) {
|
|
60
|
-
const cursorStart = currentEl.selectionStart;
|
|
61
|
-
if (cursorStart === 0) {
|
|
62
|
-
event.preventDefault();
|
|
63
|
-
moveFocus(elementItems, elementItemIndex, -1);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
case "ArrowDown":
|
|
69
|
-
case "ArrowRight": {
|
|
70
|
-
if (event.key === "ArrowRight" && hasSubmenu(currentEl) && openSubmenu) {
|
|
71
|
-
event.preventDefault();
|
|
72
|
-
const submenuId = getSubmenuId(currentEl);
|
|
73
|
-
if (submenuId) {
|
|
74
|
-
openSubmenu(submenuId);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (!isTextInput(currentEl) && !isTextArea(currentEl)) {
|
|
79
|
-
event.preventDefault();
|
|
80
|
-
moveFocus(elementItems, elementItemIndex, 1);
|
|
81
|
-
} else if (isTextInput(currentEl) || isTextArea(currentEl)) {
|
|
82
|
-
const value = currentEl.value;
|
|
83
|
-
const cursorEnd = currentEl.selectionStart;
|
|
84
|
-
if (cursorEnd === value.length) {
|
|
85
|
-
event.preventDefault();
|
|
86
|
-
moveFocus(elementItems, elementItemIndex, 1);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
case "Escape": {
|
|
92
|
-
event.preventDefault();
|
|
93
|
-
if (menuElementDiv && triggerButton) {
|
|
94
|
-
if (getComputedStyle(menuElementDiv).display === "block") {
|
|
95
|
-
handleMenuClose(menuElementDiv, triggerButton);
|
|
96
|
-
if (onOpenChange) {
|
|
97
|
-
onOpenChange(false);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
triggerButton.focus();
|
|
101
|
-
}
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
case "Enter":
|
|
105
|
-
case " ": {
|
|
106
|
-
if (!isNativeButton(currentEl) && !isLink(currentEl) && isClickableButNotSemantic(currentEl)) {
|
|
107
|
-
event.preventDefault();
|
|
108
|
-
currentEl.click();
|
|
109
|
-
} else if (isNativeButton(currentEl)) {
|
|
110
|
-
event.preventDefault();
|
|
111
|
-
currentEl.click();
|
|
112
|
-
}
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
case "Tab": {
|
|
116
|
-
if (menuElementDiv && triggerButton && (!event.shiftKey || event.shiftKey)) {
|
|
117
|
-
handleMenuClose(menuElementDiv, triggerButton);
|
|
118
|
-
if (onOpenChange) {
|
|
119
|
-
onOpenChange(false);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export { handleKeyPress };
|