aria-ease 6.5.1 → 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.
@@ -2,7 +2,7 @@ import {
2
2
  ContractReporter,
3
3
  contract_default,
4
4
  createTestPage
5
- } from "./chunk-AUJAN4RK.js";
5
+ } from "./chunk-LKN5PRYD.js";
6
6
  import {
7
7
  __export,
8
8
  __reExport
@@ -166,29 +166,20 @@ This indicates a problem with the menu component's close functionality.`
166
166
  }
167
167
  }
168
168
  async shouldSkipTest(test, page) {
169
- for (const act of test.action) {
170
- if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
171
- const submenuSelector = this.selectors[act.target];
172
- if (submenuSelector) {
173
- const submenuCount = await page.locator(submenuSelector).count();
174
- if (submenuCount === 0) {
175
- return true;
176
- }
177
- }
178
- }
169
+ const requiresSubmenu = test.action.some(
170
+ (act) => act.target === "submenu" || act.target === "submenuTrigger" || act.target === "submenuItems"
171
+ ) || test.assertions.some(
172
+ (assertion) => assertion.target === "submenu" || assertion.target === "submenuTrigger" || assertion.target === "submenuItems"
173
+ );
174
+ if (!requiresSubmenu) {
175
+ return false;
179
176
  }
180
- for (const assertion of test.assertions) {
181
- if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
182
- const submenuSelector = this.selectors[assertion.target];
183
- if (submenuSelector) {
184
- const submenuCount = await page.locator(submenuSelector).count();
185
- if (submenuCount === 0) {
186
- return true;
187
- }
188
- }
189
- }
177
+ const submenuTriggerSelector = this.selectors.submenuTrigger;
178
+ if (!submenuTriggerSelector) {
179
+ return true;
190
180
  }
191
- return false;
181
+ const submenuTriggerCount = await page.locator(submenuTriggerSelector).count();
182
+ return submenuTriggerCount === 0;
192
183
  }
193
184
  getMainSelector() {
194
185
  return this.mainSelector;
@@ -300,6 +291,9 @@ var ActionExecutor = class {
300
291
  this.selectors = selectors;
301
292
  this.timeoutMs = timeoutMs;
302
293
  }
294
+ isOptionalMenuTarget(target) {
295
+ return ["submenu", "submenuTrigger", "submenuItems"].includes(target);
296
+ }
303
297
  /**
304
298
  * Check if error is due to browser/page being closed
305
299
  */
@@ -420,7 +414,7 @@ var ActionExecutor = class {
420
414
  } else if (keyValue.includes(" ")) {
421
415
  keyValue = keyValue.replace(/ /g, "");
422
416
  }
423
- if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
417
+ if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape", "Home", "End", "Tab", "Shift+Tab"].includes(keyValue)) {
424
418
  await this.page.keyboard.press(keyValue);
425
419
  return { success: true };
426
420
  }
@@ -431,9 +425,10 @@ var ActionExecutor = class {
431
425
  const locator = this.page.locator(selector).first();
432
426
  const elementCount = await locator.count();
433
427
  if (elementCount === 0) {
428
+ const optionalMenuTarget = this.isOptionalMenuTarget(target);
434
429
  return {
435
430
  success: false,
436
- error: `${target} element not found (optional submenu test)`,
431
+ error: optionalMenuTarget ? `${target} element not found (optional submenu test)` : `${target} element not found.`,
437
432
  shouldBreak: true
438
433
  // Signal to skip this test
439
434
  };
@@ -792,23 +787,35 @@ This usually means:
792
787
  }).catch(() => {
793
788
  });
794
789
  }
795
- const failuresBeforeStatic = failures.length;
790
+ const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
791
+ let staticFailed = 0;
796
792
  const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
