html-validate 8.15.0 → 8.17.0

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/es/core.js CHANGED
@@ -801,6 +801,10 @@ const definitions = {
801
801
  type: "object",
802
802
  additionalProperties: false,
803
803
  properties: {
804
+ disablable: {
805
+ type: "boolean",
806
+ title: "Disablable elements can be disabled using the disabled attribute."
807
+ },
804
808
  listed: {
805
809
  type: "boolean",
806
810
  title: "Listed elements have a name attribute and is listed in the form and fieldset elements property."
@@ -1126,6 +1130,7 @@ function migrateElement(src) {
1126
1130
  }
1127
1131
  if (src.formAssociated) {
1128
1132
  result.formAssociated = {
1133
+ disablable: Boolean(src.formAssociated.disablable),
1129
1134
  listed: Boolean(src.formAssociated.listed)
1130
1135
  };
1131
1136
  } else {
@@ -1385,10 +1390,14 @@ function expandRegexValue(value) {
1385
1390
  if (value instanceof RegExp) {
1386
1391
  return value;
1387
1392
  }
1388
- const match = value.match(/^\/\^?([^/$]*)\$?\/([i]*)$/);
1393
+ const match = value.match(/^\/(.*(?=\/))\/(i?)$/);
1389
1394
  if (match) {
1390
1395
  const [, expr, flags] = match;
1391
- return new RegExp(`^${expr}$`, flags);
1396
+ if (expr.startsWith("^") || expr.endsWith("$")) {
1397
+ return new RegExp(expr, flags);
1398
+ } else {
1399
+ return new RegExp(`^${expr}$`, flags);
1400
+ }
1392
1401
  } else {
1393
1402
  return value;
1394
1403
  }
@@ -2290,6 +2299,7 @@ class TextNode extends DOMNode {
2290
2299
  }
2291
2300
 
2292
2301
  const ROLE = Symbol("role");
2302
+ const TABINDEX = Symbol("tabindex");
2293
2303
  var NodeClosed = /* @__PURE__ */ ((NodeClosed2) => {
2294
2304
  NodeClosed2[NodeClosed2["Open"] = 0] = "Open";
2295
2305
  NodeClosed2[NodeClosed2["EndTag"] = 1] = "EndTag";
@@ -2570,6 +2580,43 @@ class HtmlElement extends DOMNode {
2570
2580
  }
2571
2581
  this.attr[key].push(new Attribute(key, value, keyLocation, valueLocation, originalAttribute));
2572
2582
  }
2583
+ /**
2584
+ * Get parsed tabindex for this element.
2585
+ *
2586
+ * - If `tabindex` attribute is not present `null` is returned.
2587
+ * - If attribute value is omitted or the empty string `null` is returned.
2588
+ * - If attribute value cannot be parsed `null` is returned.
2589
+ * - If attribute value is dynamic `0` is returned.
2590
+ * - Otherwise the parsed value is returned.
2591
+ *
2592
+ * This property does *NOT* take into account if the element have a default
2593
+ * `tabindex` (such as `<input>` have). Instead use the `focusable` metadata
2594
+ * property to determine this.
2595
+ *
2596
+ * @public
2597
+ * @since 8.16.0
2598
+ */
2599
+ get tabIndex() {
2600
+ const cached = this.cacheGet(TABINDEX);
2601
+ if (cached !== void 0) {
2602
+ return cached;
2603
+ }
2604
+ const tabindex = this.getAttribute("tabindex");
2605
+ if (!tabindex) {
2606
+ return this.cacheSet(TABINDEX, null);
2607
+ }
2608
+ if (tabindex.value === null) {
2609
+ return this.cacheSet(TABINDEX, null);
2610
+ }
2611
+ if (tabindex.value instanceof DynamicValue) {
2612
+ return this.cacheSet(TABINDEX, 0);
2613
+ }
2614
+ const parsed = parseInt(tabindex.value, 10);
2615
+ if (isNaN(parsed)) {
2616
+ return this.cacheSet(TABINDEX, null);
2617
+ }
2618
+ return this.cacheSet(TABINDEX, parsed);
2619
+ }
2573
2620
  /**
2574
2621
  * Get a list of all attributes on this node.
2575
2622
  */
@@ -3366,6 +3413,7 @@ function isKeywordIgnored(options, keyword, matcher = (list, it) => list.include
3366
3413
 
3367
3414
  const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
3368
3415
  const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
3416
+ const INERT_CACHE = Symbol(isInert.name);
3369
3417
  const ROLE_PRESENTATION_CACHE = Symbol(isPresentation.name);
3370
3418
  const STYLE_HIDDEN_CACHE = Symbol(isStyleHidden.name);
3371
3419
  function inAccessibilityTree(node) {
@@ -3378,6 +3426,9 @@ function inAccessibilityTree(node) {
3378
3426
  if (isHTMLHidden(node)) {
3379
3427
  return false;
3380
3428
  }
3429
+ if (isInert(node)) {
3430
+ return false;
3431
+ }
3381
3432
  if (isStyleHidden(node)) {
3382
3433
  return false;
3383
3434
  }
@@ -3419,6 +3470,24 @@ function isHTMLHidden(node, details) {
3419
3470
  const result = node.cacheSet(HTML_HIDDEN_CACHE, isHTMLHiddenImpl(node));
3420
3471
  return details ? result : result.byParent || result.bySelf;
3421
3472
  }
3473
+ function isInertImpl(node) {
3474
+ const isInert2 = (node2) => {
3475
+ const inert = node2.getAttribute("inert");
3476
+ return inert !== null && inert.isStatic;
3477
+ };
3478
+ return {
3479
+ byParent: node.parent ? isInert2(node.parent) : false,
3480
+ bySelf: isInert2(node)
3481
+ };
3482
+ }
3483
+ function isInert(node, details) {
3484
+ const cached = node.cacheGet(INERT_CACHE);
3485
+ if (cached) {
3486
+ return details ? cached : cached.byParent || cached.bySelf;
3487
+ }
3488
+ const result = node.cacheSet(INERT_CACHE, isInertImpl(node));
3489
+ return details ? result : result.byParent || result.bySelf;
3490
+ }
3422
3491
  function isStyleHiddenImpl(node) {
3423
3492
  const isHidden = (node2) => {
3424
3493
  const style = node2.getAttribute("style");
@@ -3844,7 +3913,7 @@ class Rule {
3844
3913
  }
3845
3914
  }
3846
3915
 
3847
- const defaults$w = {
3916
+ const defaults$x = {
3848
3917
  allowExternal: true,
3849
3918
  allowRelative: true,
3850
3919
  allowAbsolute: true,
@@ -3885,7 +3954,7 @@ function matchList(value, list) {
3885
3954
  }
3886
3955
  class AllowedLinks extends Rule {
3887
3956
  constructor(options) {
3888
- super({ ...defaults$w, ...options });
3957
+ super({ ...defaults$x, ...options });
3889
3958
  this.allowExternal = parseAllow(this.options.allowExternal);
3890
3959
  this.allowRelative = parseAllow(this.options.allowRelative);
3891
3960
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -4049,7 +4118,7 @@ class AllowedLinks extends Rule {
4049
4118
  }
4050
4119
  }
4051
4120
 
4052
- const defaults$v = {
4121
+ const defaults$w = {
4053
4122
  accessible: true
4054
4123
  };
4055
4124
  function findByTarget(target, siblings) {
@@ -4079,7 +4148,7 @@ function getDescription$1(context) {
4079
4148
  }
4080
4149
  class AreaAlt extends Rule {
4081
4150
  constructor(options) {
4082
- super({ ...defaults$v, ...options });
4151
+ super({ ...defaults$w, ...options });
4083
4152
  }
4084
4153
  static schema() {
4085
4154
  return {
@@ -4158,7 +4227,7 @@ class AriaHiddenBody extends Rule {
4158
4227
  }
4159
4228
  }
4160
4229
 
4161
- const defaults$u = {
4230
+ const defaults$v = {
4162
4231
  allowAnyNamable: false
4163
4232
  };
4164
4233
  const whitelisted = [
@@ -4200,7 +4269,7 @@ function isValidUsage(target, meta) {
4200
4269
  }
4201
4270
  class AriaLabelMisuse extends Rule {
4202
4271
  constructor(options) {
4203
- super({ ...defaults$u, ...options });
4272
+ super({ ...defaults$v, ...options });
4204
4273
  }
4205
4274
  documentation() {
4206
4275
  const valid = [
@@ -4310,13 +4379,13 @@ class CaseStyle {
4310
4379
  }
4311
4380
  }
4312
4381
 
4313
- const defaults$t = {
4382
+ const defaults$u = {
4314
4383
  style: "lowercase",
4315
4384
  ignoreForeign: true
4316
4385
  };
4317
4386
  class AttrCase extends Rule {
4318
4387
  constructor(options) {
4319
- super({ ...defaults$t, ...options });
4388
+ super({ ...defaults$u, ...options });
4320
4389
  this.style = new CaseStyle(this.options.style, "attr-case");
4321
4390
  }
4322
4391
  static schema() {
@@ -4672,7 +4741,7 @@ class AttrDelimiter extends Rule {
4672
4741
  }
4673
4742
 
4674
4743
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4675
- const defaults$s = {
4744
+ const defaults$t = {
4676
4745
  pattern: DEFAULT_PATTERN,
4677
4746
  ignoreForeign: true
4678
4747
  };
@@ -4704,7 +4773,7 @@ function generateDescription(name, pattern) {
4704
4773
  }
4705
4774
  class AttrPattern extends Rule {
4706
4775
  constructor(options) {
4707
- super({ ...defaults$s, ...options });
4776
+ super({ ...defaults$t, ...options });
4708
4777
  this.pattern = generateRegexp(this.options.pattern);
4709
4778
  }
4710
4779
  static schema() {
@@ -4751,7 +4820,7 @@ class AttrPattern extends Rule {
4751
4820
  }
4752
4821
  }
4753
4822
 
4754
- const defaults$r = {
4823
+ const defaults$s = {
4755
4824
  style: "auto",
4756
4825
  unquoted: false
4757
4826
  };
@@ -4790,7 +4859,7 @@ function describeStyle(style, unquoted) {
4790
4859
  }
4791
4860
  class AttrQuotes extends Rule {
4792
4861
  constructor(options) {
4793
- super({ ...defaults$r, ...options });
4862
+ super({ ...defaults$s, ...options });
4794
4863
  this.style = parseStyle$3(this.options.style);
4795
4864
  }
4796
4865
  static schema() {
@@ -4974,12 +5043,12 @@ class AttributeAllowedValues extends Rule {
4974
5043
  }
4975
5044
  }
4976
5045
 
4977
- const defaults$q = {
5046
+ const defaults$r = {
4978
5047
  style: "omit"
4979
5048
  };
4980
5049
  class AttributeBooleanStyle extends Rule {
4981
5050
  constructor(options) {
4982
- super({ ...defaults$q, ...options });
5051
+ super({ ...defaults$r, ...options });
4983
5052
  this.hasInvalidStyle = parseStyle$2(this.options.style);
4984
5053
  }
4985
5054
  static schema() {
@@ -5046,12 +5115,12 @@ function reportMessage$1(attr, style) {
5046
5115
  return "";
5047
5116
  }
5048
5117
 
5049
- const defaults$p = {
5118
+ const defaults$q = {
5050
5119
  style: "omit"
5051
5120
  };
5052
5121
  class AttributeEmptyStyle extends Rule {
5053
5122
  constructor(options) {
5054
- super({ ...defaults$p, ...options });
5123
+ super({ ...defaults$q, ...options });
5055
5124
  this.hasInvalidStyle = parseStyle$1(this.options.style);
5056
5125
  }
5057
5126
  static schema() {
@@ -5169,64 +5238,95 @@ class AttributeMisuse extends Rule {
5169
5238
  function parsePattern(pattern) {
5170
5239
  switch (pattern) {
5171
5240
  case "kebabcase":
5172
- return /^[a-z0-9-]+$/;
5241
+ return { regexp: /^[a-z0-9-]+$/, description: pattern };
5173
5242
  case "camelcase":
5174
- return /^[a-z][a-zA-Z0-9]+$/;
5243
+ return { regexp: /^[a-z][a-zA-Z0-9]+$/, description: pattern };
5175
5244
  case "underscore":
5176
- return /^[a-z0-9_]+$/;
5177
- default:
5178
- return new RegExp(pattern);
5245
+ return { regexp: /^[a-z0-9_]+$/, description: pattern };
5246
+ default: {
5247
+ const regexp = new RegExp(pattern);
5248
+ return { regexp, description: regexp.toString() };
5249
+ }
5179
5250
  }
5180
5251
  }
5181
- function describePattern(pattern) {
5182
- const regexp = parsePattern(pattern).toString();
5183
- switch (pattern) {
5184
- case "kebabcase":
5185
- case "camelcase":
5186
- case "underscore": {
5187
- return `${regexp} (${pattern})`;
5252
+
5253
+ function toArray$1(value) {
5254
+ return Array.isArray(value) ? value : [value];
5255
+ }
5256
+ class BasePatternRule extends Rule {
5257
+ /**
5258
+ * @param attr - Attribute holding the value.
5259
+ * @param options - Rule options with defaults expanded.
5260
+ */
5261
+ constructor(attr, options) {
5262
+ super(options);
5263
+ const { pattern } = this.options;
5264
+ this.attr = attr;
5265
+ this.patterns = toArray$1(pattern).map((it) => parsePattern(it));
5266
+ }
5267
+ static schema() {
5268
+ return {
5269
+ pattern: {
5270
+ oneOf: [{ type: "array", items: { type: "string" }, minItems: 1 }, { type: "string" }]
5271
+ }
5272
+ };
5273
+ }
5274
+ description(context) {
5275
+ const { attr, patterns } = this;
5276
+ const { value } = context;
5277
+ const lead = patterns.length === 1 ? `The \`${attr}\` attribute value \`"${value}"\` does not match the configured pattern.` : `The \`${attr}\` attribute value \`"${value}"\` does not match either of the configured patterns.`;
5278
+ return [
5279
+ lead,
5280
+ "For consistency within the codebase the `${attr}` is required to match one or more of the following patterns:",
5281
+ "",
5282
+ ...patterns.map((it) => `- \`${it.description}\``)
5283
+ ].join("\n");
5284
+ }
5285
+ validateValue(node, value, location) {
5286
+ const { attr, patterns } = this;
5287
+ const matches = patterns.some((it) => it.regexp.test(value));
5288
+ if (matches) {
5289
+ return;
5188
5290
  }
5189
- default:
5190
- return regexp;
5291
+ const allowed = naturalJoin(patterns.map((it) => `"${it.description}"`));
5292
+ const message = patterns.length === 1 ? `${attr} "${value}" does not match the configured pattern ${allowed}` : `${attr} "${value}" does not match either of the configured patterns: ${allowed}`;
5293
+ this.report({
5294
+ node,
5295
+ message,
5296
+ location,
5297
+ context: {
5298
+ value
5299
+ }
5300
+ });
5191
5301
  }
5192
5302
  }
5193
5303
 
5194
- const defaults$o = {
5304
+ const defaults$p = {
5195
5305
  pattern: "kebabcase"
5196
5306
  };
5197
- class ClassPattern extends Rule {
5307
+ class ClassPattern extends BasePatternRule {
5198
5308
  constructor(options) {
5199
- super({ ...defaults$o, ...options });
5200
- this.pattern = parsePattern(this.options.pattern);
5309
+ super("class", { ...defaults$p, ...options });
5201
5310
  }
5202
5311
  static schema() {
5203
- return {
5204
- pattern: {
5205
- type: "string"
5206
- }
5207
- };
5312
+ return BasePatternRule.schema();
5208
5313
  }
5209
- documentation() {
5210
- const pattern = describePattern(this.options.pattern);
5314
+ documentation(context) {
5211
5315
  return {
5212
- description: `For consistency all classes are required to match the pattern ${pattern}.`,
5316
+ description: this.description(context),
5213
5317
  url: "https://html-validate.org/rules/class-pattern.html"
5214
5318
  };
5215
5319
  }
5216
5320
  setup() {
5217
5321
  this.on("attr", (event) => {
5218
- if (event.key.toLowerCase() !== "class") {
5322
+ const { target, key, value, valueLocation } = event;
5323
+ if (key.toLowerCase() !== "class") {
5219
5324
  return;
5220
5325
  }
5221
- const classes = new DOMTokenList(event.value, event.valueLocation);
5222
- classes.forEach((cur, index) => {
5223
- if (!cur.match(this.pattern)) {
5224
- const location = classes.location(index);
5225
- const pattern = this.pattern.toString();
5226
- const message = `Class "${cur}" does not match required pattern "${pattern}"`;
5227
- this.report(event.target, message, location);
5228
- }
5229
- });
5326
+ const classes = new DOMTokenList(value, valueLocation);
5327
+ for (const { item, location } of classes.iterator()) {
5328
+ this.validateValue(target, item, location);
5329
+ }
5230
5330
  });
5231
5331
  }
5232
5332
  }
@@ -5302,13 +5402,13 @@ class CloseOrder extends Rule {
5302
5402
  }
5303
5403
  }
5304
5404
 
5305
- const defaults$n = {
5405
+ const defaults$o = {
5306
5406
  include: null,
5307
5407
  exclude: null
5308
5408
  };
5309
5409
  class Deprecated extends Rule {
5310
5410
  constructor(options) {
5311
- super({ ...defaults$n, ...options });
5411
+ super({ ...defaults$o, ...options });
5312
5412
  }
5313
5413
  static schema() {
5314
5414
  return {
@@ -5462,12 +5562,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
5462
5562
  }
5463
5563
  };
5464
5564
 
5465
- const defaults$m = {
5565
+ const defaults$n = {
5466
5566
  style: "uppercase"
5467
5567
  };
5468
5568
  class DoctypeStyle extends Rule {
5469
5569
  constructor(options) {
5470
- super({ ...defaults$m, ...options });
5570
+ super({ ...defaults$n, ...options });
5471
5571
  }
5472
5572
  static schema() {
5473
5573
  return {
@@ -5495,12 +5595,12 @@ class DoctypeStyle extends Rule {
5495
5595
  }
5496
5596
  }
5497
5597
 
5498
- const defaults$l = {
5598
+ const defaults$m = {
5499
5599
  style: "lowercase"
5500
5600
  };
5501
5601
  class ElementCase extends Rule {
5502
5602
  constructor(options) {
5503
- super({ ...defaults$l, ...options });
5603
+ super({ ...defaults$m, ...options });
5504
5604
  this.style = new CaseStyle(this.options.style, "element-case");
5505
5605
  }
5506
5606
  static schema() {
@@ -5560,14 +5660,14 @@ class ElementCase extends Rule {
5560
5660
  }
5561
5661
  }
5562
5662
 
5563
- const defaults$k = {
5663
+ const defaults$l = {
5564
5664
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
5565
5665
  whitelist: [],
5566
5666
  blacklist: []
5567
5667
  };
5568
5668
  class ElementName extends Rule {
5569
5669
  constructor(options) {
5570
- super({ ...defaults$k, ...options });
5670
+ super({ ...defaults$l, ...options });
5571
5671
  this.pattern = new RegExp(this.options.pattern);
5572
5672
  }
5573
5673
  static schema() {
@@ -5604,7 +5704,7 @@ class ElementName extends Rule {
5604
5704
  ...context.blacklist.map((cur) => `- ${cur}`)
5605
5705
  ];
5606
5706
  }
5607
- if (context.pattern !== defaults$k.pattern) {
5707
+ if (context.pattern !== defaults$l.pattern) {
5608
5708
  return [
5609
5709
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
5610
5710
  "",
@@ -6093,7 +6193,7 @@ class EmptyTitle extends Rule {
6093
6193
  }
6094
6194
  }
6095
6195
 
6096
- const defaults$j = {
6196
+ const defaults$k = {
6097
6197
  allowArrayBrackets: true,
6098
6198
  shared: ["radio", "button", "reset", "submit"]
6099
6199
  };
@@ -6121,7 +6221,7 @@ function getDocumentation(context) {
6121
6221
  }
6122
6222
  class FormDupName extends Rule {
6123
6223
  constructor(options) {
6124
- super({ ...defaults$j, ...options });
6224
+ super({ ...defaults$k, ...options });
6125
6225
  }
6126
6226
  static schema() {
6127
6227
  return {
@@ -6270,7 +6370,7 @@ class FormDupName extends Rule {
6270
6370
  }
6271
6371
  }
6272
6372
 
6273
- const defaults$i = {
6373
+ const defaults$j = {
6274
6374
  allowMultipleH1: false,
6275
6375
  minInitialRank: "h1",
6276
6376
  sectioningRoots: ["dialog", '[role="dialog"]', '[role="alertdialog"]']
@@ -6299,7 +6399,7 @@ function parseMaxInitial(value) {
6299
6399
  }
6300
6400
  class HeadingLevel extends Rule {
6301
6401
  constructor(options) {
6302
- super({ ...defaults$i, ...options });
6402
+ super({ ...defaults$j, ...options });
6303
6403
  this.stack = [];
6304
6404
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
6305
6405
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -6450,6 +6550,46 @@ class HeadingLevel extends Rule {
6450
6550
  }
6451
6551
  }
6452
6552
 
6553
+ const FOCUSABLE_CACHE = Symbol(isFocusable.name);
6554
+ function isDisabled(element, meta) {
6555
+ var _a;
6556
+ if (!((_a = meta.formAssociated) == null ? void 0 : _a.disablable)) {
6557
+ return false;
6558
+ }
6559
+ const disabled = element.matches("[disabled]");
6560
+ if (disabled) {
6561
+ return true;
6562
+ }
6563
+ const fieldset = element.closest("fieldset[disabled]");
6564
+ if (fieldset) {
6565
+ return true;
6566
+ }
6567
+ return false;
6568
+ }
6569
+ function isFocusableImpl(element) {
6570
+ if (isHTMLHidden(element) || isInert(element) || isStyleHidden(element)) {
6571
+ return false;
6572
+ }
6573
+ const { tabIndex, meta } = element;
6574
+ if (tabIndex !== null) {
6575
+ return tabIndex >= 0;
6576
+ }
6577
+ if (!meta) {
6578
+ return false;
6579
+ }
6580
+ if (isDisabled(element, meta)) {
6581
+ return false;
6582
+ }
6583
+ return Boolean(meta == null ? void 0 : meta.focusable);
6584
+ }
6585
+ function isFocusable(element) {
6586
+ const cached = element.cacheGet(FOCUSABLE_CACHE);
6587
+ if (cached) {
6588
+ return cached;
6589
+ }
6590
+ return element.cacheSet(FOCUSABLE_CACHE, isFocusableImpl(element));
6591
+ }
6592
+
6453
6593
  class HiddenFocusable extends Rule {
6454
6594
  documentation(context) {
6455
6595
  const byParent = context === "parent" ? " In this case it is being hidden by an ancestor with `aria-hidden.`" : "";
@@ -6463,7 +6603,8 @@ class HiddenFocusable extends Rule {
6463
6603
  "To fix this either:",
6464
6604
  " - Remove `aria-hidden`.",
6465
6605
  " - Remove the element from the DOM instead.",
6466
- " - Use the `hidden` attribute or similar means to hide the element."
6606
+ ' - Use `tabindex="-1"` to remove the element from tab order.',
6607
+ " - Use `hidden`, `inert` or similar means to hide or disable the element."
6467
6608
  ].join("\n"),
6468
6609
  url: "https://html-validate.org/rules/hidden-focusable.html"
6469
6610
  };
@@ -6474,21 +6615,13 @@ class HiddenFocusable extends Rule {
6474
6615
  this.on("dom:ready", (event) => {
6475
6616
  const { document } = event;
6476
6617
  for (const element of document.querySelectorAll(selector)) {
6477
- if (isHTMLHidden(element) || isStyleHidden(element)) {
6478
- continue;
6479
- }
6480
- if (isAriaHidden(element)) {
6481
- this.validateElement(element);
6618
+ if (isFocusable(element) && isAriaHidden(element)) {
6619
+ this.reportElement(element);
6482
6620
  }
6483
6621
  }
6484
6622
  });
6485
6623
  }
6486
- validateElement(element) {
6487
- const { meta } = element;
6488
- const tabindex = element.getAttribute("tabindex");
6489
- if (meta && !meta.focusable && !tabindex) {
6490
- return;
6491
- }
6624
+ reportElement(element) {
6492
6625
  const attribute = element.getAttribute("aria-hidden");
6493
6626
  const message = attribute ? `aria-hidden cannot be used on focusable elements` : `aria-hidden cannot be used on focusable elements (hidden by ancestor element)`;
6494
6627
  const location = attribute ? attribute.keyLocation : element.location;
@@ -6502,43 +6635,35 @@ class HiddenFocusable extends Rule {
6502
6635
  }
6503
6636
  }
6504
6637
 
6505
- const defaults$h = {
6638
+ const defaults$i = {
6506
6639
  pattern: "kebabcase"
6507
6640
  };
6508
- class IdPattern extends Rule {
6641
+ class IdPattern extends BasePatternRule {
6509
6642
  constructor(options) {
6510
- super({ ...defaults$h, ...options });
6511
- this.pattern = parsePattern(this.options.pattern);
6643
+ super("id", { ...defaults$i, ...options });
6512
6644
  }
6513
6645
  static schema() {
6514
- return {
6515
- pattern: {
6516
- type: "string"
6517
- }
6518
- };
6646
+ return BasePatternRule.schema();
6519
6647
  }
6520
- documentation() {
6521
- const pattern = describePattern(this.options.pattern);
6648
+ documentation(context) {
6522
6649
  return {
6523
- description: `For consistency all IDs are required to match the pattern ${pattern}.`,
6650
+ description: this.description(context),
6524
6651
  url: "https://html-validate.org/rules/id-pattern.html"
6525
6652
  };
6526
6653
  }
6527
6654
  setup() {
6528
6655
  this.on("attr", (event) => {
6529
- var _a;
6530
- if (event.key.toLowerCase() !== "id") {
6656
+ const { target, key, value, valueLocation } = event;
6657
+ if (key.toLowerCase() !== "id") {
6531
6658
  return;
6532
6659
  }
6533
- if (event.value instanceof DynamicValue) {
6660
+ if (value instanceof DynamicValue) {
6534
6661
  return;
6535
6662
  }
6536
- if (!((_a = event.value) == null ? void 0 : _a.match(this.pattern))) {
6537
- const value = event.value ?? "";
6538
- const pattern = this.pattern.toString();
6539
- const message = `ID "${value}" does not match required pattern "${pattern}"`;
6540
- this.report(event.target, message, event.valueLocation);
6663
+ if (value === null) {
6664
+ return;
6541
6665
  }
6666
+ this.validateValue(target, value, valueLocation);
6542
6667
  });
6543
6668
  }
6544
6669
  }
@@ -6777,7 +6902,7 @@ class InputMissingLabel extends Rule {
6777
6902
  });
6778
6903
  }
6779
6904
  validateInput(root, elem) {
6780
- if (isHTMLHidden(elem) || isAriaHidden(elem)) {
6905
+ if (!inAccessibilityTree(elem)) {
6781
6906
  return;
6782
6907
  }
6783
6908
  if (isIgnored(elem)) {
@@ -6810,7 +6935,7 @@ class InputMissingLabel extends Rule {
6810
6935
  * Reports error if none of the labels are accessible.
6811
6936
  */
6812
6937
  validateLabel(root, elem, labels) {
6813
- const visible = labels.filter(isVisible);
6938
+ const visible = labels.filter(inAccessibilityTree);
6814
6939
  if (visible.length === 0) {
6815
6940
  this.report(elem, `<${elem.tagName}> element has <label> but <label> element is hidden`);
6816
6941
  return;
@@ -6820,10 +6945,6 @@ class InputMissingLabel extends Rule {
6820
6945
  }
6821
6946
  }
6822
6947
  }
6823
- function isVisible(elem) {
6824
- const hidden = isHTMLHidden(elem) || isAriaHidden(elem);
6825
- return !hidden;
6826
- }
6827
6948
  function findLabelById(root, id) {
6828
6949
  if (!id)
6829
6950
  return [];
@@ -6840,12 +6961,12 @@ function findLabelByParent(el) {
6840
6961
  return [];
6841
6962
  }
6842
6963
 
6843
- const defaults$g = {
6964
+ const defaults$h = {
6844
6965
  maxlength: 70
6845
6966
  };
6846
6967
  class LongTitle extends Rule {
6847
6968
  constructor(options) {
6848
- super({ ...defaults$g, ...options });
6969
+ super({ ...defaults$h, ...options });
6849
6970
  this.maxlength = this.options.maxlength;
6850
6971
  }
6851
6972
  static schema() {
@@ -6874,12 +6995,12 @@ class LongTitle extends Rule {
6874
6995
  }
6875
6996
  }
6876
6997
 
6877
- const defaults$f = {
6998
+ const defaults$g = {
6878
6999
  allowLongDelay: false
6879
7000
  };
6880
7001
  class MetaRefresh extends Rule {
6881
7002
  constructor(options) {
6882
- super({ ...defaults$f, ...options });
7003
+ super({ ...defaults$g, ...options });
6883
7004
  }
6884
7005
  documentation() {
6885
7006
  return {
@@ -7067,6 +7188,45 @@ class MultipleLabeledControls extends Rule {
7067
7188
  }
7068
7189
  }
7069
7190
 
7191
+ const defaults$f = {
7192
+ pattern: "camelcase"
7193
+ };
7194
+ class NamePattern extends BasePatternRule {
7195
+ constructor(options) {
7196
+ super("name", { ...defaults$f, ...options });
7197
+ }
7198
+ static schema() {
7199
+ return BasePatternRule.schema();
7200
+ }
7201
+ documentation(context) {
7202
+ return {
7203
+ description: this.description(context),
7204
+ url: "https://html-validate.org/rules/name-pattern.html"
7205
+ };
7206
+ }
7207
+ setup() {
7208
+ this.on("attr", (event) => {
7209
+ var _a;
7210
+ const { target, key, value, valueLocation } = event;
7211
+ const { meta } = target;
7212
+ if (!((_a = meta == null ? void 0 : meta.formAssociated) == null ? void 0 : _a.listed)) {
7213
+ return;
7214
+ }
7215
+ if (key.toLowerCase() !== "name") {
7216
+ return;
7217
+ }
7218
+ if (value instanceof DynamicValue) {
7219
+ return;
7220
+ }
7221
+ if (value === null) {
7222
+ return;
7223
+ }
7224
+ const name = value.endsWith("[]") ? value.slice(0, -2) : value;
7225
+ this.validateValue(target, name, valueLocation);
7226
+ });
7227
+ }
7228
+ }
7229
+
7070
7230
  const abstractRoles = [
7071
7231
  "command",
7072
7232
  "composite",
@@ -10048,6 +10208,7 @@ const bundledRules = {
10048
10208
  "meta-refresh": MetaRefresh,
10049
10209
  "missing-doctype": MissingDoctype,
10050
10210
  "multiple-labeled-controls": MultipleLabeledControls,
10211
+ "name-pattern": NamePattern,
10051
10212
  "no-abstract-role": NoAbstractRole,
10052
10213
  "no-autoplay": NoAutoplay,
10053
10214
  "no-conditional-comment": NoConditionalComment,
@@ -12488,7 +12649,7 @@ class HtmlValidate {
12488
12649
  }
12489
12650
 
12490
12651
  const name = "html-validate";
12491
- const version = "8.15.0";
12652
+ const version = "8.17.0";
12492
12653
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
12493
12654
 
12494
12655
  function definePlugin(plugin) {