test-a11y-js 0.5.5 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11069,9 +11069,9 @@ var require_CSSStyleRule = __commonJS({
11069
11069
  return text;
11070
11070
  },
11071
11071
  set: function(cssText) {
11072
- var rule7 = CSSOM.CSSStyleRule.parse(cssText);
11073
- this.style = rule7.style;
11074
- this.selectorText = rule7.selectorText;
11072
+ var rule14 = CSSOM.CSSStyleRule.parse(cssText);
11073
+ this.style = rule14.style;
11074
+ this.selectorText = rule14.selectorText;
11075
11075
  }
11076
11076
  });
11077
11077
  CSSOM.CSSStyleRule.parse = function(ruleText) {
@@ -11209,11 +11209,11 @@ var require_CSSStyleSheet = __commonJS({
11209
11209
  };
11210
11210
  CSSOM.CSSStyleSheet.prototype = new CSSOM.StyleSheet();
11211
11211
  CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
11212
- CSSOM.CSSStyleSheet.prototype.insertRule = function(rule7, index) {
11212
+ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule14, index) {
11213
11213
  if (index < 0 || index > this.cssRules.length) {
11214
11214
  throw new RangeError("INDEX_SIZE_ERR");
11215
11215
  }
11216
- var cssRule = CSSOM.parse(rule7).cssRules[0];
11216
+ var cssRule = CSSOM.parse(rule14).cssRules[0];
11217
11217
  cssRule.parentStyleSheet = this;
11218
11218
  this.cssRules.splice(index, 0, cssRule);
11219
11219
  return index;
@@ -11408,11 +11408,11 @@ var require_CSSGroupingRule = __commonJS({
11408
11408
  };
11409
11409
  CSSOM.CSSGroupingRule.prototype = new CSSOM.CSSRule();
11410
11410
  CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
11411
- CSSOM.CSSGroupingRule.prototype.insertRule = function insertRule(rule7, index) {
11411
+ CSSOM.CSSGroupingRule.prototype.insertRule = function insertRule(rule14, index) {
11412
11412
  if (index < 0 || index > this.cssRules.length) {
11413
11413
  throw new RangeError("INDEX_SIZE_ERR");
11414
11414
  }
11415
- var cssRule = CSSOM.parse(rule7).cssRules[0];
11415
+ var cssRule = CSSOM.parse(rule14).cssRules[0];
11416
11416
  cssRule.parentRule = this;
11417
11417
  this.cssRules.splice(index, 0, cssRule);
11418
11418
  return index;
@@ -12612,9 +12612,9 @@ var require_clone = __commonJS({
12612
12612
  return cloned;
12613
12613
  }
12614
12614
  for (var i = 0, rulesLength = rules.length; i < rulesLength; i++) {
12615
- var rule7 = rules[i];
12616
- var ruleClone = cloned.cssRules[i] = new rule7.constructor();
12617
- var style = rule7.style;
12615
+ var rule14 = rules[i];
12616
+ var ruleClone = cloned.cssRules[i] = new rule14.constructor();
12617
+ var style = rule14.style;
12618
12618
  if (style) {
12619
12619
  var styleClone = ruleClone.style = new CSSOM.CSSStyleDeclaration();
12620
12620
  for (var j = 0, styleLength = style.length; j < styleLength; j++) {
@@ -12624,23 +12624,23 @@ var require_clone = __commonJS({
12624
12624
  }
12625
12625
  styleClone.length = style.length;
12626
12626
  }
12627
- if (rule7.hasOwnProperty("keyText")) {
12628
- ruleClone.keyText = rule7.keyText;
12627
+ if (rule14.hasOwnProperty("keyText")) {
12628
+ ruleClone.keyText = rule14.keyText;
12629
12629
  }
12630
- if (rule7.hasOwnProperty("selectorText")) {
12631
- ruleClone.selectorText = rule7.selectorText;
12630
+ if (rule14.hasOwnProperty("selectorText")) {
12631
+ ruleClone.selectorText = rule14.selectorText;
12632
12632
  }
12633
- if (rule7.hasOwnProperty("mediaText")) {
12634
- ruleClone.mediaText = rule7.mediaText;
12633
+ if (rule14.hasOwnProperty("mediaText")) {
12634
+ ruleClone.mediaText = rule14.mediaText;
12635
12635
  }
12636
- if (rule7.hasOwnProperty("conditionText")) {
12637
- ruleClone.conditionText = rule7.conditionText;
12636
+ if (rule14.hasOwnProperty("conditionText")) {
12637
+ ruleClone.conditionText = rule14.conditionText;
12638
12638
  }
12639
- if (rule7.hasOwnProperty("layerName")) {
12640
- ruleClone.layerName = rule7.layerName;
12639
+ if (rule14.hasOwnProperty("layerName")) {
12640
+ ruleClone.layerName = rule14.layerName;
12641
12641
  }
12642
- if (rule7.hasOwnProperty("cssRules")) {
12643
- ruleClone.cssRules = clone(rule7).cssRules;
12642
+ if (rule14.hasOwnProperty("cssRules")) {
12643
+ ruleClone.cssRules = clone(rule14).cssRules;
12644
12644
  }
12645
12645
  }
12646
12646
  return cloned;
@@ -28126,9 +28126,9 @@ var require_CSSStyleRule2 = __commonJS({
28126
28126
  return text;
28127
28127
  },
28128
28128
  set: function(cssText) {
28129
- var rule7 = CSSOM.CSSStyleRule.parse(cssText);
28130
- this.style = rule7.style;
28131
- this.selectorText = rule7.selectorText;
28129
+ var rule14 = CSSOM.CSSStyleRule.parse(cssText);
28130
+ this.style = rule14.style;
28131
+ this.selectorText = rule14.selectorText;
28132
28132
  }
28133
28133
  });
28134
28134
  CSSOM.CSSStyleRule.parse = function(ruleText) {
@@ -28266,11 +28266,11 @@ var require_CSSStyleSheet2 = __commonJS({
28266
28266
  };
28267
28267
  CSSOM.CSSStyleSheet.prototype = new CSSOM.StyleSheet();
28268
28268
  CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
28269
- CSSOM.CSSStyleSheet.prototype.insertRule = function(rule7, index) {
28269
+ CSSOM.CSSStyleSheet.prototype.insertRule = function(rule14, index) {
28270
28270
  if (index < 0 || index > this.cssRules.length) {
28271
28271
  throw new RangeError("INDEX_SIZE_ERR");
28272
28272
  }
28273
- var cssRule = CSSOM.parse(rule7).cssRules[0];
28273
+ var cssRule = CSSOM.parse(rule14).cssRules[0];
28274
28274
  cssRule.parentStyleSheet = this;
28275
28275
  this.cssRules.splice(index, 0, cssRule);
28276
28276
  return index;
@@ -28465,11 +28465,11 @@ var require_CSSGroupingRule2 = __commonJS({
28465
28465
  };
28466
28466
  CSSOM.CSSGroupingRule.prototype = new CSSOM.CSSRule();
28467
28467
  CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
28468
- CSSOM.CSSGroupingRule.prototype.insertRule = function insertRule(rule7, index) {
28468
+ CSSOM.CSSGroupingRule.prototype.insertRule = function insertRule(rule14, index) {
28469
28469
  if (index < 0 || index > this.cssRules.length) {
28470
28470
  throw new RangeError("INDEX_SIZE_ERR");
28471
28471
  }
28472
- var cssRule = CSSOM.parse(rule7).cssRules[0];
28472
+ var cssRule = CSSOM.parse(rule14).cssRules[0];
28473
28473
  cssRule.parentRule = this;
28474
28474
  this.cssRules.splice(index, 0, cssRule);
28475
28475
  return index;
@@ -29503,9 +29503,9 @@ var require_clone2 = __commonJS({
29503
29503
  return cloned;
29504
29504
  }
29505
29505
  for (var i = 0, rulesLength = rules.length; i < rulesLength; i++) {
29506
- var rule7 = rules[i];
29507
- var ruleClone = cloned.cssRules[i] = new rule7.constructor();
29508
- var style = rule7.style;
29506
+ var rule14 = rules[i];
29507
+ var ruleClone = cloned.cssRules[i] = new rule14.constructor();
29508
+ var style = rule14.style;
29509
29509
  if (style) {
29510
29510
  var styleClone = ruleClone.style = new CSSOM.CSSStyleDeclaration();
29511
29511
  for (var j = 0, styleLength = style.length; j < styleLength; j++) {
@@ -29515,20 +29515,20 @@ var require_clone2 = __commonJS({
29515
29515
  }
29516
29516
  styleClone.length = style.length;
29517
29517
  }
29518
- if (rule7.hasOwnProperty("keyText")) {
29519
- ruleClone.keyText = rule7.keyText;
29518
+ if (rule14.hasOwnProperty("keyText")) {
29519
+ ruleClone.keyText = rule14.keyText;
29520
29520
  }
29521
- if (rule7.hasOwnProperty("selectorText")) {
29522
- ruleClone.selectorText = rule7.selectorText;
29521
+ if (rule14.hasOwnProperty("selectorText")) {
29522
+ ruleClone.selectorText = rule14.selectorText;
29523
29523
  }
29524
- if (rule7.hasOwnProperty("mediaText")) {
29525
- ruleClone.mediaText = rule7.mediaText;
29524
+ if (rule14.hasOwnProperty("mediaText")) {
29525
+ ruleClone.mediaText = rule14.mediaText;
29526
29526
  }
29527
- if (rule7.hasOwnProperty("conditionText")) {
29528
- ruleClone.conditionText = rule7.conditionText;
29527
+ if (rule14.hasOwnProperty("conditionText")) {
29528
+ ruleClone.conditionText = rule14.conditionText;
29529
29529
  }
29530
- if (rule7.hasOwnProperty("cssRules")) {
29531
- ruleClone.cssRules = clone(rule7).cssRules;
29530
+ if (rule14.hasOwnProperty("cssRules")) {
29531
+ ruleClone.cssRules = clone(rule14).cssRules;
29532
29532
  }
29533
29533
  }
29534
29534
  return cloned;
@@ -64262,17 +64262,17 @@ var require_style_rules = __commonJS({
64262
64262
  };
64263
64263
  function forEachMatchingSheetRuleOfElement(elementImpl, handleRule) {
64264
64264
  function handleSheet(sheet) {
64265
- forEach.call(sheet.cssRules, (rule7) => {
64266
- if (rule7.media) {
64267
- if (indexOf.call(rule7.media, "screen") !== -1) {
64268
- forEach.call(rule7.cssRules, (innerRule) => {
64265
+ forEach.call(sheet.cssRules, (rule14) => {
64266
+ if (rule14.media) {
64267
+ if (indexOf.call(rule14.media, "screen") !== -1) {
64268
+ forEach.call(rule14.cssRules, (innerRule) => {
64269
64269
  if (matches2(innerRule, elementImpl)) {
64270
64270
  handleRule(innerRule);
64271
64271
  }
64272
64272
  });
64273
64273
  }
64274
- } else if (matches2(rule7, elementImpl)) {
64275
- handleRule(rule7);
64274
+ } else if (matches2(rule14, elementImpl)) {
64275
+ handleRule(rule14);
64276
64276
  }
64277
64277
  });
64278
64278
  }
@@ -64309,9 +64309,9 @@ var require_style_rules = __commonJS({
64309
64309
  );
64310
64310
  }
64311
64311
  }
64312
- forEachMatchingSheetRuleOfElement(elementImpl, (rule7) => {
64313
- forEach.call(rule7.style, (property) => {
64314
- handleProperty(rule7.style, property);
64312
+ forEachMatchingSheetRuleOfElement(elementImpl, (rule14) => {
64313
+ forEach.call(rule14.style, (property) => {
64314
+ handleProperty(rule14.style, property);
64315
64315
  });
64316
64316
  });
64317
64317
  forEach.call(elementImpl.style, (property) => {
@@ -64320,8 +64320,8 @@ var require_style_rules = __commonJS({
64320
64320
  styleCache.set(elementImpl, declaration);
64321
64321
  return declaration;
64322
64322
  };
64323
- function matches2(rule7, elementImpl) {
64324
- return matchesDontThrow(rule7.selectorText, elementImpl);
64323
+ function matches2(rule14, elementImpl) {
64324
+ return matchesDontThrow(rule14.selectorText, elementImpl);
64325
64325
  }
64326
64326
  function getCascadedPropertyValue(element, property) {
64327
64327
  return exports.getDeclarationForElement(element).getPropertyValue(property);
@@ -170845,6 +170845,254 @@ var A11yChecker = class {
170845
170845
  }
170846
170846
  return violations;
170847
170847
  }
170848
+ static checkFieldsetLegend(element) {
170849
+ const violations = [];
170850
+ const fieldsets = element.getElementsByTagName("fieldset");
170851
+ for (const fieldset of Array.from(fieldsets)) {
170852
+ const legend = Array.from(fieldset.children).find(
170853
+ (child) => child.tagName.toLowerCase() === "legend"
170854
+ );
170855
+ if (!legend) {
170856
+ violations.push({
170857
+ id: "fieldset-legend",
170858
+ description: "fieldset must have a legend element as a direct child",
170859
+ element: fieldset,
170860
+ impact: "serious"
170861
+ });
170862
+ } else if (!legend.textContent?.trim()) {
170863
+ violations.push({
170864
+ id: "fieldset-legend-empty",
170865
+ description: "fieldset legend must have non-empty text content",
170866
+ element: fieldset,
170867
+ impact: "serious"
170868
+ });
170869
+ }
170870
+ }
170871
+ return violations;
170872
+ }
170873
+ static checkTableStructure(element) {
170874
+ const violations = [];
170875
+ const tables = element.getElementsByTagName("table");
170876
+ for (const table of Array.from(tables)) {
170877
+ const hasCaption = table.querySelector("caption");
170878
+ const hasAriaLabel = table.hasAttribute("aria-label");
170879
+ const hasAriaLabelledBy = table.hasAttribute("aria-labelledby");
170880
+ if (!hasCaption && !hasAriaLabel && !hasAriaLabelledBy) {
170881
+ violations.push({
170882
+ id: "table-caption",
170883
+ description: "Table must have a caption or aria-label/aria-labelledby",
170884
+ element: table,
170885
+ impact: "serious"
170886
+ });
170887
+ }
170888
+ const headerCells = table.querySelectorAll("th");
170889
+ const dataCells = table.querySelectorAll("td");
170890
+ if (dataCells.length > 0 && headerCells.length === 0) {
170891
+ violations.push({
170892
+ id: "table-headers",
170893
+ description: "Table must have header cells (th elements) when it has data cells",
170894
+ element: table,
170895
+ impact: "serious"
170896
+ });
170897
+ }
170898
+ for (const th of Array.from(headerCells)) {
170899
+ if (!th.hasAttribute("scope")) {
170900
+ violations.push({
170901
+ id: "table-header-scope",
170902
+ description: "Table header cells (th) should have a scope attribute",
170903
+ element: th,
170904
+ impact: "moderate"
170905
+ });
170906
+ }
170907
+ }
170908
+ }
170909
+ return violations;
170910
+ }
170911
+ static checkDetailsSummary(element) {
170912
+ const violations = [];
170913
+ const detailsElements = element.getElementsByTagName("details");
170914
+ for (const details of Array.from(detailsElements)) {
170915
+ const firstChild = details.firstElementChild;
170916
+ if (!firstChild || firstChild.tagName.toLowerCase() !== "summary") {
170917
+ violations.push({
170918
+ id: "details-summary",
170919
+ description: "details element must have a summary element as its first child",
170920
+ element: details,
170921
+ impact: "serious"
170922
+ });
170923
+ } else if (!firstChild.textContent?.trim()) {
170924
+ violations.push({
170925
+ id: "details-summary-empty",
170926
+ description: "details summary element must have non-empty text content",
170927
+ element: details,
170928
+ impact: "serious"
170929
+ });
170930
+ }
170931
+ }
170932
+ return violations;
170933
+ }
170934
+ static checkVideoCaptions(element) {
170935
+ const violations = [];
170936
+ const videos = element.getElementsByTagName("video");
170937
+ for (const video of Array.from(videos)) {
170938
+ const tracks = video.querySelectorAll("track");
170939
+ const captionTracks = Array.from(tracks).filter(
170940
+ (track) => track.getAttribute("kind")?.toLowerCase() === "captions"
170941
+ );
170942
+ if (captionTracks.length === 0) {
170943
+ violations.push({
170944
+ id: "video-captions",
170945
+ description: 'Video element must have at least one track element with kind="captions"',
170946
+ element: video,
170947
+ impact: "serious"
170948
+ });
170949
+ } else {
170950
+ for (const track of captionTracks) {
170951
+ if (!track.hasAttribute("srclang")) {
170952
+ violations.push({
170953
+ id: "video-track-srclang",
170954
+ description: "Video caption track must have a srclang attribute",
170955
+ element: track,
170956
+ impact: "serious"
170957
+ });
170958
+ }
170959
+ if (!track.hasAttribute("label")) {
170960
+ violations.push({
170961
+ id: "video-track-label",
170962
+ description: "Video caption track should have a label attribute",
170963
+ element: track,
170964
+ impact: "moderate"
170965
+ });
170966
+ }
170967
+ }
170968
+ }
170969
+ }
170970
+ return violations;
170971
+ }
170972
+ static checkAudioCaptions(element) {
170973
+ const violations = [];
170974
+ const audios = element.getElementsByTagName("audio");
170975
+ for (const audio of Array.from(audios)) {
170976
+ const tracks = audio.querySelectorAll("track");
170977
+ const hasTranscript = audio.hasAttribute("aria-describedby") || audio.querySelector('a[href*="transcript"]') || audio.closest("div")?.querySelector('a[href*="transcript"]');
170978
+ if (tracks.length === 0 && !hasTranscript) {
170979
+ violations.push({
170980
+ id: "audio-captions",
170981
+ description: "Audio element must have track elements or a transcript link",
170982
+ element: audio,
170983
+ impact: "serious"
170984
+ });
170985
+ } else if (tracks.length > 0) {
170986
+ for (const track of Array.from(tracks)) {
170987
+ if (!track.hasAttribute("srclang")) {
170988
+ violations.push({
170989
+ id: "audio-track-srclang",
170990
+ description: "Audio track must have a srclang attribute",
170991
+ element: track,
170992
+ impact: "serious"
170993
+ });
170994
+ }
170995
+ if (!track.hasAttribute("label")) {
170996
+ violations.push({
170997
+ id: "audio-track-label",
170998
+ description: "Audio track should have a label attribute",
170999
+ element: track,
171000
+ impact: "moderate"
171001
+ });
171002
+ }
171003
+ }
171004
+ }
171005
+ }
171006
+ return violations;
171007
+ }
171008
+ static checkLandmarks(element) {
171009
+ const violations = [];
171010
+ const landmarkTags = ["nav", "main", "header", "footer", "aside", "section", "article"];
171011
+ const mainElements = element.getElementsByTagName("main");
171012
+ if (mainElements.length > 1) {
171013
+ const mains = Array.from(mainElements);
171014
+ for (let i = 1; i < mains.length; i++) {
171015
+ violations.push({
171016
+ id: "landmark-multiple-main",
171017
+ description: "Page should have only one main element",
171018
+ element: mains[i],
171019
+ impact: "serious"
171020
+ });
171021
+ }
171022
+ }
171023
+ for (const tag of landmarkTags) {
171024
+ const landmarks = element.getElementsByTagName(tag);
171025
+ for (const landmark of Array.from(landmarks)) {
171026
+ if (tag === "section" || tag === "article") {
171027
+ const hasHeading = landmark.querySelector("h1, h2, h3, h4, h5, h6");
171028
+ const hasAriaLabel = landmark.hasAttribute("aria-label");
171029
+ const hasAriaLabelledBy = landmark.hasAttribute("aria-labelledby");
171030
+ if (!hasHeading && !hasAriaLabel && !hasAriaLabelledBy) {
171031
+ violations.push({
171032
+ id: "landmark-missing-name",
171033
+ description: `${tag} element should have an accessible name (heading, aria-label, or aria-labelledby)`,
171034
+ element: landmark,
171035
+ impact: "moderate"
171036
+ });
171037
+ }
171038
+ }
171039
+ if (tag === "nav" || tag === "aside") {
171040
+ const hasAriaLabel = landmark.hasAttribute("aria-label");
171041
+ const hasAriaLabelledBy = landmark.hasAttribute("aria-labelledby");
171042
+ const sameType = Array.from(element.getElementsByTagName(tag));
171043
+ const unnamed = sameType.filter(
171044
+ (l) => !l.hasAttribute("aria-label") && !l.hasAttribute("aria-labelledby")
171045
+ );
171046
+ if (unnamed.length > 1 && !hasAriaLabel && !hasAriaLabelledBy) {
171047
+ violations.push({
171048
+ id: "landmark-duplicate-unnamed",
171049
+ description: `Multiple ${tag} elements found. Each should have an accessible name (aria-label or aria-labelledby)`,
171050
+ element: landmark,
171051
+ impact: "moderate"
171052
+ });
171053
+ }
171054
+ }
171055
+ }
171056
+ }
171057
+ return violations;
171058
+ }
171059
+ static checkDialogModal(element) {
171060
+ const violations = [];
171061
+ const dialogs = element.getElementsByTagName("dialog");
171062
+ for (const dialog of Array.from(dialogs)) {
171063
+ const hasAriaLabel = dialog.hasAttribute("aria-label");
171064
+ const hasAriaLabelledBy = dialog.hasAttribute("aria-labelledby");
171065
+ const hasHeading = dialog.querySelector("h1, h2, h3, h4, h5, h6");
171066
+ if (!hasAriaLabel && !hasAriaLabelledBy && !hasHeading) {
171067
+ violations.push({
171068
+ id: "dialog-missing-name",
171069
+ description: "Dialog element must have an accessible name (aria-label, aria-labelledby, or heading)",
171070
+ element: dialog,
171071
+ impact: "serious"
171072
+ });
171073
+ }
171074
+ const isModal = dialog.hasAttribute("open") || dialog.getAttribute("aria-modal") === "true";
171075
+ if (isModal && !dialog.hasAttribute("aria-modal")) {
171076
+ violations.push({
171077
+ id: "dialog-missing-modal",
171078
+ description: 'Modal dialog should have aria-modal="true" attribute',
171079
+ element: dialog,
171080
+ impact: "moderate"
171081
+ });
171082
+ }
171083
+ const hasRole = dialog.hasAttribute("role");
171084
+ const roleValue = dialog.getAttribute("role");
171085
+ if (hasRole && roleValue !== "dialog" && roleValue !== "alertdialog") {
171086
+ violations.push({
171087
+ id: "dialog-invalid-role",
171088
+ description: 'Dialog element should have role="dialog" or role="alertdialog"',
171089
+ element: dialog,
171090
+ impact: "moderate"
171091
+ });
171092
+ }
171093
+ }
171094
+ return violations;
171095
+ }
170848
171096
  static async check(element) {
170849
171097
  const violations = [
170850
171098
  ...this.checkImageAlt(element),
@@ -170852,7 +171100,14 @@ var A11yChecker = class {
170852
171100
  ...this.checkButtonLabel(element),
170853
171101
  ...this.checkFormLabels(element),
170854
171102
  ...this.checkHeadingOrder(element),
170855
- ...this.checkIframeTitle(element)
171103
+ ...this.checkIframeTitle(element),
171104
+ ...this.checkFieldsetLegend(element),
171105
+ ...this.checkTableStructure(element),
171106
+ ...this.checkDetailsSummary(element),
171107
+ ...this.checkVideoCaptions(element),
171108
+ ...this.checkAudioCaptions(element),
171109
+ ...this.checkLandmarks(element),
171110
+ ...this.checkDialogModal(element)
170856
171111
  ];
170857
171112
  if (violations.length > 0) {
170858
171113
  console.warn("\nAccessibility Violations Found:");
@@ -170875,6 +171130,13 @@ var recommended = {
170875
171130
  "test-a11y-js/button-label": "error",
170876
171131
  "test-a11y-js/form-label": "error",
170877
171132
  "test-a11y-js/iframe-title": "error",
171133
+ "test-a11y-js/fieldset-legend": "error",
171134
+ "test-a11y-js/table-structure": "error",
171135
+ "test-a11y-js/details-summary": "error",
171136
+ "test-a11y-js/video-captions": "error",
171137
+ "test-a11y-js/audio-captions": "error",
171138
+ "test-a11y-js/landmark-roles": "warn",
171139
+ "test-a11y-js/dialog-modal": "error",
170878
171140
  // Moderate/Minor violations - set to warn
170879
171141
  "test-a11y-js/link-text": "warn",
170880
171142
  "test-a11y-js/heading-order": "warn"
@@ -170889,7 +171151,14 @@ var strict = {
170889
171151
  "test-a11y-js/link-text": "error",
170890
171152
  "test-a11y-js/form-label": "error",
170891
171153
  "test-a11y-js/heading-order": "error",
170892
- "test-a11y-js/iframe-title": "error"
171154
+ "test-a11y-js/iframe-title": "error",
171155
+ "test-a11y-js/fieldset-legend": "error",
171156
+ "test-a11y-js/table-structure": "error",
171157
+ "test-a11y-js/details-summary": "error",
171158
+ "test-a11y-js/video-captions": "error",
171159
+ "test-a11y-js/audio-captions": "error",
171160
+ "test-a11y-js/landmark-roles": "error",
171161
+ "test-a11y-js/dialog-modal": "error"
170893
171162
  };
170894
171163
  var strict_default = strict;
170895
171164
 
@@ -171847,11 +172116,863 @@ var rule6 = {
171847
172116
  };
171848
172117
  var iframe_title_default = rule6;
171849
172118
 
172119
+ // src/linter/eslint-plugin/rules/fieldset-legend.ts
172120
+ var rule7 = {
172121
+ meta: {
172122
+ type: "problem",
172123
+ docs: {
172124
+ description: "Enforce fieldset elements have a legend element as a direct child",
172125
+ category: "Accessibility",
172126
+ recommended: true,
172127
+ url: "https://github.com/nolrm/test-a11y-js"
172128
+ },
172129
+ messages: {
172130
+ missingLegend: "fieldset must have a legend element as a direct child",
172131
+ emptyLegend: "fieldset legend must have non-empty text content"
172132
+ },
172133
+ fixable: void 0,
172134
+ schema: []
172135
+ },
172136
+ create(context) {
172137
+ return {
172138
+ // Check JSX fieldset elements
172139
+ JSXOpeningElement(node) {
172140
+ const jsxNode = node;
172141
+ if (jsxNode.name?.name === "fieldset") {
172142
+ try {
172143
+ const element = jsxToElement(node, context);
172144
+ const violations = A11yChecker.checkFieldsetLegend(element);
172145
+ for (const violation of violations) {
172146
+ if (violation.id === "fieldset-legend") {
172147
+ context.report({
172148
+ node,
172149
+ messageId: "missingLegend"
172150
+ });
172151
+ } else if (violation.id === "fieldset-legend-empty") {
172152
+ context.report({
172153
+ node,
172154
+ messageId: "emptyLegend"
172155
+ });
172156
+ }
172157
+ }
172158
+ } catch (error2) {
172159
+ }
172160
+ }
172161
+ },
172162
+ // Check HTML strings
172163
+ Literal(node) {
172164
+ if (isHTMLLiteral(node)) {
172165
+ const element = htmlNodeToElement(node, context);
172166
+ if (element) {
172167
+ const violations = A11yChecker.checkFieldsetLegend(element);
172168
+ for (const violation of violations) {
172169
+ if (violation.id === "fieldset-legend") {
172170
+ context.report({
172171
+ node,
172172
+ messageId: "missingLegend"
172173
+ });
172174
+ } else if (violation.id === "fieldset-legend-empty") {
172175
+ context.report({
172176
+ node,
172177
+ messageId: "emptyLegend"
172178
+ });
172179
+ }
172180
+ }
172181
+ }
172182
+ }
172183
+ },
172184
+ TemplateLiteral(node) {
172185
+ if (isHTMLLiteral(node)) {
172186
+ const element = htmlNodeToElement(node, context);
172187
+ if (element) {
172188
+ const violations = A11yChecker.checkFieldsetLegend(element);
172189
+ for (const violation of violations) {
172190
+ if (violation.id === "fieldset-legend") {
172191
+ context.report({
172192
+ node,
172193
+ messageId: "missingLegend"
172194
+ });
172195
+ } else if (violation.id === "fieldset-legend-empty") {
172196
+ context.report({
172197
+ node,
172198
+ messageId: "emptyLegend"
172199
+ });
172200
+ }
172201
+ }
172202
+ }
172203
+ }
172204
+ },
172205
+ // Check Vue template fieldset elements
172206
+ VElement(node) {
172207
+ const vueNode = node;
172208
+ if (vueNode.name === "fieldset") {
172209
+ try {
172210
+ const element = vueElementToDOM(node, context);
172211
+ if (element) {
172212
+ const violations = A11yChecker.checkFieldsetLegend(element);
172213
+ for (const violation of violations) {
172214
+ if (violation.id === "fieldset-legend") {
172215
+ context.report({
172216
+ node,
172217
+ messageId: "missingLegend"
172218
+ });
172219
+ } else if (violation.id === "fieldset-legend-empty") {
172220
+ context.report({
172221
+ node,
172222
+ messageId: "emptyLegend"
172223
+ });
172224
+ }
172225
+ }
172226
+ }
172227
+ } catch (error2) {
172228
+ }
172229
+ }
172230
+ }
172231
+ };
172232
+ }
172233
+ };
172234
+ var fieldset_legend_default = rule7;
172235
+
172236
+ // src/linter/eslint-plugin/rules/table-structure.ts
172237
+ var rule8 = {
172238
+ meta: {
172239
+ type: "problem",
172240
+ docs: {
172241
+ description: "Enforce table elements have proper accessibility structure (caption, headers, scope)",
172242
+ category: "Accessibility",
172243
+ recommended: true,
172244
+ url: "https://github.com/nolrm/test-a11y-js"
172245
+ },
172246
+ messages: {
172247
+ missingCaption: "Table must have a caption or aria-label/aria-labelledby",
172248
+ missingHeaders: "Table must have header cells (th elements) when it has data cells",
172249
+ missingScope: "Table header cells (th) should have a scope attribute"
172250
+ },
172251
+ fixable: void 0,
172252
+ schema: []
172253
+ },
172254
+ create(context) {
172255
+ return {
172256
+ // Check JSX table elements
172257
+ JSXOpeningElement(node) {
172258
+ const jsxNode = node;
172259
+ if (jsxNode.name?.name === "table") {
172260
+ try {
172261
+ const element = jsxToElement(node, context);
172262
+ const violations = A11yChecker.checkTableStructure(element);
172263
+ for (const violation of violations) {
172264
+ if (violation.id === "table-caption") {
172265
+ context.report({
172266
+ node,
172267
+ messageId: "missingCaption"
172268
+ });
172269
+ } else if (violation.id === "table-headers") {
172270
+ context.report({
172271
+ node,
172272
+ messageId: "missingHeaders"
172273
+ });
172274
+ } else if (violation.id === "table-header-scope") {
172275
+ context.report({
172276
+ node: violation.element,
172277
+ messageId: "missingScope"
172278
+ });
172279
+ }
172280
+ }
172281
+ } catch (error2) {
172282
+ }
172283
+ }
172284
+ },
172285
+ // Check HTML strings
172286
+ Literal(node) {
172287
+ if (isHTMLLiteral(node)) {
172288
+ const element = htmlNodeToElement(node, context);
172289
+ if (element) {
172290
+ const violations = A11yChecker.checkTableStructure(element);
172291
+ for (const violation of violations) {
172292
+ if (violation.id === "table-caption") {
172293
+ context.report({
172294
+ node,
172295
+ messageId: "missingCaption"
172296
+ });
172297
+ } else if (violation.id === "table-headers") {
172298
+ context.report({
172299
+ node,
172300
+ messageId: "missingHeaders"
172301
+ });
172302
+ }
172303
+ }
172304
+ }
172305
+ }
172306
+ },
172307
+ TemplateLiteral(node) {
172308
+ if (isHTMLLiteral(node)) {
172309
+ const element = htmlNodeToElement(node, context);
172310
+ if (element) {
172311
+ const violations = A11yChecker.checkTableStructure(element);
172312
+ for (const violation of violations) {
172313
+ if (violation.id === "table-caption") {
172314
+ context.report({
172315
+ node,
172316
+ messageId: "missingCaption"
172317
+ });
172318
+ } else if (violation.id === "table-headers") {
172319
+ context.report({
172320
+ node,
172321
+ messageId: "missingHeaders"
172322
+ });
172323
+ }
172324
+ }
172325
+ }
172326
+ }
172327
+ },
172328
+ // Check Vue template table elements
172329
+ VElement(node) {
172330
+ const vueNode = node;
172331
+ if (vueNode.name === "table") {
172332
+ try {
172333
+ const element = vueElementToDOM(node, context);
172334
+ if (element) {
172335
+ const violations = A11yChecker.checkTableStructure(element);
172336
+ for (const violation of violations) {
172337
+ if (violation.id === "table-caption") {
172338
+ context.report({
172339
+ node,
172340
+ messageId: "missingCaption"
172341
+ });
172342
+ } else if (violation.id === "table-headers") {
172343
+ context.report({
172344
+ node,
172345
+ messageId: "missingHeaders"
172346
+ });
172347
+ } else if (violation.id === "table-header-scope") {
172348
+ context.report({
172349
+ node: violation.element,
172350
+ messageId: "missingScope"
172351
+ });
172352
+ }
172353
+ }
172354
+ }
172355
+ } catch (error2) {
172356
+ }
172357
+ }
172358
+ }
172359
+ };
172360
+ }
172361
+ };
172362
+ var table_structure_default = rule8;
172363
+
172364
+ // src/linter/eslint-plugin/rules/details-summary.ts
172365
+ var rule9 = {
172366
+ meta: {
172367
+ type: "problem",
172368
+ docs: {
172369
+ description: "Enforce details elements have a summary element as first child",
172370
+ category: "Accessibility",
172371
+ recommended: true,
172372
+ url: "https://github.com/nolrm/test-a11y-js"
172373
+ },
172374
+ messages: {
172375
+ missingSummary: "details element must have a summary element as its first child",
172376
+ emptySummary: "details summary element must have non-empty text content"
172377
+ },
172378
+ fixable: void 0,
172379
+ schema: []
172380
+ },
172381
+ create(context) {
172382
+ return {
172383
+ // Check JSX details elements
172384
+ JSXOpeningElement(node) {
172385
+ const jsxNode = node;
172386
+ if (jsxNode.name?.name === "details") {
172387
+ try {
172388
+ const element = jsxToElement(node, context);
172389
+ const violations = A11yChecker.checkDetailsSummary(element);
172390
+ for (const violation of violations) {
172391
+ if (violation.id === "details-summary") {
172392
+ context.report({
172393
+ node,
172394
+ messageId: "missingSummary"
172395
+ });
172396
+ } else if (violation.id === "details-summary-empty") {
172397
+ context.report({
172398
+ node,
172399
+ messageId: "emptySummary"
172400
+ });
172401
+ }
172402
+ }
172403
+ } catch (error2) {
172404
+ }
172405
+ }
172406
+ },
172407
+ // Check HTML strings
172408
+ Literal(node) {
172409
+ if (isHTMLLiteral(node)) {
172410
+ const element = htmlNodeToElement(node, context);
172411
+ if (element) {
172412
+ const violations = A11yChecker.checkDetailsSummary(element);
172413
+ for (const violation of violations) {
172414
+ if (violation.id === "details-summary") {
172415
+ context.report({
172416
+ node,
172417
+ messageId: "missingSummary"
172418
+ });
172419
+ } else if (violation.id === "details-summary-empty") {
172420
+ context.report({
172421
+ node,
172422
+ messageId: "emptySummary"
172423
+ });
172424
+ }
172425
+ }
172426
+ }
172427
+ }
172428
+ },
172429
+ TemplateLiteral(node) {
172430
+ if (isHTMLLiteral(node)) {
172431
+ const element = htmlNodeToElement(node, context);
172432
+ if (element) {
172433
+ const violations = A11yChecker.checkDetailsSummary(element);
172434
+ for (const violation of violations) {
172435
+ if (violation.id === "details-summary") {
172436
+ context.report({
172437
+ node,
172438
+ messageId: "missingSummary"
172439
+ });
172440
+ } else if (violation.id === "details-summary-empty") {
172441
+ context.report({
172442
+ node,
172443
+ messageId: "emptySummary"
172444
+ });
172445
+ }
172446
+ }
172447
+ }
172448
+ }
172449
+ },
172450
+ // Check Vue template details elements
172451
+ VElement(node) {
172452
+ const vueNode = node;
172453
+ if (vueNode.name === "details") {
172454
+ try {
172455
+ const element = vueElementToDOM(node, context);
172456
+ if (element) {
172457
+ const violations = A11yChecker.checkDetailsSummary(element);
172458
+ for (const violation of violations) {
172459
+ if (violation.id === "details-summary") {
172460
+ context.report({
172461
+ node,
172462
+ messageId: "missingSummary"
172463
+ });
172464
+ } else if (violation.id === "details-summary-empty") {
172465
+ context.report({
172466
+ node,
172467
+ messageId: "emptySummary"
172468
+ });
172469
+ }
172470
+ }
172471
+ }
172472
+ } catch (error2) {
172473
+ }
172474
+ }
172475
+ }
172476
+ };
172477
+ }
172478
+ };
172479
+ var details_summary_default = rule9;
172480
+
172481
+ // src/linter/eslint-plugin/rules/video-captions.ts
172482
+ var rule10 = {
172483
+ meta: {
172484
+ type: "problem",
172485
+ docs: {
172486
+ description: "Enforce video elements have caption tracks",
172487
+ category: "Accessibility",
172488
+ recommended: true,
172489
+ url: "https://github.com/nolrm/test-a11y-js"
172490
+ },
172491
+ messages: {
172492
+ missingCaptions: 'Video element must have at least one track element with kind="captions"',
172493
+ missingSrclang: "Video caption track must have a srclang attribute",
172494
+ missingLabel: "Video caption track should have a label attribute"
172495
+ },
172496
+ fixable: void 0,
172497
+ schema: []
172498
+ },
172499
+ create(context) {
172500
+ return {
172501
+ // Check JSX video elements
172502
+ JSXOpeningElement(node) {
172503
+ const jsxNode = node;
172504
+ if (jsxNode.name?.name === "video") {
172505
+ try {
172506
+ const element = jsxToElement(node, context);
172507
+ const violations = A11yChecker.checkVideoCaptions(element);
172508
+ for (const violation of violations) {
172509
+ if (violation.id === "video-captions") {
172510
+ context.report({
172511
+ node,
172512
+ messageId: "missingCaptions"
172513
+ });
172514
+ } else if (violation.id === "video-track-srclang") {
172515
+ context.report({
172516
+ node: violation.element,
172517
+ messageId: "missingSrclang"
172518
+ });
172519
+ } else if (violation.id === "video-track-label") {
172520
+ context.report({
172521
+ node: violation.element,
172522
+ messageId: "missingLabel"
172523
+ });
172524
+ }
172525
+ }
172526
+ } catch (error2) {
172527
+ }
172528
+ }
172529
+ },
172530
+ // Check HTML strings
172531
+ Literal(node) {
172532
+ if (isHTMLLiteral(node)) {
172533
+ const element = htmlNodeToElement(node, context);
172534
+ if (element) {
172535
+ const violations = A11yChecker.checkVideoCaptions(element);
172536
+ for (const violation of violations) {
172537
+ if (violation.id === "video-captions") {
172538
+ context.report({
172539
+ node,
172540
+ messageId: "missingCaptions"
172541
+ });
172542
+ }
172543
+ }
172544
+ }
172545
+ }
172546
+ },
172547
+ TemplateLiteral(node) {
172548
+ if (isHTMLLiteral(node)) {
172549
+ const element = htmlNodeToElement(node, context);
172550
+ if (element) {
172551
+ const violations = A11yChecker.checkVideoCaptions(element);
172552
+ for (const violation of violations) {
172553
+ if (violation.id === "video-captions") {
172554
+ context.report({
172555
+ node,
172556
+ messageId: "missingCaptions"
172557
+ });
172558
+ }
172559
+ }
172560
+ }
172561
+ }
172562
+ },
172563
+ // Check Vue template video elements
172564
+ VElement(node) {
172565
+ const vueNode = node;
172566
+ if (vueNode.name === "video") {
172567
+ try {
172568
+ const element = vueElementToDOM(node, context);
172569
+ if (element) {
172570
+ const violations = A11yChecker.checkVideoCaptions(element);
172571
+ for (const violation of violations) {
172572
+ if (violation.id === "video-captions") {
172573
+ context.report({
172574
+ node,
172575
+ messageId: "missingCaptions"
172576
+ });
172577
+ } else if (violation.id === "video-track-srclang") {
172578
+ context.report({
172579
+ node: violation.element,
172580
+ messageId: "missingSrclang"
172581
+ });
172582
+ } else if (violation.id === "video-track-label") {
172583
+ context.report({
172584
+ node: violation.element,
172585
+ messageId: "missingLabel"
172586
+ });
172587
+ }
172588
+ }
172589
+ }
172590
+ } catch (error2) {
172591
+ }
172592
+ }
172593
+ }
172594
+ };
172595
+ }
172596
+ };
172597
+ var video_captions_default = rule10;
172598
+
172599
+ // src/linter/eslint-plugin/rules/audio-captions.ts
172600
+ var rule11 = {
172601
+ meta: {
172602
+ type: "problem",
172603
+ docs: {
172604
+ description: "Enforce audio elements have caption tracks or transcripts",
172605
+ category: "Accessibility",
172606
+ recommended: true,
172607
+ url: "https://github.com/nolrm/test-a11y-js"
172608
+ },
172609
+ messages: {
172610
+ missingCaptions: "Audio element must have track elements or a transcript link",
172611
+ missingSrclang: "Audio track must have a srclang attribute",
172612
+ missingLabel: "Audio track should have a label attribute"
172613
+ },
172614
+ fixable: void 0,
172615
+ schema: []
172616
+ },
172617
+ create(context) {
172618
+ return {
172619
+ // Check JSX audio elements
172620
+ JSXOpeningElement(node) {
172621
+ const jsxNode = node;
172622
+ if (jsxNode.name?.name === "audio") {
172623
+ try {
172624
+ const element = jsxToElement(node, context);
172625
+ const violations = A11yChecker.checkAudioCaptions(element);
172626
+ for (const violation of violations) {
172627
+ if (violation.id === "audio-captions") {
172628
+ context.report({
172629
+ node,
172630
+ messageId: "missingCaptions"
172631
+ });
172632
+ } else if (violation.id === "audio-track-srclang") {
172633
+ context.report({
172634
+ node: violation.element,
172635
+ messageId: "missingSrclang"
172636
+ });
172637
+ } else if (violation.id === "audio-track-label") {
172638
+ context.report({
172639
+ node: violation.element,
172640
+ messageId: "missingLabel"
172641
+ });
172642
+ }
172643
+ }
172644
+ } catch (error2) {
172645
+ }
172646
+ }
172647
+ },
172648
+ // Check HTML strings
172649
+ Literal(node) {
172650
+ if (isHTMLLiteral(node)) {
172651
+ const element = htmlNodeToElement(node, context);
172652
+ if (element) {
172653
+ const violations = A11yChecker.checkAudioCaptions(element);
172654
+ for (const violation of violations) {
172655
+ if (violation.id === "audio-captions") {
172656
+ context.report({
172657
+ node,
172658
+ messageId: "missingCaptions"
172659
+ });
172660
+ }
172661
+ }
172662
+ }
172663
+ }
172664
+ },
172665
+ TemplateLiteral(node) {
172666
+ if (isHTMLLiteral(node)) {
172667
+ const element = htmlNodeToElement(node, context);
172668
+ if (element) {
172669
+ const violations = A11yChecker.checkAudioCaptions(element);
172670
+ for (const violation of violations) {
172671
+ if (violation.id === "audio-captions") {
172672
+ context.report({
172673
+ node,
172674
+ messageId: "missingCaptions"
172675
+ });
172676
+ }
172677
+ }
172678
+ }
172679
+ }
172680
+ },
172681
+ // Check Vue template audio elements
172682
+ VElement(node) {
172683
+ const vueNode = node;
172684
+ if (vueNode.name === "audio") {
172685
+ try {
172686
+ const element = vueElementToDOM(node, context);
172687
+ if (element) {
172688
+ const violations = A11yChecker.checkAudioCaptions(element);
172689
+ for (const violation of violations) {
172690
+ if (violation.id === "audio-captions") {
172691
+ context.report({
172692
+ node,
172693
+ messageId: "missingCaptions"
172694
+ });
172695
+ } else if (violation.id === "audio-track-srclang") {
172696
+ context.report({
172697
+ node: violation.element,
172698
+ messageId: "missingSrclang"
172699
+ });
172700
+ } else if (violation.id === "audio-track-label") {
172701
+ context.report({
172702
+ node: violation.element,
172703
+ messageId: "missingLabel"
172704
+ });
172705
+ }
172706
+ }
172707
+ }
172708
+ } catch (error2) {
172709
+ }
172710
+ }
172711
+ }
172712
+ };
172713
+ }
172714
+ };
172715
+ var audio_captions_default = rule11;
172716
+
172717
+ // src/linter/eslint-plugin/rules/landmark-roles.ts
172718
+ var rule12 = {
172719
+ meta: {
172720
+ type: "problem",
172721
+ docs: {
172722
+ description: "Enforce proper use of landmark elements (nav, main, header, footer, aside, section, article)",
172723
+ category: "Accessibility",
172724
+ recommended: true,
172725
+ url: "https://github.com/nolrm/test-a11y-js"
172726
+ },
172727
+ messages: {
172728
+ multipleMain: "Page should have only one main element",
172729
+ missingName: "Landmark element should have an accessible name (heading, aria-label, or aria-labelledby)",
172730
+ duplicateUnnamed: "Multiple landmark elements found. Each should have an accessible name (aria-label or aria-labelledby)"
172731
+ },
172732
+ fixable: void 0,
172733
+ schema: []
172734
+ },
172735
+ create(context) {
172736
+ return {
172737
+ // Check JSX landmark elements
172738
+ JSXOpeningElement(node) {
172739
+ const jsxNode = node;
172740
+ const tagName = jsxNode.name?.name?.toLowerCase();
172741
+ const landmarkTags = ["nav", "main", "header", "footer", "aside", "section", "article"];
172742
+ if (landmarkTags.includes(tagName)) {
172743
+ try {
172744
+ const element = jsxToElement(node, context);
172745
+ const violations = A11yChecker.checkLandmarks(element);
172746
+ for (const violation of violations) {
172747
+ if (violation.id === "landmark-multiple-main") {
172748
+ context.report({
172749
+ node,
172750
+ messageId: "multipleMain"
172751
+ });
172752
+ } else if (violation.id === "landmark-missing-name") {
172753
+ context.report({
172754
+ node,
172755
+ messageId: "missingName"
172756
+ });
172757
+ } else if (violation.id === "landmark-duplicate-unnamed") {
172758
+ context.report({
172759
+ node,
172760
+ messageId: "duplicateUnnamed"
172761
+ });
172762
+ }
172763
+ }
172764
+ } catch (error2) {
172765
+ }
172766
+ }
172767
+ },
172768
+ // Check HTML strings
172769
+ Literal(node) {
172770
+ if (isHTMLLiteral(node)) {
172771
+ const element = htmlNodeToElement(node, context);
172772
+ if (element) {
172773
+ const violations = A11yChecker.checkLandmarks(element);
172774
+ for (const violation of violations) {
172775
+ if (violation.id === "landmark-multiple-main") {
172776
+ context.report({
172777
+ node,
172778
+ messageId: "multipleMain"
172779
+ });
172780
+ } else if (violation.id === "landmark-missing-name") {
172781
+ context.report({
172782
+ node,
172783
+ messageId: "missingName"
172784
+ });
172785
+ }
172786
+ }
172787
+ }
172788
+ }
172789
+ },
172790
+ TemplateLiteral(node) {
172791
+ if (isHTMLLiteral(node)) {
172792
+ const element = htmlNodeToElement(node, context);
172793
+ if (element) {
172794
+ const violations = A11yChecker.checkLandmarks(element);
172795
+ for (const violation of violations) {
172796
+ if (violation.id === "landmark-multiple-main") {
172797
+ context.report({
172798
+ node,
172799
+ messageId: "multipleMain"
172800
+ });
172801
+ } else if (violation.id === "landmark-missing-name") {
172802
+ context.report({
172803
+ node,
172804
+ messageId: "missingName"
172805
+ });
172806
+ }
172807
+ }
172808
+ }
172809
+ }
172810
+ },
172811
+ // Check Vue template landmark elements
172812
+ VElement(node) {
172813
+ const vueNode = node;
172814
+ const tagName = vueNode.name?.toLowerCase();
172815
+ const landmarkTags = ["nav", "main", "header", "footer", "aside", "section", "article"];
172816
+ if (landmarkTags.includes(tagName)) {
172817
+ try {
172818
+ const element = vueElementToDOM(node, context);
172819
+ if (element) {
172820
+ const violations = A11yChecker.checkLandmarks(element);
172821
+ for (const violation of violations) {
172822
+ if (violation.id === "landmark-multiple-main") {
172823
+ context.report({
172824
+ node,
172825
+ messageId: "multipleMain"
172826
+ });
172827
+ } else if (violation.id === "landmark-missing-name") {
172828
+ context.report({
172829
+ node,
172830
+ messageId: "missingName"
172831
+ });
172832
+ } else if (violation.id === "landmark-duplicate-unnamed") {
172833
+ context.report({
172834
+ node,
172835
+ messageId: "duplicateUnnamed"
172836
+ });
172837
+ }
172838
+ }
172839
+ }
172840
+ } catch (error2) {
172841
+ }
172842
+ }
172843
+ }
172844
+ };
172845
+ }
172846
+ };
172847
+ var landmark_roles_default = rule12;
172848
+
172849
+ // src/linter/eslint-plugin/rules/dialog-modal.ts
172850
+ var rule13 = {
172851
+ meta: {
172852
+ type: "problem",
172853
+ docs: {
172854
+ description: "Enforce dialog elements have proper accessibility attributes",
172855
+ category: "Accessibility",
172856
+ recommended: true,
172857
+ url: "https://github.com/nolrm/test-a11y-js"
172858
+ },
172859
+ messages: {
172860
+ missingName: "Dialog element must have an accessible name (aria-label, aria-labelledby, or heading)",
172861
+ missingModal: 'Modal dialog should have aria-modal="true" attribute',
172862
+ invalidRole: 'Dialog element should have role="dialog" or role="alertdialog"'
172863
+ },
172864
+ fixable: void 0,
172865
+ schema: []
172866
+ },
172867
+ create(context) {
172868
+ return {
172869
+ // Check JSX dialog elements
172870
+ JSXOpeningElement(node) {
172871
+ const jsxNode = node;
172872
+ if (jsxNode.name?.name === "dialog" || jsxNode.name?.name && jsxNode.attributes?.some(
172873
+ (attr) => attr.name?.name === "role" && (attr.value?.value === "dialog" || attr.value?.value === "alertdialog")
172874
+ )) {
172875
+ try {
172876
+ const element = jsxToElement(node, context);
172877
+ const violations = A11yChecker.checkDialogModal(element);
172878
+ for (const violation of violations) {
172879
+ if (violation.id === "dialog-missing-name") {
172880
+ context.report({
172881
+ node,
172882
+ messageId: "missingName"
172883
+ });
172884
+ } else if (violation.id === "dialog-missing-modal") {
172885
+ context.report({
172886
+ node,
172887
+ messageId: "missingModal"
172888
+ });
172889
+ } else if (violation.id === "dialog-invalid-role") {
172890
+ context.report({
172891
+ node,
172892
+ messageId: "invalidRole"
172893
+ });
172894
+ }
172895
+ }
172896
+ } catch (error2) {
172897
+ }
172898
+ }
172899
+ },
172900
+ // Check HTML strings
172901
+ Literal(node) {
172902
+ if (isHTMLLiteral(node)) {
172903
+ const element = htmlNodeToElement(node, context);
172904
+ if (element) {
172905
+ const violations = A11yChecker.checkDialogModal(element);
172906
+ for (const violation of violations) {
172907
+ if (violation.id === "dialog-missing-name") {
172908
+ context.report({
172909
+ node,
172910
+ messageId: "missingName"
172911
+ });
172912
+ }
172913
+ }
172914
+ }
172915
+ }
172916
+ },
172917
+ TemplateLiteral(node) {
172918
+ if (isHTMLLiteral(node)) {
172919
+ const element = htmlNodeToElement(node, context);
172920
+ if (element) {
172921
+ const violations = A11yChecker.checkDialogModal(element);
172922
+ for (const violation of violations) {
172923
+ if (violation.id === "dialog-missing-name") {
172924
+ context.report({
172925
+ node,
172926
+ messageId: "missingName"
172927
+ });
172928
+ }
172929
+ }
172930
+ }
172931
+ }
172932
+ },
172933
+ // Check Vue template dialog elements
172934
+ VElement(node) {
172935
+ const vueNode = node;
172936
+ if (vueNode.name === "dialog" || vueNode.startTag?.attributes?.some(
172937
+ (attr) => attr.key?.name === "role" && (attr.value?.value === "dialog" || attr.value?.value === "alertdialog")
172938
+ )) {
172939
+ try {
172940
+ const element = vueElementToDOM(node, context);
172941
+ if (element) {
172942
+ const violations = A11yChecker.checkDialogModal(element);
172943
+ for (const violation of violations) {
172944
+ if (violation.id === "dialog-missing-name") {
172945
+ context.report({
172946
+ node,
172947
+ messageId: "missingName"
172948
+ });
172949
+ } else if (violation.id === "dialog-missing-modal") {
172950
+ context.report({
172951
+ node,
172952
+ messageId: "missingModal"
172953
+ });
172954
+ } else if (violation.id === "dialog-invalid-role") {
172955
+ context.report({
172956
+ node,
172957
+ messageId: "invalidRole"
172958
+ });
172959
+ }
172960
+ }
172961
+ }
172962
+ } catch (error2) {
172963
+ }
172964
+ }
172965
+ }
172966
+ };
172967
+ }
172968
+ };
172969
+ var dialog_modal_default = rule13;
172970
+
171850
172971
  // src/linter/eslint-plugin/index.ts
171851
172972
  var plugin = {
171852
172973
  meta: {
171853
172974
  name: "test-a11y-js",
171854
- version: "0.2.0"
172975
+ version: "0.6.2"
171855
172976
  },
171856
172977
  rules: {
171857
172978
  "image-alt": image_alt_default,
@@ -171859,7 +172980,14 @@ var plugin = {
171859
172980
  "link-text": link_text_default,
171860
172981
  "form-label": form_label_default,
171861
172982
  "heading-order": heading_order_default,
171862
- "iframe-title": iframe_title_default
172983
+ "iframe-title": iframe_title_default,
172984
+ "fieldset-legend": fieldset_legend_default,
172985
+ "table-structure": table_structure_default,
172986
+ "details-summary": details_summary_default,
172987
+ "video-captions": video_captions_default,
172988
+ "audio-captions": audio_captions_default,
172989
+ "landmark-roles": landmark_roles_default,
172990
+ "dialog-modal": dialog_modal_default
171863
172991
  },
171864
172992
  configs: {
171865
172993
  recommended: {