html-validate 8.15.0 → 8.16.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 {
@@ -2290,6 +2295,7 @@ class TextNode extends DOMNode {
2290
2295
  }
2291
2296
 
2292
2297
  const ROLE = Symbol("role");
2298
+ const TABINDEX = Symbol("tabindex");
2293
2299
  var NodeClosed = /* @__PURE__ */ ((NodeClosed2) => {
2294
2300
  NodeClosed2[NodeClosed2["Open"] = 0] = "Open";
2295
2301
  NodeClosed2[NodeClosed2["EndTag"] = 1] = "EndTag";
@@ -2570,6 +2576,43 @@ class HtmlElement extends DOMNode {
2570
2576
  }
2571
2577
  this.attr[key].push(new Attribute(key, value, keyLocation, valueLocation, originalAttribute));
2572
2578
  }
2579
+ /**
2580
+ * Get parsed tabindex for this element.
2581
+ *
2582
+ * - If `tabindex` attribute is not present `null` is returned.
2583
+ * - If attribute value is omitted or the empty string `null` is returned.
2584
+ * - If attribute value cannot be parsed `null` is returned.
2585
+ * - If attribute value is dynamic `0` is returned.
2586
+ * - Otherwise the parsed value is returned.
2587
+ *
2588
+ * This property does *NOT* take into account if the element have a default
2589
+ * `tabindex` (such as `<input>` have). Instead use the `focusable` metadata
2590
+ * property to determine this.
2591
+ *
2592
+ * @public
2593
+ * @since 8.16.0
2594
+ */
2595
+ get tabIndex() {
2596
+ const cached = this.cacheGet(TABINDEX);
2597
+ if (cached !== void 0) {
2598
+ return cached;
2599
+ }
2600
+ const tabindex = this.getAttribute("tabindex");
2601
+ if (!tabindex) {
2602
+ return this.cacheSet(TABINDEX, null);
2603
+ }
2604
+ if (tabindex.value === null) {
2605
+ return this.cacheSet(TABINDEX, null);
2606
+ }
2607
+ if (tabindex.value instanceof DynamicValue) {
2608
+ return this.cacheSet(TABINDEX, 0);
2609
+ }
2610
+ const parsed = parseInt(tabindex.value, 10);
2611
+ if (isNaN(parsed)) {
2612
+ return this.cacheSet(TABINDEX, null);
2613
+ }
2614
+ return this.cacheSet(TABINDEX, parsed);
2615
+ }
2573
2616
  /**
2574
2617
  * Get a list of all attributes on this node.
2575
2618
  */
@@ -3366,6 +3409,7 @@ function isKeywordIgnored(options, keyword, matcher = (list, it) => list.include
3366
3409
 
3367
3410
  const ARIA_HIDDEN_CACHE = Symbol(isAriaHidden.name);
3368
3411
  const HTML_HIDDEN_CACHE = Symbol(isHTMLHidden.name);
3412
+ const INERT_CACHE = Symbol(isInert.name);
3369
3413
  const ROLE_PRESENTATION_CACHE = Symbol(isPresentation.name);
3370
3414
  const STYLE_HIDDEN_CACHE = Symbol(isStyleHidden.name);
3371
3415
  function inAccessibilityTree(node) {
@@ -3378,6 +3422,9 @@ function inAccessibilityTree(node) {
3378
3422
  if (isHTMLHidden(node)) {
3379
3423
  return false;
3380
3424
  }
3425
+ if (isInert(node)) {
3426
+ return false;
3427
+ }
3381
3428
  if (isStyleHidden(node)) {
3382
3429
  return false;
3383
3430
  }
@@ -3419,6 +3466,24 @@ function isHTMLHidden(node, details) {
3419
3466
  const result = node.cacheSet(HTML_HIDDEN_CACHE, isHTMLHiddenImpl(node));
3420
3467
  return details ? result : result.byParent || result.bySelf;
3421
3468
  }
3469
+ function isInertImpl(node) {
3470
+ const isInert2 = (node2) => {
3471
+ const inert = node2.getAttribute("inert");
3472
+ return inert !== null && inert.isStatic;
3473
+ };
3474
+ return {
3475
+ byParent: node.parent ? isInert2(node.parent) : false,
3476
+ bySelf: isInert2(node)
3477
+ };
3478
+ }
3479
+ function isInert(node, details) {
3480
+ const cached = node.cacheGet(INERT_CACHE);
3481
+ if (cached) {
3482
+ return details ? cached : cached.byParent || cached.bySelf;
3483
+ }
3484
+ const result = node.cacheSet(INERT_CACHE, isInertImpl(node));
3485
+ return details ? result : result.byParent || result.bySelf;
3486
+ }
3422
3487
  function isStyleHiddenImpl(node) {
3423
3488
  const isHidden = (node2) => {
3424
3489
  const style = node2.getAttribute("style");
@@ -6450,6 +6515,46 @@ class HeadingLevel extends Rule {
6450
6515
  }
6451
6516
  }
6452
6517
 
6518
+ const FOCUSABLE_CACHE = Symbol(isFocusable.name);
6519
+ function isDisabled(element, meta) {
6520
+ var _a;
6521
+ if (!((_a = meta.formAssociated) == null ? void 0 : _a.disablable)) {
6522
+ return false;
6523
+ }
6524
+ const disabled = element.matches("[disabled]");
6525
+ if (disabled) {
6526
+ return true;
6527
+ }
6528
+ const fieldset = element.closest("fieldset[disabled]");
6529
+ if (fieldset) {
6530
+ return true;
6531
+ }
6532
+ return false;
6533
+ }
6534
+ function isFocusableImpl(element) {
6535
+ if (isHTMLHidden(element) || isInert(element) || isStyleHidden(element)) {
6536
+ return false;
6537
+ }
6538
+ const { tabIndex, meta } = element;
6539
+ if (tabIndex !== null) {
6540
+ return tabIndex >= 0;
6541
+ }
6542
+ if (!meta) {
6543
+ return false;
6544
+ }
6545
+ if (isDisabled(element, meta)) {
6546
+ return false;
6547
+ }
6548
+ return Boolean(meta == null ? void 0 : meta.focusable);
6549
+ }
6550
+ function isFocusable(element) {
6551
+ const cached = element.cacheGet(FOCUSABLE_CACHE);
6552
+ if (cached) {
6553
+ return cached;
6554
+ }
6555
+ return element.cacheSet(FOCUSABLE_CACHE, isFocusableImpl(element));
6556
+ }
6557
+
6453
6558
  class HiddenFocusable extends Rule {
6454
6559
  documentation(context) {
6455
6560
  const byParent = context === "parent" ? " In this case it is being hidden by an ancestor with `aria-hidden.`" : "";
@@ -6463,7 +6568,8 @@ class HiddenFocusable extends Rule {
6463
6568
  "To fix this either:",
6464
6569
  " - Remove `aria-hidden`.",
6465
6570
  " - Remove the element from the DOM instead.",
6466
- " - Use the `hidden` attribute or similar means to hide the element."
6571
+ ' - Use `tabindex="-1"` to remove the element from tab order.',
6572
+ " - Use `hidden`, `inert` or similar means to hide or disable the element."
6467
6573
  ].join("\n"),
6468
6574
  url: "https://html-validate.org/rules/hidden-focusable.html"
6469
6575
  };
@@ -6474,21 +6580,13 @@ class HiddenFocusable extends Rule {
6474
6580
  this.on("dom:ready", (event) => {
6475
6581
  const { document } = event;
6476
6582
  for (const element of document.querySelectorAll(selector)) {
6477
- if (isHTMLHidden(element) || isStyleHidden(element)) {
6478
- continue;
6479
- }
6480
- if (isAriaHidden(element)) {
6481
- this.validateElement(element);
6583
+ if (isFocusable(element) && isAriaHidden(element)) {
6584
+ this.reportElement(element);
6482
6585
  }
6483
6586
  }
6484
6587
  });
6485
6588
  }
6486
- validateElement(element) {
6487
- const { meta } = element;
6488
- const tabindex = element.getAttribute("tabindex");
6489
- if (meta && !meta.focusable && !tabindex) {
6490
- return;
6491
- }
6589
+ reportElement(element) {
6492
6590
  const attribute = element.getAttribute("aria-hidden");
6493
6591
  const message = attribute ? `aria-hidden cannot be used on focusable elements` : `aria-hidden cannot be used on focusable elements (hidden by ancestor element)`;
6494
6592
  const location = attribute ? attribute.keyLocation : element.location;
@@ -12488,7 +12586,7 @@ class HtmlValidate {
12488
12586
  }
12489
12587
 
12490
12588
  const name = "html-validate";
12491
- const version = "8.15.0";
12589
+ const version = "8.16.0";
12492
12590
  const bugs = "https://gitlab.com/html-validate/html-validate/issues/new";
12493
12591
 
12494
12592
  function definePlugin(plugin) {