797
793
  for (const test of componentContract.static[0]?.assertions || []) {
798
794
  if (test.target === "relative") continue;
799
795
  const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
796
+ if (componentName === "menu" && test.target === "submenuTrigger" && !hasSubmenuCapability) {
797
+ passes.push(`Skipping submenu static assertion for ${test.target}: no submenu capability detected in rendered component.`);
798
+ reporter.reportStaticTest(staticDescription, true);
799
+ continue;
800
+ }
800
801
  const targetSelector = componentContract.selectors[test.target];
801
802
  if (!targetSelector) {
802
803
  const failure = `Selector for target ${test.target} not found.`;
803
804
  failures.push(failure);
805
+ staticFailed += 1;
804
806
  reporter.reportStaticTest(staticDescription, false, failure);
805
807
  continue;
806
808
  }
807
809
  const target = page.locator(targetSelector).first();
808
810
  const exists = await target.count() > 0;
809
811
  if (!exists) {
812
+ if (test.isOptional === true) {
813
+ reporter.reportStaticTest(staticDescription, true);
814
+ continue;
815
+ }
810
816
  const failure = `Target ${test.target} not found.`;
811
817
  failures.push(failure);
818
+ staticFailed += 1;
812
819
  reporter.reportStaticTest(staticDescription, false, failure);
813
820
  continue;
814
821
  }
@@ -845,6 +852,7 @@ This usually means:
845
852
  if (!hasAny && !allRedundant) {
846
853
  const failure = test.failureMessage + ` None of the attributes "${test.attribute}" are present.`;
847
854
  failures.push(failure);
855
+ staticFailed += 1;
848
856
  reporter.reportStaticTest(staticDescription, false, failure);
849
857
  } else if (!allRedundant && hasAny) {
850
858
  passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
@@ -870,6 +878,7 @@ This usually means:
870
878
  reporter.reportStaticTest(staticDescription, true);
871
879
  } else if (!result.success && result.failMessage) {
872
880
  failures.push(result.failMessage);
881
+ staticFailed += 1;
873
882
  reporter.reportStaticTest(staticDescription, false, result.failMessage);
874
883
  }
875
884
  }
@@ -899,9 +908,12 @@ This usually means:
899
908
  }
900
909
  const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
901
910
  const assertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
911
+ let shouldSkipCurrentTest = false;
912
+ let shouldAbortCurrentTest = false;
902
913
  for (const act of action) {
903
914
  if (!page || page.isClosed()) {
904
915
  failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
916
+ shouldAbortCurrentTest = true;
905
917
  break;
906
918
  }
907
919
  let result;
@@ -919,18 +931,29 @@ This usually means:
919
931
  continue;
920
932
  }
921
933
  if (!result.success) {
922
- if (result.error) {
923
- failures.push(result.error);
924
- }
925
934
  if (result.shouldBreak) {
926
935
  if (result.error?.includes("optional submenu test")) {
927
936
  reporter.reportTest(dynamicTest, "skip", result.error);
937
+ shouldSkipCurrentTest = true;
938
+ } else if (result.error) {
939
+ failures.push(result.error);
940
+ shouldAbortCurrentTest = true;
928
941
  }
929
942
  break;
930
943
  }
944
+ if (result.error) {
945
+ failures.push(result.error);
946
+ }
931
947
  continue;
932
948
  }
933
949
  }
950
+ if (shouldSkipCurrentTest) {
951
+ continue;
952
+ }
953
+ if (shouldAbortCurrentTest) {
954
+ reporter.reportTest(dynamicTest, "fail", failures[failures.length - 1]);
955
+ continue;
956
+ }
934
957
  for (const assertion of assertions) {
935
958
  const result = await assertionRunner.validate(assertion, dynamicTest.description);
936
959
  if (result.success && result.passMessage) {
@@ -950,7 +973,6 @@ This usually means:
950
973
  }
951
974
  }
952
975
  const staticTotal = componentContract.static[0].assertions.length;
953
- const staticFailed = failures.length - failuresBeforeStatic;
954
976
  const staticPassed = Math.max(0, staticTotal - staticFailed);
