aria-ease 6.2.2 → 6.3.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 (47) hide show
  1. package/README.md +91 -12
  2. package/bin/{chunk-7RMRFSJL.js → chunk-XLG3MIPQ.js} +5 -1
  3. package/bin/cli.cjs +52 -11
  4. package/bin/cli.js +1 -1
  5. package/bin/{contractTestRunnerPlaywright-ACAWN34W.js → contractTestRunnerPlaywright-JXQUUKFO.js} +48 -11
  6. package/bin/{test-A3ESFXOR.js → test-XSDP2NX3.js} +2 -2
  7. package/dist/{chunk-PDZQOXUN.js → chunk-RDEAG4KE.js} +5 -1
  8. package/dist/{contractTestRunnerPlaywright-O7FF7GV4.js → contractTestRunnerPlaywright-EUXD6ZZK.js} +48 -11
  9. package/dist/index.cjs +316 -69
  10. package/dist/index.d.cts +34 -4
  11. package/dist/index.d.ts +34 -4
  12. package/dist/index.js +265 -60
  13. package/dist/src/{Types.d-CRjhbrcw.d.cts → Types.d-DYfYR3Vc.d.cts} +18 -1
  14. package/dist/src/{Types.d-CRjhbrcw.d.ts → Types.d-DYfYR3Vc.d.ts} +18 -1
  15. package/dist/src/accordion/index.d.cts +2 -2
  16. package/dist/src/accordion/index.d.ts +2 -2
  17. package/dist/src/block/index.d.cts +1 -1
  18. package/dist/src/block/index.d.ts +1 -1
  19. package/dist/src/checkbox/index.cjs +0 -22
  20. package/dist/src/checkbox/index.d.cts +2 -2
  21. package/dist/src/checkbox/index.d.ts +2 -2
  22. package/dist/src/checkbox/index.js +0 -22
  23. package/dist/src/combobox/index.d.cts +1 -1
  24. package/dist/src/combobox/index.d.ts +1 -1
  25. package/dist/src/menu/index.d.cts +1 -1
  26. package/dist/src/menu/index.d.ts +1 -1
  27. package/dist/src/radio/index.cjs +0 -8
  28. package/dist/src/radio/index.d.cts +2 -2
  29. package/dist/src/radio/index.d.ts +2 -2
  30. package/dist/src/radio/index.js +0 -8
  31. package/dist/src/tabs/index.cjs +265 -0
  32. package/dist/src/tabs/index.d.cts +16 -0
  33. package/dist/src/tabs/index.d.ts +16 -0
  34. package/dist/src/tabs/index.js +263 -0
  35. package/dist/src/toggle/index.cjs +0 -28
  36. package/dist/src/toggle/index.d.cts +1 -1
  37. package/dist/src/toggle/index.d.ts +1 -1
  38. package/dist/src/toggle/index.js +0 -28
  39. package/dist/src/utils/test/{chunk-7RMRFSJL.js → chunk-XLG3MIPQ.js} +5 -1
  40. package/dist/src/utils/test/{contractTestRunnerPlaywright-7BPRTIN4.js → contractTestRunnerPlaywright-N77NEY25.js} +48 -11
  41. package/dist/src/utils/test/contracts/AccordionContract.json +18 -17
  42. package/dist/src/utils/test/contracts/ComboboxContract.json +32 -48
  43. package/dist/src/utils/test/contracts/MenuContract.json +19 -25
  44. package/dist/src/utils/test/contracts/TabsContract.json +348 -0
  45. package/dist/src/utils/test/index.cjs +52 -11
  46. package/dist/src/utils/test/index.js +2 -2
  47. package/package.json +8 -3
package/dist/index.cjs CHANGED
@@ -47,6 +47,10 @@ var init_contract = __esm({
47
47
  accordion: {
48
48
  path: "./contracts/AccordionContract.json",
49
49
  component: "accordion"
50
+ },
51
+ tabs: {
52
+ path: "./contracts/TabsContract.json",
53
+ component: "tabs"
50
54
  }
51
55
  };
52
56
  }
@@ -167,7 +171,7 @@ ${"\u2500".repeat(60)}`);
167
171
  this.log(`\u{1F4A1} Optional Enhancements (${suggestions.length}):
168
172
  `);
169
173
  this.log(`These features are optional per APG guidelines but recommended`);
170
- this.log(`for improved user experience and keyboard navigation:
174
+ this.log(`for improved user experience and keyboard interaction:
171
175
  `);
