aria-ease 6.5.0 → 6.6.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.
Files changed (42) hide show
  1. package/README.md +14 -10
  2. package/bin/{chunk-AUJAN4RK.js → chunk-LKN5PRYD.js} +0 -5
  3. package/bin/cli.cjs +50 -33
  4. package/bin/cli.js +1 -1
  5. package/{dist/contractTestRunnerPlaywright-7F756CFB.js → bin/contractTestRunnerPlaywright-PC6JOYYV.js} +51 -29
  6. package/bin/{test-C3CMRHSI.js → test-LP723IXM.js} +2 -2
  7. package/dist/{chunk-AUJAN4RK.js → chunk-LKN5PRYD.js} +0 -5
  8. package/{bin/contractTestRunnerPlaywright-7F756CFB.js → dist/contractTestRunnerPlaywright-PC6JOYYV.js} +51 -29
  9. package/dist/index.cjs +165 -100
  10. package/dist/index.js +117 -69
  11. package/dist/src/{Types.d-yGC2bBaB.d.cts → Types.d-DYfYR3Vc.d.cts} +1 -1
  12. package/dist/src/{Types.d-yGC2bBaB.d.ts → Types.d-DYfYR3Vc.d.ts} +1 -1
  13. package/dist/src/accordion/index.d.cts +1 -1
  14. package/dist/src/accordion/index.d.ts +1 -1
  15. package/dist/src/block/index.cjs +1 -6
  16. package/dist/src/block/index.d.cts +1 -1
  17. package/dist/src/block/index.d.ts +1 -1
  18. package/dist/src/block/index.js +72 -1
  19. package/dist/src/checkbox/index.d.cts +1 -1
  20. package/dist/src/checkbox/index.d.ts +1 -1
  21. package/dist/src/combobox/index.d.cts +1 -1
  22. package/dist/src/combobox/index.d.ts +1 -1
  23. package/dist/src/menu/index.cjs +112 -142
  24. package/dist/src/menu/index.d.cts +1 -1
  25. package/dist/src/menu/index.d.ts +1 -1
  26. package/dist/src/menu/index.js +112 -18
  27. package/dist/src/radio/index.d.cts +1 -1
  28. package/dist/src/radio/index.d.ts +1 -1
  29. package/dist/src/tabs/index.d.cts +1 -1
  30. package/dist/src/tabs/index.d.ts +1 -1
  31. package/dist/src/toggle/index.d.cts +1 -1
  32. package/dist/src/toggle/index.d.ts +1 -1
  33. package/dist/src/utils/test/aria-contracts/accordion/accordion.contract.json +1 -1
  34. package/dist/src/utils/test/aria-contracts/combobox/combobox.listbox.contract.json +1 -1
  35. package/dist/src/utils/test/aria-contracts/menu/menu.contract.json +143 -30
  36. package/dist/src/utils/test/aria-contracts/tabs/tabs.contract.json +8 -8
  37. package/dist/src/utils/test/{chunk-AUJAN4RK.js → chunk-LKN5PRYD.js} +0 -5
  38. package/dist/src/utils/test/{contractTestRunnerPlaywright-HL73FADJ.js → contractTestRunnerPlaywright-RGKMGXND.js} +51 -29
  39. package/dist/src/utils/test/index.cjs +50 -33
  40. package/dist/src/utils/test/index.js +2 -2
  41. package/package.json +1 -1
  42. package/dist/src/chunk-ZJXZZDUR.js +0 -127
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  ContractReporter,
3
3
  closeSharedBrowser,
4
4
  contract_default
5
- } from "./chunk-AUJAN4RK.js";
5
+ } from "./chunk-LKN5PRYD.js";
6
6
  import "./chunk-I2KLQ2HA.js";
7
7
 
8
8
  // src/accordion/src/makeAccordionAccessible/makeAccordionAccessible.ts
@@ -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 handleMenuClose(menuElement, menuTriggerButton) {
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:
@@ -514,11 +464,14 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
514
464
  for (let i = 0; i < allItems.length; i++) {
515
465
  const item = allItems.item(i);
516
466
  const isNested = isItemInNestedSubmenu(item);
467
+ const isDisabled = item.getAttribute("aria-disabled") === "true";
517
468
  if (!isNested) {
518
469
  if (!item.hasAttribute("tabindex")) {
519
470
  item.setAttribute("tabindex", "-1");
520
471
  }
521
- filteredItems.push(item);
472
+ if (!isDisabled) {
473
+ filteredItems.push(item);
474
+ }
522
475
  }
523
476
  }
524
477
  }
@@ -543,15 +496,123 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
543
496
  const items = getItems();
