@accesslint/core 0.3.5 → 0.3.7

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.js CHANGED
@@ -117,69 +117,69 @@ function ue(t) {
117
117
  return null;
118
118
  }
119
119
  }
120
- function C(t) {
120
+ function L(t) {
121
121
  var n;
122
122
  const a = O.get(t);
123
123
  if (a !== void 0) return a;
124
124
  const i = ((n = t.getAttribute("role")) == null ? void 0 : n.trim().toLowerCase()) || null || ue(t);
125
125
  return O.set(t, i), i;
126
126
  }
127
- let W = /* @__PURE__ */ new WeakMap();
128
- function we() {
129
- W = /* @__PURE__ */ new WeakMap();
127
+ let F = /* @__PURE__ */ new WeakMap();
128
+ function ye() {
129
+ F = /* @__PURE__ */ new WeakMap();
130
130
  }
131
131
  function v(t) {
132
- const a = W.get(t);
132
+ const a = F.get(t);
133
133
  if (a !== void 0) return a;
134
- const e = ye(t);
135
- return W.set(t, e), e;
134
+ const e = we(t);
135
+ return F.set(t, e), e;
136
136
  }
137
- function ye(t) {
138
- var r, o, s, d, h;
137
+ function we(t) {
138
+ var r, o, s, l, h;
139
139
  const a = t.getAttribute("aria-labelledby");
140
140
  if (a) {
141
- const l = a.split(/\s+/).map((m) => {
142
- const g = t.ownerDocument.getElementById(m);
143
- return g ? y(g).trim() : "";
141
+ const c = a.split(/\s+/).map((d) => {
142
+ const g = t.ownerDocument.getElementById(d);
143
+ return g ? w(g).trim() : "";
144
144
  }).filter(Boolean);
145
- if (l.length) return l.join(" ");
145
+ if (c.length) return c.join(" ");
146
146
  }
147
147
  const e = (r = t.getAttribute("aria-label")) == null ? void 0 : r.trim();
148
148
  if (e) return e;
149
149
  if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement) {
150
150
  if (t.id) {
151
- const g = t.ownerDocument.querySelector(`label[for="${CSS.escape(t.id)}"]`), b = g ? y(g).trim() : "";
151
+ const g = t.ownerDocument.querySelector(`label[for="${CSS.escape(t.id)}"]`), b = g ? w(g).trim() : "";
152
152
  if (b) return b;
153
153
  }
154
- const l = t.closest("label"), m = l ? y(l).trim() : "";
155
- if (m) return m;
154
+ const c = t.closest("label"), d = c ? w(c).trim() : "";
155
+ if (d) return d;
156
156
  }
157
157
  const i = (o = t.getAttribute("title")) == null ? void 0 : o.trim();
158
158
  if (i) return i;
159
159
  if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) {
160
- const l = (s = t.getAttribute("placeholder")) == null ? void 0 : s.trim();
161
- if (l) return l;
160
+ const c = (s = t.getAttribute("placeholder")) == null ? void 0 : s.trim();
161
+ if (c) return c;
162
162
  }
163
163
  const n = t.tagName.toLowerCase();
164
164
  if (n === "fieldset") {
165
- const l = t.querySelector(":scope > legend");
166
- if (l) {
167
- const m = y(l).trim();
168
- if (m) return m;
165
+ const c = t.querySelector(":scope > legend");
166
+ if (c) {
167
+ const d = w(c).trim();
168
+ if (d) return d;
169
169
  }
170
170
  }
171
171
  if (n === "table") {
172
- const l = t.querySelector(":scope > caption");
173
- if (l) {
174
- const m = y(l).trim();
175
- if (m) return m;
172
+ const c = t.querySelector(":scope > caption");
173
+ if (c) {
174
+ const d = w(c).trim();
175
+ if (d) return d;
176
176
  }
177
177
  }
178
178
  if (!(t instanceof HTMLInputElement)) {
179
- const l = y(t).trim();
180
- if (l) return l;
179
+ const c = w(t).trim();
180
+ if (c) return c;
181
181
  }
182
- return t instanceof HTMLImageElement || t instanceof HTMLAreaElement ? ((d = t.alt) == null ? void 0 : d.trim()) ?? "" : t instanceof HTMLInputElement && t.type === "image" ? ((h = t.alt) == null ? void 0 : h.trim()) ?? "" : "";
182
+ return t instanceof HTMLImageElement || t instanceof HTMLAreaElement ? ((l = t.alt) == null ? void 0 : l.trim()) ?? "" : t instanceof HTMLInputElement && t.type === "image" ? ((h = t.alt) == null ? void 0 : h.trim()) ?? "" : "";
183
183
  }
184
184
  const Ae = /* @__PURE__ */ new Set([
185
185
  "alert",
@@ -265,12 +265,12 @@ const Ae = /* @__PURE__ */ new Set([
265
265
  "treegrid",
266
266
  "treeitem"
267
267
  ]);
268
- function Se(t) {
268
+ function xe(t) {
269
269
  const a = t.trim().toLowerCase().replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g, "");
270
270
  return Ae.has(a);
271
271
  }
272
272
  let B = /* @__PURE__ */ new WeakMap();
273
- function xe() {
273
+ function Se() {
274
274
  B = /* @__PURE__ */ new WeakMap();
275
275
  }
276
276
  function p(t) {
@@ -282,50 +282,50 @@ function p(t) {
282
282
  function ke(t) {
283
283
  return !!(t.getAttribute("aria-hidden") === "true" || t instanceof HTMLElement && (t.hidden || t.style.display === "none"));
284
284
  }
285
- function y(t) {
285
+ function w(t) {
286
286
  var e, i, n, r, o;
287
287
  let a = "";
288
288
  for (const s of t.childNodes)
289
289
  if (s.nodeType === 3)
290
290
  a += s.textContent ?? "";
291
291
  else if (s.nodeType === 1) {
292
- const d = s;
293
- if (!ke(d)) {
294
- const h = (e = d.tagName) == null ? void 0 : e.toLowerCase();
292
+ const l = s;
293
+ if (!ke(l)) {
294
+ const h = (e = l.tagName) == null ? void 0 : e.toLowerCase();
295
295
  if (h === "img" || h === "area") {
296
- const l = d.getAttribute("aria-labelledby");
297
- if (l) {
298
- const m = l.split(/\s+/).map((g) => {
296
+ const c = l.getAttribute("aria-labelledby");
297
+ if (c) {
298
+ const d = c.split(/\s+/).map((g) => {
299
299
  var b, f;
300
- return ((f = (b = d.ownerDocument.getElementById(g)) == null ? void 0 : b.textContent) == null ? void 0 : f.trim()) ?? "";
300
+ return ((f = (b = l.ownerDocument.getElementById(g)) == null ? void 0 : b.textContent) == null ? void 0 : f.trim()) ?? "";
301
301
  }).filter(Boolean);
302
- if (m.length) {
303
- a += m.join(" ");
302
+ if (d.length) {
303
+ a += d.join(" ");
304
304
  continue;
305
305
  }
306
306
  }
307
- a += ((i = d.getAttribute("aria-label")) == null ? void 0 : i.trim()) ?? d.getAttribute("alt") ?? ((n = d.getAttribute("title")) == null ? void 0 : n.trim()) ?? "";
307
+ a += ((i = l.getAttribute("aria-label")) == null ? void 0 : i.trim()) ?? l.getAttribute("alt") ?? ((n = l.getAttribute("title")) == null ? void 0 : n.trim()) ?? "";
308
308
  } else if (h === "svg") {
309
- const l = (r = d.getAttribute("aria-label")) == null ? void 0 : r.trim();
310
- if (l)
311
- a += l;
309
+ const c = (r = l.getAttribute("aria-label")) == null ? void 0 : r.trim();
310
+ if (c)
311
+ a += c;
312
312
  else {
313
- const m = d.querySelector("title");
314
- m && (a += m.textContent ?? "");
313
+ const d = l.querySelector("title");
314
+ d && (a += d.textContent ?? "");
315
315
  }
316
- } else (o = d.getAttribute("aria-label")) != null && o.trim() ? a += d.getAttribute("aria-label").trim() : a += y(d);
316
+ } else (o = l.getAttribute("aria-label")) != null && o.trim() ? a += l.getAttribute("aria-label").trim() : a += w(l);
317
317
  }
318
318
  }
319
319
  return a;
320
320
  }
321
- let F = /* @__PURE__ */ new WeakMap();
321
+ let W = /* @__PURE__ */ new WeakMap();
322
322
  function Ie() {
323
- F = /* @__PURE__ */ new WeakMap();
323
+ W = /* @__PURE__ */ new WeakMap();
324
324
  }
325
- function Ee(t) {
325
+ function Te(t) {
326
326
  return t.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
327
327
  }
328
- const Te = [
328
+ const Ee = [
329
329
  "data-testid",
330
330
  "data-test-id",
331
331
  "data-cy",
@@ -335,12 +335,12 @@ const Te = [
335
335
  "for",
336
336
  "aria-label"
337
337
  ];
338
- function Le(t) {
338
+ function Ce(t) {
339
339
  const a = t.tagName.toLowerCase();
340
- for (const i of Te) {
340
+ for (const i of Ee) {
341
341
  const n = t.getAttribute(i);
342
342
  if (n != null && n.length > 0 && n.length < 100)
343
- return `${a}[${i}="${Ee(n)}"]`;
343
+ return `${a}[${i}="${Te(n)}"]`;
344
344
  }
345
345
  const e = t.parentElement;
346
346
  if (e) {
@@ -352,7 +352,7 @@ function Le(t) {
352
352
  }
353
353
  return a;
354
354
  }
355
- function $(t) {
355
+ function M(t) {
356
356
  if (t.id) return `#${CSS.escape(t.id)}`;
357
357
  const a = t.getRootNode(), e = a instanceof ShadowRoot ? null : a.documentElement, i = [];
358
358
  let n = t;
@@ -361,7 +361,7 @@ function $(t) {
361
361
  i.unshift(`#${CSS.escape(n.id)}`);
362
362
  break;
363
363
  }
364
- if (i.unshift(Le(n)), i.length >= 2) {
364
+ if (i.unshift(Ce(n)), i.length >= 2) {
365
365
  const r = i.join(" > ");
366
366
  try {
367
367
  const o = a.querySelectorAll(r);
@@ -373,30 +373,30 @@ function $(t) {
373
373
  }
374
374
  return i.join(" > ");
375
375
  }
376
- function u(t) {
376
+ function m(t) {
377
377
  var r;
378
- const a = F.get(t);
378
+ const a = W.get(t);
379
379
  if (a !== void 0) return a;
380
380
  const e = [];
381
381
  let i = t;
382
382
  for (; i; ) {
383
383
  const o = i.getRootNode();
384
384
  if (o instanceof ShadowRoot)
385
- e.unshift({ selector: $(i), delimiter: " >>> " }), i = o.host;
385
+ e.unshift({ selector: M(i), delimiter: " >>> " }), i = o.host;
386
386
  else {
387
387
  const s = (r = o.defaultView) == null ? void 0 : r.frameElement;
388
388
  if (s)
389
- e.unshift({ selector: $(i), delimiter: " >>>iframe> " }), i = s;
389
+ e.unshift({ selector: M(i), delimiter: " >>>iframe> " }), i = s;
390
390
  else {
391
- e.unshift({ selector: $(i), delimiter: "" });
391
+ e.unshift({ selector: M(i), delimiter: "" });
392
392
  break;
393
393
  }
394
394
  }
395
395
  }
396
396
  const n = e.map((o, s) => (s === 0 ? "" : o.delimiter) + o.selector).join("");
397
- return F.set(t, n), n;
397
+ return W.set(t, n), n;
398
398
  }
399
- function yi(t) {
399
+ function wi(t) {
400
400
  const a = [], e = [];
401
401
  let i = t;
402
402
  for (; i; ) {
@@ -429,11 +429,11 @@ function yi(t) {
429
429
  }
430
430
  return null;
431
431
  }
432
- function c(t) {
432
+ function u(t) {
433
433
  const a = t.outerHTML;
434
434
  return a.length > 200 ? a.slice(0, 200) + "..." : a;
435
435
  }
436
- const Ce = /* @__PURE__ */ new Set([
436
+ const Le = /* @__PURE__ */ new Set([
437
437
  "aria-activedescendant",
438
438
  "aria-atomic",
439
439
  "aria-autocomplete",
@@ -568,7 +568,7 @@ const Ce = /* @__PURE__ */ new Set([
568
568
  u: !0,
569
569
  var: !0,
570
570
  wbr: !0
571
- }, Me = {
571
+ }, $e = {
572
572
  alert: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
573
573
  article: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
574
574
  banner: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
@@ -601,95 +601,95 @@ const Ce = /* @__PURE__ */ new Set([
601
601
  time: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"]),
602
602
  tooltip: /* @__PURE__ */ new Set(["aria-disabled", "aria-errormessage", "aria-haspopup", "aria-invalid"])
603
603
  };
604
- let T = null, L = null;
605
- function $e() {
606
- T = null, L = null;
604
+ let E = null, C = null;
605
+ function Me() {
606
+ E = null, C = null;
607
607
  }
608
608
  function z(t) {
609
609
  var n;
610
- if (L && (T == null ? void 0 : T.deref()) === t) return L;
610
+ if (C && (E == null ? void 0 : E.deref()) === t) return C;
611
611
  const a = [], e = [], i = [];
612
612
  for (const r of t.querySelectorAll("*")) {
613
613
  let o = !1;
614
- for (const l of r.attributes)
615
- if (l.name.startsWith("aria-")) {
614
+ for (const c of r.attributes)
615
+ if (c.name.startsWith("aria-")) {
616
616
  o = !0;
617
617
  break;
618
618
  }
619
619
  if (!o) continue;
620
- let s, d;
621
- const h = () => (s === void 0 && (s = u(r), d = c(r)), { selector: s, html: d });
622
- for (const l of r.attributes)
623
- if (l.name.startsWith("aria-") && !Ce.has(l.name)) {
624
- const m = h();
620
+ let s, l;
621
+ const h = () => (s === void 0 && (s = m(r), l = u(r)), { selector: s, html: l });
622
+ for (const c of r.attributes)
623
+ if (c.name.startsWith("aria-") && !Le.has(c.name)) {
624
+ const d = h();
625
625
  a.push({
626
626
  ruleId: "aria-valid-attr",
627
- selector: m.selector,
628
- html: m.html,
627
+ selector: d.selector,
628
+ html: d.html,
629
629
  impact: "critical",
630
- message: `Invalid ARIA attribute "${l.name}".`
630
+ message: `Invalid ARIA attribute "${c.name}".`
631
631
  });
632
632
  break;
633
633
  }
634
- for (const l of r.attributes) {
635
- if (!l.name.startsWith("aria-")) continue;
636
- const m = l.value.trim();
637
- if (!(m === "" && !G.has(l.name) && !X.has(l.name))) {
638
- if (G.has(l.name)) {
639
- if (m !== "true" && m !== "false") {
634
+ for (const c of r.attributes) {
635
+ if (!c.name.startsWith("aria-")) continue;
636
+ const d = c.value.trim();
637
+ if (!(d === "" && !G.has(c.name) && !X.has(c.name))) {
638
+ if (G.has(c.name)) {
639
+ if (d !== "true" && d !== "false") {
640
640
  const g = h();
641
641
  e.push({
642
642
  ruleId: "aria-valid-attr-value",
643
643
  selector: g.selector,
644
644
  html: g.html,
645
645
  impact: "critical",
646
- message: `${l.name} must be "true" or "false", got "${m}".`
646
+ message: `${c.name} must be "true" or "false", got "${d}".`
647
647
  });
648
648
  }
649
- } else if (X.has(l.name)) {
650
- if (m !== "true" && m !== "false" && m !== "mixed") {
649
+ } else if (X.has(c.name)) {
650
+ if (d !== "true" && d !== "false" && d !== "mixed") {
651
651
  const g = h();
652
652
  e.push({
653
653
  ruleId: "aria-valid-attr-value",
654
654
  selector: g.selector,
655
655
  html: g.html,
656
656
  impact: "critical",
657
- message: `${l.name} must be "true", "false", or "mixed", got "${m}".`
657
+ message: `${c.name} must be "true", "false", or "mixed", got "${d}".`
658
658
  });
659
659
  }
660
- } else if (qe.has(l.name)) {
661
- if (m === "" || !/^-?\d+$/.test(m)) {
660
+ } else if (qe.has(c.name)) {
661
+ if (d === "" || !/^-?\d+$/.test(d)) {
662
662
  const g = h();
663
663
  e.push({
664
664
  ruleId: "aria-valid-attr-value",
665
665
  selector: g.selector,
666
666
  html: g.html,
667
667
  impact: "critical",
668
- message: `${l.name} must be an integer, got "${m}".`
668
+ message: `${c.name} must be an integer, got "${d}".`
669
669
  });
670
670
  }
671
- } else if (Re.has(l.name)) {
672
- if (m === "" || isNaN(Number(m))) {
671
+ } else if (Re.has(c.name)) {
672
+ if (d === "" || isNaN(Number(d))) {
673
673
  const g = h();
674
674
  e.push({
675
675
  ruleId: "aria-valid-attr-value",
676
676
  selector: g.selector,
677
677
  html: g.html,
678
678
  impact: "critical",
679
- message: `${l.name} must be a number, got "${m}".`
679
+ message: `${c.name} must be a number, got "${d}".`
680
680
  });
681
681
  }
682
- } else if (Y[l.name]) {
683
- const g = m.split(/\s+/);
682
+ } else if (Y[c.name]) {
683
+ const g = d.split(/\s+/);
684
684
  for (const b of g)
685
- if (!Y[l.name].has(b)) {
685
+ if (!Y[c.name].has(b)) {
686
686
  const f = h();
687
687
  e.push({
688
688
  ruleId: "aria-valid-attr-value",
689
689
  selector: f.selector,
690
690
  html: f.html,
691
691
  impact: "critical",
692
- message: `Invalid value "${m}" for ${l.name}.`
692
+ message: `Invalid value "${d}" for ${c.name}.`
693
693
  });
694
694
  break;
695
695
  }
@@ -697,8 +697,8 @@ function z(t) {
697
697
  }
698
698
  }
699
699
  if (!p(r)) {
700
- const l = (n = r.getAttribute("role")) == null ? void 0 : n.trim().toLowerCase(), m = r.tagName.toLowerCase();
701
- if (!l && Ne[m]) {
700
+ const c = (n = r.getAttribute("role")) == null ? void 0 : n.trim().toLowerCase(), d = r.tagName.toLowerCase();
701
+ if (!c && Ne[d]) {
702
702
  const g = r.hasAttribute("aria-label"), b = r.hasAttribute("aria-labelledby");
703
703
  if (g || b) {
704
704
  const f = h();
@@ -707,28 +707,28 @@ function z(t) {
707
707
  selector: f.selector,
708
708
  html: f.html,
709
709
  impact: "serious",
710
- message: `aria-label and aria-labelledby are prohibited on <${m}> elements.`
710
+ message: `aria-label and aria-labelledby are prohibited on <${d}> elements.`
711
711
  });
712
712
  }
713
- } else if (l) {
714
- if (K.has(l)) {
713
+ } else if (c) {
714
+ if (K.has(c)) {
715
715
  const b = r.hasAttribute("aria-label"), f = r.hasAttribute("aria-labelledby");
716
716
  if (b || f) {
717
- const w = h();
717
+ const y = h();
718
718
  i.push({
719
719
  ruleId: "aria-prohibited-attr",
720
- selector: w.selector,
721
- html: w.html,
720
+ selector: y.selector,
721
+ html: y.html,
722
722
  impact: "serious",
723
- message: `aria-label and aria-labelledby are prohibited on role "${l}".`
723
+ message: `aria-label and aria-labelledby are prohibited on role "${c}".`
724
724
  });
725
725
  }
726
726
  }
727
- const g = Me[l];
727
+ const g = $e[c];
728
728
  if (g) {
729
729
  for (const b of r.attributes)
730
730
  if (b.name.startsWith("aria-") && g.has(b.name)) {
731
- if ((b.name === "aria-label" || b.name === "aria-labelledby") && K.has(l))
731
+ if ((b.name === "aria-label" || b.name === "aria-labelledby") && K.has(c))
732
732
  continue;
733
733
  const f = h();
734
734
  i.push({
@@ -736,18 +736,18 @@ function z(t) {
736
736
  selector: f.selector,
737
737
  html: f.html,
738
738
  impact: "serious",
739
- message: `Attribute "${b.name}" is prohibited on role "${l}".`
739
+ message: `Attribute "${b.name}" is prohibited on role "${c}".`
740
740
  });
741
741
  }
742
742
  }
743
743
  }
744
744
  }
745
745
  }
746
- return T = new WeakRef(t), L = { validAttr: a, validAttrValue: e, prohibitedAttr: i }, L;
746
+ return E = new WeakRef(t), C = { validAttr: a, validAttrValue: e, prohibitedAttr: i }, C;
747
747
  }
748
- let _ = /* @__PURE__ */ new WeakMap(), j = /* @__PURE__ */ new WeakMap(), P = /* @__PURE__ */ new WeakMap();
748
+ let _ = /* @__PURE__ */ new WeakMap(), P = /* @__PURE__ */ new WeakMap(), j = /* @__PURE__ */ new WeakMap();
749
749
  function He() {
750
- _ = /* @__PURE__ */ new WeakMap(), j = /* @__PURE__ */ new WeakMap(), P = /* @__PURE__ */ new WeakMap();
750
+ _ = /* @__PURE__ */ new WeakMap(), P = /* @__PURE__ */ new WeakMap(), j = /* @__PURE__ */ new WeakMap();
751
751
  }
752
752
  function A(t) {
753
753
  let a = _.get(t);
@@ -764,7 +764,7 @@ function de(t, a) {
764
764
  const e = Math.max(t, a), i = Math.min(t, a);
765
765
  return (e + 0.05) / (i + 0.05);
766
766
  }
767
- const Q = {
767
+ const J = {
768
768
  black: [0, 0, 0],
769
769
  white: [255, 255, 255],
770
770
  red: [255, 0, 0],
@@ -786,7 +786,7 @@ const Q = {
786
786
  };
787
787
  function R(t) {
788
788
  const a = t.trim().toLowerCase();
789
- if (Q[a]) return Q[a];
789
+ if (J[a]) return J[a];
790
790
  const e = a.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);
791
791
  if (e)
792
792
  return [parseInt(e[1] + e[1], 16), parseInt(e[2] + e[2], 16), parseInt(e[3] + e[3], 16)];
@@ -804,10 +804,10 @@ function R(t) {
804
804
  return r ? [parseInt(r[1]), parseInt(r[2]), parseInt(r[3])] : null;
805
805
  }
806
806
  function De(t) {
807
- const a = j.get(t);
807
+ const a = P.get(t);
808
808
  if (a !== void 0) return a;
809
809
  const e = Oe(t);
810
- return j.set(t, e), e;
810
+ return P.set(t, e), e;
811
811
  }
812
812
  function Oe(t) {
813
813
  let a = t;
@@ -828,20 +828,20 @@ function Oe(t) {
828
828
  }
829
829
  return [255, 255, 255];
830
830
  }
831
- const We = /* @__PURE__ */ new Set(["IMG", "PICTURE", "VIDEO", "SVG"]);
831
+ const Fe = /* @__PURE__ */ new Set(["IMG", "PICTURE", "VIDEO", "SVG"]);
832
832
  function Be(t) {
833
- const a = P.get(t);
833
+ const a = j.get(t);
834
834
  if (a !== void 0) return a;
835
- const e = Fe(t);
836
- return P.set(t, e), e;
835
+ const e = We(t);
836
+ return j.set(t, e), e;
837
837
  }
838
- function Fe(t) {
838
+ function We(t) {
839
839
  let a = t, e = !1;
840
840
  for (; a; ) {
841
841
  const i = A(a).position;
842
842
  if ((i === "absolute" || i === "fixed") && (e = !0), a !== t && i !== "static") {
843
843
  for (const n of a.children)
844
- if (!(n === t || n.contains(t)) && We.has(n.tagName)) {
844
+ if (!(n === t || n.contains(t)) && Fe.has(n.tagName)) {
845
845
  if (e) return !0;
846
846
  const r = A(n).position;
847
847
  if (r === "absolute" || r === "fixed") return !0;
@@ -856,7 +856,7 @@ function _e(t) {
856
856
  const a = parseFloat(t);
857
857
  return t.endsWith("pt") ? a * (4 / 3) : a;
858
858
  }
859
- function je(t) {
859
+ function Pe(t) {
860
860
  const a = A(t), e = _e(a.fontSize), i = parseInt(a.fontWeight) || (a.fontWeight === "bold" ? 700 : 400);
861
861
  return e >= 23.5 || e >= 18.5 && i >= 700;
862
862
  }
@@ -874,13 +874,13 @@ function H(t) {
874
874
  }
875
875
  const n = t.parentElement;
876
876
  if (n && n !== e) {
877
- const s = t instanceof HTMLImageElement && t.alt || "", d = (o = n.textContent) == null ? void 0 : o.replace(s, "").trim().slice(0, 100);
878
- d && a.push(`Adjacent text: ${d}`);
877
+ const s = t instanceof HTMLImageElement && t.alt || "", l = (o = n.textContent) == null ? void 0 : o.replace(s, "").trim().slice(0, 100);
878
+ l && a.push(`Adjacent text: ${l}`);
879
879
  }
880
880
  return a.length > 0 ? a.join(`
881
881
  `) : void 0;
882
882
  }
883
- function J(t) {
883
+ function Q(t) {
884
884
  let a = t;
885
885
  for (; a; ) {
886
886
  if (a instanceof HTMLElement && a.style.visibility === "hidden") return !0;
@@ -888,7 +888,7 @@ function J(t) {
888
888
  }
889
889
  return !1;
890
890
  }
891
- const Pe = {
891
+ const je = {
892
892
  id: "img-alt",
893
893
  wcag: ["1.1.1"],
894
894
  level: "A",
@@ -898,7 +898,7 @@ const Pe = {
898
898
  run(t) {
899
899
  const a = [];
900
900
  for (const e of t.querySelectorAll("img")) {
901
- if (p(e) || J(e)) continue;
901
+ if (p(e) || Q(e)) continue;
902
902
  const i = e.getAttribute("role");
903
903
  if (i === "presentation" || i === "none") {
904
904
  const r = e.getAttribute("tabindex");
@@ -908,8 +908,8 @@ const Pe = {
908
908
  if (n !== null && n.trim() === "" && n !== "") {
909
909
  a.push({
910
910
  ruleId: "img-alt",
911
- selector: u(e),
912
- html: c(e),
911
+ selector: m(e),
912
+ html: u(e),
913
913
  impact: "critical",
914
914
  message: 'Image has whitespace-only alt text. Use alt="" for decorative images or provide descriptive text.',
915
915
  context: H(e)
@@ -918,18 +918,18 @@ const Pe = {
918
918
  }
919
919
  !e.hasAttribute("alt") && !v(e) && a.push({
920
920
  ruleId: "img-alt",
921
- selector: u(e),
922
- html: c(e),
921
+ selector: m(e),
922
+ html: u(e),
923
923
  impact: "critical",
924
924
  message: "Image element missing alt attribute.",
925
925
  context: H(e)
926
926
  });
927
927
  }
928
928
  for (const e of t.querySelectorAll('[role="img"]:not(img):not(svg)'))
929
- p(e) || J(e) || v(e) || a.push({
929
+ p(e) || Q(e) || v(e) || a.push({
930
930
  ruleId: "img-alt",
931
- selector: u(e),
932
- html: c(e),
931
+ selector: m(e),
932
+ html: u(e),
933
933
  impact: "critical",
934
934
  message: 'Element with role="img" has no accessible name. Add aria-label or aria-labelledby.',
935
935
  context: H(e)
@@ -941,11 +941,11 @@ function Ve(t) {
941
941
  var r, o, s;
942
942
  const a = t.getAttribute("aria-labelledby");
943
943
  if (a) {
944
- const d = a.split(/\s+/).map((h) => {
945
- var l, m;
946
- return ((m = (l = t.ownerDocument.getElementById(h)) == null ? void 0 : l.textContent) == null ? void 0 : m.trim()) ?? "";
944
+ const l = a.split(/\s+/).map((h) => {
945
+ var c, d;
946
+ return ((d = (c = t.ownerDocument.getElementById(h)) == null ? void 0 : c.textContent) == null ? void 0 : d.trim()) ?? "";
947
947
  }).filter(Boolean);
948
- if (d.length) return d.join(" ");
948
+ if (l.length) return l.join(" ");
949
949
  }
950
950
  const e = (r = t.getAttribute("aria-label")) == null ? void 0 : r.trim();
951
951
  if (e) return e;
@@ -969,8 +969,8 @@ const ze = {
969
969
  const r = i.getAttribute("role");
970
970
  a.push({
971
971
  ruleId: "svg-img-alt",
972
- selector: u(i),
973
- html: c(i),
972
+ selector: m(i),
973
+ html: u(i),
974
974
  impact: "serious",
975
975
  message: `${i.tagName.toLowerCase()} with role='${r}' has no accessible name.`
976
976
  });
@@ -990,8 +990,8 @@ const ze = {
990
990
  for (const e of t.querySelectorAll('input[type="image"]'))
991
991
  p(e) || v(e) || a.push({
992
992
  ruleId: "input-image-alt",
993
- selector: u(e),
994
- html: c(e),
993
+ selector: m(e),
994
+ html: u(e),
995
995
  impact: "critical",
996
996
  message: "Image input missing alt text."
997
997
  });
@@ -1004,7 +1004,7 @@ const ze = {
1004
1004
  tags: ["best-practice"],
1005
1005
  description: "Image alt text should not duplicate adjacent link or button text. When alt text repeats surrounding text, screen reader users hear the same information twice.",
1006
1006
  guidance: "When an image is inside a link or button that also has text, make the alt text complementary rather than identical. If the image is purely decorative in that context, use alt='' to avoid repetition.",
1007
- prompt: "Show the duplicated text and suggest either an empty alt or a complementary description.",
1007
+ prompt: "The image alt text is identical to the text already visible in the parent link or button. Screen reader users hear the same words twice. If the image is decorative in this context (e.g. an icon next to a label), recommend alt=''. Otherwise suggest brief complementary alt text that adds information the visible text doesn't convey — for example what the image depicts, not what the link says.",
1008
1008
  run(t) {
1009
1009
  var e;
1010
1010
  const a = [];
@@ -1014,13 +1014,17 @@ const ze = {
1014
1014
  const r = i.closest("a, button");
1015
1015
  if (r) {
1016
1016
  const o = ((e = r.textContent) == null ? void 0 : e.trim().toLowerCase()) || "";
1017
- o && o === n && a.push({
1018
- ruleId: "image-redundant-alt",
1019
- selector: u(i),
1020
- html: c(i),
1021
- impact: "minor",
1022
- message: `Alt text "${i.getAttribute("alt")}" duplicates surrounding ${r.tagName.toLowerCase()} text.`
1023
- });
1017
+ if (o && o === n) {
1018
+ const s = r.tagName.toLowerCase(), l = r.getAttribute("href");
1019
+ a.push({
1020
+ ruleId: "image-redundant-alt",
1021
+ selector: m(i),
1022
+ html: u(i),
1023
+ impact: "minor",
1024
+ message: `Alt text "${i.getAttribute("alt")}" duplicates surrounding ${s} text.`,
1025
+ context: `Duplicated text: "${i.getAttribute("alt")}", parent element: <${s}>${l ? ` href="${l}"` : ""}`
1026
+ });
1027
+ }
1024
1028
  }
1025
1029
  }
1026
1030
  return a;
@@ -1032,17 +1036,20 @@ const ze = {
1032
1036
  tags: ["best-practice"],
1033
1037
  description: "Image alt text should not contain words like 'image', 'photo', or 'picture' — screen readers already announce the element type.",
1034
1038
  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'.",
1035
- prompt: "Identify the redundant word(s) in the alt text and show the corrected version with those words removed.",
1039
+ prompt: "The alt text contains a word like 'image', 'photo', or 'picture' that is already announced by the screen reader. Rewrite the alt text with the redundant word removed while keeping the description meaningful. For example: 'image of a sunset over the ocean' → 'sunset over the ocean'; 'photo of team members' → 'team members at the 2024 offsite'; 'icon for settings' → 'settings'.",
1036
1040
  run(t) {
1037
1041
  const a = [];
1038
1042
  for (const e of t.querySelectorAll("img[alt]")) {
1039
1043
  const i = e.getAttribute("alt").toLowerCase();
1040
- i && Xe.some((n) => i.split(/\s+/).includes(n)) && a.push({
1044
+ if (!i) continue;
1045
+ const n = Xe.filter((r) => i.split(/\s+/).includes(r));
1046
+ n.length > 0 && a.push({
1041
1047
  ruleId: "image-alt-redundant-words",
1042
- selector: u(e),
1043
- html: c(e),
1048
+ selector: m(e),
1049
+ html: u(e),
1044
1050
  impact: "minor",
1045
- message: `Alt text "${e.getAttribute("alt")}" contains redundant word(s).`
1051
+ message: `Alt text "${e.getAttribute("alt")}" contains redundant word(s): ${n.join(", ")}.`,
1052
+ context: `Current alt: "${e.getAttribute("alt")}", redundant word(s): ${n.join(", ")}`
1046
1053
  });
1047
1054
  }
1048
1055
  return a;
@@ -1060,8 +1067,8 @@ const ze = {
1060
1067
  if (p(e)) continue;
1061
1068
  v(e) || a.push({
1062
1069
  ruleId: "area-alt",
1063
- selector: u(e),
1064
- html: c(e),
1070
+ selector: m(e),
1071
+ html: u(e),
1065
1072
  impact: "critical",
1066
1073
  message: "Image map <area> element is missing alternative text."
1067
1074
  });
@@ -1069,13 +1076,13 @@ const ze = {
1069
1076
  return a;
1070
1077
  }
1071
1078
  };
1072
- function Qe(t) {
1079
+ function Je(t) {
1073
1080
  var n, r;
1074
1081
  const a = t.getAttribute("aria-labelledby");
1075
1082
  if (a) {
1076
1083
  const o = a.split(/\s+/).map((s) => {
1077
- var d, h;
1078
- return ((h = (d = t.ownerDocument.getElementById(s)) == null ? void 0 : d.textContent) == null ? void 0 : h.trim()) ?? "";
1084
+ var l, h;
1085
+ return ((h = (l = t.ownerDocument.getElementById(s)) == null ? void 0 : l.textContent) == null ? void 0 : h.trim()) ?? "";
1079
1086
  }).filter(Boolean);
1080
1087
  if (o.length) return o.join(" ");
1081
1088
  }
@@ -1084,7 +1091,7 @@ function Qe(t) {
1084
1091
  const i = (r = t.getAttribute("title")) == null ? void 0 : r.trim();
1085
1092
  return i || "";
1086
1093
  }
1087
- const Je = {
1094
+ const Qe = {
1088
1095
  id: "object-alt",
1089
1096
  wcag: ["1.1.1"],
1090
1097
  level: "A",
@@ -1104,7 +1111,7 @@ const Je = {
1104
1111
  }
1105
1112
  n = n.parentElement;
1106
1113
  }
1107
- if (r || i.getAttribute("role") === "presentation" || i.getAttribute("role") === "none" || Qe(i)) continue;
1114
+ if (r || i.getAttribute("role") === "presentation" || i.getAttribute("role") === "none" || Je(i)) continue;
1108
1115
  const o = i.getAttribute("data") || "";
1109
1116
  if (!((i.getAttribute("type") || "").startsWith("image/") || /\.(png|jpg|jpeg|gif|svg|webp|bmp|ico)$/i.test(o))) {
1110
1117
  const h = i.querySelector("img[alt]");
@@ -1112,8 +1119,8 @@ const Je = {
1112
1119
  }
1113
1120
  a.push({
1114
1121
  ruleId: "object-alt",
1115
- selector: u(i),
1116
- html: c(i),
1122
+ selector: m(i),
1123
+ html: u(i),
1117
1124
  impact: "serious",
1118
1125
  message: "<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute."
1119
1126
  });
@@ -1133,8 +1140,8 @@ const Je = {
1133
1140
  if (p(e) || e.tagName.toLowerCase() === "svg" || e.tagName.toLowerCase() === "img") continue;
1134
1141
  v(e) || a.push({
1135
1142
  ruleId: "role-img-alt",
1136
- selector: u(e),
1137
- html: c(e),
1143
+ selector: m(e),
1144
+ html: u(e),
1138
1145
  impact: "serious",
1139
1146
  message: "Element with role='img' has no accessible name. Add aria-label or aria-labelledby."
1140
1147
  });
@@ -1193,7 +1200,7 @@ function et(t) {
1193
1200
  return `Unknown check type: ${String(t.type)}`;
1194
1201
  }
1195
1202
  }
1196
- function x(t, a, e) {
1203
+ function S(t, a, e) {
1197
1204
  let i = t;
1198
1205
  if (i.includes("{{tag}}") && (i = i.replace(/\{\{tag\}\}/g, a.tagName.toLowerCase())), i.includes("{{value}}")) {
1199
1206
  let n = "";
@@ -1218,10 +1225,10 @@ function k(t) {
1218
1225
  for (const n of e.querySelectorAll(t.selector))
1219
1226
  a && p(n) || i.push({
1220
1227
  ruleId: t.id,
1221
- selector: u(n),
1222
- html: c(n),
1228
+ selector: m(n),
1229
+ html: u(n),
1223
1230
  impact: t.impact,
1224
- message: x(t.message, n, t.check),
1231
+ message: S(t.message, n, t.check),
1225
1232
  element: n
1226
1233
  });
1227
1234
  break;
@@ -1230,13 +1237,13 @@ function k(t) {
1230
1237
  const { attribute: n, operator: r, value: o } = t.check;
1231
1238
  for (const s of e.querySelectorAll(t.selector)) {
1232
1239
  if (a && p(s)) continue;
1233
- const d = s.getAttribute(n);
1234
- d !== null && tt(d, r, o) && i.push({
1240
+ const l = s.getAttribute(n);
1241
+ l !== null && tt(l, r, o) && i.push({
1235
1242
  ruleId: t.id,
1236
- selector: u(s),
1237
- html: c(s),
1243
+ selector: m(s),
1244
+ html: u(s),
1238
1245
  impact: t.impact,
1239
- message: x(t.message, s, t.check),
1246
+ message: S(t.message, s, t.check),
1240
1247
  element: s
1241
1248
  });
1242
1249
  }
@@ -1247,40 +1254,40 @@ function k(t) {
1247
1254
  for (const r of e.querySelectorAll(t.selector))
1248
1255
  a && p(r) || r.hasAttribute(n) || i.push({
1249
1256
  ruleId: t.id,
1250
- selector: u(r),
1251
- html: c(r),
1257
+ selector: m(r),
1258
+ html: u(r),
1252
1259
  impact: t.impact,
1253
- message: x(t.message, r, t.check),
1260
+ message: S(t.message, r, t.check),
1254
1261
  element: r
1255
1262
  });
1256
1263
  break;
1257
1264
  }
1258
1265
  case "attribute-regex": {
1259
1266
  const { attribute: n, pattern: r, flags: o, shouldMatch: s } = t.check;
1260
- let d;
1267
+ let l;
1261
1268
  try {
1262
- d = new RegExp(r, o);
1269
+ l = new RegExp(r, o);
1263
1270
  } catch {
1264
1271
  break;
1265
1272
  }
1266
1273
  for (const h of e.querySelectorAll(t.selector)) {
1267
1274
  if (a && p(h)) continue;
1268
- const l = h.getAttribute(n);
1269
- if (l === null) continue;
1270
- const m = d.test(l);
1271
- s && !m ? i.push({
1275
+ const c = h.getAttribute(n);
1276
+ if (c === null) continue;
1277
+ const d = l.test(c);
1278
+ s && !d ? i.push({
1272
1279
  ruleId: t.id,
1273
- selector: u(h),
1274
- html: c(h),
1280
+ selector: m(h),
1281
+ html: u(h),
1275
1282
  impact: t.impact,
1276
- message: x(t.message, h, t.check),
1283
+ message: S(t.message, h, t.check),
1277
1284
  element: h
1278
- }) : !s && m && i.push({
1285
+ }) : !s && d && i.push({
1279
1286
  ruleId: t.id,
1280
- selector: u(h),
1281
- html: c(h),
1287
+ selector: m(h),
1288
+ html: u(h),
1282
1289
  impact: t.impact,
1283
- message: x(t.message, h, t.check),
1290
+ message: S(t.message, h, t.check),
1284
1291
  element: h
1285
1292
  });
1286
1293
  }
@@ -1291,10 +1298,10 @@ function k(t) {
1291
1298
  for (const r of e.querySelectorAll(t.selector))
1292
1299
  a && p(r) || r.querySelector(n) || i.push({
1293
1300
  ruleId: t.id,
1294
- selector: u(r),
1295
- html: c(r),
1301
+ selector: m(r),
1302
+ html: u(r),
1296
1303
  impact: t.impact,
1297
- message: x(t.message, r, t.check),
1304
+ message: S(t.message, r, t.check),
1298
1305
  element: r
1299
1306
  });
1300
1307
  break;
@@ -1309,10 +1316,10 @@ function k(t) {
1309
1316
  if (!n.has(o.tagName.toLowerCase())) {
1310
1317
  i.push({
1311
1318
  ruleId: t.id,
1312
- selector: u(o),
1313
- html: c(o),
1319
+ selector: m(o),
1320
+ html: u(o),
1314
1321
  impact: t.impact,
1315
- message: x(t.message, o, t.check),
1322
+ message: S(t.message, o, t.check),
1316
1323
  element: o
1317
1324
  });
1318
1325
  break;
@@ -1381,39 +1388,39 @@ const at = {
1381
1388
  "textbox"
1382
1389
  ]);
1383
1390
  function st(t) {
1384
- var o, s, d, h;
1391
+ var o, s, l, h;
1385
1392
  const a = (o = t.getAttribute("role")) == null ? void 0 : o.trim().toLowerCase();
1386
1393
  if (a && rt.has(a) || (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) && !(a && ot.has(a)))
1387
1394
  return v(t);
1388
1395
  const i = t.getAttribute("aria-labelledby");
1389
1396
  if (i) {
1390
- const l = i.split(/\s+/).map((m) => {
1391
- const g = t.ownerDocument.getElementById(m);
1392
- return g ? y(g).trim() : "";
1397
+ const c = i.split(/\s+/).map((d) => {
1398
+ const g = t.ownerDocument.getElementById(d);
1399
+ return g ? w(g).trim() : "";
1393
1400
  }).filter(Boolean);
1394
- if (l.length) return l.join(" ");
1401
+ if (c.length) return c.join(" ");
1395
1402
  }
1396
1403
  const n = (s = t.getAttribute("aria-label")) == null ? void 0 : s.trim();
1397
1404
  if (n) return n;
1398
1405
  if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement) {
1399
1406
  if (t.id) {
1400
- const m = t.ownerDocument.querySelector(`label[for="${CSS.escape(t.id)}"]`);
1401
- if (m) {
1402
- const g = y(m).trim();
1407
+ const d = t.ownerDocument.querySelector(`label[for="${CSS.escape(t.id)}"]`);
1408
+ if (d) {
1409
+ const g = w(d).trim();
1403
1410
  if (g) return g;
1404
1411
  }
1405
1412
  }
1406
- const l = t.closest("label");
1407
- if (l) {
1408
- const m = y(l).trim();
1409
- if (m) return m;
1413
+ const c = t.closest("label");
1414
+ if (c) {
1415
+ const d = w(c).trim();
1416
+ if (d) return d;
1410
1417
  }
1411
1418
  }
1412
- const r = (d = t.getAttribute("title")) == null ? void 0 : d.trim();
1419
+ const r = (l = t.getAttribute("title")) == null ? void 0 : l.trim();
1413
1420
  if (r) return r;
1414
1421
  if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) {
1415
- const l = (h = t.getAttribute("placeholder")) == null ? void 0 : h.trim();
1416
- if (l) return l;
1422
+ const c = (h = t.getAttribute("placeholder")) == null ? void 0 : h.trim();
1423
+ if (c) return c;
1417
1424
  }
1418
1425
  return "";
1419
1426
  }
@@ -1423,7 +1430,7 @@ const lt = {
1423
1430
  level: "A",
1424
1431
  description: "Form elements must have labels. Use <label>, aria-label, or aria-labelledby.",
1425
1432
  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.",
1426
- prompt: "Based on the input type, name attribute, or placeholder, suggest a label element with appropriate text, or an aria-label.",
1433
+ prompt: `This form field has no accessible label. Based on the context (input type, name attribute, placeholder, or surrounding elements), suggest adding a <label for="id"> element with descriptive text, or an aria-label attribute. The label should describe what information the user should enter, not the field type. For example: 'Email address', 'Search', 'Phone number'.`,
1427
1434
  run(t) {
1428
1435
  var n;
1429
1436
  const a = [], i = t.querySelectorAll(`input:not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]), textarea, select, ${nt}`);
@@ -1431,13 +1438,23 @@ const lt = {
1431
1438
  if (p(r) || r instanceof HTMLElement && (r.hidden || r.style.display === "none")) continue;
1432
1439
  const o = (n = r.getAttribute("role")) == null ? void 0 : n.trim().toLowerCase();
1433
1440
  if (o === "presentation" || o === "none") continue;
1434
- st(r) || a.push({
1435
- ruleId: "label",
1436
- selector: u(r),
1437
- html: c(r),
1438
- impact: "critical",
1439
- message: "Form element has no accessible label."
1440
- });
1441
+ if (!st(r)) {
1442
+ const l = [], h = r.tagName.toLowerCase(), c = r.getAttribute("type");
1443
+ c && h === "input" && l.push(`type: ${c}`);
1444
+ const d = r.getAttribute("name");
1445
+ d && l.push(`name: "${d}"`);
1446
+ const g = r.getAttribute("placeholder");
1447
+ g && l.push(`placeholder: "${g}"`), o && l.push(`role: ${o}`);
1448
+ const b = r.getAttribute("id");
1449
+ b && l.push(`id: "${b}"`), a.push({
1450
+ ruleId: "label",
1451
+ selector: m(r),
1452
+ html: u(r),
1453
+ impact: "critical",
1454
+ message: "Form element has no accessible label.",
1455
+ context: l.length > 0 ? l.join(", ") : void 0
1456
+ });
1457
+ }
1441
1458
  }
1442
1459
  return a;
1443
1460
  }
@@ -1465,8 +1482,8 @@ const lt = {
1465
1482
  const s = n.length + r;
1466
1483
  s > 1 && a.push({
1467
1484
  ruleId: "form-field-multiple-labels",
1468
- selector: u(i),
1469
- html: c(i),
1485
+ selector: m(i),
1486
+ html: u(i),
1470
1487
  impact: "moderate",
1471
1488
  message: `Form field has ${s} labels. Use a single label element.`
1472
1489
  });
@@ -1485,8 +1502,8 @@ const lt = {
1485
1502
  for (const e of t.querySelectorAll("select"))
1486
1503
  p(e) || v(e) || a.push({
1487
1504
  ruleId: "select-name",
1488
- selector: u(e),
1489
- html: c(e),
1505
+ selector: m(e),
1506
+ html: u(e),
1490
1507
  impact: "critical",
1491
1508
  message: "Select element has no accessible name."
1492
1509
  });
@@ -1509,8 +1526,8 @@ const lt = {
1509
1526
  const r = (e = n.getAttribute("value")) == null ? void 0 : e.trim(), o = (i = n.getAttribute("type")) == null ? void 0 : i.toLowerCase(), s = (o === "submit" || o === "reset") && !n.hasAttribute("value");
1510
1527
  !r && !s && !v(n) && a.push({
1511
1528
  ruleId: "input-button-name",
1512
- selector: u(n),
1513
- html: c(n),
1529
+ selector: m(n),
1530
+ html: u(n),
1514
1531
  impact: "critical",
1515
1532
  message: "Input button has no discernible text."
1516
1533
  });
@@ -1606,8 +1623,8 @@ const vt = {
1606
1623
  const i = e.getAttribute("autocomplete").trim();
1607
1624
  i && (ft(i) || a.push({
1608
1625
  ruleId: "autocomplete-valid",
1609
- selector: u(e),
1610
- html: c(e),
1626
+ selector: m(e),
1627
+ html: u(e),
1611
1628
  impact: "serious",
1612
1629
  message: `Invalid autocomplete value "${i}".`
1613
1630
  }));
@@ -1638,7 +1655,7 @@ function V(t) {
1638
1655
  }
1639
1656
  return a;
1640
1657
  }
1641
- const wt = {
1658
+ const yt = {
1642
1659
  id: "label-content-name-mismatch",
1643
1660
  wcag: ["2.5.3"],
1644
1661
  level: "A",
@@ -1658,8 +1675,8 @@ const wt = {
1658
1675
  const o = e.hasAttribute("aria-label"), s = e.hasAttribute("aria-labelledby");
1659
1676
  !o && !s || ee(i, n) || a.push({
1660
1677
  ruleId: "label-content-name-mismatch",
1661
- selector: u(e),
1662
- html: c(e),
1678
+ selector: m(e),
1679
+ html: u(e),
1663
1680
  impact: "serious",
1664
1681
  message: `Accessible name "${i}" does not contain visible text "${n.trim()}".`
1665
1682
  });
@@ -1676,15 +1693,15 @@ const wt = {
1676
1693
  }
1677
1694
  o.trim() && (ee(i, o) || a.push({
1678
1695
  ruleId: "label-content-name-mismatch",
1679
- selector: u(e),
1680
- html: c(e),
1696
+ selector: m(e),
1697
+ html: u(e),
1681
1698
  impact: "serious",
1682
1699
  message: `Accessible name "${i}" does not contain visible label "${o.trim()}".`
1683
1700
  }));
1684
1701
  }
1685
1702
  return a;
1686
1703
  }
1687
- }, yt = {
1704
+ }, wt = {
1688
1705
  id: "label-title-only",
1689
1706
  wcag: [],
1690
1707
  level: "A",
@@ -1699,18 +1716,18 @@ const wt = {
1699
1716
  );
1700
1717
  for (const s of e) {
1701
1718
  if (p(s)) continue;
1702
- const d = s.hasAttribute("title") && ((i = s.getAttribute("title")) == null ? void 0 : i.trim()), h = s.hasAttribute("aria-label") && ((n = s.getAttribute("aria-label")) == null ? void 0 : n.trim()), l = s.hasAttribute("aria-labelledby");
1703
- let m = !1;
1719
+ const l = s.hasAttribute("title") && ((i = s.getAttribute("title")) == null ? void 0 : i.trim()), h = s.hasAttribute("aria-label") && ((n = s.getAttribute("aria-label")) == null ? void 0 : n.trim()), c = s.hasAttribute("aria-labelledby");
1720
+ let d = !1;
1704
1721
  const g = s.id;
1705
1722
  if (g) {
1706
1723
  const f = s.ownerDocument.querySelector(`label[for="${CSS.escape(g)}"]`);
1707
- (r = f == null ? void 0 : f.textContent) != null && r.trim() && (m = !0);
1724
+ (r = f == null ? void 0 : f.textContent) != null && r.trim() && (d = !0);
1708
1725
  }
1709
1726
  const b = s.closest("label");
1710
- (o = b == null ? void 0 : b.textContent) != null && o.trim() && (m = !0), d && !h && !l && !m && a.push({
1727
+ (o = b == null ? void 0 : b.textContent) != null && o.trim() && (d = !0), l && !h && !c && !d && a.push({
1711
1728
  ruleId: "label-title-only",
1712
- selector: u(s),
1713
- html: c(s),
1729
+ selector: m(s),
1730
+ html: u(s),
1714
1731
  impact: "serious",
1715
1732
  message: "Form element uses title attribute as only label. Use <label>, aria-label, or aria-labelledby instead."
1716
1733
  });
@@ -1729,7 +1746,7 @@ const wt = {
1729
1746
  tags: ["best-practice"],
1730
1747
  guidance: "Positive tabindex values force elements to the front of the tab order regardless of DOM position, creating unpredictable navigation for keyboard users. Use tabindex='0' to add elements to the natural tab order, or tabindex='-1' to make elements programmatically focusable but not in tab order. Rely on DOM order for tab sequence.",
1731
1748
  prompt: "Change the positive tabindex value to tabindex='0' and rely on DOM order for tab sequence instead."
1732
- }, St = k(At), xt = /* @__PURE__ */ new Set([
1749
+ }, xt = k(At), St = /* @__PURE__ */ new Set([
1733
1750
  "div",
1734
1751
  "span",
1735
1752
  "p",
@@ -1768,11 +1785,11 @@ const wt = {
1768
1785
  const a = [];
1769
1786
  for (const e of t.querySelectorAll('[tabindex="0"]')) {
1770
1787
  const i = e.tagName.toLowerCase();
1771
- if (!xt.has(i)) continue;
1788
+ if (!St.has(i)) continue;
1772
1789
  e.getAttribute("role") || a.push({
1773
1790
  ruleId: "focus-order-semantics",
1774
- selector: u(e),
1775
- html: c(e),
1791
+ selector: m(e),
1792
+ html: u(e),
1776
1793
  impact: "moderate",
1777
1794
  message: `Non-interactive <${i}> with tabindex="0" has no interactive role.`
1778
1795
  });
@@ -1788,7 +1805,7 @@ const wt = {
1788
1805
  "select",
1789
1806
  "textarea",
1790
1807
  "video"
1791
- ]), Et = /* @__PURE__ */ new Set([
1808
+ ]), Tt = /* @__PURE__ */ new Set([
1792
1809
  "button",
1793
1810
  "checkbox",
1794
1811
  "combobox",
@@ -1812,7 +1829,7 @@ const wt = {
1812
1829
  "tabpanel",
1813
1830
  "textbox",
1814
1831
  "treeitem"
1815
- ]), Tt = {
1832
+ ]), Et = {
1816
1833
  grid: /* @__PURE__ */ new Set(["gridcell", "row", "columnheader", "rowheader"]),
1817
1834
  listbox: /* @__PURE__ */ new Set(["option"]),
1818
1835
  menu: /* @__PURE__ */ new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]),
@@ -1822,18 +1839,18 @@ const wt = {
1822
1839
  tree: /* @__PURE__ */ new Set(["treeitem"]),
1823
1840
  treegrid: /* @__PURE__ */ new Set(["gridcell", "row", "columnheader", "rowheader", "treeitem"])
1824
1841
  };
1825
- function Lt(t, a) {
1842
+ function Ct(t, a) {
1826
1843
  var n, r, o;
1827
1844
  const e = (n = t.getAttribute("role")) == null ? void 0 : n.toLowerCase(), i = (r = a.getAttribute("role")) == null ? void 0 : r.toLowerCase();
1828
- return !e || !i ? !1 : ((o = Tt[e]) == null ? void 0 : o.has(i)) ?? !1;
1845
+ return !e || !i ? !1 : ((o = Et[e]) == null ? void 0 : o.has(i)) ?? !1;
1829
1846
  }
1830
- function Ct(t) {
1847
+ function Lt(t) {
1831
1848
  var n;
1832
1849
  const a = t.tagName.toLowerCase();
1833
1850
  if (It.has(a))
1834
1851
  return a === "a" && !t.hasAttribute("href") ? !1 : a === "audio" || a === "video" ? t.hasAttribute("controls") : !(a === "img" && !t.hasAttribute("usemap") || a === "input" && t.type === "hidden" || t.disabled);
1835
1852
  const e = (n = t.getAttribute("role")) == null ? void 0 : n.toLowerCase();
1836
- if (e && Et.has(e)) return !0;
1853
+ if (e && Tt.has(e)) return !0;
1837
1854
  const i = t.getAttribute("tabindex");
1838
1855
  return i !== null && i !== "-1" || t.getAttribute("contenteditable") === "true";
1839
1856
  }
@@ -1856,13 +1873,13 @@ const Rt = {
1856
1873
  for (; o; ) {
1857
1874
  for (; r.length > 0 && !r[r.length - 1].contains(o); )
1858
1875
  r.pop();
1859
- if (!p(o) && Ct(o)) {
1876
+ if (!p(o) && Lt(o)) {
1860
1877
  if (r.length > 0) {
1861
1878
  const s = r[r.length - 1];
1862
- Lt(s, o) || a.push({
1879
+ Ct(s, o) || a.push({
1863
1880
  ruleId: "nested-interactive",
1864
- selector: u(o),
1865
- html: c(o),
1881
+ selector: m(o),
1882
+ html: u(o),
1866
1883
  impact: "serious",
1867
1884
  message: `Interactive element <${o.tagName.toLowerCase()}> is nested inside <${s.tagName.toLowerCase()}>.`
1868
1885
  });
@@ -1889,23 +1906,23 @@ const Rt = {
1889
1906
  if (e.scrollHeight > 0 || e.clientHeight > 0) {
1890
1907
  if (e.scrollHeight <= e.clientHeight && e.scrollWidth <= e.clientWidth) continue;
1891
1908
  } else {
1892
- const l = i.height !== "" || i.maxHeight !== "", m = e.textContent != null && e.textContent.trim().length > 0;
1893
- if (!l || !m) continue;
1909
+ const c = i.height !== "" || i.maxHeight !== "", d = e.textContent != null && e.textContent.trim().length > 0;
1910
+ if (!c || !d) continue;
1894
1911
  }
1895
- const d = e.getAttribute("tabindex");
1896
- d !== null && d !== "-1" || e.querySelector(
1912
+ const l = e.getAttribute("tabindex");
1913
+ l !== null && l !== "-1" || e.querySelector(
1897
1914
  'a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
1898
1915
  ) || a.push({
1899
1916
  ruleId: "scrollable-region-focusable",
1900
- selector: u(e),
1901
- html: c(e),
1917
+ selector: m(e),
1918
+ html: u(e),
1902
1919
  impact: "serious",
1903
1920
  message: "Scrollable region is not keyboard accessible. Add tabindex='0' or include focusable elements."
1904
1921
  });
1905
1922
  }
1906
1923
  return a;
1907
1924
  }
1908
- }, Mt = {
1925
+ }, $t = {
1909
1926
  id: "accesskeys",
1910
1927
  wcag: [],
1911
1928
  level: "A",
@@ -1928,14 +1945,14 @@ const Rt = {
1928
1945
  for (const o of r.slice(1))
1929
1946
  a.push({
1930
1947
  ruleId: "accesskeys",
1931
- selector: u(o),
1932
- html: c(o),
1948
+ selector: m(o),
1949
+ html: u(o),
1933
1950
  impact: "serious",
1934
1951
  message: `Duplicate accesskey "${n}". Each accesskey must be unique.`
1935
1952
  });
1936
1953
  return a;
1937
1954
  }
1938
- }, $t = {
1955
+ }, Mt = {
1939
1956
  id: "heading-order",
1940
1957
  wcag: [],
1941
1958
  level: "A",
@@ -1951,11 +1968,11 @@ const Rt = {
1951
1968
  let o;
1952
1969
  r.hasAttribute("aria-level") ? o = parseInt(r.getAttribute("aria-level"), 10) : o = parseInt(r.tagName[1], 10), i > 0 && o > i + 1 && a.push({
1953
1970
  ruleId: "heading-order",
1954
- selector: u(r),
1955
- html: c(r),
1971
+ selector: m(r),
1972
+ html: u(r),
1956
1973
  impact: "moderate",
1957
1974
  message: `Heading level ${o} skipped from level ${i}.`,
1958
- context: n ? `Previous heading: ${c(n)}` : void 0
1975
+ context: n ? `Previous heading: ${u(n)}` : void 0
1959
1976
  }), i = o, n = r;
1960
1977
  }
1961
1978
  return a;
@@ -1978,8 +1995,8 @@ const Rt = {
1978
1995
  message: "Page has no main landmark."
1979
1996
  }] : a.length > 1 ? Array.from(a).slice(1).map((e) => ({
1980
1997
  ruleId: "landmark-one-main",
1981
- selector: u(e),
1982
- html: c(e),
1998
+ selector: m(e),
1999
+ html: u(e),
1983
2000
  impact: "moderate",
1984
2001
  message: "Page has multiple main landmarks."
1985
2002
  })) : [];
@@ -1997,8 +2014,8 @@ const Rt = {
1997
2014
  return i.length > 1 && i.slice(1).forEach(
1998
2015
  (n) => a.push({
1999
2016
  ruleId: "landmark-no-duplicate-banner",
2000
- selector: u(n),
2001
- html: c(n),
2017
+ selector: m(n),
2018
+ html: u(n),
2002
2019
  impact: "moderate",
2003
2020
  message: "Page has multiple banner landmarks."
2004
2021
  })
@@ -2017,14 +2034,14 @@ const Rt = {
2017
2034
  return i.length > 1 && i.slice(1).forEach(
2018
2035
  (n) => a.push({
2019
2036
  ruleId: "landmark-no-duplicate-contentinfo",
2020
- selector: u(n),
2021
- html: c(n),
2037
+ selector: m(n),
2038
+ html: u(n),
2022
2039
  impact: "moderate",
2023
2040
  message: "Page has multiple contentinfo landmarks."
2024
2041
  })
2025
2042
  ), a;
2026
2043
  }
2027
- }, Wt = {
2044
+ }, Ft = {
2028
2045
  id: "landmark-no-duplicate-main",
2029
2046
  wcag: [],
2030
2047
  level: "A",
@@ -2037,8 +2054,8 @@ const Rt = {
2037
2054
  return e.length > 1 && Array.from(e).slice(1).forEach(
2038
2055
  (i) => a.push({
2039
2056
  ruleId: "landmark-no-duplicate-main",
2040
- selector: u(i),
2041
- html: c(i),
2057
+ selector: m(i),
2058
+ html: u(i),
2042
2059
  impact: "moderate",
2043
2060
  message: "Page has multiple main landmarks."
2044
2061
  })
@@ -2057,14 +2074,14 @@ const Rt = {
2057
2074
  for (const i of e)
2058
2075
  i.closest(N) && a.push({
2059
2076
  ruleId: "landmark-banner-is-top-level",
2060
- selector: u(i),
2061
- html: c(i),
2077
+ selector: m(i),
2078
+ html: u(i),
2062
2079
  impact: "moderate",
2063
2080
  message: "Banner landmark is nested within another landmark."
2064
2081
  });
2065
2082
  return a;
2066
2083
  }
2067
- }, Ft = {
2084
+ }, Wt = {
2068
2085
  id: "landmark-contentinfo-is-top-level",
2069
2086
  wcag: [],
2070
2087
  level: "A",
@@ -2077,8 +2094,8 @@ const Rt = {
2077
2094
  for (const i of e)
2078
2095
  i.closest(N) && a.push({
2079
2096
  ruleId: "landmark-contentinfo-is-top-level",
2080
- selector: u(i),
2081
- html: c(i),
2097
+ selector: m(i),
2098
+ html: u(i),
2082
2099
  impact: "moderate",
2083
2100
  message: "Contentinfo landmark is nested within another landmark."
2084
2101
  });
@@ -2098,15 +2115,15 @@ const Rt = {
2098
2115
  const n = i.parentElement;
2099
2116
  n != null && n.closest('article, aside, nav, section, [role="article"], [role="complementary"], [role="navigation"], [role="region"]') && a.push({
2100
2117
  ruleId: "landmark-main-is-top-level",
2101
- selector: u(i),
2102
- html: c(i),
2118
+ selector: m(i),
2119
+ html: u(i),
2103
2120
  impact: "moderate",
2104
2121
  message: "Main landmark is nested within another landmark."
2105
2122
  });
2106
2123
  }
2107
2124
  return a;
2108
2125
  }
2109
- }, jt = {
2126
+ }, Pt = {
2110
2127
  id: "landmark-complementary-is-top-level",
2111
2128
  wcag: [],
2112
2129
  level: "A",
@@ -2120,15 +2137,15 @@ const Rt = {
2120
2137
  const n = i.parentElement;
2121
2138
  n && !n.matches('body, main, [role="main"]') && i.closest('article, nav, section, [role="article"], [role="navigation"], [role="region"]') && a.push({
2122
2139
  ruleId: "landmark-complementary-is-top-level",
2123
- selector: u(i),
2124
- html: c(i),
2140
+ selector: m(i),
2141
+ html: u(i),
2125
2142
  impact: "moderate",
2126
2143
  message: "Complementary landmark should be top-level."
2127
2144
  });
2128
2145
  }
2129
2146
  return a;
2130
2147
  }
2131
- }, Pt = {
2148
+ }, jt = {
2132
2149
  id: "landmark-unique",
2133
2150
  wcag: [],
2134
2151
  level: "A",
@@ -2148,16 +2165,16 @@ const Rt = {
2148
2165
  if (r.length <= 1) continue;
2149
2166
  const o = /* @__PURE__ */ new Map();
2150
2167
  for (const s of r) {
2151
- const d = v(s).toLowerCase() || "", h = o.get(d) || [];
2152
- h.push(s), o.set(d, h);
2168
+ const l = v(s).toLowerCase() || "", h = o.get(l) || [];
2169
+ h.push(s), o.set(l, h);
2153
2170
  }
2154
- for (const [s, d] of o)
2155
- if (d.length > 1)
2156
- for (const h of d.slice(1))
2171
+ for (const [s, l] of o)
2172
+ if (l.length > 1)
2173
+ for (const h of l.slice(1))
2157
2174
  a.push({
2158
2175
  ruleId: "landmark-unique",
2159
- selector: u(h),
2160
- html: c(h),
2176
+ selector: m(h),
2177
+ html: u(h),
2161
2178
  impact: "moderate",
2162
2179
  message: s ? `Multiple ${n} landmarks have the same label "${s}".` : `Multiple ${n} landmarks have no label. Add unique aria-label attributes.`
2163
2180
  });
@@ -2181,8 +2198,8 @@ const Rt = {
2181
2198
  const r = n.matches(te), o = (i = n.textContent) == null ? void 0 : i.trim();
2182
2199
  !r && o && (n.querySelector(te) || a.push({
2183
2200
  ruleId: "region",
2184
- selector: u(n),
2185
- html: c(n),
2201
+ selector: m(n),
2202
+ html: u(n),
2186
2203
  impact: "moderate",
2187
2204
  message: "Content is not contained within a landmark region."
2188
2205
  }));
@@ -2212,8 +2229,8 @@ const Rt = {
2212
2229
  for (const e of t.querySelectorAll("dt, dd"))
2213
2230
  (!e.parentElement || e.parentElement.tagName.toLowerCase() !== "dl") && a.push({
2214
2231
  ruleId: "dlitem",
2215
- selector: u(e),
2216
- html: c(e),
2232
+ selector: m(e),
2233
+ html: u(e),
2217
2234
  impact: "serious",
2218
2235
  message: `<${e.tagName.toLowerCase()}> is not contained in a <dl>.`
2219
2236
  });
@@ -2236,65 +2253,89 @@ const Rt = {
2236
2253
  level: "A",
2237
2254
  description: "Documents must have a <title> element to provide users with an overview of content.",
2238
2255
  guidance: "Screen reader users rely on page titles to identify and navigate between tabs/windows. Add a descriptive <title> element in <head> that summarizes the page purpose. Keep titles unique across the site, placing specific content before the site name (e.g., 'Contact Us - Acme Corp').",
2239
- prompt: "Suggest a descriptive page title based on the visible content.",
2256
+ prompt: "The page has no title or an empty title. Suggest a concise, descriptive <title> based on the page content sample in context. Good titles are specific and front-load the unique part: 'Product Details - Store Name' rather than 'Store Name - Product Details'.",
2240
2257
  run(t) {
2241
- var e;
2258
+ var e, i, n;
2242
2259
  const a = t.querySelector("title");
2243
- return !a || !((e = a.textContent) != null && e.trim()) ? [{
2244
- ruleId: "document-title",
2245
- selector: "html",
2246
- html: "<html>",
2247
- impact: "serious",
2248
- message: a ? "Document <title> element is empty." : "Document is missing a <title> element."
2249
- }] : [];
2260
+ if (!a || !((e = a.textContent) != null && e.trim())) {
2261
+ let r;
2262
+ const o = t.querySelector("h1");
2263
+ if ((i = o == null ? void 0 : o.textContent) != null && i.trim())
2264
+ r = `h1: "${o.textContent.trim().slice(0, 100)}"`;
2265
+ else if (t.body) {
2266
+ const s = ((n = t.body.textContent) == null ? void 0 : n.trim().replace(/\s+/g, " ")) || "";
2267
+ s && (r = `Page text: "${s.slice(0, 150)}"`);
2268
+ }
2269
+ return [{
2270
+ ruleId: "document-title",
2271
+ selector: "html",
2272
+ html: "<html>",
2273
+ impact: "serious",
2274
+ message: a ? "Document <title> element is empty." : "Document is missing a <title> element.",
2275
+ context: r
2276
+ }];
2277
+ }
2278
+ return [];
2250
2279
  }
2251
- }, Qt = {
2280
+ }, Jt = {
2252
2281
  id: "bypass",
2253
2282
  wcag: ["2.4.1"],
2254
2283
  level: "A",
2255
2284
  description: "Page must have a mechanism to bypass repeated blocks of content.",
2256
2285
  guidance: 'Keyboard users must be able to skip repetitive content like navigation. Provide a skip link at the top of the page that links to the main content (e.g., <a href="#main">Skip to main content</a>), or use a <main> landmark. Screen readers can jump directly to landmarks, so a properly marked-up <main> element satisfies this requirement.',
2257
- prompt: "Explain whether to add a skip link or a <main> landmark based on the page structure.",
2286
+ prompt: 'The page has no mechanism for keyboard users to skip repeated content. The simplest fix is to wrap the primary content area in a <main> element — screen readers can jump directly to it. Alternatively, add a skip link as the first element in <body>: <a href="#main" class="skip-link">Skip to main content</a>, with a matching id on the target element. Use the context to understand what the page is missing.',
2258
2287
  run(t) {
2259
2288
  if (t.querySelector(
2260
2289
  'main, [role="main"], nav, [role="navigation"], aside, [role="complementary"], header, [role="banner"], footer, [role="contentinfo"], [role="search"], [role="region"]'
2261
2290
  )) return [];
2262
2291
  const e = t.querySelector('a[href^="#"]');
2263
2292
  if (e) {
2264
- const n = e.getAttribute("href");
2265
- if (n && n.length > 1) {
2266
- const r = n.slice(1);
2267
- if (t.getElementById(r)) return [];
2293
+ const r = e.getAttribute("href");
2294
+ if (r && r.length > 1) {
2295
+ const o = r.slice(1);
2296
+ if (t.getElementById(o)) return [];
2268
2297
  }
2269
2298
  }
2270
- return t.querySelector("h1, h2, h3, [role='heading']") ? [] : [{
2299
+ if (t.querySelector("h1, h2, h3, [role='heading']")) return [];
2300
+ const n = [];
2301
+ return n.push("no landmarks (<main>, <nav>, <header>, <footer>)"), n.push("no skip link"), n.push("no headings"), [{
2271
2302
  ruleId: "bypass",
2272
2303
  selector: "html",
2273
2304
  html: "<html>",
2274
2305
  impact: "serious",
2275
- message: "Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link."
2306
+ message: "Page has no mechanism to bypass repeated content. Add a <main> landmark or skip link.",
2307
+ context: `Missing: ${n.join(", ")}`
2276
2308
  }];
2277
2309
  }
2278
- }, Jt = {
2310
+ }, Qt = {
2279
2311
  id: "page-has-heading-one",
2280
2312
  wcag: [],
2281
2313
  level: "A",
2282
2314
  tags: ["best-practice"],
2283
2315
  description: "Page should contain a level-one heading.",
2284
2316
  guidance: "A level-one heading (<h1> or role='heading' with aria-level='1') helps users understand the page topic and provides a landmark for screen reader navigation. Each page should have exactly one h1 that describes the main content, typically matching or similar to the page title.",
2285
- prompt: "Suggest appropriate h1 text based on the page's visible content.",
2317
+ prompt: "The page has no <h1> heading. Suggest appropriate h1 text based on the page title or content sample in context. The h1 should describe the page's main topic and typically be placed at the start of the main content area.",
2286
2318
  run(t) {
2319
+ var o, s, l;
2287
2320
  const a = t.querySelector("h1");
2288
2321
  if (a && v(a)) return [];
2289
2322
  const e = t.querySelectorAll('[role="heading"][aria-level="1"]');
2290
- for (const i of e)
2291
- if (v(i)) return [];
2323
+ for (const h of e)
2324
+ if (v(h)) return [];
2325
+ const i = [], n = (s = (o = t.querySelector("title")) == null ? void 0 : o.textContent) == null ? void 0 : s.trim();
2326
+ n && i.push(`Page title: "${n}"`);
2327
+ const r = t.querySelector("main");
2328
+ if (r) {
2329
+ const h = ((l = r.textContent) == null ? void 0 : l.trim().replace(/\s+/g, " ")) || "";
2330
+ h && i.push(`Main content: "${h.slice(0, 100)}"`);
2331
+ }
2292
2332
  return [{
2293
2333
  ruleId: "page-has-heading-one",
2294
2334
  selector: "html",
2295
2335
  html: "<html>",
2296
2336
  impact: "moderate",
2297
- message: "Page does not contain a level-one heading."
2337
+ message: "Page does not contain a level-one heading.",
2338
+ context: i.length > 0 ? i.join(", ") : void 0
2298
2339
  }];
2299
2340
  }
2300
2341
  };
@@ -2310,18 +2351,22 @@ const Zt = {
2310
2351
  level: "A",
2311
2352
  description: "Frames must have an accessible name.",
2312
2353
  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'.",
2313
- prompt: "Suggest a descriptive title based on the frame's src URL or visible content.",
2354
+ prompt: "This iframe has no accessible name. Based on the src URL in context, suggest a descriptive title attribute that tells screen reader users what the frame contains. For example: 'YouTube video player', 'Google Map', 'Payment form', 'Chat widget'. If the frame appears decorative or non-essential, recommend adding aria-hidden='true' instead.",
2314
2355
  run(t) {
2315
2356
  const a = [];
2316
2357
  for (const e of t.querySelectorAll("iframe, frame")) {
2317
2358
  if (p(e) || me(e)) continue;
2318
- v(e) || a.push({
2319
- ruleId: "frame-title",
2320
- selector: u(e),
2321
- html: c(e),
2322
- impact: "serious",
2323
- message: "Frame is missing an accessible name. Add a title attribute."
2324
- });
2359
+ if (!v(e)) {
2360
+ const n = e.getAttribute("src");
2361
+ a.push({
2362
+ ruleId: "frame-title",
2363
+ selector: m(e),
2364
+ html: u(e),
2365
+ impact: "serious",
2366
+ message: "Frame is missing an accessible name. Add a title attribute.",
2367
+ context: n ? `src: "${n}"` : void 0
2368
+ });
2369
+ }
2325
2370
  }
2326
2371
  return a;
2327
2372
  }
@@ -2349,8 +2394,8 @@ const Zt = {
2349
2394
  for (const o of r.slice(1))
2350
2395
  a.push({
2351
2396
  ruleId: "frame-title-unique",
2352
- selector: u(o),
2353
- html: c(o),
2397
+ selector: m(o),
2398
+ html: u(o),
2354
2399
  impact: "moderate",
2355
2400
  message: "Frame title is not unique. Use a distinct title for each frame."
2356
2401
  });
@@ -2363,17 +2408,27 @@ const Zt = {
2363
2408
  tags: ["best-practice"],
2364
2409
  description: "Headings must have discernible text.",
2365
2410
  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.",
2366
- prompt: "Suggest appropriate heading text or explain why to use a different element.",
2411
+ prompt: "This heading element has no text content, so screen reader users encounter a blank heading when navigating. Either add descriptive text that summarizes the following section, or if this element is used only for visual styling, replace it with a styled <p> or <div> and use CSS for appearance. The context includes nearby content to help suggest appropriate heading text.",
2367
2412
  run(t) {
2413
+ var i;
2368
2414
  const a = [], e = t.querySelectorAll('h1, h2, h3, h4, h5, h6, [role="heading"]');
2369
- for (const i of e)
2370
- p(i) || v(i) || a.push({
2371
- ruleId: "empty-heading",
2372
- selector: u(i),
2373
- html: c(i),
2374
- impact: "minor",
2375
- message: "Heading is empty. Add text content or remove the heading element."
2376
- });
2415
+ for (const n of e)
2416
+ if (!p(n) && !v(n)) {
2417
+ let r;
2418
+ const o = n.nextElementSibling;
2419
+ if (o) {
2420
+ const s = ((i = o.textContent) == null ? void 0 : i.trim().replace(/\s+/g, " ")) || "";
2421
+ s && (r = s.slice(0, 100));
2422
+ }
2423
+ a.push({
2424
+ ruleId: "empty-heading",
2425
+ selector: m(n),
2426
+ html: u(n),
2427
+ impact: "minor",
2428
+ message: "Heading is empty. Add text content or remove the heading element.",
2429
+ context: r ? `Following content: "${r}"` : void 0
2430
+ });
2431
+ }
2377
2432
  return a;
2378
2433
  }
2379
2434
  }, aa = {
@@ -2382,28 +2437,29 @@ const Zt = {
2382
2437
  level: "AA",
2383
2438
  description: "Viewport meta tag must not disable user scaling.",
2384
2439
  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.",
2385
- prompt: "Explain which viewport restrictions to remove and show the corrected meta tag.",
2440
+ prompt: "The viewport meta tag restricts zooming, which prevents low-vision users from enlarging content. Show the current content attribute and a corrected version with the problematic properties removed. Keep other viewport properties (like width=device-width, initial-scale=1) intact — only remove user-scalable=no and maximum-scale restrictions.",
2386
2441
  run(t) {
2387
- var r;
2388
2442
  const a = [], e = t.querySelector('meta[name="viewport"]');
2389
2443
  if (!e) return [];
2390
- const i = ((r = e.getAttribute("content")) == null ? void 0 : r.toLowerCase()) || "";
2391
- (/user-scalable\s*=\s*no/i.test(i) || /user-scalable\s*=\s*0/i.test(i)) && a.push({
2444
+ const i = e.getAttribute("content") || "", n = i.toLowerCase();
2445
+ (/user-scalable\s*=\s*no/i.test(n) || /user-scalable\s*=\s*0/i.test(n)) && a.push({
2392
2446
  ruleId: "meta-viewport",
2393
- selector: u(e),
2394
- html: c(e),
2447
+ selector: m(e),
2448
+ html: u(e),
2395
2449
  impact: "critical",
2396
- message: "Viewport disables user scaling. Remove user-scalable=no."
2450
+ message: "Viewport disables user scaling. Remove user-scalable=no.",
2451
+ context: `content: "${i}"`
2397
2452
  });
2398
- const n = i.match(/maximum-scale\s*=\s*([\d.]+|yes)/i);
2399
- if (n) {
2400
- const o = n[1], s = o.toLowerCase() === "yes" ? 1 : parseFloat(o);
2453
+ const r = n.match(/maximum-scale\s*=\s*([\d.]+|yes)/i);
2454
+ if (r) {
2455
+ const o = r[1], s = o.toLowerCase() === "yes" ? 1 : parseFloat(o);
2401
2456
  s < 2 && a.push({
2402
2457
  ruleId: "meta-viewport",
2403
- selector: u(e),
2404
- html: c(e),
2458
+ selector: m(e),
2459
+ html: u(e),
2405
2460
  impact: "critical",
2406
- message: `Viewport maximum-scale=${s} restricts zooming. Set to at least 2 or remove.`
2461
+ message: `Viewport maximum-scale=${s} restricts zooming. Set to at least 2 or remove.`,
2462
+ context: `content: "${i}"`
2407
2463
  });
2408
2464
  }
2409
2465
  return a;
@@ -2423,16 +2479,16 @@ const Zt = {
2423
2479
  if (/^\d+\s*[;,]\s*url\s*=/i.test(e) || /^\d+\s*[;,]\s*['"]?\s*https?:/i.test(e))
2424
2480
  return n > 0 && n <= 72e3 ? [{
2425
2481
  ruleId: "meta-refresh",
2426
- selector: u(a),
2427
- html: c(a),
2482
+ selector: m(a),
2483
+ html: u(a),
2428
2484
  impact: "critical",
2429
2485
  message: `Page redirects after ${n} seconds without warning. Use server-side redirect.`
2430
2486
  }] : [];
2431
2487
  if (n > 0 && n <= 72e3)
2432
2488
  return [{
2433
2489
  ruleId: "meta-refresh",
2434
- selector: u(a),
2435
- html: c(a),
2490
+ selector: m(a),
2491
+ html: u(a),
2436
2492
  impact: "critical",
2437
2493
  message: `Page auto-refreshes after ${n} seconds. Provide user control over refresh.`
2438
2494
  }];
@@ -2474,13 +2530,13 @@ const Zt = {
2474
2530
  const a = [];
2475
2531
  for (const n of t.querySelectorAll("p")) {
2476
2532
  if (p(n)) continue;
2477
- const r = n.getAttribute("style") || "", o = /font-weight\s*:\s*(bold|[6-9]00)/i.test(r), s = /font-size\s*:\s*(\d+)\s*(px|em|rem)/i.test(r), d = ((e = n.className) == null ? void 0 : e.toLowerCase()) || "", h = /\bh[1-6]\b|\bheading\b/.test(d), l = ((i = n.textContent) == null ? void 0 : i.trim()) || "", m = l.length > 0 && l.length < 50, g = !l.match(/[.!?,;:]$/);
2478
- if ((o && s || o && h) && m && g) {
2479
- const w = n.nextElementSibling;
2480
- w && (w.tagName === "P" || w.tagName === "DIV" || w.tagName === "UL") && a.push({
2533
+ const r = n.getAttribute("style") || "", o = /font-weight\s*:\s*(bold|[6-9]00)/i.test(r), s = /font-size\s*:\s*(\d+)\s*(px|em|rem)/i.test(r), l = ((e = n.className) == null ? void 0 : e.toLowerCase()) || "", h = /\bh[1-6]\b|\bheading\b/.test(l), c = ((i = n.textContent) == null ? void 0 : i.trim()) || "", d = c.length > 0 && c.length < 50, g = !c.match(/[.!?,;:]$/);
2534
+ if ((o && s || o && h) && d && g) {
2535
+ const y = n.nextElementSibling;
2536
+ y && (y.tagName === "P" || y.tagName === "DIV" || y.tagName === "UL") && a.push({
2481
2537
  ruleId: "p-as-heading",
2482
- selector: u(n),
2483
- html: c(n),
2538
+ selector: m(n),
2539
+ html: u(n),
2484
2540
  impact: "serious",
2485
2541
  message: "Paragraph appears to be styled as a heading. Use an h1-h6 element instead."
2486
2542
  });
@@ -2499,10 +2555,10 @@ const Zt = {
2499
2555
  const a = [];
2500
2556
  for (const e of t.querySelectorAll("[role]")) {
2501
2557
  const r = e.getAttribute("role").replace(/[\u201C\u201D\u2018\u2019\u00AB\u00BB]/g, "").split(/\s+/).filter(Boolean);
2502
- !r.some((s) => Se(s)) && r.length > 0 && a.push({
2558
+ !r.some((s) => xe(s)) && r.length > 0 && a.push({
2503
2559
  ruleId: "aria-roles",
2504
- selector: u(e),
2505
- html: c(e),
2560
+ selector: m(e),
2561
+ html: u(e),
2506
2562
  impact: "critical",
2507
2563
  message: `Invalid ARIA role "${r[0]}".`
2508
2564
  });
@@ -2565,8 +2621,8 @@ const Zt = {
2565
2621
  if (!e.hasAttribute(r)) {
2566
2622
  a.push({
2567
2623
  ruleId: "aria-required-attr",
2568
- selector: u(e),
2569
- html: c(e),
2624
+ selector: m(e),
2625
+ html: u(e),
2570
2626
  impact: "critical",
2571
2627
  message: `Role "${i}" requires attribute "${r}".`
2572
2628
  });
@@ -2584,13 +2640,13 @@ function pa(t) {
2584
2640
  e && typeof e == "string" && e.trim() && a.push(`Classes: ${e.trim().slice(0, 100)}`);
2585
2641
  const i = t.closest("form");
2586
2642
  if (i) {
2587
- const d = i.getAttribute("aria-label") || ((o = (r = i.querySelector("legend")) == null ? void 0 : r.textContent) == null ? void 0 : o.trim());
2588
- d && a.push(`Form: ${d.slice(0, 60)}`);
2643
+ const l = i.getAttribute("aria-label") || ((o = (r = i.querySelector("legend")) == null ? void 0 : r.textContent) == null ? void 0 : o.trim());
2644
+ l && a.push(`Form: ${l.slice(0, 60)}`);
2589
2645
  }
2590
2646
  const n = t.parentElement;
2591
2647
  if (n) {
2592
- const d = n.closest("h1, h2, h3, h4, h5, h6") || n.querySelector("h1, h2, h3, h4, h5, h6");
2593
- (s = d == null ? void 0 : d.textContent) != null && s.trim() && a.push(`Nearby heading: ${d.textContent.trim().slice(0, 60)}`);
2648
+ const l = n.closest("h1, h2, h3, h4, h5, h6") || n.querySelector("h1, h2, h3, h4, h5, h6");
2649
+ (s = l == null ? void 0 : l.textContent) != null && s.trim() && a.push(`Nearby heading: ${l.textContent.trim().slice(0, 60)}`);
2594
2650
  }
2595
2651
  return a.length > 0 ? a.join(`
2596
2652
  `) : void 0;
@@ -2610,8 +2666,8 @@ const ga = {
2610
2666
  if ((i === "none" || i === "presentation") && !(e.matches('button:not([disabled]), [tabindex]:not([tabindex="-1"])') || e.tagName.toLowerCase() === "button" && !e.disabled) || e.getRootNode() instanceof ShadowRoot) continue;
2611
2667
  v(e) || a.push({
2612
2668
  ruleId: "button-name",
2613
- selector: u(e),
2614
- html: c(e),
2669
+ selector: m(e),
2670
+ html: u(e),
2615
2671
  impact: "critical",
2616
2672
  message: "Button has no discernible text.",
2617
2673
  context: pa(e)
@@ -2721,27 +2777,31 @@ const ga = {
2721
2777
  level: "A",
2722
2778
  description: "ARIA attributes must be allowed for the element's role.",
2723
2779
  guidance: "Each ARIA role supports specific attributes. Using unsupported attributes creates confusion for assistive technologies. Check the ARIA specification for which attributes are valid for each role, or remove the attribute if it's not needed.",
2724
- prompt: "State which attribute is not allowed on this role and recommend removing it or using a different element/role that supports the attribute.",
2780
+ prompt: "The ARIA attribute listed in context is not supported on this element's role. Either remove the attribute (if the behavior it describes isn't needed), or change the element's role to one that supports it. The context lists which attributes ARE allowed on this role — use that to suggest alternatives if applicable.",
2725
2781
  run(t) {
2726
2782
  const a = [];
2727
2783
  for (const e of t.querySelectorAll("[role], [aria-*]")) {
2728
2784
  if (p(e)) continue;
2729
- const i = C(e);
2785
+ const i = L(e);
2730
2786
  if (!i) continue;
2731
2787
  const n = ba[i];
2732
2788
  if (n)
2733
- for (const r of e.attributes)
2734
- r.name.startsWith("aria-") && (fa.has(r.name) || n.has(r.name) || a.push({
2789
+ for (const r of e.attributes) {
2790
+ if (!r.name.startsWith("aria-") || fa.has(r.name) || n.has(r.name)) continue;
2791
+ const o = n.size > 0 ? [...n].join(", ") : "none (only global ARIA attributes)";
2792
+ a.push({
2735
2793
  ruleId: "aria-allowed-attr",
2736
- selector: u(e),
2737
- html: c(e),
2794
+ selector: m(e),
2795
+ html: u(e),
2738
2796
  impact: "critical",
2739
- message: `ARIA attribute "${r.name}" is not allowed on role "${i}".`
2740
- }));
2797
+ message: `ARIA attribute "${r.name}" is not allowed on role "${i}".`,
2798
+ context: `Attribute: ${r.name}="${r.value}", role: ${i}, allowed role-specific attributes: ${o}`
2799
+ });
2800
+ }
2741
2801
  }
2742
2802
  return a;
2743
2803
  }
2744
- }, wa = /* @__PURE__ */ new Set([
2804
+ }, ya = /* @__PURE__ */ new Set([
2745
2805
  "base",
2746
2806
  "col",
2747
2807
  "colgroup",
@@ -2756,7 +2816,7 @@ const ga = {
2756
2816
  "template",
2757
2817
  "title",
2758
2818
  "track"
2759
- ]), E = {
2819
+ ]), T = {
2760
2820
  a: /* @__PURE__ */ new Set(["button", "checkbox", "menuitem", "menuitemcheckbox", "menuitemradio", "option", "radio", "switch", "tab", "treeitem", "link"]),
2761
2821
  "a[href]": /* @__PURE__ */ new Set(["button", "checkbox", "menuitem", "menuitemcheckbox", "menuitemradio", "option", "radio", "switch", "tab", "treeitem"]),
2762
2822
  abbr: "any",
@@ -2863,20 +2923,20 @@ const ga = {
2863
2923
  video: /* @__PURE__ */ new Set(["application"]),
2864
2924
  wbr: /* @__PURE__ */ new Set(["none", "presentation"])
2865
2925
  };
2866
- function ya(t) {
2926
+ function wa(t) {
2867
2927
  var e;
2868
2928
  const a = t.tagName.toLowerCase();
2869
- if (wa.has(a))
2929
+ if (ya.has(a))
2870
2930
  return "none";
2871
2931
  if (a === "a" && t.hasAttribute("href"))
2872
- return E["a[href]"];
2932
+ return T["a[href]"];
2873
2933
  if (a === "img" && t.getAttribute("alt") === "")
2874
- return E["img[alt='']"];
2934
+ return T["img[alt='']"];
2875
2935
  if (a === "input") {
2876
2936
  const n = `input[type=${((e = t.getAttribute("type")) == null ? void 0 : e.toLowerCase()) || "text"}]`;
2877
- return n in E ? E[n] : "none";
2937
+ return n in T ? T[n] : "none";
2878
2938
  }
2879
- return E[a] || "any";
2939
+ return T[a] || "any";
2880
2940
  }
2881
2941
  const Aa = {
2882
2942
  id: "aria-allowed-role",
@@ -2894,17 +2954,17 @@ const Aa = {
2894
2954
  if (!n) continue;
2895
2955
  const r = ue(i);
2896
2956
  if (r && n === r) continue;
2897
- const o = ya(i);
2957
+ const o = wa(i);
2898
2958
  o === "none" ? a.push({
2899
2959
  ruleId: "aria-allowed-role",
2900
- selector: u(i),
2901
- html: c(i),
2960
+ selector: m(i),
2961
+ html: u(i),
2902
2962
  impact: "minor",
2903
2963
  message: `Element <${i.tagName.toLowerCase()}> should not have an explicit role.`
2904
2964
  }) : o !== "any" && !o.has(n) && a.push({
2905
2965
  ruleId: "aria-allowed-role",
2906
- selector: u(i),
2907
- html: c(i),
2966
+ selector: m(i),
2967
+ html: u(i),
2908
2968
  impact: "minor",
2909
2969
  message: `Role "${n}" is not allowed on element <${i.tagName.toLowerCase()}>.`
2910
2970
  });
@@ -2942,25 +3002,25 @@ const Aa = {
2942
3002
  tab: ["tablist"],
2943
3003
  treeitem: ["tree", "group"]
2944
3004
  };
2945
- function Sa(t, a) {
3005
+ function xa(t, a) {
2946
3006
  var r;
2947
3007
  const e = ((r = t.getAttribute("aria-owns")) == null ? void 0 : r.split(/\s+/)) || [], i = t.ownerDocument, n = /* @__PURE__ */ new Set();
2948
3008
  for (const o of t.querySelectorAll("*")) {
2949
- const s = C(o);
3009
+ const s = L(o);
2950
3010
  s && !p(o) && n.add(s);
2951
3011
  }
2952
3012
  for (const o of e) {
2953
3013
  const s = i.getElementById(o);
2954
3014
  if (s) {
2955
- const d = C(s);
2956
- d && !p(s) && n.add(d);
3015
+ const l = L(s);
3016
+ l && !p(s) && n.add(l);
2957
3017
  }
2958
3018
  }
2959
3019
  for (const o of a)
2960
- if (!o.some((d) => n.has(d))) return !1;
3020
+ if (!o.some((l) => n.has(l))) return !1;
2961
3021
  return !0;
2962
3022
  }
2963
- const xa = {
3023
+ const Sa = {
2964
3024
  id: "aria-required-children",
2965
3025
  wcag: ["4.1.2"],
2966
3026
  level: "A",
@@ -2975,12 +3035,12 @@ const xa = {
2975
3035
  const n = (e = i.getAttribute("role")) == null ? void 0 : e.trim().toLowerCase();
2976
3036
  if (!n || !(n in ae)) continue;
2977
3037
  const r = ae[n];
2978
- if (!Sa(i, r)) {
3038
+ if (!xa(i, r)) {
2979
3039
  const o = r.map((s) => s.join(" or ")).join(", ");
2980
3040
  a.push({
2981
3041
  ruleId: "aria-required-children",
2982
- selector: u(i),
2983
- html: c(i),
3042
+ selector: m(i),
3043
+ html: u(i),
2984
3044
  impact: "critical",
2985
3045
  message: `Role "${n}" requires children with role: ${o}.`
2986
3046
  });
@@ -3005,8 +3065,8 @@ const xa = {
3005
3065
  const r = ie[n];
3006
3066
  let o = i.parentElement, s = !1;
3007
3067
  for (; o && o !== t.documentElement; ) {
3008
- const d = C(o);
3009
- if (d && r.includes(d)) {
3068
+ const l = L(o);
3069
+ if (l && r.includes(l)) {
3010
3070
  s = !0;
3011
3071
  break;
3012
3072
  }
@@ -3014,8 +3074,8 @@ const xa = {
3014
3074
  }
3015
3075
  s || a.push({
3016
3076
  ruleId: "aria-required-parent",
3017
- selector: u(i),
3018
- html: c(i),
3077
+ selector: m(i),
3078
+ html: u(i),
3019
3079
  impact: "critical",
3020
3080
  message: `Role "${n}" must be contained within: ${r.join(", ")}.`
3021
3081
  });
@@ -3051,7 +3111,7 @@ function Ia(t) {
3051
3111
  }
3052
3112
  return !0;
3053
3113
  }
3054
- const Ea = {
3114
+ const Ta = {
3055
3115
  id: "aria-hidden-body",
3056
3116
  selector: 'body[aria-hidden="true"]',
3057
3117
  check: { type: "selector-exists" },
@@ -3063,13 +3123,13 @@ const Ea = {
3063
3123
  guidance: "Setting aria-hidden='true' on the body element hides all page content from assistive technologies, making the page completely inaccessible to screen reader users. Remove aria-hidden from the body element. If you need to hide content temporarily (e.g., behind a modal), use aria-hidden on specific sections instead.",
3064
3124
  prompt: "Instruct to remove aria-hidden='true' from the body element.",
3065
3125
  skipAriaHidden: !1
3066
- }, Ta = k(Ea), La = {
3126
+ }, Ea = k(Ta), Ca = {
3067
3127
  id: "aria-hidden-focus",
3068
3128
  wcag: ["4.1.2"],
3069
3129
  level: "A",
3070
3130
  description: "Elements with aria-hidden='true' must not contain focusable elements.",
3071
3131
  guidance: "When aria-hidden='true' hides an element from assistive technologies but the element contains focusable children, keyboard users can focus those children but screen reader users won't know they exist. Either remove focusable elements from the hidden region, add tabindex='-1' to them, or remove aria-hidden.",
3072
- prompt: "Suggest adding tabindex='-1' to this focusable element, or moving it outside the aria-hidden region, or removing aria-hidden from the ancestor.",
3132
+ prompt: "This element can receive keyboard focus but is inside an aria-hidden region, making it invisible to screen readers. The context explains why it's focusable. Fix by either: (1) adding tabindex='-1' to remove it from tab order, (2) moving it outside the aria-hidden region, or (3) removing aria-hidden='true' from the ancestor if the content should be accessible.",
3073
3133
  run(t) {
3074
3134
  const a = [];
3075
3135
  for (const e of t.querySelectorAll('[aria-hidden="true"]')) {
@@ -3078,19 +3138,25 @@ const Ea = {
3078
3138
  e.matches(ne) && i.push(e);
3079
3139
  for (const n of i)
3080
3140
  if (n instanceof HTMLElement) {
3081
- if (n.getAttribute("tabindex") === "-1" || n.disabled || n instanceof HTMLInputElement && n.type === "hidden" || !Ia(n)) continue;
3141
+ const r = n.getAttribute("tabindex");
3142
+ if (r === "-1" || n.disabled || n instanceof HTMLInputElement && n.type === "hidden" || !Ia(n)) continue;
3143
+ const o = n.tagName.toLowerCase();
3144
+ let s;
3145
+ r !== null ? s = `has tabindex="${r}"` : o === "a" && n.hasAttribute("href") ? s = "is a link with href" : o === "button" ? s = "is a <button>" : o === "input" ? s = `is an <input type="${n.type}">` : o === "select" ? s = "is a <select>" : o === "textarea" ? s = "is a <textarea>" : o === "iframe" ? s = "is an <iframe>" : s = `is a natively focusable <${o}>`;
3146
+ const l = n === e ? n : n.closest('[aria-hidden="true"]');
3082
3147
  a.push({
3083
3148
  ruleId: "aria-hidden-focus",
3084
- selector: u(n),
3085
- html: c(n),
3149
+ selector: m(n),
3150
+ html: u(n),
3086
3151
  impact: "serious",
3087
- message: "Focusable element is inside an aria-hidden region."
3152
+ message: "Focusable element is inside an aria-hidden region.",
3153
+ context: `Focusable because: ${s}. aria-hidden ancestor: ${l ? u(l) : "unknown"}`
3088
3154
  });
3089
3155
  }
3090
3156
  }
3091
3157
  return a;
3092
3158
  }
3093
- }, Ca = {
3159
+ }, La = {
3094
3160
  id: "aria-command-name",
3095
3161
  wcag: ["4.1.2"],
3096
3162
  level: "A",
@@ -3107,8 +3173,8 @@ const Ea = {
3107
3173
  if ((e = r == null ? void 0 : r.getAttribute("alt")) != null && e.trim()) continue;
3108
3174
  a.push({
3109
3175
  ruleId: "aria-command-name",
3110
- selector: u(i),
3111
- html: c(i),
3176
+ selector: m(i),
3177
+ html: u(i),
3112
3178
  impact: "serious",
3113
3179
  message: "ARIA command has no accessible name."
3114
3180
  });
@@ -3129,8 +3195,8 @@ const Ea = {
3129
3195
  if (p(i) || i.matches("input, select, textarea")) continue;
3130
3196
  v(i) || a.push({
3131
3197
  ruleId: "aria-input-field-name",
3132
- selector: u(i),
3133
- html: c(i),
3198
+ selector: m(i),
3199
+ html: u(i),
3134
3200
  impact: "serious",
3135
3201
  message: "ARIA input field has no accessible name."
3136
3202
  });
@@ -3150,8 +3216,8 @@ const Ea = {
3150
3216
  if (p(i) || i.matches('input[type="checkbox"], input[type="radio"]')) continue;
3151
3217
  v(i) || a.push({
3152
3218
  ruleId: "aria-toggle-field-name",
3153
- selector: u(i),
3154
- html: c(i),
3219
+ selector: m(i),
3220
+ html: u(i),
3155
3221
  impact: "serious",
3156
3222
  message: "ARIA toggle field has no accessible name."
3157
3223
  });
@@ -3171,15 +3237,15 @@ const Ea = {
3171
3237
  if (p(e)) continue;
3172
3238
  v(e) || a.push({
3173
3239
  ruleId: "aria-meter-name",
3174
- selector: u(e),
3175
- html: c(e),
3240
+ selector: m(e),
3241
+ html: u(e),
3176
3242
  impact: "serious",
3177
3243
  message: "Meter has no accessible name."
3178
3244
  });
3179
3245
  }
3180
3246
  return a;
3181
3247
  }
3182
- }, Ma = {
3248
+ }, $a = {
3183
3249
  id: "aria-progressbar-name",
3184
3250
  wcag: ["4.1.2"],
3185
3251
  level: "A",
@@ -3192,15 +3258,15 @@ const Ea = {
3192
3258
  if (p(e)) continue;
3193
3259
  v(e) || a.push({
3194
3260
  ruleId: "aria-progressbar-name",
3195
- selector: u(e),
3196
- html: c(e),
3261
+ selector: m(e),
3262
+ html: u(e),
3197
3263
  impact: "serious",
3198
3264
  message: "Progressbar has no accessible name."
3199
3265
  });
3200
3266
  }
3201
3267
  return a;
3202
3268
  }
3203
- }, $a = {
3269
+ }, Ma = {
3204
3270
  id: "aria-dialog-name",
3205
3271
  wcag: ["4.1.2"],
3206
3272
  level: "A",
@@ -3213,8 +3279,8 @@ const Ea = {
3213
3279
  if (p(e)) continue;
3214
3280
  v(e) || a.push({
3215
3281
  ruleId: "aria-dialog-name",
3216
- selector: u(e),
3217
- html: c(e),
3282
+ selector: m(e),
3283
+ html: u(e),
3218
3284
  impact: "serious",
3219
3285
  message: "Dialog has no accessible name."
3220
3286
  });
@@ -3234,8 +3300,8 @@ const Ea = {
3234
3300
  if (p(e)) continue;
3235
3301
  v(e) || a.push({
3236
3302
  ruleId: "aria-tooltip-name",
3237
- selector: u(e),
3238
- html: c(e),
3303
+ selector: m(e),
3304
+ html: u(e),
3239
3305
  impact: "serious",
3240
3306
  message: "Tooltip has no accessible name."
3241
3307
  });
@@ -3255,8 +3321,8 @@ const Ea = {
3255
3321
  if (p(e)) continue;
3256
3322
  v(e) || a.push({
3257
3323
  ruleId: "aria-treeitem-name",
3258
- selector: u(e),
3259
- html: c(e),
3324
+ selector: m(e),
3325
+ html: u(e),
3260
3326
  impact: "serious",
3261
3327
  message: "Treeitem has no accessible name."
3262
3328
  });
@@ -3273,7 +3339,7 @@ const Ea = {
3273
3339
  run(t) {
3274
3340
  return z(t).prohibitedAttr;
3275
3341
  }
3276
- }, Wa = [
3342
+ }, Fa = [
3277
3343
  "a[href]",
3278
3344
  "button:not([disabled])",
3279
3345
  'input:not([disabled]):not([type="hidden"])',
@@ -3297,7 +3363,7 @@ const Ea = {
3297
3363
  ];
3298
3364
  function re(t) {
3299
3365
  const a = [];
3300
- t.matches(Wa) && a.push("element is focusable");
3366
+ t.matches(Fa) && a.push("element is focusable");
3301
3367
  for (const e of Ba)
3302
3368
  if (t.hasAttribute(e)) {
3303
3369
  a.push(`has ${e}`);
@@ -3305,7 +3371,7 @@ function re(t) {
3305
3371
  }
3306
3372
  return (t.hasAttribute("aria-label") || t.hasAttribute("aria-labelledby")) && a.push("has accessible name"), a;
3307
3373
  }
3308
- const Fa = {
3374
+ const Wa = {
3309
3375
  id: "presentation-role-conflict",
3310
3376
  wcag: ["4.1.2"],
3311
3377
  level: "A",
@@ -3319,8 +3385,8 @@ const Fa = {
3319
3385
  const i = re(e);
3320
3386
  i.length > 0 && a.push({
3321
3387
  ruleId: "presentation-role-conflict",
3322
- selector: u(e),
3323
- html: c(e),
3388
+ selector: m(e),
3389
+ html: u(e),
3324
3390
  impact: "serious",
3325
3391
  message: `Presentation role conflicts with: ${i.join(", ")}. The role will be ignored.`
3326
3392
  });
@@ -3330,8 +3396,8 @@ const Fa = {
3330
3396
  const i = re(e);
3331
3397
  i.length > 0 && a.push({
3332
3398
  ruleId: "presentation-role-conflict",
3333
- selector: u(e),
3334
- html: c(e),
3399
+ selector: m(e),
3400
+ html: u(e),
3335
3401
  impact: "serious",
3336
3402
  message: `Element with implicit presentation role (alt="") conflicts with: ${i.join(", ")}. The decorative role will be ignored.`
3337
3403
  });
@@ -3351,8 +3417,8 @@ const Fa = {
3351
3417
  if (p(e)) continue;
3352
3418
  v(e) || a.push({
3353
3419
  ruleId: "summary-name",
3354
- selector: u(e),
3355
- html: c(e),
3420
+ selector: m(e),
3421
+ html: u(e),
3356
3422
  impact: "serious",
3357
3423
  message: "<summary> element has no accessible name. Add descriptive text."
3358
3424
  });
@@ -3360,7 +3426,7 @@ const Fa = {
3360
3426
  return a;
3361
3427
  }
3362
3428
  };
3363
- function ja(t) {
3429
+ function Pa(t) {
3364
3430
  var n, r;
3365
3431
  const a = [], e = t.getAttribute("href");
3366
3432
  e && a.push(`href: ${e}`);
@@ -3377,7 +3443,7 @@ function ja(t) {
3377
3443
  return a.length > 0 ? a.join(`
3378
3444
  `) : void 0;
3379
3445
  }
3380
- const Pa = {
3446
+ const ja = {
3381
3447
  id: "link-name",
3382
3448
  wcag: ["2.4.4", "4.1.2"],
3383
3449
  level: "A",
@@ -3390,11 +3456,11 @@ const Pa = {
3390
3456
  if (p(e)) continue;
3391
3457
  v(e) || a.push({
3392
3458
  ruleId: "link-name",
3393
- selector: u(e),
3394
- html: c(e),
3459
+ selector: m(e),
3460
+ html: u(e),
3395
3461
  impact: "serious",
3396
3462
  message: "Link has no discernible text.",
3397
- context: ja(e)
3463
+ context: Pa(e)
3398
3464
  });
3399
3465
  }
3400
3466
  return a;
@@ -3412,13 +3478,13 @@ const Pa = {
3412
3478
  for (const i of e) {
3413
3479
  const n = i.getAttribute("href");
3414
3480
  if (!n || n === "#") continue;
3415
- const r = y(i).toLowerCase();
3481
+ const r = w(i).toLowerCase();
3416
3482
  if (!(r.includes("skip") || r.includes("jump") || r.includes("main content") || r.includes("navigation"))) continue;
3417
3483
  const s = n.slice(1);
3418
3484
  t.getElementById(s) || a.push({
3419
3485
  ruleId: "skip-link",
3420
- selector: u(i),
3421
- html: c(i),
3486
+ selector: m(i),
3487
+ html: u(i),
3422
3488
  impact: "moderate",
3423
3489
  message: `Skip link points to "#${s}" which does not exist on the page.`
3424
3490
  });
@@ -3454,20 +3520,20 @@ function Xa(t) {
3454
3520
  t,
3455
3521
  NodeFilter.SHOW_TEXT
3456
3522
  );
3457
- let e;
3458
- for (; e = a.nextNode(); ) {
3459
- if (!e.data.trim()) continue;
3460
- let i = e.parentElement, n = !1;
3461
- for (; i && i !== t; ) {
3462
- if (i.tagName === "A") {
3463
- n = !0;
3523
+ let e = "", i;
3524
+ for (; i = a.nextNode(); ) {
3525
+ if (!i.data.trim()) continue;
3526
+ let n = i.parentElement, r = !1;
3527
+ for (; n && n !== t; ) {
3528
+ if (n.tagName === "A") {
3529
+ r = !0;
3464
3530
  break;
3465
3531
  }
3466
- i = i.parentElement;
3532
+ n = n.parentElement;
3467
3533
  }
3468
- if (!n) return !0;
3534
+ r || (e += i.data);
3469
3535
  }
3470
- return !1;
3536
+ return /[a-zA-Z\u00C0-\u024F]{2,}/.test(e);
3471
3537
  }
3472
3538
  function Ya(t, a) {
3473
3539
  const e = t.ownerDocument.createTreeWalker(
@@ -3500,14 +3566,14 @@ function Ka(t, a) {
3500
3566
  const o = parseFloat(t.outlineWidth) || 0, s = t.outlineStyle || "";
3501
3567
  if (o > 0 && s !== "none")
3502
3568
  return !0;
3503
- const d = t.backgroundImage || "";
3504
- if (d && d !== "none" && d !== "initial")
3569
+ const l = t.backgroundImage || "";
3570
+ if (l && l !== "none" && l !== "initial")
3505
3571
  return !0;
3506
- const h = oe(t.fontWeight), l = oe(a.fontWeight);
3507
- if (Math.abs(h - l) >= 300 || t.fontStyle !== a.fontStyle)
3572
+ const h = oe(t.fontWeight), c = oe(a.fontWeight);
3573
+ if (Math.abs(h - c) >= 300 || t.fontStyle !== a.fontStyle)
3508
3574
  return !0;
3509
- const m = parseFloat(t.fontSize) || 16, g = parseFloat(a.fontSize) || 16;
3510
- return g > 0 && m / g >= 1.2;
3575
+ const d = parseFloat(t.fontSize) || 16, g = parseFloat(a.fontSize) || 16;
3576
+ return g > 0 && d / g >= 1.2;
3511
3577
  }
3512
3578
  function oe(t) {
3513
3579
  return t === "bold" ? 700 : t === "normal" ? 400 : parseInt(t) || 400;
@@ -3515,7 +3581,7 @@ function oe(t) {
3515
3581
  function se(t, a, e) {
3516
3582
  return "#" + [t, a, e].map((i) => i.toString(16).padStart(2, "0")).join("");
3517
3583
  }
3518
- const Qa = {
3584
+ const Ja = {
3519
3585
  id: "link-in-text-block",
3520
3586
  wcag: ["1.4.1"],
3521
3587
  level: "A",
@@ -3525,22 +3591,22 @@ const Qa = {
3525
3591
  run(t) {
3526
3592
  const a = [];
3527
3593
  for (const e of t.querySelectorAll("a[href]")) {
3528
- if (p(e) || !y(e).trim() || e.closest('nav, [role="navigation"], [role="banner"], [role="contentinfo"]')) continue;
3594
+ if (p(e) || !w(e).trim() || e.closest('nav, header, footer, [role="navigation"], [role="banner"], [role="contentinfo"]')) continue;
3529
3595
  const i = A(e), n = i.display || "inline";
3530
3596
  if (!Ua.has(n)) continue;
3531
3597
  const r = Ga(e);
3532
3598
  if (!r) continue;
3533
3599
  const o = A(r);
3534
3600
  if (Ka(i, o)) continue;
3535
- const s = R(i.color), d = Ya(r);
3536
- if (!s || !d) continue;
3537
- const h = q(...s), l = q(...d), m = de(h, l);
3538
- if (m >= 3) continue;
3539
- const g = se(...s), b = se(...d), f = `link color: ${g} rgb(${s.join(", ")}), surrounding text: ${b} rgb(${d.join(", ")}), ratio: ${m.toFixed(2)}:1`;
3601
+ const s = R(i.color), l = Ya(r);
3602
+ if (!s || !l) continue;
3603
+ const h = q(...s), c = q(...l), d = de(h, c);
3604
+ if (d >= 3) continue;
3605
+ const g = se(...s), b = se(...l), f = `link color: ${g} rgb(${s.join(", ")}), surrounding text: ${b} rgb(${l.join(", ")}), ratio: ${d.toFixed(2)}:1`;
3540
3606
  a.push({
3541
3607
  ruleId: "link-in-text-block",
3542
- selector: u(e),
3543
- html: c(e),
3608
+ selector: m(e),
3609
+ html: u(e),
3544
3610
  impact: "serious",
3545
3611
  message: "Link in text block is not visually distinguishable from surrounding text. Add an underline, border, or ensure 3:1 color contrast with surrounding text.",
3546
3612
  context: f
@@ -3548,30 +3614,39 @@ const Qa = {
3548
3614
  }
3549
3615
  return a;
3550
3616
  }
3551
- }, Ja = {
3617
+ }, Qa = {
3552
3618
  id: "html-has-lang",
3553
3619
  wcag: ["3.1.1"],
3554
3620
  level: "A",
3555
3621
  description: "The <html> element must have a lang attribute.",
3556
3622
  guidance: "Screen readers use the lang attribute to determine which language rules and pronunciation to use. Without it, content may be mispronounced. Set lang to the primary language of the page (e.g., lang='en' for English, lang='es' for Spanish).",
3557
- prompt: "Determine the page's primary language and suggest the appropriate lang value.",
3623
+ prompt: `The page is missing a lang attribute on <html>. Use the text sample in context to determine the primary language and suggest the correct BCP 47 code (e.g. 'en' for English, 'es' for Spanish, 'fr' for French, 'de' for German, 'ja' for Japanese, 'zh' for Chinese, 'pt' for Portuguese, 'ar' for Arabic). Add lang to the <html> element: <html lang="...">.`,
3558
3624
  run(t) {
3559
- var e;
3625
+ var e, i;
3560
3626
  const a = t.documentElement;
3561
3627
  if (a.tagName.toLowerCase() !== "html") return [];
3562
3628
  if (!t.doctype && t.body) {
3563
- const i = t.body.children;
3564
- if (i.length > 0 && Array.from(i).every(
3565
- (n) => n.tagName.toLowerCase() === "svg" || n.tagName.toLowerCase() === "math"
3629
+ const n = t.body.children;
3630
+ if (n.length > 0 && Array.from(n).every(
3631
+ (r) => r.tagName.toLowerCase() === "svg" || r.tagName.toLowerCase() === "math"
3566
3632
  )) return [];
3567
3633
  }
3568
- return (e = a.getAttribute("lang")) != null && e.trim() ? [] : [{
3569
- ruleId: "html-has-lang",
3570
- selector: u(a),
3571
- html: c(a),
3572
- impact: "serious",
3573
- message: "<html> element missing lang attribute."
3574
- }];
3634
+ if (!((e = a.getAttribute("lang")) != null && e.trim())) {
3635
+ let n;
3636
+ if (t.body) {
3637
+ const r = ((i = t.body.textContent) == null ? void 0 : i.trim().replace(/\s+/g, " ")) || "";
3638
+ r && (n = r.slice(0, 200));
3639
+ }
3640
+ return [{
3641
+ ruleId: "html-has-lang",
3642
+ selector: m(a),
3643
+ html: u(a),
3644
+ impact: "serious",
3645
+ message: "<html> element missing lang attribute.",
3646
+ context: n ? `Page text sample: "${n}"` : void 0
3647
+ }];
3648
+ }
3649
+ return [];
3575
3650
  }
3576
3651
  }, Za = new Set(
3577
3652
  "aa ab ae af ak am an ar as av ay az ba be bg bh bi bm bn bo br bs ca ce ch co cr cs cu cv cy da de dv dz ee el en eo es et eu fa ff fi fj fo fr fy ga gd gl gn gu gv ha he hi ho hr ht hu hy hz ia id ie ig ii ik io is it iu ja jv ka kg ki kj kk kl km kn ko kr ks ku kv kw ky la lb lg li ln lo lt lu lv mg mh mi mk ml mn mr ms mt my na nb nd ne ng nl nn no nr nv ny oc oj om or os pa pi pl ps pt qu rm rn ro ru rw sa sc sd se sg si sk sl sm sn so sq sr ss st su sv sw ta te tg th ti tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa wo xh yi yo za zh zu".split(" ")
@@ -3596,7 +3671,7 @@ const ai = {
3596
3671
  return a && !he(a) ? [{
3597
3672
  ruleId: "html-lang-valid",
3598
3673
  selector: "html",
3599
- html: c(t.documentElement),
3674
+ html: u(t.documentElement),
3600
3675
  impact: "serious",
3601
3676
  message: `Invalid lang attribute value "${a}".`
3602
3677
  }] : [];
@@ -3649,8 +3724,8 @@ const ii = {
3649
3724
  if (i && !n) {
3650
3725
  le(e) && a.push({
3651
3726
  ruleId: "valid-lang",
3652
- selector: u(e),
3653
- html: c(e),
3727
+ selector: m(e),
3728
+ html: u(e),
3654
3729
  impact: "serious",
3655
3730
  message: "Empty lang attribute value."
3656
3731
  });
@@ -3658,8 +3733,8 @@ const ii = {
3658
3733
  }
3659
3734
  n && le(e) && (he(n) || a.push({
3660
3735
  ruleId: "valid-lang",
3661
- selector: u(e),
3662
- html: c(e),
3736
+ selector: m(e),
3737
+ html: u(e),
3663
3738
  impact: "serious",
3664
3739
  message: `Invalid lang attribute value "${n}".`
3665
3740
  }));
@@ -3682,7 +3757,7 @@ const ii = {
3682
3757
  return [{
3683
3758
  ruleId: "html-xml-lang-mismatch",
3684
3759
  selector: "html",
3685
- html: c(a),
3760
+ html: u(a),
3686
3761
  impact: "moderate",
3687
3762
  message: `lang="${e}" and xml:lang="${i}" do not match.`
3688
3763
  }];
@@ -3707,8 +3782,8 @@ const ii = {
3707
3782
  if (o === n) {
3708
3783
  a.push({
3709
3784
  ruleId: "td-headers-attr",
3710
- selector: u(e),
3711
- html: c(e),
3785
+ selector: m(e),
3786
+ html: u(e),
3712
3787
  impact: "serious",
3713
3788
  message: `Headers attribute references the cell itself ("${o}").`
3714
3789
  });
@@ -3717,8 +3792,8 @@ const ii = {
3717
3792
  if (!i.querySelector(`th#${CSS.escape(o)}, td#${CSS.escape(o)}`)) {
3718
3793
  a.push({
3719
3794
  ruleId: "td-headers-attr",
3720
- selector: u(e),
3721
- html: c(e),
3795
+ selector: m(e),
3796
+ html: u(e),
3722
3797
  impact: "serious",
3723
3798
  message: `Headers attribute references non-existent ID "${o}".`
3724
3799
  });
@@ -3742,8 +3817,8 @@ const ii = {
3742
3817
  const i = e.querySelectorAll("th"), n = e.querySelectorAll("td");
3743
3818
  i.length > 0 && n.length === 0 && a.push({
3744
3819
  ruleId: "th-has-data-cells",
3745
- selector: u(e),
3746
- html: c(e),
3820
+ selector: m(e),
3821
+ html: u(e),
3747
3822
  impact: "serious",
3748
3823
  message: "Table has header cells but no data cells."
3749
3824
  });
@@ -3764,36 +3839,36 @@ const ii = {
3764
3839
  if (p(n) || n.getAttribute("role") === "presentation" || n.getAttribute("role") === "none") continue;
3765
3840
  const r = n.querySelectorAll("tr"), o = r.length;
3766
3841
  let s = 0;
3767
- for (const m of r) {
3768
- const g = m.querySelectorAll("td, th");
3842
+ for (const d of r) {
3843
+ const g = d.querySelectorAll("td, th");
3769
3844
  let b = 0;
3770
3845
  for (const f of g)
3771
3846
  b += parseInt(f.getAttribute("colspan") || "1", 10);
3772
3847
  s = Math.max(s, b);
3773
3848
  }
3774
3849
  if (o <= 3 && s <= 3) continue;
3775
- const d = n.querySelector("th") !== null, h = n.querySelector("th[scope]") !== null, l = n.querySelector("td[headers]") !== null;
3776
- if (d)
3777
- for (const m of n.querySelectorAll("td")) {
3778
- if (p(m) || m.hasAttribute("headers")) continue;
3779
- const g = m.closest("tr");
3850
+ const l = n.querySelector("th") !== null, h = n.querySelector("th[scope]") !== null, c = n.querySelector("td[headers]") !== null;
3851
+ if (l)
3852
+ for (const d of n.querySelectorAll("td")) {
3853
+ if (p(d) || d.hasAttribute("headers")) continue;
3854
+ const g = d.closest("tr");
3780
3855
  if (!g) continue;
3781
- const b = g.querySelector("th") !== null, f = Array.from(g.children).indexOf(m);
3782
- let w = !1;
3856
+ const b = g.querySelector("th") !== null, f = Array.from(g.children).indexOf(d);
3857
+ let y = !1;
3783
3858
  const I = n.querySelector("thead");
3784
3859
  if (I) {
3785
- const S = I.querySelector("tr");
3786
- S && ((e = S.querySelectorAll("th, td")[f]) == null ? void 0 : e.tagName.toLowerCase()) === "th" && (w = !0);
3860
+ const x = I.querySelector("tr");
3861
+ x && ((e = x.querySelectorAll("th, td")[f]) == null ? void 0 : e.tagName.toLowerCase()) === "th" && (y = !0);
3787
3862
  }
3788
- if (!w) {
3789
- const S = n.querySelector("tbody > tr, tr");
3790
- S && ((i = S.querySelectorAll("th, td")[f]) == null ? void 0 : i.tagName.toLowerCase()) === "th" && (w = !0);
3863
+ if (!y) {
3864
+ const x = n.querySelector("tbody > tr, tr");
3865
+ x && ((i = x.querySelectorAll("th, td")[f]) == null ? void 0 : i.tagName.toLowerCase()) === "th" && (y = !0);
3791
3866
  }
3792
- if (!b && !w && !h && !l) {
3867
+ if (!b && !y && !h && !c) {
3793
3868
  a.push({
3794
3869
  ruleId: "td-has-header",
3795
- selector: u(m),
3796
- html: c(m),
3870
+ selector: m(d),
3871
+ html: u(d),
3797
3872
  impact: "serious",
3798
3873
  message: "Data cell has no associated header. Add th elements with scope, or headers attribute."
3799
3874
  });
@@ -3818,8 +3893,8 @@ const ii = {
3818
3893
  const r = (i = n.getAttribute("scope")) == null ? void 0 : i.toLowerCase();
3819
3894
  r && !e.has(r) && a.push({
3820
3895
  ruleId: "scope-attr-valid",
3821
- selector: u(n),
3822
- html: c(n),
3896
+ selector: m(n),
3897
+ html: u(n),
3823
3898
  impact: "moderate",
3824
3899
  message: `Invalid scope value "${r}". Use row, col, rowgroup, or colgroup.`
3825
3900
  });
@@ -3841,8 +3916,8 @@ const ii = {
3841
3916
  const i = e.closest("table");
3842
3917
  (i == null ? void 0 : i.getAttribute("role")) === "presentation" || (i == null ? void 0 : i.getAttribute("role")) === "none" || v(e) || a.push({
3843
3918
  ruleId: "empty-table-header",
3844
- selector: u(e),
3845
- html: c(e),
3919
+ selector: m(e),
3920
+ html: u(e),
3846
3921
  impact: "minor",
3847
3922
  message: "Table header cell is empty. Add text or use aria-label."
3848
3923
  });
@@ -3873,25 +3948,25 @@ const ii = {
3873
3948
  for (const [n, r] of i) {
3874
3949
  if (r <= 1) continue;
3875
3950
  const o = t.querySelectorAll(`#${CSS.escape(n)}`), s = t.querySelector(
3876
- D.map((l) => `[${l}~="${CSS.escape(n)}"]`).join(", ")
3877
- ), d = t.querySelector(`label[for="${CSS.escape(n)}"]`);
3951
+ D.map((c) => `[${c}~="${CSS.escape(n)}"]`).join(", ")
3952
+ ), l = t.querySelector(`label[for="${CSS.escape(n)}"]`);
3878
3953
  let h;
3879
3954
  if (s) {
3880
- const l = D.find(
3881
- (m) => {
3955
+ const c = D.find(
3956
+ (d) => {
3882
3957
  var g;
3883
- return (g = s.getAttribute(m)) == null ? void 0 : g.split(/\s+/).includes(n);
3958
+ return (g = s.getAttribute(d)) == null ? void 0 : g.split(/\s+/).includes(n);
3884
3959
  }
3885
3960
  );
3886
- l && (h = l);
3887
- } else d && (h = "label[for]");
3961
+ c && (h = c);
3962
+ } else l && (h = "label[for]");
3888
3963
  a.push({
3889
3964
  ruleId: "duplicate-id-aria",
3890
- selector: u(o[1]),
3891
- html: c(o[1]),
3965
+ selector: m(o[1]),
3966
+ html: u(o[1]),
3892
3967
  impact: "critical",
3893
3968
  message: `Duplicate ID "${n}" referenced by ${h ?? "an accessibility attribute"}.`,
3894
- context: `First element: ${c(o[0])}${h ? `
3969
+ context: `First element: ${u(o[0])}${h ? `
3895
3970
  Referenced by: ${h}` : ""}`
3896
3971
  });
3897
3972
  }
@@ -3910,8 +3985,8 @@ Referenced by: ${h}` : ""}`
3910
3985
  if (p(e) || e.hasAttribute("muted") || e.hasAttribute("autoplay")) continue;
3911
3986
  e.querySelector('track[kind="captions"], track[kind="subtitles"]') || a.push({
3912
3987
  ruleId: "video-caption",
3913
- selector: u(e),
3914
- html: c(e),
3988
+ selector: m(e),
3989
+ html: u(e),
3915
3990
  impact: "critical",
3916
3991
  message: "Video element has no captions track."
3917
3992
  });
@@ -3932,8 +4007,8 @@ Referenced by: ${h}` : ""}`
3932
4007
  const n = e.parentElement;
3933
4008
  n && n.querySelector('a[href*="transcript"], a[href*="text"]') || a.push({
3934
4009
  ruleId: "audio-caption",
3935
- selector: u(e),
3936
- html: c(e),
4010
+ selector: m(e),
4011
+ html: u(e),
3937
4012
  impact: "critical",
3938
4013
  message: "Audio element has no transcript or text alternative. Add a transcript or track element."
3939
4014
  });
@@ -4016,24 +4091,24 @@ const vi = {
4016
4091
  if (!o || n.has(o) || (n.add(o), hi.has(o.tagName)) || pi(o) || gi(o, t) || fi(o)) continue;
4017
4092
  const s = A(o);
4018
4093
  if (parseFloat(s.opacity) === 0) continue;
4019
- const d = s.textShadow;
4020
- if (d && d !== "none" && d !== "initial") continue;
4094
+ const l = s.textShadow;
4095
+ if (l && l !== "none" && l !== "initial") continue;
4021
4096
  const h = R(s.color);
4022
4097
  if (!h) continue;
4023
- const l = s.color.match(/rgba\(.+?,\s*([\d.]+)\s*\)/) || s.color.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);
4024
- if (l && (l[1].endsWith("%") ? parseFloat(l[1]) / 100 : parseFloat(l[1])) === 0 || Be(o)) continue;
4025
- const m = De(o);
4026
- if (!m) continue;
4027
- const g = q(h[0], h[1], h[2]), b = q(m[0], m[1], m[2]), f = de(g, b), w = je(o) ? 3 : 4.5;
4028
- if (f < w) {
4029
- const I = Math.round(f * 100) / 100, S = ce(h), M = ce(m);
4098
+ const c = s.color.match(/rgba\(.+?,\s*([\d.]+)\s*\)/) || s.color.match(/rgba?\(.+?\/\s*([\d.]+%?)\s*\)/);
4099
+ if (c && (c[1].endsWith("%") ? parseFloat(c[1]) / 100 : parseFloat(c[1])) === 0 || Be(o)) continue;
4100
+ const d = De(o);
4101
+ if (!d) continue;
4102
+ const g = q(h[0], h[1], h[2]), b = q(d[0], d[1], d[2]), f = de(g, b), y = Pe(o) ? 3 : 4.5;
4103
+ if (f < y) {
4104
+ const I = Math.round(f * 100) / 100, x = ce(h), $ = ce(d);
4030
4105
  a.push({
4031
4106
  ruleId: "color-contrast",
4032
- selector: u(o),
4033
- html: c(o),
4107
+ selector: m(o),
4108
+ html: u(o),
4034
4109
  impact: "serious",
4035
- message: `Insufficient color contrast ratio of ${I}:1 (required ${w}:1).`,
4036
- context: `foreground: ${S} rgb(${h.join(", ")}), background: ${M} rgb(${m.join(", ")}), ratio: ${I}:1, required: ${w}:1`
4110
+ message: `Insufficient color contrast ratio of ${I}:1 (required ${y}:1).`,
4111
+ context: `foreground: ${x} rgb(${h.join(", ")}), background: ${$} rgb(${d.join(", ")}), ratio: ${I}:1, required: ${y}:1`
4037
4112
  });
4038
4113
  }
4039
4114
  }
@@ -4042,8 +4117,8 @@ const vi = {
4042
4117
  }, pe = [
4043
4118
  // Document Structure
4044
4119
  Kt,
4045
- Qt,
4046
4120
  Jt,
4121
+ Qt,
4047
4122
  Zt,
4048
4123
  ea,
4049
4124
  aa,
@@ -4051,13 +4126,13 @@ const vi = {
4051
4126
  ra,
4052
4127
  sa,
4053
4128
  // Images
4054
- Pe,
4129
+ je,
4055
4130
  ze,
4056
4131
  Ue,
4057
4132
  Ge,
4058
4133
  Ye,
4059
4134
  Ke,
4060
- Je,
4135
+ Qe,
4061
4136
  Ze,
4062
4137
  it,
4063
4138
  // Forms
@@ -4066,27 +4141,27 @@ const vi = {
4066
4141
  ut,
4067
4142
  dt,
4068
4143
  vt,
4069
- wt,
4070
4144
  yt,
4145
+ wt,
4071
4146
  // Keyboard
4072
- St,
4147
+ xt,
4073
4148
  kt,
4074
4149
  Rt,
4075
4150
  Nt,
4076
- Mt,
4077
- // Structure
4078
4151
  $t,
4152
+ // Structure
4153
+ Mt,
4079
4154
  ta,
4080
4155
  la,
4081
4156
  Ht,
4082
4157
  Dt,
4083
4158
  Ot,
4084
- Wt,
4085
- Bt,
4086
4159
  Ft,
4160
+ Bt,
4161
+ Wt,
4087
4162
  _t,
4088
- jt,
4089
4163
  Pt,
4164
+ jt,
4090
4165
  Vt,
4091
4166
  Ut,
4092
4167
  Gt,
@@ -4098,28 +4173,28 @@ const vi = {
4098
4173
  ha,
4099
4174
  va,
4100
4175
  Aa,
4101
- xa,
4176
+ Sa,
4102
4177
  ka,
4103
- Ta,
4104
- La,
4178
+ Ea,
4105
4179
  Ca,
4180
+ La,
4106
4181
  qa,
4107
4182
  Ra,
4108
4183
  Na,
4109
- Ma,
4110
4184
  $a,
4185
+ Ma,
4111
4186
  Ha,
4112
4187
  Da,
4113
4188
  Oa,
4114
- Fa,
4189
+ Wa,
4115
4190
  ga,
4116
4191
  _a,
4117
4192
  // Links
4118
- Pa,
4193
+ ja,
4119
4194
  Va,
4120
- Qa,
4121
- // Language
4122
4195
  Ja,
4196
+ // Language
4197
+ Qa,
4123
4198
  ai,
4124
4199
  ii,
4125
4200
  ni,
@@ -4138,13 +4213,13 @@ const vi = {
4138
4213
  vi
4139
4214
  ];
4140
4215
  let U = [], ge = /* @__PURE__ */ new Set();
4141
- function Si(t) {
4216
+ function xi(t) {
4142
4217
  t.additionalRules && (U = t.additionalRules), t.disabledRules && (ge = new Set(t.disabledRules));
4143
4218
  }
4144
4219
  function be() {
4145
4220
  return pe.filter((a) => !ge.has(a.id)).concat(U);
4146
4221
  }
4147
- function xi(t) {
4222
+ function Si(t) {
4148
4223
  fe();
4149
4224
  const a = be(), e = [];
4150
4225
  let i = 0;
@@ -4166,7 +4241,7 @@ function xi(t) {
4166
4241
  };
4167
4242
  }
4168
4243
  function fe() {
4169
- xe(), ve(), we(), He(), $e(), Ie();
4244
+ Se(), ve(), ye(), He(), Me(), Ie();
4170
4245
  }
4171
4246
  function ki(t) {
4172
4247
  var i;
@@ -4184,31 +4259,31 @@ function ki(t) {
4184
4259
  ruleCount: a.length
4185
4260
  };
4186
4261
  }
4187
- const wi = new Map(pe.map((t) => [t.id, t]));
4262
+ const yi = new Map(pe.map((t) => [t.id, t]));
4188
4263
  function Ii(t) {
4189
- const a = wi.get(t);
4264
+ const a = yi.get(t);
4190
4265
  return a || U.find((e) => e.id === t);
4191
4266
  }
4192
4267
  export {
4193
4268
  fe as clearAllCaches,
4194
- $e as clearAriaAttrAuditCache,
4195
- xe as clearAriaHiddenCache,
4269
+ Me as clearAriaAttrAuditCache,
4270
+ Se as clearAriaHiddenCache,
4196
4271
  He as clearColorCaches,
4197
4272
  ve as clearComputedRoleCache,
4198
4273
  k as compileDeclarativeRule,
4199
- Si as configureRules,
4200
- xi as createChunkedAudit,
4274
+ xi as configureRules,
4275
+ Si as createChunkedAudit,
4201
4276
  v as getAccessibleName,
4202
- y as getAccessibleTextContent,
4277
+ w as getAccessibleTextContent,
4203
4278
  be as getActiveRules,
4204
- C as getComputedRole,
4205
- c as getHtmlSnippet,
4279
+ L as getComputedRole,
4280
+ u as getHtmlSnippet,
4206
4281
  ue as getImplicitRole,
4207
4282
  Ii as getRuleById,
4208
- u as getSelector,
4283
+ m as getSelector,
4209
4284
  p as isAriaHidden,
4210
- Se as isValidRole,
4211
- yi as querySelectorShadowAware,
4285
+ xe as isValidRole,
4286
+ wi as querySelectorShadowAware,
4212
4287
  pe as rules,
4213
4288
  ki as runAudit,
4214
4289
  Ai as validateDeclarativeRule