@accesslint/core 0.8.4 → 0.8.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
@@ -1,4 +1,4 @@
1
- const j = [
1
+ const W = [
2
2
  "a[href]",
3
3
  "button:not([disabled])",
4
4
  'input:not([disabled]):not([type="hidden"])',
@@ -133,7 +133,7 @@ function $e(e) {
133
133
  return null;
134
134
  }
135
135
  }
136
- function W(e) {
136
+ function j(e) {
137
137
  var n;
138
138
  const a = K.get(e);
139
139
  if (a !== void 0) return a;
@@ -1184,7 +1184,7 @@ function Ae(e) {
1184
1184
  return a.length > 0 ? a.join(`
1185
1185
  `) : void 0;
1186
1186
  }
1187
- const jt = {
1187
+ const Wt = {
1188
1188
  id: "text-alternatives/img-alt",
1189
1189
  category: "text-alternatives",
1190
1190
  actRuleIds: ["23a2a8"],
@@ -1229,7 +1229,7 @@ const jt = {
1229
1229
  return a;
1230
1230
  }
1231
1231
  };
1232
- function Wt(e) {
1232
+ function jt(e) {
1233
1233
  var i;
1234
1234
  const a = De(e);
1235
1235
  if (a) return a;
@@ -1250,7 +1250,7 @@ const Ut = {
1250
1250
  const a = [], t = 'svg[role="img"], [role="graphics-document"], [role="graphics-symbol"]';
1251
1251
  for (const i of e.querySelectorAll(t)) {
1252
1252
  if (h(i)) continue;
1253
- if (!Wt(i)) {
1253
+ if (!jt(i)) {
1254
1254
  const o = i.getAttribute("role");
1255
1255
  a.push({
1256
1256
  ruleId: "text-alternatives/svg-img-alt",
@@ -1321,7 +1321,7 @@ const Ut = {
1321
1321
  }
1322
1322
  return a;
1323
1323
  }
1324
- }, Bt = ["image", "picture", "photo", "graphic", "icon", "img"], _t = {
1324
+ }, Bt = /^(image|picture|photo|graphic|icon|img)(\s+of\b|\s*[:\u2013\u2014-]|\s*$)/i, _t = {
1325
1325
  id: "text-alternatives/image-alt-words",
1326
1326
  category: "text-alternatives",
1327
1327
  wcag: ["1.1.1"],
@@ -1329,23 +1329,26 @@ const Ut = {
1329
1329
  tags: ["best-practice"],
1330
1330
  fixability: "contextual",
1331
1331
  browserHint: "Screenshot the image to verify the alt text accurately describes it without filler words like 'image of'.",
1332
- description: "Image alt text should not contain words like 'image', 'photo', or 'picture' — screen readers already announce the element type.",
1332
+ description: "Image alt text should not start with words like 'image of', 'photo of', or 'picture of' — screen readers already announce the element type.",
1333
1333
  guidance: "Screen readers already announce 'image' or 'graphic' before reading alt text, so phrases like 'image of', 'photo of', or 'picture of' are redundant. Remove these words and describe what the image shows. For example, change 'image of a dog' to 'golden retriever playing fetch'.",
1334
1334
  run(e) {
1335
1335
  const a = [];
1336
1336
  for (const t of e.querySelectorAll("img[alt]")) {
1337
- const i = t.getAttribute("alt").toLowerCase();
1337
+ const i = t.getAttribute("alt").trim();
1338
1338
  if (!i) continue;
1339
- const n = Bt.filter((o) => i.split(/\s+/).includes(o));
1340
- n.length > 0 && a.push({
1341
- ruleId: "text-alternatives/image-alt-words",
1342
- selector: p(t),
1343
- html: m(t),
1344
- impact: "minor",
1345
- message: `Alt text "${t.getAttribute("alt")}" contains redundant word(s): ${n.join(", ")}.`,
1346
- context: `Current alt: "${t.getAttribute("alt")}", redundant word(s): ${n.join(", ")}`,
1347
- fix: { type: "suggest", suggestion: "Remove the redundant word(s) from the alt text; screen readers already announce the element as an image" }
1348
- });
1339
+ const n = i.match(Bt);
1340
+ if (n) {
1341
+ const o = n[1].toLowerCase();
1342
+ a.push({
1343
+ ruleId: "text-alternatives/image-alt-words",
1344
+ selector: p(t),
1345
+ html: m(t),
1346
+ impact: "minor",
1347
+ message: `Alt text "${i}" starts with redundant prefix "${o}".`,
1348
+ context: `Current alt: "${i}", redundant prefix: "${o}"`,
1349
+ fix: { type: "suggest", suggestion: "Remove the redundant prefix from the alt text; screen readers already announce the element as an image" }
1350
+ });
1351
+ }
1349
1352
  }
1350
1353
  return a;
1351
1354
  }
@@ -1816,10 +1819,10 @@ function ra(e, a, t) {
1816
1819
  const sa = {
1817
1820
  id: "adaptable/list-children",
1818
1821
  selector: "ul, ol",
1819
- check: { type: "child-invalid", allowedChildren: ["li", "script", "template"], allowedChildRoles: ["listitem"] },
1822
+ check: { type: "child-invalid", allowedChildren: ["li", "script", "template", "style"], allowedChildRoles: ["listitem"] },
1820
1823
  impact: "serious",
1821
1824
  message: "List contains non-<li> child <{{tag}}>.",
1822
- description: "<ul> and <ol> must only contain <li>, <script>, or <template> as direct children.",
1825
+ description: "<ul> and <ol> must only contain <li>, <script>, <template>, or <style> as direct children.",
1823
1826
  wcag: ["1.3.1"],
1824
1827
  level: "A",
1825
1828
  fixability: "contextual",
@@ -1876,16 +1879,16 @@ const sa = {
1876
1879
  }, ua = {
1877
1880
  id: "adaptable/definition-list",
1878
1881
  selector: "dl",
1879
- check: { type: "child-invalid", allowedChildren: ["dt", "dd", "div", "script", "template"] },
1882
+ check: { type: "child-invalid", allowedChildren: ["dt", "dd", "div", "script", "template", "style"] },
1880
1883
  impact: "serious",
1881
1884
  message: "<dl> contains invalid child <{{tag}}>.",
1882
- description: "<dl> elements must only contain <dt>, <dd>, <div>, <script>, or <template>.",
1885
+ description: "<dl> elements must only contain <dt>, <dd>, <div>, <script>, <template>, or <style>.",
1883
1886
  wcag: ["1.3.1"],
1884
1887
  level: "A",
1885
1888
  fixability: "contextual",
1886
1889
  guidance: "Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or if they represent a term change to <dt>, if a definition change to <dd>. Styling wrappers should be replaced with <div> elements containing <dt>/<dd> pairs."
1887
1890
  }, ma = N(ua);
1888
- function je(e, a) {
1891
+ function We(e, a) {
1889
1892
  switch (a.toLowerCase()) {
1890
1893
  case "deg":
1891
1894
  return e;
@@ -1907,7 +1910,7 @@ function pa(e) {
1907
1910
  /rotate[Z]?\(\s*(-?[\d.]+)(deg|rad|turn|grad)\s*\)/i
1908
1911
  );
1909
1912
  if (a) {
1910
- const n = je(parseFloat(a[1]), a[2]);
1913
+ const n = We(parseFloat(a[1]), a[2]);
1911
1914
  if (B(n)) return !0;
1912
1915
  }
1913
1916
  const t = e.match(
@@ -1929,7 +1932,7 @@ function pa(e) {
1929
1932
  function ba(e) {
1930
1933
  const a = e.match(/(-?[\d.]+)(deg|rad|turn|grad)/i);
1931
1934
  if (!a) return !1;
1932
- const t = je(parseFloat(a[1]), a[2]);
1935
+ const t = We(parseFloat(a[1]), a[2]);
1933
1936
  return B(t);
1934
1937
  }
1935
1938
  const ha = {
@@ -2007,14 +2010,14 @@ function fa(e, a) {
2007
2010
  for (const s of e.querySelectorAll("*")) {
2008
2011
  if (h(s)) continue;
2009
2012
  o = !0;
2010
- const l = W(s);
2013
+ const l = j(s);
2011
2014
  l && n.add(l);
2012
2015
  }
2013
2016
  for (const s of t) {
2014
2017
  const l = i.getElementById(s);
2015
2018
  if (l && !h(l)) {
2016
2019
  o = !0;
2017
- const d = W(l);
2020
+ const d = j(l);
2018
2021
  d && n.add(d);
2019
2022
  }
2020
2023
  }
@@ -2091,7 +2094,7 @@ const va = {
2091
2094
  const o = Se[n];
2092
2095
  let r = i.parentElement, s = !1;
2093
2096
  for (; r && r !== e.documentElement; ) {
2094
- const l = W(r);
2097
+ const l = j(r);
2095
2098
  if (l && o.includes(l)) {
2096
2099
  s = !0;
2097
2100
  break;
@@ -2340,7 +2343,7 @@ const ka = {
2340
2343
  function ce(e) {
2341
2344
  return e.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
2342
2345
  }
2343
- function We(e, a) {
2346
+ function je(e, a) {
2344
2347
  const t = e.getAttribute("style");
2345
2348
  if (!t) return null;
2346
2349
  const i = new RegExp(
@@ -2446,7 +2449,7 @@ function Be(e, a, t, i) {
2446
2449
  const n = [];
2447
2450
  for (const o of e.querySelectorAll("[style]")) {
2448
2451
  if (h(o) || !Oe(o) || Ve(o) || !de(o, t)) continue;
2449
- const r = We(o, t);
2452
+ const r = je(o, t);
2450
2453
  if (!r) continue;
2451
2454
  let s = !1;
2452
2455
  if (r.em !== null ? s = r.em < i : r.px !== null && (s = Ue(o, t, r.px, i)), s) {
@@ -2503,7 +2506,7 @@ const Ra = {
2503
2506
  const o = parseFloat(w(t).lineHeight);
2504
2507
  if (o > 0 && t.scrollHeight <= o * 1.5) continue;
2505
2508
  }
2506
- const i = We(t, "line-height");
2509
+ const i = je(t, "line-height");
2507
2510
  if (!i) continue;
2508
2511
  let n = !1;
2509
2512
  if (i.em !== null ? n = i.em < 1.5 : i.px !== null && (n = Ue(t, "line-height", i.px, 1.5)), n) {
@@ -2668,10 +2671,10 @@ function za(e) {
2668
2671
  }
2669
2672
  return !1;
2670
2673
  }
2671
- function ja(e) {
2674
+ function Wa(e) {
2672
2675
  return e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement || e instanceof HTMLSelectElement || e instanceof HTMLButtonElement ? e.disabled : !!(e.closest("fieldset[disabled]") || e.getAttribute("aria-disabled") === "true");
2673
2676
  }
2674
- function Wa(e, a) {
2677
+ function ja(e, a) {
2675
2678
  if (e.tagName !== "LABEL") return !1;
2676
2679
  const t = e, i = t.htmlFor;
2677
2680
  if (i) {
@@ -2787,7 +2790,7 @@ function _e(e, a, t) {
2787
2790
  const l = s.parentElement;
2788
2791
  if (!l || r.has(l) || (r.add(l), Da.has(l.tagName))) continue;
2789
2792
  const d = l.tagName;
2790
- if (d === "BODY" || d === "HTML" || Ua(l) || ja(l) || Wa(l, e) || Va(l) || za(l)) continue;
2793
+ if (d === "BODY" || d === "HTML" || Ua(l) || Wa(l) || ja(l, e) || Va(l) || za(l)) continue;
2791
2794
  const c = w(l);
2792
2795
  if (parseFloat(c.opacity) === 0) continue;
2793
2796
  const u = Mt(l);
@@ -3065,7 +3068,7 @@ const di = {
3065
3068
  } else
3066
3069
  continue;
3067
3070
  const u = i.getAttribute("tabindex");
3068
- u !== null && u !== "-1" || i.querySelector(j) || a.push({
3071
+ u !== null && u !== "-1" || i.querySelector(W) || a.push({
3069
3072
  ruleId: "keyboard-accessible/scrollable-region",
3070
3073
  selector: p(i),
3071
3074
  html: m(i),
@@ -3118,7 +3121,7 @@ const di = {
3118
3121
  guidance: "Keyboard users need to see which element has focus. Do not remove the default focus outline (outline: none) without providing an alternative visible indicator. Use :focus-visible or :focus styles to ensure focus is always perceivable.",
3119
3122
  run(e) {
3120
3123
  const a = [];
3121
- for (const t of e.querySelectorAll(j)) {
3124
+ for (const t of e.querySelectorAll(W)) {
3122
3125
  if (h(t) || !(t instanceof HTMLElement)) continue;
3123
3126
  const i = t.getAttribute("style") || "";
3124
3127
  if (/outline\s*:\s*(none|0)\s*(;|$|!)/i.test(i)) {
@@ -3759,15 +3762,15 @@ const Ei = {
3759
3762
  }
3760
3763
  return [];
3761
3764
  }
3762
- }, ji = new Set(
3765
+ }, Wi = new Set(
3763
3766
  "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(" ")
3764
- ), Wi = new Set(
3767
+ ), ji = new Set(
3765
3768
  "aar abk afr aka amh ara arg asm ava ave aym aze bak bam bel ben bih bis bod bos bre bul cat ces cha che chu chv cor cos cre cym dan deu div dzo ell eng epo est eus ewe fao fas fij fin fra fry ful gla gle glg glv grn guj hat hau hbs heb her hin hmo hrv hun hye ibo iii iku ile ina ind ipk isl ita jav jpn kal kan kas kat kau kaz khm kik kin kir kom kon kor kua kur lao lat lav lim lin lit ltz lub lug mah mal mar mkd mlg mlt mon mri msa mya nau nav nbl nde ndo nep nld nno nob nor nya oci oji ori orm oss pan pli pol por pus que roh ron run rus sag san sin slk slv sme smo sna snd som sot spa sqi srd srp ssw sun swa swe tah tam tat tel tgk tgl tha tir ton tsn tso tuk tur twi uig ukr urd uzb ven vie vol wln wol xho yid yor zha zho zul".split(" ")
3766
3769
  ), Ui = /^[a-z]{2,8}(-[a-z0-9]{1,8})*$/i;
3767
3770
  function Ke(e) {
3768
3771
  if (!Ui.test(e)) return !1;
3769
3772
  const a = e.split("-")[0].toLowerCase();
3770
- return a.length === 2 ? ji.has(a) : a.length === 3 ? !Wi.has(a) : !1;
3773
+ return a.length === 2 ? Wi.has(a) : a.length === 3 ? !ji.has(a) : !1;
3771
3774
  }
3772
3775
  function Ce(e) {
3773
3776
  var i;
@@ -4703,7 +4706,7 @@ Referenced by: ${d}` : ""}`,
4703
4706
  }
4704
4707
  for (const o of t) {
4705
4708
  if (h(o)) continue;
4706
- const r = W(o);
4709
+ const r = j(o);
4707
4710
  if (!r) continue;
4708
4711
  const s = In[r];
4709
4712
  if (s)
@@ -4953,8 +4956,8 @@ const $n = {
4953
4956
  const a = [];
4954
4957
  for (const t of e.querySelectorAll('[aria-hidden="true"]')) {
4955
4958
  if (t === e.body) continue;
4956
- const i = [...t.querySelectorAll(j)];
4957
- t.matches(j) && i.push(t);
4959
+ const i = [...t.querySelectorAll(W)];
4960
+ t.matches(W) && i.push(t);
4958
4961
  for (const n of i)
4959
4962
  if (n instanceof HTMLElement) {
4960
4963
  const o = n.getAttribute("tabindex");
@@ -5077,7 +5080,7 @@ const Fn = {
5077
5080
  "spinbutton",
5078
5081
  "switch",
5079
5082
  "tab"
5080
- ]), jn = {
5083
+ ]), Wn = {
5081
5084
  id: "aria/presentational-children-focusable",
5082
5085
  category: "aria",
5083
5086
  actRuleIds: ["307n5z"],
@@ -5090,9 +5093,9 @@ const Fn = {
5090
5093
  const a = [];
5091
5094
  for (const t of e.querySelectorAll("*")) {
5092
5095
  if (h(t)) continue;
5093
- const i = W(t);
5096
+ const i = j(t);
5094
5097
  if (!(!i || !zn.has(i))) {
5095
- for (const n of t.querySelectorAll(j))
5098
+ for (const n of t.querySelectorAll(W))
5096
5099
  if (n !== t && !n.disabled && n.getAttribute("tabindex") !== "-1") {
5097
5100
  a.push({
5098
5101
  ruleId: "aria/presentational-children-focusable",
@@ -5109,7 +5112,7 @@ const Fn = {
5109
5112
  }
5110
5113
  }, Ze = [
5111
5114
  // Text Alternatives
5112
- jt,
5115
+ Wt,
5113
5116
  Ut,
5114
5117
  Ot,
5115
5118
  Vt,
@@ -5212,7 +5215,7 @@ const Fn = {
5212
5215
  $n,
5213
5216
  Hn,
5214
5217
  Fn,
5215
- jn
5218
+ Wn
5216
5219
  ];
5217
5220
  let me = [], et = /* @__PURE__ */ new Set(), tt = !1, at = !1, T, z;
5218
5221
  function Bn(e) {
@@ -5288,11 +5291,11 @@ function Yn(e, a) {
5288
5291
  n.has(l) || s.push(d);
5289
5292
  return { added: o, fixed: s, unchanged: r };
5290
5293
  }
5291
- const Wn = new Map(Ze.map((e) => [e.id, e]));
5294
+ const jn = new Map(Ze.map((e) => [e.id, e]));
5292
5295
  function Xn(e) {
5293
5296
  if (T)
5294
5297
  return pe().find((i) => i.id === e);
5295
- const a = Wn.get(e);
5298
+ const a = jn.get(e);
5296
5299
  return a || me.find((t) => t.id === e);
5297
5300
  }
5298
5301
  const Jn = {
@@ -5309,7 +5312,7 @@ const Jn = {
5309
5312
  "text-alternatives/svg-img-alt": { description: "SVG elements with an img, graphics-document, or graphics-symbol role must have an accessible name via a <title> element, aria-label, or aria-labelledby.", guidance: "Inline SVGs with role='img' need accessible names. Add a <title> element as the first child of the SVG (screen readers will announce it), or use aria-label on the SVG element. For complex SVGs, use aria-labelledby referencing both a <title> and <desc> element. Decorative SVGs should use aria-hidden='true' instead.", messages: { "{0} with role='{1}' has no accessible name.": "{0} with role='{1}' has no accessible name." } },
5310
5313
  "text-alternatives/input-image-alt": { description: 'Image inputs (<input type="image">) must have alternate text describing the button action.', guidance: "Image buttons (<input type='image'>) act as submit buttons with a custom image. Add alt text via alt, aria-label, or aria-labelledby that describes the action (e.g. alt='Search' or alt='Submit order'), not the image itself. Without it, screen readers announce only 'image' or the filename, giving no clue what the button does.", messages: { "Image input missing alt text.": "Image input missing alt text." } },
5311
5314
  "text-alternatives/image-redundant-alt": { 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.", 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.", messages: { 'Alt text "{0}" duplicates surrounding {1} text.': 'Alt text "{0}" duplicates surrounding {1} text.' } },
5312
- "text-alternatives/image-alt-words": { description: "Image alt text should not contain words like 'image', 'photo', or 'picture' — screen readers already announce the element type.", 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'.", messages: { 'Alt text "{0}" contains redundant word(s): {1}.': 'Alt text "{0}" contains redundant word(s): {1}.' } },
5315
+ "text-alternatives/image-alt-words": { description: "Image alt text should not start with words like 'image of', 'photo of', or 'picture of' — screen readers already announce the element type.", 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'.", messages: { 'Alt text "{0}" contains redundant word(s): {1}.': 'Alt text "{0}" contains redundant word(s): {1}.' } },
5313
5316
  "text-alternatives/area-alt": { description: "Image map <area> elements must have alternative text.", guidance: "Each clickable region in an image map needs alternative text so screen reader users know what the region represents. Add an alt attribute to every <area> element describing its purpose. For complex image maps, consider using alternative approaches like SVG with embedded links, or a list of text links.", messages: { "Image map <area> element is missing alternative text.": "Image map <area> element is missing alternative text." } },
5314
5317
  "text-alternatives/object-alt": { description: "<object> elements must have alternative text.", guidance: "Object elements embed external content that may not be accessible to all users. Provide alternative text via aria-label, aria-labelledby, or a title attribute. The fallback content inside <object> is only shown when the object fails to load and does not serve as an accessible name.", messages: { "<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute.": "<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute." } },
5315
5318
  "text-alternatives/role-img-alt": { description: "Elements with role='img' must have an accessible name.", guidance: "When you assign role='img' to an element (like a div containing icon fonts or CSS backgrounds), you must provide an accessible name via aria-label or aria-labelledby. Without this, screen reader users have no way to understand what the image represents. If the image is decorative, use role='presentation' or role='none' instead.", messages: { "Element with role='img' has no accessible name. Add aria-label or aria-labelledby.": "Element with role='img' has no accessible name. Add aria-label or aria-labelledby." } },
@@ -5338,10 +5341,10 @@ const Jn = {
5338
5341
  "landmarks/complementary-is-top-level": { description: "Aside (complementary) landmark should be top-level or directly inside main.", guidance: "The complementary landmark (aside) should be top-level or a direct child of main. Nesting aside deep within other landmarks reduces its discoverability for screen reader users navigating by landmarks.", messages: { "Complementary landmark should be top-level.": "Complementary landmark should be top-level." } },
5339
5342
  "landmarks/landmark-unique": { description: "Landmarks should have unique labels when there are multiple of the same type.", guidance: "When a page has multiple landmarks of the same type (e.g., multiple nav elements), each should have a unique accessible name via aria-label or aria-labelledby. This helps screen reader users distinguish between them (e.g., 'Main navigation' vs 'Footer navigation').", messages: { 'Multiple {0} landmarks have the same label "{1}".': 'Multiple {0} landmarks have the same label "{1}".', "Multiple {0} landmarks have no label. Add unique aria-label attributes.": "Multiple {0} landmarks have no label. Add unique aria-label attributes." } },
5340
5343
  "landmarks/region": { description: "All page content should be contained within landmarks.", guidance: "Screen reader users navigate pages by landmarks. Content outside landmarks is harder to find and understand. Wrap all visible content in appropriate landmarks: <header>, <nav>, <main>, <aside>, <footer>, or <section> with a label. Skip links may exist outside landmarks.", messages: { "Content is not contained within a landmark region.": "Content is not contained within a landmark region." } },
5341
- "adaptable/list-children": { description: "<ul> and <ol> must only contain <li>, <script>, or <template> as direct children.", guidance: "Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, apply styles to <li> elements directly and remove the wrapper (e.g., change <ul><div>item</div></ul> to <ul><li>item</li></ul>).", messages: { "List contains non-<li> child <{0}>.": "List contains non-<li> child <{0}>." } },
5344
+ "adaptable/list-children": { description: "<ul> and <ol> must only contain <li>, <script>, <template>, or <style> as direct children.", guidance: "Screen readers announce list structure ('list with 5 items') based on proper markup. Placing non-<li> elements directly inside <ul> or <ol> breaks this structure. Wrap content in <li> elements, or if you need wrapper divs for styling, apply styles to <li> elements directly and remove the wrapper (e.g., change <ul><div>item</div></ul> to <ul><li>item</li></ul>).", messages: { "List contains non-<li> child <{0}>.": "List contains non-<li> child <{0}>." } },
5342
5345
  "adaptable/listitem-parent": { description: "<li> elements must be contained in a <ul>, <ol>, or <menu>.", guidance: "List items (<li>) only have semantic meaning inside a list container (<ul>, <ol>, or <menu>). Without a list parent, screen readers will not announce 'list with N items' or allow users to skip between items using list navigation shortcuts. Wrap <li> elements in the appropriate list container — <ul> for unordered lists, <ol> for ordered/numbered lists.", messages: { "<li> is not contained in a <ul>, <ol>, or <menu>.": "<li> is not contained in a <ul>, <ol>, or <menu>." } },
5343
5346
  "adaptable/dl-children": { description: "<dt> and <dd> elements must be contained in a <dl>.", guidance: "Definition terms (<dt>) and definitions (<dd>) only have semantic meaning inside a definition list (<dl>). Outside of <dl>, they're treated as generic text. Wrap related <dt> and <dd> pairs in a <dl> element to convey the term/definition relationship to assistive technologies.", messages: { "<{0}> is not contained in a <dl>.": "<{0}> is not contained in a <dl>." } },
5344
- "adaptable/definition-list": { description: "<dl> elements must only contain <dt>, <dd>, <div>, <script>, or <template>.", guidance: "Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or if they represent a term change to <dt>, if a definition change to <dd>. Styling wrappers should be replaced with <div> elements containing <dt>/<dd> pairs.", messages: { "<dl> contains invalid child <{0}>.": "<dl> contains invalid child <{0}>." } },
5347
+ "adaptable/definition-list": { description: "<dl> elements must only contain <dt>, <dd>, <div>, <script>, <template>, or <style>.", guidance: "Definition lists have strict content requirements. Only <dt> (terms), <dd> (definitions), and <div> (for grouping dt/dd pairs) are valid children. Other elements break the list structure for screen readers. Move invalid elements outside the <dl>, or if they represent a term change to <dt>, if a definition change to <dd>. Styling wrappers should be replaced with <div> elements containing <dt>/<dd> pairs.", messages: { "<dl> contains invalid child <{0}>.": "<dl> contains invalid child <{0}>." } },
5345
5348
  "aria/aria-roles": { description: "ARIA role values must be valid.", guidance: "Invalid role values are ignored by assistive technologies, meaning the element will not have the intended semantics. Check the spelling and use only roles defined in the WAI-ARIA specification. Common roles include: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.", messages: { 'Invalid ARIA role "{0}".': 'Invalid ARIA role "{0}".' } },
5346
5349
  "aria/aria-valid-attr": { description: "ARIA attributes must be valid (correctly spelled).", guidance: "Misspelled ARIA attributes are ignored by assistive technologies. Check the spelling against the WAI-ARIA specification. Common mistakes: aria-labeledby (should be aria-labelledby), aria-role (should be role), aria-description (valid in ARIA 1.3+).", messages: { 'Invalid ARIA attribute "{0}".': 'Invalid ARIA attribute "{0}".' } },
5347
5350
  "aria/aria-valid-attr-value": { description: "ARIA attributes must have valid values.", guidance: "Each ARIA attribute accepts specific value types. Boolean attributes (aria-hidden, aria-disabled) accept only 'true' or 'false'. Tristate attributes (aria-checked, aria-pressed) also accept 'mixed'. Token attributes (aria-live, aria-autocomplete) accept predefined values. ID reference attributes (aria-labelledby, aria-describedby) must reference existing element IDs.", messages: { '{0} must be "true" or "false", got "{1}".': '{0} must be "true" or "false", got "{1}".', '{0} must be "true", "false", or "mixed", got "{1}".': '{0} must be "true", "false", or "mixed", got "{1}".', '{0} must be an integer, got "{1}".': '{0} must be an integer, got "{1}".', '{0} must be a number, got "{1}".': '{0} must be a number, got "{1}".', 'Invalid value "{0}" for {1}.': 'Invalid value "{0}" for {1}.' } },
@@ -5403,7 +5406,7 @@ const Jn = {
5403
5406
  "text-alternatives/svg-img-alt": { description: "Los elementos SVG con rol img, graphics-document o graphics-symbol deben tener un nombre accesible mediante un elemento <title>, aria-label o aria-labelledby.", guidance: "Los SVG en línea con role='img' necesitan nombres accesibles. Agregue un elemento <title> como primer hijo del SVG (los lectores de pantalla lo anunciarán), o use aria-label en el elemento SVG. Para SVGs complejos, use aria-labelledby haciendo referencia tanto a un elemento <title> como a un elemento <desc>. Los SVGs decorativos deben usar aria-hidden='true' en su lugar.", messages: { "{0} with role='{1}' has no accessible name.": "{0} con role='{1}' no tiene nombre accesible." } },
5404
5407
  "text-alternatives/input-image-alt": { description: 'Las entradas de imagen (<input type="image">) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen.', guidance: "Los botones de imagen (<input type='image'>) deben tener texto alternativo mediante alt, aria-label o aria-labelledby. El texto debe describir la acción del botón, no la imagen.", messages: { "Image input missing alt text.": "A la entrada de imagen le falta texto alt." } },
5405
5408
  "text-alternatives/image-redundant-alt": { description: "El texto alternativo de la imagen no debe duplicar el texto del enlace o botón adyacente. Cuando el texto alt repite el texto circundante, los usuarios de lectores de pantalla escuchan la misma información dos veces.", guidance: "Cuando una imagen está dentro de un enlace o botón que también tiene texto, haga que el texto alt sea complementario en lugar de idéntico. Si la imagen es puramente decorativa en ese contexto, use alt='' para evitar la repetición.", messages: { 'Alt text "{0}" duplicates surrounding {1} text.': 'El texto alt "{0}" duplica el texto del {1} circundante.' } },
5406
- "text-alternatives/image-alt-words": { description: "El texto alternativo de la imagen no debe contener palabras como 'imagen', 'foto' o 'fotografía': los lectores de pantalla ya anuncian el tipo de elemento.", guidance: "Los lectores de pantalla ya anuncian 'imagen' o 'gráfico' antes de leer el texto alt, por lo que frases como 'imagen de', 'foto de' o 'fotografía de' son redundantes. Elimine estas palabras y describa lo que muestra la imagen. Por ejemplo, cambie 'imagen de un perro' a 'golden retriever jugando a buscar'.", messages: { 'Alt text "{0}" contains redundant word(s): {1}.': 'El texto alt "{0}" contiene palabra(s) redundante(s): {1}.' } },
5409
+ "text-alternatives/image-alt-words": { description: "El texto alternativo de la imagen no debe comenzar con palabras como 'imagen de', 'foto de' o 'fotografía de': los lectores de pantalla ya anuncian el tipo de elemento.", guidance: "Los lectores de pantalla ya anuncian 'imagen' o 'gráfico' antes de leer el texto alt, por lo que frases como 'imagen de', 'foto de' o 'fotografía de' son redundantes. Elimine estas palabras y describa lo que muestra la imagen. Por ejemplo, cambie 'imagen de un perro' a 'golden retriever jugando a buscar'.", messages: { 'Alt text "{0}" contains redundant word(s): {1}.': 'El texto alt "{0}" contiene palabra(s) redundante(s): {1}.' } },
5407
5410
  "text-alternatives/area-alt": { description: "Los elementos <area> del mapa de imagen deben tener texto alternativo.", guidance: "Cada región clicable en un mapa de imagen necesita texto alternativo para que los usuarios de lectores de pantalla sepan qué representa la región. Agregue un atributo alt a cada elemento <area> describiendo su propósito. Para mapas de imagen complejos, considere usar enfoques alternativos como SVG con enlaces incrustados, o una lista de enlaces de texto.", messages: { "Image map <area> element is missing alternative text.": "Al elemento <area> del mapa de imagen le falta texto alternativo." } },
5408
5411
  "text-alternatives/object-alt": { description: "Los elementos <object> deben tener texto alternativo.", guidance: "Los elementos object incrustan contenido externo que puede no ser accesible para todos los usuarios. Proporcione texto alternativo mediante aria-label, aria-labelledby o un atributo title. El contenido de respaldo dentro de <object> solo se muestra cuando el objeto no se carga y no sirve como nombre accesible.", messages: { "<object> element is missing alternative text. Add aria-label, aria-labelledby, or a title attribute.": "Al elemento <object> le falta texto alternativo. Agregue aria-label, aria-labelledby o un atributo title." } },
5409
5412
  "text-alternatives/role-img-alt": { description: "Los elementos con role='img' deben tener un nombre accesible.", guidance: "Cuando asigna role='img' a un elemento (como un div que contiene fuentes de iconos o fondos CSS), debe proporcionar un nombre accesible mediante aria-label o aria-labelledby. Sin esto, los usuarios de lectores de pantalla no tienen forma de entender lo que representa la imagen. Si la imagen es decorativa, use role='presentation' o role='none' en su lugar.", messages: { "Element with role='img' has no accessible name. Add aria-label or aria-labelledby.": "El elemento con role='img' no tiene nombre accesible. Agregue aria-label o aria-labelledby." } },
@@ -5432,10 +5435,10 @@ const Jn = {
5432
5435
  "landmarks/complementary-is-top-level": { description: "El landmark aside (complementary) debe ser de nivel superior o estar directamente dentro de main.", guidance: "El landmark complementary (aside) debe ser de nivel superior o un hijo directo de main. Anidar aside profundamente dentro de otros landmarks reduce su descubrimiento para los usuarios de lectores de pantalla que navegan por landmarks.", messages: { "Complementary landmark should be top-level.": "El landmark complementary debe ser de nivel superior." } },
5433
5436
  "landmarks/landmark-unique": { description: "Los landmarks deben tener etiquetas únicas cuando hay múltiples del mismo tipo.", guidance: "Cuando una página tiene múltiples landmarks del mismo tipo (por ejemplo, múltiples elementos nav), cada uno debe tener un nombre accesible único mediante aria-label o aria-labelledby. Esto ayuda a los usuarios de lectores de pantalla a distinguirlos (por ejemplo, 'Navegación principal' vs 'Navegación del pie de página').", messages: { 'Multiple {0} landmarks have the same label "{1}".': 'Múltiples landmarks {0} tienen la misma etiqueta "{1}".', "Multiple {0} landmarks have no label. Add unique aria-label attributes.": "Múltiples landmarks {0} no tienen etiqueta. Agregue atributos aria-label únicos." } },
5434
5437
  "landmarks/region": { description: "Todo el contenido de la página debe estar contenido dentro de landmarks.", guidance: "Los usuarios de lectores de pantalla navegan las páginas por landmarks. El contenido fuera de los landmarks es más difícil de encontrar y comprender. Envuelva todo el contenido visible en landmarks apropiados: <header>, <nav>, <main>, <aside>, <footer>, o <section> con una etiqueta. Los enlaces de salto pueden existir fuera de los landmarks.", messages: { "Content is not contained within a landmark region.": "El contenido no está contenido dentro de una región landmark." } },
5435
- "adaptable/list-children": { description: "<ul> y <ol> solo deben contener <li>, <script> o <template> como hijos directos.", guidance: "Los lectores de pantalla anuncian la estructura de la lista ('lista con 5 elementos') basándose en el marcado correcto. Colocar elementos que no son <li> directamente dentro de <ul> u <ol> rompe esta estructura. Envuelva el contenido en elementos <li>, o si necesita divs envolventes para el estilo, aplique los estilos a los elementos <li> directamente y elimine el envoltorio (por ejemplo, cambie <ul><div>item</div></ul> a <ul><li>item</li></ul>).", messages: { "List contains non-<li> child <{0}>.": "La lista contiene un hijo no <li>: <{0}>." } },
5438
+ "adaptable/list-children": { description: "<ul> y <ol> solo deben contener <li>, <script>, <template> o <style> como hijos directos.", guidance: "Los lectores de pantalla anuncian la estructura de la lista ('lista con 5 elementos') basándose en el marcado correcto. Colocar elementos que no son <li> directamente dentro de <ul> u <ol> rompe esta estructura. Envuelva el contenido en elementos <li>, o si necesita divs envolventes para el estilo, aplique los estilos a los elementos <li> directamente y elimine el envoltorio (por ejemplo, cambie <ul><div>item</div></ul> a <ul><li>item</li></ul>).", messages: { "List contains non-<li> child <{0}>.": "La lista contiene un hijo no <li>: <{0}>." } },
5436
5439
  "adaptable/listitem-parent": { description: "Los elementos <li> deben estar contenidos en un <ul>, <ol> o <menu>.", guidance: "Los elementos de lista (<li>) solo tienen significado semántico dentro de un contenedor de lista (<ul>, <ol> o <menu>). Fuera de estos contenedores, las tecnologías de asistencia no pueden transmitir la relación de lista. Envuelva los elementos <li> en el contenedor de lista apropiado.", messages: { "<li> is not contained in a <ul>, <ol>, or <menu>.": "<li> no está contenido en un <ul>, <ol> o <menu>." } },
5437
5440
  "adaptable/dl-children": { description: "Los elementos <dt> y <dd> deben estar contenidos en un <dl>.", guidance: "Los términos de definición (<dt>) y las definiciones (<dd>) solo tienen significado semántico dentro de una lista de definiciones (<dl>). Fuera de <dl>, se tratan como texto genérico. Envuelva los pares relacionados de <dt> y <dd> en un elemento <dl> para transmitir la relación término/definición a las tecnologías de asistencia.", messages: { "<{0}> is not contained in a <dl>.": "<{0}> no está contenido en un <dl>." } },
5438
- "adaptable/definition-list": { description: "Los elementos <dl> solo deben contener <dt>, <dd>, <div>, <script> o <template>.", guidance: "Las listas de definiciones tienen requisitos estrictos de contenido. Solo <dt> (términos), <dd> (definiciones) y <div> (para agrupar pares dt/dd) son hijos válidos. Otros elementos rompen la estructura de la lista para los lectores de pantalla. Mueva los elementos inválidos fuera del <dl>, o si representan un término cámbielos a <dt>, si son una definición cámbielos a <dd>. Los envoltorios de estilo deben reemplazarse con elementos <div> que contengan pares <dt>/<dd>.", messages: { "<dl> contains invalid child <{0}>.": "<dl> contiene un hijo inválido <{0}>." } },
5441
+ "adaptable/definition-list": { description: "Los elementos <dl> solo deben contener <dt>, <dd>, <div>, <script>, <template> o <style>.", guidance: "Las listas de definiciones tienen requisitos estrictos de contenido. Solo <dt> (términos), <dd> (definiciones) y <div> (para agrupar pares dt/dd) son hijos válidos. Otros elementos rompen la estructura de la lista para los lectores de pantalla. Mueva los elementos inválidos fuera del <dl>, o si representan un término cámbielos a <dt>, si son una definición cámbielos a <dd>. Los envoltorios de estilo deben reemplazarse con elementos <div> que contengan pares <dt>/<dd>.", messages: { "<dl> contains invalid child <{0}>.": "<dl> contiene un hijo inválido <{0}>." } },
5439
5442
  "aria/aria-roles": { description: "Los valores de rol ARIA deben ser válidos.", guidance: "Los valores de rol inválidos son ignorados por las tecnologías de asistencia, lo que significa que el elemento no tendrá la semántica prevista. Verifique la ortografía y use solo roles definidos en la especificación WAI-ARIA. Los roles comunes incluyen: button, link, navigation, main, dialog, alert, tab, tabpanel, menu, menuitem.", messages: { 'Invalid ARIA role "{0}".': 'Rol ARIA inválido "{0}".' } },
5440
5443
  "aria/aria-valid-attr": { description: "Los atributos ARIA deben ser válidos (correctamente escritos).", guidance: "Los atributos ARIA mal escritos son ignorados por las tecnologías de asistencia. Verifique la ortografía contra la especificación WAI-ARIA. Errores comunes: aria-labeledby (debe ser aria-labelledby), aria-role (debe ser role), aria-description (válido en ARIA 1.3+).", messages: { 'Invalid ARIA attribute "{0}".': 'Atributo ARIA inválido "{0}".' } },
5441
5444
  "aria/aria-valid-attr-value": { description: "Los atributos ARIA deben tener valores válidos.", guidance: "Cada atributo ARIA acepta tipos de valores específicos. Los atributos booleanos (aria-hidden, aria-disabled) aceptan solo 'true' o 'false'. Los atributos triestado (aria-checked, aria-pressed) también aceptan 'mixed'. Los atributos de token (aria-live, aria-autocomplete) aceptan valores predefinidos. Los atributos de referencia de ID (aria-labelledby, aria-describedby) deben referenciar IDs de elementos existentes.", messages: { '{0} must be "true" or "false", got "{1}".': '{0} debe ser "true" o "false", se obtuvo "{1}".', '{0} must be "true", "false", or "mixed", got "{1}".': '{0} debe ser "true", "false" o "mixed", se obtuvo "{1}".', '{0} must be an integer, got "{1}".': '{0} debe ser un entero, se obtuvo "{1}".', '{0} must be a number, got "{1}".': '{0} debe ser un número, se obtuvo "{1}".', 'Invalid value "{0}" for {1}.': 'Valor inválido "{0}" para {1}.' } },
@@ -5497,7 +5500,7 @@ export {
5497
5500
  x as getAccessibleName,
5498
5501
  k as getAccessibleTextContent,
5499
5502
  pe as getActiveRules,
5500
- W as getComputedRole,
5503
+ j as getComputedRole,
5501
5504
  m as getHtmlSnippet,
5502
5505
  $e as getImplicitRole,
5503
5506
  Xn as getRuleById,
@@ -1,9 +1,9 @@
1
1
  import type { Rule } from "../types";
2
2
  /**
3
- * Checks if alt text contains self-referential words like "image" or "photo".
4
- * Screen readers already announce "image"/"graphic" before alt text, so these
5
- * words are redundant. Separate from image-redundant-alt because axe-core
6
- * does not have an equivalent check.
3
+ * Checks if alt text starts with a self-referential prefix like "image of"
4
+ * or "photo:". Screen readers already announce "image"/"graphic" before alt
5
+ * text, so these prefixes are redundant. Separate from image-redundant-alt
6
+ * because axe-core does not have an equivalent check.
7
7
  */
8
8
  export declare const imageAltWords: Rule;
9
9
  //# sourceMappingURL=image-alt-words.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"image-alt-words.d.ts","sourceRoot":"","sources":["../../../src/rules/text-alternatives/image-alt-words.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAKrC;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,IAiC3B,CAAC"}
1
+ {"version":3,"file":"image-alt-words.d.ts","sourceRoot":"","sources":["../../../src/rules/text-alternatives/image-alt-words.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAerC;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,IAkC3B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@accesslint/core",
3
- "version": "0.8.4",
3
+ "version": "0.8.7",
4
4
  "description": "Pure accessibility rule engine — WCAG audit with zero browser dependencies",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",