544
497
  items.forEach((item) => {
545
498
  item.setAttribute("role", "menuitem");
499
+ const submenuId = item.getAttribute("data-submenu-id") ?? item.getAttribute("aria-controls");
500
+ const hasSubmenuTriggerAttributes = item.hasAttribute("aria-haspopup") && submenuId;
501
+ if (submenuId && (item.hasAttribute("data-submenu-id") || hasSubmenuTriggerAttributes)) {
502
+ item.setAttribute("aria-haspopup", "menu");
503
+ item.setAttribute("aria-controls", submenuId);
504
+ if (!item.hasAttribute("aria-expanded")) {
505
+ item.setAttribute("aria-expanded", "false");
506
+ }
507
+ }
546
508
  });
547
509
  }
510
+ function moveFocus2(elementItems, currentIndex, direction) {
511
+ const len = elementItems.length;
512
+ const nextIndex = (currentIndex + direction + len) % len;
513
+ elementItems.item(nextIndex).focus();
514
+ }
515
+ function focusItemAtIndex(items, index) {
516
+ if (items.length === 0) return;
517
+ items[index]?.focus();
518
+ }
519
+ function hasSubmenu(menuItem) {
520
+ return menuItem.hasAttribute("aria-controls") && menuItem.hasAttribute("aria-haspopup") && menuItem.getAttribute("role") === "menuitem";
521
+ }
548
522
  intializeMenuItems();
523
+ function handleItemsKeydown(event, menuItem, menuItemIndex) {
524
+ switch (event.key) {
525
+ case "ArrowLeft": {
526
+ if (event.key === "ArrowLeft" && triggerButton.getAttribute("role") === "menuitem") {
527
+ event.preventDefault();
528
+ closeMenu();
529
+ return;
530
+ }
531
+ break;
532
+ }
533
+ case "ArrowUp": {
534
+ event.preventDefault();
535
+ moveFocus2(toNodeListLike(getFilteredItems()), menuItemIndex, -1);
536
+ break;
537
+ }
538
+ case "ArrowRight": {
539
+ if (event.key === "ArrowRight" && hasSubmenu(menuItem)) {
540
+ event.preventDefault();
541
+ const submenuId = menuItem.getAttribute("aria-controls");
542
+ if (submenuId) {
543
+ openSubmenu(submenuId);
544
+ return;
545
+ }
546
+ }
547
+ break;
548
+ }
549
+ case "ArrowDown": {
550
+ event.preventDefault();
551
+ moveFocus2(toNodeListLike(getFilteredItems()), menuItemIndex, 1);
552
+ break;
553
+ }
554
+ case "Home": {
555
+ event.preventDefault();
556
+ focusItemAtIndex(getFilteredItems(), 0);
557
+ break;
558
+ }
559
+ case "End": {
560
+ event.preventDefault();
561
+ const items = getFilteredItems();
562
+ focusItemAtIndex(items, items.length - 1);
563
+ break;
564
+ }
565
+ case "Escape": {
566
+ event.preventDefault();
567
+ closeMenu();
568
+ triggerButton.focus();
569
+ if (onOpenChange) {
570
+ onOpenChange(false);
571
+ }
572
+ break;
573
+ }
574
+ case "Enter":
575
+ case " ": {
576
+ event.preventDefault();
577
+ if (hasSubmenu(menuItem)) {
578
+ const submenuId = menuItem.getAttribute("aria-controls");
579
+ if (submenuId) {
580
+ openSubmenu(submenuId);
581
+ return;
582
+ }
583
+ }
584
+ menuItem.click();
585
+ closeMenu();
586
+ if (onOpenChange) {
587
+ onOpenChange(false);
588
+ }
589
+ break;
590
+ }
591
+ case "Tab": {
592
+ if (!event.shiftKey || event.shiftKey) {
593
+ closeMenu();
594
+ if (onOpenChange) {
595
+ onOpenChange(false);
596
+ }
597
+ }
598
+ break;
599
+ }
600
+ default:
601
+ break;
602
+ }
603
+ }
549
604
  function isItemInNestedSubmenu(item) {
550
605
  let parent = item.parentElement;
551
606
  while (parent && parent !== menuDiv) {
552
607
  if (parent.getAttribute("role") === "menu") {
553
608
  return true;
554
609
  }
610
+ if (parent.id) {
611
+ const parentMenuTrigger = menuDiv.querySelector(`[aria-controls="${parent.id}"]`);
612
+ if (parentMenuTrigger) {
613
+ return true;
614
+ }
615
+ }
555
616
  parent = parent.parentElement;
556
617
  }
557
618
  return false;
@@ -587,9 +648,6 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
587
648
  }