955
977
  reporter.reportStatic(staticPassed, staticFailed);
956
978
  reporter.summary(failures);
package/dist/index.cjs CHANGED
@@ -237,11 +237,6 @@ ${"\u2500".repeat(60)}`);
237
237
  ${"\u2550".repeat(60)}`);
238
238
  this.log(`\u{1F4CA} Summary
239
239
  `);
240
- const staticIcon = this.staticFailures === 0 ? "\u2705" : "\u274C";
241
- const staticStatus = this.staticFailures === 0 ? "PASS" : "FAIL";
242
- this.log(`${staticIcon} Static ARIA Tests: ${staticStatus}`);
243
- this.log(` ${this.staticPasses}/${this.staticPasses + this.staticFailures} required attributes present`);
244
- this.log("");
245
240
  if (totalFailures === 0 && this.skipped === 0 && this.optionalSuggestions === 0) {
246
241
  this.log(`\u2705 All ${totalRun} tests passed!`);
247
242
  this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
@@ -528,29 +523,20 @@ This indicates a problem with the menu component's close functionality.`
528
523
  }
529
524
  }
530
525
  async shouldSkipTest(test, page) {
531
- for (const act of test.action) {
532
- if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
533
- const submenuSelector = this.selectors[act.target];
534
- if (submenuSelector) {
535
- const submenuCount = await page.locator(submenuSelector).count();
536
- if (submenuCount === 0) {
537
- return true;
538
- }
539
- }
540
- }
526
+ const requiresSubmenu = test.action.some(
527
+ (act) => act.target === "submenu" || act.target === "submenuTrigger" || act.target === "submenuItems"
528
+ ) || test.assertions.some(
529
+ (assertion) => assertion.target === "submenu" || assertion.target === "submenuTrigger" || assertion.target === "submenuItems"
530
+ );
531
+ if (!requiresSubmenu) {
532
+ return false;
541
533
  }
542
- for (const assertion of test.assertions) {
543
- if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
544
- const submenuSelector = this.selectors[assertion.target];
545
- if (submenuSelector) {
546
- const submenuCount = await page.locator(submenuSelector).count();
547
- if (submenuCount === 0) {
548
- return true;
549
- }
550
- }
551
- }
534
+ const submenuTriggerSelector = this.selectors.submenuTrigger;
535
+ if (!submenuTriggerSelector) {
536
+ return true;
552
537
  }
553
- return false;
538
+ const submenuTriggerCount = await page.locator(submenuTriggerSelector).count();
539
+ return submenuTriggerCount === 0;
554
540
  }
