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/cjs/core.js CHANGED
@@ -811,6 +811,10 @@ const definitions = {
811
811
  type: "object",
812
812
  additionalProperties: false,
813
813
  properties: {
814
+ disablable: {
815
+ type: "boolean",
816
+ title: "Disablable elements can be disabled using the disabled attribute."
817
+ },
814
818
  listed: {
815
819
  type: "boolean",
816
820
  title: "Listed elements have a name attribute and is listed in the form and fieldset elements property."
@@ -1136,6 +1140,7 @@ function migrateElement(src) {
1136
1140
  }
1137
1141
  if (src.formAssociated) {
1138
1142
  result.formAssociated = {
1143
+ disablable: Boolean(src.formAssociated.disablable),
1139
1144
  listed: Boolean(src.formAssociated.listed)
1140
1145
  };
1141
1146
  } else {
@@ -1395,10 +1400,14 @@ function expandRegexValue(value) {
1395
1400
  if (value instanceof RegExp) {
1396
1401
  return value;
1397
1402
  }
1398
- const match = value.match(/^\/\^?([^/$]*)\$?\/([i]*)$/);
1403
+ const match = value.match(/^\/(.*(?=\/))\/(i?)$/);
1399
1404
  if (match) {
1400
1405
  const [, expr, flags] = match;
1401
- return new RegExp(`^${expr}$`, flags);
1406
+ if (expr.startsWith("^") || expr.endsWith("$")) {
1407
+ return new RegExp(expr, flags);
1408
+ } else {
1409
+ return new RegExp(`^${expr}$`, flags);
1410
+ }
1402
1411
  } else {
1403
1412
  return value;
1404
1413
  }
@@ -2300,6 +2309,7 @@ class TextNode extends DOMNode {
2300
2309
  }
2301
2310
 
2302
2311
  const ROLE = Symbol("role");
2312
+ const TABINDEX = Symbol("tabindex");
2303
2313
  var NodeClosed = /* @__PURE__ */ ((NodeClosed2) => {
2304
2314
  NodeClosed2[NodeClosed2["Open"] = 0] = "Open";
2305
2315
  NodeClosed2[NodeClosed2["EndTag"] = 1] = "EndTag";
@@ -2580,6 +2590,43 @@ class HtmlElement extends DOMNode {
2580
2590
  }
2581
2591
  this.attr[key].push(new Attribute(key, value, keyLocation, valueLocation, originalAttribute));
2582
2592
  }
2593
+ /**
2594
+ * Get parsed tabindex for this element.
2595
+ *
2596
+ * - If `tabindex` attribute is not present `null` is returned.
2597
+ * - If attribute value is omitted or the empty string `null` is returned.
2598
+ * - If attribute value cannot be parsed `null` is returned.
2599
+ * - If attribute value is dynamic `0` is returned.
2600
+ * - Otherwise the parsed value is returned.
2601
+ *
2602
+ * This property does *NOT* take into account if the element have a default
2603
+ * `tabindex` (such as `<input>` have). Instead use the `focusable` metadata
2604
+ * property to determine this.
2605
+ *
2606
+ * @public
2607
+ * @since 8.16.0
2608
+ */
2609
+ get tabIndex() {
2610
+ const cached = this.cacheGet(TABINDEX);
2611
+ if (cached !== void 0) {
2612
+ return cached;
2613
+ }
2614
+ const tabindex = this.getAttribute("tabindex");
2615
+ if (!tabindex) {
2616
+ return this.cacheSet(TABINDEX, null);
2617
+ }
2618
+ if (tabindex.value === null) {
2619
+ return this.cacheSet(TABINDEX, null);
2620
+ }
2621
+ if (tabindex.value instanceof DynamicValue) {
2622
+ return this.cacheSet(TABINDEX, 0);
2623
+ }
2624
+ const parsed = parseInt(tabindex.value, 10);
2625
+ if (isNaN(parsed)) {
2626
+ return this.cacheSet(TABINDEX, null);
2627
+ }
2628
+ return this.cacheSet(TABINDEX, parsed);
2629
+ }
2583
2630
  /**
2584
2631
  * Get a list of all attributes on this node.
2585
2632
  */
@@ -3376,6 +3423,7 @@ function isKeywordIgnored(options, keyword, matcher = (list, it) => list.include
3376
3423
 
3377
3424
  const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
3378
3425
  const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
3426
+ const INERT_CACHE = Symbol(isInert.name);
3379
3427
  const ROLE_PRESENTATION_CACHE = Symbol(isPresentation.name);
3380
3428
  const STYLE_HIDDEN_CACHE = Symbol(isStyleHidden.name);
3381
3429
  function inAccessibilityTree(node) {
@@ -3388,6 +3436,9 @@ function inAccessibilityTree(node) {
3388
3436
  if (isHTMLHidden(node)) {
3389
3437
  return false;
3390
3438
  }
3439
+ if (isInert(node)) {
3440
+ return false;
3441
+ }
3391
3442
  if (isStyleHidden(node)) {
3392
3443
  return false;
3393
3444
  }
@@ -3429,6 +3480,24 @@ function isHTMLHidden(node, details) {
3429
3480
  const result = node.cacheSet(HTML_HIDDEN_CACHE, isHTMLHiddenImpl(node));
3430
3481
  return details ? result : result.byParent || result.bySelf;
3431
3482
  }
3483
+ function isInertImpl(node) {
3484
+ const isInert2 = (node2) => {
3485
+ const inert = node2.getAttribute("inert");
3486
+ return inert !== null && inert.isStatic;
3487
+ };
3488
+ return {
3489
+ byParent: node.parent ? isInert2(node.parent) : false,
3490
+ bySelf: isInert2(node)
3491
+ };
3492
+ }
3493
+ function isInert(node, details) {
3494
+ const cached = node.cacheGet(INERT_CACHE);
3495
+ if (cached) {
3496
+ return details ? cached : cached.byParent || cached.bySelf;
3497
+ }
3498
+ const result = node.cacheSet(INERT_CACHE, isInertImpl(node));
3499
+ return details ? result : result.byParent || result.bySelf;
3500
+ }
3432
3501
  function isStyleHiddenImpl(node) {
3433
3502
  const isHidden = (node2) => {
3434
3503
  const style = node2.getAttribute("style");
@@ -3854,7 +3923,7 @@ class Rule {
3854
3923
  }
3855
3924
  }
3856
3925
 
3857
- const defaults$w = {
3926
+ const defaults$x = {
3858
3927
  allowExternal: true,
3859
3928
  allowRelative: true,
3860
3929
  allowAbsolute: true,
@@ -3895,7 +3964,7 @@ function matchList(value, list) {
3895
3964
  }
3896
3965
  class AllowedLinks extends Rule {
3897
3966
  constructor(options) {
3898
- super({ ...defaults$w, ...options });
3967
+ super({ ...defaults$x, ...options });
3899
3968
  this.allowExternal = parseAllow(this.options.allowExternal);
3900
3969
  this.allowRelative = parseAllow(this.options.allowRelative);
3901
3970
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -4059,7 +4128,7 @@ class AllowedLinks extends Rule {
4059
4128
  }
4060
4129
  }
4061
4130
 
4062
- const defaults$v = {
4131
+ const defaults$w = {
4063
4132
  accessible: true
4064
4133
  };
4065
4134
  function findByTarget(target, siblings) {
@@ -4089,7 +4158,7 @@ function getDescription$1(context) {
4089
4158
  }
4090
4159
  class AreaAlt extends Rule {
4091
4160
  constructor(options) {
4092
- super({ ...defaults$v, ...options });
4161
+ super({ ...defaults$w, ...options });
4093
4162
  }
4094
4163
  static schema() {
4095
4164
  return {
@@ -4168,7 +4237,7 @@ class AriaHiddenBody extends Rule {
4168
4237
  }
4169
4238
  }
4170
4239
 
4171
- const defaults$u = {
4240
+ const defaults$v = {
4172
4241
  allowAnyNamable: false
4173
4242
  };
4174
4243
  const whitelisted = [
@@ -4210,7 +4279,7 @@ function isValidUsage(target, meta) {
4210
4279
  }
4211
4280
  class AriaLabelMisuse extends Rule {
4212
4281
  constructor(options) {
4213
- super({ ...defaults$u, ...options });
4282
+ super({ ...defaults$v, ...options });
4214
4283
  }
4215
4284
  documentation() {
4216
4285
  const valid = [
@@ -4320,13 +4389,13 @@ class CaseStyle {
4320
4389
  }
4321
4390
  }
4322
4391
 
4323
- const defaults$t = {
4392
+ const defaults$u = {
4324
4393
  style: "lowercase",
4325
4394
  ignoreForeign: true
4326
4395
  };
4327
4396
  class AttrCase extends Rule {
4328
4397
  constructor(options) {
4329
- super({ ...defaults$t, ...options });
4398
+ super({ ...defaults$u, ...options });
4330
4399
  this.style = new CaseStyle(this.options.style, "attr-case");
4331
4400
  }
4332
4401
  static schema() {
@@ -4682,7 +4751,7 @@ class AttrDelimiter extends Rule {
4682
4751
  }
4683
4752
 
4684
4753
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4685
- const defaults$s = {
4754
+ const defaults$t = {
4686
4755
  pattern: DEFAULT_PATTERN,
4687
4756
  ignoreForeign: true
4688
4757
  };
@@ -4714,7 +4783,7 @@ function generateDescription(name, pattern) {
4714
4783
  }
4715
4784
  class AttrPattern extends Rule {
4716
4785
  constructor(options) {
4717
- super({ ...defaults$s, ...options });
4786
+ super({ ...defaults$t, ...options });
4718
4787
  this.pattern = generateRegexp(this.options.pattern);
4719
4788
  }
4720
4789
  static schema() {
@@ -4761,7 +4830,7 @@ class AttrPattern extends Rule {
4761
4830
  }
4762
4831
  }
4763
4832
 
4764
- const defaults$r = {
4833
+ const defaults$s = {
4765
4834
  style: "auto",
4766
4835
  unquoted: false
4767
4836
  };
@@ -4800,7 +4869,7 @@ function describeStyle(style, unquoted) {
4800
4869
  }
4801
4870
  class AttrQuotes extends Rule {
4802
4871
  constructor(options) {
4803
- super({ ...defaults$r, ...options });
4872
+ super({ ...defaults$s, ...options });
4804
4873
  this.style = parseStyle$3(this.options.style);
4805
4874
  }
4806
4875
  static schema() {
@@ -4984,12 +5053,12 @@ class AttributeAllowedValues extends Rule {
4984
5053
  }
4985
5054
  }
4986
5055
 
4987
- const defaults$q = {
5056
+ const defaults$r = {
4988
5057
  style: "omit"
4989
5058
  };
4990
5059
  class AttributeBooleanStyle extends Rule {
4991
5060
  constructor(options) {
4992
- super({ ...defaults$q, ...options });
5061
+ super({ ...defaults$r, ...options });
4993
5062
  this.hasInvalidStyle = parseStyle$2(this.options.style);
4994
5063
  }
4995
5064
  static schema() {
@@ -5056,12 +5125,12 @@ function reportMessage$1(attr, style) {
5056
5125
  return "";
5057
5126
  }
5058
5127
 
5059
- const defaults$p = {
5128
+ const defaults$q = {
5060
5129
  style: "omit"
5061
5130
  };
5062
5131
  class AttributeEmptyStyle extends Rule {
5063
5132
  constructor(options) {
5064
- super({ ...defaults$p, ...options });
5133
+ super({ ...defaults$q, ...options });
5065
5134
  this.hasInvalidStyle = parseStyle$1(this.options.style);
5066
5135
  }
5067
5136
  static schema() {
@@ -5179,64 +5248,95 @@ class AttributeMisuse extends Rule {
5179
5248
  function parsePattern(pattern) {
5180
5249
  switch (pattern) {
5181
5250
  case "kebabcase":
5182
- return /^[a-z0-9-]+$/;
5251
+ return { regexp: /^[a-z0-9-]+$/, description: pattern };
5183
5252
  case "camelcase":
5184
- return /^[a-z][a-zA-Z0-9]+$/;
5253
+ return { regexp: /^[a-z][a-zA-Z0-9]+$/, description: pattern };
5185
5254
  case "underscore":
5186
- return /^[a-z0-9_]+$/;
5187
- default:
5188
- return new RegExp(pattern);
5255
+ return { regexp: /^[a-z0-9_]+$/, description: pattern };
5256
+ default: {
5257
+ const regexp = new RegExp(pattern);
5258
+ return { regexp, description: regexp.toString() };
5259
+ }
5189
5260
  }
5190
5261
  }
5191
- function describePattern(pattern) {
5192
- const regexp = parsePattern(pattern).toString();
5193
- switch (pattern) {
5194
- case "kebabcase":
5195
- case "camelcase":
5196
- case "underscore": {
5197
- return `${regexp} (${pattern})`;
5262
+
5263
+ function toArray$1(value) {
5264
+ return Array.isArray(value) ? value : [value];
5265
+ }
5266
+ class BasePatternRule extends Rule {
5267
+ /**
5268
+ * @param attr - Attribute holding the value.
5269
+ * @param options - Rule options with defaults expanded.
5270
+ */
5271
+ constructor(attr, options) {
5272
+ super(options);
5273
+ const { pattern } = this.options;
5274
+ this.attr = attr;
5275
+ this.patterns = toArray$1(pattern).map((it) => parsePattern(it));
5276
+ }
5277
+ static schema() {
5278
+ return {
5279
+ pattern: {
5280
+ oneOf: [{ type: "array", items: { type: "string" }, minItems: 1 }, { type: "string" }]
5281
+ }
5282
+ };
5283
+ }
5284
+ description(context) {
5285
+ const { attr, patterns } = this;
5286
+ const { value } = context;
5287
+ 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.`;
5288
+ return [
5289
+ lead,
5290
+ "For consistency within the codebase the `${attr}` is required to match one or more of the following patterns:",
5291
+ "",
5292
+ ...patterns.map((it) => `- \`${it.description}\``)
5293
+ ].join("\n");
5294
+ }
5295
+ validateValue(node, value, location) {
5296
+ const { attr, patterns } = this;
5297
+ const matches = patterns.some((it) => it.regexp.test(value));
5298
+ if (matches) {
5299
+ return;
5198
5300
  }
5199
- default:
5200
- return regexp;
5301
+ const allowed = utils_naturalJoin.naturalJoin(patterns.map((it) => `"${it.description}"`));
5302
+ 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}`;
5303
+ this.report({
5304
+ node,
5305
+ message,
5306
+ location,
5307
+ context: {
5308
+ value
5309
+ }
5310
+ });
5201
5311
  }
5202
5312
  }
5203
5313
 
5204
- const defaults$o = {
5314
+ const defaults$p = {
5205
5315
  pattern: "kebabcase"
5206
5316
  };
5207
- class ClassPattern extends Rule {
5317
+ class ClassPattern extends BasePatternRule {
5208
5318
  constructor(options) {
5209
- super({ ...defaults$o, ...options });
5210
- this.pattern = parsePattern(this.options.pattern);
5319
+ super("class", { ...defaults$p, ...options });
5211
5320
  }
5212
5321
  static schema() {
5213
- return {
5214
- pattern: {
5215
- type: "string"
5216
- }
5217
- };
5322
+ return BasePatternRule.schema();
5218
5323
  }
5219
- documentation() {
5220
- const pattern = describePattern(this.options.pattern);
5324
+ documentation(context) {
5221
5325
  return {
5222
- description: `For consistency all classes are required to match the pattern ${pattern}.`,
5326
+ description: this.description(context),
5223
5327
  url: "https://html-validate.org/rules/class-pattern.html"
5224
5328
  };
5225
5329
  }
5226
5330
  setup() {
5227
5331
  this.on("attr", (event) => {
5228
- if (event.key.toLowerCase() !== "class") {
5332
+ const { target, key, value, valueLocation } = event;
5333
+ if (key.toLowerCase() !== "class") {
5229
5334
  return;
5230
5335
  }
5231
- const classes = new DOMTokenList(event.value, event.valueLocation);
5232
- classes.forEach((cur, index) => {
5233
- if (!cur.match(this.pattern)) {
5234
- const location = classes.location(index);
5235
- const pattern = this.pattern.toString();
5236
- const message = `Class "${cur}" does not match required pattern "${pattern}"`;
5237
- this.report(event.target, message, location);
5238
- }
5239
- });
5336
+ const classes = new DOMTokenList(value, valueLocation);
5337
+ for (const { item, location } of classes.iterator()) {
5338
+ this.validateValue(target, item, location);
5339
+ }
5240
5340
  });
5241
5341
  }
5242
5342
  }
@@ -5312,13 +5412,13 @@ class CloseOrder extends Rule {
5312
5412
  }
5313
5413
  }
5314
5414
 
5315
- const defaults$n = {
5415
+ const defaults$o = {
5316
5416
  include: null,
5317
5417
  exclude: null
5318
5418
  };
5319
5419
  class Deprecated extends Rule {
5320
5420
  constructor(options) {
5321
- super({ ...defaults$n, ...options });
5421
+ super({ ...defaults$o, ...options });
5322
5422
  }
5323
5423
  static schema() {
5324
5424
  return {
@@ -5472,12 +5572,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
5472
5572
  }
5473
5573
  };
5474
5574
 
5475
- const defaults$m = {
5575
+ const defaults$n = {
5476
5576
  style: "uppercase"
5477
5577
  };
5478
5578
  class DoctypeStyle extends Rule {
5479
5579
  constructor(options) {
5480
- super({ ...defaults$m, ...options });
5580
+ super({ ...defaults$n, ...options });
5481
5581
  }
5482
5582
  static schema() {
5483
5583
  return {
@@ -5505,12 +5605,12 @@ class DoctypeStyle extends Rule {
5505
5605
  }
5506
5606
  }
5507
5607
 
5508
- const defaults$l = {
5608
+ const defaults$m = {
5509
5609
  style: "lowercase"
5510
5610
  };
5511
5611
  class ElementCase extends Rule {
5512
5612
  constructor(options) {
5513
- super({ ...defaults$l, ...options });
5613
+ super({ ...defaults$m, ...options });
5514
5614
  this.style = new CaseStyle(this.options.style, "element-case");
5515
5615
  }
5516
5616
  static schema() {
@@ -5570,14 +5670,14 @@ class ElementCase extends Rule {
5570
5670
  }
5571
5671
  }
5572
5672
 
5573
- const defaults$k = {
5673
+ const defaults$l = {
5574
5674
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
5575
5675
  whitelist: [],
5576
5676
  blacklist: []
5577
5677
  };
5578
5678
  class ElementName extends Rule {
5579
5679
  constructor(options) {
5580
- super({ ...defaults$k, ...options });
5680
+ super({ ...defaults$l, ...options });
5581
5681
  this.pattern = new RegExp(this.options.pattern);
5582
5682
  }
5583
5683
  static schema() {
@@ -5614,7 +5714,7 @@ class ElementName extends Rule {
5614
5714
  ...context.blacklist.map((cur) => `- ${cur}`)
5615
5715
  ];
5616
5716
  }
5617
- if (context.pattern !== defaults$k.pattern) {
5717
+ if (context.pattern !== defaults$l.pattern) {
5618
5718
  return [
5619
5719
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
5620
5720
  "",
@@ -6103,7 +6203,7 @@ class EmptyTitle extends Rule {
6103
6203
  }
6104
6204
  }
6105
6205
 
6106
- const defaults$j = {
6206
+ const defaults$k = {
6107
6207
  allowArrayBrackets: true,
6108
6208
  shared: ["radio", "button", "reset", "submit"]
6109
6209
  };
@@ -6131,7 +6231,7 @@ function getDocumentation(context) {
6131
6231
  }
6132
6232
  class FormDupName extends Rule {
6133
6233
  constructor(options) {
6134
- super({ ...defaults$j, ...options });
6234
+ super({ ...defaults$k, ...options });
6135
6235
  }
6136
6236
  static schema() {
6137
6237
  return {
@@ -6280,7 +6380,7 @@ class FormDupName extends Rule {
6280
6380
  }
6281
6381
  }
6282
6382
 
6283
- const defaults$i = {
6383
+ const defaults$j = {
6284
6384
  allowMultipleH1: false,
6285
6385
  minInitialRank: "h1",
6286
6386
  sectioningRoots: ["dialog", '[role="dialog"]', '[role="alertdialog"]']
@@ -6309,7 +6409,7 @@ function parseMaxInitial(value) {
6309
6409
  }
6310
6410
  class HeadingLevel extends Rule {
6311
6411
  constructor(options) {
6312
- super({ ...defaults$i, ...options });
6412
+ super({ ...defaults$j, ...options });
6313
6413
  this.stack = [];
6314
6414
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
6315
6415
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Pattern(it));
@@ -6460,6 +6560,46 @@ class HeadingLevel extends Rule {
6460
6560
  }
6461
6561
  }
6462
6562
 
6563
+ const FOCUSABLE_CACHE = Symbol(isFocusable.name);
6564
+ function isDisabled(element, meta) {
6565
+ var _a;
6566
+ if (!((_a = meta.formAssociated) == null ? void 0 : _a.disablable)) {
6567
+ return false;
6568
+ }
6569
+ const disabled = element.matches("[disabled]");
6570
+ if (disabled) {
6571
+ return true;
6572
+ }
6573
+ const fieldset = element.closest("fieldset[disabled]");
6574
+ if (fieldset) {
6575
+ return true;
6576
+ }
6577
+ return false;
6578
+ }
6579
+ function isFocusableImpl(element) {
6580
+ if (isHTMLHidden(element) || isInert(element) || isStyleHidden(element)) {
6581
+ return false;
6582
+ }
6583
+ const { tabIndex, meta } = element;
6584
+ if (tabIndex !== null) {
6585
+ return tabIndex >= 0;
6586
+ }
6587
+ if (!meta) {
6588
+ return false;
6589
+ }
6590
+ if (isDisabled(element, meta)) {
6591
+ return false;
6592
+ }
6593
+ return Boolean(meta == null ? void 0 : meta.focusable);
6594
+ }
6595
+ function isFocusable(element) {
6596
+ const cached = element.cacheGet(FOCUSABLE_CACHE);
6597
+ if (cached) {
6598
+ return cached;
6599
+ }
6600
+ return element.cacheSet(FOCUSABLE_CACHE, isFocusableImpl(element));
6601
+ }
6602
+
6463
6603
  class HiddenFocusable extends Rule {
6464
6604
  documentation(context) {
6465
6605
  const byParent = context === "parent" ? " In this case it is being hidden by an ancestor with `aria-hidden.`" : "";
@@ -6473,7 +6613,8 @@ class HiddenFocusable extends Rule {
6473
6613
  "To fix this either:",
6474
6614
  " - Remove `aria-hidden`.",
6475
6615
  " - Remove the element from the DOM instead.",
6476
- " - Use the `hidden` attribute or similar means to hide the element."
6616
+ ' - Use `tabindex="-1"` to remove the element from tab order.',
6617
+ " - Use `hidden`, `inert` or similar means to hide or disable the element."
6477
6618
  ].join("\n"),
6478
6619
  url: "https://html-validate.org/rules/hidden-focusable.html"
6479
6620
  };
@@ -6484,21 +6625,13 @@ class HiddenFocusable extends Rule {
6484
6625
  this.on("dom:ready", (event) => {
6485
6626
  const { document } = event;
6486
6627
  for (const element of document.querySelectorAll(selector)) {
6487
- if (isHTMLHidden(element) || isStyleHidden(element)) {
6488
- continue;
6489
- }
6490
- if (isAriaHidden(element)) {
6491
- this.validateElement(element);
6628
+ if (isFocusable(element) && isAriaHidden(element)) {
6629
+ this.reportElement(element);
6492
6630
  }
6493
6631
  }
6494
6632
  });
6495
6633
  }
6496
- validateElement(element) {
6497
- const { meta } = element;
6498
- const tabindex = element.getAttribute("tabindex");
6499
- if (meta && !meta.focusable && !tabindex) {
6500
- return;
6501
- }
6634
+ reportElement(element) {
6502
6635
  const attribute = element.getAttribute("aria-hidden");
6503
6636
  const message = attribute ? `aria-hidden cannot be used on focusable elements` : `aria-hidden cannot be used on focusable elements (hidden by ancestor element)`;
6504
6637
  const location = attribute ? attribute.keyLocation : element.location;
@@ -6512,43 +6645,35 @@ class HiddenFocusable extends Rule {
6512
6645
  }
6513
6646
  }
6514
6647
 
6515
- const defaults$h = {
6648
+ const defaults$i = {
6516
6649
  pattern: "kebabcase"
6517
6650
  };
6518
- class IdPattern extends Rule {
6651
+ class IdPattern extends BasePatternRule {
6519
6652
  constructor(options) {
6520
- super({ ...defaults$h, ...options });
6521
- this.pattern = parsePattern(this.options.pattern);
6653
+ super("id", { ...defaults$i, ...options });
6522
6654
  }
6523
6655
  static schema() {
6524
- return {
6525
- pattern: {
6526
- type: "string"
6527
- }
6528
- };
6656
+ return BasePatternRule.schema();
6529
6657
  }
6530
- documentation() {
6531
- const pattern = describePattern(this.options.pattern);
6658
+ documentation(context) {
6532
6659
  return {
6533
- description: `For consistency all IDs are required to match the pattern ${pattern}.`,
6660
+ description: this.description(context),
6534
6661
  url: "https://html-validate.org/rules/id-pattern.html"
6535
6662
  };
6536
6663
  }
6537
6664
  setup() {
6538
6665
  this.on("attr", (event) => {
6539
- var _a;
6540
- if (event.key.toLowerCase() !== "id") {
6666
+ const { target, key, value, valueLocation } = event;
6667
+ if (key.toLowerCase() !== "id") {
6541
6668
  return;
6542
6669
  }
6543
- if (event.value instanceof DynamicValue) {
6670
+ if (value instanceof DynamicValue) {
6544
6671
  return;
6545
6672
  }
6546
- if (!((_a = event.value) == null ? void 0 : _a.match(this.pattern))) {
6547
- const value = event.value ?? "";
6548
- const pattern = this.pattern.toString();
6549
- const message = `ID "${value}" does not match required pattern "${pattern}"`;
6550
- this.report(event.target, message, event.valueLocation);
6673
+ if (value === null) {
6674
+ return;
6551
6675
  }
6676
+ this.validateValue(target, value, valueLocation);
6552
6677
  });
6553
6678
  }
6554
6679
  }
@@ -6787,7 +6912,7 @@ class InputMissingLabel extends Rule {
6787
6912
  });
6788
6913
  }
6789
6914
  validateInput(root, elem) {
6790
- if (isHTMLHidden(elem) || isAriaHidden(elem)) {
6915
+ if (!inAccessibilityTree(elem)) {
6791
6916
  return;
6792
6917
  }
6793
6918
  if (isIgnored(elem)) {
@@ -6820,7 +6945,7 @@ class InputMissingLabel extends Rule {
6820
6945
  * Reports error if none of the labels are accessible.
6821
6946
  */
6822
6947
  validateLabel(root, elem, labels) {
6823
- const visible = labels.filter(isVisible);
6948
+ const visible = labels.filter(inAccessibilityTree);
6824
6949
  if (visible.length === 0) {
6825
6950
  this.report(elem, `<${elem.tagName}> element has <label> but <label> element is hidden`);
6826
6951
  return;
@@ -6830,10 +6955,6 @@ class InputMissingLabel extends Rule {
6830
6955
  }
6831
6956
  }
6832
6957
  }
6833
- function isVisible(elem) {
6834
- const hidden = isHTMLHidden(elem) || isAriaHidden(elem);
6835
- return !hidden;
6836
- }
6837
6958
  function findLabelById(root, id) {
6838
6959
  if (!id)
6839
6960
  return [];
@@ -6850,12 +6971,12 @@ function findLabelByParent(el) {
6850
6971
  return [];
6851
6972
  }
6852
6973
 
6853
- const defaults$g = {
6974
+ const defaults$h = {
6854
6975
  maxlength: 70
6855
6976
  };
6856
6977
  class LongTitle extends Rule {
6857
6978
  constructor(options) {
6858
- super({ ...defaults$g, ...options });
6979
+ super({ ...defaults$h, ...options });
6859
6980
  this.maxlength = this.options.maxlength;
6860
6981
  }
6861
6982
  static schema() {
@@ -6884,12 +7005,12 @@ class LongTitle extends Rule {
6884
7005
  }
6885
7006
  }
6886
7007
 
6887
- const defaults$f = {
7008
+ const defaults$g = {
6888
7009
  allowLongDelay: false
6889
7010
  };
6890
7011
  class MetaRefresh extends Rule {
6891
7012
  constructor(options) {
6892
- super({ ...defaults$f, ...options });
7013
+ super({ ...defaults$g, ...options });
6893
7014
  }
6894
7015
  documentation() {
6895
7016
  return {
@@ -7077,6 +7198,45 @@ class MultipleLabeledControls extends Rule {
7077
7198
  }
7078
7199
  }
7079
7200
 
7201
+ const defaults$f = {
7202
+ pattern: "camelcase"
7203
+ };
7204
+ class NamePattern extends BasePatternRule {
7205
+ constructor(options) {
7206
+ super("name", { ...defaults$f, ...options });
7207
+ }
7208
+ static schema() {
7209
+ return BasePatternRule.schema();
7210
+ }
7211
+ documentation(context) {
7212
+ return {
7213
+ description: this.description(context),
7214
+ url: "https://html-validate.org/rules/name-pattern.html"
7215
+ };
7216
+ }
7217
+ setup() {
7218
+ this.on("attr", (event) => {
7219
+ var _a;
7220
+ const { target, key, value, valueLocation } = event;
7221
+ const { meta } = target;
7222
+ if (!((_a = meta == null ? void 0 : meta.formAssociated) == null ? void 0 : _a.listed)) {
7223
+ return;
7224
+ }
7225
+ if (key.toLowerCase() !== "name") {
7226
+ return;
7227
+ }
7228
+ if (value instanceof DynamicValue) {
7229
+ return;
7230
+ }
7231
+ if (value === null) {
7232
+ return;
7233
+ }
7234
+ const name = value.endsWith("[]") ? value.slice(0, -2) : value;
7235
+ this.validateValue(target, name, valueLocation);
7236
+ });
7237
+ }
7238
+ }
7239
+
7080
7240
  const abstractRoles = [
7081
7241
  "command",
7082
7242
  "composite",
@@ -10058,6 +10218,7 @@ const bundledRules = {
10058
10218
  "meta-refresh": MetaRefresh,
10059
10219
  "missing-doctype": MissingDoctype,
10060
10220
  "multiple-labeled-controls": MultipleLabeledControls,
10221
+ "name-pattern": NamePattern,
10061
10222
  "no-abstract-role": NoAbstractRole,
10062
10223
  "no-autoplay": NoAutoplay,
10063
10224
  "no-conditional-comment": NoConditionalComment,
@@ -12498,7 +12659,7 @@ class HtmlValidate {
12498
12659
  }
12499
12660
 
12500
12661
  const name = "html-validate";
12501
- const version = "8.15.0";
12662
+ const version = "8.17.0";
12502
12663
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
12503
12664
 
12504
12665
  function definePlugin(plugin) {