abledom 0.6.0 → 0.6.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/esm/index.js CHANGED
@@ -860,10 +860,17 @@ var AccessibilityAffectingElements = {
860
860
  aside: true,
861
861
  body: true,
862
862
  button: true,
863
+ caption: true,
864
+ col: true,
865
+ colgroup: true,
863
866
  datalist: true,
867
+ dd: true,
864
868
  details: true,
865
869
  dialog: true,
866
870
  dl: true,
871
+ dt: true,
872
+ figcaption: true,
873
+ figure: true,
867
874
  form: true,
868
875
  h1: true,
869
876
  h2: true,
@@ -875,6 +882,7 @@ var AccessibilityAffectingElements = {
875
882
  iframe: true,
876
883
  img: true,
877
884
  input: true,
885
+ legend: true,
878
886
  li: true,
879
887
  link: true,
880
888
  main: true,
@@ -884,15 +892,22 @@ var AccessibilityAffectingElements = {
884
892
  nav: true,
885
893
  object: true,
886
894
  ol: true,
895
+ optgroup: true,
887
896
  option: true,
888
897
  progress: true,
889
898
  section: true,
890
899
  select: true,
900
+ source: true,
901
+ summary: true,
902
+ table: true,
891
903
  tbody: true,
904
+ td: true,
892
905
  textarea: true,
893
906
  tfoot: true,
894
907
  th: true,
895
908
  thead: true,
909
+ tr: true,
910
+ track: true,
896
911
  ul: true
897
912
  };
898
913
  var AccessibilityAttributes = {
@@ -2062,6 +2077,475 @@ var CustomNotifyRule = class extends ValidationRule {
2062
2077
  });
2063
2078
  }
2064
2079
  };