555
541
  getMainSelector() {
556
542
  return this.mainSelector;
@@ -693,6 +679,9 @@ var init_ActionExecutor = __esm({
693
679
  this.selectors = selectors;
694
680
  this.timeoutMs = timeoutMs;
695
681
  }
682
+ isOptionalMenuTarget(target) {
683
+ return ["submenu", "submenuTrigger", "submenuItems"].includes(target);
684
+ }
696
685
  /**
697
686
  * Check if error is due to browser/page being closed
698
687
  */
@@ -813,7 +802,7 @@ var init_ActionExecutor = __esm({
813
802
  } else if (keyValue.includes(" ")) {
814
803
  keyValue = keyValue.replace(/ /g, "");
815
804
  }
816
- if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
805
+ if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape", "Home", "End", "Tab", "Shift+Tab"].includes(keyValue)) {
817
806
  await this.page.keyboard.press(keyValue);
818
807
  return { success: true };
819
808
  }
@@ -824,9 +813,10 @@ var init_ActionExecutor = __esm({
824
813
  const locator = this.page.locator(selector).first();
825
814
  const elementCount = await locator.count();
826
815
  if (elementCount === 0) {
816
+ const optionalMenuTarget = this.isOptionalMenuTarget(target);
827
817
  return {
828
818
  success: false,
829
- error: `${target} element not found (optional submenu test)`,
819
+ error: optionalMenuTarget ? `${target} element not found (optional submenu test)` : `${target} element not found.`,
830
820
  shouldBreak: true
831
821
  // Signal to skip this test
832
822
  };
@@ -1199,23 +1189,35 @@ This usually means:
1199
1189
  }).catch(() => {
1200
1190
  });
1201
1191
  }
1202
- const failuresBeforeStatic = failures.length;
1192
+ const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
1193
+ let staticFailed = 0;
1203
1194
  const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
1204
1195
  for (const test of componentContract.static[0]?.assertions || []) {
1205
1196
  if (test.target === "relative") continue;
1206
1197
  const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
1198
+ if (componentName === "menu" && test.target === "submenuTrigger" && !hasSubmenuCapability) {
1199
+ passes.push(`Skipping submenu static assertion for ${test.target}: no submenu capability detected in rendered component.`);
1200
+ reporter.reportStaticTest(staticDescription, true);
1201
+ continue;
1202
+ }
1207
1203
  const targetSelector = componentContract.selectors[test.target];
1208
1204
  if (!targetSelector) {
1209
1205
  const failure = `Selector for target ${test.target} not found.`;
1210
1206
  failures.push(failure);
1207
+ staticFailed += 1;
1211
1208
  reporter.reportStaticTest(staticDescription, false, failure);
1212
1209
  continue;
1213
1210
  }
1214
1211
  const target = page.locator(targetSelector).first();
1215
1212
  const exists = await target.count() > 0;
1216
1213
  if (!exists) {
1214
+ if (test.isOptional === true) {
1215
+ reporter.reportStaticTest(staticDescription, true);
1216
+ continue;
1217
+ }
1217
1218
  const failure = `Target ${test.target} not found.`;
1218
1219
  failures.push(failure);
1220
+ staticFailed += 1;
1219
1221
  reporter.reportStaticTest(staticDescription, false, failure);
1220
1222
  continue;
1221
1223
  }
@@ -1252,6 +1254,7 @@ This usually means:
1252
1254
  if (!hasAny && !allRedundant) {
1253
1255
  const failure = test.failureMessage + ` None of the attributes "${test.attribute}" are present.`;
1254
1256
  failures.push(failure);
1257
+ staticFailed += 1;
1255
1258
  reporter.reportStaticTest(staticDescription, false, failure);
1256
1259
  } else if (!allRedundant && hasAny) {
1257
1260
  passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
@@ -1277,6 +1280,7 @@ This usually means:
1277
1280
  reporter.reportStaticTest(staticDescription, true);
1278
1281
  } else if (!result.success && result.failMessage) {
1279
1282
  failures.push(result.failMessage);
1283
+ staticFailed += 1;
1280
1284
  reporter.reportStaticTest(staticDescription, false, result.failMessage);
1281
1285
  }
1282
1286
  }
@@ -1306,9 +1310,12 @@ This usually means:
1306
1310
  }
1307
1311
  const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
1308
1312
  const assertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
1313
+ let shouldSkipCurrentTest = false;
1314
+ let shouldAbortCurrentTest = false;
1309
1315
  for (const act of action) {
1310
1316
  if (!page || page.isClosed()) {
1311
1317
  failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
1318
+ shouldAbortCurrentTest = true;
1312
1319
  break;
1313
1320
  }
1314
1321
  let result;
@@ -1326,18 +1333,29 @@ This usually means:
1326
1333
  continue;
1327
1334
  }
1328
1335
  if (!result.success) {
1329
- if (result.error) {
1330
- failures.push(result.error);
1331
- }
1332
1336
  if (result.shouldBreak) {
1333
1337
  if (result.error?.includes("optional submenu test")) {
1334
1338
  reporter.reportTest(dynamicTest, "skip", result.error);
1339
+ shouldSkipCurrentTest = true;
1340
+ } else if (result.error) {
1341
+ failures.push(result.error);
1342
+ shouldAbortCurrentTest = true;
1335
1343
  }
1336
1344
  break;
1337
1345
  }
1346
+ if (result.error) {
1347
+ failures.push(result.error);
1348
+ }
1338
1349
  continue;
1339
1350
  }
1340
1351
  }
