@accesslint/core 0.8.0 → 0.8.2
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/README.md +0 -2
- package/dist/index.cjs +5 -5
- package/dist/index.iife.js +5 -5
- package/dist/index.js +281 -243
- package/dist/rules/adaptable/empty-table-header.d.ts.map +1 -1
- package/dist/rules/adaptable/td-has-header.d.ts.map +1 -1
- package/dist/rules/distinguishable/color-contrast-enhanced.d.ts.map +1 -1
- package/dist/rules/distinguishable/color-contrast-helpers.d.ts +4 -0
- package/dist/rules/distinguishable/color-contrast-helpers.d.ts.map +1 -1
- package/dist/rules/distinguishable/color-contrast.d.ts.map +1 -1
- package/dist/rules/distinguishable/link-in-text-block.d.ts.map +1 -1
- package/dist/rules/distinguishable/meta-viewport.d.ts.map +1 -1
- package/dist/rules/engine.d.ts.map +1 -1
- package/dist/rules/index.d.ts +5 -0
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/keyboard-accessible/focus-visible.d.ts.map +1 -1
- package/dist/rules/keyboard-accessible/scrollable-region.d.ts.map +1 -1
- package/dist/rules/labels-and-names/button-name.d.ts.map +1 -1
- package/dist/rules/labels-and-names/form-label.d.ts.map +1 -1
- package/dist/rules/labels-and-names/frame-title.d.ts.map +1 -1
- package/dist/rules/labels-and-names/input-button-name.d.ts.map +1 -1
- package/dist/rules/labels-and-names/label-content-mismatch.d.ts.map +1 -1
- package/dist/rules/navigable/empty-heading.d.ts.map +1 -1
- package/dist/rules/navigable/heading-order.d.ts.map +1 -1
- package/dist/rules/navigable/link-name.d.ts.map +1 -1
- package/dist/rules/navigable/p-as-heading.d.ts.map +1 -1
- package/dist/rules/text-alternatives/image-alt-words.d.ts.map +1 -1
- package/dist/rules/text-alternatives/img-alt.d.ts.map +1 -1
- package/dist/rules/text-alternatives/input-image-alt.d.ts.map +1 -1
- package/dist/rules/text-alternatives/object-alt.d.ts.map +1 -1
- package/dist/rules/text-alternatives/role-img-alt.d.ts.map +1 -1
- package/dist/rules/text-alternatives/svg-img-alt.d.ts.map +1 -1
- package/dist/rules/time-based-media/audio-transcript.d.ts.map +1 -1
- package/dist/rules/time-based-media/video-captions.d.ts.map +1 -1
- package/dist/rules/types.d.ts +8 -1
- package/dist/rules/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const j = [
|
|
2
2
|
"a[href]",
|
|
3
3
|
"button:not([disabled])",
|
|
4
4
|
'input:not([disabled]):not([type="hidden"])',
|
|
@@ -133,22 +133,22 @@ function $e(e) {
|
|
|
133
133
|
return null;
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
-
function
|
|
136
|
+
function U(e) {
|
|
137
137
|
var n;
|
|
138
138
|
const a = K.get(e);
|
|
139
139
|
if (a !== void 0) return a;
|
|
140
140
|
const i = ((n = e.getAttribute("role")) == null ? void 0 : n.trim().toLowerCase()) || null || $e(e);
|
|
141
141
|
return K.set(e, i), i;
|
|
142
142
|
}
|
|
143
|
-
let
|
|
143
|
+
let Q = /* @__PURE__ */ new WeakMap();
|
|
144
144
|
function st() {
|
|
145
|
-
|
|
145
|
+
Q = /* @__PURE__ */ new WeakMap();
|
|
146
146
|
}
|
|
147
|
-
function
|
|
148
|
-
const a =
|
|
147
|
+
function w(e) {
|
|
148
|
+
const a = Q.get(e);
|
|
149
149
|
if (a !== void 0) return a;
|
|
150
150
|
const t = lt(e);
|
|
151
|
-
return
|
|
151
|
+
return Q.set(e, t), t;
|
|
152
152
|
}
|
|
153
153
|
function lt(e) {
|
|
154
154
|
var r, o, s, l, d;
|
|
@@ -313,22 +313,22 @@ const ut = /* @__PURE__ */ new Set([
|
|
|
313
313
|
function q(e) {
|
|
314
314
|
let a = e;
|
|
315
315
|
for (; a; ) {
|
|
316
|
-
if (
|
|
316
|
+
if (He(a)) return !0;
|
|
317
317
|
a = a.parentElement;
|
|
318
318
|
}
|
|
319
319
|
return !1;
|
|
320
320
|
}
|
|
321
|
-
let
|
|
321
|
+
let Z = /* @__PURE__ */ new WeakMap();
|
|
322
322
|
function mt() {
|
|
323
|
-
|
|
323
|
+
Z = /* @__PURE__ */ new WeakMap();
|
|
324
324
|
}
|
|
325
325
|
function h(e) {
|
|
326
|
-
const a =
|
|
326
|
+
const a = Z.get(e);
|
|
327
327
|
if (a !== void 0) return a;
|
|
328
328
|
let t;
|
|
329
|
-
return e.getAttribute("aria-hidden") === "true" || e instanceof HTMLElement && (e.hidden || e.style.display === "none") ? t = !0 : e.parentElement ? t = h(e.parentElement) : t = !1,
|
|
329
|
+
return e.getAttribute("aria-hidden") === "true" || e instanceof HTMLElement && (e.hidden || e.style.display === "none") ? t = !0 : e.parentElement ? t = h(e.parentElement) : t = !1, Z.set(e, t), t;
|
|
330
330
|
}
|
|
331
|
-
function
|
|
331
|
+
function He(e) {
|
|
332
332
|
if (e.getAttribute("aria-hidden") === "true" || e instanceof HTMLElement && e.hidden) return !0;
|
|
333
333
|
if (typeof getComputedStyle == "function") {
|
|
334
334
|
const a = getComputedStyle(e);
|
|
@@ -345,7 +345,7 @@ function k(e) {
|
|
|
345
345
|
a += s.textContent ?? "";
|
|
346
346
|
else if (s.nodeType === 1) {
|
|
347
347
|
const l = s;
|
|
348
|
-
if (!
|
|
348
|
+
if (!He(l)) {
|
|
349
349
|
const d = (t = l.tagName) == null ? void 0 : t.toLowerCase();
|
|
350
350
|
if (d === "img" || d === "area") {
|
|
351
351
|
const c = l.getAttribute("aria-labelledby");
|
|
@@ -373,7 +373,7 @@ function k(e) {
|
|
|
373
373
|
}
|
|
374
374
|
return a;
|
|
375
375
|
}
|
|
376
|
-
function
|
|
376
|
+
function ee(e) {
|
|
377
377
|
let a = "";
|
|
378
378
|
for (const t of e.childNodes)
|
|
379
379
|
if (t.nodeType === 3)
|
|
@@ -383,11 +383,11 @@ function Z(e) {
|
|
|
383
383
|
if (n === "style" || n === "script" || n === "svg" || i.getAttribute("aria-hidden") === "true" || i instanceof HTMLElement && i.style.display === "none") continue;
|
|
384
384
|
const r = i.getAttribute("role");
|
|
385
385
|
if (r === "img" || r === "presentation" || r === "none") continue;
|
|
386
|
-
a +=
|
|
386
|
+
a += ee(i);
|
|
387
387
|
}
|
|
388
388
|
return a;
|
|
389
389
|
}
|
|
390
|
-
function
|
|
390
|
+
function De(e) {
|
|
391
391
|
let a = e;
|
|
392
392
|
for (; a; ) {
|
|
393
393
|
if (a instanceof HTMLElement && a.style.visibility === "hidden") return !0;
|
|
@@ -395,7 +395,7 @@ function Pe(e) {
|
|
|
395
395
|
}
|
|
396
396
|
return !1;
|
|
397
397
|
}
|
|
398
|
-
function
|
|
398
|
+
function Pe(e) {
|
|
399
399
|
var n, r;
|
|
400
400
|
const a = e.getAttribute("aria-labelledby");
|
|
401
401
|
if (a) {
|
|
@@ -413,9 +413,9 @@ function Fe(e) {
|
|
|
413
413
|
function se(e) {
|
|
414
414
|
return e.getRootNode() instanceof ShadowRoot;
|
|
415
415
|
}
|
|
416
|
-
let
|
|
416
|
+
let te = /* @__PURE__ */ new WeakMap();
|
|
417
417
|
function pt() {
|
|
418
|
-
|
|
418
|
+
te = /* @__PURE__ */ new WeakMap();
|
|
419
419
|
}
|
|
420
420
|
function bt(e) {
|
|
421
421
|
return e.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
@@ -447,7 +447,7 @@ function gt(e) {
|
|
|
447
447
|
}
|
|
448
448
|
return a;
|
|
449
449
|
}
|
|
450
|
-
function
|
|
450
|
+
function Y(e) {
|
|
451
451
|
if (e.id) return `#${CSS.escape(e.id)}`;
|
|
452
452
|
const a = e.getRootNode(), t = a instanceof ShadowRoot ? null : a.documentElement;
|
|
453
453
|
if (e === t) return e.tagName.toLowerCase();
|
|
@@ -472,26 +472,26 @@ function G(e) {
|
|
|
472
472
|
}
|
|
473
473
|
function p(e) {
|
|
474
474
|
var r;
|
|
475
|
-
const a =
|
|
475
|
+
const a = te.get(e);
|
|
476
476
|
if (a !== void 0) return a;
|
|
477
477
|
const t = [];
|
|
478
478
|
let i = e;
|
|
479
479
|
for (; i; ) {
|
|
480
480
|
const o = i.getRootNode();
|
|
481
481
|
if (o instanceof ShadowRoot)
|
|
482
|
-
t.unshift({ selector:
|
|
482
|
+
t.unshift({ selector: Y(i), delimiter: " >>> " }), i = o.host;
|
|
483
483
|
else {
|
|
484
484
|
const s = (r = o.defaultView) == null ? void 0 : r.frameElement;
|
|
485
485
|
if (s)
|
|
486
|
-
t.unshift({ selector:
|
|
486
|
+
t.unshift({ selector: Y(i), delimiter: " >>>iframe> " }), i = s;
|
|
487
487
|
else {
|
|
488
|
-
t.unshift({ selector:
|
|
488
|
+
t.unshift({ selector: Y(i), delimiter: "" });
|
|
489
489
|
break;
|
|
490
490
|
}
|
|
491
491
|
}
|
|
492
492
|
}
|
|
493
493
|
const n = t.map((o, s) => (s === 0 ? "" : o.delimiter) + o.selector).join("");
|
|
494
|
-
return
|
|
494
|
+
return te.set(e, n), n;
|
|
495
495
|
}
|
|
496
496
|
function Un(e) {
|
|
497
497
|
const a = [], t = [];
|
|
@@ -609,7 +609,7 @@ const ft = /* @__PURE__ */ new Set([
|
|
|
609
609
|
"aria-valuemax",
|
|
610
610
|
"aria-valuemin",
|
|
611
611
|
"aria-valuenow"
|
|
612
|
-
]),
|
|
612
|
+
]), X = {
|
|
613
613
|
"aria-autocomplete": /* @__PURE__ */ new Set(["inline", "list", "both", "none"]),
|
|
614
614
|
"aria-expanded": /* @__PURE__ */ new Set(["true", "false", "undefined"]),
|
|
615
615
|
"aria-current": /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"]),
|
|
@@ -781,10 +781,10 @@ function le(e) {
|
|
|
781
781
|
fix: { type: "suggest", suggestion: `Set ${c.name} to a valid number value` }
|
|
782
782
|
});
|
|
783
783
|
}
|
|
784
|
-
} else if (
|
|
784
|
+
} else if (X[c.name]) {
|
|
785
785
|
const b = u.split(/\s+/);
|
|
786
786
|
for (const g of b)
|
|
787
|
-
if (!
|
|
787
|
+
if (!X[c.name].has(g)) {
|
|
788
788
|
const f = d();
|
|
789
789
|
t.push({
|
|
790
790
|
ruleId: "aria/aria-valid-attr-value",
|
|
@@ -792,7 +792,7 @@ function le(e) {
|
|
|
792
792
|
html: f.html,
|
|
793
793
|
impact: "critical",
|
|
794
794
|
message: `Invalid value "${u}" for ${c.name}.`,
|
|
795
|
-
fix: { type: "suggest", suggestion: `Set ${c.name} to one of: ${[...
|
|
795
|
+
fix: { type: "suggest", suggestion: `Set ${c.name} to one of: ${[...X[c.name]].join(", ")}` }
|
|
796
796
|
});
|
|
797
797
|
break;
|
|
798
798
|
}
|
|
@@ -818,14 +818,14 @@ function le(e) {
|
|
|
818
818
|
if (fe.has(c)) {
|
|
819
819
|
const g = r.hasAttribute("aria-label"), f = r.hasAttribute("aria-labelledby");
|
|
820
820
|
if (g || f) {
|
|
821
|
-
const v = d(),
|
|
821
|
+
const v = d(), y = g ? "aria-label" : "aria-labelledby";
|
|
822
822
|
i.push({
|
|
823
823
|
ruleId: "aria/aria-prohibited-attr",
|
|
824
824
|
selector: v.selector,
|
|
825
825
|
html: v.html,
|
|
826
826
|
impact: "serious",
|
|
827
827
|
message: `aria-label and aria-labelledby are prohibited on role "${c}".`,
|
|
828
|
-
fix: { type: "remove-attribute", attribute:
|
|
828
|
+
fix: { type: "remove-attribute", attribute: y }
|
|
829
829
|
});
|
|
830
830
|
}
|
|
831
831
|
}
|
|
@@ -851,13 +851,13 @@ function le(e) {
|
|
|
851
851
|
}
|
|
852
852
|
return P = new WeakRef(e), F = { validAttr: a, validAttrValue: t, prohibitedAttr: i }, F;
|
|
853
853
|
}
|
|
854
|
-
let
|
|
854
|
+
let ae = /* @__PURE__ */ new WeakMap(), ie = /* @__PURE__ */ new WeakMap(), ne = /* @__PURE__ */ new WeakMap();
|
|
855
855
|
function kt() {
|
|
856
|
-
|
|
856
|
+
ae = /* @__PURE__ */ new WeakMap(), ie = /* @__PURE__ */ new WeakMap(), ne = /* @__PURE__ */ new WeakMap();
|
|
857
857
|
}
|
|
858
|
-
function
|
|
859
|
-
let a =
|
|
860
|
-
return a || (a = getComputedStyle(e),
|
|
858
|
+
function x(e) {
|
|
859
|
+
let a = ae.get(e);
|
|
860
|
+
return a || (a = getComputedStyle(e), ae.set(e, a), a);
|
|
861
861
|
}
|
|
862
862
|
function I(e, a, t) {
|
|
863
863
|
const [i, n, r] = [e, a, t].map((o) => {
|
|
@@ -909,7 +909,7 @@ function C(e) {
|
|
|
909
909
|
);
|
|
910
910
|
return r ? [parseInt(r[1]), parseInt(r[2]), parseInt(r[3])] : null;
|
|
911
911
|
}
|
|
912
|
-
function
|
|
912
|
+
function W(e) {
|
|
913
913
|
const a = e.match(/rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)/);
|
|
914
914
|
if (a) return parseFloat(a[1]);
|
|
915
915
|
const t = e.match(/rgba?\([^)]+\/\s*([\d.]+%?)\s*\)/);
|
|
@@ -927,12 +927,12 @@ function R(e, a, t) {
|
|
|
927
927
|
];
|
|
928
928
|
}
|
|
929
929
|
function ye(e) {
|
|
930
|
-
const a =
|
|
930
|
+
const a = ie.get(e);
|
|
931
931
|
if (a !== void 0) return a;
|
|
932
932
|
const t = St(e);
|
|
933
|
-
return
|
|
933
|
+
return ie.set(e, t), t;
|
|
934
934
|
}
|
|
935
|
-
function
|
|
935
|
+
function J(e, a) {
|
|
936
936
|
let t = a;
|
|
937
937
|
for (let i = e.length - 1; i >= 0; i--)
|
|
938
938
|
t = R(e[i].color, t, e[i].alpha);
|
|
@@ -942,13 +942,13 @@ function St(e) {
|
|
|
942
942
|
const a = [];
|
|
943
943
|
let t = e;
|
|
944
944
|
for (; t; ) {
|
|
945
|
-
const n =
|
|
945
|
+
const n = x(t), r = n.backgroundImage;
|
|
946
946
|
if (r && r !== "none" && r !== "initial") {
|
|
947
947
|
const d = n.backgroundColor;
|
|
948
948
|
if (d && d !== "transparent" && d !== "rgba(0, 0, 0, 0)" && d !== "rgba(0 0 0 / 0)") {
|
|
949
949
|
const c = C(d);
|
|
950
950
|
if (c)
|
|
951
|
-
return a.length > 0 ?
|
|
951
|
+
return a.length > 0 ? J(a, c) : c;
|
|
952
952
|
}
|
|
953
953
|
return null;
|
|
954
954
|
}
|
|
@@ -957,7 +957,7 @@ function St(e) {
|
|
|
957
957
|
t = t.parentElement;
|
|
958
958
|
continue;
|
|
959
959
|
}
|
|
960
|
-
const s =
|
|
960
|
+
const s = W(o);
|
|
961
961
|
if (s < 0.01) {
|
|
962
962
|
t = t.parentElement;
|
|
963
963
|
continue;
|
|
@@ -968,13 +968,13 @@ function St(e) {
|
|
|
968
968
|
continue;
|
|
969
969
|
}
|
|
970
970
|
if (s >= 1)
|
|
971
|
-
return a.length > 0 ?
|
|
971
|
+
return a.length > 0 ? J(a, l) : l;
|
|
972
972
|
a.push({ color: l, alpha: s }), t = t.parentElement;
|
|
973
973
|
}
|
|
974
974
|
const i = [255, 255, 255];
|
|
975
|
-
return a.length > 0 ?
|
|
975
|
+
return a.length > 0 ? J(a, i) : i;
|
|
976
976
|
}
|
|
977
|
-
function
|
|
977
|
+
function Fe(e) {
|
|
978
978
|
const a = [];
|
|
979
979
|
let t = 0, i = 0;
|
|
980
980
|
for (let n = 0; n < e.length; n++)
|
|
@@ -989,7 +989,7 @@ function It(e, a = [255, 255, 255]) {
|
|
|
989
989
|
let r = 1, o = n + 1;
|
|
990
990
|
for (; o < e.length && r > 0; o++)
|
|
991
991
|
e[o] === "(" ? r++ : e[o] === ")" && r--;
|
|
992
|
-
const s = e.slice(n + 1, o - 1), l =
|
|
992
|
+
const s = e.slice(n + 1, o - 1), l = Fe(s);
|
|
993
993
|
for (const d of l) {
|
|
994
994
|
const c = d.trim();
|
|
995
995
|
if (/^(to\s|[\d.]+deg|[\d.]+turn|[\d.]+rad)/i.test(c)) continue;
|
|
@@ -1003,28 +1003,28 @@ function It(e, a = [255, 255, 255]) {
|
|
|
1003
1003
|
return t;
|
|
1004
1004
|
}
|
|
1005
1005
|
const qt = /* @__PURE__ */ new Set(["IMG", "PICTURE", "VIDEO", "SVG"]);
|
|
1006
|
-
function
|
|
1007
|
-
const a =
|
|
1006
|
+
function Et(e) {
|
|
1007
|
+
const a = ne.get(e);
|
|
1008
1008
|
if (a !== void 0) return a;
|
|
1009
1009
|
const t = Rt(e);
|
|
1010
|
-
return
|
|
1010
|
+
return ne.set(e, t), t;
|
|
1011
1011
|
}
|
|
1012
|
-
function
|
|
1012
|
+
function Lt(e) {
|
|
1013
1013
|
return qt.has(e.tagName) ? !0 : !!e.querySelector("img, picture, video, svg");
|
|
1014
1014
|
}
|
|
1015
1015
|
function Rt(e) {
|
|
1016
1016
|
let a = e, t = !1;
|
|
1017
1017
|
for (; a; ) {
|
|
1018
|
-
const i =
|
|
1018
|
+
const i = x(a).position;
|
|
1019
1019
|
if ((i === "absolute" || i === "fixed") && (t = !0), a !== e && i !== "static") {
|
|
1020
1020
|
for (const n of a.children) {
|
|
1021
1021
|
if (n === e || n.contains(e)) continue;
|
|
1022
|
-
if (
|
|
1022
|
+
if (Lt(n)) {
|
|
1023
1023
|
if (t) return !0;
|
|
1024
|
-
const o =
|
|
1024
|
+
const o = x(n).position;
|
|
1025
1025
|
if (o === "absolute" || o === "fixed") return !0;
|
|
1026
1026
|
}
|
|
1027
|
-
const r =
|
|
1027
|
+
const r = x(n);
|
|
1028
1028
|
if (r.position === "absolute" || r.position === "fixed") {
|
|
1029
1029
|
const o = r.backgroundImage;
|
|
1030
1030
|
if (o && o !== "none" && o !== "initial") return !0;
|
|
@@ -1041,11 +1041,11 @@ function Ct(e) {
|
|
|
1041
1041
|
return e.endsWith("pt") ? a * (4 / 3) : a;
|
|
1042
1042
|
}
|
|
1043
1043
|
function xe(e) {
|
|
1044
|
-
const a =
|
|
1044
|
+
const a = x(e), t = Ct(a.fontSize), i = parseInt(a.fontWeight) || (a.fontWeight === "bold" ? 700 : 400);
|
|
1045
1045
|
return t >= 23.5 || t >= 18.5 && i >= 700;
|
|
1046
1046
|
}
|
|
1047
1047
|
function Tt(e) {
|
|
1048
|
-
const a =
|
|
1048
|
+
const a = Fe(e), t = [];
|
|
1049
1049
|
for (const i of a) {
|
|
1050
1050
|
const n = i.trim();
|
|
1051
1051
|
if (!n) continue;
|
|
@@ -1059,7 +1059,7 @@ function Tt(e) {
|
|
|
1059
1059
|
function we(e) {
|
|
1060
1060
|
return e === "transparent" || e === "rgba(0, 0, 0, 0)" || e === "rgba(0 0 0 / 0)";
|
|
1061
1061
|
}
|
|
1062
|
-
function
|
|
1062
|
+
function _([e, a, t]) {
|
|
1063
1063
|
return "#" + [e, a, t].map((i) => i.toString(16).padStart(2, "0")).join("");
|
|
1064
1064
|
}
|
|
1065
1065
|
function Nt(e, a, t) {
|
|
@@ -1074,7 +1074,7 @@ function Nt(e, a, t) {
|
|
|
1074
1074
|
function Mt(e) {
|
|
1075
1075
|
let a = 1, t = e;
|
|
1076
1076
|
for (; t; ) {
|
|
1077
|
-
const i =
|
|
1077
|
+
const i = x(t), n = parseFloat(i.opacity);
|
|
1078
1078
|
isNaN(n) || (a *= n), t = t.parentElement;
|
|
1079
1079
|
}
|
|
1080
1080
|
return a;
|
|
@@ -1087,7 +1087,7 @@ function $t(e) {
|
|
|
1087
1087
|
const r = getComputedStyle(a, n), o = r.content;
|
|
1088
1088
|
if (!o || o === "none" || o === "normal" || o === '""') continue;
|
|
1089
1089
|
const s = r.backgroundColor;
|
|
1090
|
-
if (s && !we(s) &&
|
|
1090
|
+
if (s && !we(s) && W(s) >= 0.1) return !0;
|
|
1091
1091
|
const l = r.backgroundImage;
|
|
1092
1092
|
if (l && l !== "none" && l !== "initial") return !0;
|
|
1093
1093
|
const d = r.position;
|
|
@@ -1097,18 +1097,18 @@ function $t(e) {
|
|
|
1097
1097
|
}
|
|
1098
1098
|
} catch {
|
|
1099
1099
|
}
|
|
1100
|
-
const i =
|
|
1101
|
-
if (i && !we(i) &&
|
|
1100
|
+
const i = x(a).backgroundColor;
|
|
1101
|
+
if (i && !we(i) && W(i) >= 1) break;
|
|
1102
1102
|
a = a.parentElement;
|
|
1103
1103
|
}
|
|
1104
1104
|
return !1;
|
|
1105
1105
|
}
|
|
1106
|
-
const
|
|
1106
|
+
const G = /* @__PURE__ */ new Map();
|
|
1107
1107
|
function Wn(e, a) {
|
|
1108
|
-
|
|
1108
|
+
G.set(e, a), re.delete(e);
|
|
1109
1109
|
}
|
|
1110
|
-
function
|
|
1111
|
-
const t =
|
|
1110
|
+
function Ht(e, a) {
|
|
1111
|
+
const t = G.get(a);
|
|
1112
1112
|
return t ? e.map((i) => {
|
|
1113
1113
|
const n = t[i.id];
|
|
1114
1114
|
return n ? {
|
|
@@ -1119,20 +1119,20 @@ function Dt(e, a) {
|
|
|
1119
1119
|
}) : e;
|
|
1120
1120
|
}
|
|
1121
1121
|
const re = /* @__PURE__ */ new Map();
|
|
1122
|
-
function
|
|
1122
|
+
function Dt(e) {
|
|
1123
1123
|
return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1124
1124
|
}
|
|
1125
|
-
function
|
|
1125
|
+
function Pt(e) {
|
|
1126
1126
|
const a = e.split(/\{(\d+)\}/);
|
|
1127
1127
|
let t = "^";
|
|
1128
1128
|
for (let i = 0; i < a.length; i++)
|
|
1129
|
-
i % 2 === 0 ? t +=
|
|
1129
|
+
i % 2 === 0 ? t += Dt(a[i]) : t += "(.+?)";
|
|
1130
1130
|
return t += "$", new RegExp(t);
|
|
1131
1131
|
}
|
|
1132
|
-
function
|
|
1132
|
+
function Ft(e, a) {
|
|
1133
1133
|
let t = re.get(e);
|
|
1134
1134
|
if (t || (t = /* @__PURE__ */ new Map(), re.set(e, t)), t.has(a)) return t.get(a);
|
|
1135
|
-
const i =
|
|
1135
|
+
const i = G.get(e);
|
|
1136
1136
|
if (!i) return;
|
|
1137
1137
|
const n = i[a];
|
|
1138
1138
|
if (!(n != null && n.messages))
|
|
@@ -1140,13 +1140,13 @@ function Ht(e, a) {
|
|
|
1140
1140
|
const r = [];
|
|
1141
1141
|
for (const [o, s] of Object.entries(n.messages))
|
|
1142
1142
|
r.push({
|
|
1143
|
-
regex:
|
|
1143
|
+
regex: Pt(o),
|
|
1144
1144
|
translated: s
|
|
1145
1145
|
});
|
|
1146
1146
|
return t.set(a, r), r;
|
|
1147
1147
|
}
|
|
1148
1148
|
function zt(e, a, t) {
|
|
1149
|
-
const i =
|
|
1149
|
+
const i = Ft(t, e);
|
|
1150
1150
|
if (!i) return a;
|
|
1151
1151
|
for (const { regex: n, translated: r } of i) {
|
|
1152
1152
|
const o = a.match(n);
|
|
@@ -1159,7 +1159,7 @@ function zt(e, a, t) {
|
|
|
1159
1159
|
return a;
|
|
1160
1160
|
}
|
|
1161
1161
|
function ze(e, a) {
|
|
1162
|
-
return
|
|
1162
|
+
return G.has(a) ? e.map((t) => {
|
|
1163
1163
|
const i = zt(t.ruleId, t.message, a);
|
|
1164
1164
|
return i === t.message ? t : { ...t, message: i };
|
|
1165
1165
|
}) : e;
|
|
@@ -1191,12 +1191,13 @@ const jt = {
|
|
|
1191
1191
|
wcag: ["1.1.1"],
|
|
1192
1192
|
level: "A",
|
|
1193
1193
|
fixability: "contextual",
|
|
1194
|
+
browserHint: "Screenshot the image to describe its visual content for alt text.",
|
|
1194
1195
|
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'.`,
|
|
1195
1196
|
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.",
|
|
1196
1197
|
run(e) {
|
|
1197
1198
|
const a = [];
|
|
1198
1199
|
for (const t of e.querySelectorAll("img")) {
|
|
1199
|
-
if (h(t) ||
|
|
1200
|
+
if (h(t) || De(t)) continue;
|
|
1200
1201
|
const i = t.getAttribute("role");
|
|
1201
1202
|
if (i === "presentation" || i === "none") {
|
|
1202
1203
|
const r = t.getAttribute("tabindex");
|
|
@@ -1215,7 +1216,7 @@ const jt = {
|
|
|
1215
1216
|
});
|
|
1216
1217
|
continue;
|
|
1217
1218
|
}
|
|
1218
|
-
!t.hasAttribute("alt") && !
|
|
1219
|
+
!t.hasAttribute("alt") && !w(t) && a.push({
|
|
1219
1220
|
ruleId: "text-alternatives/img-alt",
|
|
1220
1221
|
selector: p(t),
|
|
1221
1222
|
html: m(t),
|
|
@@ -1230,7 +1231,7 @@ const jt = {
|
|
|
1230
1231
|
};
|
|
1231
1232
|
function Ut(e) {
|
|
1232
1233
|
var i;
|
|
1233
|
-
const a =
|
|
1234
|
+
const a = Pe(e);
|
|
1234
1235
|
if (a) return a;
|
|
1235
1236
|
const t = e.querySelector("title");
|
|
1236
1237
|
return (i = t == null ? void 0 : t.textContent) != null && i.trim() ? t.textContent.trim() : "";
|
|
@@ -1242,6 +1243,7 @@ const Wt = {
|
|
|
1242
1243
|
wcag: ["1.1.1"],
|
|
1243
1244
|
level: "A",
|
|
1244
1245
|
fixability: "contextual",
|
|
1246
|
+
browserHint: "Screenshot the SVG to understand its content, then add a title element or aria-label.",
|
|
1245
1247
|
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.",
|
|
1246
1248
|
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.",
|
|
1247
1249
|
run(e) {
|
|
@@ -1269,12 +1271,13 @@ const Wt = {
|
|
|
1269
1271
|
wcag: ["1.1.1", "4.1.2"],
|
|
1270
1272
|
level: "A",
|
|
1271
1273
|
fixability: "contextual",
|
|
1274
|
+
browserHint: "Screenshot the image button to see its icon, then set alt to describe the action (e.g., 'Search', 'Submit').",
|
|
1272
1275
|
description: 'Image inputs (<input type="image">) must have alternate text describing the button action.',
|
|
1273
1276
|
guidance: "Image buttons (<input type='image'>) act as submit buttons with a custom image. Add alt text via alt, aria-label, or aria-labelledby that describes the action (e.g. alt='Search' or alt='Submit order'), not the image itself. Without it, screen readers announce only 'image' or the filename, giving no clue what the button does.",
|
|
1274
1277
|
run(e) {
|
|
1275
1278
|
const a = [];
|
|
1276
1279
|
for (const t of e.querySelectorAll('input[type="image"]'))
|
|
1277
|
-
h(t) ||
|
|
1280
|
+
h(t) || w(t) || a.push({
|
|
1278
1281
|
ruleId: "text-alternatives/input-image-alt",
|
|
1279
1282
|
selector: p(t),
|
|
1280
1283
|
html: m(t),
|
|
@@ -1325,6 +1328,7 @@ const Wt = {
|
|
|
1325
1328
|
level: "A",
|
|
1326
1329
|
tags: ["best-practice"],
|
|
1327
1330
|
fixability: "contextual",
|
|
1331
|
+
browserHint: "Screenshot the image to verify the alt text accurately describes it without filler words like 'image of'.",
|
|
1328
1332
|
description: "Image alt text should not contain words like 'image', 'photo', or 'picture' — screen readers already announce the element type.",
|
|
1329
1333
|
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'.",
|
|
1330
1334
|
run(e) {
|
|
@@ -1357,7 +1361,7 @@ const Wt = {
|
|
|
1357
1361
|
const a = [];
|
|
1358
1362
|
for (const t of e.querySelectorAll("area[href]")) {
|
|
1359
1363
|
if (h(t)) continue;
|
|
1360
|
-
|
|
1364
|
+
w(t) || a.push({
|
|
1361
1365
|
ruleId: "text-alternatives/area-alt",
|
|
1362
1366
|
selector: p(t),
|
|
1363
1367
|
html: m(t),
|
|
@@ -1375,13 +1379,14 @@ const Wt = {
|
|
|
1375
1379
|
wcag: ["1.1.1"],
|
|
1376
1380
|
level: "A",
|
|
1377
1381
|
fixability: "contextual",
|
|
1382
|
+
browserHint: "Screenshot the embedded object to see its content, then add aria-label or title describing it.",
|
|
1378
1383
|
description: "<object> elements must have alternative text.",
|
|
1379
1384
|
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.",
|
|
1380
1385
|
run(e) {
|
|
1381
1386
|
var t;
|
|
1382
1387
|
const a = [];
|
|
1383
1388
|
for (const i of e.querySelectorAll("object")) {
|
|
1384
|
-
if (h(i) ||
|
|
1389
|
+
if (h(i) || De(i) || i.getAttribute("role") === "presentation" || i.getAttribute("role") === "none" || Pe(i)) continue;
|
|
1385
1390
|
const n = i.getAttribute("data") || "";
|
|
1386
1391
|
if (!((i.getAttribute("type") || "").startsWith("image/") || /\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(n))) {
|
|
1387
1392
|
const s = i.querySelector("img[alt]");
|
|
@@ -1401,16 +1406,18 @@ const Wt = {
|
|
|
1401
1406
|
}, Xt = {
|
|
1402
1407
|
id: "text-alternatives/role-img-alt",
|
|
1403
1408
|
category: "text-alternatives",
|
|
1409
|
+
actRuleIds: ["23a2a8"],
|
|
1404
1410
|
wcag: ["1.1.1"],
|
|
1405
1411
|
level: "A",
|
|
1406
1412
|
fixability: "contextual",
|
|
1413
|
+
browserHint: "Screenshot the element to see its visual appearance, then provide an aria-label describing what it represents.",
|
|
1407
1414
|
description: "Elements with role='img' must have an accessible name.",
|
|
1408
1415
|
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.",
|
|
1409
1416
|
run(e) {
|
|
1410
1417
|
const a = [];
|
|
1411
1418
|
for (const t of e.querySelectorAll('[role="img"]')) {
|
|
1412
1419
|
if (h(t) || t.tagName.toLowerCase() === "svg" || t.tagName.toLowerCase() === "img") continue;
|
|
1413
|
-
|
|
1420
|
+
w(t) || a.push({
|
|
1414
1421
|
ruleId: "text-alternatives/role-img-alt",
|
|
1415
1422
|
selector: p(t),
|
|
1416
1423
|
html: m(t),
|
|
@@ -1421,13 +1428,14 @@ const Wt = {
|
|
|
1421
1428
|
}
|
|
1422
1429
|
return a;
|
|
1423
1430
|
}
|
|
1424
|
-
},
|
|
1431
|
+
}, Jt = {
|
|
1425
1432
|
id: "time-based-media/video-captions",
|
|
1426
1433
|
category: "time-based-media",
|
|
1427
1434
|
actRuleIds: ["eac66b"],
|
|
1428
1435
|
wcag: ["1.2.2"],
|
|
1429
1436
|
level: "A",
|
|
1430
1437
|
fixability: "contextual",
|
|
1438
|
+
browserHint: "Screenshot the video element to see its poster or content for context when writing captions.",
|
|
1431
1439
|
description: "Video elements must have captions via <track kind='captions'> or <track kind='subtitles'>.",
|
|
1432
1440
|
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.",
|
|
1433
1441
|
run(e) {
|
|
@@ -1444,13 +1452,14 @@ const Wt = {
|
|
|
1444
1452
|
}
|
|
1445
1453
|
return a;
|
|
1446
1454
|
}
|
|
1447
|
-
},
|
|
1455
|
+
}, Kt = {
|
|
1448
1456
|
id: "time-based-media/audio-transcript",
|
|
1449
1457
|
category: "time-based-media",
|
|
1450
1458
|
actRuleIds: ["e7aa44"],
|
|
1451
1459
|
wcag: ["1.2.1"],
|
|
1452
1460
|
level: "A",
|
|
1453
1461
|
fixability: "contextual",
|
|
1462
|
+
browserHint: "Inspect the page around the audio element for existing transcript links or associated text content.",
|
|
1454
1463
|
description: "Audio elements should have a text alternative or transcript.",
|
|
1455
1464
|
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.",
|
|
1456
1465
|
run(e) {
|
|
@@ -1619,7 +1628,7 @@ function ra(e) {
|
|
|
1619
1628
|
return `Unknown check type: ${String(e.type)}`;
|
|
1620
1629
|
}
|
|
1621
1630
|
}
|
|
1622
|
-
function
|
|
1631
|
+
function L(e, a, t) {
|
|
1623
1632
|
let i = e;
|
|
1624
1633
|
if (i.includes("{{tag}}") && (i = i.replace(/\{\{tag\}\}/g, a.tagName.toLowerCase())), i.includes("{{value}}")) {
|
|
1625
1634
|
let n = "";
|
|
@@ -1637,6 +1646,7 @@ function N(e) {
|
|
|
1637
1646
|
level: e.level,
|
|
1638
1647
|
tags: e.tags,
|
|
1639
1648
|
fixability: e.fixability,
|
|
1649
|
+
browserHint: e.browserHint,
|
|
1640
1650
|
description: e.description,
|
|
1641
1651
|
guidance: e.guidance,
|
|
1642
1652
|
run(t) {
|
|
@@ -1650,7 +1660,7 @@ function N(e) {
|
|
|
1650
1660
|
selector: p(o),
|
|
1651
1661
|
html: m(o),
|
|
1652
1662
|
impact: e.impact,
|
|
1653
|
-
message:
|
|
1663
|
+
message: L(e.message, o, e.check),
|
|
1654
1664
|
...e.fix ? { fix: e.fix } : {},
|
|
1655
1665
|
element: o
|
|
1656
1666
|
});
|
|
@@ -1666,7 +1676,7 @@ function N(e) {
|
|
|
1666
1676
|
selector: p(d),
|
|
1667
1677
|
html: m(d),
|
|
1668
1678
|
impact: e.impact,
|
|
1669
|
-
message:
|
|
1679
|
+
message: L(e.message, d, e.check),
|
|
1670
1680
|
...e.fix ? { fix: e.fix } : {},
|
|
1671
1681
|
element: d
|
|
1672
1682
|
});
|
|
@@ -1681,7 +1691,7 @@ function N(e) {
|
|
|
1681
1691
|
selector: p(s),
|
|
1682
1692
|
html: m(s),
|
|
1683
1693
|
impact: e.impact,
|
|
1684
|
-
message:
|
|
1694
|
+
message: L(e.message, s, e.check),
|
|
1685
1695
|
...e.fix ? { fix: e.fix } : {},
|
|
1686
1696
|
element: s
|
|
1687
1697
|
});
|
|
@@ -1705,7 +1715,7 @@ function N(e) {
|
|
|
1705
1715
|
selector: p(u),
|
|
1706
1716
|
html: m(u),
|
|
1707
1717
|
impact: e.impact,
|
|
1708
|
-
message:
|
|
1718
|
+
message: L(e.message, u, e.check),
|
|
1709
1719
|
...e.fix ? { fix: e.fix } : {},
|
|
1710
1720
|
element: u
|
|
1711
1721
|
}) : !d && g && i.push({
|
|
@@ -1713,7 +1723,7 @@ function N(e) {
|
|
|
1713
1723
|
selector: p(u),
|
|
1714
1724
|
html: m(u),
|
|
1715
1725
|
impact: e.impact,
|
|
1716
|
-
message:
|
|
1726
|
+
message: L(e.message, u, e.check),
|
|
1717
1727
|
...e.fix ? { fix: e.fix } : {},
|
|
1718
1728
|
element: u
|
|
1719
1729
|
});
|
|
@@ -1728,7 +1738,7 @@ function N(e) {
|
|
|
1728
1738
|
selector: p(s),
|
|
1729
1739
|
html: m(s),
|
|
1730
1740
|
impact: e.impact,
|
|
1731
|
-
message:
|
|
1741
|
+
message: L(e.message, s, e.check),
|
|
1732
1742
|
...e.fix ? { fix: e.fix } : {},
|
|
1733
1743
|
element: s
|
|
1734
1744
|
});
|
|
@@ -1770,7 +1780,7 @@ function N(e) {
|
|
|
1770
1780
|
selector: p(b),
|
|
1771
1781
|
html: m(b),
|
|
1772
1782
|
impact: e.impact,
|
|
1773
|
-
message:
|
|
1783
|
+
message: L(e.message, b, e.check),
|
|
1774
1784
|
...e.fix ? { fix: e.fix } : {},
|
|
1775
1785
|
element: b
|
|
1776
1786
|
});
|
|
@@ -1889,7 +1899,7 @@ function je(e, a) {
|
|
|
1889
1899
|
return NaN;
|
|
1890
1900
|
}
|
|
1891
1901
|
}
|
|
1892
|
-
function
|
|
1902
|
+
function B(e) {
|
|
1893
1903
|
return isNaN(e) ? !1 : (e = (e % 360 + 360) % 360, e >= 85 && e <= 95 || e >= 265 && e <= 275);
|
|
1894
1904
|
}
|
|
1895
1905
|
function pa(e) {
|
|
@@ -1898,21 +1908,21 @@ function pa(e) {
|
|
|
1898
1908
|
);
|
|
1899
1909
|
if (a) {
|
|
1900
1910
|
const n = je(parseFloat(a[1]), a[2]);
|
|
1901
|
-
if (
|
|
1911
|
+
if (B(n)) return !0;
|
|
1902
1912
|
}
|
|
1903
1913
|
const t = e.match(
|
|
1904
1914
|
/matrix\(\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)/i
|
|
1905
1915
|
);
|
|
1906
1916
|
if (t) {
|
|
1907
1917
|
const n = parseFloat(t[1]), r = parseFloat(t[2]), o = Math.atan2(r, n) * (180 / Math.PI);
|
|
1908
|
-
if (
|
|
1918
|
+
if (B(o)) return !0;
|
|
1909
1919
|
}
|
|
1910
1920
|
const i = e.match(
|
|
1911
1921
|
/matrix3d\(\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)/i
|
|
1912
1922
|
);
|
|
1913
1923
|
if (i) {
|
|
1914
1924
|
const n = parseFloat(i[1]), r = parseFloat(i[2]), o = Math.atan2(r, n) * (180 / Math.PI);
|
|
1915
|
-
if (
|
|
1925
|
+
if (B(o)) return !0;
|
|
1916
1926
|
}
|
|
1917
1927
|
return !1;
|
|
1918
1928
|
}
|
|
@@ -1920,7 +1930,7 @@ function ba(e) {
|
|
|
1920
1930
|
const a = e.match(/(-?[\d.]+)(deg|rad|turn|grad)/i);
|
|
1921
1931
|
if (!a) return !1;
|
|
1922
1932
|
const t = je(parseFloat(a[1]), a[2]);
|
|
1923
|
-
return
|
|
1933
|
+
return B(t);
|
|
1924
1934
|
}
|
|
1925
1935
|
const ha = {
|
|
1926
1936
|
id: "adaptable/orientation-lock",
|
|
@@ -1997,14 +2007,14 @@ function fa(e, a) {
|
|
|
1997
2007
|
for (const s of e.querySelectorAll("*")) {
|
|
1998
2008
|
if (h(s)) continue;
|
|
1999
2009
|
r = !0;
|
|
2000
|
-
const l =
|
|
2010
|
+
const l = U(s);
|
|
2001
2011
|
l && n.add(l);
|
|
2002
2012
|
}
|
|
2003
2013
|
for (const s of t) {
|
|
2004
2014
|
const l = i.getElementById(s);
|
|
2005
2015
|
if (l && !h(l)) {
|
|
2006
2016
|
r = !0;
|
|
2007
|
-
const d =
|
|
2017
|
+
const d = U(l);
|
|
2008
2018
|
d && n.add(d);
|
|
2009
2019
|
}
|
|
2010
2020
|
}
|
|
@@ -2081,7 +2091,7 @@ const va = {
|
|
|
2081
2091
|
const r = Se[n];
|
|
2082
2092
|
let o = i.parentElement, s = !1;
|
|
2083
2093
|
for (; o && o !== e.documentElement; ) {
|
|
2084
|
-
const l =
|
|
2094
|
+
const l = U(o);
|
|
2085
2095
|
if (l && r.includes(l)) {
|
|
2086
2096
|
s = !0;
|
|
2087
2097
|
break;
|
|
@@ -2187,6 +2197,7 @@ const ka = {
|
|
|
2187
2197
|
wcag: ["1.3.1"],
|
|
2188
2198
|
level: "A",
|
|
2189
2199
|
fixability: "contextual",
|
|
2200
|
+
browserHint: "Screenshot the table to understand its visual layout, then add scope or headers attributes to associate data cells with headers.",
|
|
2190
2201
|
description: "Data cells in tables larger than 3x3 should have associated headers.",
|
|
2191
2202
|
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.",
|
|
2192
2203
|
run(e) {
|
|
@@ -2213,9 +2224,9 @@ const ka = {
|
|
|
2213
2224
|
let g = !1;
|
|
2214
2225
|
const f = i.querySelector("thead"), v = (f == null ? void 0 : f.querySelector("tr")) ?? i.querySelector("tbody > tr, tr");
|
|
2215
2226
|
if (v)
|
|
2216
|
-
for (const
|
|
2217
|
-
const A = Ie(
|
|
2218
|
-
if (
|
|
2227
|
+
for (const y of v.querySelectorAll("th, td")) {
|
|
2228
|
+
const A = Ie(y), S = parseInt(y.getAttribute("colspan") || "1", 10);
|
|
2229
|
+
if (y.tagName.toLowerCase() === "th" && b >= A && b < A + S) {
|
|
2219
2230
|
g = !0;
|
|
2220
2231
|
break;
|
|
2221
2232
|
}
|
|
@@ -2265,6 +2276,7 @@ const ka = {
|
|
|
2265
2276
|
level: "A",
|
|
2266
2277
|
tags: ["best-practice"],
|
|
2267
2278
|
fixability: "contextual",
|
|
2279
|
+
browserHint: "Screenshot the table to see which header cells are visually empty, then add text content or aria-label.",
|
|
2268
2280
|
description: "Table header cells should have visible text.",
|
|
2269
2281
|
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.",
|
|
2270
2282
|
run(e) {
|
|
@@ -2272,7 +2284,7 @@ const ka = {
|
|
|
2272
2284
|
for (const t of e.querySelectorAll("th")) {
|
|
2273
2285
|
if (h(t)) continue;
|
|
2274
2286
|
const i = t.closest("table");
|
|
2275
|
-
(i == null ? void 0 : i.getAttribute("role")) === "presentation" || (i == null ? void 0 : i.getAttribute("role")) === "none" ||
|
|
2287
|
+
(i == null ? void 0 : i.getAttribute("role")) === "presentation" || (i == null ? void 0 : i.getAttribute("role")) === "none" || w(t) || a.push({
|
|
2276
2288
|
ruleId: "adaptable/empty-table-header",
|
|
2277
2289
|
selector: p(t),
|
|
2278
2290
|
html: m(t),
|
|
@@ -2290,6 +2302,7 @@ const ka = {
|
|
|
2290
2302
|
level: "AA",
|
|
2291
2303
|
tags: ["page-level"],
|
|
2292
2304
|
fixability: "mechanical",
|
|
2305
|
+
browserHint: "After fixing the viewport meta tag, resize the viewport to 320px wide and screenshot to verify content remains readable and usable.",
|
|
2293
2306
|
description: "Viewport meta tag must not disable user scaling.",
|
|
2294
2307
|
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.",
|
|
2295
2308
|
run(e) {
|
|
@@ -2386,7 +2399,7 @@ function We(e, a, t, i) {
|
|
|
2386
2399
|
}
|
|
2387
2400
|
for (const s of r.childNodes)
|
|
2388
2401
|
if (s.nodeType === 3 && ((o = s.textContent) != null && o.trim())) {
|
|
2389
|
-
const l = parseFloat(
|
|
2402
|
+
const l = parseFloat(x(r).fontSize);
|
|
2390
2403
|
if (l > 0 && t / l < i) return !0;
|
|
2391
2404
|
break;
|
|
2392
2405
|
}
|
|
@@ -2396,7 +2409,7 @@ function We(e, a, t, i) {
|
|
|
2396
2409
|
}
|
|
2397
2410
|
return n(e);
|
|
2398
2411
|
}
|
|
2399
|
-
function
|
|
2412
|
+
function Ea(e) {
|
|
2400
2413
|
var a;
|
|
2401
2414
|
for (const t of e.childNodes)
|
|
2402
2415
|
if (t.nodeType === 3 && ((a = t.textContent) != null && a.trim()))
|
|
@@ -2418,7 +2431,7 @@ function Ve(e) {
|
|
|
2418
2431
|
return !1;
|
|
2419
2432
|
}
|
|
2420
2433
|
function de(e, a) {
|
|
2421
|
-
if (
|
|
2434
|
+
if (Ea(e)) return !0;
|
|
2422
2435
|
for (const t of e.children) {
|
|
2423
2436
|
const i = t.getAttribute("style") || "";
|
|
2424
2437
|
if (!new RegExp(
|
|
@@ -2449,10 +2462,10 @@ function Be(e, a, t, i) {
|
|
|
2449
2462
|
}
|
|
2450
2463
|
return n;
|
|
2451
2464
|
}
|
|
2452
|
-
function
|
|
2465
|
+
function La(e) {
|
|
2453
2466
|
let a = e, t = !1;
|
|
2454
2467
|
for (; a; ) {
|
|
2455
|
-
const i =
|
|
2468
|
+
const i = x(a);
|
|
2456
2469
|
parseFloat(i.width) > 500 && (t = !0), (i.whiteSpace === "nowrap" || i.whiteSpace === "pre") && (t = !0);
|
|
2457
2470
|
const r = i.overflowX, o = i.overflowY;
|
|
2458
2471
|
if ((r === "scroll" || r === "auto") && o !== "scroll" && o !== "auto")
|
|
@@ -2485,9 +2498,9 @@ const Ra = {
|
|
|
2485
2498
|
run(e) {
|
|
2486
2499
|
const a = [];
|
|
2487
2500
|
for (const t of e.querySelectorAll("[style]")) {
|
|
2488
|
-
if (h(t) || !Oe(t) || Ve(t) || !de(t, "line-height") ||
|
|
2501
|
+
if (h(t) || !Oe(t) || Ve(t) || !de(t, "line-height") || La(t)) continue;
|
|
2489
2502
|
if (t instanceof HTMLElement && t.scrollHeight > 0) {
|
|
2490
|
-
const r = parseFloat(
|
|
2503
|
+
const r = parseFloat(x(t).lineHeight);
|
|
2491
2504
|
if (r > 0 && t.scrollHeight <= r * 1.5) continue;
|
|
2492
2505
|
}
|
|
2493
2506
|
const i = Ue(t, "line-height");
|
|
@@ -2534,7 +2547,7 @@ const Ra = {
|
|
|
2534
2547
|
]);
|
|
2535
2548
|
function $a(e) {
|
|
2536
2549
|
let a = e.parentElement;
|
|
2537
|
-
for (; a && !Na.has(
|
|
2550
|
+
for (; a && !Na.has(x(a).display); )
|
|
2538
2551
|
a = a.parentElement;
|
|
2539
2552
|
if (!a) return null;
|
|
2540
2553
|
const t = a.ownerDocument.createTreeWalker(a, NodeFilter.SHOW_TEXT);
|
|
@@ -2549,7 +2562,7 @@ function $a(e) {
|
|
|
2549
2562
|
}
|
|
2550
2563
|
s = s.parentElement;
|
|
2551
2564
|
}
|
|
2552
|
-
l || (i += r.data, !n && r.parentElement && (n = C(
|
|
2565
|
+
l || (i += r.data, !n && r.parentElement && (n = C(x(r.parentElement).color)));
|
|
2553
2566
|
}
|
|
2554
2567
|
const o = i.match(new RegExp("\\p{L}{3,}", "gu"));
|
|
2555
2568
|
return !n || !o || o.length < 2 ? null : { block: a, textColor: n };
|
|
@@ -2558,40 +2571,41 @@ function qe(e, a) {
|
|
|
2558
2571
|
const t = e.textDecorationLine || e.textDecoration || "";
|
|
2559
2572
|
return (t.includes("underline") || t.includes("line-through")) && t !== a;
|
|
2560
2573
|
}
|
|
2561
|
-
function
|
|
2574
|
+
function V(e) {
|
|
2562
2575
|
return e === "bold" ? 700 : e === "normal" ? 400 : parseInt(e) || 400;
|
|
2563
2576
|
}
|
|
2564
|
-
function
|
|
2577
|
+
function Ha(e) {
|
|
2565
2578
|
const a = e.ownerDocument.createTreeWalker(e, NodeFilter.SHOW_TEXT);
|
|
2566
2579
|
let t;
|
|
2567
2580
|
for (; t = a.nextNode(); )
|
|
2568
2581
|
if (t.data.trim()) return !1;
|
|
2569
2582
|
return !0;
|
|
2570
2583
|
}
|
|
2571
|
-
const
|
|
2584
|
+
const Da = {
|
|
2572
2585
|
id: "distinguishable/link-in-text-block",
|
|
2573
2586
|
category: "distinguishable",
|
|
2574
2587
|
wcag: ["1.4.1"],
|
|
2575
2588
|
level: "A",
|
|
2576
2589
|
fixability: "visual",
|
|
2590
|
+
browserHint: "Screenshot the text block to see how the link blends with surrounding text, then verify your fix (e.g., underline or border) makes the link visually distinct.",
|
|
2577
2591
|
description: "Links within text blocks must be distinguishable by more than color alone.",
|
|
2578
2592
|
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.",
|
|
2579
2593
|
run(e) {
|
|
2580
2594
|
const a = [];
|
|
2581
2595
|
for (const t of e.querySelectorAll("a[href]")) {
|
|
2582
|
-
if (h(t) || !k(t).trim() ||
|
|
2583
|
-
const i =
|
|
2596
|
+
if (h(t) || !k(t).trim() || Ha(t) || t.closest('nav, header, footer, aside, [role="navigation"], [role="banner"], [role="contentinfo"], [role="complementary"]')) continue;
|
|
2597
|
+
const i = x(t);
|
|
2584
2598
|
if (!Ma.has(i.display || "inline")) continue;
|
|
2585
2599
|
const n = $a(t);
|
|
2586
2600
|
if (!n) continue;
|
|
2587
|
-
const r =
|
|
2588
|
-
if (qe(i, o) || (parseFloat(i.borderBottomWidth) || 0) > 0 && i.borderBottomStyle !== "none" && i.borderBottomStyle !== "hidden" || Math.abs(
|
|
2601
|
+
const r = x(n.block), o = r.textDecorationLine || r.textDecoration || "";
|
|
2602
|
+
if (qe(i, o) || (parseFloat(i.borderBottomWidth) || 0) > 0 && i.borderBottomStyle !== "none" && i.borderBottomStyle !== "hidden" || Math.abs(V(i.fontWeight) - V(r.fontWeight)) >= 300 || i.fontStyle !== r.fontStyle) continue;
|
|
2589
2603
|
const l = parseFloat(i.fontSize) || 16, d = parseFloat(r.fontSize) || 16;
|
|
2590
2604
|
if (d > 0 && l / d >= 1.2) continue;
|
|
2591
2605
|
let c = !1;
|
|
2592
|
-
for (const
|
|
2593
|
-
const A = y
|
|
2594
|
-
if (qe(A, o) || Math.abs(
|
|
2606
|
+
for (const y of t.querySelectorAll("*")) {
|
|
2607
|
+
const A = x(y);
|
|
2608
|
+
if (qe(A, o) || Math.abs(V(A.fontWeight) - V(r.fontWeight)) >= 300) {
|
|
2595
2609
|
c = !0;
|
|
2596
2610
|
break;
|
|
2597
2611
|
}
|
|
@@ -2601,19 +2615,20 @@ const Pa = {
|
|
|
2601
2615
|
if (!u) continue;
|
|
2602
2616
|
const b = I(...u), g = I(...n.textColor), f = $(b, g);
|
|
2603
2617
|
if (f < 1.1 || f >= 3) continue;
|
|
2604
|
-
const v = (
|
|
2618
|
+
const v = (y) => "#" + y.map((A) => A.toString(16).padStart(2, "0")).join("");
|
|
2605
2619
|
a.push({
|
|
2606
2620
|
ruleId: "distinguishable/link-in-text-block",
|
|
2607
2621
|
selector: p(t),
|
|
2608
2622
|
html: m(t),
|
|
2609
2623
|
impact: "serious",
|
|
2610
2624
|
message: "Link in text block is not visually distinguishable from surrounding text. Add a non-color visual indicator such as an underline or border.",
|
|
2611
|
-
context: `link color: ${v(u)} rgb(${u.join(", ")}), surrounding text: ${v(n.textColor)} rgb(${n.textColor.join(", ")}), ratio: ${f.toFixed(2)}:1
|
|
2625
|
+
context: `link color: ${v(u)} rgb(${u.join(", ")}), surrounding text: ${v(n.textColor)} rgb(${n.textColor.join(", ")}), ratio: ${f.toFixed(2)}:1`,
|
|
2626
|
+
fix: { type: "suggest", suggestion: "Add text-decoration: underline to the link, or add a visible border-bottom. If relying on color contrast alone, ensure at least 3:1 ratio between the link color and surrounding text color." }
|
|
2612
2627
|
});
|
|
2613
2628
|
}
|
|
2614
2629
|
return a;
|
|
2615
2630
|
}
|
|
2616
|
-
},
|
|
2631
|
+
}, Pa = /* @__PURE__ */ new Set([
|
|
2617
2632
|
"SCRIPT",
|
|
2618
2633
|
"STYLE",
|
|
2619
2634
|
"NOSCRIPT",
|
|
@@ -2629,7 +2644,7 @@ const Pa = {
|
|
|
2629
2644
|
"BR",
|
|
2630
2645
|
"HR"
|
|
2631
2646
|
]);
|
|
2632
|
-
function
|
|
2647
|
+
function Fa(e) {
|
|
2633
2648
|
const a = e.clip;
|
|
2634
2649
|
if (a && a.startsWith("rect(")) {
|
|
2635
2650
|
const i = a.match(/[\d.]+/g);
|
|
@@ -2647,8 +2662,8 @@ function za(e) {
|
|
|
2647
2662
|
if (h(e)) return !0;
|
|
2648
2663
|
let a = e;
|
|
2649
2664
|
for (; a; ) {
|
|
2650
|
-
const t =
|
|
2651
|
-
if (t.display === "none" || t.visibility === "hidden" ||
|
|
2665
|
+
const t = x(a);
|
|
2666
|
+
if (t.display === "none" || t.visibility === "hidden" || Fa(t)) return !0;
|
|
2652
2667
|
a = a.parentElement;
|
|
2653
2668
|
}
|
|
2654
2669
|
return !1;
|
|
@@ -2693,10 +2708,10 @@ function _a(e) {
|
|
|
2693
2708
|
const a = parseFloat(e);
|
|
2694
2709
|
return isNaN(a) ? NaN : e.trim().endsWith("%") ? a / 100 : a;
|
|
2695
2710
|
}
|
|
2696
|
-
const
|
|
2697
|
-
function
|
|
2711
|
+
const Ee = /([a-z-]+)\(([^)]*)\)/g;
|
|
2712
|
+
function Le(e) {
|
|
2698
2713
|
let a, t = !1;
|
|
2699
|
-
for (
|
|
2714
|
+
for (Ee.lastIndex = 0; a = Ee.exec(e); ) {
|
|
2700
2715
|
t = !0;
|
|
2701
2716
|
const i = Ba[a[1]];
|
|
2702
2717
|
if (i === void 0 || _a(a[2]) !== i) return !1;
|
|
@@ -2706,12 +2721,12 @@ function Ee(e) {
|
|
|
2706
2721
|
function Ga(e) {
|
|
2707
2722
|
let a = e;
|
|
2708
2723
|
for (; a; ) {
|
|
2709
|
-
const t =
|
|
2710
|
-
if (i && i !== "none" && i !== "initial" && !
|
|
2724
|
+
const t = x(a), i = t.filter;
|
|
2725
|
+
if (i && i !== "none" && i !== "initial" && !Le(i)) return !0;
|
|
2711
2726
|
const n = t.mixBlendMode;
|
|
2712
2727
|
if (n && n !== "normal" && n !== "initial") return !0;
|
|
2713
2728
|
const r = t.backdropFilter;
|
|
2714
|
-
if (r && r !== "none" && r !== "initial" && !
|
|
2729
|
+
if (r && r !== "none" && r !== "initial" && !Le(r)) return !0;
|
|
2715
2730
|
a = a.parentElement;
|
|
2716
2731
|
}
|
|
2717
2732
|
return !1;
|
|
@@ -2719,7 +2734,7 @@ function Ga(e) {
|
|
|
2719
2734
|
function Ya(e) {
|
|
2720
2735
|
let a = e;
|
|
2721
2736
|
for (; a; ) {
|
|
2722
|
-
const t =
|
|
2737
|
+
const t = x(a), i = t.backgroundImage;
|
|
2723
2738
|
if (i && i !== "none" && i !== "initial")
|
|
2724
2739
|
return i.includes("gradient(") ? { bgImage: i, gradientEl: a } : null;
|
|
2725
2740
|
const n = t.backgroundColor;
|
|
@@ -2727,7 +2742,7 @@ function Ya(e) {
|
|
|
2727
2742
|
a = a.parentElement;
|
|
2728
2743
|
continue;
|
|
2729
2744
|
}
|
|
2730
|
-
if (
|
|
2745
|
+
if (W(n) < 0.01) {
|
|
2731
2746
|
a = a.parentElement;
|
|
2732
2747
|
continue;
|
|
2733
2748
|
}
|
|
@@ -2742,11 +2757,11 @@ function Xa(e, a, t, i, n, r, o, s, l) {
|
|
|
2742
2757
|
for (const f of d) {
|
|
2743
2758
|
let v = a;
|
|
2744
2759
|
t < 1 && (v = R(a, f, t)), i < 1 && (v = R(v, f, i));
|
|
2745
|
-
const
|
|
2760
|
+
const y = $(
|
|
2746
2761
|
I(v[0], v[1], v[2]),
|
|
2747
2762
|
I(f[0], f[1], f[2])
|
|
2748
2763
|
);
|
|
2749
|
-
|
|
2764
|
+
y > c && (c = y, u = f);
|
|
2750
2765
|
}
|
|
2751
2766
|
if (c >= n) return null;
|
|
2752
2767
|
let b = a;
|
|
@@ -2758,7 +2773,8 @@ function Xa(e, a, t, i, n, r, o, s, l) {
|
|
|
2758
2773
|
html: m(e),
|
|
2759
2774
|
impact: "serious",
|
|
2760
2775
|
message: `Insufficient${o === "AAA" ? " enhanced" : ""} color contrast ratio of ${g}:1 (required ${n}:1).`,
|
|
2761
|
-
context: `foreground: ${
|
|
2776
|
+
context: `foreground: ${_(b)} rgb(${b.join(", ")}), background: gradient, ratio: ${g}:1, required: ${n}:1`,
|
|
2777
|
+
fix: { type: "suggest", suggestion: `Change the text color or gradient background so the contrast ratio meets ${n}:1. The current foreground is ${_(b)}.` }
|
|
2762
2778
|
};
|
|
2763
2779
|
}
|
|
2764
2780
|
function _e(e, a, t) {
|
|
@@ -2769,10 +2785,10 @@ function _e(e, a, t) {
|
|
|
2769
2785
|
for (; s = r.nextNode(); ) {
|
|
2770
2786
|
if (!s.textContent || !s.textContent.trim() || Oa(s.textContent)) continue;
|
|
2771
2787
|
const l = s.parentElement;
|
|
2772
|
-
if (!l || o.has(l) || (o.add(l),
|
|
2788
|
+
if (!l || o.has(l) || (o.add(l), Pa.has(l.tagName))) continue;
|
|
2773
2789
|
const d = l.tagName;
|
|
2774
2790
|
if (d === "BODY" || d === "HTML" || Wa(l) || ja(l) || Ua(l, e) || Va(l) || za(l)) continue;
|
|
2775
|
-
const c =
|
|
2791
|
+
const c = x(l);
|
|
2776
2792
|
if (parseFloat(c.opacity) === 0) continue;
|
|
2777
2793
|
const u = Mt(l);
|
|
2778
2794
|
if (u < 0.1) continue;
|
|
@@ -2781,47 +2797,48 @@ function _e(e, a, t) {
|
|
|
2781
2797
|
if (b && b !== "none" && b !== "initial" && (g = Tt(b), !g) || Ga(l) || $t(l)) continue;
|
|
2782
2798
|
const f = C(c.color);
|
|
2783
2799
|
if (!f) continue;
|
|
2784
|
-
const v =
|
|
2785
|
-
if (v === 0 ||
|
|
2786
|
-
const
|
|
2800
|
+
const v = W(c.color);
|
|
2801
|
+
if (v === 0 || Et(l)) continue;
|
|
2802
|
+
const y = t === "AAA" ? xe(l) ? 4.5 : 7 : xe(l) ? 3 : 4.5;
|
|
2787
2803
|
let A = ye(l);
|
|
2788
2804
|
if (!A) {
|
|
2789
2805
|
if (g) continue;
|
|
2790
|
-
const
|
|
2791
|
-
if (
|
|
2792
|
-
const
|
|
2806
|
+
const E = Ya(l);
|
|
2807
|
+
if (E) {
|
|
2808
|
+
const O = E.gradientEl.parentElement ? ye(E.gradientEl.parentElement) : null, H = Xa(
|
|
2793
2809
|
l,
|
|
2794
2810
|
f,
|
|
2795
2811
|
v,
|
|
2796
2812
|
u,
|
|
2797
|
-
|
|
2813
|
+
y,
|
|
2798
2814
|
a,
|
|
2799
2815
|
t,
|
|
2800
|
-
|
|
2801
|
-
|
|
2816
|
+
E.bgImage,
|
|
2817
|
+
O ?? [255, 255, 255]
|
|
2802
2818
|
);
|
|
2803
|
-
|
|
2819
|
+
H && i.push(H);
|
|
2804
2820
|
}
|
|
2805
2821
|
continue;
|
|
2806
2822
|
}
|
|
2807
2823
|
let S = f;
|
|
2808
2824
|
v < 1 && (S = R(f, A, v)), u < 1 && (S = R(S, A, u));
|
|
2809
2825
|
const nt = I(S[0], S[1], S[2]), rt = I(A[0], A[1], A[2]), be = g ? Nt(S, A, g) : $(nt, rt);
|
|
2810
|
-
if (be <
|
|
2811
|
-
const
|
|
2826
|
+
if (be < y) {
|
|
2827
|
+
const E = Math.round(be * 100) / 100, O = _(S), H = _(A);
|
|
2812
2828
|
i.push({
|
|
2813
2829
|
ruleId: a,
|
|
2814
2830
|
selector: p(l),
|
|
2815
2831
|
html: m(l),
|
|
2816
2832
|
impact: "serious",
|
|
2817
|
-
message: `Insufficient${t === "AAA" ? " enhanced" : ""} color contrast ratio of ${
|
|
2818
|
-
context: `foreground: ${
|
|
2833
|
+
message: `Insufficient${t === "AAA" ? " enhanced" : ""} color contrast ratio of ${E}:1 (required ${y}:1).`,
|
|
2834
|
+
context: `foreground: ${O} rgb(${S.join(", ")}), background: ${H} rgb(${A.join(", ")}), ratio: ${E}:1, required: ${y}:1`,
|
|
2835
|
+
fix: { type: "suggest", suggestion: `Change the text color or background color so the contrast ratio meets ${y}:1. Current foreground is ${O}, background is ${H}.` }
|
|
2819
2836
|
});
|
|
2820
2837
|
}
|
|
2821
2838
|
}
|
|
2822
2839
|
return i;
|
|
2823
2840
|
}
|
|
2824
|
-
const
|
|
2841
|
+
const Ja = {
|
|
2825
2842
|
id: "distinguishable/color-contrast",
|
|
2826
2843
|
category: "distinguishable",
|
|
2827
2844
|
actRuleIds: ["afw4f7"],
|
|
@@ -2829,11 +2846,12 @@ const Ka = {
|
|
|
2829
2846
|
level: "AA",
|
|
2830
2847
|
fixability: "visual",
|
|
2831
2848
|
description: "Text elements must have sufficient color contrast against the background.",
|
|
2849
|
+
browserHint: "Violation context includes computed colors and ratio. After changing colors, use JavaScript to read getComputedStyle() on the element and recalculate the contrast ratio. Screenshot the element to verify the fix looks correct in context.",
|
|
2832
2850
|
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.",
|
|
2833
2851
|
run(e) {
|
|
2834
2852
|
return _e(e, "distinguishable/color-contrast", "AA");
|
|
2835
2853
|
}
|
|
2836
|
-
},
|
|
2854
|
+
}, Ka = {
|
|
2837
2855
|
id: "distinguishable/color-contrast-enhanced",
|
|
2838
2856
|
category: "distinguishable",
|
|
2839
2857
|
actRuleIds: ["09o5cg"],
|
|
@@ -2841,6 +2859,7 @@ const Ka = {
|
|
|
2841
2859
|
level: "AAA",
|
|
2842
2860
|
fixability: "visual",
|
|
2843
2861
|
description: "Text elements must have enhanced color contrast against the background (WCAG AAA).",
|
|
2862
|
+
browserHint: "Violation context includes computed colors and ratio. After changing colors, use JavaScript to read getComputedStyle() on the element and recalculate the contrast ratio. Screenshot the element to verify the fix looks correct in context.",
|
|
2844
2863
|
guidance: "WCAG SC 1.4.6 (AAA) requires a contrast ratio of at least 7:1 for normal text and 4.5:1 for large text (>=24px or >=18.66px bold). Higher contrast benefits users with low vision, aging eyes, or poor screen conditions. Increase the contrast by darkening the text or lightening the background, or vice versa.",
|
|
2845
2864
|
run(e) {
|
|
2846
2865
|
return _e(e, "distinguishable/color-contrast-enhanced", "AAA");
|
|
@@ -3024,6 +3043,7 @@ const di = {
|
|
|
3024
3043
|
wcag: ["2.1.1"],
|
|
3025
3044
|
level: "A",
|
|
3026
3045
|
fixability: "contextual",
|
|
3046
|
+
browserHint: "Tab to the scrollable region and verify keyboard scrolling works with arrow keys.",
|
|
3027
3047
|
description: "Scrollable regions must be keyboard accessible.",
|
|
3028
3048
|
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.",
|
|
3029
3049
|
run(e) {
|
|
@@ -3035,17 +3055,17 @@ const di = {
|
|
|
3035
3055
|
if (n === "body" || n === "html") continue;
|
|
3036
3056
|
const r = i.getAttribute("role");
|
|
3037
3057
|
if (r === "presentation" || r === "none" || r === "listbox" || r === "menu" || r === "tree" || r === "tabpanel") continue;
|
|
3038
|
-
const o =
|
|
3058
|
+
const o = x(i), s = o.overflowX, l = o.overflowY;
|
|
3039
3059
|
if (!(s === "scroll" || s === "auto" || l === "scroll" || l === "auto")) continue;
|
|
3040
3060
|
if (i.scrollHeight > 0 || i.clientHeight > 0) {
|
|
3041
3061
|
const g = i.scrollHeight - i.clientHeight, f = i.scrollWidth - i.clientWidth;
|
|
3042
3062
|
if (g <= 0 && f <= 0 || g < 14 && f < 14 || i.clientWidth < 64 && i.clientHeight < 64) continue;
|
|
3043
|
-
const v = ((t = i.textContent) == null ? void 0 : t.trim().length) ?? 0,
|
|
3044
|
-
if (v === 0 && !
|
|
3063
|
+
const v = ((t = i.textContent) == null ? void 0 : t.trim().length) ?? 0, y = i.querySelector("img, svg, video, canvas, picture") !== null;
|
|
3064
|
+
if (v === 0 && !y) continue;
|
|
3045
3065
|
} else
|
|
3046
3066
|
continue;
|
|
3047
3067
|
const u = i.getAttribute("tabindex");
|
|
3048
|
-
u !== null && u !== "-1" || i.querySelector(
|
|
3068
|
+
u !== null && u !== "-1" || i.querySelector(j) || a.push({
|
|
3049
3069
|
ruleId: "keyboard-accessible/scrollable-region",
|
|
3050
3070
|
selector: p(i),
|
|
3051
3071
|
html: m(i),
|
|
@@ -3093,11 +3113,12 @@ const di = {
|
|
|
3093
3113
|
wcag: ["2.4.7"],
|
|
3094
3114
|
level: "AA",
|
|
3095
3115
|
fixability: "visual",
|
|
3116
|
+
browserHint: "Tab to the element and screenshot to verify a visible focus indicator appears. Check that the indicator has sufficient contrast against the background.",
|
|
3096
3117
|
description: "Elements in sequential focus order must have a visible focus indicator.",
|
|
3097
3118
|
guidance: "Keyboard users need to see which element has focus. Do not remove the default focus outline (outline: none) without providing an alternative visible indicator. Use :focus-visible or :focus styles to ensure focus is always perceivable.",
|
|
3098
3119
|
run(e) {
|
|
3099
3120
|
const a = [];
|
|
3100
|
-
for (const t of e.querySelectorAll(
|
|
3121
|
+
for (const t of e.querySelectorAll(j)) {
|
|
3101
3122
|
if (h(t) || !(t instanceof HTMLElement)) continue;
|
|
3102
3123
|
const i = t.getAttribute("style") || "";
|
|
3103
3124
|
if (/outline\s*:\s*(none|0)\s*(;|$|!)/i.test(i)) {
|
|
@@ -3127,7 +3148,7 @@ function Ye(e) {
|
|
|
3127
3148
|
return { seconds: t, hasValidUrl: i };
|
|
3128
3149
|
}
|
|
3129
3150
|
const Xe = 'article, aside, main, nav, section, [role="article"], [role="complementary"], [role="main"], [role="navigation"], [role="region"]', Re = 'main, [role="main"], header, [role="banner"], footer, [role="contentinfo"], nav, [role="navigation"], aside, [role="complementary"], section[aria-label], section[aria-labelledby], [role="region"][aria-label], [role="region"][aria-labelledby], form[aria-label], form[aria-labelledby], [role="form"][aria-label], [role="form"][aria-labelledby], [role="search"]';
|
|
3130
|
-
function
|
|
3151
|
+
function Je(e) {
|
|
3131
3152
|
return {
|
|
3132
3153
|
id: e.id,
|
|
3133
3154
|
category: e.id.split("/")[0],
|
|
@@ -3350,10 +3371,10 @@ const bi = {
|
|
|
3350
3371
|
run(e) {
|
|
3351
3372
|
var o, s, l;
|
|
3352
3373
|
const a = e.querySelector("h1");
|
|
3353
|
-
if (a &&
|
|
3374
|
+
if (a && w(a)) return [];
|
|
3354
3375
|
const t = e.querySelectorAll('[role="heading"][aria-level="1"]');
|
|
3355
3376
|
for (const d of t)
|
|
3356
|
-
if (
|
|
3377
|
+
if (w(d)) return [];
|
|
3357
3378
|
const i = [], n = (s = (o = e.querySelector("title")) == null ? void 0 : o.textContent) == null ? void 0 : s.trim();
|
|
3358
3379
|
n && i.push(`Page title: "${n}"`);
|
|
3359
3380
|
const r = e.querySelector("main");
|
|
@@ -3377,6 +3398,7 @@ const bi = {
|
|
|
3377
3398
|
level: "A",
|
|
3378
3399
|
tags: ["best-practice"],
|
|
3379
3400
|
fixability: "contextual",
|
|
3401
|
+
browserHint: "Screenshot the page to see the visual hierarchy, then take an accessibility tree snapshot to map heading levels to visual sections.",
|
|
3380
3402
|
description: "Heading levels should increase by one; skipping levels (e.g. h2 to h4) makes navigation harder.",
|
|
3381
3403
|
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.",
|
|
3382
3404
|
run(e) {
|
|
@@ -3405,13 +3427,14 @@ const bi = {
|
|
|
3405
3427
|
level: "A",
|
|
3406
3428
|
tags: ["best-practice"],
|
|
3407
3429
|
fixability: "contextual",
|
|
3430
|
+
browserHint: "Screenshot the heading area to verify it's visually empty, then add meaningful text or remove the heading element.",
|
|
3408
3431
|
description: "Headings must have discernible text.",
|
|
3409
3432
|
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.",
|
|
3410
3433
|
run(e) {
|
|
3411
3434
|
var i;
|
|
3412
3435
|
const a = [], t = e.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');
|
|
3413
3436
|
for (const n of t)
|
|
3414
|
-
if (!h(n) && !
|
|
3437
|
+
if (!h(n) && !w(n)) {
|
|
3415
3438
|
let r;
|
|
3416
3439
|
const o = n.nextElementSibling;
|
|
3417
3440
|
if (o) {
|
|
@@ -3436,7 +3459,8 @@ const bi = {
|
|
|
3436
3459
|
wcag: ["1.3.1"],
|
|
3437
3460
|
level: "A",
|
|
3438
3461
|
tags: ["best-practice"],
|
|
3439
|
-
fixability: "
|
|
3462
|
+
fixability: "contextual",
|
|
3463
|
+
browserHint: "Screenshot the page to verify the paragraph visually functions as a heading and choose the correct heading level.",
|
|
3440
3464
|
description: "Paragraphs should not be styled to look like headings.",
|
|
3441
3465
|
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.",
|
|
3442
3466
|
run(e) {
|
|
@@ -3452,7 +3476,8 @@ const bi = {
|
|
|
3452
3476
|
selector: p(n),
|
|
3453
3477
|
html: m(n),
|
|
3454
3478
|
impact: "serious",
|
|
3455
|
-
message: "Paragraph appears to be styled as a heading. Use an h1-h6 element instead."
|
|
3479
|
+
message: "Paragraph appears to be styled as a heading. Use an h1-h6 element instead.",
|
|
3480
|
+
fix: { type: "suggest", suggestion: "Replace the <p> element with the appropriate heading level (h1-h6) based on the document outline. Preserve the text content and move any inline styles to a CSS class on the new heading element." }
|
|
3456
3481
|
});
|
|
3457
3482
|
}
|
|
3458
3483
|
}
|
|
@@ -3476,20 +3501,21 @@ function qi(e) {
|
|
|
3476
3501
|
return a.length > 0 ? a.join(`
|
|
3477
3502
|
`) : void 0;
|
|
3478
3503
|
}
|
|
3479
|
-
const
|
|
3504
|
+
const Ei = {
|
|
3480
3505
|
id: "navigable/link-name",
|
|
3481
3506
|
category: "navigable",
|
|
3482
3507
|
actRuleIds: ["c487ae"],
|
|
3483
3508
|
wcag: ["2.4.4", "4.1.2"],
|
|
3484
3509
|
level: "A",
|
|
3485
3510
|
fixability: "contextual",
|
|
3511
|
+
browserHint: "Screenshot the link in context to understand its destination, then write descriptive link text.",
|
|
3486
3512
|
description: "Links must have discernible text via content, aria-label, or aria-labelledby.",
|
|
3487
3513
|
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.",
|
|
3488
3514
|
run(e) {
|
|
3489
3515
|
const a = [];
|
|
3490
3516
|
for (const t of e.querySelectorAll('a[href], area[href], [role="link"]')) {
|
|
3491
3517
|
if (h(t) || q(t) || t.getRootNode() instanceof ShadowRoot) continue;
|
|
3492
|
-
|
|
3518
|
+
w(t) || a.push({
|
|
3493
3519
|
ruleId: "navigable/link-name",
|
|
3494
3520
|
selector: p(t),
|
|
3495
3521
|
html: m(t),
|
|
@@ -3501,7 +3527,7 @@ const Li = {
|
|
|
3501
3527
|
}
|
|
3502
3528
|
return a;
|
|
3503
3529
|
}
|
|
3504
|
-
},
|
|
3530
|
+
}, Li = {
|
|
3505
3531
|
id: "navigable/skip-link",
|
|
3506
3532
|
category: "navigable",
|
|
3507
3533
|
wcag: ["2.4.1"],
|
|
@@ -3574,19 +3600,19 @@ const Li = {
|
|
|
3574
3600
|
description: "Page should not have more than one main landmark.",
|
|
3575
3601
|
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.",
|
|
3576
3602
|
filterTopLevel: !1
|
|
3577
|
-
}), Mi =
|
|
3603
|
+
}), Mi = Je({
|
|
3578
3604
|
id: "landmarks/banner-is-top-level",
|
|
3579
3605
|
selector: '[role="banner"]',
|
|
3580
3606
|
landmarkName: "Banner",
|
|
3581
3607
|
description: "Banner landmark should not be nested within another landmark.",
|
|
3582
3608
|
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."
|
|
3583
|
-
}), $i =
|
|
3609
|
+
}), $i = Je({
|
|
3584
3610
|
id: "landmarks/contentinfo-is-top-level",
|
|
3585
3611
|
selector: '[role="contentinfo"]',
|
|
3586
3612
|
landmarkName: "Contentinfo",
|
|
3587
3613
|
description: "Contentinfo landmark should not be nested within another landmark.",
|
|
3588
3614
|
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."
|
|
3589
|
-
}),
|
|
3615
|
+
}), Hi = {
|
|
3590
3616
|
id: "landmarks/main-is-top-level",
|
|
3591
3617
|
category: "landmarks",
|
|
3592
3618
|
wcag: [],
|
|
@@ -3609,7 +3635,7 @@ const Li = {
|
|
|
3609
3635
|
}
|
|
3610
3636
|
return a;
|
|
3611
3637
|
}
|
|
3612
|
-
},
|
|
3638
|
+
}, Di = {
|
|
3613
3639
|
id: "landmarks/complementary-is-top-level",
|
|
3614
3640
|
category: "landmarks",
|
|
3615
3641
|
wcag: [],
|
|
@@ -3632,7 +3658,7 @@ const Li = {
|
|
|
3632
3658
|
}
|
|
3633
3659
|
return a;
|
|
3634
3660
|
}
|
|
3635
|
-
},
|
|
3661
|
+
}, Pi = {
|
|
3636
3662
|
id: "landmarks/landmark-unique",
|
|
3637
3663
|
category: "landmarks",
|
|
3638
3664
|
wcag: [],
|
|
@@ -3653,7 +3679,7 @@ const Li = {
|
|
|
3653
3679
|
if (r.length <= 1) continue;
|
|
3654
3680
|
const o = /* @__PURE__ */ new Map();
|
|
3655
3681
|
for (const s of r) {
|
|
3656
|
-
const l =
|
|
3682
|
+
const l = w(s).toLowerCase() || "", d = o.get(l) || [];
|
|
3657
3683
|
d.push(s), o.set(l, d);
|
|
3658
3684
|
}
|
|
3659
3685
|
for (const [s, l] of o)
|
|
@@ -3669,7 +3695,7 @@ const Li = {
|
|
|
3669
3695
|
}
|
|
3670
3696
|
return a;
|
|
3671
3697
|
}
|
|
3672
|
-
},
|
|
3698
|
+
}, Fi = {
|
|
3673
3699
|
id: "landmarks/region",
|
|
3674
3700
|
category: "landmarks",
|
|
3675
3701
|
wcag: [],
|
|
@@ -3738,7 +3764,7 @@ const Li = {
|
|
|
3738
3764
|
), Ui = new Set(
|
|
3739
3765
|
"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(" ")
|
|
3740
3766
|
), Wi = /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;
|
|
3741
|
-
function
|
|
3767
|
+
function Ke(e) {
|
|
3742
3768
|
if (!Wi.test(e)) return !1;
|
|
3743
3769
|
const a = e.split("-")[0].toLowerCase();
|
|
3744
3770
|
return a.length === 2 ? ji.has(a) : a.length === 3 ? !Ui.has(a) : !1;
|
|
@@ -3788,7 +3814,7 @@ const Oi = {
|
|
|
3788
3814
|
run(e) {
|
|
3789
3815
|
var t;
|
|
3790
3816
|
const a = (t = e.documentElement.getAttribute("lang")) == null ? void 0 : t.trim();
|
|
3791
|
-
return a && !
|
|
3817
|
+
return a && !Ke(a) ? [{
|
|
3792
3818
|
ruleId: "readable/html-lang-valid",
|
|
3793
3819
|
selector: "html",
|
|
3794
3820
|
html: m(e.documentElement),
|
|
@@ -3821,7 +3847,7 @@ const Oi = {
|
|
|
3821
3847
|
});
|
|
3822
3848
|
continue;
|
|
3823
3849
|
}
|
|
3824
|
-
n && Ce(t) && (
|
|
3850
|
+
n && Ce(t) && (Ke(n) || a.push({
|
|
3825
3851
|
ruleId: "readable/valid-lang",
|
|
3826
3852
|
selector: p(t),
|
|
3827
3853
|
html: m(t),
|
|
@@ -3864,13 +3890,14 @@ const Oi = {
|
|
|
3864
3890
|
wcag: ["4.1.2"],
|
|
3865
3891
|
level: "A",
|
|
3866
3892
|
fixability: "contextual",
|
|
3893
|
+
browserHint: "Screenshot the iframe to see what content it displays, then add a title describing its purpose.",
|
|
3867
3894
|
description: "Frames must have an accessible name.",
|
|
3868
3895
|
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'.",
|
|
3869
3896
|
run(e) {
|
|
3870
3897
|
const a = [];
|
|
3871
3898
|
for (const t of e.querySelectorAll("iframe, frame")) {
|
|
3872
3899
|
if (h(t) || Ge(t)) continue;
|
|
3873
|
-
if (!
|
|
3900
|
+
if (!w(t)) {
|
|
3874
3901
|
const n = t.getAttribute("src");
|
|
3875
3902
|
a.push({
|
|
3876
3903
|
ruleId: "labels-and-names/frame-title",
|
|
@@ -3940,7 +3967,7 @@ function Xi(e) {
|
|
|
3940
3967
|
}
|
|
3941
3968
|
return "";
|
|
3942
3969
|
}
|
|
3943
|
-
const
|
|
3970
|
+
const Ji = [
|
|
3944
3971
|
'[role="checkbox"]',
|
|
3945
3972
|
'[role="combobox"]',
|
|
3946
3973
|
'[role="listbox"]',
|
|
@@ -3952,7 +3979,7 @@ const Ki = [
|
|
|
3952
3979
|
'[role="spinbutton"]',
|
|
3953
3980
|
'[role="switch"]',
|
|
3954
3981
|
'[role="textbox"]'
|
|
3955
|
-
].join(", "),
|
|
3982
|
+
].join(", "), Ki = /* @__PURE__ */ new Set([
|
|
3956
3983
|
"checkbox",
|
|
3957
3984
|
"menuitemcheckbox",
|
|
3958
3985
|
"menuitemradio",
|
|
@@ -3969,8 +3996,8 @@ const Ki = [
|
|
|
3969
3996
|
function Zi(e) {
|
|
3970
3997
|
var o, s, l, d;
|
|
3971
3998
|
const a = (o = e.getAttribute("role")) == null ? void 0 : o.trim().toLowerCase();
|
|
3972
|
-
if (a &&
|
|
3973
|
-
return
|
|
3999
|
+
if (a && Ki.has(a) || (e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement) && !(a && Qi.has(a)))
|
|
4000
|
+
return w(e);
|
|
3974
4001
|
const i = e.getAttribute("aria-labelledby");
|
|
3975
4002
|
if (i) {
|
|
3976
4003
|
const c = i.split(/\s+/).map((u) => {
|
|
@@ -4000,11 +4027,12 @@ const en = {
|
|
|
4000
4027
|
wcag: ["4.1.2"],
|
|
4001
4028
|
level: "A",
|
|
4002
4029
|
fixability: "contextual",
|
|
4030
|
+
browserHint: "Screenshot the form to see visual label placement relative to the input, then associate them with a label element or aria-labelledby.",
|
|
4003
4031
|
description: "Form elements must have labels. Use <label>, aria-label, or aria-labelledby.",
|
|
4004
4032
|
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. Labels should describe the information requested, not the field type (e.g., 'Email address', 'Search', 'Phone number').",
|
|
4005
4033
|
run(e) {
|
|
4006
4034
|
var i;
|
|
4007
|
-
const a = [], t = e.querySelectorAll(`${Qe}, ${
|
|
4035
|
+
const a = [], t = e.querySelectorAll(`${Qe}, ${Ji}`);
|
|
4008
4036
|
for (const n of t) {
|
|
4009
4037
|
if (h(n) || q(n)) continue;
|
|
4010
4038
|
const r = (i = n.getAttribute("role")) == null ? void 0 : i.trim().toLowerCase();
|
|
@@ -4070,6 +4098,7 @@ const en = {
|
|
|
4070
4098
|
wcag: ["4.1.2"],
|
|
4071
4099
|
level: "A",
|
|
4072
4100
|
fixability: "contextual",
|
|
4101
|
+
browserHint: "Screenshot the button to see its visual purpose, then set value or aria-label to describe the action.",
|
|
4073
4102
|
description: "Input buttons must have discernible text via value, aria-label, or aria-labelledby.",
|
|
4074
4103
|
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.",
|
|
4075
4104
|
run(e) {
|
|
@@ -4080,7 +4109,7 @@ const en = {
|
|
|
4080
4109
|
)) {
|
|
4081
4110
|
if (h(n) || q(n)) continue;
|
|
4082
4111
|
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");
|
|
4083
|
-
!r && !s && !
|
|
4112
|
+
!r && !s && !w(n) && a.push({
|
|
4084
4113
|
ruleId: "labels-and-names/input-button-name",
|
|
4085
4114
|
selector: p(n),
|
|
4086
4115
|
html: m(n),
|
|
@@ -4109,16 +4138,17 @@ const nn = {
|
|
|
4109
4138
|
level: "A",
|
|
4110
4139
|
tags: ["best-practice"],
|
|
4111
4140
|
fixability: "contextual",
|
|
4141
|
+
browserHint: "Screenshot the control to see its visible label, then ensure aria-label starts with that visible text.",
|
|
4112
4142
|
description: "Interactive elements with visible text must have accessible names that contain that text.",
|
|
4113
4143
|
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.",
|
|
4114
4144
|
run(e) {
|
|
4115
4145
|
const a = [];
|
|
4116
4146
|
for (const t of e.querySelectorAll('button, [role="button"], a[href], input[type="submit"], input[type="button"]')) {
|
|
4117
4147
|
if (h(t)) continue;
|
|
4118
|
-
const i =
|
|
4148
|
+
const i = w(t);
|
|
4119
4149
|
if (!i) continue;
|
|
4120
4150
|
let n = "";
|
|
4121
|
-
t instanceof HTMLInputElement ? n = t.value || "" : n =
|
|
4151
|
+
t instanceof HTMLInputElement ? n = t.value || "" : n = ee(t);
|
|
4122
4152
|
const r = n.trim();
|
|
4123
4153
|
if (!r || r.length <= 2) continue;
|
|
4124
4154
|
const o = t.hasAttribute("aria-label"), s = t.hasAttribute("aria-labelledby");
|
|
@@ -4133,13 +4163,13 @@ const nn = {
|
|
|
4133
4163
|
}
|
|
4134
4164
|
for (const t of e.querySelectorAll("input, select, textarea")) {
|
|
4135
4165
|
if (h(t) || t instanceof HTMLInputElement && ["hidden", "submit", "button", "image"].includes(t.type)) continue;
|
|
4136
|
-
const i =
|
|
4166
|
+
const i = w(t);
|
|
4137
4167
|
if (!i || !t.hasAttribute("aria-label")) continue;
|
|
4138
4168
|
const r = t.id;
|
|
4139
4169
|
let o = "";
|
|
4140
4170
|
if (r) {
|
|
4141
4171
|
const s = e.querySelector(`label[for="${CSS.escape(r)}"]`);
|
|
4142
|
-
s && (o =
|
|
4172
|
+
s && (o = ee(s));
|
|
4143
4173
|
}
|
|
4144
4174
|
o.trim() && (Ne(i, o) || a.push({
|
|
4145
4175
|
ruleId: "labels-and-names/label-content-mismatch",
|
|
@@ -4194,7 +4224,7 @@ const nn = {
|
|
|
4194
4224
|
if (h(i) || q(i) || se(i)) continue;
|
|
4195
4225
|
const n = i.getAttribute("role");
|
|
4196
4226
|
if ((i.tagName.toLowerCase() === "button" || i.tagName.toLowerCase() === "a") && n !== "menuitem") continue;
|
|
4197
|
-
if (!
|
|
4227
|
+
if (!w(i)) {
|
|
4198
4228
|
const o = i.querySelector("img[alt]");
|
|
4199
4229
|
if ((t = o == null ? void 0 : o.getAttribute("alt")) != null && t.trim()) continue;
|
|
4200
4230
|
a.push({
|
|
@@ -4230,7 +4260,7 @@ function M(e) {
|
|
|
4230
4260
|
if (!o || !e.roleSet.has(o)) continue;
|
|
4231
4261
|
}
|
|
4232
4262
|
if (e.skipNative && n.matches(e.skipNative)) continue;
|
|
4233
|
-
|
|
4263
|
+
w(n) || t.push({
|
|
4234
4264
|
ruleId: e.id,
|
|
4235
4265
|
selector: p(n),
|
|
4236
4266
|
html: m(n),
|
|
@@ -4330,6 +4360,7 @@ const hn = {
|
|
|
4330
4360
|
wcag: ["4.1.2"],
|
|
4331
4361
|
level: "A",
|
|
4332
4362
|
fixability: "contextual",
|
|
4363
|
+
browserHint: "Screenshot the button to identify its icon or visual label, then add a matching aria-label.",
|
|
4333
4364
|
description: "Buttons must have discernible text.",
|
|
4334
4365
|
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.",
|
|
4335
4366
|
run(e) {
|
|
@@ -4338,7 +4369,7 @@ const hn = {
|
|
|
4338
4369
|
if (h(t) || q(t)) continue;
|
|
4339
4370
|
const i = t.getAttribute("role");
|
|
4340
4371
|
if ((i === "none" || i === "presentation") && !(t.matches('button:not([disabled]), [tabindex]:not([tabindex="-1"])') || t.tagName.toLowerCase() === "button" && !t.disabled) || se(t)) continue;
|
|
4341
|
-
|
|
4372
|
+
w(t) || a.push({
|
|
4342
4373
|
ruleId: "labels-and-names/button-name",
|
|
4343
4374
|
selector: p(t),
|
|
4344
4375
|
html: m(t),
|
|
@@ -4363,7 +4394,7 @@ const hn = {
|
|
|
4363
4394
|
const a = [];
|
|
4364
4395
|
for (const t of e.querySelectorAll("details > summary:first-of-type")) {
|
|
4365
4396
|
if (h(t)) continue;
|
|
4366
|
-
|
|
4397
|
+
w(t) || a.push({
|
|
4367
4398
|
ruleId: "labels-and-names/summary-name",
|
|
4368
4399
|
selector: p(t),
|
|
4369
4400
|
html: m(t),
|
|
@@ -4614,7 +4645,7 @@ Referenced by: ${d}` : ""}`,
|
|
|
4614
4645
|
const a = [];
|
|
4615
4646
|
for (const t of e.querySelectorAll("[role], [aria-*]")) {
|
|
4616
4647
|
if (h(t)) continue;
|
|
4617
|
-
const i =
|
|
4648
|
+
const i = U(t);
|
|
4618
4649
|
if (!i) continue;
|
|
4619
4650
|
const n = Sn[i];
|
|
4620
4651
|
if (n)
|
|
@@ -4756,7 +4787,7 @@ Referenced by: ${d}` : ""}`,
|
|
|
4756
4787
|
video: /* @__PURE__ */ new Set(["application"]),
|
|
4757
4788
|
wbr: /* @__PURE__ */ new Set(["none", "presentation"])
|
|
4758
4789
|
};
|
|
4759
|
-
function
|
|
4790
|
+
function En(e) {
|
|
4760
4791
|
var t;
|
|
4761
4792
|
const a = e.tagName.toLowerCase();
|
|
4762
4793
|
if (qn.has(a))
|
|
@@ -4771,7 +4802,7 @@ function Ln(e) {
|
|
|
4771
4802
|
}
|
|
4772
4803
|
return D[a] || "any";
|
|
4773
4804
|
}
|
|
4774
|
-
const
|
|
4805
|
+
const Ln = {
|
|
4775
4806
|
id: "aria/aria-allowed-role",
|
|
4776
4807
|
category: "aria",
|
|
4777
4808
|
wcag: ["4.1.2"],
|
|
@@ -4788,7 +4819,7 @@ const En = {
|
|
|
4788
4819
|
if (!n) continue;
|
|
4789
4820
|
const r = $e(i);
|
|
4790
4821
|
if (r && n === r) continue;
|
|
4791
|
-
const o =
|
|
4822
|
+
const o = En(i);
|
|
4792
4823
|
o === "none" ? a.push({
|
|
4793
4824
|
ruleId: "aria/aria-allowed-role",
|
|
4794
4825
|
selector: p(i),
|
|
@@ -4863,8 +4894,8 @@ const Mn = {
|
|
|
4863
4894
|
const a = [];
|
|
4864
4895
|
for (const t of e.querySelectorAll('[aria-hidden="true"]')) {
|
|
4865
4896
|
if (t === e.body) continue;
|
|
4866
|
-
const i = [...t.querySelectorAll(
|
|
4867
|
-
t.matches(
|
|
4897
|
+
const i = [...t.querySelectorAll(j)];
|
|
4898
|
+
t.matches(j) && i.push(t);
|
|
4868
4899
|
for (const n of i)
|
|
4869
4900
|
if (n instanceof HTMLElement) {
|
|
4870
4901
|
const r = n.getAttribute("tabindex");
|
|
@@ -4900,14 +4931,14 @@ const Mn = {
|
|
|
4900
4931
|
run(e) {
|
|
4901
4932
|
return le(e).prohibitedAttr;
|
|
4902
4933
|
}
|
|
4903
|
-
},
|
|
4934
|
+
}, Hn = [
|
|
4904
4935
|
"a[href]",
|
|
4905
4936
|
"button:not([disabled])",
|
|
4906
4937
|
'input:not([disabled]):not([type="hidden"])',
|
|
4907
4938
|
"select:not([disabled])",
|
|
4908
4939
|
"textarea:not([disabled])",
|
|
4909
4940
|
'[tabindex]:not([tabindex="-1"])'
|
|
4910
|
-
].join(", "),
|
|
4941
|
+
].join(", "), Dn = [
|
|
4911
4942
|
"aria-atomic",
|
|
4912
4943
|
"aria-busy",
|
|
4913
4944
|
"aria-controls",
|
|
@@ -4924,15 +4955,15 @@ const Mn = {
|
|
|
4924
4955
|
];
|
|
4925
4956
|
function Me(e) {
|
|
4926
4957
|
const a = [];
|
|
4927
|
-
e.matches(
|
|
4928
|
-
for (const t of
|
|
4958
|
+
e.matches(Hn) && a.push("element is focusable");
|
|
4959
|
+
for (const t of Dn)
|
|
4929
4960
|
if (e.hasAttribute(t)) {
|
|
4930
4961
|
a.push(`has ${t}`);
|
|
4931
4962
|
break;
|
|
4932
4963
|
}
|
|
4933
4964
|
return (e.hasAttribute("aria-label") || e.hasAttribute("aria-labelledby")) && a.push("has accessible name"), a;
|
|
4934
4965
|
}
|
|
4935
|
-
const
|
|
4966
|
+
const Pn = {
|
|
4936
4967
|
id: "aria/presentation-role-conflict",
|
|
4937
4968
|
category: "aria",
|
|
4938
4969
|
actRuleIds: ["46ca7f"],
|
|
@@ -4969,7 +5000,7 @@ const Fn = {
|
|
|
4969
5000
|
}
|
|
4970
5001
|
return a;
|
|
4971
5002
|
}
|
|
4972
|
-
},
|
|
5003
|
+
}, Fn = /* @__PURE__ */ new Set([
|
|
4973
5004
|
"button",
|
|
4974
5005
|
"checkbox",
|
|
4975
5006
|
"img",
|
|
@@ -5000,9 +5031,9 @@ const Fn = {
|
|
|
5000
5031
|
const a = [];
|
|
5001
5032
|
for (const t of e.querySelectorAll("*")) {
|
|
5002
5033
|
if (h(t)) continue;
|
|
5003
|
-
const i =
|
|
5004
|
-
if (!(!i || !
|
|
5005
|
-
for (const n of t.querySelectorAll(
|
|
5034
|
+
const i = U(t);
|
|
5035
|
+
if (!(!i || !Fn.has(i))) {
|
|
5036
|
+
for (const n of t.querySelectorAll(j))
|
|
5006
5037
|
if (n !== t && !n.disabled) {
|
|
5007
5038
|
a.push({
|
|
5008
5039
|
ruleId: "aria/presentational-children-focusable",
|
|
@@ -5028,8 +5059,8 @@ const Fn = {
|
|
|
5028
5059
|
Yt,
|
|
5029
5060
|
Xt,
|
|
5030
5061
|
// Time-based Media
|
|
5031
|
-
Kt,
|
|
5032
5062
|
Jt,
|
|
5063
|
+
Kt,
|
|
5033
5064
|
// Adaptable
|
|
5034
5065
|
na,
|
|
5035
5066
|
la,
|
|
@@ -5049,9 +5080,9 @@ const Fn = {
|
|
|
5049
5080
|
Ra,
|
|
5050
5081
|
Ca,
|
|
5051
5082
|
Ta,
|
|
5052
|
-
|
|
5053
|
-
Ka,
|
|
5083
|
+
Da,
|
|
5054
5084
|
Ja,
|
|
5085
|
+
Ka,
|
|
5055
5086
|
// Keyboard Accessible
|
|
5056
5087
|
Za,
|
|
5057
5088
|
ti,
|
|
@@ -5072,8 +5103,8 @@ const Fn = {
|
|
|
5072
5103
|
ki,
|
|
5073
5104
|
Si,
|
|
5074
5105
|
Ii,
|
|
5075
|
-
Li,
|
|
5076
5106
|
Ei,
|
|
5107
|
+
Li,
|
|
5077
5108
|
// Landmarks
|
|
5078
5109
|
Ri,
|
|
5079
5110
|
Ci,
|
|
@@ -5081,10 +5112,10 @@ const Fn = {
|
|
|
5081
5112
|
Ni,
|
|
5082
5113
|
Mi,
|
|
5083
5114
|
$i,
|
|
5115
|
+
Hi,
|
|
5084
5116
|
Di,
|
|
5085
5117
|
Pi,
|
|
5086
5118
|
Fi,
|
|
5087
|
-
Hi,
|
|
5088
5119
|
// Readable
|
|
5089
5120
|
zi,
|
|
5090
5121
|
Oi,
|
|
@@ -5115,43 +5146,48 @@ const Fn = {
|
|
|
5115
5146
|
wn,
|
|
5116
5147
|
kn,
|
|
5117
5148
|
In,
|
|
5118
|
-
|
|
5149
|
+
Ln,
|
|
5119
5150
|
Cn,
|
|
5120
5151
|
Mn,
|
|
5121
5152
|
$n,
|
|
5122
|
-
|
|
5153
|
+
Pn,
|
|
5123
5154
|
zn
|
|
5124
5155
|
];
|
|
5125
|
-
let me = [], et = /* @__PURE__ */ new Set(), tt = !1, at = !1, T,
|
|
5156
|
+
let me = [], et = /* @__PURE__ */ new Set(), tt = !1, at = !1, T, z;
|
|
5126
5157
|
function Vn(e) {
|
|
5127
|
-
e.additionalRules && (me = e.additionalRules), e.disabledRules && (et = new Set(e.disabledRules)), "includeAAA" in e && (tt = !!e.includeAAA), "componentMode" in e && (at = !!e.componentMode), "locale" in e && (T = e.locale || void 0),
|
|
5158
|
+
e.additionalRules && (me = e.additionalRules), e.disabledRules && (et = new Set(e.disabledRules)), "includeAAA" in e && (tt = !!e.includeAAA), "componentMode" in e && (at = !!e.componentMode), "locale" in e && (T = e.locale || void 0), z = void 0;
|
|
5128
5159
|
}
|
|
5129
5160
|
function pe() {
|
|
5130
|
-
if (
|
|
5161
|
+
if (z) return z;
|
|
5131
5162
|
const a = Ze.filter((t) => {
|
|
5132
5163
|
var i;
|
|
5133
5164
|
return !(et.has(t.id) || t.level === "AAA" && !tt || at && ((i = t.tags) != null && i.includes("page-level")));
|
|
5134
5165
|
}).concat(me);
|
|
5135
|
-
return T ? (
|
|
5166
|
+
return T ? (z = Ht(a, T), z) : a;
|
|
5136
5167
|
}
|
|
5137
5168
|
function Bn(e) {
|
|
5138
5169
|
it();
|
|
5139
|
-
const a = pe(), t = T, i = [];
|
|
5140
|
-
let
|
|
5170
|
+
const a = pe(), t = T, i = [], n = [];
|
|
5171
|
+
let r = 0;
|
|
5141
5172
|
return {
|
|
5142
|
-
processChunk(
|
|
5143
|
-
const
|
|
5144
|
-
for (;
|
|
5173
|
+
processChunk(o) {
|
|
5174
|
+
const s = performance.now();
|
|
5175
|
+
for (; r < a.length; ) {
|
|
5176
|
+
const l = a[r];
|
|
5145
5177
|
try {
|
|
5146
|
-
i.push(...
|
|
5147
|
-
} catch {
|
|
5178
|
+
i.push(...l.run(e));
|
|
5179
|
+
} catch (d) {
|
|
5180
|
+
n.push({ ruleId: l.id, error: d instanceof Error ? d.message : String(d) });
|
|
5148
5181
|
}
|
|
5149
|
-
if (
|
|
5182
|
+
if (r++, performance.now() - s >= o) break;
|
|
5150
5183
|
}
|
|
5151
|
-
return
|
|
5184
|
+
return r < a.length;
|
|
5152
5185
|
},
|
|
5153
5186
|
getViolations() {
|
|
5154
5187
|
return t ? ze(i, t) : i;
|
|
5188
|
+
},
|
|
5189
|
+
getSkippedRules() {
|
|
5190
|
+
return n;
|
|
5155
5191
|
}
|
|
5156
5192
|
};
|
|
5157
5193
|
}
|
|
@@ -5159,19 +5195,21 @@ function it() {
|
|
|
5159
5195
|
mt(), ot(), st(), kt(), At(), pt();
|
|
5160
5196
|
}
|
|
5161
5197
|
function _n(e) {
|
|
5162
|
-
var
|
|
5198
|
+
var n;
|
|
5163
5199
|
it();
|
|
5164
|
-
const a = pe(), t = [];
|
|
5165
|
-
for (const
|
|
5200
|
+
const a = pe(), t = [], i = [];
|
|
5201
|
+
for (const r of a)
|
|
5166
5202
|
try {
|
|
5167
|
-
t.push(...
|
|
5168
|
-
} catch {
|
|
5203
|
+
t.push(...r.run(e));
|
|
5204
|
+
} catch (o) {
|
|
5205
|
+
i.push({ ruleId: r.id, error: o instanceof Error ? o.message : String(o) });
|
|
5169
5206
|
}
|
|
5170
5207
|
return {
|
|
5171
|
-
url: ((
|
|
5208
|
+
url: ((n = e.location) == null ? void 0 : n.href) ?? "",
|
|
5172
5209
|
timestamp: Date.now(),
|
|
5173
5210
|
violations: T ? ze(t, T) : t,
|
|
5174
|
-
ruleCount: a.length
|
|
5211
|
+
ruleCount: a.length,
|
|
5212
|
+
skippedRules: i
|
|
5175
5213
|
};
|
|
5176
5214
|
}
|
|
5177
5215
|
function Gn(e, a) {
|
|
@@ -5289,7 +5327,7 @@ const Xn = {
|
|
|
5289
5327
|
"adaptable/orientation-lock": { description: "Page orientation must not be restricted using CSS transforms.", guidance: "Users with motor disabilities may mount their device in a fixed orientation. Using CSS transforms with @media (orientation: portrait/landscape) to rotate content 90° effectively locks the page to one orientation. Remove the orientation-dependent transform and use responsive design instead.", messages: { "CSS locks page orientation via @media (orientation: {0}) with a 90° transform.": "CSS locks page orientation via @media (orientation: {0}) with a 90° transform." } },
|
|
5290
5328
|
"aria/presentational-children-focusable": { description: "Elements with a role that makes children presentational must not contain focusable content.", guidance: "Roles like button, checkbox, img, tab, and others make their children presentational — hidden from assistive technologies. If those children are focusable, keyboard users can reach elements that screen reader users cannot perceive. Move focusable content outside the parent or remove the focusability.", messages: { 'Focusable element inside a "{0}" role whose children are presentational.': 'Focusable element inside a "{0}" role whose children are presentational.' } },
|
|
5291
5329
|
"keyboard-accessible/focus-visible": { description: "Elements in sequential focus order must have a visible focus indicator.", guidance: "Keyboard users need to see which element has focus. Do not remove the default focus outline (outline: none) without providing an alternative visible indicator. Use :focus-visible or :focus styles to ensure focus is always perceivable.", messages: { "Focusable element has outline removed without a visible focus alternative.": "Focusable element has outline removed without a visible focus alternative." } }
|
|
5292
|
-
},
|
|
5330
|
+
}, Jn = {
|
|
5293
5331
|
"navigable/document-title": { description: "Los documentos deben tener un elemento <title> para proporcionar a los usuarios una vista general del contenido.", guidance: "Los usuarios de lectores de pantalla dependen de los títulos de página para identificar y navegar entre pestañas/ventanas. Agregue un elemento <title> descriptivo en <head> que resuma el propósito de la página. Mantenga los títulos únicos en todo el sitio, colocando el contenido específico antes del nombre del sitio (por ejemplo, 'Contáctenos - Acme Corp').", messages: { "Document <title> element is empty.": "El elemento <title> del documento está vacío.", "Document is missing a <title> element.": "Al documento le falta un elemento <title>." } },
|
|
5294
5332
|
"navigable/bypass": { description: "La página debe tener un mecanismo para omitir bloques de contenido repetidos.", guidance: 'Los usuarios de teclado deben poder omitir contenido repetitivo como la navegación. Proporcione un enlace de salto en la parte superior de la página que enlace al contenido principal (por ejemplo, <a href="#main">Saltar al contenido principal</a>), o use un landmark <main>. Los lectores de pantalla pueden saltar directamente a los landmarks, por lo que un elemento <main> correctamente marcado satisface este requisito.', messages: { "Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.": "La página no tiene un mecanismo para omitir contenido repetido. Agregue un landmark <main> o un enlace de salto." } },
|
|
5295
5333
|
"navigable/page-has-heading-one": { description: "La página debe contener un encabezado de nivel uno.", guidance: "Un encabezado de nivel uno (<h1> o role='heading' con aria-level='1') ayuda a los usuarios a comprender el tema de la página y proporciona un punto de referencia para la navegación con lector de pantalla. Cada página debe tener exactamente un h1 que describa el contenido principal, típicamente coincidiendo o similar al título de la página.", messages: { "Page does not contain a level-one heading.": "La página no contiene un encabezado de nivel uno." } },
|
|
@@ -5393,10 +5431,10 @@ export {
|
|
|
5393
5431
|
Vn as configureRules,
|
|
5394
5432
|
Bn as createChunkedAudit,
|
|
5395
5433
|
Gn as diffAudit,
|
|
5396
|
-
|
|
5434
|
+
w as getAccessibleName,
|
|
5397
5435
|
k as getAccessibleTextContent,
|
|
5398
5436
|
pe as getActiveRules,
|
|
5399
|
-
|
|
5437
|
+
U as getComputedRole,
|
|
5400
5438
|
m as getHtmlSnippet,
|
|
5401
5439
|
$e as getImplicitRole,
|
|
5402
5440
|
Yn as getRuleById,
|
|
@@ -5404,7 +5442,7 @@ export {
|
|
|
5404
5442
|
h as isAriaHidden,
|
|
5405
5443
|
dt as isValidRole,
|
|
5406
5444
|
Xn as localeEn,
|
|
5407
|
-
|
|
5445
|
+
Jn as localeEs,
|
|
5408
5446
|
Un as querySelectorShadowAware,
|
|
5409
5447
|
Wn as registerLocale,
|
|
5410
5448
|
Ze as rules,
|