2080
+
2081
+ // src/rules/requiredparent.ts
2082
+ var RequiredParentRule = class extends ValidationRule {
2083
+ constructor() {
2084
+ super(...arguments);
2085
+ __publicField(this, "type", 1 /* Error */);
2086
+ __publicField(this, "name", "aria-required-parent");
2087
+ __publicField(this, "anchored", true);
2088
+ __publicField(this, "parentRequirements", /* @__PURE__ */ new Map([
2089
+ [
2090
+ "LI",
2091
+ {
2092
+ allowedParents: ["UL", "OL"],
2093
+ allowedParentRoles: ["list"]
2094
+ }
2095
+ ],
2096
+ [
2097
+ "DT",
2098
+ {
2099
+ allowedParents: ["DL"],
2100
+ allowIntermediateWrappers: true,
2101
+ allowedWrappers: ["DIV"]
2102
+ }
2103
+ ],
2104
+ [
2105
+ "DD",
2106
+ {
2107
+ allowedParents: ["DL"],
2108
+ allowIntermediateWrappers: true,
2109
+ allowedWrappers: ["DIV"]
2110
+ }
2111
+ ],
2112
+ [
2113
+ "TR",
2114
+ {
2115
+ allowedParents: ["TABLE", "THEAD", "TBODY", "TFOOT"],
2116
+ allowedParentRoles: ["table", "grid", "treegrid"]
2117
+ }
2118
+ ],
2119
+ [
2120
+ "TH",
2121
+ {
2122
+ allowedParents: ["TR"],
2123
+ allowedParentRoles: ["row"]
2124
+ }
2125
+ ],
2126
+ [
2127
+ "TD",
2128
+ {
2129
+ allowedParents: ["TR"],
2130
+ allowedParentRoles: ["row"]
2131
+ }
2132
+ ],
2133
+ [
2134
+ "THEAD",
2135
+ {
2136
+ allowedParents: ["TABLE"],
2137
+ allowedParentRoles: ["table", "grid", "treegrid"]
2138
+ }
2139
+ ],
2140
+ [
2141
+ "TBODY",
2142
+ {
2143
+ allowedParents: ["TABLE"],
2144
+ allowedParentRoles: ["table", "grid", "treegrid"]
2145
+ }
2146
+ ],
2147
+ [
2148
+ "TFOOT",
2149
+ {
2150
+ allowedParents: ["TABLE"],
2151
+ allowedParentRoles: ["table", "grid", "treegrid"]
2152
+ }
2153
+ ],
2154
+ [
2155
+ "CAPTION",
2156
+ {
2157
+ allowedParents: ["TABLE"]
2158
+ }
2159
+ ],
2160
+ [
2161
+ "COLGROUP",
2162
+ {
2163
+ allowedParents: ["TABLE"]
2164
+ }
2165
+ ],
2166
+ [
2167
+ "COL",
2168
+ {
2169
+ allowedParents: ["COLGROUP"]
2170
+ }
2171
+ ],
2172
+ [
2173
+ "FIGCAPTION",
2174
+ {
2175
+ allowedParents: ["FIGURE"]
2176
+ }
2177
+ ],
2178
+ [
2179
+ "OPTION",
2180
+ {
2181
+ allowedParents: ["SELECT", "OPTGROUP", "DATALIST"]
2182
+ }
2183
+ ],
2184
+ [
2185
+ "OPTGROUP",
2186
+ {
2187
+ allowedParents: ["SELECT"]
2188
+ }
2189
+ ],
2190
+ [
2191
+ "LEGEND",
2192
+ {
2193
+ allowedParents: ["FIELDSET"]
2194
+ }
2195
+ ],
2196
+ [
2197
+ "SUMMARY",
2198
+ {
2199
+ allowedParents: ["DETAILS"]
2200
+ }
2201
+ ],
2202
+ [
2203
+ "SOURCE",
2204
+ {
2205
+ allowedParents: ["AUDIO", "VIDEO", "PICTURE"]
2206
+ }
2207
+ ],
2208
+ [
2209
+ "TRACK",
2210
+ {
2211
+ allowedParents: ["AUDIO", "VIDEO"]
2212
+ }
2213
+ ],
2214
+ [
2215
+ "role=menuitem",
2216
+ {
2217
+ allowedParentRoles: ["menu", "menubar", "group"]
2218
+ }
2219
+ ],
2220
+ [
2221
+ "role=menuitemcheckbox",
2222
+ {
2223
+ allowedParentRoles: ["menu", "menubar", "group"]
2224
+ }
2225
+ ],
2226
+ [
2227
+ "role=menuitemradio",
2228
+ {
2229
+ allowedParentRoles: ["menu", "menubar", "group"]
2230
+ }
2231
+ ],
2232
+ [
2233
+ "role=listitem",
2234
+ {
2235
+ allowedParentRoles: ["list", "group"]
2236
+ }
2237
+ ],
2238
+ [
2239
+ "role=treeitem",
2240
+ {
2241
+ allowedParentRoles: ["tree", "group"]
2242
+ }
2243
+ ],
2244
+ [
2245
+ "role=tab",
2246
+ {
2247
+ allowedParentRoles: ["tablist"]
2248
+ }
2249
+ ],
2250
+ [
2251
+ "role=row",
2252
+ {
2253
+ allowedParentRoles: ["table", "grid", "treegrid", "rowgroup"]
2254
+ }
2255
+ ],
2256
+ [
2257
+ "role=cell",
2258
+ {
2259
+ allowedParentRoles: ["row"]
2260
+ }
2261
+ ],
2262
+ [
2263
+ "role=gridcell",
2264
+ {
2265
+ allowedParentRoles: ["row"]
2266
+ }
2267
+ ],
2268
+ [
2269
+ "role=columnheader",
2270
+ {
2271
+ allowedParentRoles: ["row"]
2272
+ }
2273
+ ],
2274
+ [
2275
+ "role=rowheader",
2276
+ {
2277
+ allowedParentRoles: ["row"]
2278
+ }
2279
+ ],
2280
+ [
2281
+ "role=rowgroup",
2282
+ {
2283
+ allowedParentRoles: ["table", "grid", "treegrid"]
2284
+ }
2285
+ ],
2286
+ [
2287
+ "role=option",
2288
+ {
2289
+ allowedParentRoles: ["listbox", "group"]
2290
+ }
2291
+ ]
2292
+ ]));
2293
+ }
2294
+ accept(element) {
2295
+ const tagName = element.tagName;
2296
+ const role = element.getAttribute("role");
2297
+ if (this.parentRequirements.has(tagName)) {
2298
+ return true;
2299
+ }
2300
+ if (role && this.parentRequirements.has(`role=${role}`)) {
2301
+ return true;
2302
+ }
2303
+ return false;
2304
+ }
2305
+ validate(element) {
2306
+ const tagName = element.tagName;
2307
+ const role = element.getAttribute("role");
2308
+ let requirement;
2309
+ let identifier = "";
2310
+ if (role && this.parentRequirements.has(`role=${role}`)) {
2311
+ requirement = this.parentRequirements.get(`role=${role}`);
2312
+ identifier = `role="${role}"`;
2313
+ } else if (this.parentRequirements.has(tagName)) {
2314
+ requirement = this.parentRequirements.get(tagName);
2315
+ identifier = `<${tagName.toLowerCase()}>`;
2316
+ }
2317
+ if (!requirement) {
2318
+ return null;
2319
+ }
2320
+ if (requirement.customValidator) {
2321
+ if (requirement.customValidator(element)) {
2322
+ return null;
2323
+ } else {
2324
+ return this.createIssue(element, identifier, requirement);
2325
+ }
2326
+ }
2327
+ if (this.hasValidParent(element, requirement)) {
2328
+ return null;
2329
+ }
2330
+ return this.createIssue(element, identifier, requirement);
2331
+ }
2332
+ hasValidParent(element, requirement) {
2333
+ var _a, _b, _c;
2334
+ let parent = element.parentElement;
2335
+ let depth = 0;
2336
+ const maxDepth = requirement.allowIntermediateWrappers ? 2 : 1;
2337
+ while (parent && depth < maxDepth) {
2338
+ if ((_a = requirement.allowedParents) == null ? void 0 : _a.includes(parent.tagName)) {
2339
+ return true;
2340
+ }
2341
+ const parentRole = parent.getAttribute("role");
2342
+ if (parentRole && ((_b = requirement.allowedParentRoles) == null ? void 0 : _b.includes(parentRole))) {
2343
+ return true;
2344
+ }
2345
+ if (depth === 0 && !requirement.allowIntermediateWrappers) {
2346
+ break;
2347
+ }
2348
+ if (depth === 0 && requirement.allowIntermediateWrappers) {
2349
+ if (!((_c = requirement.allowedWrappers) == null ? void 0 : _c.includes(parent.tagName))) {
2350
+ break;
2351
+ }
2352
+ }
2353
+ parent = parent.parentElement;
2354
+ depth++;
2355
+ }
2356
+ return false;
2357
+ }
2358
+ createIssue(element, identifier, requirement) {
2359
+ var _a, _b;
2360
+ const allowedParentsText = [
2361
+ ...((_a = requirement.allowedParents) == null ? void 0 : _a.map((p) => `<${p.toLowerCase()}>`)) || [],
2362
+ ...((_b = requirement.allowedParentRoles) == null ? void 0 : _b.map((r) => `role="${r}"`)) || []
2363
+ ].join(", ");
2364
+ const message = `${identifier} must be contained by ${allowedParentsText}`;
2365
+ return {
2366
+ issue: {
2367
+ id: "aria-required-parent",
2368
+ message,
2369
+ element,
2370
+ help: "https://dequeuniversity.com/rules/axe/4.2/aria-required-parent"
2371
+ }
2372
+ };
2373
+ }
2374
+ };
2375
+
2376
+ // src/rules/nestedInteractive.ts
2377
+ var interactiveElementSelector = [
2378
+ "a[href]",
2379
+ "button",
2380
+ "input:not([type='hidden'])",
2381
+ "select",
2382
+ "textarea",
2383
+ "details",
2384
+ "audio[controls]",
2385
+ "video[controls]",
2386
+ "*[role='button']",
2387
+ "*[role='link']",
2388
+ "*[role='checkbox']",
2389
+ "*[role='radio']",
2390
+ "*[role='switch']",
2391
+ "*[role='tab']",
2392
+ "*[role='menuitem']",
2393
+ "*[role='menuitemcheckbox']",
2394
+ "*[role='menuitemradio']",
2395
+ "*[role='option']",
2396
+ "*[role='treeitem']"
2397
+ ].join(", ");
2398
+ var NestedInteractiveElementRule = class extends ValidationRule {
2399
+ constructor() {
2400
+ super(...arguments);
2401
+ __publicField(this, "type", 1 /* Error */);
2402
+ __publicField(this, "name", "NestedInteractiveElementRule");
2403
+ __publicField(this, "anchored", true);
2404
+ }
2405
+ _isAriaHidden(element) {
2406
+ return element.ownerDocument.evaluate(
2407
+ `ancestor-or-self::*[@aria-hidden = 'true' or @hidden]`,
2408
+ element,
2409
+ null,
2410
+ XPathResult.BOOLEAN_TYPE,
2411
+ null
2412
+ ).booleanValue;
2413
+ }
2414
+ _isInteractive(element) {
2415
+ return matchesSelector(element, interactiveElementSelector);
2416
+ }
2417
+ _findNestedInteractive(element) {
2418
+ const descendants = element.querySelectorAll(interactiveElementSelector);
2419
+ for (let i = 0; i < descendants.length; i++) {
2420
+ const descendant = descendants[i];
2421
+ if (this._isAriaHidden(descendant)) {
2422
+ continue;
2423
+ }
2424
+ return descendant;
2425
+ }
2426
+ return null;
2427
+ }
2428
+ accept(element) {
2429
+ return this._isInteractive(element);
2430
+ }
2431
+ validate(element) {
2432
+ if (this._isAriaHidden(element)) {
2433
+ return null;
2434
+ }
2435
+ const nestedElement = this._findNestedInteractive(element);
2436
+ if (nestedElement) {
2437
+ const elementTag = element.tagName.toLowerCase();
2438
+ const elementRole = element.getAttribute("role");
2439
+ const nestedTag = nestedElement.tagName.toLowerCase();
2440
+ const nestedRole = nestedElement.getAttribute("role");
2441
+ const elementDesc = elementRole ? `${elementTag}[role="${elementRole}"]` : elementTag;
2442
+ const nestedDesc = nestedRole ? `${nestedTag}[role="${nestedRole}"]` : nestedTag;
2443
+ return {
2444
+ issue: isElementVisible(element) ? {
2445
+ id: "nested-interactive",
2446
+ message: `Interactive element <${elementDesc}> contains a nested interactive element <${nestedDesc}>. This can confuse users and assistive technologies.`,
2447
+ element,
2448
+ rel: nestedElement,
2449
+ help: "https://dequeuniversity.com/rules/axe/4.4/nested-interactive"
2450
+ } : void 0
2451
+ };
2452
+ }
2453
+ return null;
2454
+ }
2455
+ };
2456
+
2457
+ // src/rules/tabindex.ts
2458
+ var INTERACTIVE_ELEMENTS = /* @__PURE__ */ new Set([
2459
+ "A",
2460
+ "BUTTON",
2461
+ "INPUT",
2462
+ "SELECT",
2463
+ "TEXTAREA",
2464
+ "DETAILS",
2465
+ "SUMMARY",
2466
+ "AUDIO",
2467
+ "VIDEO"
2468
+ ]);
2469
+ var INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
2470
+ "button",
2471
+ "link",
2472
+ "checkbox",
2473
+ "radio",
2474
+ "textbox",
2475
+ "combobox",
2476
+ "listbox",
2477
+ "menu",
2478
+ "menubar",
2479
+ "menuitem",
2480
+ "menuitemcheckbox",
2481
+ "menuitemradio",
2482
+ "option",
2483
+ "searchbox",
2484
+ "slider",
2485
+ "spinbutton",
2486
+ "switch",
2487
+ "tab",
2488
+ "tablist",
2489
+ "tree",
2490
+ "treegrid",
2491
+ "treeitem",
2492
+ "grid",
2493
+ "gridcell"
2494
+ ]);
2495
+ var TabIndexRule = class extends ValidationRule {
2496
+ constructor() {
2497
+ super(...arguments);
2498
+ __publicField(this, "type", 2 /* Warning */);
2499
+ __publicField(this, "name", "tabindex");
2500
+ __publicField(this, "anchored", true);
2501
+ }
2502
+ accept(element) {
2503
+ return element.hasAttribute("tabindex");
2504
+ }
2505
+ isInteractiveElement(element) {
2506
+ if (INTERACTIVE_ELEMENTS.has(element.tagName)) {
2507
+ if (element.hasAttribute("disabled")) {
2508
+ return false;
2509
+ }
2510
+ if (element.tagName === "A" && !element.hasAttribute("href")) {
2511
+ return false;
2512
+ }
2513
+ return true;
2514
+ }
2515
+ const role = element.getAttribute("role");
2516
+ if (role && INTERACTIVE_ROLES.has(role)) {
2517
+ return true;
2518
+ }
2519
+ if (element.isContentEditable) {
2520
+ return true;
2521
+ }
2522
+ return false;
2523
+ }
2524
+ validate(element) {
2525
+ const tabindex = parseInt(element.getAttribute("tabindex") || "0", 10);
2526
+ if (tabindex > 0 && this.isInteractiveElement(element)) {
2527
+ return {
2528
+ issue: {
2529
+ id: "tabindex",
2530
+ message: `Avoid positive tabindex values (found: ${tabindex})`,
2531
+ element,
2532
+ help: "https://dequeuniversity.com/rules/axe/4.2/tabindex"
2533
+ }
2534
+ };
2535
+ }
2536
+ if (!this.isInteractiveElement(element)) {
2537
+ return {
2538
+ issue: {
2539
+ id: "tabindex-non-interactive",
2540
+ message: `Avoid using tabindex on non-interactive elements (<${element.tagName.toLowerCase()}>). Consider adding an interactive role or making the element naturally interactive.`,
2541
+ element,
2542
+ help: "https://dequeuniversity.com/rules/axe/4.2/tabindex"
2543
+ }
2544
+ };
2545
+ }
2546
+ return null;
2547
+ }
2548
+ };
2065
2549
  export {
2066
2550
  AbleDOM,
2067
2551
  AtomicRule,
@@ -2072,6 +2556,9 @@ export {
2072
2556
  FindElementRule,
2073
2557
  FocusLostRule,
2074
2558
  FocusableElementLabelRule,
2559
+ NestedInteractiveElementRule,
2560
+ RequiredParentRule,
2561
+ TabIndexRule,
2075
2562
  ValidationRule,
2076
2563
  ValidationRuleType,
2077
2564
  hasAccessibilityAttribute,