1352
+ if (shouldSkipCurrentTest) {
1353
+ continue;
1354
+ }
1355
+ if (shouldAbortCurrentTest) {
1356
+ reporter.reportTest(dynamicTest, "fail", failures[failures.length - 1]);
1357
+ continue;
1358
+ }
1341
1359
  for (const assertion of assertions) {
1342
1360
  const result = await assertionRunner.validate(assertion, dynamicTest.description);
1343
1361
  if (result.success && result.passMessage) {
@@ -1357,7 +1375,6 @@ This usually means:
1357
1375
  }
1358
1376
  }
1359
1377
  const staticTotal = componentContract.static[0].assertions.length;
1360
- const staticFailed = failures.length - failuresBeforeStatic;
1361
1378
  const staticPassed = Math.max(0, staticTotal - staticFailed);
1362
1379
  reporter.reportStatic(staticPassed, staticFailed);
1363
1380
  reporter.summary(failures);
@@ -1990,11 +2007,14 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
1990
2007
  for (let i = 0; i < allItems.length; i++) {
1991
2008
  const item = allItems.item(i);
1992
2009
  const isNested = isItemInNestedSubmenu(item);
2010
+ const isDisabled = item.getAttribute("aria-disabled") === "true";
1993
2011
  if (!isNested) {
1994
2012
  if (!item.hasAttribute("tabindex")) {
1995
2013
  item.setAttribute("tabindex", "-1");
1996
2014
  }
1997
- filteredItems.push(item);
2015
+ if (!isDisabled) {
2016
+ filteredItems.push(item);
2017
+ }
1998
2018
  }
1999
2019
  }
2000
2020
  }
@@ -2019,9 +2039,14 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
2019
2039
  const items = getItems();
2020
2040
  items.forEach((item) => {
2021
2041
  item.setAttribute("role", "menuitem");
2022
- if (item.hasAttribute("data-submenu-id")) {
2042
+ const submenuId = item.getAttribute("data-submenu-id") ?? item.getAttribute("aria-controls");
2043
+ const hasSubmenuTriggerAttributes = item.hasAttribute("aria-haspopup") && submenuId;
2044
+ if (submenuId && (item.hasAttribute("data-submenu-id") || hasSubmenuTriggerAttributes)) {
2023
2045
  item.setAttribute("aria-haspopup", "menu");
2024
- item.setAttribute("aria-controls", item.getAttribute("data-submenu-id"));
2046
+ item.setAttribute("aria-controls", submenuId);
2047
+ if (!item.hasAttribute("aria-expanded")) {
2048
+ item.setAttribute("aria-expanded", "false");
2049
+ }
2025
2050
  }
2026
2051
  });
2027
2052
  }
@@ -2030,24 +2055,29 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
2030
2055
  const nextIndex = (currentIndex + direction + len) % len;
2031
2056
  elementItems.item(nextIndex).focus();
2032
2057
  }
2058
+ function focusItemAtIndex(items, index) {
2059
+ if (items.length === 0) return;
2060
+ items[index]?.focus();
2061
+ }
2033
2062
  function hasSubmenu(menuItem) {
2034
2063
  return menuItem.hasAttribute("aria-controls") && menuItem.hasAttribute("aria-haspopup") && menuItem.getAttribute("role") === "menuitem";
2035
2064
  }
2036
2065
  intializeMenuItems();