588
649
  submenuInstance.openMenu();
589
650
  }
590
- function closeSubmenu() {
591
- closeMenu();
592
- }
593
651
  function onOpenChange(isOpen) {
594
652
  if (callback?.onOpenChange) {
595
653
  try {
@@ -601,19 +659,9 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
601
659
  }
602
660
  function addListeners() {
603
661
  const items = getFilteredItems();
604
- const nodeListLike = toNodeListLike(items);
605
662
  items.forEach((menuItem, index) => {
606
663
  if (!handlerMap.has(menuItem)) {
607
- const handler = (event) => handleKeyPress(
608
- event,
609
- nodeListLike,
610
- index,
611
- menuDiv,
612
- triggerButton,
613
- openSubmenu,
614
- closeSubmenu,
615
- onOpenChange
616
- );
664
+ const handler = (event) => handleItemsKeydown(event, menuItem, index);
617
665
  menuItem.addEventListener("keydown", handler);
618
666
  handlerMap.set(menuItem, handler);
619
667
  }
@@ -646,6 +694,7 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
646
694
  }
647
695
  }
648
696
  function closeMenu() {
697
+ submenuInstances.forEach((instance) => instance.closeMenu());
649
698
  setAria(false);
650
699
  menuDiv.style.display = "none";
651
700
  removeListeners();
@@ -677,7 +726,6 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
677
726
  }
678
727
  triggerButton.addEventListener("click", handleTriggerClick);
679
728
  document.addEventListener("click", handleClickOutside);
680
- triggerButton.setAttribute("data-menu-initialized", "true");
681
729
  function cleanup() {
682
730
  removeListeners();
683
731
  triggerButton.removeEventListener("click", handleTriggerClick);
@@ -1541,7 +1589,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
1541
1589
  const devServerUrl = await checkDevServer(url);
1542
1590
  if (devServerUrl) {
1543
1591
  console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
1544
- const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-7F756CFB.js");
1592
+ const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-PC6JOYYV.js");
1545
1593
  contract = await runContractTestsPlaywright(componentName, devServerUrl);
1546
1594
  } else {
1547
1595
  throw new Error(
@@ -88,4 +88,4 @@ interface MenuCallback {
88
88
  onOpenChange?: (isOpen: boolean) => void;
89
89
  }
90
90
 
91
- export type { AccessibilityInstance as A, ComboboxConfig as C, MenuConfig as M, TabsConfig as T, AccordionConfig as a };
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 { AccessibilityInstance as A, ComboboxConfig as C, MenuConfig as M, TabsConfig as T, AccordionConfig as a };
91
+ export type { AccordionConfig as A, ComboboxConfig as C, MenuConfig as M, TabsConfig as T, AccessibilityInstance as a };
@@ -1,4 +1,4 @@
1
- import { a as AccordionConfig, A as AccessibilityInstance } from '../Types.d-yGC2bBaB.cjs';
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 { a as AccordionConfig, A as AccessibilityInstance } from '../Types.d-yGC2bBaB.js';
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.
@@ -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 hasSubmenu(menuItem) {
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);
@@ -1,4 +1,4 @@
1
- import { A as AccessibilityInstance } from '../Types.d-yGC2bBaB.cjs';
1
+ import { a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
2
2
 
3
3
  /**
4
4
  * Adds keyboard interaction to block. The block traps focus and can be interacted with using the keyboard.
@@ -1,4 +1,4 @@
1
- import { A as AccessibilityInstance } from '../Types.d-yGC2bBaB.js';
1
+ import { a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
2
2
 
3
3
  /**
4
4
  * Adds keyboard interaction to block. The block traps focus and can be interacted with using the keyboard.
@@ -1,4 +1,75 @@
1
- import { handleKeyPress } from '../chunk-ZJXZZDUR.js';
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 { A as AccessibilityInstance } from '../Types.d-yGC2bBaB.cjs';
1
+ import { a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.cjs';
2
2
 
3
3
  /**
4
4
  * Makes a checkbox group accessible by managing ARIA attributes and keyboard interaction.
@@ -1,4 +1,4 @@
1
- import { A as AccessibilityInstance } from '../Types.d-yGC2bBaB.js';
1
+ import { a as AccessibilityInstance } from '../Types.d-DYfYR3Vc.js';
2
2
 
3
3
  /**
4
4
  * Makes a checkbox group accessible by managing ARIA attributes and keyboard interaction.
@@ -1,4 +1,4 @@
1
- import { C as ComboboxConfig, A as AccessibilityInstance } from '../Types.d-yGC2bBaB.cjs';
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, A as AccessibilityInstance } from '../Types.d-yGC2bBaB.js';
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.