@accesslint/core 0.8.1 → 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/dist/index.cjs +5 -5
- package/dist/index.iife.js +5 -5
- package/dist/index.js +280 -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]");
|
|
@@ -1405,13 +1410,14 @@ const Wt = {
|
|
|
1405
1410
|
wcag: ["1.1.1"],
|
|
1406
1411
|
level: "A",
|
|
1407
1412
|
fixability: "contextual",
|
|
1413
|
+
browserHint: "Screenshot the element to see its visual appearance, then provide an aria-label describing what it represents.",
|
|
1408
1414
|
description: "Elements with role='img' must have an accessible name.",
|
|
1409
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.",
|
|
1410
1416
|
run(e) {
|
|
1411
1417
|
const a = [];
|
|
1412
1418
|
for (const t of e.querySelectorAll('[role="img"]')) {
|
|
1413
1419
|
if (h(t) || t.tagName.toLowerCase() === "svg" || t.tagName.toLowerCase() === "img") continue;
|
|
1414
|
-
|
|
1420
|
+
w(t) || a.push({
|
|
1415
1421
|
ruleId: "text-alternatives/role-img-alt",
|
|
1416
1422
|
selector: p(t),
|
|
1417
1423
|
html: m(t),
|
|
@@ -1422,13 +1428,14 @@ const Wt = {
|
|
|
1422
1428
|
}
|
|
1423
1429
|
return a;
|
|
1424
1430
|
}
|
|
1425
|
-
},
|
|
1431
|
+
}, Jt = {
|
|
1426
1432
|
id: "time-based-media/video-captions",
|
|
1427
1433
|
category: "time-based-media",
|
|
1428
1434
|
actRuleIds: ["eac66b"],
|
|
1429
1435
|
wcag: ["1.2.2"],
|
|
1430
1436
|
level: "A",
|
|
1431
1437
|
fixability: "contextual",
|
|
1438
|
+
browserHint: "Screenshot the video element to see its poster or content for context when writing captions.",
|
|
1432
1439
|
description: "Video elements must have captions via <track kind='captions'> or <track kind='subtitles'>.",
|
|
1433
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.",
|
|
1434
1441
|
run(e) {
|
|
@@ -1445,13 +1452,14 @@ const Wt = {
|
|
|
1445
1452
|
}
|
|
1446
1453
|
return a;
|
|
1447
1454
|
}
|
|
1448
|
-
},
|
|
1455
|
+
}, Kt = {
|
|
1449
1456
|
id: "time-based-media/audio-transcript",
|
|
1450
1457
|
category: "time-based-media",
|
|
1451
1458
|
actRuleIds: ["e7aa44"],
|
|
1452
1459
|
wcag: ["1.2.1"],
|
|
1453
1460
|
level: "A",
|
|
1454
1461
|
fixability: "contextual",
|
|
1462
|
+
browserHint: "Inspect the page around the audio element for existing transcript links or associated text content.",
|
|
1455
1463
|
description: "Audio elements should have a text alternative or transcript.",
|
|
1456
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.",
|
|
1457
1465
|
run(e) {
|
|
@@ -1620,7 +1628,7 @@ function ra(e) {
|
|
|
1620
1628
|
return `Unknown check type: ${String(e.type)}`;
|
|
1621
1629
|
}
|
|
1622
1630
|
}
|
|
1623
|
-
function
|
|
1631
|
+
function L(e, a, t) {
|
|
1624
1632
|
let i = e;
|
|
1625
1633
|
if (i.includes("{{tag}}") && (i = i.replace(/\{\{tag\}\}/g, a.tagName.toLowerCase())), i.includes("{{value}}")) {
|
|
1626
1634
|
let n = "";
|
|
@@ -1638,6 +1646,7 @@ function N(e) {
|
|
|
1638
1646
|
level: e.level,
|
|
1639
1647
|
tags: e.tags,
|
|
1640
1648
|
fixability: e.fixability,
|
|
1649
|
+
browserHint: e.browserHint,
|
|
1641
1650
|
description: e.description,
|
|
1642
1651
|
guidance: e.guidance,
|
|
1643
1652
|
run(t) {
|
|
@@ -1651,7 +1660,7 @@ function N(e) {
|
|
|
1651
1660
|
selector: p(o),
|
|
1652
1661
|
html: m(o),
|
|
1653
1662
|
impact: e.impact,
|
|
1654
|
-
message:
|
|
1663
|
+
message: L(e.message, o, e.check),
|
|
1655
1664
|
...e.fix ? { fix: e.fix } : {},
|
|
1656
1665
|
element: o
|
|
1657
1666
|
});
|
|
@@ -1667,7 +1676,7 @@ function N(e) {
|
|
|
1667
1676
|
selector: p(d),
|
|
1668
1677
|
html: m(d),
|
|
1669
1678
|
impact: e.impact,
|
|
1670
|
-
message:
|
|
1679
|
+
message: L(e.message, d, e.check),
|
|
1671
1680
|
...e.fix ? { fix: e.fix } : {},
|
|
1672
1681
|
element: d
|
|
1673
1682
|
});
|
|
@@ -1682,7 +1691,7 @@ function N(e) {
|
|
|
1682
1691
|
selector: p(s),
|
|
1683
1692
|
html: m(s),
|
|
1684
1693
|
impact: e.impact,
|
|
1685
|
-
message:
|
|
1694
|
+
message: L(e.message, s, e.check),
|
|
1686
1695
|
...e.fix ? { fix: e.fix } : {},
|
|
1687
1696
|
element: s
|
|
1688
1697
|
});
|
|
@@ -1706,7 +1715,7 @@ function N(e) {
|
|
|
1706
1715
|
selector: p(u),
|
|
1707
1716
|
html: m(u),
|
|
1708
1717
|
impact: e.impact,
|
|
1709
|
-
message:
|
|
1718
|
+
message: L(e.message, u, e.check),
|
|
1710
1719
|
...e.fix ? { fix: e.fix } : {},
|
|
1711
1720
|
element: u
|
|
1712
1721
|
}) : !d && g && i.push({
|
|
@@ -1714,7 +1723,7 @@ function N(e) {
|
|
|
1714
1723
|
selector: p(u),
|
|
1715
1724
|
html: m(u),
|
|
1716
1725
|
impact: e.impact,
|
|
1717
|
-
message:
|
|
1726
|
+
message: L(e.message, u, e.check),
|
|
1718
1727
|
...e.fix ? { fix: e.fix } : {},
|
|
1719
1728
|
element: u
|
|
1720
1729
|
});
|
|
@@ -1729,7 +1738,7 @@ function N(e) {
|
|
|
1729
1738
|
selector: p(s),
|
|
1730
1739
|
html: m(s),
|
|
1731
1740
|
impact: e.impact,
|
|
1732
|
-
message:
|
|
1741
|
+
message: L(e.message, s, e.check),
|
|
1733
1742
|
...e.fix ? { fix: e.fix } : {},
|
|
1734
1743
|
element: s
|
|
1735
1744
|
});
|
|
@@ -1771,7 +1780,7 @@ function N(e) {
|
|
|
1771
1780
|
selector: p(b),
|
|
1772
1781
|
html: m(b),
|
|
1773
1782
|
impact: e.impact,
|
|
1774
|
-
message:
|
|
1783
|
+
message: L(e.message, b, e.check),
|
|
1775
1784
|
...e.fix ? { fix: e.fix } : {},
|
|
1776
1785
|
element: b
|
|
1777
1786
|
});
|
|
@@ -1890,7 +1899,7 @@ function je(e, a) {
|
|
|
1890
1899
|
return NaN;
|
|
1891
1900
|
}
|
|
1892
1901
|
}
|
|
1893
|
-
function
|
|
1902
|
+
function B(e) {
|
|
1894
1903
|
return isNaN(e) ? !1 : (e = (e % 360 + 360) % 360, e >= 85 && e <= 95 || e >= 265 && e <= 275);
|
|
1895
1904
|
}
|
|
1896
1905
|
function pa(e) {
|
|
@@ -1899,21 +1908,21 @@ function pa(e) {
|
|
|
1899
1908
|
);
|
|
1900
1909
|
if (a) {
|
|
1901
1910
|
const n = je(parseFloat(a[1]), a[2]);
|
|
1902
|
-
if (
|
|
1911
|
+
if (B(n)) return !0;
|
|
1903
1912
|
}
|
|
1904
1913
|
const t = e.match(
|
|
1905
1914
|
/matrix\(\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)\s*,\s*(-?[\d.e]+)/i
|
|
1906
1915
|
);
|
|
1907
1916
|
if (t) {
|
|
1908
1917
|
const n = parseFloat(t[1]), r = parseFloat(t[2]), o = Math.atan2(r, n) * (180 / Math.PI);
|
|
1909
|
-
if (
|
|
1918
|
+
if (B(o)) return !0;
|
|
1910
1919
|
}
|
|
1911
1920
|
const i = e.match(
|
|
1912
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
|
|
1913
1922
|
);
|
|
1914
1923
|
if (i) {
|
|
1915
1924
|
const n = parseFloat(i[1]), r = parseFloat(i[2]), o = Math.atan2(r, n) * (180 / Math.PI);
|
|
1916
|
-
if (
|
|
1925
|
+
if (B(o)) return !0;
|
|
1917
1926
|
}
|
|
1918
1927
|
return !1;
|
|
1919
1928
|
}
|
|
@@ -1921,7 +1930,7 @@ function ba(e) {
|
|
|
1921
1930
|
const a = e.match(/(-?[\d.]+)(deg|rad|turn|grad)/i);
|
|
1922
1931
|
if (!a) return !1;
|
|
1923
1932
|
const t = je(parseFloat(a[1]), a[2]);
|
|
1924
|
-
return
|
|
1933
|
+
return B(t);
|
|
1925
1934
|
}
|
|
1926
1935
|
const ha = {
|
|
1927
1936
|
id: "adaptable/orientation-lock",
|
|
@@ -1998,14 +2007,14 @@ function fa(e, a) {
|
|
|
1998
2007
|
for (const s of e.querySelectorAll("*")) {
|
|
1999
2008
|
if (h(s)) continue;
|
|
2000
2009
|
r = !0;
|
|
2001
|
-
const l =
|
|
2010
|
+
const l = U(s);
|
|
2002
2011
|
l && n.add(l);
|
|
2003
2012
|
}
|
|
2004
2013
|
for (const s of t) {
|
|
2005
2014
|
const l = i.getElementById(s);
|
|
2006
2015
|
if (l && !h(l)) {
|
|
2007
2016
|
r = !0;
|
|
2008
|
-
const d =
|
|
2017
|
+
const d = U(l);
|
|
2009
2018
|
d && n.add(d);
|
|
2010
2019
|
}
|
|
2011
2020
|
}
|
|
@@ -2082,7 +2091,7 @@ const va = {
|
|
|
2082
2091
|
const r = Se[n];
|
|
2083
2092
|
let o = i.parentElement, s = !1;
|
|
2084
2093
|
for (; o && o !== e.documentElement; ) {
|
|
2085
|
-
const l =
|
|
2094
|
+
const l = U(o);
|
|
2086
2095
|
if (l && r.includes(l)) {
|
|
2087
2096
|
s = !0;
|
|
2088
2097
|
break;
|
|
@@ -2188,6 +2197,7 @@ const ka = {
|
|
|
2188
2197
|
wcag: ["1.3.1"],
|
|
2189
2198
|
level: "A",
|
|
2190
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.",
|
|
2191
2201
|
description: "Data cells in tables larger than 3x3 should have associated headers.",
|
|
2192
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.",
|
|
2193
2203
|
run(e) {
|
|
@@ -2214,9 +2224,9 @@ const ka = {
|
|
|
2214
2224
|
let g = !1;
|
|
2215
2225
|
const f = i.querySelector("thead"), v = (f == null ? void 0 : f.querySelector("tr")) ?? i.querySelector("tbody > tr, tr");
|
|
2216
2226
|
if (v)
|
|
2217
|
-
for (const
|
|
2218
|
-
const A = Ie(
|
|
2219
|
-
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) {
|
|
2220
2230
|
g = !0;
|
|
2221
2231
|
break;
|
|
2222
2232
|
}
|
|
@@ -2266,6 +2276,7 @@ const ka = {
|
|
|
2266
2276
|
level: "A",
|
|
2267
2277
|
tags: ["best-practice"],
|
|
2268
2278
|
fixability: "contextual",
|
|
2279
|
+
browserHint: "Screenshot the table to see which header cells are visually empty, then add text content or aria-label.",
|
|
2269
2280
|
description: "Table header cells should have visible text.",
|
|
2270
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.",
|
|
2271
2282
|
run(e) {
|
|
@@ -2273,7 +2284,7 @@ const ka = {
|
|
|
2273
2284
|
for (const t of e.querySelectorAll("th")) {
|
|
2274
2285
|
if (h(t)) continue;
|
|
2275
2286
|
const i = t.closest("table");
|
|
2276
|
-
(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({
|
|
2277
2288
|
ruleId: "adaptable/empty-table-header",
|
|
2278
2289
|
selector: p(t),
|
|
2279
2290
|
html: m(t),
|
|
@@ -2291,6 +2302,7 @@ const ka = {
|
|
|
2291
2302
|
level: "AA",
|
|
2292
2303
|
tags: ["page-level"],
|
|
2293
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.",
|
|
2294
2306
|
description: "Viewport meta tag must not disable user scaling.",
|
|
2295
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.",
|
|
2296
2308
|
run(e) {
|
|
@@ -2387,7 +2399,7 @@ function We(e, a, t, i) {
|
|
|
2387
2399
|
}
|
|
2388
2400
|
for (const s of r.childNodes)
|
|
2389
2401
|
if (s.nodeType === 3 && ((o = s.textContent) != null && o.trim())) {
|
|
2390
|
-
const l = parseFloat(
|
|
2402
|
+
const l = parseFloat(x(r).fontSize);
|
|
2391
2403
|
if (l > 0 && t / l < i) return !0;
|
|
2392
2404
|
break;
|
|
2393
2405
|
}
|
|
@@ -2397,7 +2409,7 @@ function We(e, a, t, i) {
|
|
|
2397
2409
|
}
|
|
2398
2410
|
return n(e);
|
|
2399
2411
|
}
|
|
2400
|
-
function
|
|
2412
|
+
function Ea(e) {
|
|
2401
2413
|
var a;
|
|
2402
2414
|
for (const t of e.childNodes)
|
|
2403
2415
|
if (t.nodeType === 3 && ((a = t.textContent) != null && a.trim()))
|
|
@@ -2419,7 +2431,7 @@ function Ve(e) {
|
|
|
2419
2431
|
return !1;
|
|
2420
2432
|
}
|
|
2421
2433
|
function de(e, a) {
|
|
2422
|
-
if (
|
|
2434
|
+
if (Ea(e)) return !0;
|
|
2423
2435
|
for (const t of e.children) {
|
|
2424
2436
|
const i = t.getAttribute("style") || "";
|
|
2425
2437
|
if (!new RegExp(
|
|
@@ -2450,10 +2462,10 @@ function Be(e, a, t, i) {
|
|
|
2450
2462
|
}
|
|
2451
2463
|
return n;
|
|
2452
2464
|
}
|
|
2453
|
-
function
|
|
2465
|
+
function La(e) {
|
|
2454
2466
|
let a = e, t = !1;
|
|
2455
2467
|
for (; a; ) {
|
|
2456
|
-
const i =
|
|
2468
|
+
const i = x(a);
|
|
2457
2469
|
parseFloat(i.width) > 500 && (t = !0), (i.whiteSpace === "nowrap" || i.whiteSpace === "pre") && (t = !0);
|
|
2458
2470
|
const r = i.overflowX, o = i.overflowY;
|
|
2459
2471
|
if ((r === "scroll" || r === "auto") && o !== "scroll" && o !== "auto")
|
|
@@ -2486,9 +2498,9 @@ const Ra = {
|
|
|
2486
2498
|
run(e) {
|
|
2487
2499
|
const a = [];
|
|
2488
2500
|
for (const t of e.querySelectorAll("[style]")) {
|
|
2489
|
-
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;
|
|
2490
2502
|
if (t instanceof HTMLElement && t.scrollHeight > 0) {
|
|
2491
|
-
const r = parseFloat(
|
|
2503
|
+
const r = parseFloat(x(t).lineHeight);
|
|
2492
2504
|
if (r > 0 && t.scrollHeight <= r * 1.5) continue;
|
|
2493
2505
|
}
|
|
2494
2506
|
const i = Ue(t, "line-height");
|
|
@@ -2535,7 +2547,7 @@ const Ra = {
|
|
|
2535
2547
|
]);
|
|
2536
2548
|
function $a(e) {
|
|
2537
2549
|
let a = e.parentElement;
|
|
2538
|
-
for (; a && !Na.has(
|
|
2550
|
+
for (; a && !Na.has(x(a).display); )
|
|
2539
2551
|
a = a.parentElement;
|
|
2540
2552
|
if (!a) return null;
|
|
2541
2553
|
const t = a.ownerDocument.createTreeWalker(a, NodeFilter.SHOW_TEXT);
|
|
@@ -2550,7 +2562,7 @@ function $a(e) {
|
|
|
2550
2562
|
}
|
|
2551
2563
|
s = s.parentElement;
|
|
2552
2564
|
}
|
|
2553
|
-
l || (i += r.data, !n && r.parentElement && (n = C(
|
|
2565
|
+
l || (i += r.data, !n && r.parentElement && (n = C(x(r.parentElement).color)));
|
|
2554
2566
|
}
|
|
2555
2567
|
const o = i.match(new RegExp("\\p{L}{3,}", "gu"));
|
|
2556
2568
|
return !n || !o || o.length < 2 ? null : { block: a, textColor: n };
|
|
@@ -2559,40 +2571,41 @@ function qe(e, a) {
|
|
|
2559
2571
|
const t = e.textDecorationLine || e.textDecoration || "";
|
|
2560
2572
|
return (t.includes("underline") || t.includes("line-through")) && t !== a;
|
|
2561
2573
|
}
|
|
2562
|
-
function
|
|
2574
|
+
function V(e) {
|
|
2563
2575
|
return e === "bold" ? 700 : e === "normal" ? 400 : parseInt(e) || 400;
|
|
2564
2576
|
}
|
|
2565
|
-
function
|
|
2577
|
+
function Ha(e) {
|
|
2566
2578
|
const a = e.ownerDocument.createTreeWalker(e, NodeFilter.SHOW_TEXT);
|
|
2567
2579
|
let t;
|
|
2568
2580
|
for (; t = a.nextNode(); )
|
|
2569
2581
|
if (t.data.trim()) return !1;
|
|
2570
2582
|
return !0;
|
|
2571
2583
|
}
|
|
2572
|
-
const
|
|
2584
|
+
const Da = {
|
|
2573
2585
|
id: "distinguishable/link-in-text-block",
|
|
2574
2586
|
category: "distinguishable",
|
|
2575
2587
|
wcag: ["1.4.1"],
|
|
2576
2588
|
level: "A",
|
|
2577
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.",
|
|
2578
2591
|
description: "Links within text blocks must be distinguishable by more than color alone.",
|
|
2579
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.",
|
|
2580
2593
|
run(e) {
|
|
2581
2594
|
const a = [];
|
|
2582
2595
|
for (const t of e.querySelectorAll("a[href]")) {
|
|
2583
|
-
if (h(t) || !k(t).trim() ||
|
|
2584
|
-
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);
|
|
2585
2598
|
if (!Ma.has(i.display || "inline")) continue;
|
|
2586
2599
|
const n = $a(t);
|
|
2587
2600
|
if (!n) continue;
|
|
2588
|
-
const r =
|
|
2589
|
-
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;
|
|
2590
2603
|
const l = parseFloat(i.fontSize) || 16, d = parseFloat(r.fontSize) || 16;
|
|
2591
2604
|
if (d > 0 && l / d >= 1.2) continue;
|
|
2592
2605
|
let c = !1;
|
|
2593
|
-
for (const
|
|
2594
|
-
const A = y
|
|
2595
|
-
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) {
|
|
2596
2609
|
c = !0;
|
|
2597
2610
|
break;
|
|
2598
2611
|
}
|
|
@@ -2602,19 +2615,20 @@ const Pa = {
|
|
|
2602
2615
|
if (!u) continue;
|
|
2603
2616
|
const b = I(...u), g = I(...n.textColor), f = $(b, g);
|
|
2604
2617
|
if (f < 1.1 || f >= 3) continue;
|
|
2605
|
-
const v = (
|
|
2618
|
+
const v = (y) => "#" + y.map((A) => A.toString(16).padStart(2, "0")).join("");
|
|
2606
2619
|
a.push({
|
|
2607
2620
|
ruleId: "distinguishable/link-in-text-block",
|
|
2608
2621
|
selector: p(t),
|
|
2609
2622
|
html: m(t),
|
|
2610
2623
|
impact: "serious",
|
|
2611
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.",
|
|
2612
|
-
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." }
|
|
2613
2627
|
});
|
|
2614
2628
|
}
|
|
2615
2629
|
return a;
|
|
2616
2630
|
}
|
|
2617
|
-
},
|
|
2631
|
+
}, Pa = /* @__PURE__ */ new Set([
|
|
2618
2632
|
"SCRIPT",
|
|
2619
2633
|
"STYLE",
|
|
2620
2634
|
"NOSCRIPT",
|
|
@@ -2630,7 +2644,7 @@ const Pa = {
|
|
|
2630
2644
|
"BR",
|
|
2631
2645
|
"HR"
|
|
2632
2646
|
]);
|
|
2633
|
-
function
|
|
2647
|
+
function Fa(e) {
|
|
2634
2648
|
const a = e.clip;
|
|
2635
2649
|
if (a && a.startsWith("rect(")) {
|
|
2636
2650
|
const i = a.match(/[\d.]+/g);
|
|
@@ -2648,8 +2662,8 @@ function za(e) {
|
|
|
2648
2662
|
if (h(e)) return !0;
|
|
2649
2663
|
let a = e;
|
|
2650
2664
|
for (; a; ) {
|
|
2651
|
-
const t =
|
|
2652
|
-
if (t.display === "none" || t.visibility === "hidden" ||
|
|
2665
|
+
const t = x(a);
|
|
2666
|
+
if (t.display === "none" || t.visibility === "hidden" || Fa(t)) return !0;
|
|
2653
2667
|
a = a.parentElement;
|
|
2654
2668
|
}
|
|
2655
2669
|
return !1;
|
|
@@ -2694,10 +2708,10 @@ function _a(e) {
|
|
|
2694
2708
|
const a = parseFloat(e);
|
|
2695
2709
|
return isNaN(a) ? NaN : e.trim().endsWith("%") ? a / 100 : a;
|
|
2696
2710
|
}
|
|
2697
|
-
const
|
|
2698
|
-
function
|
|
2711
|
+
const Ee = /([a-z-]+)\(([^)]*)\)/g;
|
|
2712
|
+
function Le(e) {
|
|
2699
2713
|
let a, t = !1;
|
|
2700
|
-
for (
|
|
2714
|
+
for (Ee.lastIndex = 0; a = Ee.exec(e); ) {
|
|
2701
2715
|
t = !0;
|
|
2702
2716
|
const i = Ba[a[1]];
|
|
2703
2717
|
if (i === void 0 || _a(a[2]) !== i) return !1;
|
|
@@ -2707,12 +2721,12 @@ function Ee(e) {
|
|
|
2707
2721
|
function Ga(e) {
|
|
2708
2722
|
let a = e;
|
|
2709
2723
|
for (; a; ) {
|
|
2710
|
-
const t =
|
|
2711
|
-
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;
|
|
2712
2726
|
const n = t.mixBlendMode;
|
|
2713
2727
|
if (n && n !== "normal" && n !== "initial") return !0;
|
|
2714
2728
|
const r = t.backdropFilter;
|
|
2715
|
-
if (r && r !== "none" && r !== "initial" && !
|
|
2729
|
+
if (r && r !== "none" && r !== "initial" && !Le(r)) return !0;
|
|
2716
2730
|
a = a.parentElement;
|
|
2717
2731
|
}
|
|
2718
2732
|
return !1;
|
|
@@ -2720,7 +2734,7 @@ function Ga(e) {
|
|
|
2720
2734
|
function Ya(e) {
|
|
2721
2735
|
let a = e;
|
|
2722
2736
|
for (; a; ) {
|
|
2723
|
-
const t =
|
|
2737
|
+
const t = x(a), i = t.backgroundImage;
|
|
2724
2738
|
if (i && i !== "none" && i !== "initial")
|
|
2725
2739
|
return i.includes("gradient(") ? { bgImage: i, gradientEl: a } : null;
|
|
2726
2740
|
const n = t.backgroundColor;
|
|
@@ -2728,7 +2742,7 @@ function Ya(e) {
|
|
|
2728
2742
|
a = a.parentElement;
|
|
2729
2743
|
continue;
|
|
2730
2744
|
}
|
|
2731
|
-
if (
|
|
2745
|
+
if (W(n) < 0.01) {
|
|
2732
2746
|
a = a.parentElement;
|
|
2733
2747
|
continue;
|
|
2734
2748
|
}
|
|
@@ -2743,11 +2757,11 @@ function Xa(e, a, t, i, n, r, o, s, l) {
|
|
|
2743
2757
|
for (const f of d) {
|
|
2744
2758
|
let v = a;
|
|
2745
2759
|
t < 1 && (v = R(a, f, t)), i < 1 && (v = R(v, f, i));
|
|
2746
|
-
const
|
|
2760
|
+
const y = $(
|
|
2747
2761
|
I(v[0], v[1], v[2]),
|
|
2748
2762
|
I(f[0], f[1], f[2])
|
|
2749
2763
|
);
|
|
2750
|
-
|
|
2764
|
+
y > c && (c = y, u = f);
|
|
2751
2765
|
}
|
|
2752
2766
|
if (c >= n) return null;
|
|
2753
2767
|
let b = a;
|
|
@@ -2759,7 +2773,8 @@ function Xa(e, a, t, i, n, r, o, s, l) {
|
|
|
2759
2773
|
html: m(e),
|
|
2760
2774
|
impact: "serious",
|
|
2761
2775
|
message: `Insufficient${o === "AAA" ? " enhanced" : ""} color contrast ratio of ${g}:1 (required ${n}:1).`,
|
|
2762
|
-
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)}.` }
|
|
2763
2778
|
};
|
|
2764
2779
|
}
|
|
2765
2780
|
function _e(e, a, t) {
|
|
@@ -2770,10 +2785,10 @@ function _e(e, a, t) {
|
|
|
2770
2785
|
for (; s = r.nextNode(); ) {
|
|
2771
2786
|
if (!s.textContent || !s.textContent.trim() || Oa(s.textContent)) continue;
|
|
2772
2787
|
const l = s.parentElement;
|
|
2773
|
-
if (!l || o.has(l) || (o.add(l),
|
|
2788
|
+
if (!l || o.has(l) || (o.add(l), Pa.has(l.tagName))) continue;
|
|
2774
2789
|
const d = l.tagName;
|
|
2775
2790
|
if (d === "BODY" || d === "HTML" || Wa(l) || ja(l) || Ua(l, e) || Va(l) || za(l)) continue;
|
|
2776
|
-
const c =
|
|
2791
|
+
const c = x(l);
|
|
2777
2792
|
if (parseFloat(c.opacity) === 0) continue;
|
|
2778
2793
|
const u = Mt(l);
|
|
2779
2794
|
if (u < 0.1) continue;
|
|
@@ -2782,47 +2797,48 @@ function _e(e, a, t) {
|
|
|
2782
2797
|
if (b && b !== "none" && b !== "initial" && (g = Tt(b), !g) || Ga(l) || $t(l)) continue;
|
|
2783
2798
|
const f = C(c.color);
|
|
2784
2799
|
if (!f) continue;
|
|
2785
|
-
const v =
|
|
2786
|
-
if (v === 0 ||
|
|
2787
|
-
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;
|
|
2788
2803
|
let A = ye(l);
|
|
2789
2804
|
if (!A) {
|
|
2790
2805
|
if (g) continue;
|
|
2791
|
-
const
|
|
2792
|
-
if (
|
|
2793
|
-
const
|
|
2806
|
+
const E = Ya(l);
|
|
2807
|
+
if (E) {
|
|
2808
|
+
const O = E.gradientEl.parentElement ? ye(E.gradientEl.parentElement) : null, H = Xa(
|
|
2794
2809
|
l,
|
|
2795
2810
|
f,
|
|
2796
2811
|
v,
|
|
2797
2812
|
u,
|
|
2798
|
-
|
|
2813
|
+
y,
|
|
2799
2814
|
a,
|
|
2800
2815
|
t,
|
|
2801
|
-
|
|
2802
|
-
|
|
2816
|
+
E.bgImage,
|
|
2817
|
+
O ?? [255, 255, 255]
|
|
2803
2818
|
);
|
|
2804
|
-
|
|
2819
|
+
H && i.push(H);
|
|
2805
2820
|
}
|
|
2806
2821
|
continue;
|
|
2807
2822
|
}
|
|
2808
2823
|
let S = f;
|
|
2809
2824
|
v < 1 && (S = R(f, A, v)), u < 1 && (S = R(S, A, u));
|
|
2810
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);
|
|
2811
|
-
if (be <
|
|
2812
|
-
const
|
|
2826
|
+
if (be < y) {
|
|
2827
|
+
const E = Math.round(be * 100) / 100, O = _(S), H = _(A);
|
|
2813
2828
|
i.push({
|
|
2814
2829
|
ruleId: a,
|
|
2815
2830
|
selector: p(l),
|
|
2816
2831
|
html: m(l),
|
|
2817
2832
|
impact: "serious",
|
|
2818
|
-
message: `Insufficient${t === "AAA" ? " enhanced" : ""} color contrast ratio of ${
|
|
2819
|
-
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}.` }
|
|
2820
2836
|
});
|
|
2821
2837
|
}
|
|
2822
2838
|
}
|
|
2823
2839
|
return i;
|
|
2824
2840
|
}
|
|
2825
|
-
const
|
|
2841
|
+
const Ja = {
|
|
2826
2842
|
id: "distinguishable/color-contrast",
|
|
2827
2843
|
category: "distinguishable",
|
|
2828
2844
|
actRuleIds: ["afw4f7"],
|
|
@@ -2830,11 +2846,12 @@ const Ka = {
|
|
|
2830
2846
|
level: "AA",
|
|
2831
2847
|
fixability: "visual",
|
|
2832
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.",
|
|
2833
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.",
|
|
2834
2851
|
run(e) {
|
|
2835
2852
|
return _e(e, "distinguishable/color-contrast", "AA");
|
|
2836
2853
|
}
|
|
2837
|
-
},
|
|
2854
|
+
}, Ka = {
|
|
2838
2855
|
id: "distinguishable/color-contrast-enhanced",
|
|
2839
2856
|
category: "distinguishable",
|
|
2840
2857
|
actRuleIds: ["09o5cg"],
|
|
@@ -2842,6 +2859,7 @@ const Ka = {
|
|
|
2842
2859
|
level: "AAA",
|
|
2843
2860
|
fixability: "visual",
|
|
2844
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.",
|
|
2845
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.",
|
|
2846
2864
|
run(e) {
|
|
2847
2865
|
return _e(e, "distinguishable/color-contrast-enhanced", "AAA");
|
|
@@ -3025,6 +3043,7 @@ const di = {
|
|
|
3025
3043
|
wcag: ["2.1.1"],
|
|
3026
3044
|
level: "A",
|
|
3027
3045
|
fixability: "contextual",
|
|
3046
|
+
browserHint: "Tab to the scrollable region and verify keyboard scrolling works with arrow keys.",
|
|
3028
3047
|
description: "Scrollable regions must be keyboard accessible.",
|
|
3029
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.",
|
|
3030
3049
|
run(e) {
|
|
@@ -3036,17 +3055,17 @@ const di = {
|
|
|
3036
3055
|
if (n === "body" || n === "html") continue;
|
|
3037
3056
|
const r = i.getAttribute("role");
|
|
3038
3057
|
if (r === "presentation" || r === "none" || r === "listbox" || r === "menu" || r === "tree" || r === "tabpanel") continue;
|
|
3039
|
-
const o =
|
|
3058
|
+
const o = x(i), s = o.overflowX, l = o.overflowY;
|
|
3040
3059
|
if (!(s === "scroll" || s === "auto" || l === "scroll" || l === "auto")) continue;
|
|
3041
3060
|
if (i.scrollHeight > 0 || i.clientHeight > 0) {
|
|
3042
3061
|
const g = i.scrollHeight - i.clientHeight, f = i.scrollWidth - i.clientWidth;
|
|
3043
3062
|
if (g <= 0 && f <= 0 || g < 14 && f < 14 || i.clientWidth < 64 && i.clientHeight < 64) continue;
|
|
3044
|
-
const v = ((t = i.textContent) == null ? void 0 : t.trim().length) ?? 0,
|
|
3045
|
-
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;
|
|
3046
3065
|
} else
|
|
3047
3066
|
continue;
|
|
3048
3067
|
const u = i.getAttribute("tabindex");
|
|
3049
|
-
u !== null && u !== "-1" || i.querySelector(
|
|
3068
|
+
u !== null && u !== "-1" || i.querySelector(j) || a.push({
|
|
3050
3069
|
ruleId: "keyboard-accessible/scrollable-region",
|
|
3051
3070
|
selector: p(i),
|
|
3052
3071
|
html: m(i),
|
|
@@ -3094,11 +3113,12 @@ const di = {
|
|
|
3094
3113
|
wcag: ["2.4.7"],
|
|
3095
3114
|
level: "AA",
|
|
3096
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.",
|
|
3097
3117
|
description: "Elements in sequential focus order must have a visible focus indicator.",
|
|
3098
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.",
|
|
3099
3119
|
run(e) {
|
|
3100
3120
|
const a = [];
|
|
3101
|
-
for (const t of e.querySelectorAll(
|
|
3121
|
+
for (const t of e.querySelectorAll(j)) {
|
|
3102
3122
|
if (h(t) || !(t instanceof HTMLElement)) continue;
|
|
3103
3123
|
const i = t.getAttribute("style") || "";
|
|
3104
3124
|
if (/outline\s*:\s*(none|0)\s*(;|$|!)/i.test(i)) {
|
|
@@ -3128,7 +3148,7 @@ function Ye(e) {
|
|
|
3128
3148
|
return { seconds: t, hasValidUrl: i };
|
|
3129
3149
|
}
|
|
3130
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"]';
|
|
3131
|
-
function
|
|
3151
|
+
function Je(e) {
|
|
3132
3152
|
return {
|
|
3133
3153
|
id: e.id,
|
|
3134
3154
|
category: e.id.split("/")[0],
|
|
@@ -3351,10 +3371,10 @@ const bi = {
|
|
|
3351
3371
|
run(e) {
|
|
3352
3372
|
var o, s, l;
|
|
3353
3373
|
const a = e.querySelector("h1");
|
|
3354
|
-
if (a &&
|
|
3374
|
+
if (a && w(a)) return [];
|
|
3355
3375
|
const t = e.querySelectorAll('[role="heading"][aria-level="1"]');
|
|
3356
3376
|
for (const d of t)
|
|
3357
|
-
if (
|
|
3377
|
+
if (w(d)) return [];
|
|
3358
3378
|
const i = [], n = (s = (o = e.querySelector("title")) == null ? void 0 : o.textContent) == null ? void 0 : s.trim();
|
|
3359
3379
|
n && i.push(`Page title: "${n}"`);
|
|
3360
3380
|
const r = e.querySelector("main");
|
|
@@ -3378,6 +3398,7 @@ const bi = {
|
|
|
3378
3398
|
level: "A",
|
|
3379
3399
|
tags: ["best-practice"],
|
|
3380
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.",
|
|
3381
3402
|
description: "Heading levels should increase by one; skipping levels (e.g. h2 to h4) makes navigation harder.",
|
|
3382
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.",
|
|
3383
3404
|
run(e) {
|
|
@@ -3406,13 +3427,14 @@ const bi = {
|
|
|
3406
3427
|
level: "A",
|
|
3407
3428
|
tags: ["best-practice"],
|
|
3408
3429
|
fixability: "contextual",
|
|
3430
|
+
browserHint: "Screenshot the heading area to verify it's visually empty, then add meaningful text or remove the heading element.",
|
|
3409
3431
|
description: "Headings must have discernible text.",
|
|
3410
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.",
|
|
3411
3433
|
run(e) {
|
|
3412
3434
|
var i;
|
|
3413
3435
|
const a = [], t = e.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');
|
|
3414
3436
|
for (const n of t)
|
|
3415
|
-
if (!h(n) && !
|
|
3437
|
+
if (!h(n) && !w(n)) {
|
|
3416
3438
|
let r;
|
|
3417
3439
|
const o = n.nextElementSibling;
|
|
3418
3440
|
if (o) {
|
|
@@ -3437,7 +3459,8 @@ const bi = {
|
|
|
3437
3459
|
wcag: ["1.3.1"],
|
|
3438
3460
|
level: "A",
|
|
3439
3461
|
tags: ["best-practice"],
|
|
3440
|
-
fixability: "
|
|
3462
|
+
fixability: "contextual",
|
|
3463
|
+
browserHint: "Screenshot the page to verify the paragraph visually functions as a heading and choose the correct heading level.",
|
|
3441
3464
|
description: "Paragraphs should not be styled to look like headings.",
|
|
3442
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.",
|
|
3443
3466
|
run(e) {
|
|
@@ -3453,7 +3476,8 @@ const bi = {
|
|
|
3453
3476
|
selector: p(n),
|
|
3454
3477
|
html: m(n),
|
|
3455
3478
|
impact: "serious",
|
|
3456
|
-
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." }
|
|
3457
3481
|
});
|
|
3458
3482
|
}
|
|
3459
3483
|
}
|
|
@@ -3477,20 +3501,21 @@ function qi(e) {
|
|
|
3477
3501
|
return a.length > 0 ? a.join(`
|
|
3478
3502
|
`) : void 0;
|
|
3479
3503
|
}
|
|
3480
|
-
const
|
|
3504
|
+
const Ei = {
|
|
3481
3505
|
id: "navigable/link-name",
|
|
3482
3506
|
category: "navigable",
|
|
3483
3507
|
actRuleIds: ["c487ae"],
|
|
3484
3508
|
wcag: ["2.4.4", "4.1.2"],
|
|
3485
3509
|
level: "A",
|
|
3486
3510
|
fixability: "contextual",
|
|
3511
|
+
browserHint: "Screenshot the link in context to understand its destination, then write descriptive link text.",
|
|
3487
3512
|
description: "Links must have discernible text via content, aria-label, or aria-labelledby.",
|
|
3488
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.",
|
|
3489
3514
|
run(e) {
|
|
3490
3515
|
const a = [];
|
|
3491
3516
|
for (const t of e.querySelectorAll('a[href], area[href], [role="link"]')) {
|
|
3492
3517
|
if (h(t) || q(t) || t.getRootNode() instanceof ShadowRoot) continue;
|
|
3493
|
-
|
|
3518
|
+
w(t) || a.push({
|
|
3494
3519
|
ruleId: "navigable/link-name",
|
|
3495
3520
|
selector: p(t),
|
|
3496
3521
|
html: m(t),
|
|
@@ -3502,7 +3527,7 @@ const Li = {
|
|
|
3502
3527
|
}
|
|
3503
3528
|
return a;
|
|
3504
3529
|
}
|
|
3505
|
-
},
|
|
3530
|
+
}, Li = {
|
|
3506
3531
|
id: "navigable/skip-link",
|
|
3507
3532
|
category: "navigable",
|
|
3508
3533
|
wcag: ["2.4.1"],
|
|
@@ -3575,19 +3600,19 @@ const Li = {
|
|
|
3575
3600
|
description: "Page should not have more than one main landmark.",
|
|
3576
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.",
|
|
3577
3602
|
filterTopLevel: !1
|
|
3578
|
-
}), Mi =
|
|
3603
|
+
}), Mi = Je({
|
|
3579
3604
|
id: "landmarks/banner-is-top-level",
|
|
3580
3605
|
selector: '[role="banner"]',
|
|
3581
3606
|
landmarkName: "Banner",
|
|
3582
3607
|
description: "Banner landmark should not be nested within another landmark.",
|
|
3583
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."
|
|
3584
|
-
}), $i =
|
|
3609
|
+
}), $i = Je({
|
|
3585
3610
|
id: "landmarks/contentinfo-is-top-level",
|
|
3586
3611
|
selector: '[role="contentinfo"]',
|
|
3587
3612
|
landmarkName: "Contentinfo",
|
|
3588
3613
|
description: "Contentinfo landmark should not be nested within another landmark.",
|
|
3589
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."
|
|
3590
|
-
}),
|
|
3615
|
+
}), Hi = {
|
|
3591
3616
|
id: "landmarks/main-is-top-level",
|
|
3592
3617
|
category: "landmarks",
|
|
3593
3618
|
wcag: [],
|
|
@@ -3610,7 +3635,7 @@ const Li = {
|
|
|
3610
3635
|
}
|
|
3611
3636
|
return a;
|
|
3612
3637
|
}
|
|
3613
|
-
},
|
|
3638
|
+
}, Di = {
|
|
3614
3639
|
id: "landmarks/complementary-is-top-level",
|
|
3615
3640
|
category: "landmarks",
|
|
3616
3641
|
wcag: [],
|
|
@@ -3633,7 +3658,7 @@ const Li = {
|
|
|
3633
3658
|
}
|
|
3634
3659
|
return a;
|
|
3635
3660
|
}
|
|
3636
|
-
},
|
|
3661
|
+
}, Pi = {
|
|
3637
3662
|
id: "landmarks/landmark-unique",
|
|
3638
3663
|
category: "landmarks",
|
|
3639
3664
|
wcag: [],
|
|
@@ -3654,7 +3679,7 @@ const Li = {
|
|
|
3654
3679
|
if (r.length <= 1) continue;
|
|
3655
3680
|
const o = /* @__PURE__ */ new Map();
|
|
3656
3681
|
for (const s of r) {
|
|
3657
|
-
const l =
|
|
3682
|
+
const l = w(s).toLowerCase() || "", d = o.get(l) || [];
|
|
3658
3683
|
d.push(s), o.set(l, d);
|
|
3659
3684
|
}
|
|
3660
3685
|
for (const [s, l] of o)
|
|
@@ -3670,7 +3695,7 @@ const Li = {
|
|
|
3670
3695
|
}
|
|
3671
3696
|
return a;
|
|
3672
3697
|
}
|
|
3673
|
-
},
|
|
3698
|
+
}, Fi = {
|
|
3674
3699
|
id: "landmarks/region",
|
|
3675
3700
|
category: "landmarks",
|
|
3676
3701
|
wcag: [],
|
|
@@ -3739,7 +3764,7 @@ const Li = {
|
|
|
3739
3764
|
), Ui = new Set(
|
|
3740
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(" ")
|
|
3741
3766
|
), Wi = /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;
|
|
3742
|
-
function
|
|
3767
|
+
function Ke(e) {
|
|
3743
3768
|
if (!Wi.test(e)) return !1;
|
|
3744
3769
|
const a = e.split("-")[0].toLowerCase();
|
|
3745
3770
|
return a.length === 2 ? ji.has(a) : a.length === 3 ? !Ui.has(a) : !1;
|
|
@@ -3789,7 +3814,7 @@ const Oi = {
|
|
|
3789
3814
|
run(e) {
|
|
3790
3815
|
var t;
|
|
3791
3816
|
const a = (t = e.documentElement.getAttribute("lang")) == null ? void 0 : t.trim();
|
|
3792
|
-
return a && !
|
|
3817
|
+
return a && !Ke(a) ? [{
|
|
3793
3818
|
ruleId: "readable/html-lang-valid",
|
|
3794
3819
|
selector: "html",
|
|
3795
3820
|
html: m(e.documentElement),
|
|
@@ -3822,7 +3847,7 @@ const Oi = {
|
|
|
3822
3847
|
});
|
|
3823
3848
|
continue;
|
|
3824
3849
|
}
|
|
3825
|
-
n && Ce(t) && (
|
|
3850
|
+
n && Ce(t) && (Ke(n) || a.push({
|
|
3826
3851
|
ruleId: "readable/valid-lang",
|
|
3827
3852
|
selector: p(t),
|
|
3828
3853
|
html: m(t),
|
|
@@ -3865,13 +3890,14 @@ const Oi = {
|
|
|
3865
3890
|
wcag: ["4.1.2"],
|
|
3866
3891
|
level: "A",
|
|
3867
3892
|
fixability: "contextual",
|
|
3893
|
+
browserHint: "Screenshot the iframe to see what content it displays, then add a title describing its purpose.",
|
|
3868
3894
|
description: "Frames must have an accessible name.",
|
|
3869
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'.",
|
|
3870
3896
|
run(e) {
|
|
3871
3897
|
const a = [];
|
|
3872
3898
|
for (const t of e.querySelectorAll("iframe, frame")) {
|
|
3873
3899
|
if (h(t) || Ge(t)) continue;
|
|
3874
|
-
if (!
|
|
3900
|
+
if (!w(t)) {
|
|
3875
3901
|
const n = t.getAttribute("src");
|
|
3876
3902
|
a.push({
|
|
3877
3903
|
ruleId: "labels-and-names/frame-title",
|
|
@@ -3941,7 +3967,7 @@ function Xi(e) {
|
|
|
3941
3967
|
}
|
|
3942
3968
|
return "";
|
|
3943
3969
|
}
|
|
3944
|
-
const
|
|
3970
|
+
const Ji = [
|
|
3945
3971
|
'[role="checkbox"]',
|
|
3946
3972
|
'[role="combobox"]',
|
|
3947
3973
|
'[role="listbox"]',
|
|
@@ -3953,7 +3979,7 @@ const Ki = [
|
|
|
3953
3979
|
'[role="spinbutton"]',
|
|
3954
3980
|
'[role="switch"]',
|
|
3955
3981
|
'[role="textbox"]'
|
|
3956
|
-
].join(", "),
|
|
3982
|
+
].join(", "), Ki = /* @__PURE__ */ new Set([
|
|
3957
3983
|
"checkbox",
|
|
3958
3984
|
"menuitemcheckbox",
|
|
3959
3985
|
"menuitemradio",
|
|
@@ -3970,8 +3996,8 @@ const Ki = [
|
|
|
3970
3996
|
function Zi(e) {
|
|
3971
3997
|
var o, s, l, d;
|
|
3972
3998
|
const a = (o = e.getAttribute("role")) == null ? void 0 : o.trim().toLowerCase();
|
|
3973
|
-
if (a &&
|
|
3974
|
-
return
|
|
3999
|
+
if (a && Ki.has(a) || (e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement) && !(a && Qi.has(a)))
|
|
4000
|
+
return w(e);
|
|
3975
4001
|
const i = e.getAttribute("aria-labelledby");
|
|
3976
4002
|
if (i) {
|
|
3977
4003
|
const c = i.split(/\s+/).map((u) => {
|
|
@@ -4001,11 +4027,12 @@ const en = {
|
|
|
4001
4027
|
wcag: ["4.1.2"],
|
|
4002
4028
|
level: "A",
|
|
4003
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.",
|
|
4004
4031
|
description: "Form elements must have labels. Use <label>, aria-label, or aria-labelledby.",
|
|
4005
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').",
|
|
4006
4033
|
run(e) {
|
|
4007
4034
|
var i;
|
|
4008
|
-
const a = [], t = e.querySelectorAll(`${Qe}, ${
|
|
4035
|
+
const a = [], t = e.querySelectorAll(`${Qe}, ${Ji}`);
|
|
4009
4036
|
for (const n of t) {
|
|
4010
4037
|
if (h(n) || q(n)) continue;
|
|
4011
4038
|
const r = (i = n.getAttribute("role")) == null ? void 0 : i.trim().toLowerCase();
|
|
@@ -4071,6 +4098,7 @@ const en = {
|
|
|
4071
4098
|
wcag: ["4.1.2"],
|
|
4072
4099
|
level: "A",
|
|
4073
4100
|
fixability: "contextual",
|
|
4101
|
+
browserHint: "Screenshot the button to see its visual purpose, then set value or aria-label to describe the action.",
|
|
4074
4102
|
description: "Input buttons must have discernible text via value, aria-label, or aria-labelledby.",
|
|
4075
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.",
|
|
4076
4104
|
run(e) {
|
|
@@ -4081,7 +4109,7 @@ const en = {
|
|
|
4081
4109
|
)) {
|
|
4082
4110
|
if (h(n) || q(n)) continue;
|
|
4083
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");
|
|
4084
|
-
!r && !s && !
|
|
4112
|
+
!r && !s && !w(n) && a.push({
|
|
4085
4113
|
ruleId: "labels-and-names/input-button-name",
|
|
4086
4114
|
selector: p(n),
|
|
4087
4115
|
html: m(n),
|
|
@@ -4110,16 +4138,17 @@ const nn = {
|
|
|
4110
4138
|
level: "A",
|
|
4111
4139
|
tags: ["best-practice"],
|
|
4112
4140
|
fixability: "contextual",
|
|
4141
|
+
browserHint: "Screenshot the control to see its visible label, then ensure aria-label starts with that visible text.",
|
|
4113
4142
|
description: "Interactive elements with visible text must have accessible names that contain that text.",
|
|
4114
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.",
|
|
4115
4144
|
run(e) {
|
|
4116
4145
|
const a = [];
|
|
4117
4146
|
for (const t of e.querySelectorAll('button, [role="button"], a[href], input[type="submit"], input[type="button"]')) {
|
|
4118
4147
|
if (h(t)) continue;
|
|
4119
|
-
const i =
|
|
4148
|
+
const i = w(t);
|
|
4120
4149
|
if (!i) continue;
|
|
4121
4150
|
let n = "";
|
|
4122
|
-
t instanceof HTMLInputElement ? n = t.value || "" : n =
|
|
4151
|
+
t instanceof HTMLInputElement ? n = t.value || "" : n = ee(t);
|
|
4123
4152
|
const r = n.trim();
|
|
4124
4153
|
if (!r || r.length <= 2) continue;
|
|
4125
4154
|
const o = t.hasAttribute("aria-label"), s = t.hasAttribute("aria-labelledby");
|
|
@@ -4134,13 +4163,13 @@ const nn = {
|
|
|
4134
4163
|
}
|
|
4135
4164
|
for (const t of e.querySelectorAll("input, select, textarea")) {
|
|
4136
4165
|
if (h(t) || t instanceof HTMLInputElement && ["hidden", "submit", "button", "image"].includes(t.type)) continue;
|
|
4137
|
-
const i =
|
|
4166
|
+
const i = w(t);
|
|
4138
4167
|
if (!i || !t.hasAttribute("aria-label")) continue;
|
|
4139
4168
|
const r = t.id;
|
|
4140
4169
|
let o = "";
|
|
4141
4170
|
if (r) {
|
|
4142
4171
|
const s = e.querySelector(`label[for="${CSS.escape(r)}"]`);
|
|
4143
|
-
s && (o =
|
|
4172
|
+
s && (o = ee(s));
|
|
4144
4173
|
}
|
|
4145
4174
|
o.trim() && (Ne(i, o) || a.push({
|
|
4146
4175
|
ruleId: "labels-and-names/label-content-mismatch",
|
|
@@ -4195,7 +4224,7 @@ const nn = {
|
|
|
4195
4224
|
if (h(i) || q(i) || se(i)) continue;
|
|
4196
4225
|
const n = i.getAttribute("role");
|
|
4197
4226
|
if ((i.tagName.toLowerCase() === "button" || i.tagName.toLowerCase() === "a") && n !== "menuitem") continue;
|
|
4198
|
-
if (!
|
|
4227
|
+
if (!w(i)) {
|
|
4199
4228
|
const o = i.querySelector("img[alt]");
|
|
4200
4229
|
if ((t = o == null ? void 0 : o.getAttribute("alt")) != null && t.trim()) continue;
|
|
4201
4230
|
a.push({
|
|
@@ -4231,7 +4260,7 @@ function M(e) {
|
|
|
4231
4260
|
if (!o || !e.roleSet.has(o)) continue;
|
|
4232
4261
|
}
|
|
4233
4262
|
if (e.skipNative && n.matches(e.skipNative)) continue;
|
|
4234
|
-
|
|
4263
|
+
w(n) || t.push({
|
|
4235
4264
|
ruleId: e.id,
|
|
4236
4265
|
selector: p(n),
|
|
4237
4266
|
html: m(n),
|
|
@@ -4331,6 +4360,7 @@ const hn = {
|
|
|
4331
4360
|
wcag: ["4.1.2"],
|
|
4332
4361
|
level: "A",
|
|
4333
4362
|
fixability: "contextual",
|
|
4363
|
+
browserHint: "Screenshot the button to identify its icon or visual label, then add a matching aria-label.",
|
|
4334
4364
|
description: "Buttons must have discernible text.",
|
|
4335
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.",
|
|
4336
4366
|
run(e) {
|
|
@@ -4339,7 +4369,7 @@ const hn = {
|
|
|
4339
4369
|
if (h(t) || q(t)) continue;
|
|
4340
4370
|
const i = t.getAttribute("role");
|
|
4341
4371
|
if ((i === "none" || i === "presentation") && !(t.matches('button:not([disabled]), [tabindex]:not([tabindex="-1"])') || t.tagName.toLowerCase() === "button" && !t.disabled) || se(t)) continue;
|
|
4342
|
-
|
|
4372
|
+
w(t) || a.push({
|
|
4343
4373
|
ruleId: "labels-and-names/button-name",
|
|
4344
4374
|
selector: p(t),
|
|
4345
4375
|
html: m(t),
|
|
@@ -4364,7 +4394,7 @@ const hn = {
|
|
|
4364
4394
|
const a = [];
|
|
4365
4395
|
for (const t of e.querySelectorAll("details > summary:first-of-type")) {
|
|
4366
4396
|
if (h(t)) continue;
|
|
4367
|
-
|
|
4397
|
+
w(t) || a.push({
|
|
4368
4398
|
ruleId: "labels-and-names/summary-name",
|
|
4369
4399
|
selector: p(t),
|
|
4370
4400
|
html: m(t),
|
|
@@ -4615,7 +4645,7 @@ Referenced by: ${d}` : ""}`,
|
|
|
4615
4645
|
const a = [];
|
|
4616
4646
|
for (const t of e.querySelectorAll("[role], [aria-*]")) {
|
|
4617
4647
|
if (h(t)) continue;
|
|
4618
|
-
const i =
|
|
4648
|
+
const i = U(t);
|
|
4619
4649
|
if (!i) continue;
|
|
4620
4650
|
const n = Sn[i];
|
|
4621
4651
|
if (n)
|
|
@@ -4757,7 +4787,7 @@ Referenced by: ${d}` : ""}`,
|
|
|
4757
4787
|
video: /* @__PURE__ */ new Set(["application"]),
|
|
4758
4788
|
wbr: /* @__PURE__ */ new Set(["none", "presentation"])
|
|
4759
4789
|
};
|
|
4760
|
-
function
|
|
4790
|
+
function En(e) {
|
|
4761
4791
|
var t;
|
|
4762
4792
|
const a = e.tagName.toLowerCase();
|
|
4763
4793
|
if (qn.has(a))
|
|
@@ -4772,7 +4802,7 @@ function Ln(e) {
|
|
|
4772
4802
|
}
|
|
4773
4803
|
return D[a] || "any";
|
|
4774
4804
|
}
|
|
4775
|
-
const
|
|
4805
|
+
const Ln = {
|
|
4776
4806
|
id: "aria/aria-allowed-role",
|
|
4777
4807
|
category: "aria",
|
|
4778
4808
|
wcag: ["4.1.2"],
|
|
@@ -4789,7 +4819,7 @@ const En = {
|
|
|
4789
4819
|
if (!n) continue;
|
|
4790
4820
|
const r = $e(i);
|
|
4791
4821
|
if (r && n === r) continue;
|
|
4792
|
-
const o =
|
|
4822
|
+
const o = En(i);
|
|
4793
4823
|
o === "none" ? a.push({
|
|
4794
4824
|
ruleId: "aria/aria-allowed-role",
|
|
4795
4825
|
selector: p(i),
|
|
@@ -4864,8 +4894,8 @@ const Mn = {
|
|
|
4864
4894
|
const a = [];
|
|
4865
4895
|
for (const t of e.querySelectorAll('[aria-hidden="true"]')) {
|
|
4866
4896
|
if (t === e.body) continue;
|
|
4867
|
-
const i = [...t.querySelectorAll(
|
|
4868
|
-
t.matches(
|
|
4897
|
+
const i = [...t.querySelectorAll(j)];
|
|
4898
|
+
t.matches(j) && i.push(t);
|
|
4869
4899
|
for (const n of i)
|
|
4870
4900
|
if (n instanceof HTMLElement) {
|
|
4871
4901
|
const r = n.getAttribute("tabindex");
|
|
@@ -4901,14 +4931,14 @@ const Mn = {
|
|
|
4901
4931
|
run(e) {
|
|
4902
4932
|
return le(e).prohibitedAttr;
|
|
4903
4933
|
}
|
|
4904
|
-
},
|
|
4934
|
+
}, Hn = [
|
|
4905
4935
|
"a[href]",
|
|
4906
4936
|
"button:not([disabled])",
|
|
4907
4937
|
'input:not([disabled]):not([type="hidden"])',
|
|
4908
4938
|
"select:not([disabled])",
|
|
4909
4939
|
"textarea:not([disabled])",
|
|
4910
4940
|
'[tabindex]:not([tabindex="-1"])'
|
|
4911
|
-
].join(", "),
|
|
4941
|
+
].join(", "), Dn = [
|
|
4912
4942
|
"aria-atomic",
|
|
4913
4943
|
"aria-busy",
|
|
4914
4944
|
"aria-controls",
|
|
@@ -4925,15 +4955,15 @@ const Mn = {
|
|
|
4925
4955
|
];
|
|
4926
4956
|
function Me(e) {
|
|
4927
4957
|
const a = [];
|
|
4928
|
-
e.matches(
|
|
4929
|
-
for (const t of
|
|
4958
|
+
e.matches(Hn) && a.push("element is focusable");
|
|
4959
|
+
for (const t of Dn)
|
|
4930
4960
|
if (e.hasAttribute(t)) {
|
|
4931
4961
|
a.push(`has ${t}`);
|
|
4932
4962
|
break;
|
|
4933
4963
|
}
|
|
4934
4964
|
return (e.hasAttribute("aria-label") || e.hasAttribute("aria-labelledby")) && a.push("has accessible name"), a;
|
|
4935
4965
|
}
|
|
4936
|
-
const
|
|
4966
|
+
const Pn = {
|
|
4937
4967
|
id: "aria/presentation-role-conflict",
|
|
4938
4968
|
category: "aria",
|
|
4939
4969
|
actRuleIds: ["46ca7f"],
|
|
@@ -4970,7 +5000,7 @@ const Fn = {
|
|
|
4970
5000
|
}
|
|
4971
5001
|
return a;
|
|
4972
5002
|
}
|
|
4973
|
-
},
|
|
5003
|
+
}, Fn = /* @__PURE__ */ new Set([
|
|
4974
5004
|
"button",
|
|
4975
5005
|
"checkbox",
|
|
4976
5006
|
"img",
|
|
@@ -5001,9 +5031,9 @@ const Fn = {
|
|
|
5001
5031
|
const a = [];
|
|
5002
5032
|
for (const t of e.querySelectorAll("*")) {
|
|
5003
5033
|
if (h(t)) continue;
|
|
5004
|
-
const i =
|
|
5005
|
-
if (!(!i || !
|
|
5006
|
-
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))
|
|
5007
5037
|
if (n !== t && !n.disabled) {
|
|
5008
5038
|
a.push({
|
|
5009
5039
|
ruleId: "aria/presentational-children-focusable",
|
|
@@ -5029,8 +5059,8 @@ const Fn = {
|
|
|
5029
5059
|
Yt,
|
|
5030
5060
|
Xt,
|
|
5031
5061
|
// Time-based Media
|
|
5032
|
-
Kt,
|
|
5033
5062
|
Jt,
|
|
5063
|
+
Kt,
|
|
5034
5064
|
// Adaptable
|
|
5035
5065
|
na,
|
|
5036
5066
|
la,
|
|
@@ -5050,9 +5080,9 @@ const Fn = {
|
|
|
5050
5080
|
Ra,
|
|
5051
5081
|
Ca,
|
|
5052
5082
|
Ta,
|
|
5053
|
-
|
|
5054
|
-
Ka,
|
|
5083
|
+
Da,
|
|
5055
5084
|
Ja,
|
|
5085
|
+
Ka,
|
|
5056
5086
|
// Keyboard Accessible
|
|
5057
5087
|
Za,
|
|
5058
5088
|
ti,
|
|
@@ -5073,8 +5103,8 @@ const Fn = {
|
|
|
5073
5103
|
ki,
|
|
5074
5104
|
Si,
|
|
5075
5105
|
Ii,
|
|
5076
|
-
Li,
|
|
5077
5106
|
Ei,
|
|
5107
|
+
Li,
|
|
5078
5108
|
// Landmarks
|
|
5079
5109
|
Ri,
|
|
5080
5110
|
Ci,
|
|
@@ -5082,10 +5112,10 @@ const Fn = {
|
|
|
5082
5112
|
Ni,
|
|
5083
5113
|
Mi,
|
|
5084
5114
|
$i,
|
|
5115
|
+
Hi,
|
|
5085
5116
|
Di,
|
|
5086
5117
|
Pi,
|
|
5087
5118
|
Fi,
|
|
5088
|
-
Hi,
|
|
5089
5119
|
// Readable
|
|
5090
5120
|
zi,
|
|
5091
5121
|
Oi,
|
|
@@ -5116,43 +5146,48 @@ const Fn = {
|
|
|
5116
5146
|
wn,
|
|
5117
5147
|
kn,
|
|
5118
5148
|
In,
|
|
5119
|
-
|
|
5149
|
+
Ln,
|
|
5120
5150
|
Cn,
|
|
5121
5151
|
Mn,
|
|
5122
5152
|
$n,
|
|
5123
|
-
|
|
5153
|
+
Pn,
|
|
5124
5154
|
zn
|
|
5125
5155
|
];
|
|
5126
|
-
let me = [], et = /* @__PURE__ */ new Set(), tt = !1, at = !1, T,
|
|
5156
|
+
let me = [], et = /* @__PURE__ */ new Set(), tt = !1, at = !1, T, z;
|
|
5127
5157
|
function Vn(e) {
|
|
5128
|
-
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;
|
|
5129
5159
|
}
|
|
5130
5160
|
function pe() {
|
|
5131
|
-
if (
|
|
5161
|
+
if (z) return z;
|
|
5132
5162
|
const a = Ze.filter((t) => {
|
|
5133
5163
|
var i;
|
|
5134
5164
|
return !(et.has(t.id) || t.level === "AAA" && !tt || at && ((i = t.tags) != null && i.includes("page-level")));
|
|
5135
5165
|
}).concat(me);
|
|
5136
|
-
return T ? (
|
|
5166
|
+
return T ? (z = Ht(a, T), z) : a;
|
|
5137
5167
|
}
|
|
5138
5168
|
function Bn(e) {
|
|
5139
5169
|
it();
|
|
5140
|
-
const a = pe(), t = T, i = [];
|
|
5141
|
-
let
|
|
5170
|
+
const a = pe(), t = T, i = [], n = [];
|
|
5171
|
+
let r = 0;
|
|
5142
5172
|
return {
|
|
5143
|
-
processChunk(
|
|
5144
|
-
const
|
|
5145
|
-
for (;
|
|
5173
|
+
processChunk(o) {
|
|
5174
|
+
const s = performance.now();
|
|
5175
|
+
for (; r < a.length; ) {
|
|
5176
|
+
const l = a[r];
|
|
5146
5177
|
try {
|
|
5147
|
-
i.push(...
|
|
5148
|
-
} catch {
|
|
5178
|
+
i.push(...l.run(e));
|
|
5179
|
+
} catch (d) {
|
|
5180
|
+
n.push({ ruleId: l.id, error: d instanceof Error ? d.message : String(d) });
|
|
5149
5181
|
}
|
|
5150
|
-
if (
|
|
5182
|
+
if (r++, performance.now() - s >= o) break;
|
|
5151
5183
|
}
|
|
5152
|
-
return
|
|
5184
|
+
return r < a.length;
|
|
5153
5185
|
},
|
|
5154
5186
|
getViolations() {
|
|
5155
5187
|
return t ? ze(i, t) : i;
|
|
5188
|
+
},
|
|
5189
|
+
getSkippedRules() {
|
|
5190
|
+
return n;
|
|
5156
5191
|
}
|
|
5157
5192
|
};
|
|
5158
5193
|
}
|
|
@@ -5160,19 +5195,21 @@ function it() {
|
|
|
5160
5195
|
mt(), ot(), st(), kt(), At(), pt();
|
|
5161
5196
|
}
|
|
5162
5197
|
function _n(e) {
|
|
5163
|
-
var
|
|
5198
|
+
var n;
|
|
5164
5199
|
it();
|
|
5165
|
-
const a = pe(), t = [];
|
|
5166
|
-
for (const
|
|
5200
|
+
const a = pe(), t = [], i = [];
|
|
5201
|
+
for (const r of a)
|
|
5167
5202
|
try {
|
|
5168
|
-
t.push(...
|
|
5169
|
-
} catch {
|
|
5203
|
+
t.push(...r.run(e));
|
|
5204
|
+
} catch (o) {
|
|
5205
|
+
i.push({ ruleId: r.id, error: o instanceof Error ? o.message : String(o) });
|
|
5170
5206
|
}
|
|
5171
5207
|
return {
|
|
5172
|
-
url: ((
|
|
5208
|
+
url: ((n = e.location) == null ? void 0 : n.href) ?? "",
|
|
5173
5209
|
timestamp: Date.now(),
|
|
5174
5210
|
violations: T ? ze(t, T) : t,
|
|
5175
|
-
ruleCount: a.length
|
|
5211
|
+
ruleCount: a.length,
|
|
5212
|
+
skippedRules: i
|
|
5176
5213
|
};
|
|
5177
5214
|
}
|
|
5178
5215
|
function Gn(e, a) {
|
|
@@ -5290,7 +5327,7 @@ const Xn = {
|
|
|
5290
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." } },
|
|
5291
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.' } },
|
|
5292
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." } }
|
|
5293
|
-
},
|
|
5330
|
+
}, Jn = {
|
|
5294
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>." } },
|
|
5295
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." } },
|
|
5296
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." } },
|
|
@@ -5394,10 +5431,10 @@ export {
|
|
|
5394
5431
|
Vn as configureRules,
|
|
5395
5432
|
Bn as createChunkedAudit,
|
|
5396
5433
|
Gn as diffAudit,
|
|
5397
|
-
|
|
5434
|
+
w as getAccessibleName,
|
|
5398
5435
|
k as getAccessibleTextContent,
|
|
5399
5436
|
pe as getActiveRules,
|
|
5400
|
-
|
|
5437
|
+
U as getComputedRole,
|
|
5401
5438
|
m as getHtmlSnippet,
|
|
5402
5439
|
$e as getImplicitRole,
|
|
5403
5440
|
Yn as getRuleById,
|
|
@@ -5405,7 +5442,7 @@ export {
|
|
|
5405
5442
|
h as isAriaHidden,
|
|
5406
5443
|
dt as isValidRole,
|
|
5407
5444
|
Xn as localeEn,
|
|
5408
|
-
|
|
5445
|
+
Jn as localeEs,
|
|
5409
5446
|
Un as querySelectorShadowAware,
|
|
5410
5447
|
Wn as registerLocale,
|
|
5411
5448
|
Ze as rules,
|