2037
2066
  function handleItemsKeydown(event, menuItem, menuItemIndex) {
2038
2067
  switch (event.key) {
2039
- case "ArrowUp":
2040
2068
  case "ArrowLeft": {
2041
2069
  if (event.key === "ArrowLeft" && triggerButton.getAttribute("role") === "menuitem") {
2042
2070
  event.preventDefault();
2043
2071
  closeMenu();
2044
2072
  return;
2045
2073
  }
2074
+ break;
2075
+ }
2076
+ case "ArrowUp": {
2046
2077
  event.preventDefault();
2047
2078
  moveFocus2(toNodeListLike(getFilteredItems()), menuItemIndex, -1);
2048
2079
  break;
2049
2080
  }
2050
- case "ArrowDown":
2051
2081
  case "ArrowRight": {
2052
2082
  if (event.key === "ArrowRight" && hasSubmenu(menuItem)) {
2053
2083
  event.preventDefault();
@@ -2057,10 +2087,24 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
2057
2087
  return;
2058
2088
  }
2059
2089
  }
2090
+ break;
2091
+ }
2092
+ case "ArrowDown": {
2060
2093
  event.preventDefault();
2061
2094
  moveFocus2(toNodeListLike(getFilteredItems()), menuItemIndex, 1);
2062
2095
  break;
2063
2096
  }
2097
+ case "Home": {
2098
+ event.preventDefault();
2099
+ focusItemAtIndex(getFilteredItems(), 0);
2100
+ break;
2101
+ }
2102
+ case "End": {
2103
+ event.preventDefault();
2104
+ const items = getFilteredItems();
2105
+ focusItemAtIndex(items, items.length - 1);
2106
+ break;
2107
+ }
2064
2108
  case "Escape": {
2065
2109
  event.preventDefault();
2066
2110
  closeMenu();
@@ -2073,7 +2117,18 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
2073
2117
  case "Enter":
2074
2118
  case " ": {
2075
2119
  event.preventDefault();
2120
+ if (hasSubmenu(menuItem)) {
2121
+ const submenuId = menuItem.getAttribute("aria-controls");
2122
+ if (submenuId) {
2123
+ openSubmenu(submenuId);
2124
+ return;
2125
+ }
2126
+ }
2076
2127
  menuItem.click();
2128
+ closeMenu();
2129
+ if (onOpenChange) {
2130
+ onOpenChange(false);
2131
+ }
2077
2132
  break;
2078
2133
  }
2079
2134
  case "Tab": {
@@ -2182,6 +2237,7 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
2182
2237
  }
2183
2238
  }
2184
2239
  function closeMenu() {
2240
+ submenuInstances.forEach((instance) => instance.closeMenu());
2185
2241
  setAria(false);
2186
2242
  menuDiv.style.display = "none";
2187
2243
  removeListeners();
@@ -2213,7 +2269,6 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
2213
2269
  }
2214
2270
  triggerButton.addEventListener("click", handleTriggerClick);
2215
2271
  document.addEventListener("click", handleClickOutside);
2216
- triggerButton.setAttribute("data-menu-initialized", "true");
2217
2272
  function cleanup() {
2218
2273
  removeListeners();
2219
2274
  triggerButton.removeEventListener("click", handleTriggerClick);
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
@@ -464,11 +464,14 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
464
464
  for (let i = 0; i < allItems.length; i++) {
465
465
  const item = allItems.item(i);
466
466
  const isNested = isItemInNestedSubmenu(item);
467
+ const isDisabled = item.getAttribute("aria-disabled") === "true";
467
468
  if (!isNested) {
468
469
  if (!item.hasAttribute("tabindex")) {
469
470
  item.setAttribute("tabindex", "-1");
470
471
  }
471
- filteredItems.push(item);
472
+ if (!isDisabled) {
473
+ filteredItems.push(item);
474
+ }
472
475
  }
473
476
  }
474
477
  }
@@ -493,9 +496,14 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
493
496
  const items = getItems();
494
497
  items.forEach((item) => {
495
498
  item.setAttribute("role", "menuitem");
496
- if (item.hasAttribute("data-submenu-id")) {
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)) {
497
502
  item.setAttribute("aria-haspopup", "menu");
498
- item.setAttribute("aria-controls", item.getAttribute("data-submenu-id"));
503
+ item.setAttribute("aria-controls", submenuId);
504
+ if (!item.hasAttribute("aria-expanded")) {
505
+ item.setAttribute("aria-expanded", "false");
506
+ }
499
507
  }
500
508
  });
