@accesslint/core 0.3.12 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +5 -5
- package/dist/index.iife.js +5 -5
- package/dist/index.js +1235 -1185
- package/dist/rules/aria/aria-children-parent.d.ts.map +1 -1
- package/dist/rules/color/color-contrast.d.ts.map +1 -1
- package/dist/rules/engine.d.ts.map +1 -1
- package/dist/rules/structure/meta-rules.d.ts.map +1 -1
- package/dist/rules/utils/aria.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
let
|
|
2
|
-
function
|
|
3
|
-
|
|
1
|
+
let W = /* @__PURE__ */ new WeakMap();
|
|
2
|
+
function Se() {
|
|
3
|
+
W = /* @__PURE__ */ new WeakMap();
|
|
4
4
|
}
|
|
5
|
-
function
|
|
5
|
+
function he(e) {
|
|
6
6
|
var i;
|
|
7
|
-
const a =
|
|
7
|
+
const a = e.tagName.toLowerCase(), t = (i = e.getAttribute("type")) == null ? void 0 : i.toLowerCase();
|
|
8
8
|
switch (a) {
|
|
9
9
|
case "a":
|
|
10
|
-
return
|
|
10
|
+
return e.hasAttribute("href") ? "link" : null;
|
|
11
11
|
case "area":
|
|
12
|
-
return
|
|
12
|
+
return e.hasAttribute("href") ? "link" : null;
|
|
13
13
|
case "article":
|
|
14
14
|
return "article";
|
|
15
15
|
case "aside":
|
|
@@ -27,7 +27,7 @@ function de(t) {
|
|
|
27
27
|
case "figure":
|
|
28
28
|
return "figure";
|
|
29
29
|
case "footer":
|
|
30
|
-
return
|
|
30
|
+
return e.closest("article, aside, main, nav, section") ? null : "contentinfo";
|
|
31
31
|
case "form":
|
|
32
32
|
return "form";
|
|
33
33
|
case "h1":
|
|
@@ -38,13 +38,13 @@ function de(t) {
|
|
|
38
38
|
case "h6":
|
|
39
39
|
return "heading";
|
|
40
40
|
case "header":
|
|
41
|
-
return
|
|
41
|
+
return e.closest("article, aside, main, nav, section") ? null : "banner";
|
|
42
42
|
case "hr":
|
|
43
43
|
return "separator";
|
|
44
44
|
case "img":
|
|
45
|
-
return
|
|
45
|
+
return e.getAttribute("alt") === "" ? "presentation" : "img";
|
|
46
46
|
case "input":
|
|
47
|
-
switch (
|
|
47
|
+
switch (t) {
|
|
48
48
|
case "button":
|
|
49
49
|
case "image":
|
|
50
50
|
case "reset":
|
|
@@ -71,7 +71,7 @@ function de(t) {
|
|
|
71
71
|
return "textbox";
|
|
72
72
|
}
|
|
73
73
|
case "li":
|
|
74
|
-
return
|
|
74
|
+
return e.closest("ul, ol, menu") ? "listitem" : null;
|
|
75
75
|
case "main":
|
|
76
76
|
return "main";
|
|
77
77
|
case "math":
|
|
@@ -94,9 +94,9 @@ function de(t) {
|
|
|
94
94
|
case "progress":
|
|
95
95
|
return "progressbar";
|
|
96
96
|
case "section":
|
|
97
|
-
return
|
|
97
|
+
return e.hasAttribute("aria-label") || e.hasAttribute("aria-labelledby") ? "region" : null;
|
|
98
98
|
case "select":
|
|
99
|
-
return
|
|
99
|
+
return e.hasAttribute("multiple") || e.size > 1 ? "listbox" : "combobox";
|
|
100
100
|
case "summary":
|
|
101
101
|
return "button";
|
|
102
102
|
case "table":
|
|
@@ -117,71 +117,71 @@ function de(t) {
|
|
|
117
117
|
return null;
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
-
function q(
|
|
120
|
+
function q(e) {
|
|
121
121
|
var n;
|
|
122
|
-
const a =
|
|
122
|
+
const a = W.get(e);
|
|
123
123
|
if (a !== void 0) return a;
|
|
124
|
-
const i = ((n =
|
|
125
|
-
return
|
|
124
|
+
const i = ((n = e.getAttribute("role")) == null ? void 0 : n.trim().toLowerCase()) || null || he(e);
|
|
125
|
+
return W.set(e, i), i;
|
|
126
126
|
}
|
|
127
|
-
let
|
|
128
|
-
function
|
|
129
|
-
|
|
127
|
+
let O = /* @__PURE__ */ new WeakMap();
|
|
128
|
+
function ke() {
|
|
129
|
+
O = /* @__PURE__ */ new WeakMap();
|
|
130
130
|
}
|
|
131
|
-
function v(
|
|
132
|
-
const a =
|
|
131
|
+
function v(e) {
|
|
132
|
+
const a = O.get(e);
|
|
133
133
|
if (a !== void 0) return a;
|
|
134
|
-
const
|
|
135
|
-
return
|
|
134
|
+
const t = Ie(e);
|
|
135
|
+
return O.set(e, t), t;
|
|
136
136
|
}
|
|
137
|
-
function
|
|
138
|
-
var r, o, s, l,
|
|
139
|
-
const a =
|
|
137
|
+
function Ie(e) {
|
|
138
|
+
var r, o, s, l, p;
|
|
139
|
+
const a = e.getAttribute("aria-labelledby");
|
|
140
140
|
if (a) {
|
|
141
141
|
const c = a.split(/\s+/).map((u) => {
|
|
142
|
-
const
|
|
143
|
-
return
|
|
142
|
+
const h = e.ownerDocument.getElementById(u);
|
|
143
|
+
return h ? A(h).trim() : "";
|
|
144
144
|
}).filter(Boolean);
|
|
145
145
|
if (c.length) return c.join(" ");
|
|
146
146
|
}
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
149
|
-
if (
|
|
150
|
-
if (
|
|
151
|
-
const
|
|
147
|
+
const t = (r = e.getAttribute("aria-label")) == null ? void 0 : r.trim();
|
|
148
|
+
if (t) return t;
|
|
149
|
+
if (e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement || e instanceof HTMLSelectElement) {
|
|
150
|
+
if (e.id) {
|
|
151
|
+
const h = e.ownerDocument.querySelector(`label[for="${CSS.escape(e.id)}"]`), b = h ? A(h).trim() : "";
|
|
152
152
|
if (b) return b;
|
|
153
153
|
}
|
|
154
|
-
const c =
|
|
154
|
+
const c = e.closest("label"), u = c ? A(c).trim() : "";
|
|
155
155
|
if (u) return u;
|
|
156
156
|
}
|
|
157
|
-
const i = (o =
|
|
157
|
+
const i = (o = e.getAttribute("title")) == null ? void 0 : o.trim();
|
|
158
158
|
if (i) return i;
|
|
159
|
-
if (
|
|
160
|
-
const c = (s =
|
|
159
|
+
if (e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement) {
|
|
160
|
+
const c = (s = e.getAttribute("placeholder")) == null ? void 0 : s.trim();
|
|
161
161
|
if (c) return c;
|
|
162
162
|
}
|
|
163
|
-
const n =
|
|
163
|
+
const n = e.tagName.toLowerCase();
|
|
164
164
|
if (n === "fieldset") {
|
|
165
|
-
const c =
|
|
165
|
+
const c = e.querySelector(":scope > legend");
|
|
166
166
|
if (c) {
|
|
167
167
|
const u = A(c).trim();
|
|
168
168
|
if (u) return u;
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
if (n === "table") {
|
|
172
|
-
const c =
|
|
172
|
+
const c = e.querySelector(":scope > caption");
|
|
173
173
|
if (c) {
|
|
174
174
|
const u = A(c).trim();
|
|
175
175
|
if (u) return u;
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
|
-
if (!(
|
|
179
|
-
const c = A(
|
|
178
|
+
if (!(e instanceof HTMLInputElement)) {
|
|
179
|
+
const c = A(e).trim();
|
|
180
180
|
if (c) return c;
|
|
181
181
|
}
|
|
182
|
-
return
|
|
182
|
+
return e instanceof HTMLImageElement || e instanceof HTMLAreaElement ? ((l = e.alt) == null ? void 0 : l.trim()) ?? "" : e instanceof HTMLInputElement && e.type === "image" ? ((p = e.alt) == null ? void 0 : p.trim()) ?? "" : "";
|
|
183
183
|
}
|
|
184
|
-
const
|
|
184
|
+
const Te = /* @__PURE__ */ new Set([
|
|
185
185
|
"alert",
|
|
186
186
|
"alertdialog",
|
|
187
187
|
"application",
|
|
@@ -265,51 +265,53 @@ const Se = /* @__PURE__ */ new Set([
|
|
|
265
265
|
"treegrid",
|
|
266
266
|
"treeitem"
|
|
267
267
|
]);
|
|
268
|
-
function
|
|
269
|
-
const a =
|
|
270
|
-
return
|
|
268
|
+
function Ee(e) {
|
|
269
|
+
const a = e.trim().toLowerCase().replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g, "");
|
|
270
|
+
return Te.has(a);
|
|
271
271
|
}
|
|
272
|
-
function T(
|
|
273
|
-
let a =
|
|
272
|
+
function T(e) {
|
|
273
|
+
let a = e;
|
|
274
274
|
for (; a; ) {
|
|
275
|
-
if (
|
|
276
|
-
if (typeof getComputedStyle == "function") {
|
|
277
|
-
const e = getComputedStyle(a);
|
|
278
|
-
if (e.display === "none" || e.visibility === "hidden") return !0;
|
|
279
|
-
} else if (a instanceof HTMLElement && a.style.display === "none") return !0;
|
|
275
|
+
if (pe(a)) return !0;
|
|
280
276
|
a = a.parentElement;
|
|
281
277
|
}
|
|
282
278
|
return !1;
|
|
283
279
|
}
|
|
284
|
-
let
|
|
285
|
-
function
|
|
286
|
-
|
|
280
|
+
let B = /* @__PURE__ */ new WeakMap();
|
|
281
|
+
function Ce() {
|
|
282
|
+
B = /* @__PURE__ */ new WeakMap();
|
|
287
283
|
}
|
|
288
|
-
function g(
|
|
289
|
-
const a =
|
|
284
|
+
function g(e) {
|
|
285
|
+
const a = B.get(e);
|
|
290
286
|
if (a !== void 0) return a;
|
|
291
|
-
let
|
|
292
|
-
return
|
|
287
|
+
let t;
|
|
288
|
+
return e.getAttribute("aria-hidden") === "true" || e instanceof HTMLElement && (e.hidden || e.style.display === "none") ? t = !0 : e.parentElement ? t = g(e.parentElement) : t = !1, B.set(e, t), t;
|
|
293
289
|
}
|
|
294
|
-
function
|
|
295
|
-
|
|
290
|
+
function pe(e) {
|
|
291
|
+
if (e.getAttribute("aria-hidden") === "true" || e instanceof HTMLElement && e.hidden) return !0;
|
|
292
|
+
if (typeof getComputedStyle == "function") {
|
|
293
|
+
const a = getComputedStyle(e);
|
|
294
|
+
if (a.display === "none" || a.visibility === "hidden") return !0;
|
|
295
|
+
} else if (e instanceof HTMLElement && e.style.display === "none")
|
|
296
|
+
return !0;
|
|
297
|
+
return !1;
|
|
296
298
|
}
|
|
297
|
-
function A(
|
|
298
|
-
var
|
|
299
|
+
function A(e) {
|
|
300
|
+
var t, i, n, r, o;
|
|
299
301
|
let a = "";
|
|
300
|
-
for (const s of
|
|
302
|
+
for (const s of e.childNodes)
|
|
301
303
|
if (s.nodeType === 3)
|
|
302
304
|
a += s.textContent ?? "";
|
|
303
305
|
else if (s.nodeType === 1) {
|
|
304
306
|
const l = s;
|
|
305
|
-
if (!
|
|
306
|
-
const
|
|
307
|
-
if (
|
|
307
|
+
if (!pe(l)) {
|
|
308
|
+
const p = (t = l.tagName) == null ? void 0 : t.toLowerCase();
|
|
309
|
+
if (p === "img" || p === "area") {
|
|
308
310
|
const c = l.getAttribute("aria-labelledby");
|
|
309
311
|
if (c) {
|
|
310
|
-
const u = c.split(/\s+/).map((
|
|
312
|
+
const u = c.split(/\s+/).map((h) => {
|
|
311
313
|
var b, f;
|
|
312
|
-
return ((f = (b = l.ownerDocument.getElementById(
|
|
314
|
+
return ((f = (b = l.ownerDocument.getElementById(h)) == null ? void 0 : b.textContent) == null ? void 0 : f.trim()) ?? "";
|
|
313
315
|
}).filter(Boolean);
|
|
314
316
|
if (u.length) {
|
|
315
317
|
a += u.join(" ");
|
|
@@ -317,7 +319,7 @@ function A(t) {
|
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
a += ((i = l.getAttribute("aria-label")) == null ? void 0 : i.trim()) ?? l.getAttribute("alt") ?? ((n = l.getAttribute("title")) == null ? void 0 : n.trim()) ?? "";
|
|
320
|
-
} else if (
|
|
322
|
+
} else if (p === "svg") {
|
|
321
323
|
const c = (r = l.getAttribute("aria-label")) == null ? void 0 : r.trim();
|
|
322
324
|
if (c)
|
|
323
325
|
a += c;
|
|
@@ -331,13 +333,13 @@ function A(t) {
|
|
|
331
333
|
return a;
|
|
332
334
|
}
|
|
333
335
|
let _ = /* @__PURE__ */ new WeakMap();
|
|
334
|
-
function
|
|
336
|
+
function Le() {
|
|
335
337
|
_ = /* @__PURE__ */ new WeakMap();
|
|
336
338
|
}
|
|
337
|
-
function
|
|
338
|
-
return
|
|
339
|
+
function qe(e) {
|
|
340
|
+
return e.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
339
341
|
}
|
|
340
|
-
const
|
|
342
|
+
const Re = [
|
|
341
343
|
"data-testid",
|
|
342
344
|
"data-test-id",
|
|
343
345
|
"data-cy",
|
|
@@ -347,37 +349,37 @@ const Le = [
|
|
|
347
349
|
"for",
|
|
348
350
|
"aria-label"
|
|
349
351
|
];
|
|
350
|
-
function
|
|
351
|
-
const a =
|
|
352
|
-
for (const i of
|
|
353
|
-
const n =
|
|
352
|
+
function Ne(e) {
|
|
353
|
+
const a = e.tagName.toLowerCase();
|
|
354
|
+
for (const i of Re) {
|
|
355
|
+
const n = e.getAttribute(i);
|
|
354
356
|
if (n != null && n.length > 0 && n.length < 100)
|
|
355
|
-
return `${a}[${i}="${
|
|
357
|
+
return `${a}[${i}="${qe(n)}"]`;
|
|
356
358
|
}
|
|
357
|
-
const
|
|
358
|
-
if (
|
|
359
|
+
const t = e.parentElement;
|
|
360
|
+
if (t) {
|
|
359
361
|
let i = 0, n = 0;
|
|
360
|
-
for (let r = 0; r <
|
|
361
|
-
|
|
362
|
+
for (let r = 0; r < t.children.length; r++)
|
|
363
|
+
t.children[r].tagName === e.tagName && (i++, t.children[r] === e && (n = i));
|
|
362
364
|
if (i > 1)
|
|
363
365
|
return `${a}:nth-of-type(${n})`;
|
|
364
366
|
}
|
|
365
367
|
return a;
|
|
366
368
|
}
|
|
367
|
-
function H(
|
|
368
|
-
if (
|
|
369
|
-
const a =
|
|
370
|
-
let n =
|
|
371
|
-
for (; n && n !==
|
|
372
|
-
if (n !==
|
|
369
|
+
function H(e) {
|
|
370
|
+
if (e.id) return `#${CSS.escape(e.id)}`;
|
|
371
|
+
const a = e.getRootNode(), t = a instanceof ShadowRoot ? null : a.documentElement, i = [];
|
|
372
|
+
let n = e;
|
|
373
|
+
for (; n && n !== t; ) {
|
|
374
|
+
if (n !== e && n.id) {
|
|
373
375
|
i.unshift(`#${CSS.escape(n.id)}`);
|
|
374
376
|
break;
|
|
375
377
|
}
|
|
376
|
-
if (i.unshift(
|
|
378
|
+
if (i.unshift(Ne(n)), i.length >= 2) {
|
|
377
379
|
const r = i.join(" > ");
|
|
378
380
|
try {
|
|
379
381
|
const o = a.querySelectorAll(r);
|
|
380
|
-
if (o.length === 1 && o[0] ===
|
|
382
|
+
if (o.length === 1 && o[0] === e) return r;
|
|
381
383
|
} catch {
|
|
382
384
|
}
|
|
383
385
|
}
|
|
@@ -385,38 +387,38 @@ function H(t) {
|
|
|
385
387
|
}
|
|
386
388
|
return i.join(" > ");
|
|
387
389
|
}
|
|
388
|
-
function m(
|
|
390
|
+
function m(e) {
|
|
389
391
|
var r;
|
|
390
|
-
const a = _.get(
|
|
392
|
+
const a = _.get(e);
|
|
391
393
|
if (a !== void 0) return a;
|
|
392
|
-
const
|
|
393
|
-
let i =
|
|
394
|
+
const t = [];
|
|
395
|
+
let i = e;
|
|
394
396
|
for (; i; ) {
|
|
395
397
|
const o = i.getRootNode();
|
|
396
398
|
if (o instanceof ShadowRoot)
|
|
397
|
-
|
|
399
|
+
t.unshift({ selector: H(i), delimiter: " >>> " }), i = o.host;
|
|
398
400
|
else {
|
|
399
401
|
const s = (r = o.defaultView) == null ? void 0 : r.frameElement;
|
|
400
402
|
if (s)
|
|
401
|
-
|
|
403
|
+
t.unshift({ selector: H(i), delimiter: " >>>iframe> " }), i = s;
|
|
402
404
|
else {
|
|
403
|
-
|
|
405
|
+
t.unshift({ selector: H(i), delimiter: "" });
|
|
404
406
|
break;
|
|
405
407
|
}
|
|
406
408
|
}
|
|
407
409
|
}
|
|
408
|
-
const n =
|
|
409
|
-
return _.set(
|
|
410
|
+
const n = t.map((o, s) => (s === 0 ? "" : o.delimiter) + o.selector).join("");
|
|
411
|
+
return _.set(e, n), n;
|
|
410
412
|
}
|
|
411
|
-
function
|
|
412
|
-
const a = [],
|
|
413
|
-
let i =
|
|
413
|
+
function Ri(e) {
|
|
414
|
+
const a = [], t = [];
|
|
415
|
+
let i = e;
|
|
414
416
|
for (; i; ) {
|
|
415
417
|
const r = i.indexOf(" >>>iframe> "), o = i.indexOf(" >>> ");
|
|
416
418
|
if (r !== -1 && (o === -1 || r <= o))
|
|
417
|
-
a.push(i.slice(0, r).trim()),
|
|
419
|
+
a.push(i.slice(0, r).trim()), t.push("iframe"), i = i.slice(r + 12);
|
|
418
420
|
else if (o !== -1)
|
|
419
|
-
a.push(i.slice(0, o).trim()),
|
|
421
|
+
a.push(i.slice(0, o).trim()), t.push("shadow"), i = i.slice(o + 5);
|
|
420
422
|
else {
|
|
421
423
|
a.push(i.trim());
|
|
422
424
|
break;
|
|
@@ -427,7 +429,7 @@ function Ei(t) {
|
|
|
427
429
|
const o = n.querySelector(a[r]);
|
|
428
430
|
if (!o) return null;
|
|
429
431
|
if (r < a.length - 1)
|
|
430
|
-
if (
|
|
432
|
+
if (t[r] === "iframe") {
|
|
431
433
|
const s = o.contentDocument;
|
|
432
434
|
if (!s) return null;
|
|
433
435
|
n = s;
|
|
@@ -441,11 +443,11 @@ function Ei(t) {
|
|
|
441
443
|
}
|
|
442
444
|
return null;
|
|
443
445
|
}
|
|
444
|
-
function d(
|
|
445
|
-
const a =
|
|
446
|
+
function d(e) {
|
|
447
|
+
const a = e.outerHTML;
|
|
446
448
|
return a.length > 200 ? a.slice(0, 200) + "..." : a;
|
|
447
449
|
}
|
|
448
|
-
const
|
|
450
|
+
const $e = /* @__PURE__ */ new Set([
|
|
449
451
|
"aria-activedescendant",
|
|
450
452
|
"aria-atomic",
|
|
451
453
|
"aria-autocomplete",
|
|
@@ -510,7 +512,7 @@ const Re = /* @__PURE__ */ new Set([
|
|
|
510
512
|
"aria-multiselectable",
|
|
511
513
|
"aria-readonly",
|
|
512
514
|
"aria-required"
|
|
513
|
-
]), X = /* @__PURE__ */ new Set(["aria-checked", "aria-pressed"]),
|
|
515
|
+
]), X = /* @__PURE__ */ new Set(["aria-checked", "aria-pressed"]), Me = /* @__PURE__ */ new Set([
|
|
514
516
|
"aria-colcount",
|
|
515
517
|
"aria-colindex",
|
|
516
518
|
"aria-colspan",
|
|
@@ -520,7 +522,7 @@ const Re = /* @__PURE__ */ new Set([
|
|
|
520
522
|
"aria-rowindex",
|
|
521
523
|
"aria-rowspan",
|
|
522
524
|
"aria-setsize"
|
|
523
|
-
]),
|
|
525
|
+
]), He = /* @__PURE__ */ new Set([
|
|
524
526
|
"aria-valuemax",
|
|
525
527
|
"aria-valuemin",
|
|
526
528
|
"aria-valuenow"
|
|
@@ -552,7 +554,7 @@ const Re = /* @__PURE__ */ new Set([
|
|
|
552
554
|
"suggestion",
|
|
553
555
|
"term",
|
|
554
556
|
"time"
|
|
555
|
-
]),
|
|
557
|
+
]), De = {
|
|
556
558
|
abbr: !0,
|
|
557
559
|
bdi: !0,
|
|
558
560
|
bdo: !0,
|
|
@@ -580,7 +582,7 @@ const Re = /* @__PURE__ */ new Set([
|
|
|
580
582
|
u: !0,
|
|
581
583
|
var: !0,
|
|
582
584
|
wbr: !0
|
|
583
|
-
},
|
|
585
|
+
}, Fe = {
|
|
584
586
|
alert: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
|
|
585
587
|
article: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
|
|
586
588
|
banner: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
|
|
@@ -614,14 +616,14 @@ const Re = /* @__PURE__ */ new Set([
|
|
|
614
616
|
tooltip: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"])
|
|
615
617
|
};
|
|
616
618
|
let C = null, L = null;
|
|
617
|
-
function
|
|
619
|
+
function We() {
|
|
618
620
|
C = null, L = null;
|
|
619
621
|
}
|
|
620
|
-
function U(
|
|
622
|
+
function U(e) {
|
|
621
623
|
var n;
|
|
622
|
-
if (L && (C == null ? void 0 : C.deref()) ===
|
|
623
|
-
const a = [],
|
|
624
|
-
for (const r of
|
|
624
|
+
if (L && (C == null ? void 0 : C.deref()) === e) return L;
|
|
625
|
+
const a = [], t = [], i = [];
|
|
626
|
+
for (const r of e.querySelectorAll("*")) {
|
|
625
627
|
let o = !1;
|
|
626
628
|
for (const c of r.attributes)
|
|
627
629
|
if (c.name.startsWith("aria-")) {
|
|
@@ -630,10 +632,10 @@ function U(t) {
|
|
|
630
632
|
}
|
|
631
633
|
if (!o) continue;
|
|
632
634
|
let s, l;
|
|
633
|
-
const
|
|
635
|
+
const p = () => (s === void 0 && (s = m(r), l = d(r)), { selector: s, html: l });
|
|
634
636
|
for (const c of r.attributes)
|
|
635
|
-
if (c.name.startsWith("aria-") &&
|
|
636
|
-
const u =
|
|
637
|
+
if (c.name.startsWith("aria-") && !$e.has(c.name)) {
|
|
638
|
+
const u = p();
|
|
637
639
|
a.push({
|
|
638
640
|
ruleId: "aria-valid-attr",
|
|
639
641
|
selector: u.selector,
|
|
@@ -649,54 +651,54 @@ function U(t) {
|
|
|
649
651
|
if (!(u === "" && !Y.has(c.name) && !X.has(c.name))) {
|
|
650
652
|
if (Y.has(c.name)) {
|
|
651
653
|
if (u !== "true" && u !== "false") {
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
+
const h = p();
|
|
655
|
+
t.push({
|
|
654
656
|
ruleId: "aria-valid-attr-value",
|
|
655
|
-
selector:
|
|
656
|
-
html:
|
|
657
|
+
selector: h.selector,
|
|
658
|
+
html: h.html,
|
|
657
659
|
impact: "critical",
|
|
658
660
|
message: `${c.name} must be "true" or "false", got "${u}".`
|
|
659
661
|
});
|
|
660
662
|
}
|
|
661
663
|
} else if (X.has(c.name)) {
|
|
662
664
|
if (u !== "true" && u !== "false" && u !== "mixed") {
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
+
const h = p();
|
|
666
|
+
t.push({
|
|
665
667
|
ruleId: "aria-valid-attr-value",
|
|
666
|
-
selector:
|
|
667
|
-
html:
|
|
668
|
+
selector: h.selector,
|
|
669
|
+
html: h.html,
|
|
668
670
|
impact: "critical",
|
|
669
671
|
message: `${c.name} must be "true", "false", or "mixed", got "${u}".`
|
|
670
672
|
});
|
|
671
673
|
}
|
|
672
|
-
} else if (
|
|
674
|
+
} else if (Me.has(c.name)) {
|
|
673
675
|
if (u === "" || !/^-?\d+$/.test(u)) {
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
+
const h = p();
|
|
677
|
+
t.push({
|
|
676
678
|
ruleId: "aria-valid-attr-value",
|
|
677
|
-
selector:
|
|
678
|
-
html:
|
|
679
|
+
selector: h.selector,
|
|
680
|
+
html: h.html,
|
|
679
681
|
impact: "critical",
|
|
680
682
|
message: `${c.name} must be an integer, got "${u}".`
|
|
681
683
|
});
|
|
682
684
|
}
|
|
683
|
-
} else if (
|
|
685
|
+
} else if (He.has(c.name)) {
|
|
684
686
|
if (u === "" || isNaN(Number(u))) {
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
+
const h = p();
|
|
688
|
+
t.push({
|
|
687
689
|
ruleId: "aria-valid-attr-value",
|
|
688
|
-
selector:
|
|
689
|
-
html:
|
|
690
|
+
selector: h.selector,
|
|
691
|
+
html: h.html,
|
|
690
692
|
impact: "critical",
|
|
691
693
|
message: `${c.name} must be a number, got "${u}".`
|
|
692
694
|
});
|
|
693
695
|
}
|
|
694
696
|
} else if (K[c.name]) {
|
|
695
|
-
const
|
|
696
|
-
for (const b of
|
|
697
|
+
const h = u.split(/\s+/);
|
|
698
|
+
for (const b of h)
|
|
697
699
|
if (!K[c.name].has(b)) {
|
|
698
|
-
const f =
|
|
699
|
-
|
|
700
|
+
const f = p();
|
|
701
|
+
t.push({
|
|
700
702
|
ruleId: "aria-valid-attr-value",
|
|
701
703
|
selector: f.selector,
|
|
702
704
|
html: f.html,
|
|
@@ -710,10 +712,10 @@ function U(t) {
|
|
|
710
712
|
}
|
|
711
713
|
if (!g(r)) {
|
|
712
714
|
const c = (n = r.getAttribute("role")) == null ? void 0 : n.trim().toLowerCase(), u = r.tagName.toLowerCase();
|
|
713
|
-
if (!c &&
|
|
714
|
-
const
|
|
715
|
-
if (
|
|
716
|
-
const f =
|
|
715
|
+
if (!c && De[u]) {
|
|
716
|
+
const h = r.hasAttribute("aria-label"), b = r.hasAttribute("aria-labelledby");
|
|
717
|
+
if (h || b) {
|
|
718
|
+
const f = p();
|
|
717
719
|
i.push({
|
|
718
720
|
ruleId: "aria-prohibited-attr",
|
|
719
721
|
selector: f.selector,
|
|
@@ -726,23 +728,23 @@ function U(t) {
|
|
|
726
728
|
if (J.has(c)) {
|
|
727
729
|
const b = r.hasAttribute("aria-label"), f = r.hasAttribute("aria-labelledby");
|
|
728
730
|
if (b || f) {
|
|
729
|
-
const
|
|
731
|
+
const w = p();
|
|
730
732
|
i.push({
|
|
731
733
|
ruleId: "aria-prohibited-attr",
|
|
732
|
-
selector:
|
|
733
|
-
html:
|
|
734
|
+
selector: w.selector,
|
|
735
|
+
html: w.html,
|
|
734
736
|
impact: "serious",
|
|
735
737
|
message: `aria-label and aria-labelledby are prohibited on role "${c}".`
|
|
736
738
|
});
|
|
737
739
|
}
|
|
738
740
|
}
|
|
739
|
-
const
|
|
740
|
-
if (
|
|
741
|
+
const h = Fe[c];
|
|
742
|
+
if (h) {
|
|
741
743
|
for (const b of r.attributes)
|
|
742
|
-
if (b.name.startsWith("aria-") &&
|
|
744
|
+
if (b.name.startsWith("aria-") && h.has(b.name)) {
|
|
743
745
|
if ((b.name === "aria-label" || b.name === "aria-labelledby") && J.has(c))
|
|
744
746
|
continue;
|
|
745
|
-
const f =
|
|
747
|
+
const f = p();
|
|
746
748
|
i.push({
|
|
747
749
|
ruleId: "aria-prohibited-attr",
|
|
748
750
|
selector: f.selector,
|
|
@@ -755,26 +757,26 @@ function U(t) {
|
|
|
755
757
|
}
|
|
756
758
|
}
|
|
757
759
|
}
|
|
758
|
-
return C = new WeakRef(
|
|
760
|
+
return C = new WeakRef(e), L = { validAttr: a, validAttrValue: t, prohibitedAttr: i }, L;
|
|
759
761
|
}
|
|
760
762
|
let P = /* @__PURE__ */ new WeakMap(), j = /* @__PURE__ */ new WeakMap(), V = /* @__PURE__ */ new WeakMap();
|
|
761
763
|
function Oe() {
|
|
762
764
|
P = /* @__PURE__ */ new WeakMap(), j = /* @__PURE__ */ new WeakMap(), V = /* @__PURE__ */ new WeakMap();
|
|
763
765
|
}
|
|
764
|
-
function
|
|
765
|
-
let a = P.get(
|
|
766
|
-
return a || (a = getComputedStyle(
|
|
766
|
+
function y(e) {
|
|
767
|
+
let a = P.get(e);
|
|
768
|
+
return a || (a = getComputedStyle(e), P.set(e, a), a);
|
|
767
769
|
}
|
|
768
|
-
function R(
|
|
769
|
-
const [i, n, r] = [
|
|
770
|
+
function R(e, a, t) {
|
|
771
|
+
const [i, n, r] = [e, a, t].map((o) => {
|
|
770
772
|
const s = o / 255;
|
|
771
773
|
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
772
774
|
});
|
|
773
775
|
return 0.2126 * i + 0.7152 * n + 0.0722 * r;
|
|
774
776
|
}
|
|
775
|
-
function
|
|
776
|
-
const
|
|
777
|
-
return (
|
|
777
|
+
function ge(e, a) {
|
|
778
|
+
const t = Math.max(e, a), i = Math.min(e, a);
|
|
779
|
+
return (t + 0.05) / (i + 0.05);
|
|
778
780
|
}
|
|
779
781
|
const Q = {
|
|
780
782
|
black: [0, 0, 0],
|
|
@@ -796,37 +798,37 @@ const Q = {
|
|
|
796
798
|
lime: [0, 255, 0],
|
|
797
799
|
olive: [128, 128, 0]
|
|
798
800
|
};
|
|
799
|
-
function N(
|
|
800
|
-
const a =
|
|
801
|
+
function N(e) {
|
|
802
|
+
const a = e.trim().toLowerCase();
|
|
801
803
|
if (Q[a]) return Q[a];
|
|
802
|
-
const
|
|
803
|
-
if (
|
|
804
|
-
return [parseInt(
|
|
804
|
+
const t = a.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);
|
|
805
|
+
if (t)
|
|
806
|
+
return [parseInt(t[1] + t[1], 16), parseInt(t[2] + t[2], 16), parseInt(t[3] + t[3], 16)];
|
|
805
807
|
const i = a.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);
|
|
806
808
|
if (i)
|
|
807
809
|
return [parseInt(i[1], 16), parseInt(i[2], 16), parseInt(i[3], 16)];
|
|
808
|
-
const n =
|
|
810
|
+
const n = e.match(
|
|
809
811
|
/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*[\d.]+)?\s*\)/
|
|
810
812
|
);
|
|
811
813
|
if (n)
|
|
812
814
|
return [parseInt(n[1]), parseInt(n[2]), parseInt(n[3])];
|
|
813
|
-
const r =
|
|
815
|
+
const r = e.match(
|
|
814
816
|
/rgba?\(\s*(\d+)\s+(\d+)\s+(\d+)\s*(?:\/\s*[\d.]+%?)?\s*\)/
|
|
815
817
|
);
|
|
816
818
|
return r ? [parseInt(r[1]), parseInt(r[2]), parseInt(r[3])] : null;
|
|
817
819
|
}
|
|
818
|
-
function Be(
|
|
819
|
-
const a = j.get(
|
|
820
|
+
function Be(e) {
|
|
821
|
+
const a = j.get(e);
|
|
820
822
|
if (a !== void 0) return a;
|
|
821
|
-
const
|
|
822
|
-
return j.set(
|
|
823
|
+
const t = _e(e);
|
|
824
|
+
return j.set(e, t), t;
|
|
823
825
|
}
|
|
824
|
-
function
|
|
825
|
-
let a =
|
|
826
|
+
function _e(e) {
|
|
827
|
+
let a = e;
|
|
826
828
|
for (; a; ) {
|
|
827
|
-
const
|
|
829
|
+
const t = y(a), i = t.backgroundImage;
|
|
828
830
|
if (i && i !== "none" && i !== "initial") return null;
|
|
829
|
-
const n =
|
|
831
|
+
const n = t.backgroundColor;
|
|
830
832
|
if (n === "transparent" || n === "rgba(0, 0, 0, 0)" || n === "rgba(0 0 0 / 0)") {
|
|
831
833
|
a = a.parentElement;
|
|
832
834
|
continue;
|
|
@@ -840,144 +842,144 @@ function Fe(t) {
|
|
|
840
842
|
}
|
|
841
843
|
return [255, 255, 255];
|
|
842
844
|
}
|
|
843
|
-
const
|
|
844
|
-
function
|
|
845
|
-
const a = V.get(
|
|
845
|
+
const Pe = /* @__PURE__ */ new Set(["IMG", "PICTURE", "VIDEO", "SVG"]);
|
|
846
|
+
function je(e) {
|
|
847
|
+
const a = V.get(e);
|
|
846
848
|
if (a !== void 0) return a;
|
|
847
|
-
const
|
|
848
|
-
return V.set(
|
|
849
|
+
const t = Ve(e);
|
|
850
|
+
return V.set(e, t), t;
|
|
849
851
|
}
|
|
850
|
-
function
|
|
851
|
-
let a =
|
|
852
|
+
function Ve(e) {
|
|
853
|
+
let a = e, t = !1;
|
|
852
854
|
for (; a; ) {
|
|
853
|
-
const i =
|
|
854
|
-
if ((i === "absolute" || i === "fixed") && (
|
|
855
|
+
const i = y(a).position;
|
|
856
|
+
if ((i === "absolute" || i === "fixed") && (t = !0), a !== e && i !== "static") {
|
|
855
857
|
for (const n of a.children)
|
|
856
|
-
if (!(n ===
|
|
857
|
-
if (
|
|
858
|
-
const r =
|
|
858
|
+
if (!(n === e || n.contains(e)) && Pe.has(n.tagName)) {
|
|
859
|
+
if (t) return !0;
|
|
860
|
+
const r = y(n).position;
|
|
859
861
|
if (r === "absolute" || r === "fixed") return !0;
|
|
860
862
|
}
|
|
861
|
-
if (
|
|
863
|
+
if (t) break;
|
|
862
864
|
}
|
|
863
865
|
a = a.parentElement;
|
|
864
866
|
}
|
|
865
867
|
return !1;
|
|
866
868
|
}
|
|
867
|
-
function
|
|
868
|
-
const a = parseFloat(
|
|
869
|
-
return
|
|
869
|
+
function ze(e) {
|
|
870
|
+
const a = parseFloat(e);
|
|
871
|
+
return e.endsWith("pt") ? a * (4 / 3) : a;
|
|
870
872
|
}
|
|
871
|
-
function
|
|
872
|
-
const a =
|
|
873
|
-
return
|
|
873
|
+
function Ue(e) {
|
|
874
|
+
const a = y(e), t = ze(a.fontSize), i = parseInt(a.fontWeight) || (a.fontWeight === "bold" ? 700 : 400);
|
|
875
|
+
return t >= 23.5 || t >= 18.5 && i >= 700;
|
|
874
876
|
}
|
|
875
|
-
function D(
|
|
877
|
+
function D(e) {
|
|
876
878
|
var r, o;
|
|
877
|
-
const a = [],
|
|
878
|
-
if (
|
|
879
|
-
const s =
|
|
879
|
+
const a = [], t = e.closest("a");
|
|
880
|
+
if (t) {
|
|
881
|
+
const s = t.getAttribute("href");
|
|
880
882
|
s && a.push(`Link href: ${s}`);
|
|
881
883
|
}
|
|
882
|
-
const i =
|
|
884
|
+
const i = e.closest("figure");
|
|
883
885
|
if (i) {
|
|
884
886
|
const s = i.querySelector("figcaption");
|
|
885
887
|
(r = s == null ? void 0 : s.textContent) != null && r.trim() && a.push(`Figcaption: ${s.textContent.trim().slice(0, 100)}`);
|
|
886
888
|
}
|
|
887
|
-
const n =
|
|
888
|
-
if (n && n !==
|
|
889
|
-
const s =
|
|
889
|
+
const n = e.parentElement;
|
|
890
|
+
if (n && n !== t) {
|
|
891
|
+
const s = e instanceof HTMLImageElement && e.alt || "", l = (o = n.textContent) == null ? void 0 : o.replace(s, "").trim().slice(0, 100);
|
|
890
892
|
l && a.push(`Adjacent text: ${l}`);
|
|
891
893
|
}
|
|
892
894
|
return a.length > 0 ? a.join(`
|
|
893
895
|
`) : void 0;
|
|
894
896
|
}
|
|
895
|
-
function Z(
|
|
896
|
-
let a =
|
|
897
|
+
function Z(e) {
|
|
898
|
+
let a = e;
|
|
897
899
|
for (; a; ) {
|
|
898
900
|
if (a instanceof HTMLElement && a.style.visibility === "hidden") return !0;
|
|
899
901
|
a = a.parentElement;
|
|
900
902
|
}
|
|
901
903
|
return !1;
|
|
902
904
|
}
|
|
903
|
-
const
|
|
905
|
+
const Ge = {
|
|
904
906
|
id: "img-alt",
|
|
905
907
|
wcag: ["1.1.1"],
|
|
906
908
|
level: "A",
|
|
907
909
|
description: `Images must have alternate text. Add an alt attribute to <img> elements. Decorative images may use an empty alt attribute (alt=""), role='none', or role='presentation'.`,
|
|
908
910
|
guidance: "Every image needs an alt attribute. For informative images, describe the content or function concisely. For decorative images (backgrounds, spacers, purely visual flourishes), use alt='' to hide them from screen readers. Never omit alt entirely—screen readers may read the filename instead.",
|
|
909
911
|
prompt: "Describe what alt text to add. If the image appears decorative based on context (spacer, background, icon next to text that already describes it), recommend alt=''. Otherwise suggest descriptive alt text based on the src or surrounding context.",
|
|
910
|
-
run(
|
|
912
|
+
run(e) {
|
|
911
913
|
const a = [];
|
|
912
|
-
for (const
|
|
913
|
-
if (g(
|
|
914
|
-
const i =
|
|
914
|
+
for (const t of e.querySelectorAll("img")) {
|
|
915
|
+
if (g(t) || Z(t)) continue;
|
|
916
|
+
const i = t.getAttribute("role");
|
|
915
917
|
if (i === "presentation" || i === "none") {
|
|
916
|
-
const r =
|
|
918
|
+
const r = t.getAttribute("tabindex");
|
|
917
919
|
if (!r || r === "-1") continue;
|
|
918
920
|
}
|
|
919
|
-
const n =
|
|
921
|
+
const n = t.getAttribute("alt");
|
|
920
922
|
if (n !== null && n.trim() === "" && n !== "") {
|
|
921
923
|
a.push({
|
|
922
924
|
ruleId: "img-alt",
|
|
923
|
-
selector: m(
|
|
924
|
-
html: d(
|
|
925
|
+
selector: m(t),
|
|
926
|
+
html: d(t),
|
|
925
927
|
impact: "critical",
|
|
926
928
|
message: 'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.',
|
|
927
|
-
context: D(
|
|
929
|
+
context: D(t)
|
|
928
930
|
});
|
|
929
931
|
continue;
|
|
930
932
|
}
|
|
931
|
-
!
|
|
933
|
+
!t.hasAttribute("alt") && !v(t) && a.push({
|
|
932
934
|
ruleId: "img-alt",
|
|
933
|
-
selector: m(
|
|
934
|
-
html: d(
|
|
935
|
+
selector: m(t),
|
|
936
|
+
html: d(t),
|
|
935
937
|
impact: "critical",
|
|
936
938
|
message: "Image element missing alt attribute.",
|
|
937
|
-
context: D(
|
|
939
|
+
context: D(t)
|
|
938
940
|
});
|
|
939
941
|
}
|
|
940
|
-
for (const
|
|
941
|
-
g(
|
|
942
|
+
for (const t of e.querySelectorAll('[role="img"]:not(img):not(svg)'))
|
|
943
|
+
g(t) || Z(t) || v(t) || a.push({
|
|
942
944
|
ruleId: "img-alt",
|
|
943
|
-
selector: m(
|
|
944
|
-
html: d(
|
|
945
|
+
selector: m(t),
|
|
946
|
+
html: d(t),
|
|
945
947
|
impact: "critical",
|
|
946
948
|
message: 'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.',
|
|
947
|
-
context: D(
|
|
949
|
+
context: D(t)
|
|
948
950
|
});
|
|
949
951
|
return a;
|
|
950
952
|
}
|
|
951
953
|
};
|
|
952
|
-
function
|
|
954
|
+
function Ye(e) {
|
|
953
955
|
var r, o, s;
|
|
954
|
-
const a =
|
|
956
|
+
const a = e.getAttribute("aria-labelledby");
|
|
955
957
|
if (a) {
|
|
956
|
-
const l = a.split(/\s+/).map((
|
|
958
|
+
const l = a.split(/\s+/).map((p) => {
|
|
957
959
|
var c, u;
|
|
958
|
-
return ((u = (c =
|
|
960
|
+
return ((u = (c = e.ownerDocument.getElementById(p)) == null ? void 0 : c.textContent) == null ? void 0 : u.trim()) ?? "";
|
|
959
961
|
}).filter(Boolean);
|
|
960
962
|
if (l.length) return l.join(" ");
|
|
961
963
|
}
|
|
962
|
-
const
|
|
963
|
-
if (
|
|
964
|
-
const i =
|
|
964
|
+
const t = (r = e.getAttribute("aria-label")) == null ? void 0 : r.trim();
|
|
965
|
+
if (t) return t;
|
|
966
|
+
const i = e.querySelector("title");
|
|
965
967
|
if ((o = i == null ? void 0 : i.textContent) != null && o.trim()) return i.textContent.trim();
|
|
966
|
-
const n = (s =
|
|
968
|
+
const n = (s = e.getAttribute("title")) == null ? void 0 : s.trim();
|
|
967
969
|
return n || "";
|
|
968
970
|
}
|
|
969
|
-
const
|
|
971
|
+
const Xe = {
|
|
970
972
|
id: "svg-img-alt",
|
|
971
973
|
wcag: ["1.1.1"],
|
|
972
974
|
level: "A",
|
|
973
975
|
description: "SVG elements with an img, graphics-document, or graphics-symbol role must have an accessible name via a <title> element, aria-label, or aria-labelledby.",
|
|
974
976
|
guidance: "Inline SVGs with role='img' need accessible names. Add a <title> element as the first child of the SVG (screen readers will announce it), or use aria-label on the SVG element. For complex SVGs, use aria-labelledby referencing both a <title> and <desc> element. Decorative SVGs should use aria-hidden='true' instead.",
|
|
975
977
|
prompt: "Based on the SVG content or context, suggest either adding aria-label with a description, or if decorative, replacing role='img' with aria-hidden='true'.",
|
|
976
|
-
run(
|
|
977
|
-
const a = [],
|
|
978
|
-
for (const i of
|
|
978
|
+
run(e) {
|
|
979
|
+
const a = [], t = 'svg[role="img"], [role="graphics-document"], [role="graphics-symbol"]';
|
|
980
|
+
for (const i of e.querySelectorAll(t)) {
|
|
979
981
|
if (g(i)) continue;
|
|
980
|
-
if (!
|
|
982
|
+
if (!Ye(i)) {
|
|
981
983
|
const r = i.getAttribute("role");
|
|
982
984
|
a.push({
|
|
983
985
|
ruleId: "svg-img-alt",
|
|
@@ -990,26 +992,26 @@ const Ge = {
|
|
|
990
992
|
}
|
|
991
993
|
return a;
|
|
992
994
|
}
|
|
993
|
-
},
|
|
995
|
+
}, Ke = {
|
|
994
996
|
id: "input-image-alt",
|
|
995
997
|
wcag: ["1.1.1", "4.1.2"],
|
|
996
998
|
level: "A",
|
|
997
999
|
description: 'Image inputs (<input type="image">) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.',
|
|
998
1000
|
guidance: "Image buttons (<input type='image'>) must have alternate text via alt, aria-label, or aria-labelledby. The text should describe the button action, not the image.",
|
|
999
1001
|
prompt: "Based on the src attribute or form context, suggest alt text describing the button's action (e.g., 'Submit', 'Search', 'Go').",
|
|
1000
|
-
run(
|
|
1002
|
+
run(e) {
|
|
1001
1003
|
const a = [];
|
|
1002
|
-
for (const
|
|
1003
|
-
g(
|
|
1004
|
+
for (const t of e.querySelectorAll('input[type="image"]'))
|
|
1005
|
+
g(t) || v(t) || a.push({
|
|
1004
1006
|
ruleId: "input-image-alt",
|
|
1005
|
-
selector: m(
|
|
1006
|
-
html: d(
|
|
1007
|
+
selector: m(t),
|
|
1008
|
+
html: d(t),
|
|
1007
1009
|
impact: "critical",
|
|
1008
1010
|
message: "Image input missing alt text."
|
|
1009
1011
|
});
|
|
1010
1012
|
return a;
|
|
1011
1013
|
}
|
|
1012
|
-
},
|
|
1014
|
+
}, Je = {
|
|
1013
1015
|
id: "image-redundant-alt",
|
|
1014
1016
|
wcag: [],
|
|
1015
1017
|
level: "A",
|
|
@@ -1017,15 +1019,15 @@ const Ge = {
|
|
|
1017
1019
|
description: "Image alt text should not duplicate adjacent link or button text. When alt text repeats surrounding text, screen reader users hear the same information twice.",
|
|
1018
1020
|
guidance: "When an image is inside a link or button that also has text, make the alt text complementary rather than identical. If the image is purely decorative in that context, use alt='' to avoid repetition.",
|
|
1019
1021
|
prompt: "The image alt text is identical to the text already visible in the parent link or button. Screen reader users hear the same words twice. If the image is decorative in this context (e.g. an icon next to a label), recommend alt=''. Otherwise suggest brief complementary alt text that adds information the visible text doesn't convey — for example what the image depicts, not what the link says.",
|
|
1020
|
-
run(
|
|
1021
|
-
var
|
|
1022
|
+
run(e) {
|
|
1023
|
+
var t;
|
|
1022
1024
|
const a = [];
|
|
1023
|
-
for (const i of
|
|
1025
|
+
for (const i of e.querySelectorAll("img[alt]")) {
|
|
1024
1026
|
const n = i.getAttribute("alt").trim().toLowerCase();
|
|
1025
1027
|
if (!n) continue;
|
|
1026
1028
|
const r = i.closest("a, button");
|
|
1027
1029
|
if (r) {
|
|
1028
|
-
const o = ((
|
|
1030
|
+
const o = ((t = r.textContent) == null ? void 0 : t.trim().toLowerCase()) || "";
|
|
1029
1031
|
if (o && o === n) {
|
|
1030
1032
|
const s = r.tagName.toLowerCase(), l = r.getAttribute("href");
|
|
1031
1033
|
a.push({
|
|
@@ -1041,7 +1043,7 @@ const Ge = {
|
|
|
1041
1043
|
}
|
|
1042
1044
|
return a;
|
|
1043
1045
|
}
|
|
1044
|
-
},
|
|
1046
|
+
}, Qe = ["image", "picture", "photo", "graphic", "icon", "img"], Ze = {
|
|
1045
1047
|
id: "image-alt-redundant-words",
|
|
1046
1048
|
wcag: [],
|
|
1047
1049
|
level: "A",
|
|
@@ -1049,38 +1051,38 @@ const Ge = {
|
|
|
1049
1051
|
description: "Image alt text should not contain words like 'image', 'photo', or 'picture' — screen readers already announce the element type.",
|
|
1050
1052
|
guidance: "Screen readers already announce 'image' or 'graphic' before reading alt text, so phrases like 'image of', 'photo of', or 'picture of' are redundant. Remove these words and describe what the image shows. For example, change 'image of a dog' to 'golden retriever playing fetch'.",
|
|
1051
1053
|
prompt: "The alt text contains a word like 'image', 'photo', or 'picture' that is already announced by the screen reader. Rewrite the alt text with the redundant word removed while keeping the description meaningful. For example: 'image of a sunset over the ocean' → 'sunset over the ocean'; 'photo of team members' → 'team members at the 2024 offsite'; 'icon for settings' → 'settings'.",
|
|
1052
|
-
run(
|
|
1054
|
+
run(e) {
|
|
1053
1055
|
const a = [];
|
|
1054
|
-
for (const
|
|
1055
|
-
const i =
|
|
1056
|
+
for (const t of e.querySelectorAll("img[alt]")) {
|
|
1057
|
+
const i = t.getAttribute("alt").toLowerCase();
|
|
1056
1058
|
if (!i) continue;
|
|
1057
|
-
const n =
|
|
1059
|
+
const n = Qe.filter((r) => i.split(/\s+/).includes(r));
|
|
1058
1060
|
n.length > 0 && a.push({
|
|
1059
1061
|
ruleId: "image-alt-redundant-words",
|
|
1060
|
-
selector: m(
|
|
1061
|
-
html: d(
|
|
1062
|
+
selector: m(t),
|
|
1063
|
+
html: d(t),
|
|
1062
1064
|
impact: "minor",
|
|
1063
|
-
message: `Alt text "${
|
|
1064
|
-
context: `Current alt: "${
|
|
1065
|
+
message: `Alt text "${t.getAttribute("alt")}" contains redundant word(s): ${n.join(", ")}.`,
|
|
1066
|
+
context: `Current alt: "${t.getAttribute("alt")}", redundant word(s): ${n.join(", ")}`
|
|
1065
1067
|
});
|
|
1066
1068
|
}
|
|
1067
1069
|
return a;
|
|
1068
1070
|
}
|
|
1069
|
-
},
|
|
1071
|
+
}, et = {
|
|
1070
1072
|
id: "area-alt",
|
|
1071
1073
|
wcag: ["1.1.1", "4.1.2"],
|
|
1072
1074
|
level: "A",
|
|
1073
1075
|
description: "Image map <area> elements must have alternative text.",
|
|
1074
1076
|
guidance: "Each clickable region in an image map needs alternative text so screen reader users know what the region represents. Add an alt attribute to every <area> element describing its purpose. For complex image maps, consider using alternative approaches like SVG with embedded links, or a list of text links.",
|
|
1075
1077
|
prompt: "Based on the href or shape/coords, suggest alt text describing where this area links or what it represents.",
|
|
1076
|
-
run(
|
|
1078
|
+
run(e) {
|
|
1077
1079
|
const a = [];
|
|
1078
|
-
for (const
|
|
1079
|
-
if (g(
|
|
1080
|
-
v(
|
|
1080
|
+
for (const t of e.querySelectorAll("area[href]")) {
|
|
1081
|
+
if (g(t)) continue;
|
|
1082
|
+
v(t) || a.push({
|
|
1081
1083
|
ruleId: "area-alt",
|
|
1082
|
-
selector: m(
|
|
1083
|
-
html: d(
|
|
1084
|
+
selector: m(t),
|
|
1085
|
+
html: d(t),
|
|
1084
1086
|
impact: "critical",
|
|
1085
1087
|
message: "Image map <area> element is missing alternative text."
|
|
1086
1088
|
});
|
|
@@ -1088,32 +1090,32 @@ const Ge = {
|
|
|
1088
1090
|
return a;
|
|
1089
1091
|
}
|
|
1090
1092
|
};
|
|
1091
|
-
function
|
|
1093
|
+
function tt(e) {
|
|
1092
1094
|
var n, r;
|
|
1093
|
-
const a =
|
|
1095
|
+
const a = e.getAttribute("aria-labelledby");
|
|
1094
1096
|
if (a) {
|
|
1095
1097
|
const o = a.split(/\s+/).map((s) => {
|
|
1096
|
-
var l,
|
|
1097
|
-
return ((
|
|
1098
|
+
var l, p;
|
|
1099
|
+
return ((p = (l = e.ownerDocument.getElementById(s)) == null ? void 0 : l.textContent) == null ? void 0 : p.trim()) ?? "";
|
|
1098
1100
|
}).filter(Boolean);
|
|
1099
1101
|
if (o.length) return o.join(" ");
|
|
1100
1102
|
}
|
|
1101
|
-
const
|
|
1102
|
-
if (
|
|
1103
|
-
const i = (r =
|
|
1103
|
+
const t = (n = e.getAttribute("aria-label")) == null ? void 0 : n.trim();
|
|
1104
|
+
if (t) return t;
|
|
1105
|
+
const i = (r = e.getAttribute("title")) == null ? void 0 : r.trim();
|
|
1104
1106
|
return i || "";
|
|
1105
1107
|
}
|
|
1106
|
-
const
|
|
1108
|
+
const at = {
|
|
1107
1109
|
id: "object-alt",
|
|
1108
1110
|
wcag: ["1.1.1"],
|
|
1109
1111
|
level: "A",
|
|
1110
1112
|
description: "<object> elements must have alternative text.",
|
|
1111
1113
|
guidance: "Object elements embed external content that may not be accessible to all users. Provide alternative text via aria-label, aria-labelledby, or a title attribute. The fallback content inside <object> is only shown when the object fails to load and does not serve as an accessible name.",
|
|
1112
1114
|
prompt: "Based on the data/type attributes, suggest adding aria-label or a title attribute describing what the embedded content represents.",
|
|
1113
|
-
run(
|
|
1114
|
-
var
|
|
1115
|
+
run(e) {
|
|
1116
|
+
var t;
|
|
1115
1117
|
const a = [];
|
|
1116
|
-
for (const i of
|
|
1118
|
+
for (const i of e.querySelectorAll("object")) {
|
|
1117
1119
|
if (g(i) || i instanceof HTMLElement && i.style.visibility === "hidden") continue;
|
|
1118
1120
|
let n = i.parentElement, r = !1;
|
|
1119
1121
|
for (; n; ) {
|
|
@@ -1123,11 +1125,11 @@ const et = {
|
|
|
1123
1125
|
}
|
|
1124
1126
|
n = n.parentElement;
|
|
1125
1127
|
}
|
|
1126
|
-
if (r || i.getAttribute("role") === "presentation" || i.getAttribute("role") === "none" ||
|
|
1128
|
+
if (r || i.getAttribute("role") === "presentation" || i.getAttribute("role") === "none" || tt(i)) continue;
|
|
1127
1129
|
const o = i.getAttribute("data") || "";
|
|
1128
1130
|
if (!((i.getAttribute("type") || "").startsWith("image/") || /\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(o))) {
|
|
1129
|
-
const
|
|
1130
|
-
if (
|
|
1131
|
+
const p = i.querySelector("img[alt]");
|
|
1132
|
+
if (p && ((t = p.getAttribute("alt")) != null && t.trim())) continue;
|
|
1131
1133
|
}
|
|
1132
1134
|
a.push({
|
|
1133
1135
|
ruleId: "object-alt",
|
|
@@ -1139,21 +1141,21 @@ const et = {
|
|
|
1139
1141
|
}
|
|
1140
1142
|
return a;
|
|
1141
1143
|
}
|
|
1142
|
-
},
|
|
1144
|
+
}, it = {
|
|
1143
1145
|
id: "role-img-alt",
|
|
1144
1146
|
wcag: ["1.1.1"],
|
|
1145
1147
|
level: "A",
|
|
1146
1148
|
description: "Elements with role='img' must have an accessible name.",
|
|
1147
1149
|
guidance: "When you assign role='img' to an element (like a div containing icon fonts or CSS backgrounds), you must provide an accessible name via aria-label or aria-labelledby. Without this, screen reader users have no way to understand what the image represents. If the image is decorative, use role='presentation' or role='none' instead.",
|
|
1148
1150
|
prompt: "Based on the element's content or class names, suggest either an aria-label describing the image, or if decorative, recommend removing role='img' or adding aria-hidden='true'.",
|
|
1149
|
-
run(
|
|
1151
|
+
run(e) {
|
|
1150
1152
|
const a = [];
|
|
1151
|
-
for (const
|
|
1152
|
-
if (g(
|
|
1153
|
-
v(
|
|
1153
|
+
for (const t of e.querySelectorAll('[role="img"]')) {
|
|
1154
|
+
if (g(t) || t.tagName.toLowerCase() === "svg" || t.tagName.toLowerCase() === "img") continue;
|
|
1155
|
+
v(t) || a.push({
|
|
1154
1156
|
ruleId: "role-img-alt",
|
|
1155
|
-
selector: m(
|
|
1156
|
-
html: d(
|
|
1157
|
+
selector: m(t),
|
|
1158
|
+
html: d(t),
|
|
1157
1159
|
impact: "serious",
|
|
1158
1160
|
message: "Element with role='img' has no accessible name. Add aria-label or aria-labelledby."
|
|
1159
1161
|
});
|
|
@@ -1161,17 +1163,17 @@ const et = {
|
|
|
1161
1163
|
return a;
|
|
1162
1164
|
}
|
|
1163
1165
|
};
|
|
1164
|
-
function
|
|
1165
|
-
if (typeof
|
|
1166
|
+
function Ni(e) {
|
|
1167
|
+
if (typeof e != "object" || e === null)
|
|
1166
1168
|
return "Rule spec must be an object";
|
|
1167
|
-
const a =
|
|
1169
|
+
const a = e;
|
|
1168
1170
|
if (typeof a.id != "string" || a.id.length === 0)
|
|
1169
1171
|
return "Rule must have a non-empty string id";
|
|
1170
1172
|
if (typeof a.selector != "string" || a.selector.length === 0)
|
|
1171
1173
|
return "Rule must have a non-empty string selector";
|
|
1172
1174
|
if (typeof a.check != "object" || a.check === null)
|
|
1173
1175
|
return "Rule must have a check object";
|
|
1174
|
-
const
|
|
1176
|
+
const t = a.check;
|
|
1175
1177
|
if (![
|
|
1176
1178
|
"selector-exists",
|
|
1177
1179
|
"attribute-value",
|
|
@@ -1179,8 +1181,8 @@ function Ci(t) {
|
|
|
1179
1181
|
"attribute-regex",
|
|
1180
1182
|
"child-required",
|
|
1181
1183
|
"child-invalid"
|
|
1182
|
-
].includes(
|
|
1183
|
-
return `Invalid check type: ${String(
|
|
1184
|
+
].includes(t.type))
|
|
1185
|
+
return `Invalid check type: ${String(t.type)}`;
|
|
1184
1186
|
if (typeof a.impact != "string" || !["critical", "serious", "moderate", "minor"].includes(a.impact))
|
|
1185
1187
|
return "Rule must have a valid impact (critical|serious|moderate|minor)";
|
|
1186
1188
|
if (typeof a.message != "string" || a.message.length === 0)
|
|
@@ -1191,153 +1193,171 @@ function Ci(t) {
|
|
|
1191
1193
|
return "Rule must have a wcag array";
|
|
1192
1194
|
if (typeof a.level != "string" || !["A", "AA"].includes(a.level))
|
|
1193
1195
|
return "Rule must have level A or AA";
|
|
1194
|
-
const n =
|
|
1196
|
+
const n = nt(t);
|
|
1195
1197
|
return n || null;
|
|
1196
1198
|
}
|
|
1197
|
-
function
|
|
1198
|
-
switch (
|
|
1199
|
+
function nt(e) {
|
|
1200
|
+
switch (e.type) {
|
|
1199
1201
|
case "selector-exists":
|
|
1200
1202
|
return null;
|
|
1201
1203
|
case "attribute-value":
|
|
1202
|
-
return typeof
|
|
1204
|
+
return typeof e.attribute != "string" ? "attribute-value check requires attribute string" : [">", "<", "=", "!=", "in", "not-in"].includes(e.operator) ? e.value === void 0 ? "attribute-value check requires value" : null : "attribute-value check requires valid operator";
|
|
1203
1205
|
case "attribute-missing":
|
|
1204
|
-
return typeof
|
|
1206
|
+
return typeof e.attribute != "string" ? "attribute-missing check requires attribute string" : null;
|
|
1205
1207
|
case "attribute-regex":
|
|
1206
|
-
return typeof
|
|
1208
|
+
return typeof e.attribute != "string" ? "attribute-regex check requires attribute string" : typeof e.pattern != "string" ? "attribute-regex check requires pattern string" : typeof e.shouldMatch != "boolean" ? "attribute-regex check requires shouldMatch boolean" : null;
|
|
1207
1209
|
case "child-required":
|
|
1208
|
-
return typeof
|
|
1210
|
+
return typeof e.childSelector != "string" ? "child-required check requires childSelector string" : null;
|
|
1209
1211
|
case "child-invalid":
|
|
1210
|
-
return Array.isArray(
|
|
1212
|
+
return Array.isArray(e.allowedChildren) ? null : "child-invalid check requires allowedChildren array";
|
|
1211
1213
|
default:
|
|
1212
|
-
return `Unknown check type: ${String(
|
|
1214
|
+
return `Unknown check type: ${String(e.type)}`;
|
|
1213
1215
|
}
|
|
1214
1216
|
}
|
|
1215
|
-
function S(
|
|
1216
|
-
let i =
|
|
1217
|
+
function S(e, a, t) {
|
|
1218
|
+
let i = e;
|
|
1217
1219
|
if (i.includes("{{tag}}") && (i = i.replace(/\{\{tag\}\}/g, a.tagName.toLowerCase())), i.includes("{{value}}")) {
|
|
1218
1220
|
let n = "";
|
|
1219
|
-
"attribute" in
|
|
1221
|
+
"attribute" in t && t.attribute && (n = a.getAttribute(t.attribute) ?? ""), i = i.replace(/\{\{value\}\}/g, n);
|
|
1220
1222
|
}
|
|
1221
1223
|
return i;
|
|
1222
1224
|
}
|
|
1223
|
-
function k(
|
|
1224
|
-
const a =
|
|
1225
|
+
function k(e) {
|
|
1226
|
+
const a = e.skipAriaHidden !== !1;
|
|
1225
1227
|
return {
|
|
1226
|
-
id:
|
|
1227
|
-
wcag:
|
|
1228
|
-
level:
|
|
1229
|
-
tags:
|
|
1230
|
-
description:
|
|
1231
|
-
guidance:
|
|
1232
|
-
prompt:
|
|
1233
|
-
run(
|
|
1228
|
+
id: e.id,
|
|
1229
|
+
wcag: e.wcag,
|
|
1230
|
+
level: e.level,
|
|
1231
|
+
tags: e.tags,
|
|
1232
|
+
description: e.description,
|
|
1233
|
+
guidance: e.guidance,
|
|
1234
|
+
prompt: e.prompt,
|
|
1235
|
+
run(t) {
|
|
1234
1236
|
var n, r;
|
|
1235
1237
|
const i = [];
|
|
1236
|
-
switch (
|
|
1238
|
+
switch (e.check.type) {
|
|
1237
1239
|
case "selector-exists": {
|
|
1238
|
-
for (const o of
|
|
1240
|
+
for (const o of t.querySelectorAll(e.selector))
|
|
1239
1241
|
a && g(o) || i.push({
|
|
1240
|
-
ruleId:
|
|
1242
|
+
ruleId: e.id,
|
|
1241
1243
|
selector: m(o),
|
|
1242
1244
|
html: d(o),
|
|
1243
|
-
impact:
|
|
1244
|
-
message: S(
|
|
1245
|
+
impact: e.impact,
|
|
1246
|
+
message: S(e.message, o, e.check),
|
|
1245
1247
|
element: o
|
|
1246
1248
|
});
|
|
1247
1249
|
break;
|
|
1248
1250
|
}
|
|
1249
1251
|
case "attribute-value": {
|
|
1250
|
-
const { attribute: o, operator: s, value: l } =
|
|
1251
|
-
for (const
|
|
1252
|
-
if (a && g(
|
|
1253
|
-
const c =
|
|
1254
|
-
c !== null &&
|
|
1255
|
-
ruleId:
|
|
1256
|
-
selector: m(
|
|
1257
|
-
html: d(
|
|
1258
|
-
impact:
|
|
1259
|
-
message: S(
|
|
1260
|
-
element:
|
|
1252
|
+
const { attribute: o, operator: s, value: l } = e.check;
|
|
1253
|
+
for (const p of t.querySelectorAll(e.selector)) {
|
|
1254
|
+
if (a && g(p)) continue;
|
|
1255
|
+
const c = p.getAttribute(o);
|
|
1256
|
+
c !== null && rt(c, s, l) && i.push({
|
|
1257
|
+
ruleId: e.id,
|
|
1258
|
+
selector: m(p),
|
|
1259
|
+
html: d(p),
|
|
1260
|
+
impact: e.impact,
|
|
1261
|
+
message: S(e.message, p, e.check),
|
|
1262
|
+
element: p
|
|
1261
1263
|
});
|
|
1262
1264
|
}
|
|
1263
1265
|
break;
|
|
1264
1266
|
}
|
|
1265
1267
|
case "attribute-missing": {
|
|
1266
|
-
const { attribute: o } =
|
|
1267
|
-
for (const s of
|
|
1268
|
+
const { attribute: o } = e.check;
|
|
1269
|
+
for (const s of t.querySelectorAll(e.selector))
|
|
1268
1270
|
a && g(s) || s.hasAttribute(o) || i.push({
|
|
1269
|
-
ruleId:
|
|
1271
|
+
ruleId: e.id,
|
|
1270
1272
|
selector: m(s),
|
|
1271
1273
|
html: d(s),
|
|
1272
|
-
impact:
|
|
1273
|
-
message: S(
|
|
1274
|
+
impact: e.impact,
|
|
1275
|
+
message: S(e.message, s, e.check),
|
|
1274
1276
|
element: s
|
|
1275
1277
|
});
|
|
1276
1278
|
break;
|
|
1277
1279
|
}
|
|
1278
1280
|
case "attribute-regex": {
|
|
1279
|
-
const { attribute: o, pattern: s, flags: l, shouldMatch:
|
|
1281
|
+
const { attribute: o, pattern: s, flags: l, shouldMatch: p } = e.check;
|
|
1280
1282
|
let c;
|
|
1281
1283
|
try {
|
|
1282
1284
|
c = new RegExp(s, l);
|
|
1283
1285
|
} catch {
|
|
1284
1286
|
break;
|
|
1285
1287
|
}
|
|
1286
|
-
for (const u of
|
|
1288
|
+
for (const u of t.querySelectorAll(e.selector)) {
|
|
1287
1289
|
if (a && g(u)) continue;
|
|
1288
|
-
const
|
|
1289
|
-
if (
|
|
1290
|
-
const b = c.test(
|
|
1291
|
-
|
|
1292
|
-
ruleId:
|
|
1290
|
+
const h = u.getAttribute(o);
|
|
1291
|
+
if (h === null) continue;
|
|
1292
|
+
const b = c.test(h);
|
|
1293
|
+
p && !b ? i.push({
|
|
1294
|
+
ruleId: e.id,
|
|
1293
1295
|
selector: m(u),
|
|
1294
1296
|
html: d(u),
|
|
1295
|
-
impact:
|
|
1296
|
-
message: S(
|
|
1297
|
+
impact: e.impact,
|
|
1298
|
+
message: S(e.message, u, e.check),
|
|
1297
1299
|
element: u
|
|
1298
|
-
}) : !
|
|
1299
|
-
ruleId:
|
|
1300
|
+
}) : !p && b && i.push({
|
|
1301
|
+
ruleId: e.id,
|
|
1300
1302
|
selector: m(u),
|
|
1301
1303
|
html: d(u),
|
|
1302
|
-
impact:
|
|
1303
|
-
message: S(
|
|
1304
|
+
impact: e.impact,
|
|
1305
|
+
message: S(e.message, u, e.check),
|
|
1304
1306
|
element: u
|
|
1305
1307
|
});
|
|
1306
1308
|
}
|
|
1307
1309
|
break;
|
|
1308
1310
|
}
|
|
1309
1311
|
case "child-required": {
|
|
1310
|
-
const { childSelector: o } =
|
|
1311
|
-
for (const s of
|
|
1312
|
+
const { childSelector: o } = e.check;
|
|
1313
|
+
for (const s of t.querySelectorAll(e.selector))
|
|
1312
1314
|
a && g(s) || s.querySelector(o) || i.push({
|
|
1313
|
-
ruleId:
|
|
1315
|
+
ruleId: e.id,
|
|
1314
1316
|
selector: m(s),
|
|
1315
1317
|
html: d(s),
|
|
1316
|
-
impact:
|
|
1317
|
-
message: S(
|
|
1318
|
+
impact: e.impact,
|
|
1319
|
+
message: S(e.message, s, e.check),
|
|
1318
1320
|
element: s
|
|
1319
1321
|
});
|
|
1320
1322
|
break;
|
|
1321
1323
|
}
|
|
1322
1324
|
case "child-invalid": {
|
|
1323
1325
|
const o = new Set(
|
|
1324
|
-
|
|
1325
|
-
), s =
|
|
1326
|
-
for (const l of
|
|
1326
|
+
e.check.allowedChildren.map((l) => l.toLowerCase())
|
|
1327
|
+
), s = e.check.allowedChildRoles ? new Set(e.check.allowedChildRoles.map((l) => l.toLowerCase())) : null;
|
|
1328
|
+
for (const l of t.querySelectorAll(e.selector)) {
|
|
1327
1329
|
if (a && g(l)) continue;
|
|
1328
|
-
const
|
|
1329
|
-
if (
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1330
|
+
const p = (n = l.getAttribute("role")) == null ? void 0 : n.trim().toLowerCase();
|
|
1331
|
+
if (p === "presentation" || p === "none") continue;
|
|
1332
|
+
let c = !1;
|
|
1333
|
+
const u = e.check.allowedChildren.filter(
|
|
1334
|
+
(h) => h !== "script" && h !== "template"
|
|
1335
|
+
);
|
|
1336
|
+
for (const h of l.childNodes)
|
|
1337
|
+
if (h.nodeType === 3 && h.textContent && h.textContent.trim()) {
|
|
1338
|
+
const b = u.map((f) => `<${f}>`).join(" or ");
|
|
1339
|
+
i.push({
|
|
1340
|
+
ruleId: e.id,
|
|
1341
|
+
selector: m(l),
|
|
1342
|
+
html: d(l),
|
|
1343
|
+
impact: e.impact,
|
|
1344
|
+
message: `<${l.tagName.toLowerCase()}> contains direct text content. Wrap in ${b}.`,
|
|
1345
|
+
element: l
|
|
1346
|
+
}), c = !0;
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
if (!c)
|
|
1350
|
+
for (const h of l.children) {
|
|
1351
|
+
if (o.has(h.tagName.toLowerCase())) continue;
|
|
1352
|
+
const b = (r = h.getAttribute("role")) == null ? void 0 : r.trim().toLowerCase();
|
|
1353
|
+
if (!(b && (s != null && s.has(b))) && !(b === "presentation" || b === "none")) {
|
|
1334
1354
|
i.push({
|
|
1335
|
-
ruleId:
|
|
1336
|
-
selector: m(
|
|
1337
|
-
html: d(
|
|
1338
|
-
impact:
|
|
1339
|
-
message: S(
|
|
1340
|
-
element:
|
|
1355
|
+
ruleId: e.id,
|
|
1356
|
+
selector: m(h),
|
|
1357
|
+
html: d(h),
|
|
1358
|
+
impact: e.impact,
|
|
1359
|
+
message: S(e.message, h, e.check),
|
|
1360
|
+
element: h
|
|
1341
1361
|
});
|
|
1342
1362
|
break;
|
|
1343
1363
|
}
|
|
@@ -1350,25 +1370,25 @@ function k(t) {
|
|
|
1350
1370
|
}
|
|
1351
1371
|
};
|
|
1352
1372
|
}
|
|
1353
|
-
function
|
|
1373
|
+
function rt(e, a, t) {
|
|
1354
1374
|
switch (a) {
|
|
1355
1375
|
case ">":
|
|
1356
|
-
return parseFloat(
|
|
1376
|
+
return parseFloat(e) > t;
|
|
1357
1377
|
case "<":
|
|
1358
|
-
return parseFloat(
|
|
1378
|
+
return parseFloat(e) < t;
|
|
1359
1379
|
case "=":
|
|
1360
|
-
return
|
|
1380
|
+
return e === String(t);
|
|
1361
1381
|
case "!=":
|
|
1362
|
-
return
|
|
1382
|
+
return e !== String(t);
|
|
1363
1383
|
case "in":
|
|
1364
|
-
return Array.isArray(
|
|
1384
|
+
return Array.isArray(t) && t.includes(e);
|
|
1365
1385
|
case "not-in":
|
|
1366
|
-
return Array.isArray(
|
|
1386
|
+
return Array.isArray(t) && !t.includes(e);
|
|
1367
1387
|
default:
|
|
1368
1388
|
return !1;
|
|
1369
1389
|
}
|
|
1370
1390
|
}
|
|
1371
|
-
const
|
|
1391
|
+
const ot = {
|
|
1372
1392
|
id: "server-side-image-map",
|
|
1373
1393
|
selector: "img[ismap], input[type='image'][ismap]",
|
|
1374
1394
|
check: { type: "selector-exists" },
|
|
@@ -1379,7 +1399,7 @@ const nt = {
|
|
|
1379
1399
|
level: "A",
|
|
1380
1400
|
guidance: "Server-side image maps (using ismap attribute) send click coordinates to the server, which is inaccessible to keyboard users and screen readers who can't precisely click specific regions. Replace with client-side image maps (<map> with <area> elements) that provide keyboard access and accessible names, or use linked images/buttons instead.",
|
|
1381
1401
|
prompt: "Explain that the ismap attribute should be removed and the functionality replaced with a client-side <map> element with <area> children, or separate linked images/buttons."
|
|
1382
|
-
},
|
|
1402
|
+
}, st = k(ot), lt = [
|
|
1383
1403
|
'[role="checkbox"]',
|
|
1384
1404
|
'[role="combobox"]',
|
|
1385
1405
|
'[role="listbox"]',
|
|
@@ -1391,13 +1411,13 @@ const nt = {
|
|
|
1391
1411
|
'[role="spinbutton"]',
|
|
1392
1412
|
'[role="switch"]',
|
|
1393
1413
|
'[role="textbox"]'
|
|
1394
|
-
].join(", "),
|
|
1414
|
+
].join(", "), ct = /* @__PURE__ */ new Set([
|
|
1395
1415
|
"checkbox",
|
|
1396
1416
|
"menuitemcheckbox",
|
|
1397
1417
|
"menuitemradio",
|
|
1398
1418
|
"radio",
|
|
1399
1419
|
"switch"
|
|
1400
|
-
]),
|
|
1420
|
+
]), ut = /* @__PURE__ */ new Set([
|
|
1401
1421
|
"combobox",
|
|
1402
1422
|
"listbox",
|
|
1403
1423
|
"searchbox",
|
|
@@ -1405,64 +1425,64 @@ const nt = {
|
|
|
1405
1425
|
"spinbutton",
|
|
1406
1426
|
"textbox"
|
|
1407
1427
|
]);
|
|
1408
|
-
function
|
|
1409
|
-
var o, s, l,
|
|
1410
|
-
const a = (o =
|
|
1411
|
-
if (a &&
|
|
1412
|
-
return v(
|
|
1413
|
-
const i =
|
|
1428
|
+
function dt(e) {
|
|
1429
|
+
var o, s, l, p;
|
|
1430
|
+
const a = (o = e.getAttribute("role")) == null ? void 0 : o.trim().toLowerCase();
|
|
1431
|
+
if (a && ct.has(a) || (e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement) && !(a && ut.has(a)))
|
|
1432
|
+
return v(e);
|
|
1433
|
+
const i = e.getAttribute("aria-labelledby");
|
|
1414
1434
|
if (i) {
|
|
1415
1435
|
const c = i.split(/\s+/).map((u) => {
|
|
1416
|
-
const
|
|
1417
|
-
return
|
|
1436
|
+
const h = e.ownerDocument.getElementById(u);
|
|
1437
|
+
return h ? A(h).trim() : "";
|
|
1418
1438
|
}).filter(Boolean);
|
|
1419
1439
|
if (c.length) return c.join(" ");
|
|
1420
1440
|
}
|
|
1421
|
-
const n = (s =
|
|
1441
|
+
const n = (s = e.getAttribute("aria-label")) == null ? void 0 : s.trim();
|
|
1422
1442
|
if (n) return n;
|
|
1423
|
-
if (
|
|
1424
|
-
if (
|
|
1425
|
-
const u =
|
|
1443
|
+
if (e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement || e instanceof HTMLSelectElement) {
|
|
1444
|
+
if (e.id) {
|
|
1445
|
+
const u = e.ownerDocument.querySelector(`label[for="${CSS.escape(e.id)}"]`);
|
|
1426
1446
|
if (u) {
|
|
1427
|
-
const
|
|
1428
|
-
if (
|
|
1447
|
+
const h = A(u).trim();
|
|
1448
|
+
if (h) return h;
|
|
1429
1449
|
}
|
|
1430
1450
|
}
|
|
1431
|
-
const c =
|
|
1451
|
+
const c = e.closest("label");
|
|
1432
1452
|
if (c) {
|
|
1433
1453
|
const u = A(c).trim();
|
|
1434
1454
|
if (u) return u;
|
|
1435
1455
|
}
|
|
1436
1456
|
}
|
|
1437
|
-
const r = (l =
|
|
1457
|
+
const r = (l = e.getAttribute("title")) == null ? void 0 : l.trim();
|
|
1438
1458
|
if (r) return r;
|
|
1439
|
-
if (
|
|
1440
|
-
const c = (
|
|
1459
|
+
if (e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement) {
|
|
1460
|
+
const c = (p = e.getAttribute("placeholder")) == null ? void 0 : p.trim();
|
|
1441
1461
|
if (c) return c;
|
|
1442
1462
|
}
|
|
1443
1463
|
return "";
|
|
1444
1464
|
}
|
|
1445
|
-
const
|
|
1465
|
+
const mt = {
|
|
1446
1466
|
id: "label",
|
|
1447
1467
|
wcag: ["4.1.2"],
|
|
1448
1468
|
level: "A",
|
|
1449
1469
|
description: "Form elements must have labels. Use <label>, aria-label, or aria-labelledby.",
|
|
1450
1470
|
guidance: "Every form input needs an accessible label so users understand what information to enter. Use a <label> element with a for attribute matching the input's id, wrap the input in a <label>, or use aria-label/aria-labelledby for custom components. Placeholders are not sufficient as labels since they disappear when typing.",
|
|
1451
1471
|
prompt: `This form field has no accessible label. Based on the context (input type, name attribute, placeholder, or surrounding elements), suggest adding a <label for="id"> element with descriptive text, or an aria-label attribute. The label should describe what information the user should enter, not the field type. For example: 'Email address', 'Search', 'Phone number'.`,
|
|
1452
|
-
run(
|
|
1472
|
+
run(e) {
|
|
1453
1473
|
var n;
|
|
1454
|
-
const a = [], i =
|
|
1474
|
+
const a = [], i = e.querySelectorAll(`input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select, ${lt}`);
|
|
1455
1475
|
for (const r of i) {
|
|
1456
1476
|
if (g(r) || T(r) || (r instanceof HTMLInputElement || r instanceof HTMLTextAreaElement || r instanceof HTMLSelectElement || r instanceof HTMLButtonElement) && r.disabled || r.closest("fieldset[disabled]") || r.getAttribute("aria-disabled") === "true") continue;
|
|
1457
1477
|
const o = (n = r.getAttribute("role")) == null ? void 0 : n.trim().toLowerCase();
|
|
1458
1478
|
if (o === "presentation" || o === "none") continue;
|
|
1459
|
-
if (!
|
|
1460
|
-
const l = [],
|
|
1461
|
-
c &&
|
|
1479
|
+
if (!dt(r)) {
|
|
1480
|
+
const l = [], p = r.tagName.toLowerCase(), c = r.getAttribute("type");
|
|
1481
|
+
c && p === "input" && l.push(`type: ${c}`);
|
|
1462
1482
|
const u = r.getAttribute("name");
|
|
1463
1483
|
u && l.push(`name: "${u}"`);
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1484
|
+
const h = r.getAttribute("placeholder");
|
|
1485
|
+
h && l.push(`placeholder: "${h}"`), o && l.push(`role: ${o}`);
|
|
1466
1486
|
const b = r.getAttribute("id");
|
|
1467
1487
|
b && l.push(`id: "${b}"`), a.push({
|
|
1468
1488
|
ruleId: "label",
|
|
@@ -1476,7 +1496,7 @@ const ut = {
|
|
|
1476
1496
|
}
|
|
1477
1497
|
return a;
|
|
1478
1498
|
}
|
|
1479
|
-
},
|
|
1499
|
+
}, ht = {
|
|
1480
1500
|
id: "form-field-multiple-labels",
|
|
1481
1501
|
wcag: [],
|
|
1482
1502
|
level: "A",
|
|
@@ -1484,11 +1504,11 @@ const ut = {
|
|
|
1484
1504
|
description: "Form fields should not have multiple label elements.",
|
|
1485
1505
|
guidance: "When a form field has multiple <label> elements pointing to it, assistive technologies may announce only one label or behave inconsistently. Use a single <label> and combine any additional text into it, or use aria-describedby for supplementary information.",
|
|
1486
1506
|
prompt: "Identify the multiple labels and recommend consolidating them into a single <label> element or using aria-describedby for supplementary text.",
|
|
1487
|
-
run(
|
|
1488
|
-
const a = [],
|
|
1489
|
-
for (const i of
|
|
1507
|
+
run(e) {
|
|
1508
|
+
const a = [], t = e.querySelectorAll('input:not([type="hidden"]), textarea, select');
|
|
1509
|
+
for (const i of t) {
|
|
1490
1510
|
if (g(i) || !i.id) continue;
|
|
1491
|
-
const n =
|
|
1511
|
+
const n = e.querySelectorAll(`label[for="${CSS.escape(i.id)}"]`);
|
|
1492
1512
|
let r = 0, o = i.parentElement;
|
|
1493
1513
|
for (; o; ) {
|
|
1494
1514
|
if (o.tagName.toLowerCase() === "label" && !o.hasAttribute("for")) {
|
|
@@ -1508,40 +1528,40 @@ const ut = {
|
|
|
1508
1528
|
}
|
|
1509
1529
|
return a;
|
|
1510
1530
|
}
|
|
1511
|
-
},
|
|
1531
|
+
}, pt = {
|
|
1512
1532
|
id: "select-name",
|
|
1513
1533
|
wcag: ["4.1.2"],
|
|
1514
1534
|
level: "A",
|
|
1515
1535
|
description: "Select elements must have a programmatically associated label via <label>, aria-label, or aria-labelledby.",
|
|
1516
1536
|
guidance: "Select dropdowns need labels so users understand what choice they're making. Use a <label> element with a for attribute matching the select's id, or wrap the select in a <label>. For selects without visible labels, use aria-label. The first <option> is not a substitute for a proper label.",
|
|
1517
1537
|
prompt: "Based on the options or context, suggest a label element or aria-label describing what this select controls.",
|
|
1518
|
-
run(
|
|
1538
|
+
run(e) {
|
|
1519
1539
|
const a = [];
|
|
1520
|
-
for (const
|
|
1521
|
-
g(
|
|
1540
|
+
for (const t of e.querySelectorAll("select"))
|
|
1541
|
+
g(t) || v(t) || a.push({
|
|
1522
1542
|
ruleId: "select-name",
|
|
1523
|
-
selector: m(
|
|
1524
|
-
html: d(
|
|
1543
|
+
selector: m(t),
|
|
1544
|
+
html: d(t),
|
|
1525
1545
|
impact: "critical",
|
|
1526
1546
|
message: "Select element has no accessible name."
|
|
1527
1547
|
});
|
|
1528
1548
|
return a;
|
|
1529
1549
|
}
|
|
1530
|
-
},
|
|
1550
|
+
}, gt = {
|
|
1531
1551
|
id: "input-button-name",
|
|
1532
1552
|
wcag: ["4.1.2"],
|
|
1533
1553
|
level: "A",
|
|
1534
1554
|
description: "Input buttons must have discernible text via value, aria-label, or aria-labelledby.",
|
|
1535
1555
|
guidance: "Input buttons (<input type='submit'>, type='button', type='reset'>) need accessible names so users know what action the button performs. Add a value attribute with descriptive text (e.g., value='Submit Form'), or use aria-label if the value must differ from the accessible name.",
|
|
1536
1556
|
prompt: "Based on the input type and form context, suggest a value attribute describing the button's action.",
|
|
1537
|
-
run(
|
|
1538
|
-
var
|
|
1557
|
+
run(e) {
|
|
1558
|
+
var t, i;
|
|
1539
1559
|
const a = [];
|
|
1540
|
-
for (const n of
|
|
1560
|
+
for (const n of e.querySelectorAll(
|
|
1541
1561
|
'input[type="submit"], input[type="button"], input[type="reset"]'
|
|
1542
1562
|
)) {
|
|
1543
1563
|
if (g(n)) continue;
|
|
1544
|
-
const r = (
|
|
1564
|
+
const r = (t = n.getAttribute("value")) == null ? void 0 : t.trim(), o = (i = n.getAttribute("type")) == null ? void 0 : i.toLowerCase(), s = (o === "submit" || o === "reset") && !n.hasAttribute("value");
|
|
1545
1565
|
!r && !s && !v(n) && a.push({
|
|
1546
1566
|
ruleId: "input-button-name",
|
|
1547
1567
|
selector: m(n),
|
|
@@ -1552,7 +1572,7 @@ const ut = {
|
|
|
1552
1572
|
}
|
|
1553
1573
|
return a;
|
|
1554
1574
|
}
|
|
1555
|
-
},
|
|
1575
|
+
}, bt = /* @__PURE__ */ new Set([
|
|
1556
1576
|
"off",
|
|
1557
1577
|
"on",
|
|
1558
1578
|
"name",
|
|
@@ -1607,7 +1627,7 @@ const ut = {
|
|
|
1607
1627
|
"impp",
|
|
1608
1628
|
"url",
|
|
1609
1629
|
"photo"
|
|
1610
|
-
]),
|
|
1630
|
+
]), ft = /* @__PURE__ */ new Set([
|
|
1611
1631
|
"tel",
|
|
1612
1632
|
"tel-country-code",
|
|
1613
1633
|
"tel-national",
|
|
@@ -1616,33 +1636,33 @@ const ut = {
|
|
|
1616
1636
|
"tel-extension",
|
|
1617
1637
|
"email",
|
|
1618
1638
|
"impp"
|
|
1619
|
-
]),
|
|
1620
|
-
function
|
|
1621
|
-
const a =
|
|
1639
|
+
]), vt = /* @__PURE__ */ new Set(["home", "work", "mobile", "fax", "pager"]), wt = /* @__PURE__ */ new Set(["shipping", "billing"]), yt = /* @__PURE__ */ new Set(["webauthn"]);
|
|
1640
|
+
function At(e) {
|
|
1641
|
+
const a = e.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1622
1642
|
if (a.length === 0) return !0;
|
|
1623
|
-
let
|
|
1624
|
-
a[
|
|
1643
|
+
let t = 0;
|
|
1644
|
+
a[t].startsWith("section-") && t++, t < a.length && wt.has(a[t]) && t++;
|
|
1625
1645
|
let i = !1;
|
|
1626
|
-
if (
|
|
1627
|
-
const n = a[
|
|
1628
|
-
return !
|
|
1646
|
+
if (t < a.length && vt.has(a[t]) && (i = !0, t++), t >= a.length) return !1;
|
|
1647
|
+
const n = a[t];
|
|
1648
|
+
return !bt.has(n) || i && !ft.has(n) ? !1 : (t++, t < a.length && yt.has(a[t]) && t++, t === a.length);
|
|
1629
1649
|
}
|
|
1630
|
-
const
|
|
1650
|
+
const xt = {
|
|
1631
1651
|
id: "autocomplete-valid",
|
|
1632
1652
|
wcag: ["1.3.5"],
|
|
1633
1653
|
level: "AA",
|
|
1634
1654
|
description: "Autocomplete attribute must use valid values from the HTML specification.",
|
|
1635
1655
|
guidance: "The autocomplete attribute helps users fill forms by identifying input purposes. Use standard values like 'name', 'email', 'tel', 'street-address', 'postal-code', 'cc-number'. This benefits users with cognitive disabilities, motor impairments, and anyone using password managers or autofill. Check the HTML specification for the complete list of valid tokens.",
|
|
1636
1656
|
prompt: "Show the invalid autocomplete value and suggest the correct standard value based on the input's apparent purpose.",
|
|
1637
|
-
run(
|
|
1657
|
+
run(e) {
|
|
1638
1658
|
const a = [];
|
|
1639
|
-
for (const
|
|
1640
|
-
if (g(
|
|
1641
|
-
const i =
|
|
1642
|
-
i && (
|
|
1659
|
+
for (const t of e.querySelectorAll("[autocomplete]")) {
|
|
1660
|
+
if (g(t) || t instanceof HTMLElement && t.style.display === "none" || t.disabled || t.getAttribute("aria-disabled") === "true") continue;
|
|
1661
|
+
const i = t.getAttribute("autocomplete").trim();
|
|
1662
|
+
i && (At(i) || a.push({
|
|
1643
1663
|
ruleId: "autocomplete-valid",
|
|
1644
|
-
selector: m(
|
|
1645
|
-
html: d(
|
|
1664
|
+
selector: m(t),
|
|
1665
|
+
html: d(t),
|
|
1646
1666
|
impact: "serious",
|
|
1647
1667
|
message: `Invalid autocomplete value "${i}".`
|
|
1648
1668
|
}));
|
|
@@ -1650,22 +1670,22 @@ const wt = {
|
|
|
1650
1670
|
return a;
|
|
1651
1671
|
}
|
|
1652
1672
|
};
|
|
1653
|
-
function ee(
|
|
1654
|
-
return
|
|
1673
|
+
function ee(e) {
|
|
1674
|
+
return e.toLowerCase().replace(/\s+/g, " ").trim();
|
|
1655
1675
|
}
|
|
1656
|
-
function te(
|
|
1657
|
-
const
|
|
1658
|
-
if (!
|
|
1676
|
+
function te(e, a) {
|
|
1677
|
+
const t = ee(e), i = ee(a);
|
|
1678
|
+
if (!t || !i || t.includes(i) || i.includes(t)) return !0;
|
|
1659
1679
|
const n = i.split(/\s+/).map((r) => r.replace(/[.,;:!?\u2026]+$/g, "")).filter((r) => r.length > 2);
|
|
1660
|
-
return n.length >= 2 && n.filter((o) =>
|
|
1680
|
+
return n.length >= 2 && n.filter((o) => t.includes(o)).length / n.length > 0.5;
|
|
1661
1681
|
}
|
|
1662
|
-
function z(
|
|
1682
|
+
function z(e) {
|
|
1663
1683
|
let a = "";
|
|
1664
|
-
for (const
|
|
1665
|
-
if (
|
|
1666
|
-
a +=
|
|
1667
|
-
else if (
|
|
1668
|
-
const i =
|
|
1684
|
+
for (const t of e.childNodes)
|
|
1685
|
+
if (t.nodeType === 3)
|
|
1686
|
+
a += t.textContent ?? "";
|
|
1687
|
+
else if (t.nodeType === 1) {
|
|
1688
|
+
const i = t, n = i.tagName.toLowerCase();
|
|
1669
1689
|
if (n === "style" || n === "script" || n === "svg" || i.getAttribute("aria-hidden") === "true" || i instanceof HTMLElement && i.style.display === "none") continue;
|
|
1670
1690
|
const r = i.getAttribute("role");
|
|
1671
1691
|
if (r === "img" || r === "presentation" || r === "none") continue;
|
|
@@ -1673,7 +1693,7 @@ function z(t) {
|
|
|
1673
1693
|
}
|
|
1674
1694
|
return a;
|
|
1675
1695
|
}
|
|
1676
|
-
const
|
|
1696
|
+
const St = {
|
|
1677
1697
|
id: "label-content-name-mismatch",
|
|
1678
1698
|
wcag: [],
|
|
1679
1699
|
level: "A",
|
|
@@ -1681,46 +1701,46 @@ const At = {
|
|
|
1681
1701
|
description: "Interactive elements with visible text must have accessible names that contain that text.",
|
|
1682
1702
|
guidance: "For voice control users who activate controls by speaking their visible label, the accessible name must include the visible text. If aria-label is 'Submit form' but the button shows 'Send', voice users saying 'click Send' won't activate it. Ensure aria-label/aria-labelledby contains or matches the visible text.",
|
|
1683
1703
|
prompt: "Show the mismatch between the visible text and accessible name, and suggest updating aria-label to include the visible text.",
|
|
1684
|
-
run(
|
|
1704
|
+
run(e) {
|
|
1685
1705
|
const a = [];
|
|
1686
|
-
for (const
|
|
1687
|
-
if (g(
|
|
1688
|
-
const i = v(
|
|
1706
|
+
for (const t of e.querySelectorAll('button, [role="button"], a[href], input[type="submit"], input[type="button"]')) {
|
|
1707
|
+
if (g(t)) continue;
|
|
1708
|
+
const i = v(t);
|
|
1689
1709
|
if (!i) continue;
|
|
1690
1710
|
let n = "";
|
|
1691
|
-
|
|
1711
|
+
t instanceof HTMLInputElement ? n = t.value || "" : n = z(t);
|
|
1692
1712
|
const r = n.trim();
|
|
1693
1713
|
if (!r || r.length <= 2) continue;
|
|
1694
|
-
const o =
|
|
1714
|
+
const o = t.hasAttribute("aria-label"), s = t.hasAttribute("aria-labelledby");
|
|
1695
1715
|
!o && !s || te(i, n) || a.push({
|
|
1696
1716
|
ruleId: "label-content-name-mismatch",
|
|
1697
|
-
selector: m(
|
|
1698
|
-
html: d(
|
|
1717
|
+
selector: m(t),
|
|
1718
|
+
html: d(t),
|
|
1699
1719
|
impact: "serious",
|
|
1700
1720
|
message: `Accessible name "${i}" does not contain visible text "${n.trim()}".`
|
|
1701
1721
|
});
|
|
1702
1722
|
}
|
|
1703
|
-
for (const
|
|
1704
|
-
if (g(
|
|
1705
|
-
const i = v(
|
|
1706
|
-
if (!i || !
|
|
1707
|
-
const r =
|
|
1723
|
+
for (const t of e.querySelectorAll("input, select, textarea")) {
|
|
1724
|
+
if (g(t) || t instanceof HTMLInputElement && ["hidden", "submit", "button", "image"].includes(t.type)) continue;
|
|
1725
|
+
const i = v(t);
|
|
1726
|
+
if (!i || !t.hasAttribute("aria-label")) continue;
|
|
1727
|
+
const r = t.id;
|
|
1708
1728
|
let o = "";
|
|
1709
1729
|
if (r) {
|
|
1710
|
-
const s =
|
|
1730
|
+
const s = e.querySelector(`label[for="${CSS.escape(r)}"]`);
|
|
1711
1731
|
s && (o = z(s));
|
|
1712
1732
|
}
|
|
1713
1733
|
o.trim() && (te(i, o) || a.push({
|
|
1714
1734
|
ruleId: "label-content-name-mismatch",
|
|
1715
|
-
selector: m(
|
|
1716
|
-
html: d(
|
|
1735
|
+
selector: m(t),
|
|
1736
|
+
html: d(t),
|
|
1717
1737
|
impact: "serious",
|
|
1718
1738
|
message: `Accessible name "${i}" does not contain visible label "${o.trim()}".`
|
|
1719
1739
|
}));
|
|
1720
1740
|
}
|
|
1721
1741
|
return a;
|
|
1722
1742
|
}
|
|
1723
|
-
},
|
|
1743
|
+
}, kt = {
|
|
1724
1744
|
id: "label-title-only",
|
|
1725
1745
|
wcag: [],
|
|
1726
1746
|
level: "A",
|
|
@@ -1728,22 +1748,22 @@ const At = {
|
|
|
1728
1748
|
description: "Form elements should not use title attribute as the only accessible name.",
|
|
1729
1749
|
guidance: "The title attribute is unreliable as a label because it only appears on hover/focus (not visible to touch users) and is often ignored by assistive technologies. Use a visible <label> element, aria-label, or aria-labelledby instead. Title can supplement a label but should not replace it.",
|
|
1730
1750
|
prompt: "The title attribute text should be moved to a visible <label> element or aria-label. Show what text to use based on the current title.",
|
|
1731
|
-
run(
|
|
1751
|
+
run(e) {
|
|
1732
1752
|
var i, n, r, o;
|
|
1733
|
-
const a = [],
|
|
1753
|
+
const a = [], t = e.querySelectorAll(
|
|
1734
1754
|
'input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select'
|
|
1735
1755
|
);
|
|
1736
|
-
for (const s of
|
|
1756
|
+
for (const s of t) {
|
|
1737
1757
|
if (g(s)) continue;
|
|
1738
|
-
const l = s.hasAttribute("title") && ((i = s.getAttribute("title")) == null ? void 0 : i.trim()),
|
|
1758
|
+
const l = s.hasAttribute("title") && ((i = s.getAttribute("title")) == null ? void 0 : i.trim()), p = s.hasAttribute("aria-label") && ((n = s.getAttribute("aria-label")) == null ? void 0 : n.trim()), c = s.hasAttribute("aria-labelledby");
|
|
1739
1759
|
let u = !1;
|
|
1740
|
-
const
|
|
1741
|
-
if (
|
|
1742
|
-
const f = s.ownerDocument.querySelector(`label[for="${CSS.escape(
|
|
1760
|
+
const h = s.id;
|
|
1761
|
+
if (h) {
|
|
1762
|
+
const f = s.ownerDocument.querySelector(`label[for="${CSS.escape(h)}"]`);
|
|
1743
1763
|
(r = f == null ? void 0 : f.textContent) != null && r.trim() && (u = !0);
|
|
1744
1764
|
}
|
|
1745
1765
|
const b = s.closest("label");
|
|
1746
|
-
(o = b == null ? void 0 : b.textContent) != null && o.trim() && (u = !0), l && !
|
|
1766
|
+
(o = b == null ? void 0 : b.textContent) != null && o.trim() && (u = !0), l && !p && !c && !u && a.push({
|
|
1747
1767
|
ruleId: "label-title-only",
|
|
1748
1768
|
selector: m(s),
|
|
1749
1769
|
html: d(s),
|
|
@@ -1753,7 +1773,7 @@ const At = {
|
|
|
1753
1773
|
}
|
|
1754
1774
|
return a;
|
|
1755
1775
|
}
|
|
1756
|
-
},
|
|
1776
|
+
}, It = {
|
|
1757
1777
|
id: "tabindex",
|
|
1758
1778
|
selector: "[tabindex]",
|
|
1759
1779
|
check: { type: "attribute-value", attribute: "tabindex", operator: ">", value: 0 },
|
|
@@ -1765,7 +1785,7 @@ const At = {
|
|
|
1765
1785
|
tags: ["best-practice"],
|
|
1766
1786
|
guidance: "Positive tabindex values force elements to the front of the tab order regardless of DOM position, creating unpredictable navigation for keyboard users. Use tabindex='0' to add elements to the natural tab order, or tabindex='-1' to make elements programmatically focusable but not in tab order. Rely on DOM order for tab sequence.",
|
|
1767
1787
|
prompt: "Change the positive tabindex value to tabindex='0' and rely on DOM order for tab sequence instead."
|
|
1768
|
-
},
|
|
1788
|
+
}, Tt = k(It), Et = /* @__PURE__ */ new Set([
|
|
1769
1789
|
"div",
|
|
1770
1790
|
"span",
|
|
1771
1791
|
"p",
|
|
@@ -1792,7 +1812,7 @@ const At = {
|
|
|
1792
1812
|
"tr",
|
|
1793
1813
|
"td",
|
|
1794
1814
|
"th"
|
|
1795
|
-
]),
|
|
1815
|
+
]), Ct = {
|
|
1796
1816
|
id: "focus-order-semantics",
|
|
1797
1817
|
wcag: [],
|
|
1798
1818
|
tags: ["best-practice"],
|
|
@@ -1800,22 +1820,22 @@ const At = {
|
|
|
1800
1820
|
description: "Elements that receive keyboard focus must have an appropriate role so assistive technologies can convey their purpose. Non-interactive elements with tabindex='0' need a valid interactive ARIA role.",
|
|
1801
1821
|
guidance: "When adding tabindex='0' to non-interactive elements like <div> or <span>, screen readers announce them generically. Add an appropriate role (button, link, tab, etc.) so users understand the element's purpose. Also add keyboard event handlers (Enter/Space for buttons, Enter for links). Consider using native interactive elements instead.",
|
|
1802
1822
|
prompt: "Based on the element's apparent purpose, suggest adding an appropriate role attribute (button, link, etc.) or converting to a native interactive element.",
|
|
1803
|
-
run(
|
|
1823
|
+
run(e) {
|
|
1804
1824
|
const a = [];
|
|
1805
|
-
for (const
|
|
1806
|
-
const i =
|
|
1807
|
-
if (!
|
|
1808
|
-
|
|
1825
|
+
for (const t of e.querySelectorAll('[tabindex="0"]')) {
|
|
1826
|
+
const i = t.tagName.toLowerCase();
|
|
1827
|
+
if (!Et.has(i)) continue;
|
|
1828
|
+
t.getAttribute("role") || a.push({
|
|
1809
1829
|
ruleId: "focus-order-semantics",
|
|
1810
|
-
selector: m(
|
|
1811
|
-
html: d(
|
|
1830
|
+
selector: m(t),
|
|
1831
|
+
html: d(t),
|
|
1812
1832
|
impact: "moderate",
|
|
1813
1833
|
message: `Non-interactive <${i}> with tabindex="0" has no interactive role.`
|
|
1814
1834
|
});
|
|
1815
1835
|
}
|
|
1816
1836
|
return a;
|
|
1817
1837
|
}
|
|
1818
|
-
},
|
|
1838
|
+
}, Lt = /* @__PURE__ */ new Set([
|
|
1819
1839
|
"a",
|
|
1820
1840
|
"audio",
|
|
1821
1841
|
"button",
|
|
@@ -1824,7 +1844,7 @@ const At = {
|
|
|
1824
1844
|
"select",
|
|
1825
1845
|
"textarea",
|
|
1826
1846
|
"video"
|
|
1827
|
-
]),
|
|
1847
|
+
]), qt = /* @__PURE__ */ new Set([
|
|
1828
1848
|
"button",
|
|
1829
1849
|
"checkbox",
|
|
1830
1850
|
"combobox",
|
|
@@ -1848,7 +1868,7 @@ const At = {
|
|
|
1848
1868
|
"tabpanel",
|
|
1849
1869
|
"textbox",
|
|
1850
1870
|
"treeitem"
|
|
1851
|
-
]),
|
|
1871
|
+
]), Rt = {
|
|
1852
1872
|
grid: /* @__PURE__ */ new Set(["gridcell", "row", "columnheader", "rowheader"]),
|
|
1853
1873
|
listbox: /* @__PURE__ */ new Set(["option"]),
|
|
1854
1874
|
menu: /* @__PURE__ */ new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]),
|
|
@@ -1858,44 +1878,44 @@ const At = {
|
|
|
1858
1878
|
tree: /* @__PURE__ */ new Set(["treeitem"]),
|
|
1859
1879
|
treegrid: /* @__PURE__ */ new Set(["gridcell", "row", "columnheader", "rowheader", "treeitem"])
|
|
1860
1880
|
};
|
|
1861
|
-
function
|
|
1881
|
+
function Nt(e, a) {
|
|
1862
1882
|
var n, r, o;
|
|
1863
|
-
const
|
|
1864
|
-
return !
|
|
1883
|
+
const t = (n = e.getAttribute("role")) == null ? void 0 : n.toLowerCase(), i = (r = a.getAttribute("role")) == null ? void 0 : r.toLowerCase();
|
|
1884
|
+
return !t || !i ? !1 : ((o = Rt[t]) == null ? void 0 : o.has(i)) ?? !1;
|
|
1865
1885
|
}
|
|
1866
|
-
function
|
|
1886
|
+
function $t(e) {
|
|
1867
1887
|
var n;
|
|
1868
|
-
const a =
|
|
1869
|
-
if (
|
|
1870
|
-
return a === "a" && !
|
|
1871
|
-
const
|
|
1872
|
-
if (
|
|
1873
|
-
const i =
|
|
1874
|
-
return i !== null && i !== "-1" ||
|
|
1888
|
+
const a = e.tagName.toLowerCase();
|
|
1889
|
+
if (Lt.has(a))
|
|
1890
|
+
return a === "a" && !e.hasAttribute("href") ? !1 : a === "audio" || a === "video" ? e.hasAttribute("controls") : !(a === "img" && !e.hasAttribute("usemap") || a === "input" && e.type === "hidden" || e.disabled);
|
|
1891
|
+
const t = (n = e.getAttribute("role")) == null ? void 0 : n.toLowerCase();
|
|
1892
|
+
if (t && qt.has(t)) return !0;
|
|
1893
|
+
const i = e.getAttribute("tabindex");
|
|
1894
|
+
return i !== null && i !== "-1" || e.getAttribute("contenteditable") === "true";
|
|
1875
1895
|
}
|
|
1876
|
-
function
|
|
1877
|
-
const a =
|
|
1878
|
-
return !!(a === "a" &&
|
|
1896
|
+
function Mt(e) {
|
|
1897
|
+
const a = e.tagName.toLowerCase();
|
|
1898
|
+
return !!(a === "a" && e.hasAttribute("href") || a === "button" && !e.disabled);
|
|
1879
1899
|
}
|
|
1880
|
-
const
|
|
1900
|
+
const Ht = {
|
|
1881
1901
|
id: "nested-interactive",
|
|
1882
1902
|
wcag: ["4.1.2"],
|
|
1883
1903
|
level: "A",
|
|
1884
1904
|
description: "Interactive controls must not be nested inside each other.",
|
|
1885
1905
|
guidance: "Nesting interactive elements (like a button inside a link, or a link inside a button) creates unpredictable behavior and confuses assistive technologies. The browser may remove the inner element from the accessibility tree. Restructure the HTML so interactive elements are siblings, not nested. If you need a clickable card, use CSS and JavaScript rather than nesting.",
|
|
1886
1906
|
prompt: "Identify which elements are nested and suggest restructuring them as siblings instead.",
|
|
1887
|
-
run(
|
|
1888
|
-
const a = [],
|
|
1889
|
-
if (!
|
|
1890
|
-
const n = (
|
|
1907
|
+
run(e) {
|
|
1908
|
+
const a = [], t = e.body ?? e;
|
|
1909
|
+
if (!t) return a;
|
|
1910
|
+
const n = (e.body ? e : e.ownerDocument).createTreeWalker(t, NodeFilter.SHOW_ELEMENT), r = [];
|
|
1891
1911
|
let o = n.currentNode;
|
|
1892
1912
|
for (; o; ) {
|
|
1893
1913
|
for (; r.length > 0 && !r[r.length - 1].contains(o); )
|
|
1894
1914
|
r.pop();
|
|
1895
|
-
if (!g(o) &&
|
|
1915
|
+
if (!g(o) && $t(o)) {
|
|
1896
1916
|
if (r.length > 0) {
|
|
1897
1917
|
const s = r[r.length - 1];
|
|
1898
|
-
|
|
1918
|
+
Nt(s, o) || a.push({
|
|
1899
1919
|
ruleId: "nested-interactive",
|
|
1900
1920
|
selector: m(o),
|
|
1901
1921
|
html: d(o),
|
|
@@ -1903,35 +1923,35 @@ const $t = {
|
|
|
1903
1923
|
message: `Interactive element <${o.tagName.toLowerCase()}> is nested inside <${s.tagName.toLowerCase()}>.`
|
|
1904
1924
|
});
|
|
1905
1925
|
}
|
|
1906
|
-
|
|
1926
|
+
Mt(o) && r.push(o);
|
|
1907
1927
|
}
|
|
1908
1928
|
o = n.nextNode();
|
|
1909
1929
|
}
|
|
1910
1930
|
return a;
|
|
1911
1931
|
}
|
|
1912
|
-
},
|
|
1932
|
+
}, Dt = {
|
|
1913
1933
|
id: "scrollable-region-focusable",
|
|
1914
1934
|
wcag: ["2.1.1"],
|
|
1915
1935
|
level: "A",
|
|
1916
1936
|
description: "Scrollable regions must be keyboard accessible.",
|
|
1917
1937
|
guidance: "Content that scrolls must be accessible to keyboard users. If a region has overflow:scroll or overflow:auto and contains scrollable content, it needs either tabindex='0' to be focusable, or it must contain focusable elements. Without this, keyboard users cannot scroll the content.",
|
|
1918
1938
|
prompt: "Explain how to make this scrollable region keyboard accessible.",
|
|
1919
|
-
run(
|
|
1920
|
-
var
|
|
1939
|
+
run(e) {
|
|
1940
|
+
var t;
|
|
1921
1941
|
const a = [];
|
|
1922
|
-
for (const i of
|
|
1942
|
+
for (const i of e.querySelectorAll("*")) {
|
|
1923
1943
|
if (g(i) || !(i instanceof HTMLElement)) continue;
|
|
1924
1944
|
const n = i.tagName.toLowerCase();
|
|
1925
1945
|
if (n === "body" || n === "html") continue;
|
|
1926
1946
|
const r = i.getAttribute("role");
|
|
1927
1947
|
if (r === "presentation" || r === "none") continue;
|
|
1928
|
-
const o =
|
|
1948
|
+
const o = y(i), s = o.overflowX, l = o.overflowY;
|
|
1929
1949
|
if (!(s === "scroll" || s === "auto" || l === "scroll" || l === "auto")) continue;
|
|
1930
1950
|
if (i.scrollHeight > 0 || i.clientHeight > 0) {
|
|
1931
1951
|
const b = i.scrollHeight - i.clientHeight, f = i.scrollWidth - i.clientWidth;
|
|
1932
|
-
if (b <= 0 && f <= 0 || b <
|
|
1952
|
+
if (b <= 0 && f <= 0 || b < 14 && f < 14 || i.clientWidth < 64 && i.clientHeight < 64) continue;
|
|
1933
1953
|
} else {
|
|
1934
|
-
const b = o.height !== "" || o.maxHeight !== "", f = ((
|
|
1954
|
+
const b = o.height !== "" || o.maxHeight !== "", f = ((t = i.textContent) == null ? void 0 : t.trim().length) ?? 0;
|
|
1935
1955
|
if (!b || f <= 50) continue;
|
|
1936
1956
|
}
|
|
1937
1957
|
const u = i.getAttribute("tabindex");
|
|
@@ -1947,7 +1967,7 @@ const $t = {
|
|
|
1947
1967
|
}
|
|
1948
1968
|
return a;
|
|
1949
1969
|
}
|
|
1950
|
-
},
|
|
1970
|
+
}, Ft = {
|
|
1951
1971
|
id: "accesskeys",
|
|
1952
1972
|
wcag: [],
|
|
1953
1973
|
level: "A",
|
|
@@ -1955,17 +1975,17 @@ const $t = {
|
|
|
1955
1975
|
description: "Accesskey attribute values must be unique.",
|
|
1956
1976
|
guidance: "When multiple elements share the same accesskey, browser behavior becomes unpredictable - usually only the first element is activated. Ensure each accesskey value is unique within the page. Also consider that accesskeys can conflict with browser and screen reader shortcuts, so use them sparingly.",
|
|
1957
1977
|
prompt: "Suggest removing or changing this duplicate accesskey to a unique value.",
|
|
1958
|
-
run(
|
|
1978
|
+
run(e) {
|
|
1959
1979
|
var i;
|
|
1960
|
-
const a = [],
|
|
1961
|
-
for (const n of
|
|
1980
|
+
const a = [], t = /* @__PURE__ */ new Map();
|
|
1981
|
+
for (const n of e.querySelectorAll("[accesskey]")) {
|
|
1962
1982
|
if (g(n)) continue;
|
|
1963
1983
|
const r = (i = n.getAttribute("accesskey")) == null ? void 0 : i.trim().toLowerCase();
|
|
1964
1984
|
if (!r) continue;
|
|
1965
|
-
const o =
|
|
1966
|
-
o.push(n),
|
|
1985
|
+
const o = t.get(r) || [];
|
|
1986
|
+
o.push(n), t.set(r, o);
|
|
1967
1987
|
}
|
|
1968
|
-
for (const [n, r] of
|
|
1988
|
+
for (const [n, r] of t)
|
|
1969
1989
|
if (r.length > 1)
|
|
1970
1990
|
for (const o of r.slice(1))
|
|
1971
1991
|
a.push({
|
|
@@ -1977,7 +1997,7 @@ const $t = {
|
|
|
1977
1997
|
});
|
|
1978
1998
|
return a;
|
|
1979
1999
|
}
|
|
1980
|
-
},
|
|
2000
|
+
}, Wt = {
|
|
1981
2001
|
id: "heading-order",
|
|
1982
2002
|
wcag: [],
|
|
1983
2003
|
level: "A",
|
|
@@ -1985,10 +2005,10 @@ const $t = {
|
|
|
1985
2005
|
description: "Heading levels should increase by one; skipping levels (e.g. h2 to h4) makes navigation harder.",
|
|
1986
2006
|
guidance: "Screen reader users navigate by headings to understand page structure. Skipping levels (h2 to h4) suggests missing content and creates confusion. Start with h1 for the page title, then use h2 for main sections, h3 for subsections, etc. You can go back up (h3 to h2) when starting a new section.",
|
|
1987
2007
|
prompt: "State which heading level was expected and suggest changing this heading to the appropriate level.",
|
|
1988
|
-
run(
|
|
1989
|
-
const a = [],
|
|
2008
|
+
run(e) {
|
|
2009
|
+
const a = [], t = e.querySelectorAll("h1, h2, h3, h4, h5, h6, [role='heading']");
|
|
1990
2010
|
let i = 0, n = null;
|
|
1991
|
-
for (const r of
|
|
2011
|
+
for (const r of t) {
|
|
1992
2012
|
if (g(r)) continue;
|
|
1993
2013
|
let o;
|
|
1994
2014
|
r.hasAttribute("aria-level") ? o = parseInt(r.getAttribute("aria-level"), 10) : o = parseInt(r.tagName[1], 10), i > 0 && o > i + 1 && a.push({
|
|
@@ -2010,18 +2030,18 @@ const $t = {
|
|
|
2010
2030
|
description: "Page should have exactly one main landmark.",
|
|
2011
2031
|
guidance: "The main landmark contains the primary content of the page. Screen readers allow users to jump directly to main content. Use a single <main> element (or role='main') to wrap the central content, excluding headers, footers, and navigation.",
|
|
2012
2032
|
prompt: "Identify the primary content area and explain how to wrap it in a <main> element.",
|
|
2013
|
-
run(
|
|
2014
|
-
const a =
|
|
2033
|
+
run(e) {
|
|
2034
|
+
const a = e.querySelectorAll('main, [role="main"]');
|
|
2015
2035
|
return a.length === 0 ? [{
|
|
2016
2036
|
ruleId: "landmark-one-main",
|
|
2017
2037
|
selector: "html",
|
|
2018
2038
|
html: "<html>",
|
|
2019
2039
|
impact: "moderate",
|
|
2020
2040
|
message: "Page has no main landmark."
|
|
2021
|
-
}] : a.length > 1 ? Array.from(a).slice(1).map((
|
|
2041
|
+
}] : a.length > 1 ? Array.from(a).slice(1).map((t) => ({
|
|
2022
2042
|
ruleId: "landmark-one-main",
|
|
2023
|
-
selector: m(
|
|
2024
|
-
html: d(
|
|
2043
|
+
selector: m(t),
|
|
2044
|
+
html: d(t),
|
|
2025
2045
|
impact: "moderate",
|
|
2026
2046
|
message: "Page has multiple main landmarks."
|
|
2027
2047
|
})) : [];
|
|
@@ -2034,8 +2054,8 @@ const $t = {
|
|
|
2034
2054
|
description: "Page should not have more than one banner landmark.",
|
|
2035
2055
|
guidance: "The banner landmark (typically <header>) identifies site-oriented content like logos and search. Only one top-level banner is allowed per page. If you need multiple headers, nest them inside sectioning elements (article, section, aside) where they become scoped headers rather than page-level banners.",
|
|
2036
2056
|
prompt: "Explain whether to remove this duplicate banner or nest it inside a sectioning element.",
|
|
2037
|
-
run(
|
|
2038
|
-
const a = [],
|
|
2057
|
+
run(e) {
|
|
2058
|
+
const a = [], t = e.querySelectorAll('header, [role="banner"]'), i = Array.from(t).filter((n) => !n.closest($));
|
|
2039
2059
|
return i.length > 1 && i.slice(1).forEach(
|
|
2040
2060
|
(n) => a.push({
|
|
2041
2061
|
ruleId: "landmark-no-duplicate-banner",
|
|
@@ -2046,7 +2066,7 @@ const $t = {
|
|
|
2046
2066
|
})
|
|
2047
2067
|
), a;
|
|
2048
2068
|
}
|
|
2049
|
-
},
|
|
2069
|
+
}, _t = {
|
|
2050
2070
|
id: "landmark-no-duplicate-contentinfo",
|
|
2051
2071
|
wcag: [],
|
|
2052
2072
|
level: "A",
|
|
@@ -2054,8 +2074,8 @@ const $t = {
|
|
|
2054
2074
|
description: "Page should not have more than one contentinfo landmark.",
|
|
2055
2075
|
guidance: "The contentinfo landmark (typically <footer>) contains information about the page like copyright and contact info. Only one top-level contentinfo is allowed per page. Nest additional footers inside sectioning elements to scope them.",
|
|
2056
2076
|
prompt: "Explain whether to remove this duplicate footer or nest it inside a sectioning element.",
|
|
2057
|
-
run(
|
|
2058
|
-
const a = [],
|
|
2077
|
+
run(e) {
|
|
2078
|
+
const a = [], t = e.querySelectorAll('footer, [role="contentinfo"]'), i = Array.from(t).filter((n) => !n.closest($));
|
|
2059
2079
|
return i.length > 1 && i.slice(1).forEach(
|
|
2060
2080
|
(n) => a.push({
|
|
2061
2081
|
ruleId: "landmark-no-duplicate-contentinfo",
|
|
@@ -2066,7 +2086,7 @@ const $t = {
|
|
|
2066
2086
|
})
|
|
2067
2087
|
), a;
|
|
2068
2088
|
}
|
|
2069
|
-
},
|
|
2089
|
+
}, Pt = {
|
|
2070
2090
|
id: "landmark-no-duplicate-main",
|
|
2071
2091
|
wcag: [],
|
|
2072
2092
|
level: "A",
|
|
@@ -2074,9 +2094,9 @@ const $t = {
|
|
|
2074
2094
|
description: "Page should not have more than one main landmark.",
|
|
2075
2095
|
guidance: "Only one main landmark should exist per page. The main landmark identifies the primary content area. If you have multiple content sections, use <section> with appropriate headings instead of multiple main elements.",
|
|
2076
2096
|
prompt: "Explain which main landmark to keep and how to restructure the duplicate.",
|
|
2077
|
-
run(
|
|
2078
|
-
const a = [],
|
|
2079
|
-
return
|
|
2097
|
+
run(e) {
|
|
2098
|
+
const a = [], t = e.querySelectorAll('main, [role="main"]');
|
|
2099
|
+
return t.length > 1 && Array.from(t).slice(1).forEach(
|
|
2080
2100
|
(i) => a.push({
|
|
2081
2101
|
ruleId: "landmark-no-duplicate-main",
|
|
2082
2102
|
selector: m(i),
|
|
@@ -2086,7 +2106,7 @@ const $t = {
|
|
|
2086
2106
|
})
|
|
2087
2107
|
), a;
|
|
2088
2108
|
}
|
|
2089
|
-
},
|
|
2109
|
+
}, jt = {
|
|
2090
2110
|
id: "landmark-banner-is-top-level",
|
|
2091
2111
|
wcag: [],
|
|
2092
2112
|
level: "A",
|
|
@@ -2094,9 +2114,9 @@ const $t = {
|
|
|
2094
2114
|
description: "Banner landmark should not be nested within another landmark.",
|
|
2095
2115
|
guidance: "The banner landmark should be a top-level landmark, not nested inside article, aside, main, nav, or section. If a header is inside these elements, it automatically becomes a generic header rather than a banner. Remove explicit role='banner' from nested headers or restructure the page.",
|
|
2096
2116
|
prompt: "Explain why this banner is incorrectly nested and how to fix it.",
|
|
2097
|
-
run(
|
|
2098
|
-
const a = [],
|
|
2099
|
-
for (const i of
|
|
2117
|
+
run(e) {
|
|
2118
|
+
const a = [], t = e.querySelectorAll('[role="banner"]');
|
|
2119
|
+
for (const i of t)
|
|
2100
2120
|
i.closest($) && a.push({
|
|
2101
2121
|
ruleId: "landmark-banner-is-top-level",
|
|
2102
2122
|
selector: m(i),
|
|
@@ -2106,7 +2126,7 @@ const $t = {
|
|
|
2106
2126
|
});
|
|
2107
2127
|
return a;
|
|
2108
2128
|
}
|
|
2109
|
-
},
|
|
2129
|
+
}, Vt = {
|
|
2110
2130
|
id: "landmark-contentinfo-is-top-level",
|
|
2111
2131
|
wcag: [],
|
|
2112
2132
|
level: "A",
|
|
@@ -2114,9 +2134,9 @@ const $t = {
|
|
|
2114
2134
|
description: "Contentinfo landmark should not be nested within another landmark.",
|
|
2115
2135
|
guidance: "The contentinfo landmark should be a top-level landmark. A footer inside article, aside, main, nav, or section becomes a scoped footer, not a contentinfo landmark. Remove explicit role='contentinfo' from nested footers or move the footer outside sectioning elements.",
|
|
2116
2136
|
prompt: "Explain why this contentinfo is incorrectly nested and how to fix it.",
|
|
2117
|
-
run(
|
|
2118
|
-
const a = [],
|
|
2119
|
-
for (const i of
|
|
2137
|
+
run(e) {
|
|
2138
|
+
const a = [], t = e.querySelectorAll('[role="contentinfo"]');
|
|
2139
|
+
for (const i of t)
|
|
2120
2140
|
i.closest($) && a.push({
|
|
2121
2141
|
ruleId: "landmark-contentinfo-is-top-level",
|
|
2122
2142
|
selector: m(i),
|
|
@@ -2126,7 +2146,7 @@ const $t = {
|
|
|
2126
2146
|
});
|
|
2127
2147
|
return a;
|
|
2128
2148
|
}
|
|
2129
|
-
},
|
|
2149
|
+
}, zt = {
|
|
2130
2150
|
id: "landmark-main-is-top-level",
|
|
2131
2151
|
wcag: [],
|
|
2132
2152
|
level: "A",
|
|
@@ -2134,9 +2154,9 @@ const $t = {
|
|
|
2134
2154
|
description: "Main landmark should not be nested within another landmark.",
|
|
2135
2155
|
guidance: "The main landmark must be a top-level landmark since it represents the primary content of the page. Do not nest <main> or role='main' inside article, aside, nav, or section elements.",
|
|
2136
2156
|
prompt: "Explain why the main landmark must be top-level and where to move it.",
|
|
2137
|
-
run(
|
|
2138
|
-
const a = [],
|
|
2139
|
-
for (const i of
|
|
2157
|
+
run(e) {
|
|
2158
|
+
const a = [], t = e.querySelectorAll('main, [role="main"]');
|
|
2159
|
+
for (const i of t) {
|
|
2140
2160
|
const n = i.parentElement;
|
|
2141
2161
|
n != null && n.closest('article, aside, nav, section, [role="article"], [role="complementary"], [role="navigation"], [role="region"]') && a.push({
|
|
2142
2162
|
ruleId: "landmark-main-is-top-level",
|
|
@@ -2148,7 +2168,7 @@ const $t = {
|
|
|
2148
2168
|
}
|
|
2149
2169
|
return a;
|
|
2150
2170
|
}
|
|
2151
|
-
},
|
|
2171
|
+
}, Ut = {
|
|
2152
2172
|
id: "landmark-complementary-is-top-level",
|
|
2153
2173
|
wcag: [],
|
|
2154
2174
|
level: "A",
|
|
@@ -2156,9 +2176,9 @@ const $t = {
|
|
|
2156
2176
|
description: "Aside (complementary) landmark should be top-level or directly inside main.",
|
|
2157
2177
|
guidance: "The complementary landmark (aside) should be top-level or a direct child of main. Nesting aside deep within other landmarks reduces its discoverability for screen reader users navigating by landmarks.",
|
|
2158
2178
|
prompt: "Explain why this aside should be repositioned and suggest where to move it.",
|
|
2159
|
-
run(
|
|
2160
|
-
const a = [],
|
|
2161
|
-
for (const i of
|
|
2179
|
+
run(e) {
|
|
2180
|
+
const a = [], t = e.querySelectorAll('aside, [role="complementary"]');
|
|
2181
|
+
for (const i of t) {
|
|
2162
2182
|
const n = i.parentElement;
|
|
2163
2183
|
n && !n.matches('body, main, [role="main"]') && i.closest('article, nav, section, [role="article"], [role="navigation"], [role="region"]') && a.push({
|
|
2164
2184
|
ruleId: "landmark-complementary-is-top-level",
|
|
@@ -2170,7 +2190,7 @@ const $t = {
|
|
|
2170
2190
|
}
|
|
2171
2191
|
return a;
|
|
2172
2192
|
}
|
|
2173
|
-
},
|
|
2193
|
+
}, Gt = {
|
|
2174
2194
|
id: "landmark-unique",
|
|
2175
2195
|
wcag: [],
|
|
2176
2196
|
level: "A",
|
|
@@ -2178,35 +2198,35 @@ const $t = {
|
|
|
2178
2198
|
description: "Landmarks should have unique labels when there are multiple of the same type.",
|
|
2179
2199
|
guidance: "When a page has multiple landmarks of the same type (e.g., multiple nav elements), each should have a unique accessible name via aria-label or aria-labelledby. This helps screen reader users distinguish between them (e.g., 'Main navigation' vs 'Footer navigation').",
|
|
2180
2200
|
prompt: "Suggest a unique aria-label that distinguishes this landmark based on its purpose.",
|
|
2181
|
-
run(
|
|
2182
|
-
const a = [],
|
|
2201
|
+
run(e) {
|
|
2202
|
+
const a = [], t = [
|
|
2183
2203
|
{ selector: 'nav, [role="navigation"]', type: "navigation" },
|
|
2184
2204
|
{ selector: 'aside, [role="complementary"]', type: "complementary" },
|
|
2185
2205
|
{ selector: 'section[aria-label], section[aria-labelledby], [role="region"]', type: "region" },
|
|
2186
2206
|
{ selector: 'form[aria-label], form[aria-labelledby], [role="form"], [role="search"]', type: "form" }
|
|
2187
2207
|
];
|
|
2188
|
-
for (const { selector: i, type: n } of
|
|
2189
|
-
const r = Array.from(
|
|
2208
|
+
for (const { selector: i, type: n } of t) {
|
|
2209
|
+
const r = Array.from(e.querySelectorAll(i)).filter((s) => !g(s));
|
|
2190
2210
|
if (r.length <= 1) continue;
|
|
2191
2211
|
const o = /* @__PURE__ */ new Map();
|
|
2192
2212
|
for (const s of r) {
|
|
2193
|
-
const l = v(s).toLowerCase() || "",
|
|
2194
|
-
|
|
2213
|
+
const l = v(s).toLowerCase() || "", p = o.get(l) || [];
|
|
2214
|
+
p.push(s), o.set(l, p);
|
|
2195
2215
|
}
|
|
2196
2216
|
for (const [s, l] of o)
|
|
2197
2217
|
if (l.length > 1)
|
|
2198
|
-
for (const
|
|
2218
|
+
for (const p of l.slice(1))
|
|
2199
2219
|
a.push({
|
|
2200
2220
|
ruleId: "landmark-unique",
|
|
2201
|
-
selector: m(
|
|
2202
|
-
html: d(
|
|
2221
|
+
selector: m(p),
|
|
2222
|
+
html: d(p),
|
|
2203
2223
|
impact: "moderate",
|
|
2204
2224
|
message: s ? `Multiple ${n} landmarks have the same label "${s}".` : `Multiple ${n} landmarks have no label. Add unique aria-label attributes.`
|
|
2205
2225
|
});
|
|
2206
2226
|
}
|
|
2207
2227
|
return a;
|
|
2208
2228
|
}
|
|
2209
|
-
},
|
|
2229
|
+
}, Yt = {
|
|
2210
2230
|
id: "region",
|
|
2211
2231
|
wcag: [],
|
|
2212
2232
|
level: "A",
|
|
@@ -2214,11 +2234,11 @@ const $t = {
|
|
|
2214
2234
|
description: "All page content should be contained within landmarks.",
|
|
2215
2235
|
guidance: "Screen reader users navigate pages by landmarks. Content outside landmarks is harder to find and understand. Wrap all visible content in appropriate landmarks: <header>, <nav>, <main>, <aside>, <footer>, or <section> with a label. Skip links may exist outside landmarks.",
|
|
2216
2236
|
prompt: "Based on the content, suggest which landmark element would be most appropriate.",
|
|
2217
|
-
run(
|
|
2237
|
+
run(e) {
|
|
2218
2238
|
var i;
|
|
2219
|
-
const a = [],
|
|
2220
|
-
if (!
|
|
2221
|
-
for (const n of
|
|
2239
|
+
const a = [], t = e.body;
|
|
2240
|
+
if (!t) return [];
|
|
2241
|
+
for (const n of t.children) {
|
|
2222
2242
|
if (g(n) || n instanceof HTMLScriptElement || n instanceof HTMLStyleElement || n.tagName === "NOSCRIPT" || n instanceof HTMLElement && n.hidden || n.matches('a[href^="#"]')) continue;
|
|
2223
2243
|
const r = n.matches(ae), o = (i = n.textContent) == null ? void 0 : i.trim();
|
|
2224
2244
|
!r && o && (n.querySelector(ae) || a.push({
|
|
@@ -2231,7 +2251,7 @@ const $t = {
|
|
|
2231
2251
|
}
|
|
2232
2252
|
return a;
|
|
2233
2253
|
}
|
|
2234
|
-
},
|
|
2254
|
+
}, Xt = {
|
|
2235
2255
|
id: "list",
|
|
2236
2256
|
selector: "ul, ol",
|
|
2237
2257
|
check: { type: "child-invalid", allowedChildren: ["li", "script", "template"], allowedChildRoles: ["listitem"] },
|
|
@@ -2242,26 +2262,26 @@ const $t = {
|
|
|
2242
2262
|
level: "A",
|
|
2243
2263
|
guidance: "Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, restructure your CSS to style the <li> elements directly.",
|
|
2244
2264
|
prompt: "Explain how to restructure this element within the list properly."
|
|
2245
|
-
},
|
|
2265
|
+
}, Kt = k(Xt), Jt = {
|
|
2246
2266
|
id: "dlitem",
|
|
2247
2267
|
wcag: ["1.3.1"],
|
|
2248
2268
|
level: "A",
|
|
2249
2269
|
description: "<dt> and <dd> elements must be contained in a <dl>.",
|
|
2250
2270
|
guidance: "Definition terms (<dt>) and definitions (<dd>) only have semantic meaning inside a definition list (<dl>). Outside of <dl>, they're treated as generic text. Wrap related <dt> and <dd> pairs in a <dl> element to convey the term/definition relationship to assistive technologies.",
|
|
2251
2271
|
prompt: "Explain how to properly structure this term/definition content.",
|
|
2252
|
-
run(
|
|
2272
|
+
run(e) {
|
|
2253
2273
|
const a = [];
|
|
2254
|
-
for (const
|
|
2255
|
-
(!
|
|
2274
|
+
for (const t of e.querySelectorAll("dt, dd"))
|
|
2275
|
+
(!t.parentElement || t.parentElement.tagName.toLowerCase() !== "dl") && a.push({
|
|
2256
2276
|
ruleId: "dlitem",
|
|
2257
|
-
selector: m(
|
|
2258
|
-
html: d(
|
|
2277
|
+
selector: m(t),
|
|
2278
|
+
html: d(t),
|
|
2259
2279
|
impact: "serious",
|
|
2260
|
-
message: `<${
|
|
2280
|
+
message: `<${t.tagName.toLowerCase()}> is not contained in a <dl>.`
|
|
2261
2281
|
});
|
|
2262
2282
|
return a;
|
|
2263
2283
|
}
|
|
2264
|
-
},
|
|
2284
|
+
}, Qt = {
|
|
2265
2285
|
id: "definition-list",
|
|
2266
2286
|
selector: "dl",
|
|
2267
2287
|
check: { type: "child-invalid", allowedChildren: ["dt", "dd", "div", "script", "template"] },
|
|
@@ -2272,22 +2292,22 @@ const $t = {
|
|
|
2272
2292
|
level: "A",
|
|
2273
2293
|
guidance: "Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or restructure using proper definition list markup.",
|
|
2274
2294
|
prompt: "Explain whether to move this element outside the <dl> or convert it to dt/dd."
|
|
2275
|
-
},
|
|
2295
|
+
}, Zt = k(Qt), ea = {
|
|
2276
2296
|
id: "listitem",
|
|
2277
2297
|
wcag: ["1.3.1"],
|
|
2278
2298
|
level: "A",
|
|
2279
2299
|
description: "<li> elements must be contained in a <ul>, <ol>, or <menu>.",
|
|
2280
2300
|
guidance: "List items (<li>) only have semantic meaning inside a list container (<ul>, <ol>, or <menu>). Outside of these containers, assistive technologies cannot convey the list relationship. Wrap <li> elements in the appropriate list container.",
|
|
2281
2301
|
prompt: "Explain that this <li> must be placed inside a <ul>, <ol>, or <menu> element.",
|
|
2282
|
-
run(
|
|
2283
|
-
var
|
|
2302
|
+
run(e) {
|
|
2303
|
+
var t;
|
|
2284
2304
|
const a = [];
|
|
2285
|
-
for (const i of
|
|
2305
|
+
for (const i of e.querySelectorAll("li")) {
|
|
2286
2306
|
if (g(i)) continue;
|
|
2287
2307
|
const n = i.parentElement;
|
|
2288
2308
|
if (!n) continue;
|
|
2289
2309
|
const r = n.tagName.toLowerCase();
|
|
2290
|
-
r === "ul" || r === "ol" || r === "menu" || ((
|
|
2310
|
+
r === "ul" || r === "ol" || r === "menu" || ((t = n.getAttribute("role")) == null ? void 0 : t.trim().toLowerCase()) === "list" || a.push({
|
|
2291
2311
|
ruleId: "listitem",
|
|
2292
2312
|
selector: m(i),
|
|
2293
2313
|
html: d(i),
|
|
@@ -2297,23 +2317,23 @@ const $t = {
|
|
|
2297
2317
|
}
|
|
2298
2318
|
return a;
|
|
2299
2319
|
}
|
|
2300
|
-
},
|
|
2320
|
+
}, ta = {
|
|
2301
2321
|
id: "document-title",
|
|
2302
2322
|
wcag: ["2.4.2"],
|
|
2303
2323
|
level: "A",
|
|
2304
2324
|
description: "Documents must have a <title> element to provide users with an overview of content.",
|
|
2305
2325
|
guidance: "Screen reader users rely on page titles to identify and navigate between tabs/windows. Add a descriptive <title> element in <head> that summarizes the page purpose. Keep titles unique across the site, placing specific content before the site name (e.g., 'Contact Us - Acme Corp').",
|
|
2306
2326
|
prompt: "The page has no title or an empty title. Suggest a concise, descriptive <title> based on the page content sample in context. Good titles are specific and front-load the unique part: 'Product Details - Store Name' rather than 'Store Name - Product Details'.",
|
|
2307
|
-
run(
|
|
2308
|
-
var
|
|
2309
|
-
const a =
|
|
2310
|
-
if (!a || !((
|
|
2327
|
+
run(e) {
|
|
2328
|
+
var t, i, n;
|
|
2329
|
+
const a = e.querySelector("title");
|
|
2330
|
+
if (!a || !((t = a.textContent) != null && t.trim())) {
|
|
2311
2331
|
let r;
|
|
2312
|
-
const o =
|
|
2332
|
+
const o = e.querySelector("h1");
|
|
2313
2333
|
if ((i = o == null ? void 0 : o.textContent) != null && i.trim())
|
|
2314
2334
|
r = `h1: "${o.textContent.trim().slice(0, 100)}"`;
|
|
2315
|
-
else if (
|
|
2316
|
-
const s = ((n =
|
|
2335
|
+
else if (e.body) {
|
|
2336
|
+
const s = ((n = e.body.textContent) == null ? void 0 : n.trim().replace(/\s+/g, " ")) || "";
|
|
2317
2337
|
s && (r = `Page text: "${s.slice(0, 150)}"`);
|
|
2318
2338
|
}
|
|
2319
2339
|
return [{
|
|
@@ -2327,7 +2347,7 @@ const $t = {
|
|
|
2327
2347
|
}
|
|
2328
2348
|
return [];
|
|
2329
2349
|
}
|
|
2330
|
-
},
|
|
2350
|
+
}, aa = {
|
|
2331
2351
|
id: "bypass",
|
|
2332
2352
|
wcag: [],
|
|
2333
2353
|
level: "A",
|
|
@@ -2335,19 +2355,19 @@ const $t = {
|
|
|
2335
2355
|
description: "Page must have a mechanism to bypass repeated blocks of content.",
|
|
2336
2356
|
guidance: 'Keyboard users must be able to skip repetitive content like navigation. Provide a skip link at the top of the page that links to the main content (e.g., <a href="#main">Skip to main content</a>), or use a <main> landmark. Screen readers can jump directly to landmarks, so a properly marked-up <main> element satisfies this requirement.',
|
|
2337
2357
|
prompt: 'The page has no mechanism for keyboard users to skip repeated content. The simplest fix is to wrap the primary content area in a <main> element — screen readers can jump directly to it. Alternatively, add a skip link as the first element in <body>: <a href="#main" class="skip-link">Skip to main content</a>, with a matching id on the target element. Use the context to understand what the page is missing.',
|
|
2338
|
-
run(
|
|
2339
|
-
if (
|
|
2358
|
+
run(e) {
|
|
2359
|
+
if (e.querySelector(
|
|
2340
2360
|
'main, [role="main"], nav, [role="navigation"], aside, [role="complementary"], header, [role="banner"], footer, [role="contentinfo"], [role="search"], [role="region"]'
|
|
2341
2361
|
)) return [];
|
|
2342
|
-
const
|
|
2343
|
-
if (
|
|
2344
|
-
const r =
|
|
2362
|
+
const t = e.querySelector('a[href^="#"]');
|
|
2363
|
+
if (t) {
|
|
2364
|
+
const r = t.getAttribute("href");
|
|
2345
2365
|
if (r && r.length > 1) {
|
|
2346
2366
|
const o = r.slice(1);
|
|
2347
|
-
if (
|
|
2367
|
+
if (e.getElementById(o)) return [];
|
|
2348
2368
|
}
|
|
2349
2369
|
}
|
|
2350
|
-
if (
|
|
2370
|
+
if (e.querySelector("h1, h2, h3, [role='heading']")) return [];
|
|
2351
2371
|
const n = [];
|
|
2352
2372
|
return n.push("no landmarks (<main>, <nav>, <header>, <footer>)"), n.push("no skip link"), n.push("no headings"), [{
|
|
2353
2373
|
ruleId: "bypass",
|
|
@@ -2358,7 +2378,7 @@ const $t = {
|
|
|
2358
2378
|
context: `Missing: ${n.join(", ")}`
|
|
2359
2379
|
}];
|
|
2360
2380
|
}
|
|
2361
|
-
},
|
|
2381
|
+
}, ia = {
|
|
2362
2382
|
id: "page-has-heading-one",
|
|
2363
2383
|
wcag: [],
|
|
2364
2384
|
level: "A",
|
|
@@ -2366,19 +2386,19 @@ const $t = {
|
|
|
2366
2386
|
description: "Page should contain a level-one heading.",
|
|
2367
2387
|
guidance: "A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have exactly one h1 that describes the main content, typically matching or similar to the page title.",
|
|
2368
2388
|
prompt: "The page has no <h1> heading. Suggest appropriate h1 text based on the page title or content sample in context. The h1 should describe the page's main topic and typically be placed at the start of the main content area.",
|
|
2369
|
-
run(
|
|
2389
|
+
run(e) {
|
|
2370
2390
|
var o, s, l;
|
|
2371
|
-
const a =
|
|
2391
|
+
const a = e.querySelector("h1");
|
|
2372
2392
|
if (a && v(a)) return [];
|
|
2373
|
-
const
|
|
2374
|
-
for (const
|
|
2375
|
-
if (v(
|
|
2376
|
-
const i = [], n = (s = (o =
|
|
2393
|
+
const t = e.querySelectorAll('[role="heading"][aria-level="1"]');
|
|
2394
|
+
for (const p of t)
|
|
2395
|
+
if (v(p)) return [];
|
|
2396
|
+
const i = [], n = (s = (o = e.querySelector("title")) == null ? void 0 : o.textContent) == null ? void 0 : s.trim();
|
|
2377
2397
|
n && i.push(`Page title: "${n}"`);
|
|
2378
|
-
const r =
|
|
2398
|
+
const r = e.querySelector("main");
|
|
2379
2399
|
if (r) {
|
|
2380
|
-
const
|
|
2381
|
-
|
|
2400
|
+
const p = ((l = r.textContent) == null ? void 0 : l.trim().replace(/\s+/g, " ")) || "";
|
|
2401
|
+
p && i.push(`Main content: "${p.slice(0, 100)}"`);
|
|
2382
2402
|
}
|
|
2383
2403
|
return [{
|
|
2384
2404
|
ruleId: "page-has-heading-one",
|
|
@@ -2390,29 +2410,29 @@ const $t = {
|
|
|
2390
2410
|
}];
|
|
2391
2411
|
}
|
|
2392
2412
|
};
|
|
2393
|
-
function
|
|
2394
|
-
if (!(
|
|
2395
|
-
if (
|
|
2396
|
-
const a =
|
|
2397
|
-
return (a === "0" || a === "1") && (
|
|
2413
|
+
function be(e) {
|
|
2414
|
+
if (!(e instanceof HTMLElement)) return !1;
|
|
2415
|
+
if (e.style.display === "none" || e.style.visibility === "hidden") return !0;
|
|
2416
|
+
const a = e.getAttribute("width"), t = e.getAttribute("height");
|
|
2417
|
+
return (a === "0" || a === "1") && (t === "0" || t === "1");
|
|
2398
2418
|
}
|
|
2399
|
-
const
|
|
2419
|
+
const na = {
|
|
2400
2420
|
id: "frame-title",
|
|
2401
2421
|
wcag: ["4.1.2"],
|
|
2402
2422
|
level: "A",
|
|
2403
2423
|
description: "Frames must have an accessible name.",
|
|
2404
2424
|
guidance: "Screen readers announce frame titles when users navigate frames. Add a title attribute to <iframe> and <frame> elements that describes the frame's purpose (e.g., <iframe title='Video player'>). Avoid generic titles like 'frame' or 'iframe'. If the frame is decorative, use aria-hidden='true'.",
|
|
2405
2425
|
prompt: "This iframe has no accessible name. Based on the src URL in context, suggest a descriptive title attribute that tells screen reader users what the frame contains. For example: 'YouTube video player', 'Google Map', 'Payment form', 'Chat widget'. If the frame appears decorative or non-essential, recommend adding aria-hidden='true' instead.",
|
|
2406
|
-
run(
|
|
2426
|
+
run(e) {
|
|
2407
2427
|
const a = [];
|
|
2408
|
-
for (const
|
|
2409
|
-
if (g(
|
|
2410
|
-
if (!v(
|
|
2411
|
-
const n =
|
|
2428
|
+
for (const t of e.querySelectorAll("iframe, frame")) {
|
|
2429
|
+
if (g(t) || be(t)) continue;
|
|
2430
|
+
if (!v(t)) {
|
|
2431
|
+
const n = t.getAttribute("src");
|
|
2412
2432
|
a.push({
|
|
2413
2433
|
ruleId: "frame-title",
|
|
2414
|
-
selector: m(
|
|
2415
|
-
html: d(
|
|
2434
|
+
selector: m(t),
|
|
2435
|
+
html: d(t),
|
|
2416
2436
|
impact: "serious",
|
|
2417
2437
|
message: "Frame is missing an accessible name. Add a title attribute.",
|
|
2418
2438
|
context: n ? `src: "${n}"` : void 0
|
|
@@ -2421,7 +2441,7 @@ const aa = {
|
|
|
2421
2441
|
}
|
|
2422
2442
|
return a;
|
|
2423
2443
|
}
|
|
2424
|
-
},
|
|
2444
|
+
}, ra = {
|
|
2425
2445
|
id: "frame-title-unique",
|
|
2426
2446
|
wcag: ["4.1.2"],
|
|
2427
2447
|
level: "A",
|
|
@@ -2429,11 +2449,11 @@ const aa = {
|
|
|
2429
2449
|
description: "Frame titles should be unique.",
|
|
2430
2450
|
guidance: "When multiple frames have identical titles, screen reader users cannot distinguish between them. Give each frame a unique, descriptive title that explains its specific purpose or content.",
|
|
2431
2451
|
prompt: "Suggest a more specific title to distinguish this frame from others.",
|
|
2432
|
-
run(
|
|
2452
|
+
run(e) {
|
|
2433
2453
|
var n;
|
|
2434
|
-
const a = [],
|
|
2435
|
-
for (const r of
|
|
2436
|
-
if (g(r) ||
|
|
2454
|
+
const a = [], t = Array.from(e.querySelectorAll("iframe[title], frame[title]")), i = /* @__PURE__ */ new Map();
|
|
2455
|
+
for (const r of t) {
|
|
2456
|
+
if (g(r) || be(r)) continue;
|
|
2437
2457
|
const o = (n = r.getAttribute("title")) == null ? void 0 : n.trim().toLowerCase();
|
|
2438
2458
|
if (o) {
|
|
2439
2459
|
const s = i.get(o) || [];
|
|
@@ -2452,7 +2472,7 @@ const aa = {
|
|
|
2452
2472
|
});
|
|
2453
2473
|
return a;
|
|
2454
2474
|
}
|
|
2455
|
-
},
|
|
2475
|
+
}, oa = {
|
|
2456
2476
|
id: "empty-heading",
|
|
2457
2477
|
wcag: [],
|
|
2458
2478
|
level: "A",
|
|
@@ -2460,10 +2480,10 @@ const aa = {
|
|
|
2460
2480
|
description: "Headings must have discernible text.",
|
|
2461
2481
|
guidance: "Screen reader users navigate pages by headings, so empty headings create confusing navigation points. Ensure all headings contain visible text or accessible names. If a heading is used purely for visual styling, use CSS instead of heading elements.",
|
|
2462
2482
|
prompt: "This heading element has no text content, so screen reader users encounter a blank heading when navigating. Either add descriptive text that summarizes the following section, or if this element is used only for visual styling, replace it with a styled <p> or <div> and use CSS for appearance. The context includes nearby content to help suggest appropriate heading text.",
|
|
2463
|
-
run(
|
|
2483
|
+
run(e) {
|
|
2464
2484
|
var i;
|
|
2465
|
-
const a = [],
|
|
2466
|
-
for (const n of
|
|
2485
|
+
const a = [], t = e.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');
|
|
2486
|
+
for (const n of t)
|
|
2467
2487
|
if (!g(n) && !v(n)) {
|
|
2468
2488
|
let r;
|
|
2469
2489
|
const o = n.nextElementSibling;
|
|
@@ -2482,52 +2502,55 @@ const aa = {
|
|
|
2482
2502
|
}
|
|
2483
2503
|
return a;
|
|
2484
2504
|
}
|
|
2485
|
-
},
|
|
2505
|
+
}, sa = {
|
|
2486
2506
|
id: "meta-viewport",
|
|
2487
2507
|
wcag: ["1.4.4"],
|
|
2488
2508
|
level: "AA",
|
|
2489
2509
|
description: "Viewport meta tag must not disable user scaling.",
|
|
2490
2510
|
guidance: "Users with low vision need to zoom content up to 200% or more. Setting user-scalable=no or maximum-scale=1 prevents zooming and fails WCAG. Remove these restrictions. If your layout breaks at high zoom, fix the responsive design rather than preventing zoom.",
|
|
2491
2511
|
prompt: "The viewport meta tag restricts zooming, which prevents low-vision users from enlarging content. Show the current content attribute and a corrected version with the problematic properties removed. Keep other viewport properties (like width=device-width, initial-scale=1) intact — only remove user-scalable=no and maximum-scale restrictions.",
|
|
2492
|
-
run(
|
|
2493
|
-
const a = [],
|
|
2494
|
-
if (!
|
|
2495
|
-
const i =
|
|
2496
|
-
(/user-scalable\s*=\s*no/i.test(n) || /user-scalable\s*=\s*0/i.test(n)) && a.push({
|
|
2497
|
-
ruleId: "meta-viewport",
|
|
2498
|
-
selector: m(e),
|
|
2499
|
-
html: d(e),
|
|
2500
|
-
impact: "critical",
|
|
2501
|
-
message: "Viewport disables user scaling. Remove user-scalable=no.",
|
|
2502
|
-
context: `content: "${i}"`
|
|
2503
|
-
});
|
|
2504
|
-
const r = n.match(/maximum-scale\s*=\s*([\d.]+|yes)/i);
|
|
2512
|
+
run(e) {
|
|
2513
|
+
const a = [], t = e.querySelector('meta[name="viewport"]');
|
|
2514
|
+
if (!t) return [];
|
|
2515
|
+
const i = t.getAttribute("content") || "", n = i.toLowerCase(), r = n.match(/user-scalable\s*=\s*([^\s,;]+)/i);
|
|
2505
2516
|
if (r) {
|
|
2506
|
-
const
|
|
2507
|
-
s <
|
|
2517
|
+
const s = r[1], l = parseFloat(s);
|
|
2518
|
+
(s === "no" || !isNaN(l) && l > -1 && l < 1) && a.push({
|
|
2519
|
+
ruleId: "meta-viewport",
|
|
2520
|
+
selector: m(t),
|
|
2521
|
+
html: d(t),
|
|
2522
|
+
impact: "critical",
|
|
2523
|
+
message: `Viewport disables user scaling (user-scalable=${s}). Remove this restriction.`,
|
|
2524
|
+
context: `content: "${i}"`
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
2527
|
+
const o = n.match(/maximum-scale\s*=\s*([\d.]+|yes)/i);
|
|
2528
|
+
if (o) {
|
|
2529
|
+
const s = o[1], l = s.toLowerCase() === "yes" ? 1 : parseFloat(s);
|
|
2530
|
+
l < 2 && a.push({
|
|
2508
2531
|
ruleId: "meta-viewport",
|
|
2509
|
-
selector: m(
|
|
2510
|
-
html: d(
|
|
2532
|
+
selector: m(t),
|
|
2533
|
+
html: d(t),
|
|
2511
2534
|
impact: "critical",
|
|
2512
|
-
message: `Viewport maximum-scale=${
|
|
2535
|
+
message: `Viewport maximum-scale=${l} restricts zooming. Set to at least 2 or remove.`,
|
|
2513
2536
|
context: `content: "${i}"`
|
|
2514
2537
|
});
|
|
2515
2538
|
}
|
|
2516
2539
|
return a;
|
|
2517
2540
|
}
|
|
2518
|
-
},
|
|
2541
|
+
}, la = {
|
|
2519
2542
|
id: "meta-refresh",
|
|
2520
2543
|
wcag: ["2.2.1", "2.2.4", "3.2.5"],
|
|
2521
2544
|
level: "A",
|
|
2522
2545
|
description: "Meta refresh must not redirect or refresh automatically.",
|
|
2523
2546
|
guidance: "Automatic page refreshes or redirects can disorient users, especially those using screen readers or with cognitive disabilities. They may lose their place or not have time to read content. If a redirect is needed, use a server-side redirect (HTTP 301/302) instead. For timed refreshes, provide user controls.",
|
|
2524
2547
|
prompt: "Explain why meta refresh is problematic and suggest server-side alternatives.",
|
|
2525
|
-
run(
|
|
2526
|
-
for (const a of
|
|
2527
|
-
const
|
|
2548
|
+
run(e) {
|
|
2549
|
+
for (const a of e.querySelectorAll('meta[http-equiv="refresh"]')) {
|
|
2550
|
+
const t = a.getAttribute("content") || "", i = t.match(/^(\d+)/);
|
|
2528
2551
|
if (!i) continue;
|
|
2529
2552
|
const n = parseInt(i[1], 10);
|
|
2530
|
-
if (/^\d+\s*[;,]\s*url\s*=/i.test(
|
|
2553
|
+
if (/^\d+\s*[;,]\s*url\s*=/i.test(t) || /^\d+\s*[;,]\s*['"]?\s*https?:/i.test(t))
|
|
2531
2554
|
return n > 0 && n <= 72e3 ? [{
|
|
2532
2555
|
ruleId: "meta-refresh",
|
|
2533
2556
|
selector: m(a),
|
|
@@ -2546,7 +2569,7 @@ const aa = {
|
|
|
2546
2569
|
}
|
|
2547
2570
|
return [];
|
|
2548
2571
|
}
|
|
2549
|
-
},
|
|
2572
|
+
}, ca = {
|
|
2550
2573
|
id: "blink",
|
|
2551
2574
|
selector: "blink",
|
|
2552
2575
|
check: { type: "selector-exists" },
|
|
@@ -2557,7 +2580,7 @@ const aa = {
|
|
|
2557
2580
|
level: "A",
|
|
2558
2581
|
guidance: "Blinking content can cause seizures in users with photosensitive epilepsy and is distracting for users with attention disorders. The <blink> element is deprecated and should never be used. If you need to draw attention to content, use less intrusive methods like color, borders, or icons.",
|
|
2559
2582
|
prompt: "Suggest static alternatives to the blinking effect."
|
|
2560
|
-
},
|
|
2583
|
+
}, ua = k(ca), da = {
|
|
2561
2584
|
id: "marquee",
|
|
2562
2585
|
selector: "marquee",
|
|
2563
2586
|
check: { type: "selector-exists" },
|
|
@@ -2568,7 +2591,7 @@ const aa = {
|
|
|
2568
2591
|
level: "A",
|
|
2569
2592
|
guidance: "Scrolling or moving content is difficult for many users to read, especially those with cognitive or visual disabilities. The <marquee> element is deprecated. Replace scrolling text with static content. If content must scroll, provide pause/stop controls and ensure it stops after 5 seconds.",
|
|
2570
2593
|
prompt: "Suggest static alternatives or accessible carousel patterns."
|
|
2571
|
-
},
|
|
2594
|
+
}, ma = k(da), ha = {
|
|
2572
2595
|
id: "p-as-heading",
|
|
2573
2596
|
wcag: [],
|
|
2574
2597
|
level: "A",
|
|
@@ -2576,15 +2599,15 @@ const aa = {
|
|
|
2576
2599
|
description: "Paragraphs should not be styled to look like headings.",
|
|
2577
2600
|
guidance: "When paragraphs are styled with bold, large fonts to look like headings, screen reader users miss the semantic structure. Use proper heading elements (h1-h6) instead of styled paragraphs. If you need specific styling, apply CSS to the heading elements while maintaining proper heading hierarchy.",
|
|
2578
2601
|
prompt: "Suggest the appropriate heading level based on the document structure.",
|
|
2579
|
-
run(
|
|
2580
|
-
var
|
|
2602
|
+
run(e) {
|
|
2603
|
+
var t, i;
|
|
2581
2604
|
const a = [];
|
|
2582
|
-
for (const n of
|
|
2605
|
+
for (const n of e.querySelectorAll("p")) {
|
|
2583
2606
|
if (g(n)) continue;
|
|
2584
|
-
const r = n.getAttribute("style") || "", o = /font-weight\s*:\s*(bold|[6-9]00)/i.test(r), s = /font-size\s*:\s*(\d+)\s*(px|em|rem)/i.test(r), l = ((
|
|
2585
|
-
if ((o && s || o &&
|
|
2586
|
-
const
|
|
2587
|
-
|
|
2607
|
+
const r = n.getAttribute("style") || "", o = /font-weight\s*:\s*(bold|[6-9]00)/i.test(r), s = /font-size\s*:\s*(\d+)\s*(px|em|rem)/i.test(r), l = ((t = n.className) == null ? void 0 : t.toLowerCase()) || "", p = /\bh[1-6]\b|\bheading\b/.test(l), c = ((i = n.textContent) == null ? void 0 : i.trim()) || "", u = c.length > 0 && c.length < 50, h = !c.match(/[.!?,;:]$/);
|
|
2608
|
+
if ((o && s || o && p) && u && h) {
|
|
2609
|
+
const w = n.nextElementSibling;
|
|
2610
|
+
w && (w.tagName === "P" || w.tagName === "DIV" || w.tagName === "UL") && a.push({
|
|
2588
2611
|
ruleId: "p-as-heading",
|
|
2589
2612
|
selector: m(n),
|
|
2590
2613
|
html: d(n),
|
|
@@ -2595,48 +2618,48 @@ const aa = {
|
|
|
2595
2618
|
}
|
|
2596
2619
|
return a;
|
|
2597
2620
|
}
|
|
2598
|
-
},
|
|
2621
|
+
}, pa = {
|
|
2599
2622
|
id: "aria-roles",
|
|
2600
2623
|
wcag: ["4.1.2"],
|
|
2601
2624
|
level: "A",
|
|
2602
2625
|
description: "ARIA role values must be valid.",
|
|
2603
2626
|
guidance: "Invalid role values are ignored by assistive technologies, meaning the element will not have the intended semantics. Check the spelling and use only roles defined in the WAI-ARIA specification. Common roles include: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.",
|
|
2604
2627
|
prompt: "Identify the invalid role and suggest the correct spelling or a valid alternative role that matches the intended purpose.",
|
|
2605
|
-
run(
|
|
2628
|
+
run(e) {
|
|
2606
2629
|
const a = [];
|
|
2607
|
-
for (const
|
|
2608
|
-
const r =
|
|
2609
|
-
!r.some((s) =>
|
|
2630
|
+
for (const t of e.querySelectorAll("[role]")) {
|
|
2631
|
+
const r = t.getAttribute("role").replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g, "").split(/\s+/).filter(Boolean);
|
|
2632
|
+
!r.some((s) => Ee(s)) && r.length > 0 && a.push({
|
|
2610
2633
|
ruleId: "aria-roles",
|
|
2611
|
-
selector: m(
|
|
2612
|
-
html: d(
|
|
2634
|
+
selector: m(t),
|
|
2635
|
+
html: d(t),
|
|
2613
2636
|
impact: "critical",
|
|
2614
2637
|
message: `Invalid ARIA role "${r[0]}".`
|
|
2615
2638
|
});
|
|
2616
2639
|
}
|
|
2617
2640
|
return a;
|
|
2618
2641
|
}
|
|
2619
|
-
},
|
|
2642
|
+
}, ga = {
|
|
2620
2643
|
id: "aria-valid-attr",
|
|
2621
2644
|
wcag: ["4.1.2"],
|
|
2622
2645
|
level: "A",
|
|
2623
2646
|
description: "ARIA attributes must be valid (correctly spelled).",
|
|
2624
2647
|
guidance: "Misspelled ARIA attributes are ignored by assistive technologies. Check the spelling against the WAI-ARIA specification. Common mistakes: aria-labeledby (should be aria-labelledby), aria-role (should be role), aria-description (valid in ARIA 1.3+).",
|
|
2625
2648
|
prompt: "Identify the misspelled attribute and provide the correct spelling.",
|
|
2626
|
-
run(
|
|
2627
|
-
return U(
|
|
2649
|
+
run(e) {
|
|
2650
|
+
return U(e).validAttr;
|
|
2628
2651
|
}
|
|
2629
|
-
},
|
|
2652
|
+
}, ba = {
|
|
2630
2653
|
id: "aria-valid-attr-value",
|
|
2631
2654
|
wcag: ["4.1.2"],
|
|
2632
2655
|
level: "A",
|
|
2633
2656
|
description: "ARIA attributes must have valid values.",
|
|
2634
2657
|
guidance: "Each ARIA attribute accepts specific value types. Boolean attributes (aria-hidden, aria-disabled) accept only 'true' or 'false'. Tristate attributes (aria-checked, aria-pressed) also accept 'mixed'. Token attributes (aria-live, aria-autocomplete) accept predefined values. ID reference attributes (aria-labelledby, aria-describedby) must reference existing element IDs.",
|
|
2635
2658
|
prompt: "Show the invalid value and list the valid values for this specific attribute.",
|
|
2636
|
-
run(
|
|
2637
|
-
return U(
|
|
2659
|
+
run(e) {
|
|
2660
|
+
return U(e).validAttrValue;
|
|
2638
2661
|
}
|
|
2639
|
-
},
|
|
2662
|
+
}, fa = {
|
|
2640
2663
|
checkbox: ["aria-checked"],
|
|
2641
2664
|
combobox: ["aria-expanded"],
|
|
2642
2665
|
heading: ["aria-level"],
|
|
@@ -2650,30 +2673,30 @@ const aa = {
|
|
|
2650
2673
|
slider: ["aria-valuenow"],
|
|
2651
2674
|
spinbutton: ["aria-valuenow"],
|
|
2652
2675
|
switch: ["aria-checked"]
|
|
2653
|
-
},
|
|
2676
|
+
}, va = {
|
|
2654
2677
|
id: "aria-required-attr",
|
|
2655
2678
|
wcag: ["4.1.2"],
|
|
2656
2679
|
level: "A",
|
|
2657
2680
|
description: "Elements with ARIA roles must have all required ARIA attributes.",
|
|
2658
2681
|
guidance: "Some ARIA roles require specific attributes to function correctly. For example, checkbox requires aria-checked, slider requires aria-valuenow, heading requires aria-level. Without these attributes, assistive technologies cannot convey the element's state or value to users. Add the missing required attribute with an appropriate value.",
|
|
2659
2682
|
prompt: "State which attribute is required for this role and suggest an appropriate value based on the element's apparent state.",
|
|
2660
|
-
run(
|
|
2683
|
+
run(e) {
|
|
2661
2684
|
const a = [];
|
|
2662
|
-
for (const
|
|
2663
|
-
if (g(
|
|
2664
|
-
const i =
|
|
2665
|
-
if (n && !(i === "checkbox" &&
|
|
2685
|
+
for (const t of e.querySelectorAll("[role]")) {
|
|
2686
|
+
if (g(t) || t instanceof HTMLElement && t.style.display === "none") continue;
|
|
2687
|
+
const i = t.getAttribute("role").trim().toLowerCase(), n = fa[i];
|
|
2688
|
+
if (n && !(i === "checkbox" && t instanceof HTMLInputElement && t.type === "checkbox") && !(i === "radio" && t instanceof HTMLInputElement && t.type === "radio") && !(i === "option" && t instanceof HTMLOptionElement) && !(i === "heading" && /^h[1-6]$/i.test(t.tagName))) {
|
|
2666
2689
|
if (i === "separator") {
|
|
2667
|
-
const r =
|
|
2690
|
+
const r = t.getAttribute("tabindex");
|
|
2668
2691
|
if (!r || r === "-1") continue;
|
|
2669
2692
|
}
|
|
2670
|
-
if (!(
|
|
2693
|
+
if (!(t.tagName.toLowerCase() === "hr" && !t.hasAttribute("role"))) {
|
|
2671
2694
|
for (const r of n)
|
|
2672
|
-
if (!
|
|
2695
|
+
if (!t.hasAttribute(r)) {
|
|
2673
2696
|
a.push({
|
|
2674
2697
|
ruleId: "aria-required-attr",
|
|
2675
|
-
selector: m(
|
|
2676
|
-
html: d(
|
|
2698
|
+
selector: m(t),
|
|
2699
|
+
html: d(t),
|
|
2677
2700
|
impact: "critical",
|
|
2678
2701
|
message: `Role "${i}" requires attribute "${r}".`
|
|
2679
2702
|
});
|
|
@@ -2685,16 +2708,16 @@ const aa = {
|
|
|
2685
2708
|
return a;
|
|
2686
2709
|
}
|
|
2687
2710
|
};
|
|
2688
|
-
function
|
|
2711
|
+
function wa(e) {
|
|
2689
2712
|
var r, o, s;
|
|
2690
|
-
const a = [],
|
|
2691
|
-
|
|
2692
|
-
const i =
|
|
2713
|
+
const a = [], t = e.className;
|
|
2714
|
+
t && typeof t == "string" && t.trim() && a.push(`Classes: ${t.trim().slice(0, 100)}`);
|
|
2715
|
+
const i = e.closest("form");
|
|
2693
2716
|
if (i) {
|
|
2694
2717
|
const l = i.getAttribute("aria-label") || ((o = (r = i.querySelector("legend")) == null ? void 0 : r.textContent) == null ? void 0 : o.trim());
|
|
2695
2718
|
l && a.push(`Form: ${l.slice(0, 60)}`);
|
|
2696
2719
|
}
|
|
2697
|
-
const n =
|
|
2720
|
+
const n = e.parentElement;
|
|
2698
2721
|
if (n) {
|
|
2699
2722
|
const l = n.closest("h1, h2, h3, h4, h5, h6") || n.querySelector("h1, h2, h3, h4, h5, h6");
|
|
2700
2723
|
(s = l == null ? void 0 : l.textContent) != null && s.trim() && a.push(`Nearby heading: ${l.textContent.trim().slice(0, 60)}`);
|
|
@@ -2702,31 +2725,31 @@ function fa(t) {
|
|
|
2702
2725
|
return a.length > 0 ? a.join(`
|
|
2703
2726
|
`) : void 0;
|
|
2704
2727
|
}
|
|
2705
|
-
const
|
|
2728
|
+
const ya = {
|
|
2706
2729
|
id: "button-name",
|
|
2707
2730
|
wcag: ["4.1.2"],
|
|
2708
2731
|
level: "A",
|
|
2709
2732
|
description: "Buttons must have discernible text.",
|
|
2710
2733
|
guidance: "Screen reader users need to know what a button does. Add visible text content, aria-label, or aria-labelledby. For icon buttons, use aria-label describing the action (e.g., aria-label='Close'). If the button contains an image, ensure the image has alt text describing the button's action.",
|
|
2711
2734
|
prompt: "Based on the button's content, class, or context, suggest an appropriate aria-label describing the action it performs.",
|
|
2712
|
-
run(
|
|
2735
|
+
run(e) {
|
|
2713
2736
|
const a = [];
|
|
2714
|
-
for (const
|
|
2715
|
-
if (g(
|
|
2716
|
-
const i =
|
|
2717
|
-
if ((i === "none" || i === "presentation") && !(
|
|
2718
|
-
v(
|
|
2737
|
+
for (const t of e.querySelectorAll('button, [role="button"]')) {
|
|
2738
|
+
if (g(t) || T(t)) continue;
|
|
2739
|
+
const i = t.getAttribute("role");
|
|
2740
|
+
if ((i === "none" || i === "presentation") && !(t.matches('button:not([disabled]), [tabindex]:not([tabindex="-1"])') || t.tagName.toLowerCase() === "button" && !t.disabled) || t.getRootNode() instanceof ShadowRoot) continue;
|
|
2741
|
+
v(t) || a.push({
|
|
2719
2742
|
ruleId: "button-name",
|
|
2720
|
-
selector: m(
|
|
2721
|
-
html: d(
|
|
2743
|
+
selector: m(t),
|
|
2744
|
+
html: d(t),
|
|
2722
2745
|
impact: "critical",
|
|
2723
2746
|
message: "Button has no discernible text.",
|
|
2724
|
-
context:
|
|
2747
|
+
context: wa(t)
|
|
2725
2748
|
});
|
|
2726
2749
|
}
|
|
2727
2750
|
return a;
|
|
2728
2751
|
}
|
|
2729
|
-
},
|
|
2752
|
+
}, Aa = {
|
|
2730
2753
|
alert: /* @__PURE__ */ new Set(["aria-atomic", "aria-busy", "aria-live", "aria-relevant"]),
|
|
2731
2754
|
alertdialog: /* @__PURE__ */ new Set(["aria-describedby", "aria-modal"]),
|
|
2732
2755
|
application: /* @__PURE__ */ new Set(["aria-activedescendant", "aria-disabled", "aria-errormessage", "aria-expanded", "aria-haspopup", "aria-invalid"]),
|
|
@@ -2798,7 +2821,7 @@ const va = {
|
|
|
2798
2821
|
tree: /* @__PURE__ */ new Set(["aria-activedescendant", "aria-disabled", "aria-errormessage", "aria-invalid", "aria-multiselectable", "aria-orientation", "aria-required"]),
|
|
2799
2822
|
treegrid: /* @__PURE__ */ new Set(["aria-activedescendant", "aria-colcount", "aria-disabled", "aria-errormessage", "aria-invalid", "aria-multiselectable", "aria-orientation", "aria-readonly", "aria-required", "aria-rowcount"]),
|
|
2800
2823
|
treeitem: /* @__PURE__ */ new Set(["aria-checked", "aria-disabled", "aria-expanded", "aria-haspopup", "aria-level", "aria-posinset", "aria-selected", "aria-setsize"])
|
|
2801
|
-
},
|
|
2824
|
+
}, xa = /* @__PURE__ */ new Set([
|
|
2802
2825
|
"aria-atomic",
|
|
2803
2826
|
"aria-busy",
|
|
2804
2827
|
"aria-controls",
|
|
@@ -2822,28 +2845,28 @@ const va = {
|
|
|
2822
2845
|
"aria-roledescription",
|
|
2823
2846
|
"aria-braillelabel",
|
|
2824
2847
|
"aria-brailleroledescription"
|
|
2825
|
-
]),
|
|
2848
|
+
]), Sa = {
|
|
2826
2849
|
id: "aria-allowed-attr",
|
|
2827
2850
|
wcag: ["4.1.2"],
|
|
2828
2851
|
level: "A",
|
|
2829
2852
|
description: "ARIA attributes must be allowed for the element's role.",
|
|
2830
2853
|
guidance: "Each ARIA role supports specific attributes. Using unsupported attributes creates confusion for assistive technologies. Check the ARIA specification for which attributes are valid for each role, or remove the attribute if it's not needed.",
|
|
2831
2854
|
prompt: "The ARIA attribute listed in context is not supported on this element's role. Either remove the attribute (if the behavior it describes isn't needed), or change the element's role to one that supports it. The context lists which attributes ARE allowed on this role — use that to suggest alternatives if applicable.",
|
|
2832
|
-
run(
|
|
2855
|
+
run(e) {
|
|
2833
2856
|
const a = [];
|
|
2834
|
-
for (const
|
|
2835
|
-
if (g(
|
|
2836
|
-
const i = q(
|
|
2857
|
+
for (const t of e.querySelectorAll("[role], [aria-*]")) {
|
|
2858
|
+
if (g(t)) continue;
|
|
2859
|
+
const i = q(t);
|
|
2837
2860
|
if (!i) continue;
|
|
2838
|
-
const n =
|
|
2861
|
+
const n = Aa[i];
|
|
2839
2862
|
if (n)
|
|
2840
|
-
for (const r of
|
|
2841
|
-
if (!r.name.startsWith("aria-") ||
|
|
2863
|
+
for (const r of t.attributes) {
|
|
2864
|
+
if (!r.name.startsWith("aria-") || xa.has(r.name) || n.has(r.name)) continue;
|
|
2842
2865
|
const o = n.size > 0 ? [...n].join(", ") : "none (only global ARIA attributes)";
|
|
2843
2866
|
a.push({
|
|
2844
2867
|
ruleId: "aria-allowed-attr",
|
|
2845
|
-
selector: m(
|
|
2846
|
-
html: d(
|
|
2868
|
+
selector: m(t),
|
|
2869
|
+
html: d(t),
|
|
2847
2870
|
impact: "critical",
|
|
2848
2871
|
message: `ARIA attribute "${r.name}" is not allowed on role "${i}".`,
|
|
2849
2872
|
context: `Attribute: ${r.name}="${r.value}", role: ${i}, allowed role-specific attributes: ${o}`
|
|
@@ -2852,7 +2875,7 @@ const va = {
|
|
|
2852
2875
|
}
|
|
2853
2876
|
return a;
|
|
2854
2877
|
}
|
|
2855
|
-
},
|
|
2878
|
+
}, ka = /* @__PURE__ */ new Set([
|
|
2856
2879
|
"base",
|
|
2857
2880
|
"col",
|
|
2858
2881
|
"colgroup",
|
|
@@ -2974,38 +2997,38 @@ const va = {
|
|
|
2974
2997
|
video: /* @__PURE__ */ new Set(["application"]),
|
|
2975
2998
|
wbr: /* @__PURE__ */ new Set(["none", "presentation"])
|
|
2976
2999
|
};
|
|
2977
|
-
function
|
|
2978
|
-
var
|
|
2979
|
-
const a =
|
|
2980
|
-
if (
|
|
3000
|
+
function Ia(e) {
|
|
3001
|
+
var t;
|
|
3002
|
+
const a = e.tagName.toLowerCase();
|
|
3003
|
+
if (ka.has(a))
|
|
2981
3004
|
return "none";
|
|
2982
|
-
if (a === "a" &&
|
|
3005
|
+
if (a === "a" && e.hasAttribute("href"))
|
|
2983
3006
|
return E["a[href]"];
|
|
2984
|
-
if (a === "img" &&
|
|
3007
|
+
if (a === "img" && e.getAttribute("alt") === "")
|
|
2985
3008
|
return E["img[alt='']"];
|
|
2986
3009
|
if (a === "input") {
|
|
2987
|
-
const n = `input[type=${((
|
|
3010
|
+
const n = `input[type=${((t = e.getAttribute("type")) == null ? void 0 : t.toLowerCase()) || "text"}]`;
|
|
2988
3011
|
return n in E ? E[n] : "none";
|
|
2989
3012
|
}
|
|
2990
3013
|
return E[a] || "any";
|
|
2991
3014
|
}
|
|
2992
|
-
const
|
|
3015
|
+
const Ta = {
|
|
2993
3016
|
id: "aria-allowed-role",
|
|
2994
3017
|
wcag: ["4.1.2"],
|
|
2995
3018
|
level: "A",
|
|
2996
3019
|
description: "ARIA role must be appropriate for the element.",
|
|
2997
3020
|
guidance: "Not all ARIA roles can be applied to all HTML elements. Many elements have implicit roles (e.g., <header> is implicitly banner, <nav> is navigation, <main> is main). Adding an explicit role that matches the implicit role is redundant. Adding a conflicting role breaks semantics. Either remove the role attribute or use a different element.",
|
|
2998
3021
|
prompt: "Consider implicit roles: header=banner, nav=navigation, main=main, footer=contentinfo, aside=complementary, article=article, section=region (when labeled). Explain if this role is redundant (matches implicit) or invalid (conflicts). Suggest removing it or restructuring.",
|
|
2999
|
-
run(
|
|
3000
|
-
var
|
|
3022
|
+
run(e) {
|
|
3023
|
+
var t;
|
|
3001
3024
|
const a = [];
|
|
3002
|
-
for (const i of
|
|
3025
|
+
for (const i of e.querySelectorAll("[role]")) {
|
|
3003
3026
|
if (g(i)) continue;
|
|
3004
|
-
const n = (
|
|
3027
|
+
const n = (t = i.getAttribute("role")) == null ? void 0 : t.trim().toLowerCase();
|
|
3005
3028
|
if (!n) continue;
|
|
3006
|
-
const r =
|
|
3029
|
+
const r = he(i);
|
|
3007
3030
|
if (r && n === r) continue;
|
|
3008
|
-
const o =
|
|
3031
|
+
const o = Ia(i);
|
|
3009
3032
|
o === "none" ? a.push({
|
|
3010
3033
|
ruleId: "aria-allowed-role",
|
|
3011
3034
|
selector: m(i),
|
|
@@ -3030,8 +3053,8 @@ const ka = {
|
|
|
3030
3053
|
grid: [["row", "rowgroup"]],
|
|
3031
3054
|
list: [["listitem", "group"]],
|
|
3032
3055
|
listbox: [["option", "group"]],
|
|
3033
|
-
menu: [["menuitem", "menuitemcheckbox", "menuitemradio", "group"]],
|
|
3034
|
-
menubar: [["menuitem", "menuitemcheckbox", "menuitemradio", "group"]],
|
|
3056
|
+
menu: [["menuitem", "menuitemcheckbox", "menuitemradio", "group", "menu", "separator"]],
|
|
3057
|
+
menubar: [["menuitem", "menuitemcheckbox", "menuitemradio", "group", "menu", "separator"]],
|
|
3035
3058
|
radiogroup: [["radio"]],
|
|
3036
3059
|
row: [["cell", "columnheader", "gridcell", "rowheader"]],
|
|
3037
3060
|
rowgroup: [["row"]],
|
|
@@ -3039,7 +3062,7 @@ const ka = {
|
|
|
3039
3062
|
tablist: [["tab"]],
|
|
3040
3063
|
tree: [["treeitem", "group"]],
|
|
3041
3064
|
treegrid: [["row", "rowgroup"]]
|
|
3042
|
-
},
|
|
3065
|
+
}, Ea = /* @__PURE__ */ new Set([
|
|
3043
3066
|
"doc-bibliography",
|
|
3044
3067
|
"doc-endnotes",
|
|
3045
3068
|
"grid",
|
|
@@ -3053,8 +3076,9 @@ const ka = {
|
|
|
3053
3076
|
"treegrid"
|
|
3054
3077
|
]), ne = {
|
|
3055
3078
|
caption: ["figure", "table", "grid", "treegrid"],
|
|
3056
|
-
|
|
3057
|
-
|
|
3079
|
+
cell: ["row"],
|
|
3080
|
+
columnheader: ["row"],
|
|
3081
|
+
gridcell: ["row"],
|
|
3058
3082
|
listitem: ["list", "group"],
|
|
3059
3083
|
menuitem: ["menu", "menubar", "group"],
|
|
3060
3084
|
menuitemcheckbox: ["menu", "menubar", "group"],
|
|
@@ -3062,25 +3086,26 @@ const ka = {
|
|
|
3062
3086
|
option: ["listbox", "group"],
|
|
3063
3087
|
row: ["table", "grid", "treegrid", "rowgroup"],
|
|
3064
3088
|
rowgroup: ["table", "grid", "treegrid"],
|
|
3089
|
+
rowheader: ["row"],
|
|
3065
3090
|
tab: ["tablist"],
|
|
3066
3091
|
treeitem: ["tree", "group"]
|
|
3067
3092
|
};
|
|
3068
|
-
function
|
|
3093
|
+
function Ca(e, a) {
|
|
3069
3094
|
var o;
|
|
3070
|
-
const
|
|
3095
|
+
const t = ((o = e.getAttribute("aria-owns")) == null ? void 0 : o.split(/\s+/)) || [], i = e.ownerDocument, n = /* @__PURE__ */ new Set();
|
|
3071
3096
|
let r = !1;
|
|
3072
|
-
for (const s of
|
|
3097
|
+
for (const s of e.querySelectorAll("*")) {
|
|
3073
3098
|
if (g(s)) continue;
|
|
3074
3099
|
r = !0;
|
|
3075
3100
|
const l = q(s);
|
|
3076
3101
|
l && n.add(l);
|
|
3077
3102
|
}
|
|
3078
|
-
for (const s of
|
|
3103
|
+
for (const s of t) {
|
|
3079
3104
|
const l = i.getElementById(s);
|
|
3080
3105
|
if (l && !g(l)) {
|
|
3081
3106
|
r = !0;
|
|
3082
|
-
const
|
|
3083
|
-
|
|
3107
|
+
const p = q(l);
|
|
3108
|
+
p && n.add(p);
|
|
3084
3109
|
}
|
|
3085
3110
|
}
|
|
3086
3111
|
if (!r) return "empty";
|
|
@@ -3088,27 +3113,27 @@ function Ta(t, a) {
|
|
|
3088
3113
|
if (!s.some((l) => n.has(l))) return "fail";
|
|
3089
3114
|
return "pass";
|
|
3090
3115
|
}
|
|
3091
|
-
const
|
|
3116
|
+
const La = {
|
|
3092
3117
|
id: "aria-required-children",
|
|
3093
3118
|
wcag: ["1.3.1"],
|
|
3094
3119
|
level: "A",
|
|
3095
3120
|
description: "Certain ARIA roles require specific child roles to be present.",
|
|
3096
3121
|
guidance: "Some ARIA roles represent containers that must contain specific child roles for proper semantics. For example, a list must contain listitems, a menu must contain menuitems. Add the required child elements with appropriate roles, or use native HTML elements that provide these semantics implicitly (e.g., <ul> with <li>).",
|
|
3097
3122
|
prompt: "State which child role(s) are required and suggest adding elements with those roles, or using equivalent native HTML elements.",
|
|
3098
|
-
run(
|
|
3099
|
-
var
|
|
3123
|
+
run(e) {
|
|
3124
|
+
var t;
|
|
3100
3125
|
const a = [];
|
|
3101
|
-
for (const i of
|
|
3126
|
+
for (const i of e.querySelectorAll("[role]")) {
|
|
3102
3127
|
if (g(i)) continue;
|
|
3103
|
-
const n = (
|
|
3128
|
+
const n = (t = i.getAttribute("role")) == null ? void 0 : t.trim().toLowerCase();
|
|
3104
3129
|
if (!n || !(n in ie) || i.getAttribute("aria-busy") === "true") continue;
|
|
3105
3130
|
if (n === "combobox") {
|
|
3106
3131
|
if (i.getAttribute("aria-expanded") !== "true") continue;
|
|
3107
3132
|
const l = i.tagName.toLowerCase();
|
|
3108
3133
|
if (l === "input" || l === "textarea") continue;
|
|
3109
3134
|
}
|
|
3110
|
-
const r = ie[n], o =
|
|
3111
|
-
if (o === "pass" || o === "empty" &&
|
|
3135
|
+
const r = ie[n], o = Ca(i, r);
|
|
3136
|
+
if (o === "pass" || o === "empty" && Ea.has(n)) continue;
|
|
3112
3137
|
const s = r.map((l) => l.join(" or ")).join(", ");
|
|
3113
3138
|
a.push({
|
|
3114
3139
|
ruleId: "aria-required-children",
|
|
@@ -3120,23 +3145,23 @@ const Ea = {
|
|
|
3120
3145
|
}
|
|
3121
3146
|
return a;
|
|
3122
3147
|
}
|
|
3123
|
-
},
|
|
3148
|
+
}, qa = {
|
|
3124
3149
|
id: "aria-required-parent",
|
|
3125
3150
|
wcag: ["1.3.1"],
|
|
3126
3151
|
level: "A",
|
|
3127
3152
|
description: "Certain ARIA roles must be contained within specific parent roles.",
|
|
3128
3153
|
guidance: "Some ARIA roles represent items that must exist within specific container roles. For example, a listitem must be within a list, a tab must be within a tablist. Wrap the element in the appropriate parent, or use native HTML elements that provide this structure (e.g., <li> inside <ul>).",
|
|
3129
3154
|
prompt: "State which parent role is required and suggest wrapping in an element with that role, or using equivalent native HTML structure.",
|
|
3130
|
-
run(
|
|
3131
|
-
var
|
|
3155
|
+
run(e) {
|
|
3156
|
+
var t;
|
|
3132
3157
|
const a = [];
|
|
3133
|
-
for (const i of
|
|
3158
|
+
for (const i of e.querySelectorAll("[role]")) {
|
|
3134
3159
|
if (g(i)) continue;
|
|
3135
|
-
const n = (
|
|
3160
|
+
const n = (t = i.getAttribute("role")) == null ? void 0 : t.trim().toLowerCase();
|
|
3136
3161
|
if (!n || !(n in ne)) continue;
|
|
3137
3162
|
const r = ne[n];
|
|
3138
3163
|
let o = i.parentElement, s = !1;
|
|
3139
|
-
for (; o && o !==
|
|
3164
|
+
for (; o && o !== e.documentElement; ) {
|
|
3140
3165
|
const l = q(o);
|
|
3141
3166
|
if (l && r.includes(l)) {
|
|
3142
3167
|
s = !0;
|
|
@@ -3170,10 +3195,10 @@ const Ea = {
|
|
|
3170
3195
|
"embed",
|
|
3171
3196
|
"area[href]"
|
|
3172
3197
|
].join(", ");
|
|
3173
|
-
function
|
|
3174
|
-
let a =
|
|
3175
|
-
const
|
|
3176
|
-
for (; a && a !==
|
|
3198
|
+
function Ra(e) {
|
|
3199
|
+
let a = e;
|
|
3200
|
+
const t = e.ownerDocument, i = t.defaultView;
|
|
3201
|
+
for (; a && a !== t.body; ) {
|
|
3177
3202
|
if (a.style.display === "none" || a.style.visibility === "hidden") return !1;
|
|
3178
3203
|
if (i) {
|
|
3179
3204
|
const n = i.getComputedStyle(a);
|
|
@@ -3183,7 +3208,7 @@ function La(t) {
|
|
|
3183
3208
|
}
|
|
3184
3209
|
return !0;
|
|
3185
3210
|
}
|
|
3186
|
-
const
|
|
3211
|
+
const Na = {
|
|
3187
3212
|
id: "aria-hidden-body",
|
|
3188
3213
|
selector: 'body[aria-hidden="true"]',
|
|
3189
3214
|
check: { type: "selector-exists" },
|
|
@@ -3195,27 +3220,27 @@ const qa = {
|
|
|
3195
3220
|
guidance: "Setting aria-hidden='true' on the body element hides all page content from assistive technologies, making the page completely inaccessible to screen reader users. Remove aria-hidden from the body element. If you need to hide content temporarily (e.g., behind a modal), use aria-hidden on specific sections instead.",
|
|
3196
3221
|
prompt: "Instruct to remove aria-hidden='true' from the body element.",
|
|
3197
3222
|
skipAriaHidden: !1
|
|
3198
|
-
},
|
|
3223
|
+
}, $a = k(Na), Ma = {
|
|
3199
3224
|
id: "aria-hidden-focus",
|
|
3200
3225
|
wcag: ["4.1.2"],
|
|
3201
3226
|
level: "A",
|
|
3202
3227
|
description: "Elements with aria-hidden='true' must not contain focusable elements.",
|
|
3203
3228
|
guidance: "When aria-hidden='true' hides an element from assistive technologies but the element contains focusable children, keyboard users can focus those children but screen reader users won't know they exist. Either remove focusable elements from the hidden region, add tabindex='-1' to them, or remove aria-hidden.",
|
|
3204
3229
|
prompt: "This element can receive keyboard focus but is inside an aria-hidden region, making it invisible to screen readers. The context explains why it's focusable. Fix by either: (1) adding tabindex='-1' to remove it from tab order, (2) moving it outside the aria-hidden region, or (3) removing aria-hidden='true' from the ancestor if the content should be accessible.",
|
|
3205
|
-
run(
|
|
3230
|
+
run(e) {
|
|
3206
3231
|
const a = [];
|
|
3207
|
-
for (const
|
|
3208
|
-
if (
|
|
3209
|
-
const i = [...
|
|
3210
|
-
|
|
3232
|
+
for (const t of e.querySelectorAll('[aria-hidden="true"]')) {
|
|
3233
|
+
if (t === e.body) continue;
|
|
3234
|
+
const i = [...t.querySelectorAll(re)];
|
|
3235
|
+
t.matches(re) && i.push(t);
|
|
3211
3236
|
for (const n of i)
|
|
3212
3237
|
if (n instanceof HTMLElement) {
|
|
3213
3238
|
const r = n.getAttribute("tabindex");
|
|
3214
|
-
if (r === "-1" || n.disabled || n instanceof HTMLInputElement && n.type === "hidden" || !
|
|
3239
|
+
if (r === "-1" || n.disabled || n instanceof HTMLInputElement && n.type === "hidden" || !Ra(n)) continue;
|
|
3215
3240
|
const o = n.tagName.toLowerCase();
|
|
3216
3241
|
let s;
|
|
3217
3242
|
r !== null ? s = `has tabindex="${r}"` : o === "a" && n.hasAttribute("href") ? s = "is a link with href" : o === "button" ? s = "is a <button>" : o === "input" ? s = `is an <input type="${n.type}">` : o === "select" ? s = "is a <select>" : o === "textarea" ? s = "is a <textarea>" : o === "iframe" ? s = "is an <iframe>" : s = `is a natively focusable <${o}>`;
|
|
3218
|
-
const l = n ===
|
|
3243
|
+
const l = n === t ? n : n.closest('[aria-hidden="true"]');
|
|
3219
3244
|
a.push({
|
|
3220
3245
|
ruleId: "aria-hidden-focus",
|
|
3221
3246
|
selector: m(n),
|
|
@@ -3228,21 +3253,21 @@ const qa = {
|
|
|
3228
3253
|
}
|
|
3229
3254
|
return a;
|
|
3230
3255
|
}
|
|
3231
|
-
},
|
|
3256
|
+
}, Ha = {
|
|
3232
3257
|
id: "aria-command-name",
|
|
3233
3258
|
wcag: ["4.1.2"],
|
|
3234
3259
|
level: "A",
|
|
3235
3260
|
description: "ARIA commands must have an accessible name.",
|
|
3236
3261
|
guidance: "Interactive ARIA command roles (button, link, menuitem) must have accessible names so users know what action they perform. Add visible text content, aria-label, or aria-labelledby to provide a name.",
|
|
3237
3262
|
prompt: "Based on the element's content or context, suggest an aria-label describing what this command does.",
|
|
3238
|
-
run(
|
|
3239
|
-
var
|
|
3263
|
+
run(e) {
|
|
3264
|
+
var t;
|
|
3240
3265
|
const a = [];
|
|
3241
|
-
for (const i of
|
|
3266
|
+
for (const i of e.querySelectorAll('[role="button"], [role="link"], [role="menuitem"]')) {
|
|
3242
3267
|
if (g(i) || T(i) || i.getRootNode() instanceof ShadowRoot || i.tagName.toLowerCase() === "button" || i.tagName.toLowerCase() === "a") continue;
|
|
3243
3268
|
if (!v(i)) {
|
|
3244
3269
|
const r = i.querySelector("img[alt]");
|
|
3245
|
-
if ((
|
|
3270
|
+
if ((t = r == null ? void 0 : r.getAttribute("alt")) != null && t.trim()) continue;
|
|
3246
3271
|
a.push({
|
|
3247
3272
|
ruleId: "aria-command-name",
|
|
3248
3273
|
selector: m(i),
|
|
@@ -3254,16 +3279,16 @@ const qa = {
|
|
|
3254
3279
|
}
|
|
3255
3280
|
return a;
|
|
3256
3281
|
}
|
|
3257
|
-
},
|
|
3282
|
+
}, Da = {
|
|
3258
3283
|
id: "aria-input-field-name",
|
|
3259
3284
|
wcag: ["4.1.2"],
|
|
3260
3285
|
level: "A",
|
|
3261
3286
|
description: "ARIA input fields must have an accessible name.",
|
|
3262
3287
|
guidance: "ARIA input widgets (combobox, listbox, searchbox, slider, spinbutton, textbox) must have accessible names so users understand what data to enter. Add a visible label with aria-labelledby, or use aria-label if a visible label is not possible.",
|
|
3263
3288
|
prompt: "Based on the context, suggest an aria-label describing what data this input field accepts.",
|
|
3264
|
-
run(
|
|
3265
|
-
const a = [],
|
|
3266
|
-
for (const i of
|
|
3289
|
+
run(e) {
|
|
3290
|
+
const a = [], t = '[role="combobox"], [role="listbox"], [role="searchbox"], [role="slider"], [role="spinbutton"], [role="textbox"]';
|
|
3291
|
+
for (const i of e.querySelectorAll(t)) {
|
|
3267
3292
|
if (g(i) || T(i) || i.getRootNode() instanceof ShadowRoot || i.matches("input, select, textarea")) continue;
|
|
3268
3293
|
v(i) || a.push({
|
|
3269
3294
|
ruleId: "aria-input-field-name",
|
|
@@ -3275,16 +3300,16 @@ const qa = {
|
|
|
3275
3300
|
}
|
|
3276
3301
|
return a;
|
|
3277
3302
|
}
|
|
3278
|
-
},
|
|
3303
|
+
}, Fa = {
|
|
3279
3304
|
id: "aria-toggle-field-name",
|
|
3280
3305
|
wcag: ["4.1.2"],
|
|
3281
3306
|
level: "A",
|
|
3282
3307
|
description: "ARIA toggle fields must have an accessible name.",
|
|
3283
3308
|
guidance: "ARIA toggle controls (checkbox, switch, radio, menuitemcheckbox, menuitemradio) must have accessible names so users understand what option they're selecting. Add visible text content, aria-label, or use aria-labelledby to reference a visible label.",
|
|
3284
3309
|
prompt: "Based on the context, suggest an aria-label describing what option this toggle controls.",
|
|
3285
|
-
run(
|
|
3286
|
-
const a = [],
|
|
3287
|
-
for (const i of
|
|
3310
|
+
run(e) {
|
|
3311
|
+
const a = [], t = '[role="checkbox"], [role="switch"], [role="radio"], [role="menuitemcheckbox"], [role="menuitemradio"]';
|
|
3312
|
+
for (const i of e.querySelectorAll(t)) {
|
|
3288
3313
|
if (g(i) || T(i) || i.getRootNode() instanceof ShadowRoot || i.matches('input[type="checkbox"], input[type="radio"]')) continue;
|
|
3289
3314
|
v(i) || a.push({
|
|
3290
3315
|
ruleId: "aria-toggle-field-name",
|
|
@@ -3296,21 +3321,21 @@ const qa = {
|
|
|
3296
3321
|
}
|
|
3297
3322
|
return a;
|
|
3298
3323
|
}
|
|
3299
|
-
},
|
|
3324
|
+
}, Wa = {
|
|
3300
3325
|
id: "aria-meter-name",
|
|
3301
3326
|
wcag: ["4.1.2"],
|
|
3302
3327
|
level: "A",
|
|
3303
3328
|
description: "ARIA meter elements must have an accessible name.",
|
|
3304
3329
|
guidance: "Meter elements display a value within a known range (like disk usage or password strength). They must have accessible names so screen reader users understand what is being measured. Use aria-label or aria-labelledby to provide context.",
|
|
3305
3330
|
prompt: "Based on the context or value attributes, suggest an aria-label describing what this meter measures.",
|
|
3306
|
-
run(
|
|
3331
|
+
run(e) {
|
|
3307
3332
|
const a = [];
|
|
3308
|
-
for (const
|
|
3309
|
-
if (g(
|
|
3310
|
-
v(
|
|
3333
|
+
for (const t of e.querySelectorAll('[role="meter"], meter')) {
|
|
3334
|
+
if (g(t)) continue;
|
|
3335
|
+
v(t) || a.push({
|
|
3311
3336
|
ruleId: "aria-meter-name",
|
|
3312
|
-
selector: m(
|
|
3313
|
-
html: d(
|
|
3337
|
+
selector: m(t),
|
|
3338
|
+
html: d(t),
|
|
3314
3339
|
impact: "serious",
|
|
3315
3340
|
message: "Meter has no accessible name."
|
|
3316
3341
|
});
|
|
@@ -3324,14 +3349,14 @@ const qa = {
|
|
|
3324
3349
|
description: "ARIA progressbar elements must have an accessible name.",
|
|
3325
3350
|
guidance: "Progress indicators must have accessible names so screen reader users understand what process is being tracked. Use aria-label (e.g., 'File upload progress') or aria-labelledby to reference a visible heading or label.",
|
|
3326
3351
|
prompt: "Based on the context, suggest an aria-label describing what process this progressbar tracks.",
|
|
3327
|
-
run(
|
|
3352
|
+
run(e) {
|
|
3328
3353
|
const a = [];
|
|
3329
|
-
for (const
|
|
3330
|
-
if (g(
|
|
3331
|
-
v(
|
|
3354
|
+
for (const t of e.querySelectorAll('[role="progressbar"], progress')) {
|
|
3355
|
+
if (g(t)) continue;
|
|
3356
|
+
v(t) || a.push({
|
|
3332
3357
|
ruleId: "aria-progressbar-name",
|
|
3333
|
-
selector: m(
|
|
3334
|
-
html: d(
|
|
3358
|
+
selector: m(t),
|
|
3359
|
+
html: d(t),
|
|
3335
3360
|
impact: "serious",
|
|
3336
3361
|
message: "Progressbar has no accessible name."
|
|
3337
3362
|
});
|
|
@@ -3345,80 +3370,80 @@ const qa = {
|
|
|
3345
3370
|
description: "ARIA dialogs must have an accessible name.",
|
|
3346
3371
|
guidance: "Dialog and alertdialog elements must have accessible names so screen reader users understand the dialog's purpose when it opens. Use aria-label or aria-labelledby pointing to the dialog's heading. Native <dialog> elements should also have an accessible name.",
|
|
3347
3372
|
prompt: "Suggest adding aria-labelledby pointing to the dialog's heading element, or an aria-label describing the dialog's purpose.",
|
|
3348
|
-
run(
|
|
3373
|
+
run(e) {
|
|
3349
3374
|
const a = [];
|
|
3350
|
-
for (const
|
|
3351
|
-
if (g(
|
|
3352
|
-
v(
|
|
3375
|
+
for (const t of e.querySelectorAll('[role="dialog"], [role="alertdialog"], dialog')) {
|
|
3376
|
+
if (g(t)) continue;
|
|
3377
|
+
v(t) || a.push({
|
|
3353
3378
|
ruleId: "aria-dialog-name",
|
|
3354
|
-
selector: m(
|
|
3355
|
-
html: d(
|
|
3379
|
+
selector: m(t),
|
|
3380
|
+
html: d(t),
|
|
3356
3381
|
impact: "serious",
|
|
3357
3382
|
message: "Dialog has no accessible name."
|
|
3358
3383
|
});
|
|
3359
3384
|
}
|
|
3360
3385
|
return a;
|
|
3361
3386
|
}
|
|
3362
|
-
},
|
|
3387
|
+
}, _a = {
|
|
3363
3388
|
id: "aria-tooltip-name",
|
|
3364
3389
|
wcag: ["4.1.2"],
|
|
3365
3390
|
level: "A",
|
|
3366
3391
|
description: "ARIA tooltips must have an accessible name.",
|
|
3367
3392
|
guidance: "Tooltip elements must have accessible names (usually their text content). The tooltip content itself typically serves as the accessible name. Ensure the tooltip contains descriptive text content or has aria-label.",
|
|
3368
3393
|
prompt: "Add text content to the tooltip describing the information it provides, or add aria-label.",
|
|
3369
|
-
run(
|
|
3394
|
+
run(e) {
|
|
3370
3395
|
const a = [];
|
|
3371
|
-
for (const
|
|
3372
|
-
if (g(
|
|
3373
|
-
v(
|
|
3396
|
+
for (const t of e.querySelectorAll('[role="tooltip"]')) {
|
|
3397
|
+
if (g(t)) continue;
|
|
3398
|
+
v(t) || a.push({
|
|
3374
3399
|
ruleId: "aria-tooltip-name",
|
|
3375
|
-
selector: m(
|
|
3376
|
-
html: d(
|
|
3400
|
+
selector: m(t),
|
|
3401
|
+
html: d(t),
|
|
3377
3402
|
impact: "serious",
|
|
3378
3403
|
message: "Tooltip has no accessible name."
|
|
3379
3404
|
});
|
|
3380
3405
|
}
|
|
3381
3406
|
return a;
|
|
3382
3407
|
}
|
|
3383
|
-
},
|
|
3408
|
+
}, Pa = {
|
|
3384
3409
|
id: "aria-treeitem-name",
|
|
3385
3410
|
wcag: ["4.1.2"],
|
|
3386
3411
|
level: "A",
|
|
3387
3412
|
description: "ARIA treeitem elements must have an accessible name.",
|
|
3388
3413
|
guidance: "Tree items must have accessible names so screen reader users can understand the tree structure and navigate it effectively. Provide text content, aria-label, or aria-labelledby for each treeitem.",
|
|
3389
3414
|
prompt: "Add text content describing this tree item, or add aria-label.",
|
|
3390
|
-
run(
|
|
3415
|
+
run(e) {
|
|
3391
3416
|
const a = [];
|
|
3392
|
-
for (const
|
|
3393
|
-
if (g(
|
|
3394
|
-
v(
|
|
3417
|
+
for (const t of e.querySelectorAll('[role="treeitem"]')) {
|
|
3418
|
+
if (g(t)) continue;
|
|
3419
|
+
v(t) || a.push({
|
|
3395
3420
|
ruleId: "aria-treeitem-name",
|
|
3396
|
-
selector: m(
|
|
3397
|
-
html: d(
|
|
3421
|
+
selector: m(t),
|
|
3422
|
+
html: d(t),
|
|
3398
3423
|
impact: "serious",
|
|
3399
3424
|
message: "Treeitem has no accessible name."
|
|
3400
3425
|
});
|
|
3401
3426
|
}
|
|
3402
3427
|
return a;
|
|
3403
3428
|
}
|
|
3404
|
-
},
|
|
3429
|
+
}, ja = {
|
|
3405
3430
|
id: "aria-prohibited-attr",
|
|
3406
3431
|
wcag: ["4.1.2"],
|
|
3407
3432
|
level: "A",
|
|
3408
3433
|
description: "ARIA attributes must not be prohibited for the element's role.",
|
|
3409
3434
|
guidance: "Some ARIA roles prohibit certain attributes. For example, roles like 'none', 'presentation', 'generic', and text-level roles (code, emphasis, strong) prohibit aria-label and aria-labelledby because naming is not supported for these roles. Remove the prohibited attributes or change the role.",
|
|
3410
3435
|
prompt: "Identify the prohibited attribute and recommend removing it from this element.",
|
|
3411
|
-
run(
|
|
3412
|
-
return U(
|
|
3436
|
+
run(e) {
|
|
3437
|
+
return U(e).prohibitedAttr;
|
|
3413
3438
|
}
|
|
3414
|
-
},
|
|
3439
|
+
}, Va = [
|
|
3415
3440
|
"a[href]",
|
|
3416
3441
|
"button:not([disabled])",
|
|
3417
3442
|
'input:not([disabled]):not([type="hidden"])',
|
|
3418
3443
|
"select:not([disabled])",
|
|
3419
3444
|
"textarea:not([disabled])",
|
|
3420
3445
|
'[tabindex]:not([tabindex="-1"])'
|
|
3421
|
-
].join(", "),
|
|
3446
|
+
].join(", "), za = [
|
|
3422
3447
|
"aria-atomic",
|
|
3423
3448
|
"aria-busy",
|
|
3424
3449
|
"aria-controls",
|
|
@@ -3433,64 +3458,64 @@ const qa = {
|
|
|
3433
3458
|
"aria-owns",
|
|
3434
3459
|
"aria-relevant"
|
|
3435
3460
|
];
|
|
3436
|
-
function oe(
|
|
3461
|
+
function oe(e) {
|
|
3437
3462
|
const a = [];
|
|
3438
|
-
|
|
3439
|
-
for (const
|
|
3440
|
-
if (
|
|
3441
|
-
a.push(`has ${
|
|
3463
|
+
e.matches(Va) && a.push("element is focusable");
|
|
3464
|
+
for (const t of za)
|
|
3465
|
+
if (e.hasAttribute(t)) {
|
|
3466
|
+
a.push(`has ${t}`);
|
|
3442
3467
|
break;
|
|
3443
3468
|
}
|
|
3444
|
-
return (
|
|
3469
|
+
return (e.hasAttribute("aria-label") || e.hasAttribute("aria-labelledby")) && a.push("has accessible name"), a;
|
|
3445
3470
|
}
|
|
3446
|
-
const
|
|
3471
|
+
const Ua = {
|
|
3447
3472
|
id: "presentation-role-conflict",
|
|
3448
3473
|
wcag: ["4.1.2"],
|
|
3449
3474
|
level: "A",
|
|
3450
3475
|
description: "Elements with role='presentation' or role='none' must not be focusable or have global ARIA attributes.",
|
|
3451
3476
|
guidance: "When an element has role='presentation' or role='none', it's marked as decorative and removed from the accessibility tree. However, if the element is focusable or has certain ARIA attributes, the presentation role is ignored and the element remains accessible. This creates confusion. Either remove the presentation role, or remove the focusability/ARIA attributes.",
|
|
3452
3477
|
prompt: "Identify the conflict (focusable or ARIA attribute) and suggest either removing the presentation role or removing the conflicting attribute/focusability.",
|
|
3453
|
-
run(
|
|
3478
|
+
run(e) {
|
|
3454
3479
|
const a = [];
|
|
3455
|
-
for (const
|
|
3456
|
-
if (g(
|
|
3457
|
-
const i = oe(
|
|
3480
|
+
for (const t of e.querySelectorAll('[role="presentation"], [role="none"]')) {
|
|
3481
|
+
if (g(t)) continue;
|
|
3482
|
+
const i = oe(t);
|
|
3458
3483
|
i.length > 0 && a.push({
|
|
3459
3484
|
ruleId: "presentation-role-conflict",
|
|
3460
|
-
selector: m(
|
|
3461
|
-
html: d(
|
|
3485
|
+
selector: m(t),
|
|
3486
|
+
html: d(t),
|
|
3462
3487
|
impact: "serious",
|
|
3463
3488
|
message: `Presentation role conflicts with: ${i.join(", ")}. The role will be ignored.`
|
|
3464
3489
|
});
|
|
3465
3490
|
}
|
|
3466
|
-
for (const
|
|
3467
|
-
if (g(
|
|
3468
|
-
const i = oe(
|
|
3491
|
+
for (const t of e.querySelectorAll('img[alt=""]')) {
|
|
3492
|
+
if (g(t) || t.hasAttribute("role")) continue;
|
|
3493
|
+
const i = oe(t);
|
|
3469
3494
|
i.length > 0 && a.push({
|
|
3470
3495
|
ruleId: "presentation-role-conflict",
|
|
3471
|
-
selector: m(
|
|
3472
|
-
html: d(
|
|
3496
|
+
selector: m(t),
|
|
3497
|
+
html: d(t),
|
|
3473
3498
|
impact: "serious",
|
|
3474
3499
|
message: `Element with implicit presentation role (alt="") conflicts with: ${i.join(", ")}. The decorative role will be ignored.`
|
|
3475
3500
|
});
|
|
3476
3501
|
}
|
|
3477
3502
|
return a;
|
|
3478
3503
|
}
|
|
3479
|
-
},
|
|
3504
|
+
}, Ga = {
|
|
3480
3505
|
id: "summary-name",
|
|
3481
3506
|
wcag: ["4.1.2"],
|
|
3482
3507
|
level: "A",
|
|
3483
3508
|
description: "<summary> elements must have an accessible name.",
|
|
3484
3509
|
guidance: "The <summary> element provides the visible label for a <details> disclosure widget. It must have descriptive text content so screen reader users understand what will be revealed when expanded. Add clear, concise text that indicates what content is contained in the details section.",
|
|
3485
3510
|
prompt: "Based on the surrounding context or details content, suggest text to add inside the <summary> element.",
|
|
3486
|
-
run(
|
|
3511
|
+
run(e) {
|
|
3487
3512
|
const a = [];
|
|
3488
|
-
for (const
|
|
3489
|
-
if (g(
|
|
3490
|
-
v(
|
|
3513
|
+
for (const t of e.querySelectorAll("details > summary:first-of-type")) {
|
|
3514
|
+
if (g(t)) continue;
|
|
3515
|
+
v(t) || a.push({
|
|
3491
3516
|
ruleId: "summary-name",
|
|
3492
|
-
selector: m(
|
|
3493
|
-
html: d(
|
|
3517
|
+
selector: m(t),
|
|
3518
|
+
html: d(t),
|
|
3494
3519
|
impact: "serious",
|
|
3495
3520
|
message: "<summary> element has no accessible name. Add descriptive text."
|
|
3496
3521
|
});
|
|
@@ -3498,11 +3523,11 @@ const Va = {
|
|
|
3498
3523
|
return a;
|
|
3499
3524
|
}
|
|
3500
3525
|
};
|
|
3501
|
-
function
|
|
3526
|
+
function Ya(e) {
|
|
3502
3527
|
var n, r;
|
|
3503
|
-
const a = [],
|
|
3504
|
-
|
|
3505
|
-
const i =
|
|
3528
|
+
const a = [], t = e.getAttribute("href");
|
|
3529
|
+
t && a.push(`href: ${t}`);
|
|
3530
|
+
const i = e.parentElement;
|
|
3506
3531
|
if (i) {
|
|
3507
3532
|
const o = i.closest("h1, h2, h3, h4, h5, h6");
|
|
3508
3533
|
if ((n = o == null ? void 0 : o.textContent) != null && n.trim())
|
|
@@ -3515,29 +3540,29 @@ function Ua(t) {
|
|
|
3515
3540
|
return a.length > 0 ? a.join(`
|
|
3516
3541
|
`) : void 0;
|
|
3517
3542
|
}
|
|
3518
|
-
const
|
|
3543
|
+
const Xa = {
|
|
3519
3544
|
id: "link-name",
|
|
3520
3545
|
wcag: ["2.4.4", "4.1.2"],
|
|
3521
3546
|
level: "A",
|
|
3522
3547
|
description: "Links must have discernible text via content, aria-label, or aria-labelledby.",
|
|
3523
3548
|
guidance: "Screen reader users need to know where a link goes. Add descriptive text content, aria-label, or use aria-labelledby. For image links, ensure the image has alt text describing the link destination. Avoid generic text like 'click here' or 'read more'—link text should make sense out of context.",
|
|
3524
3549
|
prompt: "Based on the href or surrounding context, suggest descriptive link text or an aria-label.",
|
|
3525
|
-
run(
|
|
3550
|
+
run(e) {
|
|
3526
3551
|
const a = [];
|
|
3527
|
-
for (const
|
|
3528
|
-
if (g(
|
|
3529
|
-
v(
|
|
3552
|
+
for (const t of e.querySelectorAll('a[href], area[href], [role="link"]')) {
|
|
3553
|
+
if (g(t) || T(t) || t.getRootNode() instanceof ShadowRoot) continue;
|
|
3554
|
+
v(t) || a.push({
|
|
3530
3555
|
ruleId: "link-name",
|
|
3531
|
-
selector: m(
|
|
3532
|
-
html: d(
|
|
3556
|
+
selector: m(t),
|
|
3557
|
+
html: d(t),
|
|
3533
3558
|
impact: "serious",
|
|
3534
3559
|
message: "Link has no discernible text.",
|
|
3535
|
-
context:
|
|
3560
|
+
context: Ya(t)
|
|
3536
3561
|
});
|
|
3537
3562
|
}
|
|
3538
3563
|
return a;
|
|
3539
3564
|
}
|
|
3540
|
-
},
|
|
3565
|
+
}, Ka = {
|
|
3541
3566
|
id: "skip-link",
|
|
3542
3567
|
wcag: ["2.4.1"],
|
|
3543
3568
|
level: "A",
|
|
@@ -3545,15 +3570,15 @@ const Ga = {
|
|
|
3545
3570
|
description: "Skip links must point to a valid target on the page.",
|
|
3546
3571
|
guidance: "Skip links allow keyboard users to bypass repetitive navigation and jump directly to main content. The skip link should be the first focusable element on the page, link to the main content (e.g., href='#main'), and become visible when focused. It can be visually hidden until focused using CSS.",
|
|
3547
3572
|
prompt: "A skip link is a single <a href='#main'>Skip to main content</a> as the first element in <body>. It can be visually hidden with CSS until focused. Explain this simple pattern.",
|
|
3548
|
-
run(
|
|
3549
|
-
const a = [],
|
|
3550
|
-
for (const i of
|
|
3573
|
+
run(e) {
|
|
3574
|
+
const a = [], t = e.querySelectorAll('a[href^="#"]');
|
|
3575
|
+
for (const i of t) {
|
|
3551
3576
|
const n = i.getAttribute("href");
|
|
3552
3577
|
if (!n || n === "#") continue;
|
|
3553
3578
|
const r = A(i).toLowerCase();
|
|
3554
3579
|
if (!(r.includes("skip") || r.includes("jump") || r.includes("main content") || r.includes("navigation"))) continue;
|
|
3555
3580
|
const s = n.slice(1);
|
|
3556
|
-
|
|
3581
|
+
e.getElementById(s) || a.push({
|
|
3557
3582
|
ruleId: "skip-link",
|
|
3558
3583
|
selector: m(i),
|
|
3559
3584
|
html: d(i),
|
|
@@ -3563,7 +3588,7 @@ const Ga = {
|
|
|
3563
3588
|
}
|
|
3564
3589
|
return a;
|
|
3565
3590
|
}
|
|
3566
|
-
},
|
|
3591
|
+
}, Ja = /* @__PURE__ */ new Set([
|
|
3567
3592
|
"block",
|
|
3568
3593
|
"flex",
|
|
3569
3594
|
"grid",
|
|
@@ -3571,52 +3596,52 @@ const Ga = {
|
|
|
3571
3596
|
"table-cell",
|
|
3572
3597
|
"list-item",
|
|
3573
3598
|
"flow-root"
|
|
3574
|
-
]),
|
|
3599
|
+
]), Qa = /* @__PURE__ */ new Set([
|
|
3575
3600
|
"inline",
|
|
3576
3601
|
"inline-block",
|
|
3577
3602
|
"inline-flex",
|
|
3578
3603
|
"inline-grid"
|
|
3579
3604
|
]);
|
|
3580
|
-
function
|
|
3581
|
-
let a =
|
|
3605
|
+
function Za(e) {
|
|
3606
|
+
let a = e.parentElement;
|
|
3582
3607
|
for (; a; ) {
|
|
3583
|
-
const
|
|
3584
|
-
if (
|
|
3585
|
-
return
|
|
3608
|
+
const t = y(a).display;
|
|
3609
|
+
if (Ja.has(t))
|
|
3610
|
+
return ei(a) ? a : null;
|
|
3586
3611
|
a = a.parentElement;
|
|
3587
3612
|
}
|
|
3588
3613
|
return null;
|
|
3589
3614
|
}
|
|
3590
|
-
function
|
|
3591
|
-
const a =
|
|
3592
|
-
|
|
3615
|
+
function ei(e) {
|
|
3616
|
+
const a = e.ownerDocument.createTreeWalker(
|
|
3617
|
+
e,
|
|
3593
3618
|
NodeFilter.SHOW_TEXT
|
|
3594
3619
|
);
|
|
3595
|
-
let
|
|
3620
|
+
let t = "", i;
|
|
3596
3621
|
for (; i = a.nextNode(); ) {
|
|
3597
3622
|
if (!i.data.trim()) continue;
|
|
3598
3623
|
let n = i.parentElement, r = !1;
|
|
3599
|
-
for (; n && n !==
|
|
3624
|
+
for (; n && n !== e; ) {
|
|
3600
3625
|
if (n.tagName === "A") {
|
|
3601
3626
|
r = !0;
|
|
3602
3627
|
break;
|
|
3603
3628
|
}
|
|
3604
3629
|
n = n.parentElement;
|
|
3605
3630
|
}
|
|
3606
|
-
r || (
|
|
3631
|
+
r || (t += i.data);
|
|
3607
3632
|
}
|
|
3608
|
-
return
|
|
3633
|
+
return new RegExp("\\p{L}{2,}", "u").test(t);
|
|
3609
3634
|
}
|
|
3610
|
-
function
|
|
3611
|
-
const
|
|
3612
|
-
|
|
3635
|
+
function ti(e, a) {
|
|
3636
|
+
const t = e.ownerDocument.createTreeWalker(
|
|
3637
|
+
e,
|
|
3613
3638
|
NodeFilter.SHOW_TEXT
|
|
3614
3639
|
);
|
|
3615
3640
|
let i;
|
|
3616
|
-
for (; i =
|
|
3641
|
+
for (; i = t.nextNode(); ) {
|
|
3617
3642
|
if (!i.data.trim()) continue;
|
|
3618
3643
|
let n = i.parentElement, r = !1, o = n;
|
|
3619
|
-
for (; o && o !==
|
|
3644
|
+
for (; o && o !== e; ) {
|
|
3620
3645
|
if (o.tagName === "A") {
|
|
3621
3646
|
r = !0;
|
|
3622
3647
|
break;
|
|
@@ -3624,61 +3649,61 @@ function Za(t, a) {
|
|
|
3624
3649
|
o = o.parentElement;
|
|
3625
3650
|
}
|
|
3626
3651
|
if (!r && n)
|
|
3627
|
-
return N(
|
|
3652
|
+
return N(y(n).color);
|
|
3628
3653
|
}
|
|
3629
3654
|
return null;
|
|
3630
3655
|
}
|
|
3631
|
-
function
|
|
3632
|
-
const
|
|
3633
|
-
if ((
|
|
3656
|
+
function ai(e, a) {
|
|
3657
|
+
const t = e.textDecorationLine || e.textDecoration || "", i = a.textDecorationLine || a.textDecoration || "";
|
|
3658
|
+
if ((t.includes("underline") || t.includes("line-through")) && t !== i)
|
|
3634
3659
|
return !0;
|
|
3635
|
-
const n = parseFloat(
|
|
3660
|
+
const n = parseFloat(e.borderBottomWidth) || 0, r = e.borderBottomStyle || "";
|
|
3636
3661
|
if (n > 0 && r !== "none" && r !== "hidden")
|
|
3637
3662
|
return !0;
|
|
3638
|
-
const o = parseFloat(
|
|
3663
|
+
const o = parseFloat(e.outlineWidth) || 0, s = e.outlineStyle || "";
|
|
3639
3664
|
if (o > 0 && s !== "none")
|
|
3640
3665
|
return !0;
|
|
3641
|
-
const l =
|
|
3666
|
+
const l = e.backgroundImage || "";
|
|
3642
3667
|
if (l && l !== "none" && l !== "initial")
|
|
3643
3668
|
return !0;
|
|
3644
|
-
const
|
|
3645
|
-
if (Math.abs(
|
|
3669
|
+
const p = se(e.fontWeight), c = se(a.fontWeight);
|
|
3670
|
+
if (Math.abs(p - c) >= 300 || e.fontStyle !== a.fontStyle)
|
|
3646
3671
|
return !0;
|
|
3647
|
-
const u = parseFloat(
|
|
3648
|
-
return
|
|
3672
|
+
const u = parseFloat(e.fontSize) || 16, h = parseFloat(a.fontSize) || 16;
|
|
3673
|
+
return h > 0 && u / h >= 1.2;
|
|
3649
3674
|
}
|
|
3650
|
-
function se(
|
|
3651
|
-
return
|
|
3675
|
+
function se(e) {
|
|
3676
|
+
return e === "bold" ? 700 : e === "normal" ? 400 : parseInt(e) || 400;
|
|
3652
3677
|
}
|
|
3653
|
-
function le(
|
|
3654
|
-
return "#" + [
|
|
3678
|
+
function le(e, a, t) {
|
|
3679
|
+
return "#" + [e, a, t].map((i) => i.toString(16).padStart(2, "0")).join("");
|
|
3655
3680
|
}
|
|
3656
|
-
const
|
|
3681
|
+
const ii = {
|
|
3657
3682
|
id: "link-in-text-block",
|
|
3658
3683
|
wcag: ["1.4.1"],
|
|
3659
3684
|
level: "A",
|
|
3660
3685
|
description: "Links within text blocks must be distinguishable by more than color alone.",
|
|
3661
3686
|
guidance: "Users who cannot perceive color differences need other visual cues to identify links. Links in text should have underlines or other non-color indicators. If using color alone, ensure 3:1 contrast with surrounding text AND provide additional indication on focus/hover.",
|
|
3662
3687
|
prompt: "Explain how to make this link visually distinguishable without relying on color alone.",
|
|
3663
|
-
run(
|
|
3688
|
+
run(e) {
|
|
3664
3689
|
const a = [];
|
|
3665
|
-
for (const
|
|
3666
|
-
if (g(
|
|
3667
|
-
const i =
|
|
3668
|
-
if (!
|
|
3669
|
-
const r =
|
|
3690
|
+
for (const t of e.querySelectorAll("a[href]")) {
|
|
3691
|
+
if (g(t) || !A(t).trim() || t.closest('nav, header, footer, [role="navigation"], [role="banner"], [role="contentinfo"]')) continue;
|
|
3692
|
+
const i = y(t), n = i.display || "inline";
|
|
3693
|
+
if (!Qa.has(n)) continue;
|
|
3694
|
+
const r = Za(t);
|
|
3670
3695
|
if (!r) continue;
|
|
3671
|
-
const o =
|
|
3672
|
-
if (
|
|
3673
|
-
const s = N(i.color), l =
|
|
3696
|
+
const o = y(r);
|
|
3697
|
+
if (ai(i, o)) continue;
|
|
3698
|
+
const s = N(i.color), l = ti(r);
|
|
3674
3699
|
if (!s || !l) continue;
|
|
3675
|
-
const
|
|
3700
|
+
const p = R(...s), c = R(...l), u = ge(p, c);
|
|
3676
3701
|
if (u >= 3) continue;
|
|
3677
|
-
const
|
|
3702
|
+
const h = le(...s), b = le(...l), f = `link color: ${h} rgb(${s.join(", ")}), surrounding text: ${b} rgb(${l.join(", ")}), ratio: ${u.toFixed(2)}:1`;
|
|
3678
3703
|
a.push({
|
|
3679
3704
|
ruleId: "link-in-text-block",
|
|
3680
|
-
selector: m(
|
|
3681
|
-
html: d(
|
|
3705
|
+
selector: m(t),
|
|
3706
|
+
html: d(t),
|
|
3682
3707
|
impact: "serious",
|
|
3683
3708
|
message: "Link in text block is not visually distinguishable from surrounding text. Add an underline, border, or ensure 3:1 color contrast with surrounding text.",
|
|
3684
3709
|
context: f
|
|
@@ -3686,27 +3711,27 @@ const ti = {
|
|
|
3686
3711
|
}
|
|
3687
3712
|
return a;
|
|
3688
3713
|
}
|
|
3689
|
-
},
|
|
3714
|
+
}, ni = {
|
|
3690
3715
|
id: "html-has-lang",
|
|
3691
3716
|
wcag: ["3.1.1"],
|
|
3692
3717
|
level: "A",
|
|
3693
3718
|
description: "The <html> element must have a lang attribute.",
|
|
3694
3719
|
guidance: "Screen readers use the lang attribute to determine which language rules and pronunciation to use. Without it, content may be mispronounced. Set lang to the primary language of the page (e.g., lang='en' for English, lang='es' for Spanish).",
|
|
3695
3720
|
prompt: `The page is missing a lang attribute on <html>. Use the text sample in context to determine the primary language and suggest the correct BCP 47 code (e.g. 'en' for English, 'es' for Spanish, 'fr' for French, 'de' for German, 'ja' for Japanese, 'zh' for Chinese, 'pt' for Portuguese, 'ar' for Arabic). Add lang to the <html> element: <html lang="...">.`,
|
|
3696
|
-
run(
|
|
3697
|
-
var
|
|
3698
|
-
const a =
|
|
3721
|
+
run(e) {
|
|
3722
|
+
var t, i;
|
|
3723
|
+
const a = e.documentElement;
|
|
3699
3724
|
if (a.tagName.toLowerCase() !== "html") return [];
|
|
3700
|
-
if (!
|
|
3701
|
-
const n =
|
|
3725
|
+
if (!e.doctype && e.body) {
|
|
3726
|
+
const n = e.body.children;
|
|
3702
3727
|
if (n.length > 0 && Array.from(n).every(
|
|
3703
3728
|
(r) => r.tagName.toLowerCase() === "svg" || r.tagName.toLowerCase() === "math"
|
|
3704
3729
|
)) return [];
|
|
3705
3730
|
}
|
|
3706
|
-
if (!((
|
|
3731
|
+
if (!((t = a.getAttribute("lang")) != null && t.trim())) {
|
|
3707
3732
|
let n;
|
|
3708
|
-
if (
|
|
3709
|
-
const r = ((i =
|
|
3733
|
+
if (e.body) {
|
|
3734
|
+
const r = ((i = e.body.textContent) == null ? void 0 : i.trim().replace(/\s+/g, " ")) || "";
|
|
3710
3735
|
r && (n = r.slice(0, 200));
|
|
3711
3736
|
}
|
|
3712
3737
|
return [{
|
|
@@ -3720,45 +3745,45 @@ const ti = {
|
|
|
3720
3745
|
}
|
|
3721
3746
|
return [];
|
|
3722
3747
|
}
|
|
3723
|
-
},
|
|
3748
|
+
}, ri = new Set(
|
|
3724
3749
|
"aa ab ae af ak am an ar as av ay az ba be bg bh bi bm bn bo br bs ca ce ch co cr cs cu cv cy da de dv dz ee el en eo es et eu fa ff fi fj fo fr fy ga gd gl gn gu gv ha he hi ho hr ht hu hy hz ia id ie ig ii ik io is it iu ja jv ka kg ki kj kk kl km kn ko kr ks ku kv kw ky la lb lg li ln lo lt lu lv mg mh mi mk ml mn mr ms mt my na nb nd ne ng nl nn no nr nv ny oc oj om or os pa pi pl ps pt qu rm rn ro ru rw sa sc sd se sg si sk sl sm sn so sq sr ss st su sv sw ta te tg th ti tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa wo xh yi yo za zh zu".split(" ")
|
|
3725
|
-
),
|
|
3750
|
+
), oi = new Set(
|
|
3726
3751
|
"aar abk afr aka amh ara arg asm ava ave aym aze bak bam bel ben bih bis bod bos bre bul cat ces cha che chu chv cor cos cre cym dan deu div dzo ell eng epo est eus ewe fao fas fij fin fra fry ful gla gle glg glv grn guj hat hau hbs heb her hin hmo hrv hun hye ibo iii iku ile ina ind ipk isl ita jav jpn kal kan kas kat kau kaz khm kik kin kir kom kon kor kua kur lao lat lav lim lin lit ltz lub lug mah mal mar mkd mlg mlt mon mri msa mya nau nav nbl nde ndo nep nld nno nob nor nya oci oji ori orm oss pan pli pol por pus que roh ron run rus sag san sin slk slv sme smo sna snd som sot spa sqi srd srp ssw sun swa swe tah tam tat tel tgk tgl tha tir ton tsn tso tuk tur twi uig ukr urd uzb ven vie vol wln wol xho yid yor zha zho zul".split(" ")
|
|
3727
|
-
),
|
|
3728
|
-
function
|
|
3729
|
-
if (!
|
|
3730
|
-
const a =
|
|
3731
|
-
return a.length === 2 ?
|
|
3752
|
+
), si = /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;
|
|
3753
|
+
function fe(e) {
|
|
3754
|
+
if (!si.test(e)) return !1;
|
|
3755
|
+
const a = e.split("-")[0].toLowerCase();
|
|
3756
|
+
return a.length === 2 ? ri.has(a) : a.length === 3 ? !oi.has(a) : !1;
|
|
3732
3757
|
}
|
|
3733
|
-
const
|
|
3758
|
+
const li = {
|
|
3734
3759
|
id: "html-lang-valid",
|
|
3735
3760
|
wcag: ["3.1.1"],
|
|
3736
3761
|
level: "A",
|
|
3737
3762
|
description: "The lang attribute on <html> must have a valid value.",
|
|
3738
3763
|
guidance: "The lang attribute must use a valid BCP 47 language tag. Use a 2 or 3 letter language code (e.g., 'en', 'fr', 'zh'), optionally followed by a region code (e.g., 'en-US', 'pt-BR'). Invalid tags prevent screen readers from correctly pronouncing content.",
|
|
3739
3764
|
prompt: "Suggest the correct BCP 47 language tag based on the invalid value provided.",
|
|
3740
|
-
run(
|
|
3741
|
-
var
|
|
3742
|
-
const a = (
|
|
3743
|
-
return a && !
|
|
3765
|
+
run(e) {
|
|
3766
|
+
var t;
|
|
3767
|
+
const a = (t = e.documentElement.getAttribute("lang")) == null ? void 0 : t.trim();
|
|
3768
|
+
return a && !fe(a) ? [{
|
|
3744
3769
|
ruleId: "html-lang-valid",
|
|
3745
3770
|
selector: "html",
|
|
3746
|
-
html: d(
|
|
3771
|
+
html: d(e.documentElement),
|
|
3747
3772
|
impact: "serious",
|
|
3748
3773
|
message: `Invalid lang attribute value "${a}".`
|
|
3749
3774
|
}] : [];
|
|
3750
3775
|
}
|
|
3751
3776
|
};
|
|
3752
|
-
function ce(
|
|
3777
|
+
function ce(e) {
|
|
3753
3778
|
var i;
|
|
3754
|
-
const a =
|
|
3755
|
-
let
|
|
3756
|
-
for (;
|
|
3757
|
-
if (!
|
|
3758
|
-
const n =
|
|
3779
|
+
const a = e.ownerDocument.createTreeWalker(e, NodeFilter.SHOW_TEXT);
|
|
3780
|
+
let t;
|
|
3781
|
+
for (; t = a.nextNode(); ) {
|
|
3782
|
+
if (!t.data.trim()) continue;
|
|
3783
|
+
const n = t.parentElement;
|
|
3759
3784
|
if (!n || n instanceof HTMLElement && (n.hidden || n.style.display === "none")) continue;
|
|
3760
3785
|
let r = n, o = !1;
|
|
3761
|
-
for (; r && r !==
|
|
3786
|
+
for (; r && r !== e; ) {
|
|
3762
3787
|
if (r.hasAttribute("lang")) {
|
|
3763
3788
|
o = !0;
|
|
3764
3789
|
break;
|
|
@@ -3767,10 +3792,10 @@ function ce(t) {
|
|
|
3767
3792
|
}
|
|
3768
3793
|
if (!o) return !0;
|
|
3769
3794
|
}
|
|
3770
|
-
for (const n of
|
|
3795
|
+
for (const n of e.querySelectorAll("img[alt]")) {
|
|
3771
3796
|
if (!((i = n.getAttribute("alt")) == null ? void 0 : i.trim())) continue;
|
|
3772
3797
|
let o = n.parentElement, s = !1;
|
|
3773
|
-
for (; o && o !==
|
|
3798
|
+
for (; o && o !== e; ) {
|
|
3774
3799
|
if (o.hasAttribute("lang")) {
|
|
3775
3800
|
s = !0;
|
|
3776
3801
|
break;
|
|
@@ -3781,81 +3806,81 @@ function ce(t) {
|
|
|
3781
3806
|
}
|
|
3782
3807
|
return !1;
|
|
3783
3808
|
}
|
|
3784
|
-
const
|
|
3809
|
+
const ci = {
|
|
3785
3810
|
id: "valid-lang",
|
|
3786
3811
|
wcag: ["3.1.2"],
|
|
3787
3812
|
level: "AA",
|
|
3788
3813
|
description: "The lang attribute must have a valid value on all elements.",
|
|
3789
3814
|
guidance: "When content in a different language appears within a page (e.g., a French quote in an English document), wrap it with a lang attribute to ensure correct pronunciation. The lang value must be a valid BCP 47 tag. Common codes: en, es, fr, de, zh, ja, pt, ar, ru.",
|
|
3790
3815
|
prompt: "Identify the content's language and suggest the correct BCP 47 tag.",
|
|
3791
|
-
run(
|
|
3816
|
+
run(e) {
|
|
3792
3817
|
const a = [];
|
|
3793
|
-
for (const
|
|
3794
|
-
if (g(
|
|
3795
|
-
const i =
|
|
3818
|
+
for (const t of e.querySelectorAll("[lang]")) {
|
|
3819
|
+
if (g(t) || t === e.documentElement) continue;
|
|
3820
|
+
const i = t.getAttribute("lang"), n = i == null ? void 0 : i.trim();
|
|
3796
3821
|
if (i && !n) {
|
|
3797
|
-
ce(
|
|
3822
|
+
ce(t) && a.push({
|
|
3798
3823
|
ruleId: "valid-lang",
|
|
3799
|
-
selector: m(
|
|
3800
|
-
html: d(
|
|
3824
|
+
selector: m(t),
|
|
3825
|
+
html: d(t),
|
|
3801
3826
|
impact: "serious",
|
|
3802
3827
|
message: "Empty lang attribute value."
|
|
3803
3828
|
});
|
|
3804
3829
|
continue;
|
|
3805
3830
|
}
|
|
3806
|
-
n && ce(
|
|
3831
|
+
n && ce(t) && (fe(n) || a.push({
|
|
3807
3832
|
ruleId: "valid-lang",
|
|
3808
|
-
selector: m(
|
|
3809
|
-
html: d(
|
|
3833
|
+
selector: m(t),
|
|
3834
|
+
html: d(t),
|
|
3810
3835
|
impact: "serious",
|
|
3811
3836
|
message: `Invalid lang attribute value "${n}".`
|
|
3812
3837
|
}));
|
|
3813
3838
|
}
|
|
3814
3839
|
return a;
|
|
3815
3840
|
}
|
|
3816
|
-
},
|
|
3841
|
+
}, ui = {
|
|
3817
3842
|
id: "html-xml-lang-mismatch",
|
|
3818
3843
|
wcag: ["3.1.1"],
|
|
3819
3844
|
level: "A",
|
|
3820
3845
|
description: "The lang and xml:lang attributes on <html> must match.",
|
|
3821
3846
|
guidance: "In XHTML documents, if both lang and xml:lang are present, they must specify the same base language. Mismatched values confuse assistive technologies. Either remove xml:lang (preferred for HTML5) or ensure both attributes have identical values.",
|
|
3822
3847
|
prompt: "Explain whether to remove xml:lang or align it with the lang value.",
|
|
3823
|
-
run(
|
|
3848
|
+
run(e) {
|
|
3824
3849
|
var n, r;
|
|
3825
|
-
const a =
|
|
3826
|
-
if (
|
|
3827
|
-
const o =
|
|
3850
|
+
const a = e.documentElement, t = (n = a.getAttribute("lang")) == null ? void 0 : n.trim().toLowerCase(), i = (r = a.getAttribute("xml:lang")) == null ? void 0 : r.trim().toLowerCase();
|
|
3851
|
+
if (t && i) {
|
|
3852
|
+
const o = t.split("-")[0], s = i.split("-")[0];
|
|
3828
3853
|
if (o !== s)
|
|
3829
3854
|
return [{
|
|
3830
3855
|
ruleId: "html-xml-lang-mismatch",
|
|
3831
3856
|
selector: "html",
|
|
3832
3857
|
html: d(a),
|
|
3833
3858
|
impact: "moderate",
|
|
3834
|
-
message: `lang="${
|
|
3859
|
+
message: `lang="${t}" and xml:lang="${i}" do not match.`
|
|
3835
3860
|
}];
|
|
3836
3861
|
}
|
|
3837
3862
|
return [];
|
|
3838
3863
|
}
|
|
3839
|
-
},
|
|
3864
|
+
}, di = {
|
|
3840
3865
|
id: "td-headers-attr",
|
|
3841
3866
|
wcag: ["1.3.1"],
|
|
3842
3867
|
level: "A",
|
|
3843
3868
|
description: "All cells in a table using headers attribute must reference valid header IDs.",
|
|
3844
3869
|
guidance: "The headers attribute on table cells must reference IDs of header cells (th or td) within the same table. This creates explicit associations for screen readers. Verify all referenced IDs exist and spell them correctly. For simple tables, consider using scope on th elements instead.",
|
|
3845
3870
|
prompt: "Identify the invalid header ID reference and suggest the correct ID or how to fix it.",
|
|
3846
|
-
run(
|
|
3871
|
+
run(e) {
|
|
3847
3872
|
const a = [];
|
|
3848
|
-
for (const
|
|
3849
|
-
if (g(
|
|
3850
|
-
const i =
|
|
3873
|
+
for (const t of e.querySelectorAll("td[headers]")) {
|
|
3874
|
+
if (g(t)) continue;
|
|
3875
|
+
const i = t.closest("table");
|
|
3851
3876
|
if (!i) continue;
|
|
3852
|
-
const n =
|
|
3877
|
+
const n = t.getAttribute("id"), r = t.getAttribute("headers").split(/\s+/);
|
|
3853
3878
|
for (const o of r) {
|
|
3854
3879
|
if (o === n) {
|
|
3855
3880
|
a.push({
|
|
3856
3881
|
ruleId: "td-headers-attr",
|
|
3857
|
-
selector: m(
|
|
3858
|
-
html: d(
|
|
3882
|
+
selector: m(t),
|
|
3883
|
+
html: d(t),
|
|
3859
3884
|
impact: "serious",
|
|
3860
3885
|
message: `Headers attribute references the cell itself ("${o}").`
|
|
3861
3886
|
});
|
|
@@ -3864,8 +3889,8 @@ const si = {
|
|
|
3864
3889
|
if (!i.querySelector(`th#${CSS.escape(o)}, td#${CSS.escape(o)}`)) {
|
|
3865
3890
|
a.push({
|
|
3866
3891
|
ruleId: "td-headers-attr",
|
|
3867
|
-
selector: m(
|
|
3868
|
-
html: d(
|
|
3892
|
+
selector: m(t),
|
|
3893
|
+
html: d(t),
|
|
3869
3894
|
impact: "serious",
|
|
3870
3895
|
message: `Headers attribute references non-existent ID "${o}".`
|
|
3871
3896
|
});
|
|
@@ -3875,68 +3900,68 @@ const si = {
|
|
|
3875
3900
|
}
|
|
3876
3901
|
return a;
|
|
3877
3902
|
}
|
|
3878
|
-
},
|
|
3903
|
+
}, mi = {
|
|
3879
3904
|
id: "th-has-data-cells",
|
|
3880
3905
|
wcag: ["1.3.1"],
|
|
3881
3906
|
level: "A",
|
|
3882
3907
|
description: "Table headers should be associated with data cells.",
|
|
3883
3908
|
guidance: "A table with header cells (th) but no data cells (td) is likely a misuse of table markup for layout or has missing content. Either add data cells that the headers describe, or use appropriate non-table markup if this is not tabular data.",
|
|
3884
3909
|
prompt: "Explain whether this table needs data cells or if non-table layout would be more appropriate.",
|
|
3885
|
-
run(
|
|
3910
|
+
run(e) {
|
|
3886
3911
|
const a = [];
|
|
3887
|
-
for (const
|
|
3888
|
-
if (g(
|
|
3889
|
-
const i =
|
|
3912
|
+
for (const t of e.querySelectorAll("table")) {
|
|
3913
|
+
if (g(t) || t.getAttribute("role") === "presentation" || t.getAttribute("role") === "none") continue;
|
|
3914
|
+
const i = t.querySelectorAll("th"), n = t.querySelectorAll("td");
|
|
3890
3915
|
i.length > 0 && n.length === 0 && a.push({
|
|
3891
3916
|
ruleId: "th-has-data-cells",
|
|
3892
|
-
selector: m(
|
|
3893
|
-
html: d(
|
|
3917
|
+
selector: m(t),
|
|
3918
|
+
html: d(t),
|
|
3894
3919
|
impact: "serious",
|
|
3895
3920
|
message: "Table has header cells but no data cells."
|
|
3896
3921
|
});
|
|
3897
3922
|
}
|
|
3898
3923
|
return a;
|
|
3899
3924
|
}
|
|
3900
|
-
},
|
|
3925
|
+
}, hi = {
|
|
3901
3926
|
id: "td-has-header",
|
|
3902
3927
|
wcag: ["1.3.1"],
|
|
3903
3928
|
level: "A",
|
|
3904
3929
|
description: "Data cells in tables larger than 3x3 should have associated headers.",
|
|
3905
3930
|
guidance: "In complex tables, screen reader users need header associations to understand data cells. Use th elements with scope attribute, or the headers attribute on td elements. For simple tables (≤3x3), this is less critical as context is usually clear.",
|
|
3906
3931
|
prompt: "Explain whether to use scope attributes on headers or headers attribute on this cell.",
|
|
3907
|
-
run(
|
|
3908
|
-
var
|
|
3932
|
+
run(e) {
|
|
3933
|
+
var t, i;
|
|
3909
3934
|
const a = [];
|
|
3910
|
-
for (const n of
|
|
3935
|
+
for (const n of e.querySelectorAll("table")) {
|
|
3911
3936
|
if (g(n) || n.getAttribute("role") === "presentation" || n.getAttribute("role") === "none") continue;
|
|
3912
3937
|
const r = n.querySelectorAll("tr"), o = r.length;
|
|
3913
3938
|
let s = 0;
|
|
3914
3939
|
for (const u of r) {
|
|
3915
|
-
const
|
|
3940
|
+
const h = u.querySelectorAll("td, th");
|
|
3916
3941
|
let b = 0;
|
|
3917
|
-
for (const f of
|
|
3942
|
+
for (const f of h)
|
|
3918
3943
|
b += parseInt(f.getAttribute("colspan") || "1", 10);
|
|
3919
3944
|
s = Math.max(s, b);
|
|
3920
3945
|
}
|
|
3921
3946
|
if (o <= 3 && s <= 3) continue;
|
|
3922
|
-
const l = n.querySelector("th") !== null,
|
|
3947
|
+
const l = n.querySelector("th") !== null, p = n.querySelector("th[scope]") !== null, c = n.querySelector("td[headers]") !== null;
|
|
3923
3948
|
if (l)
|
|
3924
3949
|
for (const u of n.querySelectorAll("td")) {
|
|
3925
3950
|
if (g(u) || u.hasAttribute("headers")) continue;
|
|
3926
|
-
const
|
|
3927
|
-
if (!
|
|
3928
|
-
const b =
|
|
3929
|
-
let
|
|
3951
|
+
const h = u.closest("tr");
|
|
3952
|
+
if (!h) continue;
|
|
3953
|
+
const b = h.querySelector("th") !== null, f = Array.from(h.children).indexOf(u);
|
|
3954
|
+
let w = !1;
|
|
3930
3955
|
const I = n.querySelector("thead");
|
|
3931
3956
|
if (I) {
|
|
3932
3957
|
const x = I.querySelector("tr");
|
|
3933
|
-
x && ((
|
|
3958
|
+
x && ((t = x.querySelectorAll("th, td")[f]) == null ? void 0 : t.tagName.toLowerCase()) === "th" && (w = !0);
|
|
3934
3959
|
}
|
|
3935
|
-
if (!
|
|
3960
|
+
if (!w) {
|
|
3936
3961
|
const x = n.querySelector("tbody > tr, tr");
|
|
3937
|
-
x && ((i = x.querySelectorAll("th, td")[f]) == null ? void 0 : i.tagName.toLowerCase()) === "th" && (
|
|
3962
|
+
x && ((i = x.querySelectorAll("th, td")[f]) == null ? void 0 : i.tagName.toLowerCase()) === "th" && (w = !0);
|
|
3938
3963
|
}
|
|
3939
|
-
if (!b && !
|
|
3964
|
+
if (!b && !w && !p && !c) {
|
|
3940
3965
|
a.push({
|
|
3941
3966
|
ruleId: "td-has-header",
|
|
3942
3967
|
selector: m(u),
|
|
@@ -3950,20 +3975,20 @@ const si = {
|
|
|
3950
3975
|
}
|
|
3951
3976
|
return a;
|
|
3952
3977
|
}
|
|
3953
|
-
},
|
|
3978
|
+
}, pi = {
|
|
3954
3979
|
id: "scope-attr-valid",
|
|
3955
3980
|
wcag: ["1.3.1"],
|
|
3956
3981
|
level: "A",
|
|
3957
3982
|
description: "The scope attribute on table headers must have a valid value.",
|
|
3958
3983
|
guidance: "The scope attribute tells screen readers which cells a header applies to. Valid values are: row, col, rowgroup, colgroup. Using invalid values breaks the association between headers and cells.",
|
|
3959
3984
|
prompt: "Explain which scope value (row, col, rowgroup, colgroup) is appropriate for this header.",
|
|
3960
|
-
run(
|
|
3985
|
+
run(e) {
|
|
3961
3986
|
var i;
|
|
3962
|
-
const a = [],
|
|
3963
|
-
for (const n of
|
|
3987
|
+
const a = [], t = /* @__PURE__ */ new Set(["row", "col", "rowgroup", "colgroup"]);
|
|
3988
|
+
for (const n of e.querySelectorAll("th[scope]")) {
|
|
3964
3989
|
if (g(n)) continue;
|
|
3965
3990
|
const r = (i = n.getAttribute("scope")) == null ? void 0 : i.toLowerCase();
|
|
3966
|
-
r && !
|
|
3991
|
+
r && !t.has(r) && a.push({
|
|
3967
3992
|
ruleId: "scope-attr-valid",
|
|
3968
3993
|
selector: m(n),
|
|
3969
3994
|
html: d(n),
|
|
@@ -3973,7 +3998,7 @@ const si = {
|
|
|
3973
3998
|
}
|
|
3974
3999
|
return a;
|
|
3975
4000
|
}
|
|
3976
|
-
},
|
|
4001
|
+
}, gi = {
|
|
3977
4002
|
id: "empty-table-header",
|
|
3978
4003
|
wcag: [],
|
|
3979
4004
|
level: "A",
|
|
@@ -3981,113 +4006,113 @@ const si = {
|
|
|
3981
4006
|
description: "Table header cells should have visible text.",
|
|
3982
4007
|
guidance: "Empty table headers provide no information to screen reader users. Either add descriptive text to the header, or if the header is intentionally empty (like a corner cell), consider using a td element instead or adding a visually hidden label.",
|
|
3983
4008
|
prompt: "Suggest header text based on the column/row content, or explain if this should be a td instead.",
|
|
3984
|
-
run(
|
|
4009
|
+
run(e) {
|
|
3985
4010
|
const a = [];
|
|
3986
|
-
for (const
|
|
3987
|
-
if (g(
|
|
3988
|
-
const i =
|
|
3989
|
-
(i == null ? void 0 : i.getAttribute("role")) === "presentation" || (i == null ? void 0 : i.getAttribute("role")) === "none" || v(
|
|
4011
|
+
for (const t of e.querySelectorAll("th")) {
|
|
4012
|
+
if (g(t)) continue;
|
|
4013
|
+
const i = t.closest("table");
|
|
4014
|
+
(i == null ? void 0 : i.getAttribute("role")) === "presentation" || (i == null ? void 0 : i.getAttribute("role")) === "none" || v(t) || a.push({
|
|
3990
4015
|
ruleId: "empty-table-header",
|
|
3991
|
-
selector: m(
|
|
3992
|
-
html: d(
|
|
4016
|
+
selector: m(t),
|
|
4017
|
+
html: d(t),
|
|
3993
4018
|
impact: "minor",
|
|
3994
4019
|
message: "Table header cell is empty. Add text or use aria-label."
|
|
3995
4020
|
});
|
|
3996
4021
|
}
|
|
3997
4022
|
return a;
|
|
3998
4023
|
}
|
|
3999
|
-
},
|
|
4024
|
+
}, F = ["aria-labelledby", "aria-describedby", "aria-controls", "aria-owns", "aria-flowto"], bi = {
|
|
4000
4025
|
id: "duplicate-id-aria",
|
|
4001
4026
|
wcag: ["4.1.2"],
|
|
4002
4027
|
level: "A",
|
|
4003
4028
|
description: "IDs used in ARIA and label associations must be unique to avoid broken references.",
|
|
4004
4029
|
guidance: "When aria-labelledby, aria-describedby, aria-controls, or label[for] reference a duplicate ID, only the first matching element is used. This breaks the intended relationship and may leave controls unnamed or descriptions missing. Ensure IDs referenced by ARIA attributes and label associations are unique throughout the document.",
|
|
4005
4030
|
prompt: "Identify which attribute references this ID and suggest a unique replacement.",
|
|
4006
|
-
run(
|
|
4007
|
-
const a = [],
|
|
4008
|
-
for (const n of
|
|
4009
|
-
for (const r of
|
|
4031
|
+
run(e) {
|
|
4032
|
+
const a = [], t = /* @__PURE__ */ new Set();
|
|
4033
|
+
for (const n of e.querySelectorAll("[aria-labelledby], [aria-describedby], [aria-controls], [aria-owns], [aria-flowto]"))
|
|
4034
|
+
for (const r of F) {
|
|
4010
4035
|
const o = n.getAttribute(r);
|
|
4011
|
-
o && o.split(/\s+/).forEach((s) =>
|
|
4036
|
+
o && o.split(/\s+/).forEach((s) => t.add(s));
|
|
4012
4037
|
}
|
|
4013
|
-
for (const n of
|
|
4038
|
+
for (const n of e.querySelectorAll("label[for]")) {
|
|
4014
4039
|
const r = n.getAttribute("for");
|
|
4015
|
-
r &&
|
|
4040
|
+
r && t.add(r);
|
|
4016
4041
|
}
|
|
4017
4042
|
const i = /* @__PURE__ */ new Map();
|
|
4018
|
-
for (const n of
|
|
4019
|
-
|
|
4043
|
+
for (const n of e.querySelectorAll("[id]"))
|
|
4044
|
+
t.has(n.id) && (n instanceof HTMLElement && (n.style.display === "none" || n.style.visibility === "hidden" || n.hidden) || i.set(n.id, (i.get(n.id) ?? 0) + 1));
|
|
4020
4045
|
for (const [n, r] of i) {
|
|
4021
4046
|
if (r <= 1) continue;
|
|
4022
|
-
const o =
|
|
4023
|
-
|
|
4024
|
-
), l =
|
|
4025
|
-
let
|
|
4047
|
+
const o = e.querySelectorAll(`#${CSS.escape(n)}`), s = e.querySelector(
|
|
4048
|
+
F.map((c) => `[${c}~="${CSS.escape(n)}"]`).join(", ")
|
|
4049
|
+
), l = e.querySelector(`label[for="${CSS.escape(n)}"]`);
|
|
4050
|
+
let p;
|
|
4026
4051
|
if (s) {
|
|
4027
|
-
const c =
|
|
4052
|
+
const c = F.find(
|
|
4028
4053
|
(u) => {
|
|
4029
|
-
var
|
|
4030
|
-
return (
|
|
4054
|
+
var h;
|
|
4055
|
+
return (h = s.getAttribute(u)) == null ? void 0 : h.split(/\s+/).includes(n);
|
|
4031
4056
|
}
|
|
4032
4057
|
);
|
|
4033
|
-
c && (
|
|
4034
|
-
} else l && (
|
|
4058
|
+
c && (p = c);
|
|
4059
|
+
} else l && (p = "label[for]");
|
|
4035
4060
|
a.push({
|
|
4036
4061
|
ruleId: "duplicate-id-aria",
|
|
4037
4062
|
selector: m(o[1]),
|
|
4038
4063
|
html: d(o[1]),
|
|
4039
4064
|
impact: "critical",
|
|
4040
|
-
message: `Duplicate ID "${n}" referenced by ${
|
|
4041
|
-
context: `First element: ${d(o[0])}${
|
|
4042
|
-
Referenced by: ${
|
|
4065
|
+
message: `Duplicate ID "${n}" referenced by ${p ?? "an accessibility attribute"}.`,
|
|
4066
|
+
context: `First element: ${d(o[0])}${p ? `
|
|
4067
|
+
Referenced by: ${p}` : ""}`
|
|
4043
4068
|
});
|
|
4044
4069
|
}
|
|
4045
4070
|
return a;
|
|
4046
4071
|
}
|
|
4047
|
-
},
|
|
4072
|
+
}, fi = {
|
|
4048
4073
|
id: "video-caption",
|
|
4049
4074
|
wcag: ["1.2.2"],
|
|
4050
4075
|
level: "A",
|
|
4051
4076
|
description: "Video elements must have captions via <track kind='captions'>.",
|
|
4052
4077
|
guidance: "Captions provide text alternatives for audio content in videos, benefiting deaf users and those who cannot hear audio. Add a <track> element with kind='captions' pointing to a WebVTT caption file. Captions should include both dialogue and important sound effects.",
|
|
4053
4078
|
prompt: "Explain how to add a captions track element to this video.",
|
|
4054
|
-
run(
|
|
4079
|
+
run(e) {
|
|
4055
4080
|
const a = [];
|
|
4056
|
-
for (const
|
|
4057
|
-
if (g(
|
|
4058
|
-
|
|
4081
|
+
for (const t of e.querySelectorAll("video")) {
|
|
4082
|
+
if (g(t) || t.hasAttribute("muted") || t.hasAttribute("autoplay")) continue;
|
|
4083
|
+
t.querySelector('track[kind="captions"], track[kind="subtitles"]') || a.push({
|
|
4059
4084
|
ruleId: "video-caption",
|
|
4060
|
-
selector: m(
|
|
4061
|
-
html: d(
|
|
4085
|
+
selector: m(t),
|
|
4086
|
+
html: d(t),
|
|
4062
4087
|
impact: "critical",
|
|
4063
4088
|
message: "Video element has no captions track."
|
|
4064
4089
|
});
|
|
4065
4090
|
}
|
|
4066
4091
|
return a;
|
|
4067
4092
|
}
|
|
4068
|
-
},
|
|
4093
|
+
}, vi = {
|
|
4069
4094
|
id: "audio-caption",
|
|
4070
4095
|
wcag: ["1.2.1"],
|
|
4071
4096
|
level: "A",
|
|
4072
4097
|
description: "Audio elements should have a text alternative or transcript.",
|
|
4073
4098
|
guidance: "Audio-only content like podcasts or recordings needs a text alternative for deaf users. Provide a transcript either on the same page or linked nearby. The transcript should include all spoken content and descriptions of relevant sounds.",
|
|
4074
4099
|
prompt: "Explain options for providing a text alternative: transcript link or aria-describedby.",
|
|
4075
|
-
run(
|
|
4100
|
+
run(e) {
|
|
4076
4101
|
const a = [];
|
|
4077
|
-
for (const
|
|
4078
|
-
if (g(
|
|
4079
|
-
const n =
|
|
4102
|
+
for (const t of e.querySelectorAll("audio")) {
|
|
4103
|
+
if (g(t) || t.querySelector('track[kind="captions"], track[kind="descriptions"]') || t.hasAttribute("aria-describedby")) continue;
|
|
4104
|
+
const n = t.parentElement;
|
|
4080
4105
|
n && n.querySelector('a[href*="transcript"], a[href*="text"]') || a.push({
|
|
4081
4106
|
ruleId: "audio-caption",
|
|
4082
|
-
selector: m(
|
|
4083
|
-
html: d(
|
|
4107
|
+
selector: m(t),
|
|
4108
|
+
html: d(t),
|
|
4084
4109
|
impact: "critical",
|
|
4085
4110
|
message: "Audio element has no transcript or text alternative. Add a transcript or track element."
|
|
4086
4111
|
});
|
|
4087
4112
|
}
|
|
4088
4113
|
return a;
|
|
4089
4114
|
}
|
|
4090
|
-
},
|
|
4115
|
+
}, wi = /* @__PURE__ */ new Set([
|
|
4091
4116
|
"SCRIPT",
|
|
4092
4117
|
"STYLE",
|
|
4093
4118
|
"NOSCRIPT",
|
|
@@ -4103,231 +4128,256 @@ Referenced by: ${h}` : ""}`
|
|
|
4103
4128
|
"BR",
|
|
4104
4129
|
"HR"
|
|
4105
4130
|
]);
|
|
4106
|
-
function ue([
|
|
4107
|
-
return "#" + [
|
|
4131
|
+
function ue([e, a, t]) {
|
|
4132
|
+
return "#" + [e, a, t].map((i) => i.toString(16).padStart(2, "0")).join("");
|
|
4108
4133
|
}
|
|
4109
|
-
function
|
|
4110
|
-
return
|
|
4134
|
+
function yi(e) {
|
|
4135
|
+
return e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement || e instanceof HTMLSelectElement || e instanceof HTMLButtonElement ? e.disabled : !!(e.closest("fieldset[disabled]") || e.getAttribute("aria-disabled") === "true");
|
|
4111
4136
|
}
|
|
4112
|
-
function
|
|
4113
|
-
if (
|
|
4114
|
-
const
|
|
4137
|
+
function Ai(e, a) {
|
|
4138
|
+
if (e.tagName !== "LABEL") return !1;
|
|
4139
|
+
const t = e, i = t.htmlFor;
|
|
4115
4140
|
if (i) {
|
|
4116
4141
|
const o = a.getElementById(i);
|
|
4117
4142
|
if (o && (o.disabled || o.getAttribute("aria-disabled") === "true")) return !0;
|
|
4118
4143
|
}
|
|
4119
|
-
const n =
|
|
4144
|
+
const n = t.querySelector("input, select, textarea, button");
|
|
4120
4145
|
if (n && (n.disabled || n.getAttribute("aria-disabled") === "true")) return !0;
|
|
4121
|
-
const r =
|
|
4146
|
+
const r = t.id;
|
|
4122
4147
|
return !!(r && a.querySelector(`[aria-labelledby~="${r}"][aria-disabled="true"]`));
|
|
4123
4148
|
}
|
|
4124
|
-
function
|
|
4125
|
-
const a =
|
|
4149
|
+
function xi(e) {
|
|
4150
|
+
const a = e.clip;
|
|
4126
4151
|
if (a && a.startsWith("rect(")) {
|
|
4127
4152
|
const i = a.match(/[\d.]+/g);
|
|
4128
4153
|
if (!i || i.every((n) => parseFloat(n) === 0)) return !0;
|
|
4129
4154
|
}
|
|
4130
|
-
const
|
|
4131
|
-
if (
|
|
4132
|
-
if (
|
|
4133
|
-
const i = parseFloat(
|
|
4155
|
+
const t = e.clipPath;
|
|
4156
|
+
if (t === "inset(50%)" || t === "inset(100%)") return !0;
|
|
4157
|
+
if (e.overflow === "hidden" && e.position === "absolute") {
|
|
4158
|
+
const i = parseFloat(e.width), n = parseFloat(e.height);
|
|
4134
4159
|
if (i <= 1 && n <= 1) return !0;
|
|
4135
4160
|
}
|
|
4136
4161
|
return !1;
|
|
4137
4162
|
}
|
|
4138
|
-
function
|
|
4139
|
-
if (g(
|
|
4140
|
-
let a =
|
|
4163
|
+
function Si(e) {
|
|
4164
|
+
if (g(e)) return !0;
|
|
4165
|
+
let a = e;
|
|
4141
4166
|
for (; a; ) {
|
|
4142
|
-
const
|
|
4143
|
-
if (
|
|
4167
|
+
const t = y(a);
|
|
4168
|
+
if (t.display === "none" || t.visibility === "hidden" || xi(t)) return !0;
|
|
4144
4169
|
a = a.parentElement;
|
|
4145
4170
|
}
|
|
4146
4171
|
return !1;
|
|
4147
4172
|
}
|
|
4148
|
-
function
|
|
4149
|
-
let a = 1,
|
|
4150
|
-
for (;
|
|
4151
|
-
const i =
|
|
4152
|
-
isNaN(n) || (a *= n),
|
|
4173
|
+
function ki(e) {
|
|
4174
|
+
let a = 1, t = e;
|
|
4175
|
+
for (; t; ) {
|
|
4176
|
+
const i = y(t), n = parseFloat(i.opacity);
|
|
4177
|
+
isNaN(n) || (a *= n), t = t.parentElement;
|
|
4153
4178
|
}
|
|
4154
4179
|
return a;
|
|
4155
4180
|
}
|
|
4156
|
-
|
|
4157
|
-
|
|
4181
|
+
const Ii = {
|
|
4182
|
+
grayscale: 0,
|
|
4183
|
+
blur: 0,
|
|
4184
|
+
"hue-rotate": 0,
|
|
4185
|
+
invert: 0,
|
|
4186
|
+
sepia: 0,
|
|
4187
|
+
brightness: 1,
|
|
4188
|
+
contrast: 1,
|
|
4189
|
+
saturate: 1,
|
|
4190
|
+
opacity: 1
|
|
4191
|
+
};
|
|
4192
|
+
function Ti(e) {
|
|
4193
|
+
const a = parseFloat(e);
|
|
4194
|
+
return isNaN(a) ? NaN : e.trim().endsWith("%") ? a / 100 : a;
|
|
4195
|
+
}
|
|
4196
|
+
const de = /([a-z-]+)\(([^)]*)\)/g;
|
|
4197
|
+
function me(e) {
|
|
4198
|
+
let a, t = !1;
|
|
4199
|
+
for (de.lastIndex = 0; a = de.exec(e); ) {
|
|
4200
|
+
t = !0;
|
|
4201
|
+
const i = Ii[a[1]];
|
|
4202
|
+
if (i === void 0 || Ti(a[2]) !== i) return !1;
|
|
4203
|
+
}
|
|
4204
|
+
return t;
|
|
4205
|
+
}
|
|
4206
|
+
function Ei(e) {
|
|
4207
|
+
let a = e;
|
|
4158
4208
|
for (; a; ) {
|
|
4159
|
-
const
|
|
4160
|
-
if (i && i !== "none" && i !== "initial") return !0;
|
|
4161
|
-
const n =
|
|
4209
|
+
const t = y(a), i = t.filter;
|
|
4210
|
+
if (i && i !== "none" && i !== "initial" && !me(i)) return !0;
|
|
4211
|
+
const n = t.mixBlendMode;
|
|
4162
4212
|
if (n && n !== "normal" && n !== "initial") return !0;
|
|
4163
|
-
const r =
|
|
4164
|
-
if (r && r !== "none" && r !== "initial") return !0;
|
|
4213
|
+
const r = t.backdropFilter;
|
|
4214
|
+
if (r && r !== "none" && r !== "initial" && !me(r)) return !0;
|
|
4165
4215
|
a = a.parentElement;
|
|
4166
4216
|
}
|
|
4167
4217
|
return !1;
|
|
4168
4218
|
}
|
|
4169
|
-
function
|
|
4170
|
-
return
|
|
4219
|
+
function Ci(e) {
|
|
4220
|
+
return e.closest("select") !== null;
|
|
4171
4221
|
}
|
|
4172
|
-
const
|
|
4222
|
+
const Li = {
|
|
4173
4223
|
id: "color-contrast",
|
|
4174
4224
|
wcag: ["1.4.3"],
|
|
4175
4225
|
level: "AA",
|
|
4176
4226
|
description: "Text elements must have sufficient color contrast against the background.",
|
|
4177
4227
|
guidance: "WCAG SC 1.4.3 requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (>=24px or >=18.66px bold). Increase the contrast by darkening the text or lightening the background, or vice versa.",
|
|
4178
4228
|
prompt: "Suggest changing the text or background color to meet the minimum contrast ratio.",
|
|
4179
|
-
run(
|
|
4180
|
-
const a = [],
|
|
4181
|
-
if (!
|
|
4182
|
-
const i =
|
|
4229
|
+
run(e) {
|
|
4230
|
+
const a = [], t = e.body;
|
|
4231
|
+
if (!t) return [];
|
|
4232
|
+
const i = e.createTreeWalker(t, NodeFilter.SHOW_TEXT), n = /* @__PURE__ */ new Set();
|
|
4183
4233
|
let r;
|
|
4184
4234
|
for (; r = i.nextNode(); ) {
|
|
4185
4235
|
if (!r.textContent || !r.textContent.trim()) continue;
|
|
4186
4236
|
const o = r.parentElement;
|
|
4187
|
-
if (!o || n.has(o) || (n.add(o),
|
|
4237
|
+
if (!o || n.has(o) || (n.add(o), wi.has(o.tagName))) continue;
|
|
4188
4238
|
const s = o.tagName;
|
|
4189
|
-
if (s === "BODY" || s === "HTML" ||
|
|
4190
|
-
const l =
|
|
4191
|
-
if (parseFloat(l.opacity) === 0 ||
|
|
4192
|
-
const
|
|
4193
|
-
if (
|
|
4239
|
+
if (s === "BODY" || s === "HTML" || Ci(o) || yi(o) || Ai(o, e) || Si(o)) continue;
|
|
4240
|
+
const l = y(o);
|
|
4241
|
+
if (parseFloat(l.opacity) === 0 || ki(o) < 0.1) continue;
|
|
4242
|
+
const p = l.textShadow;
|
|
4243
|
+
if (p && p !== "none" && p !== "initial" || Ei(o)) continue;
|
|
4194
4244
|
const c = N(l.color);
|
|
4195
4245
|
if (!c) continue;
|
|
4196
4246
|
const u = l.color.match(/rgba\(.+?,\s*([\d.]+)\s*\)/) || l.color.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);
|
|
4197
|
-
if (u && (u[1].endsWith("%") ? parseFloat(u[1]) / 100 : parseFloat(u[1])) === 0 ||
|
|
4198
|
-
const
|
|
4199
|
-
if (!
|
|
4200
|
-
const b = R(c[0], c[1], c[2]), f = R(
|
|
4201
|
-
if (
|
|
4202
|
-
const x = Math.round(
|
|
4247
|
+
if (u && (u[1].endsWith("%") ? parseFloat(u[1]) / 100 : parseFloat(u[1])) === 0 || je(o)) continue;
|
|
4248
|
+
const h = Be(o);
|
|
4249
|
+
if (!h) continue;
|
|
4250
|
+
const b = R(c[0], c[1], c[2]), f = R(h[0], h[1], h[2]), w = ge(b, f), I = Ue(o) ? 3 : 4.5;
|
|
4251
|
+
if (w < I) {
|
|
4252
|
+
const x = Math.round(w * 100) / 100, M = ue(c), xe = ue(h);
|
|
4203
4253
|
a.push({
|
|
4204
4254
|
ruleId: "color-contrast",
|
|
4205
4255
|
selector: m(o),
|
|
4206
4256
|
html: d(o),
|
|
4207
4257
|
impact: "serious",
|
|
4208
4258
|
message: `Insufficient color contrast ratio of ${x}:1 (required ${I}:1).`,
|
|
4209
|
-
context: `foreground: ${M} rgb(${c.join(", ")}), background: ${
|
|
4259
|
+
context: `foreground: ${M} rgb(${c.join(", ")}), background: ${xe} rgb(${h.join(", ")}), ratio: ${x}:1, required: ${I}:1`
|
|
4210
4260
|
});
|
|
4211
4261
|
}
|
|
4212
4262
|
}
|
|
4213
4263
|
return a;
|
|
4214
4264
|
}
|
|
4215
|
-
},
|
|
4265
|
+
}, ve = [
|
|
4216
4266
|
// Document Structure
|
|
4217
|
-
Zt,
|
|
4218
|
-
ea,
|
|
4219
4267
|
ta,
|
|
4220
4268
|
aa,
|
|
4221
4269
|
ia,
|
|
4270
|
+
na,
|
|
4222
4271
|
ra,
|
|
4223
|
-
|
|
4272
|
+
sa,
|
|
4224
4273
|
la,
|
|
4225
4274
|
ua,
|
|
4275
|
+
ma,
|
|
4226
4276
|
// Images
|
|
4227
|
-
ze,
|
|
4228
4277
|
Ge,
|
|
4229
|
-
Ye,
|
|
4230
4278
|
Xe,
|
|
4279
|
+
Ke,
|
|
4231
4280
|
Je,
|
|
4232
|
-
|
|
4281
|
+
Ze,
|
|
4233
4282
|
et,
|
|
4234
|
-
|
|
4235
|
-
|
|
4283
|
+
at,
|
|
4284
|
+
it,
|
|
4285
|
+
st,
|
|
4236
4286
|
// Forms
|
|
4237
|
-
ut,
|
|
4238
|
-
dt,
|
|
4239
4287
|
mt,
|
|
4240
4288
|
ht,
|
|
4241
|
-
|
|
4242
|
-
|
|
4289
|
+
pt,
|
|
4290
|
+
gt,
|
|
4243
4291
|
xt,
|
|
4244
|
-
|
|
4292
|
+
St,
|
|
4245
4293
|
kt,
|
|
4294
|
+
// Keyboard
|
|
4246
4295
|
Tt,
|
|
4247
|
-
|
|
4248
|
-
Mt,
|
|
4296
|
+
Ct,
|
|
4249
4297
|
Ht,
|
|
4250
|
-
// Structure
|
|
4251
4298
|
Dt,
|
|
4252
|
-
na,
|
|
4253
|
-
da,
|
|
4254
|
-
Ot,
|
|
4255
|
-
Bt,
|
|
4256
4299
|
Ft,
|
|
4300
|
+
// Structure
|
|
4257
4301
|
Wt,
|
|
4302
|
+
oa,
|
|
4303
|
+
ha,
|
|
4304
|
+
Ot,
|
|
4305
|
+
Bt,
|
|
4258
4306
|
_t,
|
|
4259
4307
|
Pt,
|
|
4260
4308
|
jt,
|
|
4261
4309
|
Vt,
|
|
4262
4310
|
zt,
|
|
4263
4311
|
Ut,
|
|
4312
|
+
Gt,
|
|
4264
4313
|
Yt,
|
|
4265
|
-
|
|
4266
|
-
|
|
4314
|
+
Kt,
|
|
4315
|
+
ea,
|
|
4267
4316
|
Jt,
|
|
4317
|
+
Zt,
|
|
4268
4318
|
// ARIA
|
|
4269
|
-
ma,
|
|
4270
|
-
ha,
|
|
4271
4319
|
pa,
|
|
4320
|
+
ga,
|
|
4272
4321
|
ba,
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
Na,
|
|
4322
|
+
va,
|
|
4323
|
+
Sa,
|
|
4324
|
+
Ta,
|
|
4325
|
+
La,
|
|
4326
|
+
qa,
|
|
4279
4327
|
$a,
|
|
4280
4328
|
Ma,
|
|
4281
4329
|
Ha,
|
|
4282
4330
|
Da,
|
|
4283
|
-
Oa,
|
|
4284
|
-
Ba,
|
|
4285
4331
|
Fa,
|
|
4286
4332
|
Wa,
|
|
4333
|
+
Oa,
|
|
4334
|
+
Ba,
|
|
4287
4335
|
_a,
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4336
|
+
Pa,
|
|
4337
|
+
ja,
|
|
4338
|
+
Ua,
|
|
4339
|
+
ya,
|
|
4292
4340
|
Ga,
|
|
4293
|
-
|
|
4294
|
-
|
|
4341
|
+
// Links
|
|
4342
|
+
Xa,
|
|
4343
|
+
Ka,
|
|
4344
|
+
ii,
|
|
4295
4345
|
// Language
|
|
4296
|
-
|
|
4297
|
-
oi,
|
|
4298
|
-
si,
|
|
4346
|
+
ni,
|
|
4299
4347
|
li,
|
|
4300
|
-
// Tables
|
|
4301
4348
|
ci,
|
|
4302
4349
|
ui,
|
|
4350
|
+
// Tables
|
|
4303
4351
|
di,
|
|
4304
4352
|
mi,
|
|
4305
4353
|
hi,
|
|
4306
|
-
// Parsing
|
|
4307
4354
|
pi,
|
|
4308
|
-
// Media
|
|
4309
4355
|
gi,
|
|
4356
|
+
// Parsing
|
|
4310
4357
|
bi,
|
|
4358
|
+
// Media
|
|
4359
|
+
fi,
|
|
4360
|
+
vi,
|
|
4311
4361
|
// Color
|
|
4312
|
-
|
|
4362
|
+
Li
|
|
4313
4363
|
];
|
|
4314
|
-
let G = [],
|
|
4315
|
-
function
|
|
4316
|
-
|
|
4364
|
+
let G = [], we = /* @__PURE__ */ new Set();
|
|
4365
|
+
function $i(e) {
|
|
4366
|
+
e.additionalRules && (G = e.additionalRules), e.disabledRules && (we = new Set(e.disabledRules));
|
|
4317
4367
|
}
|
|
4318
|
-
function
|
|
4319
|
-
return
|
|
4368
|
+
function ye() {
|
|
4369
|
+
return ve.filter((a) => !we.has(a.id)).concat(G);
|
|
4320
4370
|
}
|
|
4321
|
-
function
|
|
4322
|
-
|
|
4323
|
-
const a =
|
|
4371
|
+
function Mi(e) {
|
|
4372
|
+
Ae();
|
|
4373
|
+
const a = ye(), t = [];
|
|
4324
4374
|
let i = 0;
|
|
4325
4375
|
return {
|
|
4326
4376
|
processChunk(n) {
|
|
4327
4377
|
const r = performance.now();
|
|
4328
4378
|
for (; i < a.length; ) {
|
|
4329
4379
|
try {
|
|
4330
|
-
|
|
4380
|
+
t.push(...a[i].run(e));
|
|
4331
4381
|
} catch {
|
|
4332
4382
|
}
|
|
4333
4383
|
if (i++, performance.now() - r >= n) break;
|
|
@@ -4335,55 +4385,55 @@ function qi(t) {
|
|
|
4335
4385
|
return i < a.length;
|
|
4336
4386
|
},
|
|
4337
4387
|
getViolations() {
|
|
4338
|
-
return
|
|
4388
|
+
return t;
|
|
4339
4389
|
}
|
|
4340
4390
|
};
|
|
4341
4391
|
}
|
|
4342
|
-
function
|
|
4343
|
-
|
|
4392
|
+
function Ae() {
|
|
4393
|
+
Ce(), Se(), ke(), Oe(), We(), Le();
|
|
4344
4394
|
}
|
|
4345
|
-
function
|
|
4395
|
+
function Hi(e) {
|
|
4346
4396
|
var i;
|
|
4347
|
-
|
|
4348
|
-
const a =
|
|
4397
|
+
Ae();
|
|
4398
|
+
const a = ye(), t = [];
|
|
4349
4399
|
for (const n of a)
|
|
4350
4400
|
try {
|
|
4351
|
-
|
|
4401
|
+
t.push(...n.run(e));
|
|
4352
4402
|
} catch {
|
|
4353
4403
|
}
|
|
4354
4404
|
return {
|
|
4355
|
-
url: ((i =
|
|
4405
|
+
url: ((i = e.location) == null ? void 0 : i.href) ?? "",
|
|
4356
4406
|
timestamp: Date.now(),
|
|
4357
|
-
violations:
|
|
4407
|
+
violations: t,
|
|
4358
4408
|
ruleCount: a.length
|
|
4359
4409
|
};
|
|
4360
4410
|
}
|
|
4361
|
-
const
|
|
4362
|
-
function
|
|
4363
|
-
const a =
|
|
4364
|
-
return a || G.find((
|
|
4411
|
+
const qi = new Map(ve.map((e) => [e.id, e]));
|
|
4412
|
+
function Di(e) {
|
|
4413
|
+
const a = qi.get(e);
|
|
4414
|
+
return a || G.find((t) => t.id === e);
|
|
4365
4415
|
}
|
|
4366
4416
|
export {
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4417
|
+
Ae as clearAllCaches,
|
|
4418
|
+
We as clearAriaAttrAuditCache,
|
|
4419
|
+
Ce as clearAriaHiddenCache,
|
|
4370
4420
|
Oe as clearColorCaches,
|
|
4371
|
-
|
|
4421
|
+
Se as clearComputedRoleCache,
|
|
4372
4422
|
k as compileDeclarativeRule,
|
|
4373
|
-
|
|
4374
|
-
|
|
4423
|
+
$i as configureRules,
|
|
4424
|
+
Mi as createChunkedAudit,
|
|
4375
4425
|
v as getAccessibleName,
|
|
4376
4426
|
A as getAccessibleTextContent,
|
|
4377
|
-
|
|
4427
|
+
ye as getActiveRules,
|
|
4378
4428
|
q as getComputedRole,
|
|
4379
4429
|
d as getHtmlSnippet,
|
|
4380
|
-
|
|
4381
|
-
|
|
4430
|
+
he as getImplicitRole,
|
|
4431
|
+
Di as getRuleById,
|
|
4382
4432
|
m as getSelector,
|
|
4383
4433
|
g as isAriaHidden,
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4434
|
+
Ee as isValidRole,
|
|
4435
|
+
Ri as querySelectorShadowAware,
|
|
4436
|
+
ve as rules,
|
|
4437
|
+
Hi as runAudit,
|
|
4438
|
+
Ni as validateDeclarativeRule
|
|
4389
4439
|
};
|