172
176
  suggestions.forEach((test, index) => {
173
177
  this.log(`${index + 1}. ${test.description}`);
@@ -385,9 +389,9 @@ async function runContractTestsPlaywright(componentName, url) {
385
389
  }
386
390
  await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
387
391
  }
388
- const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container;
392
+ const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container || componentContract.selectors.tablist || componentContract.selectors.tab;
389
393
  if (!mainSelector) {
390
- throw new Error(`CRITICAL: No main selector (trigger, input, or container) found in contract for ${componentName}`);
394
+ throw new Error(`CRITICAL: No main selector (trigger, input, container, tablist, or tab) found in contract for ${componentName}`);
391
395
  }
392
396
  try {
393
397
  await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
@@ -449,28 +453,52 @@ async function runContractTestsPlaywright(componentName, url) {
449
453
  failures.push(`Target ${test.target} not found.`);
450
454
  continue;
451
455
  }
456
+ const isRedundantCheck = (selector, attrName, expectedVal) => {
457
+ const attrPattern = new RegExp(`\\[${attrName}(?:=["']?([^\\]"']+)["']?)?\\]`);
458
+ const match = selector.match(attrPattern);
459
+ if (!match) return false;
460
+ if (!expectedVal) return true;
461
+ const selectorValue = match[1];
462
+ if (selectorValue) {
463
+ const expectedValues = expectedVal.split(" | ");
464
+ return expectedValues.includes(selectorValue);
465
+ }
466
+ return false;
467
+ };
452
468
  if (!test.expectedValue) {
453
469
  const attributes = test.attribute.split(" | ");
454
470
  let hasAny = false;
471
+ let allRedundant = true;
455
472
  for (const attr of attributes) {
456
- const value = await target.getAttribute(attr.trim());
473
+ const attrTrimmed = attr.trim();
474
+ if (isRedundantCheck(targetSelector, attrTrimmed)) {
475
+ passes.push(`${attrTrimmed} on ${test.target} verified by selector (already present in: ${targetSelector}).`);
476
+ hasAny = true;
477
+ continue;
478
+ }
479
+ allRedundant = false;
480
+ const value = await target.getAttribute(attrTrimmed);
457
481
  if (value !== null) {
458
482
  hasAny = true;
459
483
  break;
460
484
  }
461
485
  }
462
- if (!hasAny) {
486
+ if (!hasAny && !allRedundant) {
463
487
  failures.push(test.failureMessage + ` None of the attributes "${test.attribute}" are present.`);
464
- } else {
488
+ } else if (!allRedundant && hasAny) {
465
489
  passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
466
490
  }
467
491
  } else {
468
- const attributeValue = await target.getAttribute(test.attribute);
469
- const expectedValues = test.expectedValue.split(" | ");
470
- if (!attributeValue || !expectedValues.includes(attributeValue)) {
471
- failures.push(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
492
+ if (isRedundantCheck(targetSelector, test.attribute, test.expectedValue)) {
493
+ passes.push(`${test.attribute}="${test.expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
472
494
  } else {
473
- passes.push(`Attribute value matches expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
495
+ const attributeValue = await target.getAttribute(test.attribute);
496
+ const expectedValues = test.expectedValue.split(" | ");
497
+ if (!attributeValue || !expectedValues.includes(attributeValue)) {
498
+ failures.push(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
499
+ } else {
500
+ passes.push(`Attribute value matches expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
501
+ }
474
502
  }
475
503
  }
476
504
  }
@@ -579,6 +607,19 @@ This indicates a problem with the menu component's close functionality.`
579
607
  if (shouldSkipTest) {
580
608
  continue;
581
609
  }
610
+ if (componentContract.selectors.panel && componentContract.selectors.tab && componentContract.selectors.tablist) {
611
+ if (dynamicTest.isVertical !== void 0 && componentContract.selectors.tablist) {
612
+ const tablistSelector = componentContract.selectors.tablist;
613
+ const tablist = page.locator(tablistSelector).first();
614
+ const orientation = await tablist.getAttribute("aria-orientation");
615
+ const isVertical = orientation === "vertical";
616
+ if (dynamicTest.isVertical !== isVertical) {
617
+ const skipReason = dynamicTest.isVertical ? `Skipping vertical tabs test - component has horizontal orientation` : `Skipping horizontal tabs test - component has vertical orientation`;
618
+ reporter.reportTest(dynamicTest, "skip", skipReason);
619
+ continue;
620
+ }
621
+ }
622
+ }
582
623
  for (const act of action) {
583
624
  if (!page || page.isClosed()) {
584
625
  failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
@@ -938,6 +979,7 @@ __export(index_exports, {
938
979
  makeComboboxAccessible: () => makeComboboxAccessible,
939
980
  makeMenuAccessible: () => makeMenuAccessible,
940
981
  makeRadioAccessible: () => makeRadioAccessible,
982
+ makeTabsAccessible: () => makeTabsAccessible,
941
983
  makeToggleAccessible: () => makeToggleAccessible,
942
984
  testUiComponent: () => testUiComponent
943
985
  });
@@ -1362,28 +1404,6 @@ function makeCheckboxAccessible({ checkboxGroupId, checkboxesClass }) {
1362
1404
  event.preventDefault();
1363
1405
  toggleCheckbox(index);
1364
1406
  break;
1365
- case "ArrowDown":
1366
- event.preventDefault();
1367
- {
1368
- const nextIndex = (index + 1) % checkboxes.length;
1369
- checkboxes[nextIndex].focus();
1370
- }
1371
- break;
1372
- case "ArrowUp":
1373
- event.preventDefault();
1374
- {
1375
- const prevIndex = (index - 1 + checkboxes.length) % checkboxes.length;
1376
- checkboxes[prevIndex].focus();
1377
- }
1378
- break;
1379
- case "Home":
1380
- event.preventDefault();
1381
- checkboxes[0].focus();
1382
- break;
1383
- case "End":
1384
- event.preventDefault();
1385
- checkboxes[checkboxes.length - 1].focus();
1386
- break;
1387
1407
  }
1388
1408
  };
1389
1409
  }
@@ -1730,14 +1750,6 @@ function makeRadioAccessible({ radioGroupId, radiosClass, defaultSelectedIndex =
1730
1750
  event.preventDefault();
1731
1751
  selectRadio(index);
1732
1752
  break;
1733
- case "Home":
1734
- event.preventDefault();
1735
- selectRadio(0);
1736
- break;
1737
- case "End":
1738
- event.preventDefault();
1739
- selectRadio(radios.length - 1);
1740
- break;
1741
1753
  }
1742
1754
  };
1743
1755
  }
@@ -1849,34 +1861,6 @@ function makeToggleAccessible({ toggleId, togglesClass, isSingleToggle = true })
1849
1861
  event.preventDefault();
1850
1862
  toggleButton(index);
1851
1863
  break;
1852
- case "ArrowDown":
1853
- case "ArrowRight":
1854
- if (!isSingleToggle && toggles.length > 1) {
1855
- event.preventDefault();
1856
- const nextIndex = (index + 1) % toggles.length;
1857
- toggles[nextIndex].focus();
1858
- }
1859
- break;
1860
- case "ArrowUp":
1861
- case "ArrowLeft":
1862
- if (!isSingleToggle && toggles.length > 1) {
1863
- event.preventDefault();
1864
- const prevIndex = (index - 1 + toggles.length) % toggles.length;
1865
- toggles[prevIndex].focus();
1866
- }
1867
- break;
1868
- case "Home":
1869
- if (!isSingleToggle && toggles.length > 1) {
1870
- event.preventDefault();
1871
- toggles[0].focus();
1872
- }
1873
- break;
1874
- case "End":
1875
- if (!isSingleToggle && toggles.length > 1) {
1876
- event.preventDefault();
1877
- toggles[toggles.length - 1].focus();
1878
- }
1879
- break;
1880
1864
  }
1881
1865
  };
1882
1866
  }
@@ -2168,6 +2152,268 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
2168
2152
  return { cleanup, refresh, openListbox, closeListbox };
2169
2153
  }
2170
2154
 
2155
+ // src/tabs/src/makeTabsAccessible/makeTabsAccessible.ts
2156
+ function makeTabsAccessible({ tabListId, tabsClass, tabPanelsClass, orientation = "horizontal", activateOnFocus = true, callback }) {
2157
+ const tabList = document.querySelector(`#${tabListId}`);
2158
+ if (!tabList) {
2159
+ console.error(`[aria-ease] Element with id="${tabListId}" not found. Make sure the tab list container exists before calling makeTabsAccessible.`);
2160
+ return { cleanup: () => {
2161
+ } };
2162
+ }
2163
+ const tabs = Array.from(tabList.querySelectorAll(`.${tabsClass}`));
2164
+ if (tabs.length === 0) {
2165
+ console.error(`[aria-ease] No elements with class="${tabsClass}" found. Make sure tab buttons exist before calling makeTabsAccessible.`);
2166
+ return { cleanup: () => {
2167
+ } };
2168
+ }
2169
+ const tabPanels = Array.from(document.querySelectorAll(`.${tabPanelsClass}`));
2170
+ if (tabPanels.length === 0) {
2171
+ console.error(`[aria-ease] No elements with class="${tabPanelsClass}" found. Make sure tab panels exist before calling makeTabsAccessible.`);
2172
+ return { cleanup: () => {
2173
+ } };
2174
+ }
2175
+ if (tabs.length !== tabPanels.length) {
2176
+ console.error(`[aria-ease] Tab/panel mismatch: found ${tabs.length} tabs but ${tabPanels.length} panels.`);
2177
+ return { cleanup: () => {
2178
+ } };
2179
+ }
2180
+ const handlerMap = /* @__PURE__ */ new WeakMap();
2181
+ const clickHandlerMap = /* @__PURE__ */ new WeakMap();
2182
+ const contextMenuHandlerMap = /* @__PURE__ */ new WeakMap();
2183
+ let activeTabIndex = 0;
2184
+ function initialize() {
2185
+ tabList.setAttribute("role", "tablist");
2186
+ tabList.setAttribute("aria-orientation", orientation);
2187
+ tabs.forEach((tab, index) => {
2188
+ const panel = tabPanels[index];
2189
+ if (!tab.id) {
2190
+ tab.id = `${tabListId}-tab-${index}`;
2191
+ }
2192
+ if (!panel.id) {
2193
+ panel.id = `${tabListId}-panel-${index}`;
2194
+ }
2195
+ tab.setAttribute("role", "tab");
2196
+ tab.setAttribute("aria-controls", panel.id);
2197
+ tab.setAttribute("aria-selected", "false");
2198
+ tab.setAttribute("tabindex", "-1");
2199
+ panel.setAttribute("role", "tabpanel");
2200
+ panel.setAttribute("aria-labelledby", tab.id);
2201
+ panel.hidden = true;
2202
+ const hasFocusableContent = panel.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
2203
+ if (!hasFocusableContent) {
2204
+ panel.setAttribute("tabindex", "0");
2205
+ }
2206
+ });
2207
+ activateTab(0, false);
2208
+ }
2209
+ function activateTab(index, shouldFocus = true) {
2210
+ if (index < 0 || index >= tabs.length) {
2211
+ console.error(`[aria-ease] Invalid tab index: ${index}`);
2212
+ return;
2213
+ }
2214
+ const previousIndex = activeTabIndex;
2215
+ tabs.forEach((tab, i) => {
2216
+ const panel = tabPanels[i];
2217
+ tab.setAttribute("aria-selected", "false");
2218
+ tab.setAttribute("tabindex", "-1");
2219
+ panel.hidden = true;
2220
+ });
2221
+ const activeTab = tabs[index];
2222
+ const activePanel = tabPanels[index];
2223
+ activeTab.setAttribute("aria-selected", "true");
2224
+ activeTab.setAttribute("tabindex", "0");
2225
+ activePanel.hidden = false;
2226
+ if (shouldFocus) {
2227
+ activeTab.focus();
2228
+ }
2229
+ activeTabIndex = index;
2230
+ if (callback?.onTabChange && previousIndex !== index) {
2231
+ try {
2232
+ callback.onTabChange(index, previousIndex);
2233
+ } catch (error) {
2234
+ console.error("[aria-ease] Error in tabs onTabChange callback:", error);
2235
+ }
2236
+ }
2237
+ }
2238
+ function moveFocus2(direction) {
2239
+ const currentFocusedIndex = tabs.findIndex((tab) => tab === document.activeElement);
2240
+ const currentIndex = currentFocusedIndex !== -1 ? currentFocusedIndex : activeTabIndex;
2241
+ let newIndex = currentIndex;
2242
+ switch (direction) {
2243
+ case "first":
2244
+ newIndex = 0;
2245
+ break;
2246
+ case "last":
2247
+ newIndex = tabs.length - 1;
2248
+ break;
2249
+ case "next":
2250
+ newIndex = (currentIndex + 1) % tabs.length;
2251
+ break;
2252
+ case "prev":
2253
+ newIndex = (currentIndex - 1 + tabs.length) % tabs.length;
2254
+ break;
2255
+ }
2256
+ tabs[newIndex].focus();
2257
+ tabs[newIndex].setAttribute("tabindex", "0");
2258
+ tabs[activeTabIndex].setAttribute("tabindex", "-1");
2259
+ if (activateOnFocus) {
2260
+ activateTab(newIndex, false);
2261
+ } else {
2262
+ const currentActive = activeTabIndex;
2263
+ tabs.forEach((tab, i) => {
2264
+ if (i === newIndex) {
2265
+ tab.setAttribute("tabindex", "0");
2266
+ } else if (i !== currentActive) {
2267
+ tab.setAttribute("tabindex", "-1");
2268
+ }
2269
+ });
2270
+ }
2271
+ }
2272
+ function handleTabClick(index) {
2273
+ return () => {
2274
+ activateTab(index);
2275
+ };
2276
+ }
2277
+ function handleTabKeydown(index) {
2278
+ return (event) => {
2279
+ const { key } = event;
2280
+ let handled = false;
2281
+ if (orientation === "horizontal") {
2282
+ switch (key) {
2283
+ case "ArrowLeft":
2284
+ event.preventDefault();
2285
+ moveFocus2("prev");
2286
+ handled = true;
2287
+ break;
2288
+ case "ArrowRight":
2289
+ event.preventDefault();
2290
+ moveFocus2("next");
2291
+ handled = true;
2292
+ break;
2293
+ }
2294
+ } else {
2295
+ switch (key) {
2296
+ case "ArrowUp":
2297
+ event.preventDefault();
2298
+ moveFocus2("prev");
2299
+ handled = true;
2300
+ break;
2301
+ case "ArrowDown":
2302
+ event.preventDefault();
2303
+ moveFocus2("next");
2304
+ handled = true;
2305
+ break;
2306
+ }
2307
+ }
2308
+ if (!handled) {
2309
+ switch (key) {
2310
+ case "Home":
2311
+ event.preventDefault();
2312
+ moveFocus2("first");
2313
+ break;
2314
+ case "End":
2315
+ event.preventDefault();
2316
+ moveFocus2("last");
2317
+ break;
2318
+ case " ":
2319
+ case "Enter":
2320
+ if (!activateOnFocus) {
2321
+ event.preventDefault();
2322
+ activateTab(index);
2323
+ }
2324
+ break;
2325
+ case "F10":
2326
+ if (event.shiftKey && callback?.onContextMenu) {
2327
+ event.preventDefault();
2328
+ try {
2329
+ callback.onContextMenu(index, tabs[index]);
2330
+ } catch (error) {
2331
+ console.error("[aria-ease] Error in tabs onContextMenu callback:", error);
2332
+ }
2333
+ }
2334
+ break;
2335
+ }
2336
+ }
2337
+ };
2338
+ }
2339
+ function handleTabContextMenu(index) {
2340
+ return (event) => {
2341
+ if (callback?.onContextMenu) {
2342
+ event.preventDefault();
2343
+ try {
2344
+ callback.onContextMenu(index, tabs[index]);
2345
+ } catch (error) {
2346
+ console.error("[aria-ease] Error in tabs onContextMenu callback:", error);
2347
+ }
2348
+ }
2349
+ };
2350
+ }
2351
+ function addListeners() {
2352
+ tabs.forEach((tab, index) => {
2353
+ const clickHandler = handleTabClick(index);
2354
+ const keydownHandler = handleTabKeydown(index);
2355
+ const contextMenuHandler = handleTabContextMenu(index);
2356
+ tab.addEventListener("click", clickHandler);
2357
+ tab.addEventListener("keydown", keydownHandler);
2358
+ if (callback?.onContextMenu) {
2359
+ tab.addEventListener("contextmenu", contextMenuHandler);
2360
+ contextMenuHandlerMap.set(tab, contextMenuHandler);
2361
+ }
2362
+ handlerMap.set(tab, keydownHandler);
2363
+ clickHandlerMap.set(tab, clickHandler);
2364
+ });
2365
+ }
2366
+ function removeListeners() {
2367
+ tabs.forEach((tab) => {
2368
+ const keydownHandler = handlerMap.get(tab);
2369
+ const clickHandler = clickHandlerMap.get(tab);
2370
+ const contextMenuHandler = contextMenuHandlerMap.get(tab);
2371
+ if (keydownHandler) {
2372
+ tab.removeEventListener("keydown", keydownHandler);
2373
+ handlerMap.delete(tab);
2374
+ }
2375
+ if (clickHandler) {
2376
+ tab.removeEventListener("click", clickHandler);
2377
+ clickHandlerMap.delete(tab);
2378
+ }
2379
+ if (contextMenuHandler) {
2380
+ tab.removeEventListener("contextmenu", contextMenuHandler);
2381
+ contextMenuHandlerMap.delete(tab);
2382
+ }
2383
+ });
2384
+ }
2385
+ function cleanup() {
2386
+ removeListeners();
2387
+ tabs.forEach((tab, index) => {
2388
+ const panel = tabPanels[index];
2389
+ tab.removeAttribute("role");
2390
+ tab.removeAttribute("aria-selected");
2391
+ tab.removeAttribute("aria-controls");
2392
+ tab.removeAttribute("tabindex");
2393
+ panel.removeAttribute("role");
2394
+ panel.removeAttribute("aria-labelledby");
2395
+ panel.removeAttribute("tabindex");
2396
+ panel.hidden = false;
2397
+ });
2398
+ tabList.removeAttribute("role");
2399
+ tabList.removeAttribute("aria-orientation");
2400
+ }
2401
+ function refresh() {
2402
+ removeListeners();
2403
+ const newTabs = Array.from(tabList.querySelectorAll(`.${tabsClass}`));
2404
+ const newPanels = Array.from(document.querySelectorAll(`.${tabPanelsClass}`));
2405
+ tabs.length = 0;
2406
+ tabs.push(...newTabs);
2407
+ tabPanels.length = 0;
2408
+ tabPanels.push(...newPanels);
2409
+ initialize();
2410
+ addListeners();
2411
+ }
2412
+ initialize();
2413
+ addListeners();
2414
+ return { activateTab, cleanup, refresh };
2415
+ }
2416
+
2171
2417
  // src/utils/test/src/test.ts
2172
2418
  var import_jest_axe = require("jest-axe");
2173
2419
 
@@ -2371,6 +2617,7 @@ async function cleanupTests() {
2371
2617
  makeComboboxAccessible,
2372
2618
  makeMenuAccessible,
2373
2619
  makeRadioAccessible,
2620
+ makeTabsAccessible,
2374
2621
  makeToggleAccessible,
2375
2622
  testUiComponent
2376
2623
  });
package/dist/index.d.cts CHANGED
@@ -17,6 +17,9 @@ interface AccessibilityInstance {
17
17
  collapseItem?: (index: number) => void;
18
18
  toggleItem?: (index: number) => void;
19
19
 
20
+ // Tabs methods
21
+ activateTab?: (index: number, shouldFocus?: boolean) => void;
22
+
20
23
  // Radio methods
21
24
  selectRadio?: (index: number) => void;
22
25
  getSelectedIndex?: () => number;
@@ -51,6 +54,20 @@ interface AccordionCallback {
51
54
  onCollapse?: (index: number) => void;
52
55
  }
53
56
 
57
+ interface TabsConfig {
58
+ tabListId: string;
59
+ tabsClass: string;
60
+ tabPanelsClass: string;
61
+ orientation?: "horizontal" | "vertical";
62
+ activateOnFocus?: boolean;
63
+ callback?: TabsCallback;
64
+ }
65
+
66
+ interface TabsCallback {
67
+ onTabChange?: (activeIndex: number, previousIndex: number) => void;
68
+ onContextMenu?: (tabIndex: number, tabElement: HTMLElement) => void;
69
+ }
70
+
54
71
  interface ComboboxConfig {
55
72
  comboboxInputId: string;
56
73
  comboboxButtonId?: string;
@@ -78,7 +95,7 @@ interface MenuCallback {
78
95
  }
79
96
 
80
97
  /**
81
- * Makes an accordion accessible by managing ARIA attributes, keyboard navigation, and state.
98
+ * Makes an accordion accessible by managing ARIA attributes, keyboard interaction, and state.
82
99
  * Handles multiple accordion items with proper focus management and keyboard interactions.
83
100
  * @param {string} accordionId - The id of the accordion container.
84
101
  * @param {string} triggersClass - The shared class of all accordion trigger buttons.
@@ -102,7 +119,7 @@ interface BlockConfig {
102
119
  declare function makeBlockAccessible({ blockId, blockItemsClass }: BlockConfig): AccessibilityInstance;
103
120
 
104
121
  /**
105
- * Makes a checkbox group accessible by managing ARIA attributes and keyboard navigation.
122
+ * Makes a checkbox group accessible by managing ARIA attributes and keyboard interaction.
106
123
  * Handles multiple independent checkboxes with proper focus management and keyboard interactions.
107
124
  * @param {string} checkboxGroupId - The id of the checkbox group container.
108
125
  * @param {string} checkboxesClass - The shared class of all checkboxes.
@@ -124,7 +141,7 @@ declare function makeCheckboxAccessible({ checkboxGroupId, checkboxesClass }: Ch
124
141
  declare function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }: MenuConfig): AccessibilityInstance;
125
142
 
126
143
  /**
127
- * Makes a radio group accessible by managing ARIA attributes, keyboard navigation, and state.
144
+ * Makes a radio group accessible by managing ARIA attributes, keyboard interaction, and state.
128
145
  * Handles radio button selection with proper focus management and keyboard interactions.
129
146
  * @param {string} radioGroupId - The id of the radio group container.
130
147
  * @param {string} radiosClass - The shared class of all radio buttons.
@@ -164,6 +181,19 @@ declare function makeToggleAccessible({ toggleId, togglesClass, isSingleToggle }
164
181
 
165
182
  declare function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId, listBoxItemsClass, callback }: ComboboxConfig): AccessibilityInstance;
166
183
 
184
+ /**
185
+ * Makes tabs accessible by managing ARIA attributes, keyboard interaction, and state.
186
+ * Implements WAI-ARIA tabs pattern with full keyboard support and ARIA properties.
187
+ * @param {string} tabListId - The id of the tab list container.
188
+ * @param {string} tabsClass - The shared class of all tab buttons.
189
+ * @param {string} tabPanelsClass - The shared class of all tab panels.
190
+ * @param {('horizontal' | 'vertical')} orientation - Tab list orientation (default: 'horizontal').
191
+ * @param {boolean} activateOnFocus - Whether tabs activate automatically on focus (default: true).
192
+ * @param {TabsCallback} callback - Configuration options for callbacks.
193
+ */
194
+
195
+ declare function makeTabsAccessible({ tabListId, tabsClass, tabPanelsClass, orientation, activateOnFocus, callback }: TabsConfig): AccessibilityInstance;
196
+
167
197
  /**
168
198
  * Runs static and interactions accessibility test on UI components.
169
199
  * @param {string} componentName The name of the component contract to test against
@@ -178,4 +208,4 @@ declare function testUiComponent(componentName: string, component: HTMLElement |
178
208
  */
179
209
  declare function cleanupTests(): Promise<void>;
180
210
 
181
- export { cleanupTests, makeAccordionAccessible, makeBlockAccessible, makeCheckboxAccessible, makeComboboxAccessible, makeMenuAccessible, makeRadioAccessible, makeToggleAccessible, testUiComponent };
211
+ export { cleanupTests, makeAccordionAccessible, makeBlockAccessible, makeCheckboxAccessible, makeComboboxAccessible, makeMenuAccessible, makeRadioAccessible, makeTabsAccessible, makeToggleAccessible, testUiComponent };
package/dist/index.d.ts CHANGED
@@ -17,6 +17,9 @@ interface AccessibilityInstance {
17
17
  collapseItem?: (index: number) => void;
18
18
  toggleItem?: (index: number) => void;
19
19
 
20
+ // Tabs methods
21
+ activateTab?: (index: number, shouldFocus?: boolean) => void;
22
+
20
23
  // Radio methods
21
24
  selectRadio?: (index: number) => void;
22
25
  getSelectedIndex?: () => number;
@@ -51,6 +54,20 @@ interface AccordionCallback {
51
54
  onCollapse?: (index: number) => void;
52
55
  }
53
56
 
57
+ interface TabsConfig {
58
+ tabListId: string;
59
+ tabsClass: string;
60
+ tabPanelsClass: string;
61
+ orientation?: "horizontal" | "vertical";
62
+ activateOnFocus?: boolean;
63
+ callback?: TabsCallback;
64
+ }
65
+
66
+ interface TabsCallback {
67
+ onTabChange?: (activeIndex: number, previousIndex: number) => void;
68
+ onContextMenu?: (tabIndex: number, tabElement: HTMLElement) => void;
69
+ }
70
+
54
71
  interface ComboboxConfig {
55
72
  comboboxInputId: string;
56
73
  comboboxButtonId?: string;
@@ -78,7 +95,7 @@ interface MenuCallback {
78
95
  }
79
96
 
80
97
  /**
81
- * Makes an accordion accessible by managing ARIA attributes, keyboard navigation, and state.
98
+ * Makes an accordion accessible by managing ARIA attributes, keyboard interaction, and state.
82
99
  * Handles multiple accordion items with proper focus management and keyboard interactions.
83
100
  * @param {string} accordionId - The id of the accordion container.
84
101
  * @param {string} triggersClass - The shared class of all accordion trigger buttons.
@@ -102,7 +119,7 @@ interface BlockConfig {
102
119
  declare function makeBlockAccessible({ blockId, blockItemsClass }: BlockConfig): AccessibilityInstance;
103
120
 
104
121
  /**
105
- * Makes a checkbox group accessible by managing ARIA attributes and keyboard navigation.
122
+ * Makes a checkbox group accessible by managing ARIA attributes and keyboard interaction.
106
123
  * Handles multiple independent checkboxes with proper focus management and keyboard interactions.
107
124
  * @param {string} checkboxGroupId - The id of the checkbox group container.
108
125
  * @param {string} checkboxesClass - The shared class of all checkboxes.
@@ -124,7 +141,7 @@ declare function makeCheckboxAccessible({ checkboxGroupId, checkboxesClass }: Ch
124
141
  declare function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }: MenuConfig): AccessibilityInstance;
125
142
 
126
143
  /**
127
- * Makes a radio group accessible by managing ARIA attributes, keyboard navigation, and state.
144
+ * Makes a radio group accessible by managing ARIA attributes, keyboard interaction, and state.
128
145
  * Handles radio button selection with proper focus management and keyboard interactions.
129
146
  * @param {string} radioGroupId - The id of the radio group container.
130
147
  * @param {string} radiosClass - The shared class of all radio buttons.
@@ -164,6 +181,19 @@ declare function makeToggleAccessible({ toggleId, togglesClass, isSingleToggle }
164
181
 
165
182
  declare function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId, listBoxItemsClass, callback }: ComboboxConfig): AccessibilityInstance;
166
183
 
184
+ /**
185
+ * Makes tabs accessible by managing ARIA attributes, keyboard interaction, and state.
186
+ * Implements WAI-ARIA tabs pattern with full keyboard support and ARIA properties.
187
+ * @param {string} tabListId - The id of the tab list container.
188
+ * @param {string} tabsClass - The shared class of all tab buttons.
189
+ * @param {string} tabPanelsClass - The shared class of all tab panels.
190
+ * @param {('horizontal' | 'vertical')} orientation - Tab list orientation (default: 'horizontal').
191
+ * @param {boolean} activateOnFocus - Whether tabs activate automatically on focus (default: true).
192
+ * @param {TabsCallback} callback - Configuration options for callbacks.
193
+ */
194
+
195
+ declare function makeTabsAccessible({ tabListId, tabsClass, tabPanelsClass, orientation, activateOnFocus, callback }: TabsConfig): AccessibilityInstance;
196
+
167
197
  /**
168
198
  * Runs static and interactions accessibility test on UI components.
169
199
  * @param {string} componentName The name of the component contract to test against
@@ -178,4 +208,4 @@ declare function testUiComponent(componentName: string, component: HTMLElement |
178
208
  */
179
209
  declare function cleanupTests(): Promise<void>;
180
210
 
181
- export { cleanupTests, makeAccordionAccessible, makeBlockAccessible, makeCheckboxAccessible, makeComboboxAccessible, makeMenuAccessible, makeRadioAccessible, makeToggleAccessible, testUiComponent };
211
+ export { cleanupTests, makeAccordionAccessible, makeBlockAccessible, makeCheckboxAccessible, makeComboboxAccessible, makeMenuAccessible, makeRadioAccessible, makeTabsAccessible, makeToggleAccessible, testUiComponent };