501
509
  }
@@ -504,24 +512,29 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
504
512
  const nextIndex = (currentIndex + direction + len) % len;
505
513
  elementItems.item(nextIndex).focus();
506
514
  }
515
+ function focusItemAtIndex(items, index) {
516
+ if (items.length === 0) return;
517
+ items[index]?.focus();
518
+ }
507
519
  function hasSubmenu(menuItem) {
508
520
  return menuItem.hasAttribute("aria-controls") && menuItem.hasAttribute("aria-haspopup") && menuItem.getAttribute("role") === "menuitem";
509
521
  }
510
522
  intializeMenuItems();
511
523
  function handleItemsKeydown(event, menuItem, menuItemIndex) {
512
524
  switch (event.key) {
513
- case "ArrowUp":
514
525
  case "ArrowLeft": {
515
526
  if (event.key === "ArrowLeft" && triggerButton.getAttribute("role") === "menuitem") {
516
527
  event.preventDefault();
517
528
  closeMenu();
518
529
  return;
519
530
  }
531
+ break;
532
+ }
533
+ case "ArrowUp": {
520
534
  event.preventDefault();
521
535
  moveFocus2(toNodeListLike(getFilteredItems()), menuItemIndex, -1);
522
536
  break;
523
537
  }
524
- case "ArrowDown":
525
538
  case "ArrowRight": {
526
539
  if (event.key === "ArrowRight" && hasSubmenu(menuItem)) {
527
540
  event.preventDefault();
@@ -531,10 +544,24 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
531
544
  return;
532
545
  }
533
546
  }
547
+ break;
548
+ }
549
+ case "ArrowDown": {
534
550
  event.preventDefault();
535
551
  moveFocus2(toNodeListLike(getFilteredItems()), menuItemIndex, 1);
536
552
  break;
537
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
+ }
538
565
  case "Escape": {
539
566
  event.preventDefault();
540
567
  closeMenu();
@@ -547,7 +574,18 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
547
574
  case "Enter":
548
575
  case " ": {
549
576
  event.preventDefault();
577
+ if (hasSubmenu(menuItem)) {
578
+ const submenuId = menuItem.getAttribute("aria-controls");
579
+ if (submenuId) {
580
+ openSubmenu(submenuId);
581
+ return;
582
+ }
583
+ }
550
584
  menuItem.click();
585
+ closeMenu();
586
+ if (onOpenChange) {
587
+ onOpenChange(false);
588
+ }
551
589
  break;
552
590
  }
553
591
  case "Tab": {
@@ -656,6 +694,7 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
656
694
  }
657
695
  }
658
696
  function closeMenu() {
697
+ submenuInstances.forEach((instance) => instance.closeMenu());
659
698
  setAria(false);
660
699
  menuDiv.style.display = "none";
661
700
  removeListeners();
@@ -687,7 +726,6 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
687
726
  }
688
727
  triggerButton.addEventListener("click", handleTriggerClick);
689
728
  document.addEventListener("click", handleClickOutside);
690
- triggerButton.setAttribute("data-menu-initialized", "true");
691
729
  function cleanup() {
692
730
  removeListeners();
693
731
  triggerButton.removeEventListener("click", handleTriggerClick);
@@ -1551,7 +1589,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
1551
1589
  const devServerUrl = await checkDevServer(url);
1552
1590
  if (devServerUrl) {
1553
1591
  console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
1554
- const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-7F756CFB.js");
1592
+ const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-PC6JOYYV.js");
1555
1593
  contract = await runContractTestsPlaywright(componentName, devServerUrl);
1556
1594
  } else {
1557
1595
  throw new Error(