html-validate 9.6.1 → 9.7.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
@@ -657,6 +657,11 @@ const patternProperties = {
657
657
  }
658
658
  ]
659
659
  },
660
+ templateRoot: {
661
+ title: "Mark element as an element ignoring DOM ancestry, i.e. <template>.",
662
+ description: "The <template> element can contain any elements.",
663
+ type: "boolean"
664
+ },
660
665
  deprecatedAttributes: {
661
666
  title: "List of deprecated attributes",
662
667
  type: "array",
@@ -1098,6 +1103,7 @@ function migrateElement(src) {
1098
1103
  textContent: src.textContent,
1099
1104
  focusable: src.focusable ?? false,
1100
1105
  implicitRole,
1106
+ templateRoot: src.templateRoot === true,
1101
1107
  aria: {
1102
1108
  implicitRole,
1103
1109
  naming: normalizeAriaNaming(src.aria?.naming)
@@ -4104,7 +4110,7 @@ class Rule {
4104
4110
  }
4105
4111
  }
4106
4112
 
4107
- const defaults$x = {
4113
+ const defaults$y = {
4108
4114
  allowExternal: true,
4109
4115
  allowRelative: true,
4110
4116
  allowAbsolute: true,
@@ -4148,7 +4154,7 @@ class AllowedLinks extends Rule {
4148
4154
  allowRelative;
4149
4155
  allowAbsolute;
4150
4156
  constructor(options) {
4151
- super({ ...defaults$x, ...options });
4157
+ super({ ...defaults$y, ...options });
4152
4158
  this.allowExternal = parseAllow(this.options.allowExternal);
4153
4159
  this.allowRelative = parseAllow(this.options.allowRelative);
4154
4160
  this.allowAbsolute = parseAllow(this.options.allowAbsolute);
@@ -4316,7 +4322,7 @@ class AllowedLinks extends Rule {
4316
4322
  }
4317
4323
  }
4318
4324
 
4319
- const defaults$w = {
4325
+ const defaults$x = {
4320
4326
  accessible: true
4321
4327
  };
4322
4328
  function findByTarget(target, siblings) {
@@ -4346,7 +4352,7 @@ function getDescription$1(context) {
4346
4352
  }
4347
4353
  class AreaAlt extends Rule {
4348
4354
  constructor(options) {
4349
- super({ ...defaults$w, ...options });
4355
+ super({ ...defaults$x, ...options });
4350
4356
  }
4351
4357
  static schema() {
4352
4358
  return {
@@ -4425,7 +4431,7 @@ class AriaHiddenBody extends Rule {
4425
4431
  }
4426
4432
  }
4427
4433
 
4428
- const defaults$v = {
4434
+ const defaults$w = {
4429
4435
  allowAnyNamable: false
4430
4436
  };
4431
4437
  const whitelisted = [
@@ -4467,7 +4473,7 @@ function isValidUsage(target, meta) {
4467
4473
  }
4468
4474
  class AriaLabelMisuse extends Rule {
4469
4475
  constructor(options) {
4470
- super({ ...defaults$v, ...options });
4476
+ super({ ...defaults$w, ...options });
4471
4477
  }
4472
4478
  documentation() {
4473
4479
  const valid = [
@@ -4578,14 +4584,14 @@ class CaseStyle {
4578
4584
  }
4579
4585
  }
4580
4586
 
4581
- const defaults$u = {
4587
+ const defaults$v = {
4582
4588
  style: "lowercase",
4583
4589
  ignoreForeign: true
4584
4590
  };
4585
4591
  class AttrCase extends Rule {
4586
4592
  style;
4587
4593
  constructor(options) {
4588
- super({ ...defaults$u, ...options });
4594
+ super({ ...defaults$v, ...options });
4589
4595
  this.style = new CaseStyle(this.options.style, "attr-case");
4590
4596
  }
4591
4597
  static schema() {
@@ -4943,7 +4949,7 @@ class AttrDelimiter extends Rule {
4943
4949
  }
4944
4950
 
4945
4951
  const DEFAULT_PATTERN = "[a-z0-9-:]+";
4946
- const defaults$t = {
4952
+ const defaults$u = {
4947
4953
  pattern: DEFAULT_PATTERN,
4948
4954
  ignoreForeign: true
4949
4955
  };
@@ -4976,7 +4982,7 @@ function generateDescription(name, pattern) {
4976
4982
  class AttrPattern extends Rule {
4977
4983
  pattern;
4978
4984
  constructor(options) {
4979
- super({ ...defaults$t, ...options });
4985
+ super({ ...defaults$u, ...options });
4980
4986
  this.pattern = generateRegexp(this.options.pattern);
4981
4987
  }
4982
4988
  static schema() {
@@ -5023,7 +5029,7 @@ class AttrPattern extends Rule {
5023
5029
  }
5024
5030
  }
5025
5031
 
5026
- const defaults$s = {
5032
+ const defaults$t = {
5027
5033
  style: "auto",
5028
5034
  unquoted: false
5029
5035
  };
@@ -5089,7 +5095,7 @@ class AttrQuotes extends Rule {
5089
5095
  };
5090
5096
  }
5091
5097
  constructor(options) {
5092
- super({ ...defaults$s, ...options });
5098
+ super({ ...defaults$t, ...options });
5093
5099
  this.style = parseStyle$3(this.options.style);
5094
5100
  }
5095
5101
  setup() {
@@ -5246,13 +5252,13 @@ class AttributeAllowedValues extends Rule {
5246
5252
  }
5247
5253
  }
5248
5254
 
5249
- const defaults$r = {
5255
+ const defaults$s = {
5250
5256
  style: "omit"
5251
5257
  };
5252
5258
  class AttributeBooleanStyle extends Rule {
5253
5259
  hasInvalidStyle;
5254
5260
  constructor(options) {
5255
- super({ ...defaults$r, ...options });
5261
+ super({ ...defaults$s, ...options });
5256
5262
  this.hasInvalidStyle = parseStyle$2(this.options.style);
5257
5263
  }
5258
5264
  static schema() {
@@ -5318,13 +5324,13 @@ function reportMessage$1(attr, style) {
5318
5324
  return "";
5319
5325
  }
5320
5326
 
5321
- const defaults$q = {
5327
+ const defaults$r = {
5322
5328
  style: "omit"
5323
5329
  };
5324
5330
  class AttributeEmptyStyle extends Rule {
5325
5331
  hasInvalidStyle;
5326
5332
  constructor(options) {
5327
- super({ ...defaults$q, ...options });
5333
+ super({ ...defaults$r, ...options });
5328
5334
  this.hasInvalidStyle = parseStyle$1(this.options.style);
5329
5335
  }
5330
5336
  static schema() {
@@ -5519,12 +5525,12 @@ class BasePatternRule extends Rule {
5519
5525
  }
5520
5526
  }
5521
5527
 
5522
- const defaults$p = {
5528
+ const defaults$q = {
5523
5529
  pattern: "kebabcase"
5524
5530
  };
5525
5531
  class ClassPattern extends BasePatternRule {
5526
5532
  constructor(options) {
5527
- super("class", { ...defaults$p, ...options });
5533
+ super("class", { ...defaults$q, ...options });
5528
5534
  }
5529
5535
  static schema() {
5530
5536
  return BasePatternRule.schema();
@@ -5671,13 +5677,13 @@ class CloseOrder extends Rule {
5671
5677
  }
5672
5678
  }
5673
5679
 
5674
- const defaults$o = {
5680
+ const defaults$p = {
5675
5681
  include: null,
5676
5682
  exclude: null
5677
5683
  };
5678
5684
  class Deprecated extends Rule {
5679
5685
  constructor(options) {
5680
- super({ ...defaults$o, ...options });
5686
+ super({ ...defaults$p, ...options });
5681
5687
  }
5682
5688
  static schema() {
5683
5689
  return {
@@ -5831,12 +5837,12 @@ let NoStyleTag$1 = class NoStyleTag extends Rule {
5831
5837
  }
5832
5838
  };
5833
5839
 
5834
- const defaults$n = {
5840
+ const defaults$o = {
5835
5841
  style: "uppercase"
5836
5842
  };
5837
5843
  class DoctypeStyle extends Rule {
5838
5844
  constructor(options) {
5839
- super({ ...defaults$n, ...options });
5845
+ super({ ...defaults$o, ...options });
5840
5846
  }
5841
5847
  static schema() {
5842
5848
  return {
@@ -5864,13 +5870,13 @@ class DoctypeStyle extends Rule {
5864
5870
  }
5865
5871
  }
5866
5872
 
5867
- const defaults$m = {
5873
+ const defaults$n = {
5868
5874
  style: "lowercase"
5869
5875
  };
5870
5876
  class ElementCase extends Rule {
5871
5877
  style;
5872
5878
  constructor(options) {
5873
- super({ ...defaults$m, ...options });
5879
+ super({ ...defaults$n, ...options });
5874
5880
  this.style = new CaseStyle(this.options.style, "element-case");
5875
5881
  }
5876
5882
  static schema() {
@@ -5930,7 +5936,7 @@ class ElementCase extends Rule {
5930
5936
  }
5931
5937
  }
5932
5938
 
5933
- const defaults$l = {
5939
+ const defaults$m = {
5934
5940
  pattern: "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$",
5935
5941
  whitelist: [],
5936
5942
  blacklist: []
@@ -5938,7 +5944,7 @@ const defaults$l = {
5938
5944
  class ElementName extends Rule {
5939
5945
  pattern;
5940
5946
  constructor(options) {
5941
- super({ ...defaults$l, ...options });
5947
+ super({ ...defaults$m, ...options });
5942
5948
  this.pattern = new RegExp(this.options.pattern);
5943
5949
  }
5944
5950
  static schema() {
@@ -5975,7 +5981,7 @@ class ElementName extends Rule {
5975
5981
  ...context.blacklist.map((cur) => `- ${cur}`)
5976
5982
  ];
5977
5983
  }
5978
- if (context.pattern !== defaults$l.pattern) {
5984
+ if (context.pattern !== defaults$m.pattern) {
5979
5985
  return [
5980
5986
  `<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
5981
5987
  "",
@@ -6021,7 +6027,8 @@ class ElementName extends Rule {
6021
6027
  }
6022
6028
 
6023
6029
  function isNativeTemplate(node) {
6024
- return Boolean(node.tagName === "template" && node.meta?.scriptSupporting);
6030
+ const { tagName, meta } = node;
6031
+ return Boolean(tagName === "template" && meta?.templateRoot && meta?.scriptSupporting);
6025
6032
  }
6026
6033
  function getTransparentChildren(node, transparent) {
6027
6034
  if (typeof transparent === "boolean") {
@@ -6464,7 +6471,7 @@ class EmptyTitle extends Rule {
6464
6471
  }
6465
6472
  }
6466
6473
 
6467
- const defaults$k = {
6474
+ const defaults$l = {
6468
6475
  allowArrayBrackets: true,
6469
6476
  allowCheckboxDefault: true,
6470
6477
  shared: ["radio", "button", "reset", "submit"]
@@ -6512,7 +6519,7 @@ function getDocumentation(context) {
6512
6519
  }
6513
6520
  class FormDupName extends Rule {
6514
6521
  constructor(options) {
6515
- super({ ...defaults$k, ...options });
6522
+ super({ ...defaults$l, ...options });
6516
6523
  }
6517
6524
  static schema() {
6518
6525
  return {
@@ -6671,7 +6678,7 @@ class FormDupName extends Rule {
6671
6678
  }
6672
6679
  }
6673
6680
 
6674
- const defaults$j = {
6681
+ const defaults$k = {
6675
6682
  allowMultipleH1: false,
6676
6683
  minInitialRank: "h1",
6677
6684
  sectioningRoots: ["dialog", '[role="dialog"]', '[role="alertdialog"]']
@@ -6703,7 +6710,7 @@ class HeadingLevel extends Rule {
6703
6710
  sectionRoots;
6704
6711
  stack = [];
6705
6712
  constructor(options) {
6706
- super({ ...defaults$j, ...options });
6713
+ super({ ...defaults$k, ...options });
6707
6714
  this.minInitialRank = parseMaxInitial(this.options.minInitialRank);
6708
6715
  this.sectionRoots = this.options.sectioningRoots.map((it) => new Compound(it));
6709
6716
  this.stack.push({
@@ -6940,12 +6947,12 @@ class HiddenFocusable extends Rule {
6940
6947
  }
6941
6948
  }
6942
6949
 
6943
- const defaults$i = {
6950
+ const defaults$j = {
6944
6951
  pattern: "kebabcase"
6945
6952
  };
6946
6953
  class IdPattern extends BasePatternRule {
6947
6954
  constructor(options) {
6948
- super("id", { ...defaults$i, ...options });
6955
+ super("id", { ...defaults$j, ...options });
6949
6956
  }
6950
6957
  static schema() {
6951
6958
  return BasePatternRule.schema();
@@ -7263,13 +7270,13 @@ function findLabelByParent(el) {
7263
7270
  return [];
7264
7271
  }
7265
7272
 
7266
- const defaults$h = {
7273
+ const defaults$i = {
7267
7274
  maxlength: 70
7268
7275
  };
7269
7276
  class LongTitle extends Rule {
7270
7277
  maxlength;
7271
7278
  constructor(options) {
7272
- super({ ...defaults$h, ...options });
7279
+ super({ ...defaults$i, ...options });
7273
7280
  this.maxlength = this.options.maxlength;
7274
7281
  }
7275
7282
  static schema() {
@@ -7297,12 +7304,12 @@ class LongTitle extends Rule {
7297
7304
  }
7298
7305
  }
7299
7306
 
7300
- const defaults$g = {
7307
+ const defaults$h = {
7301
7308
  allowLongDelay: false
7302
7309
  };
7303
7310
  class MetaRefresh extends Rule {
7304
7311
  constructor(options) {
7305
- super({ ...defaults$g, ...options });
7312
+ super({ ...defaults$h, ...options });
7306
7313
  }
7307
7314
  documentation() {
7308
7315
  return {
@@ -7487,12 +7494,12 @@ class MultipleLabeledControls extends Rule {
7487
7494
  }
7488
7495
  }
7489
7496
 
7490
- const defaults$f = {
7497
+ const defaults$g = {
7491
7498
  pattern: "camelcase"
7492
7499
  };
7493
7500
  class NamePattern extends BasePatternRule {
7494
7501
  constructor(options) {
7495
- super("name", { ...defaults$f, ...options });
7502
+ super("name", { ...defaults$g, ...options });
7496
7503
  }
7497
7504
  static schema() {
7498
7505
  return BasePatternRule.schema();
@@ -7581,13 +7588,13 @@ class NoAbstractRole extends Rule {
7581
7588
  }
7582
7589
  }
7583
7590
 
7584
- const defaults$e = {
7591
+ const defaults$f = {
7585
7592
  include: null,
7586
7593
  exclude: null
7587
7594
  };
7588
7595
  class NoAutoplay extends Rule {
7589
7596
  constructor(options) {
7590
- super({ ...defaults$e, ...options });
7597
+ super({ ...defaults$f, ...options });
7591
7598
  }
7592
7599
  documentation(context) {
7593
7600
  return {
@@ -7906,14 +7913,14 @@ Omitted end tags can be ambigious for humans to read and many editors have troub
7906
7913
  }
7907
7914
  }
7908
7915
 
7909
- const defaults$d = {
7916
+ const defaults$e = {
7910
7917
  include: null,
7911
7918
  exclude: null,
7912
7919
  allowedProperties: ["display"]
7913
7920
  };
7914
7921
  class NoInlineStyle extends Rule {
7915
7922
  constructor(options) {
7916
- super({ ...defaults$d, ...options });
7923
+ super({ ...defaults$e, ...options });
7917
7924
  }
7918
7925
  static schema() {
7919
7926
  return {
@@ -8099,7 +8106,7 @@ class NoMultipleMain extends Rule {
8099
8106
  }
8100
8107
  }
8101
8108
 
8102
- const defaults$c = {
8109
+ const defaults$d = {
8103
8110
  relaxed: false
8104
8111
  };
8105
8112
  const textRegexp = /([<>]|&(?![a-zA-Z0-9#]+;))/g;
@@ -8117,7 +8124,7 @@ const replacementTable = {
8117
8124
  class NoRawCharacters extends Rule {
8118
8125
  relaxed;
8119
8126
  constructor(options) {
8120
- super({ ...defaults$c, ...options });
8127
+ super({ ...defaults$d, ...options });
8121
8128
  this.relaxed = this.options.relaxed;
8122
8129
  }
8123
8130
  static schema() {
@@ -8290,13 +8297,13 @@ class NoRedundantRole extends Rule {
8290
8297
  }
8291
8298
 
8292
8299
  const xmlns = /^(.+):.+$/;
8293
- const defaults$b = {
8300
+ const defaults$c = {
8294
8301
  ignoreForeign: true,
8295
8302
  ignoreXML: true
8296
8303
  };
8297
8304
  class NoSelfClosing extends Rule {
8298
8305
  constructor(options) {
8299
- super({ ...defaults$b, ...options });
8306
+ super({ ...defaults$c, ...options });
8300
8307
  }
8301
8308
  static schema() {
8302
8309
  return {
@@ -8380,13 +8387,13 @@ class NoTrailingWhitespace extends Rule {
8380
8387
  }
8381
8388
  }
8382
8389
 
8383
- const defaults$a = {
8390
+ const defaults$b = {
8384
8391
  include: null,
8385
8392
  exclude: null
8386
8393
  };
8387
8394
  class NoUnknownElements extends Rule {
8388
8395
  constructor(options) {
8389
- super({ ...defaults$a, ...options });
8396
+ super({ ...defaults$b, ...options });
8390
8397
  }
8391
8398
  static schema() {
8392
8399
  return {
@@ -8492,13 +8499,13 @@ const replacement = {
8492
8499
  reset: '<button type="reset">',
8493
8500
  image: '<button type="button">'
8494
8501
  };
8495
- const defaults$9 = {
8502
+ const defaults$a = {
8496
8503
  include: null,
8497
8504
  exclude: null
8498
8505
  };
8499
8506
  class PreferButton extends Rule {
8500
8507
  constructor(options) {
8501
- super({ ...defaults$9, ...options });
8508
+ super({ ...defaults$a, ...options });
8502
8509
  }
8503
8510
  static schema() {
8504
8511
  return {
@@ -8564,7 +8571,7 @@ class PreferButton extends Rule {
8564
8571
  }
8565
8572
  }
8566
8573
 
8567
- const defaults$8 = {
8574
+ const defaults$9 = {
8568
8575
  mapping: {
8569
8576
  article: "article",
8570
8577
  banner: "header",
@@ -8594,7 +8601,7 @@ const defaults$8 = {
8594
8601
  };
8595
8602
  class PreferNativeElement extends Rule {
8596
8603
  constructor(options) {
8597
- super({ ...defaults$8, ...options });
8604
+ super({ ...defaults$9, ...options });
8598
8605
  }
8599
8606
  static schema() {
8600
8607
  return {
@@ -8708,12 +8715,12 @@ class PreferTbody extends Rule {
8708
8715
  }
8709
8716
  }
8710
8717
 
8711
- const defaults$7 = {
8718
+ const defaults$8 = {
8712
8719
  tags: ["script", "style"]
8713
8720
  };
8714
8721
  class RequireCSPNonce extends Rule {
8715
8722
  constructor(options) {
8716
- super({ ...defaults$7, ...options });
8723
+ super({ ...defaults$8, ...options });
8717
8724
  }
8718
8725
  static schema() {
8719
8726
  return {
@@ -8760,7 +8767,7 @@ class RequireCSPNonce extends Rule {
8760
8767
  }
8761
8768
  }
8762
8769
 
8763
- const defaults$6 = {
8770
+ const defaults$7 = {
8764
8771
  target: "all",
8765
8772
  include: null,
8766
8773
  exclude: null
@@ -8789,7 +8796,7 @@ function linkSupportsSri(node) {
8789
8796
  class RequireSri extends Rule {
8790
8797
  target;
8791
8798
  constructor(options) {
8792
- super({ ...defaults$6, ...options });
8799
+ super({ ...defaults$7, ...options });
8793
8800
  this.target = this.options.target;
8794
8801
  }
8795
8802
  static schema() {
@@ -8962,7 +8969,7 @@ class SvgFocusable extends Rule {
8962
8969
  }
8963
8970
  }
8964
8971
 
8965
- const defaults$5 = {
8972
+ const defaults$6 = {
8966
8973
  characters: [
8967
8974
  { pattern: " ", replacement: "&nbsp;", description: "non-breaking space" },
8968
8975
  { pattern: "-", replacement: "&#8209;", description: "non-breaking hyphen" }
@@ -8994,7 +9001,7 @@ function matchAll(text, regexp) {
8994
9001
  class TelNonBreaking extends Rule {
8995
9002
  regex;
8996
9003
  constructor(options) {
8997
- super({ ...defaults$5, ...options });
9004
+ super({ ...defaults$6, ...options });
8998
9005
  this.regex = constructRegex(this.options.characters);
8999
9006
  }
9000
9007
  static schema() {
@@ -9369,7 +9376,7 @@ class UniqueLandmark extends Rule {
9369
9376
  }
9370
9377
  }
9371
9378
 
9372
- const defaults$4 = {
9379
+ const defaults$5 = {
9373
9380
  ignoreCase: false,
9374
9381
  requireSemicolon: true
9375
9382
  };
@@ -9403,7 +9410,7 @@ function getDescription(context, options) {
9403
9410
  }
9404
9411
  class UnknownCharReference extends Rule {
9405
9412
  constructor(options) {
9406
- super({ ...defaults$4, ...options });
9413
+ super({ ...defaults$5, ...options });
9407
9414
  }
9408
9415
  static schema() {
9409
9416
  return {
@@ -10019,12 +10026,12 @@ class ValidAutocomplete extends Rule {
10019
10026
  }
10020
10027
  }
10021
10028
 
10022
- const defaults$3 = {
10029
+ const defaults$4 = {
10023
10030
  relaxed: false
10024
10031
  };
10025
10032
  class ValidID extends Rule {
10026
10033
  constructor(options) {
10027
- super({ ...defaults$3, ...options });
10034
+ super({ ...defaults$4, ...options });
10028
10035
  }
10029
10036
  static schema() {
10030
10037
  return {
@@ -10130,13 +10137,13 @@ class VoidContent extends Rule {
10130
10137
  }
10131
10138
  }
10132
10139
 
10133
- const defaults$2 = {
10140
+ const defaults$3 = {
10134
10141
  style: "omit"
10135
10142
  };
10136
10143
  class VoidStyle extends Rule {
10137
10144
  style;
10138
10145
  constructor(options) {
10139
- super({ ...defaults$2, ...options });
10146
+ super({ ...defaults$3, ...options });
10140
10147
  this.style = parseStyle(this.options.style);
10141
10148
  }
10142
10149
  static schema() {
@@ -10337,13 +10344,13 @@ class H36 extends Rule {
10337
10344
  }
10338
10345
  }
10339
10346
 
10340
- const defaults$1 = {
10347
+ const defaults$2 = {
10341
10348
  allowEmpty: true,
10342
10349
  alias: []
10343
10350
  };
10344
10351
  class H37 extends Rule {
10345
10352
  constructor(options) {
10346
- super({ ...defaults$1, ...options });
10353
+ super({ ...defaults$2, ...options });
10347
10354
  if (!Array.isArray(this.options.alias)) {
10348
10355
  this.options.alias = [this.options.alias];
10349
10356
  }
@@ -10405,9 +10412,62 @@ class H37 extends Rule {
10405
10412
  }
10406
10413
  }
10407
10414
 
10415
+ const defaults$1 = {
10416
+ strict: false
10417
+ };
10408
10418
  const { enum: validScopes } = html5.th.attributes?.scope;
10409
10419
  const joinedScopes = naturalJoin(validScopes);
10420
+ const td = 0;
10421
+ const th = 1;
10422
+ function getShape(cells) {
10423
+ const rows = cells.length;
10424
+ const cols = cells[0].length;
10425
+ return { rows, cols };
10426
+ }
10427
+ function isSimpleTable(table) {
10428
+ const haveHeadersAttr = table.querySelector("> tr > [headers], > tbody > tr > [headers]");
10429
+ if (haveHeadersAttr) {
10430
+ return false;
10431
+ }
10432
+ const rows = table.querySelectorAll("> tr, > thead > tr, > tbody > tr");
10433
+ if (rows.length === 0) {
10434
+ return false;
10435
+ }
10436
+ const cells = rows.map((tr) => tr.querySelectorAll("> *").map((el) => el.is("th") ? th : td));
10437
+ if (cells[0].length === 0) {
10438
+ return false;
10439
+ }
10440
+ const numColumns = cells[0].length;
10441
+ if (!cells.every((row) => row.length === numColumns)) {
10442
+ return false;
10443
+ }
10444
+ const shape = getShape(cells);
10445
+ const headersPerRow = cells.map((row) => row.reduce((sum, cell) => sum + cell, 0));
10446
+ const headersPerColumn = Array(shape.cols).fill(0).map((_, index) => {
10447
+ return cells.reduce((sum, it) => sum + it[index], 0);
10448
+ });
10449
+ const [firstRow, ...otherRows] = headersPerRow;
10450
+ if (firstRow === shape.cols && otherRows.every((row) => row === 0)) {
10451
+ return true;
10452
+ }
10453
+ const [firstCol, ...otherCols] = headersPerColumn;
10454
+ const haveThead = Boolean(table.querySelector("> thead"));
10455
+ if (firstCol === shape.rows && otherCols.every((col) => col === 0) && !haveThead) {
10456
+ return true;
10457
+ }
10458
+ return false;
10459
+ }
10410
10460
  class H63 extends Rule {
10461
+ constructor(options) {
10462
+ super({ ...defaults$1, ...options });
10463
+ }
10464
+ static schema() {
10465
+ return {
10466
+ strict: {
10467
+ type: "boolean"
10468
+ }
10469
+ };
10470
+ }
10411
10471
  documentation() {
10412
10472
  return {
10413
10473
  description: "H63: Using the scope attribute to associate header cells and data cells in data tables",
@@ -10415,23 +10475,31 @@ class H63 extends Rule {
10415
10475
  };
10416
10476
  }
10417
10477
  setup() {
10418
- this.on("tag:ready", (event) => {
10478
+ const { strict } = this.options;
10479
+ this.on("element:ready", (event) => {
10419
10480
  const node = event.target;
10420
- if (node.tagName !== "th") {
10481
+ if (!node.is("table")) {
10421
10482
  return;
10422
10483
  }
10423
- const scope = node.getAttribute("scope");
10484
+ if (strict || !isSimpleTable(node)) {
10485
+ this.validateTable(node);
10486
+ }
10487
+ });
10488
+ }
10489
+ validateTable(node) {
10490
+ for (const th2 of node.querySelectorAll("th")) {
10491
+ const scope = th2.getAttribute("scope");
10424
10492
  const value = scope?.value;
10425
10493
  if (value instanceof DynamicValue) {
10426
- return;
10494
+ continue;
10427
10495
  }
10428
10496
  if (value && validScopes.includes(value)) {
10429
- return;
10497
+ continue;
10430
10498
  }
10431
10499
  const message = `<th> element must have a valid scope attribute: ${joinedScopes}`;
10432
- const location = scope?.valueLocation ?? scope?.keyLocation ?? node.location;
10433
- this.report(node, message, location);
10434
- });
10500
+ const location = scope?.valueLocation ?? scope?.keyLocation ?? th2.location;
10501
+ this.report(th2, message, location);
10502
+ }
10435
10503
  }
10436
10504
  }
10437
10505
 
@@ -11723,7 +11791,7 @@ class EventHandler {
11723
11791
  }
11724
11792
 
11725
11793
  const name = "html-validate";
11726
- const version = "9.6.1";
11794
+ const version = "9.7.0";
11727
11795
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
11728
11796
 
11729
11797
  function freeze(src) {