praxis-kit 6.2.3 → 6.6.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.
@@ -28,6 +28,9 @@ function isNull(value) {
28
28
  function isNonNull(value) {
29
29
  return value != null;
30
30
  }
31
+ function isNullish(value) {
32
+ return isNull(value) || value === void 0;
33
+ }
31
34
 
32
35
  // ../../lib/primitive/src/utils/type-guards.ts
33
36
  function isObject(value, excludeArrays = false) {
@@ -655,20 +658,29 @@ function isAriaAttributeValidForRole(attr, role) {
655
658
  }
656
659
 
657
660
  // ../../lib/primitive/src/guards/aria/is-aria-role.ts
661
+ function lookupImplicitRole(tag) {
662
+ return IMPLICIT_ROLE_RECORD[tag];
663
+ }
658
664
  function isStrongImplicitRole(tag) {
659
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
660
- return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
665
+ const role = lookupImplicitRole(tag);
666
+ return !isNullish(role) && STRONG_ROLES_SET.has(role);
661
667
  }
662
- function isStandaloneTag(tag) {
663
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
664
- return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
668
+ function hasStandaloneRole(tag) {
669
+ const role = lookupImplicitRole(tag);
670
+ return !isNullish(role) && STANDALONE_ROLES_SET.has(role);
665
671
  }
666
- function getInputImplicitRole(type) {
667
- if (!isString(type) || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
668
- return INPUT_TYPE_ROLE_MAP[type];
672
+ var LIST_ELIGIBLE_INPUT_TYPES = /* @__PURE__ */ new Set(["text", "search", "tel", "url", "email"]);
673
+ function getInputImplicitRole(type, list) {
674
+ if (!isString(type)) return void 0;
675
+ const role = INPUT_TYPE_ROLE_MAP[type];
676
+ if (!role) return void 0;
677
+ if (!isNullish(list) && LIST_ELIGIBLE_INPUT_TYPES.has(type)) {
678
+ return "combobox";
679
+ }
680
+ return role;
669
681
  }
670
682
  function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
671
- const isNamed = isString(ariaLabel) || isString(ariaLabelledBy);
683
+ const isNamed = isString(ariaLabel) && ariaLabel.trim().length > 0 || isString(ariaLabelledBy) && ariaLabelledBy.trim().length > 0;
672
684
  if (!isNamed) return void 0;
673
685
  if (tag === "section") return "region";
674
686
  if (tag === "form") return "form";
@@ -720,7 +732,7 @@ function isTag(...args) {
720
732
  // ../../lib/contract/src/aria/aria-role-policy.ts
721
733
  function getImplicitRole(tag, props) {
722
734
  if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
723
- if (tag === "input") return getInputImplicitRole(props?.type);
735
+ if (tag === "input") return getInputImplicitRole(props?.type, props?.list);
724
736
  if (tag === "img") return props?.alt === "" ? "none" : "img";
725
737
  if (tag === "section" || tag === "form") {
726
738
  return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
@@ -1003,7 +1015,11 @@ var ATTRIBUTE_IGNORED_CODES = {
1003
1015
  max: DiagnosticCode3.HtmlInputMaxIgnoredForType,
1004
1016
  step: DiagnosticCode3.HtmlInputStepIgnoredForType,
1005
1017
  accept: DiagnosticCode3.HtmlInputAcceptIgnoredForType,
1006
- capture: DiagnosticCode3.HtmlInputCaptureIgnoredForType
1018
+ capture: DiagnosticCode3.HtmlInputCaptureIgnoredForType,
1019
+ size: DiagnosticCode3.HtmlInputSizeIgnoredForType,
1020
+ alt: DiagnosticCode3.HtmlInputAltIgnoredForType,
1021
+ height: DiagnosticCode3.HtmlInputHeightIgnoredForType,
1022
+ width: DiagnosticCode3.HtmlInputWidthIgnoredForType
1007
1023
  };
1008
1024
  var HtmlDiagnostics = {
1009
1025
  emptyRole(tag) {
@@ -1237,8 +1253,132 @@ var InvariantBase = class {
1237
1253
  }
1238
1254
  };
1239
1255
 
1240
- // ../../lib/contract/src/aria/polymorphic-validator.ts
1256
+ // ../../lib/contract/src/aria/spec/roles/required-properties.ts
1257
+ var REQUIRED_ARIA_PROPERTIES = {
1258
+ combobox: ["aria-expanded"],
1259
+ option: ["aria-selected"],
1260
+ slider: ["aria-valuenow"],
1261
+ scrollbar: ["aria-controls", "aria-valuenow"],
1262
+ spinbutton: ["aria-valuenow"]
1263
+ };
1264
+
1265
+ // ../../lib/contract/src/aria/spec/roles/name-required.ts
1266
+ var NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1267
+
1268
+ // ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
1241
1269
  var NO_VIOLATIONS = [{ valid: true }];
1270
+ function requiredAttributeByRole(roles, attribute) {
1271
+ return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
1272
+ }
1273
+ function checkRequiredAttributes(requirement, { props, effectiveRole }) {
1274
+ if (!effectiveRole) return NO_VIOLATIONS;
1275
+ const requiredAttributes = requirement.attributesByRole[effectiveRole];
1276
+ if (!requiredAttributes) return NO_VIOLATIONS;
1277
+ const results = [];
1278
+ for (const attribute of requiredAttributes) {
1279
+ if (attribute in props) continue;
1280
+ results.push({
1281
+ valid: false,
1282
+ fixable: false,
1283
+ severity: "warning",
1284
+ attribute,
1285
+ diagnostic: requirement.diagnosticFor(attribute, effectiveRole)
1286
+ });
1287
+ }
1288
+ return results;
1289
+ }
1290
+
1291
+ // ../../lib/contract/src/aria/spec/roles/live-region.ts
1292
+ var LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1293
+ ["alert", "assertive"],
1294
+ ["status", "polite"],
1295
+ ["log", "polite"],
1296
+ ["timer", "off"]
1297
+ ]);
1298
+ var ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
1299
+
1300
+ // ../../lib/contract/src/aria/spec/attributes/aria-value-types.ts
1301
+ var ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1302
+ // Boolean (true | false)
1303
+ ["aria-atomic", { kind: "boolean" }],
1304
+ ["aria-busy", { kind: "boolean" }],
1305
+ ["aria-disabled", { kind: "boolean" }],
1306
+ ["aria-expanded", { kind: "boolean" }],
1307
+ ["aria-hidden", { kind: "boolean" }],
1308
+ ["aria-modal", { kind: "boolean" }],
1309
+ ["aria-multiline", { kind: "boolean" }],
1310
+ ["aria-multiselectable", { kind: "boolean" }],
1311
+ ["aria-readonly", { kind: "boolean" }],
1312
+ ["aria-required", { kind: "boolean" }],
1313
+ ["aria-selected", { kind: "boolean" }],
1314
+ // Tristate (true | false | mixed)
1315
+ ["aria-checked", { kind: "tristate" }],
1316
+ ["aria-pressed", { kind: "tristate" }],
1317
+ // Numeric (any finite number)
1318
+ ["aria-valuenow", { kind: "number" }],
1319
+ ["aria-valuemin", { kind: "number" }],
1320
+ ["aria-valuemax", { kind: "number" }],
1321
+ // Integer with optional range
1322
+ ["aria-level", { kind: "integer", min: 1, max: 6 }],
1323
+ ["aria-posinset", { kind: "integer", min: 1 }],
1324
+ ["aria-setsize", { kind: "integer", min: -1 }],
1325
+ ["aria-rowcount", { kind: "integer", min: -1 }],
1326
+ ["aria-colcount", { kind: "integer", min: -1 }],
1327
+ ["aria-rowindex", { kind: "integer", min: 1 }],
1328
+ ["aria-colindex", { kind: "integer", min: 1 }],
1329
+ ["aria-rowspan", { kind: "integer", min: 0 }],
1330
+ ["aria-colspan", { kind: "integer", min: 0 }],
1331
+ // Enum (specific allowed tokens)
1332
+ ["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
1333
+ [
1334
+ "aria-current",
1335
+ {
1336
+ kind: "enum",
1337
+ values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
1338
+ }
1339
+ ],
1340
+ [
1341
+ "aria-haspopup",
1342
+ {
1343
+ kind: "enum",
1344
+ values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
1345
+ }
1346
+ ],
1347
+ ["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
1348
+ ["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
1349
+ ["aria-orientation", { kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }],
1350
+ ["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
1351
+ ]);
1352
+
1353
+ // ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
1354
+ var VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
1355
+ "additions",
1356
+ "removals",
1357
+ "text",
1358
+ "all"
1359
+ ]);
1360
+
1361
+ // ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
1362
+ var HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1363
+ ["h1", 1],
1364
+ ["h2", 2],
1365
+ ["h3", 3],
1366
+ ["h4", 4],
1367
+ ["h5", 5],
1368
+ ["h6", 6]
1369
+ ]);
1370
+
1371
+ // ../../lib/contract/src/aria/spec/elements/interactive-tags.ts
1372
+ var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
1373
+ "a",
1374
+ "button",
1375
+ "input",
1376
+ "select",
1377
+ "textarea"
1378
+ ]);
1379
+
1380
+ // ../../lib/contract/src/aria/polymorphic-validator.ts
1381
+ var NO_VIOLATIONS2 = [{ valid: true }];
1242
1382
  function isIntrinsicTag(tag) {
1243
1383
  return isString(tag);
1244
1384
  }
@@ -1281,7 +1421,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1281
1421
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1282
1422
  const implicitRole = getImplicitRole(tag, props);
1283
1423
  const hasRole2 = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1284
- if (!hasRole2) return { proceed: false, result: { props, violations: [] } };
1285
1424
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1286
1425
  const workingProps = normalized.normalized ? normalized.result.props : props;
1287
1426
  const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
@@ -1291,6 +1430,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1291
1430
  tag,
1292
1431
  implicitRole,
1293
1432
  effectiveRole,
1433
+ hasRole: hasRole2,
1294
1434
  props: workingProps,
1295
1435
  preExistingViolations,
1296
1436
  context: { tag, props: workingProps, implicitRole, effectiveRole }
@@ -1300,6 +1440,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1300
1440
  const violations = [];
1301
1441
  const fixes = [];
1302
1442
  iterate.forEach(rules, (rule) => {
1443
+ if (isNonNull(rule.tags) && !rule.tags.includes(context.tag)) return;
1303
1444
  iterate.forEach(rule(context), (result) => {
1304
1445
  if (result.valid) return;
1305
1446
  const {
@@ -1325,7 +1466,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1325
1466
  return { violations, fixes };
1326
1467
  }
1327
1468
  static #getRules(context) {
1328
- if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1469
+ if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && LIVE_REGION_ROLES.has(context.effectiveRole)) {
1329
1470
  return _AriaPolicyEngine.#pipeline;
1330
1471
  }
1331
1472
  return _AriaPolicyEngine.#implicitOnlyRules;
@@ -1333,6 +1474,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1333
1474
  static evaluate(tag, props) {
1334
1475
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1335
1476
  if (!derived.proceed) return derived.result;
1477
+ if (!derived.hasRole)
1478
+ return { props: derived.props, violations: [...derived.preExistingViolations] };
1336
1479
  const {
1337
1480
  tag: narrowedTag,
1338
1481
  implicitRole,
@@ -1357,10 +1500,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1357
1500
  props: workingProps,
1358
1501
  preExistingViolations
1359
1502
  } = derived;
1360
- const { violations, fixes } = _AriaPolicyEngine.#runRules(
1361
- [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1362
- context
1363
- );
1503
+ const rules = derived.hasRole ? [..._AriaPolicyEngine.#getRules(context), ...extraRules] : extraRules;
1504
+ const { violations, fixes } = _AriaPolicyEngine.#runRules(rules, context);
1364
1505
  const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1365
1506
  return { props: next, violations: [...preExistingViolations, ...violations] };
1366
1507
  }
@@ -1527,7 +1668,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1527
1668
  implicitRole
1528
1669
  }) {
1529
1670
  const role = props.role;
1530
- if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
1671
+ if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS2;
1531
1672
  if (isStrongImplicitRole(tag) && role === "region") {
1532
1673
  const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
1533
1674
  return [
@@ -1540,11 +1681,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1540
1681
  }
1541
1682
  ];
1542
1683
  }
1543
- return NO_VIOLATIONS;
1684
+ return NO_VIOLATIONS2;
1544
1685
  }
1545
1686
  static #checkRedundantRole({ tag, props, implicitRole }) {
1546
1687
  const role = props.role;
1547
- if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
1688
+ if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS2;
1548
1689
  const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
1549
1690
  return [
1550
1691
  {
@@ -1558,8 +1699,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1558
1699
  }
1559
1700
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1560
1701
  const role = props.role;
1561
- if (role !== "region") return NO_VIOLATIONS;
1562
- if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
1702
+ if (role !== "region") return NO_VIOLATIONS2;
1703
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS2;
1563
1704
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1564
1705
  return [
1565
1706
  {
@@ -1576,7 +1717,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1576
1717
  props,
1577
1718
  effectiveRole
1578
1719
  }) {
1579
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1720
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1580
1721
  const results = [];
1581
1722
  iterate.forEachEntry(props, (key) => {
1582
1723
  if (!key.startsWith("aria-")) return;
@@ -1594,62 +1735,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1594
1735
  return results;
1595
1736
  }
1596
1737
  // ─── ARIA attribute value validation ──────────────────────────────────────
1597
- // Accepted value shapes for typed ARIA attributes.
1598
- // Attributes not in this map are unconstrained (arbitrary string values permitted).
1599
- static #ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1600
- // Boolean (true | false)
1601
- ["aria-atomic", { kind: "boolean" }],
1602
- ["aria-busy", { kind: "boolean" }],
1603
- ["aria-disabled", { kind: "boolean" }],
1604
- ["aria-expanded", { kind: "boolean" }],
1605
- ["aria-hidden", { kind: "boolean" }],
1606
- ["aria-modal", { kind: "boolean" }],
1607
- ["aria-multiline", { kind: "boolean" }],
1608
- ["aria-multiselectable", { kind: "boolean" }],
1609
- ["aria-readonly", { kind: "boolean" }],
1610
- ["aria-required", { kind: "boolean" }],
1611
- ["aria-selected", { kind: "boolean" }],
1612
- // Tristate (true | false | mixed)
1613
- ["aria-checked", { kind: "tristate" }],
1614
- ["aria-pressed", { kind: "tristate" }],
1615
- // Numeric (any finite number)
1616
- ["aria-valuenow", { kind: "number" }],
1617
- ["aria-valuemin", { kind: "number" }],
1618
- ["aria-valuemax", { kind: "number" }],
1619
- // Integer with optional range
1620
- ["aria-level", { kind: "integer", min: 1, max: 6 }],
1621
- ["aria-posinset", { kind: "integer", min: 1 }],
1622
- ["aria-setsize", { kind: "integer", min: -1 }],
1623
- ["aria-rowcount", { kind: "integer", min: -1 }],
1624
- ["aria-colcount", { kind: "integer", min: -1 }],
1625
- ["aria-rowindex", { kind: "integer", min: 1 }],
1626
- ["aria-colindex", { kind: "integer", min: 1 }],
1627
- ["aria-rowspan", { kind: "integer", min: 0 }],
1628
- ["aria-colspan", { kind: "integer", min: 0 }],
1629
- // Enum (specific allowed tokens)
1630
- ["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
1631
- [
1632
- "aria-current",
1633
- {
1634
- kind: "enum",
1635
- values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
1636
- }
1637
- ],
1638
- [
1639
- "aria-haspopup",
1640
- {
1641
- kind: "enum",
1642
- values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
1643
- }
1644
- ],
1645
- ["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
1646
- ["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
1647
- [
1648
- "aria-orientation",
1649
- { kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }
1650
- ],
1651
- ["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
1652
- ]);
1653
1738
  static #isValidAriaValue(value, type) {
1654
1739
  switch (type.kind) {
1655
1740
  case "boolean":
@@ -1694,11 +1779,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1694
1779
  }
1695
1780
  }
1696
1781
  static #checkAriaAttributeValues({ props, effectiveRole }) {
1697
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1782
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1698
1783
  const results = [];
1699
1784
  iterate.forEachEntry(props, (key, value) => {
1700
1785
  if (!key.startsWith("aria-")) return;
1701
- const type = _AriaPolicyEngine.#ARIA_VALUE_TYPES.get(key);
1786
+ const type = ARIA_VALUE_TYPES.get(key);
1702
1787
  if (!isNonNull(type)) return;
1703
1788
  if (_AriaPolicyEngine.#isValidAriaValue(value, type)) return;
1704
1789
  results.push({
@@ -1717,26 +1802,18 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1717
1802
  return results;
1718
1803
  }
1719
1804
  // ─── Heading implicit level ────────────────────────────────────────────────
1720
- static #HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1721
- ["h1", 1],
1722
- ["h2", 2],
1723
- ["h3", 3],
1724
- ["h4", 4],
1725
- ["h5", 5],
1726
- ["h6", 6]
1727
- ]);
1728
1805
  static #checkRedundantAriaLevel({
1729
1806
  tag,
1730
1807
  props,
1731
1808
  effectiveRole
1732
1809
  }) {
1733
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1734
- const implicitLevel = _AriaPolicyEngine.#HEADING_IMPLICIT_LEVELS.get(tag);
1735
- if (!isNonNull(implicitLevel)) return NO_VIOLATIONS;
1810
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1811
+ const implicitLevel = HEADING_IMPLICIT_LEVELS.get(tag);
1812
+ if (!isNonNull(implicitLevel)) return NO_VIOLATIONS2;
1736
1813
  const raw = props["aria-level"];
1737
- if (!isNonNull(raw)) return NO_VIOLATIONS;
1814
+ if (!isNonNull(raw)) return NO_VIOLATIONS2;
1738
1815
  const n = typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : NaN;
1739
- if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS;
1816
+ if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS2;
1740
1817
  return [
1741
1818
  {
1742
1819
  valid: false,
@@ -1749,20 +1826,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1749
1826
  ];
1750
1827
  }
1751
1828
  // ─── Name-required roles ───────────────────────────────────────────────────
1752
- // Roles that always require an accessible name per WAI-ARIA APG.
1753
- // Dialog and landmark names are enforced via contracts (ariaContract) rather than
1754
- // the built-in pipeline so consumers can opt in; img is built in because role=img
1755
- // on any element (including bare <img>) is definitionally useless without a name.
1756
- static #NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1757
1829
  static #checkNameRequiredRoles({
1758
1830
  tag,
1759
1831
  props,
1760
1832
  effectiveRole
1761
1833
  }) {
1762
- if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole))
1763
- return NO_VIOLATIONS;
1764
- if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS;
1765
- if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS;
1834
+ if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return NO_VIOLATIONS2;
1835
+ if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS2;
1836
+ if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS2;
1766
1837
  return [
1767
1838
  {
1768
1839
  valid: false,
@@ -1772,51 +1843,21 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1772
1843
  }
1773
1844
  ];
1774
1845
  }
1775
- // WAI-ARIA 1.2 required states and properties, keyed by role.
1776
- // Source: https://www.w3.org/TR/wai-aria-1.2/#requiredState
1777
- static #REQUIRED_PROPERTIES = /* @__PURE__ */ new Map([
1778
- ["combobox", ["aria-expanded"]],
1779
- ["option", ["aria-selected"]],
1780
- ["slider", ["aria-valuenow"]],
1781
- ["scrollbar", ["aria-controls", "aria-valuenow"]],
1782
- ["spinbutton", ["aria-valuenow"]]
1783
- ]);
1784
- static #checkRequiredAriaProperties({
1785
- props,
1786
- effectiveRole
1787
- }) {
1788
- if (!effectiveRole) return NO_VIOLATIONS;
1789
- const required = _AriaPolicyEngine.#REQUIRED_PROPERTIES.get(effectiveRole);
1790
- if (!isNonNull(required)) return NO_VIOLATIONS;
1791
- const results = [];
1792
- iterate.forEach(required, (attr) => {
1793
- if (attr in props) return;
1794
- results.push({
1795
- valid: false,
1796
- fixable: false,
1797
- severity: "warning",
1798
- attribute: attr,
1799
- diagnostic: AriaDiagnostics.requiredProperty(attr, effectiveRole)
1800
- });
1801
- });
1802
- return results;
1846
+ static #requiredAriaPropertiesRule = {
1847
+ attributesByRole: REQUIRED_ARIA_PROPERTIES,
1848
+ diagnosticFor: (attribute, role) => AriaDiagnostics.requiredProperty(attribute, role)
1849
+ };
1850
+ static #checkRequiredAriaProperties(context) {
1851
+ return checkRequiredAttributes(_AriaPolicyEngine.#requiredAriaPropertiesRule, context);
1803
1852
  }
1804
- // Natively interactive HTML elements — always keyboard-reachable unless explicitly disabled.
1805
- static #INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
1806
- "a",
1807
- "button",
1808
- "input",
1809
- "select",
1810
- "textarea"
1811
- ]);
1812
1853
  // WAI-ARIA 1.2 §6.6: aria-hidden="true" must not be placed on focusable elements.
1813
1854
  static #checkAriaHiddenOnFocusable({ tag, props }) {
1814
- if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS;
1815
- const isInteractive = _AriaPolicyEngine.#INTERACTIVE_TAGS.has(tag);
1855
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS2;
1856
+ const isInteractive = INTERACTIVE_TAGS.has(tag);
1816
1857
  if (!isInteractive) {
1817
1858
  const tabindex = props.tabindex;
1818
1859
  const n = typeof tabindex === "number" ? tabindex : typeof tabindex === "string" ? parseInt(tabindex, 10) : NaN;
1819
- if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS;
1860
+ if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS2;
1820
1861
  }
1821
1862
  return [
1822
1863
  {
@@ -1835,7 +1876,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1835
1876
  props,
1836
1877
  effectiveRole
1837
1878
  }) {
1838
- if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS;
1879
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS2;
1839
1880
  const results = [];
1840
1881
  iterate.forEachEntry(props, (key) => {
1841
1882
  if (!key.startsWith("aria-")) return;
@@ -1851,18 +1892,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1851
1892
  });
1852
1893
  return results;
1853
1894
  }
1854
- // WAI-ARIA live region roles and their implied aria-live politeness values.
1855
- static #LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1856
- ["alert", "assertive"],
1857
- ["status", "polite"],
1858
- ["log", "polite"],
1859
- ["timer", "off"]
1860
- ]);
1861
1895
  static #checkMissingLiveRegion({ effectiveRole, props }) {
1862
- if (!effectiveRole) return NO_VIOLATIONS;
1863
- const impliedLive = _AriaPolicyEngine.#LIVE_REGION_ROLES.get(effectiveRole);
1864
- if (!impliedLive) return NO_VIOLATIONS;
1865
- if ("aria-live" in props) return NO_VIOLATIONS;
1896
+ if (!effectiveRole) return NO_VIOLATIONS2;
1897
+ const impliedLive = LIVE_REGION_ROLES.get(effectiveRole);
1898
+ if (!impliedLive) return NO_VIOLATIONS2;
1899
+ if ("aria-live" in props) return NO_VIOLATIONS2;
1866
1900
  const injectLive = {
1867
1901
  kind: `injectLive:${effectiveRole}`,
1868
1902
  apply: (ctx) => ({
@@ -1881,20 +1915,13 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1881
1915
  }
1882
1916
  ];
1883
1917
  }
1884
- static #checkMissingAtomic({ effectiveRole, props }) {
1885
- if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole))
1886
- return NO_VIOLATIONS;
1887
- if ("aria-atomic" in props) return NO_VIOLATIONS;
1888
- return [
1889
- {
1890
- valid: false,
1891
- fixable: false,
1892
- severity: "warning",
1893
- diagnostic: AriaDiagnostics.missingAtomic(effectiveRole)
1894
- }
1895
- ];
1918
+ static #missingAtomicRule = {
1919
+ attributesByRole: ATOMIC_REQUIREMENTS,
1920
+ diagnosticFor: (_attribute, role) => AriaDiagnostics.missingAtomic(role)
1921
+ };
1922
+ static #checkMissingAtomic(context) {
1923
+ return checkRequiredAttributes(_AriaPolicyEngine.#missingAtomicRule, context);
1896
1924
  }
1897
- static #VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set(["additions", "removals", "text", "all"]);
1898
1925
  // Custom fix rules passed via `options.rules` must be pure functions of (tag, props) — the cache
1899
1926
  // replays stored fixes against new prop objects, so fixes that close over external state will
1900
1927
  // produce inconsistent results on cache hits.
@@ -1908,10 +1935,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1908
1935
  };
1909
1936
  static #checkInvalidAriaRelevant({ props }) {
1910
1937
  const relevant = props["aria-relevant"];
1911
- if (relevant === void 0) return NO_VIOLATIONS;
1912
- if (typeof relevant !== "string") return NO_VIOLATIONS;
1938
+ if (relevant === void 0) return NO_VIOLATIONS2;
1939
+ if (typeof relevant !== "string") return NO_VIOLATIONS2;
1913
1940
  const tokens = relevant.trim().split(/\s+/);
1914
- const invalid = tokens.filter((t) => !_AriaPolicyEngine.#VALID_RELEVANT_TOKENS.has(t));
1941
+ const invalid = tokens.filter((t) => !VALID_RELEVANT_TOKENS.has(t));
1915
1942
  if (invalid.length > 0) {
1916
1943
  return [
1917
1944
  {
@@ -1936,7 +1963,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1936
1963
  }
1937
1964
  ];
1938
1965
  }
1939
- return NO_VIOLATIONS;
1966
+ return NO_VIOLATIONS2;
1940
1967
  }
1941
1968
  };
1942
1969
 
@@ -2256,7 +2283,59 @@ var readonlyProps = ({
2256
2283
  // ../core/src/html/evaluators.ts
2257
2284
  import { warnDiagnostics as warnDiagnostics2 } from "./_shared/diagnostics.js";
2258
2285
 
2259
- // ../core/src/html/input-rules.ts
2286
+ // ../core/src/html/spec/vocabulary/input.ts
2287
+ var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2288
+ var NUMERIC_INPUT_TYPES = [
2289
+ "number",
2290
+ "range",
2291
+ "date",
2292
+ "month",
2293
+ "week",
2294
+ "time",
2295
+ "datetime-local"
2296
+ ];
2297
+ var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
2298
+ ...TEXT_INPUT_TYPES,
2299
+ ...NUMERIC_INPUT_TYPES,
2300
+ "checkbox",
2301
+ "radio",
2302
+ "file",
2303
+ "color",
2304
+ "hidden",
2305
+ "button",
2306
+ "submit",
2307
+ "reset",
2308
+ "image"
2309
+ ]);
2310
+
2311
+ // ../core/src/html/spec/attributes/input.ts
2312
+ var INPUT_ATTRIBUTE_TYPE_POLICIES = [
2313
+ { attribute: "checked", allowedTypes: ["checkbox", "radio"] },
2314
+ { attribute: "multiple", allowedTypes: ["email", "file"] },
2315
+ { attribute: "maxLength", allowedTypes: TEXT_INPUT_TYPES },
2316
+ { attribute: "minLength", allowedTypes: TEXT_INPUT_TYPES },
2317
+ { attribute: "pattern", allowedTypes: TEXT_INPUT_TYPES },
2318
+ { attribute: "min", allowedTypes: NUMERIC_INPUT_TYPES },
2319
+ { attribute: "max", allowedTypes: NUMERIC_INPUT_TYPES },
2320
+ { attribute: "step", allowedTypes: NUMERIC_INPUT_TYPES },
2321
+ { attribute: "accept", allowedTypes: ["file"] },
2322
+ { attribute: "capture", allowedTypes: ["file"] },
2323
+ { attribute: "size", allowedTypes: TEXT_INPUT_TYPES },
2324
+ { attribute: "alt", allowedTypes: ["image"] },
2325
+ { attribute: "height", allowedTypes: ["image"] },
2326
+ { attribute: "width", allowedTypes: ["image"] }
2327
+ ];
2328
+
2329
+ // ../core/src/html/spec/constraints/input.ts
2330
+ var REQUIRED_READONLY_CONFLICT = {
2331
+ props: ["required", "readOnly"],
2332
+ diagnostic: () => InputAccessibilityDiagnostics.requiredReadOnlyConflict()
2333
+ };
2334
+ var INPUT_MUTUALLY_EXCLUSIVE_POLICIES = [
2335
+ REQUIRED_READONLY_CONFLICT
2336
+ ];
2337
+
2338
+ // ../core/src/html/spec/validators/attribute-type-validator.ts
2260
2339
  var DEFAULT_INPUT_TYPE = "text";
2261
2340
  function omit(props, key) {
2262
2341
  const next = { ...props };
@@ -2272,7 +2351,10 @@ function removeAttributeFix(attribute) {
2272
2351
  }
2273
2352
  };
2274
2353
  }
2275
- function inputAttributeRequiresType(attribute, allowedTypes) {
2354
+ function createInputAttributeTypeRule({
2355
+ attribute,
2356
+ allowedTypes
2357
+ }) {
2276
2358
  const rule = ({ tag, props }) => {
2277
2359
  if (tag !== "input" || !(attribute in props)) return [];
2278
2360
  const type = typeof props.type === "string" ? props.type : DEFAULT_INPUT_TYPE;
@@ -2288,31 +2370,30 @@ function inputAttributeRequiresType(attribute, allowedTypes) {
2288
2370
  }
2289
2371
  ];
2290
2372
  };
2291
- return Object.assign(rule, { readsProps: ["type", attribute] });
2373
+ return Object.assign(rule, { readsProps: ["type", attribute], tags: ["input"] });
2374
+ }
2375
+
2376
+ // ../core/src/html/spec/validators/mutually-exclusive-validator.ts
2377
+ function createMutuallyExclusiveRule({
2378
+ props: conflictingProps,
2379
+ diagnostic: createDiagnostic
2380
+ }) {
2381
+ const [first, second] = conflictingProps;
2382
+ const rule = ({ tag, props }) => {
2383
+ if (tag !== "input" || !props[first] || !props[second]) return [];
2384
+ const diagnostic = createDiagnostic();
2385
+ return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2386
+ };
2387
+ return Object.assign(rule, { readsProps: conflictingProps, tags: ["input"] });
2388
+ }
2389
+
2390
+ // ../core/src/html/input-rules.ts
2391
+ var policyByAttribute = Object.fromEntries(
2392
+ INPUT_ATTRIBUTE_TYPE_POLICIES.map((policy) => [policy.attribute, policy])
2393
+ );
2394
+ function policyFor(attribute) {
2395
+ return policyByAttribute[attribute];
2292
2396
  }
2293
- var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2294
- var NUMERIC_INPUT_TYPES = [
2295
- "number",
2296
- "range",
2297
- "date",
2298
- "month",
2299
- "week",
2300
- "time",
2301
- "datetime-local"
2302
- ];
2303
- var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
2304
- ...TEXT_INPUT_TYPES,
2305
- ...NUMERIC_INPUT_TYPES,
2306
- "checkbox",
2307
- "radio",
2308
- "file",
2309
- "color",
2310
- "hidden",
2311
- "button",
2312
- "submit",
2313
- "reset",
2314
- "image"
2315
- ]);
2316
2397
  var supportedInputTypeRule = Object.assign(
2317
2398
  ({ tag, props }) => {
2318
2399
  if (tag !== "input" || typeof props.type !== "string") return [];
@@ -2328,30 +2409,22 @@ var supportedInputTypeRule = Object.assign(
2328
2409
  }
2329
2410
  ];
2330
2411
  },
2331
- { readsProps: ["type"] }
2412
+ { readsProps: ["type"], tags: ["input"] }
2332
2413
  );
2333
- var checkedRequiresCheckableTypeRule = inputAttributeRequiresType("checked", [
2334
- "checkbox",
2335
- "radio"
2336
- ]);
2337
- var multipleRequiresSupportedTypeRule = inputAttributeRequiresType("multiple", [
2338
- "email",
2339
- "file"
2340
- ]);
2341
- var maxLengthRequiresTextTypeRule = inputAttributeRequiresType(
2342
- "maxLength",
2343
- TEXT_INPUT_TYPES
2344
- );
2345
- var minLengthRequiresTextTypeRule = inputAttributeRequiresType(
2346
- "minLength",
2347
- TEXT_INPUT_TYPES
2348
- );
2349
- var patternRequiresTextTypeRule = inputAttributeRequiresType("pattern", TEXT_INPUT_TYPES);
2350
- var minRequiresNumericTypeRule = inputAttributeRequiresType("min", NUMERIC_INPUT_TYPES);
2351
- var maxRequiresNumericTypeRule = inputAttributeRequiresType("max", NUMERIC_INPUT_TYPES);
2352
- var stepRequiresNumericTypeRule = inputAttributeRequiresType("step", NUMERIC_INPUT_TYPES);
2353
- var acceptRequiresFileTypeRule = inputAttributeRequiresType("accept", ["file"]);
2354
- var captureRequiresFileTypeRule = inputAttributeRequiresType("capture", ["file"]);
2414
+ var checkedRequiresCheckableTypeRule = createInputAttributeTypeRule(policyFor("checked"));
2415
+ var multipleRequiresSupportedTypeRule = createInputAttributeTypeRule(policyFor("multiple"));
2416
+ var maxLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("maxLength"));
2417
+ var minLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("minLength"));
2418
+ var patternRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("pattern"));
2419
+ var minRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("min"));
2420
+ var maxRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("max"));
2421
+ var stepRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("step"));
2422
+ var acceptRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("accept"));
2423
+ var captureRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("capture"));
2424
+ var sizeRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("size"));
2425
+ var altRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("alt"));
2426
+ var heightRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("height"));
2427
+ var widthRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("width"));
2355
2428
  var inputAccessibleNameRule = Object.assign(
2356
2429
  ({ tag, props }) => {
2357
2430
  if (tag !== "input" || props.type === "hidden") return [];
@@ -2360,7 +2433,10 @@ var inputAccessibleNameRule = Object.assign(
2360
2433
  const diagnostic = hasPlaceholder ? InputAccessibilityDiagnostics.placeholderIsNotLabel() : InputAccessibilityDiagnostics.missingAccessibleName();
2361
2434
  return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2362
2435
  },
2363
- { readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"] }
2436
+ {
2437
+ readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"],
2438
+ tags: ["input"]
2439
+ }
2364
2440
  );
2365
2441
  var PASSWORD_AUTOCOMPLETE_VALUES = ["current-password", "new-password"];
2366
2442
  var passwordAutocompleteRule = Object.assign(
@@ -2372,16 +2448,9 @@ var passwordAutocompleteRule = Object.assign(
2372
2448
  const diagnostic = InputAccessibilityDiagnostics.passwordMissingAutocomplete();
2373
2449
  return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2374
2450
  },
2375
- { readsProps: ["type", "autoComplete"] }
2376
- );
2377
- var requiredReadOnlyConflictRule = Object.assign(
2378
- ({ tag, props }) => {
2379
- if (tag !== "input" || !props.required || !props.readOnly) return [];
2380
- const diagnostic = InputAccessibilityDiagnostics.requiredReadOnlyConflict();
2381
- return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2382
- },
2383
- { readsProps: ["required", "readOnly"] }
2451
+ { readsProps: ["type", "autoComplete"], tags: ["input"] }
2384
2452
  );
2453
+ var requiredReadOnlyConflictRule = createMutuallyExclusiveRule(REQUIRED_READONLY_CONFLICT);
2385
2454
  var INPUT_RULES = [
2386
2455
  supportedInputTypeRule,
2387
2456
  checkedRequiresCheckableTypeRule,
@@ -2394,11 +2463,132 @@ var INPUT_RULES = [
2394
2463
  stepRequiresNumericTypeRule,
2395
2464
  acceptRequiresFileTypeRule,
2396
2465
  captureRequiresFileTypeRule,
2466
+ sizeRequiresTextTypeRule,
2467
+ altRequiresImageTypeRule,
2468
+ heightRequiresImageTypeRule,
2469
+ widthRequiresImageTypeRule,
2397
2470
  inputAccessibleNameRule,
2398
2471
  passwordAutocompleteRule,
2399
2472
  requiredReadOnlyConflictRule
2400
2473
  ];
2401
2474
 
2475
+ // ../core/src/html/spec/types.ts
2476
+ function definePropRolePolicy(prop, map2, fallback) {
2477
+ return { kind: "byProp", prop, map: map2, fallback };
2478
+ }
2479
+ function resolveAllowedRoles(spec, props) {
2480
+ const policy = spec.allowedRoles;
2481
+ if (!policy) return void 0;
2482
+ switch (policy.kind) {
2483
+ case "fixed":
2484
+ return policy.roles;
2485
+ case "byProp": {
2486
+ const value = typeof props[policy.prop] === "string" ? props[policy.prop] : policy.fallback;
2487
+ return policy.map[value];
2488
+ }
2489
+ case "dynamic":
2490
+ return policy.resolve({ props });
2491
+ }
2492
+ }
2493
+
2494
+ // ../core/src/html/spec/roles/input.ts
2495
+ var ALLOWED_INPUT_ROLES = {
2496
+ checkbox: ["menuitemcheckbox", "option", "switch", "button"],
2497
+ radio: ["menuitemradio"],
2498
+ range: [],
2499
+ number: [],
2500
+ search: ["combobox"],
2501
+ text: ["combobox", "searchbox", "spinbutton"],
2502
+ email: ["combobox"],
2503
+ tel: ["combobox"],
2504
+ url: ["combobox"],
2505
+ button: [
2506
+ "link",
2507
+ "menuitem",
2508
+ "menuitemcheckbox",
2509
+ "menuitemradio",
2510
+ "option",
2511
+ "radio",
2512
+ "switch",
2513
+ "tab"
2514
+ ],
2515
+ submit: [
2516
+ "link",
2517
+ "menuitem",
2518
+ "menuitemcheckbox",
2519
+ "menuitemradio",
2520
+ "option",
2521
+ "radio",
2522
+ "switch",
2523
+ "tab"
2524
+ ],
2525
+ reset: [
2526
+ "link",
2527
+ "menuitem",
2528
+ "menuitemcheckbox",
2529
+ "menuitemradio",
2530
+ "option",
2531
+ "radio",
2532
+ "switch",
2533
+ "tab"
2534
+ ],
2535
+ image: [
2536
+ "link",
2537
+ "menuitem",
2538
+ "menuitemcheckbox",
2539
+ "menuitemradio",
2540
+ "option",
2541
+ "radio",
2542
+ "switch",
2543
+ "tab"
2544
+ ],
2545
+ hidden: []
2546
+ };
2547
+
2548
+ // ../core/src/html/spec/elements/input.ts
2549
+ var inputElementSpec = {
2550
+ tag: "input",
2551
+ allowedRoles: definePropRolePolicy("type", ALLOWED_INPUT_ROLES, "text"),
2552
+ attributes: INPUT_ATTRIBUTE_TYPE_POLICIES,
2553
+ mutuallyExclusive: INPUT_MUTUALLY_EXCLUSIVE_POLICIES
2554
+ };
2555
+
2556
+ // ../core/src/html/spec/roles/img.ts
2557
+ var IMG_NAMED_ROLES = [
2558
+ "button",
2559
+ "checkbox",
2560
+ "link",
2561
+ "menuitem",
2562
+ "menuitemcheckbox",
2563
+ "menuitemradio",
2564
+ "option",
2565
+ "progressbar",
2566
+ "scrollbar",
2567
+ "separator",
2568
+ "slider",
2569
+ "switch",
2570
+ "tab",
2571
+ "treeitem"
2572
+ ];
2573
+
2574
+ // ../core/src/html/spec/elements/img.ts
2575
+ var imgElementSpec = {
2576
+ tag: "img",
2577
+ allowedRoles: {
2578
+ kind: "dynamic",
2579
+ resolve: ({ props }) => props.alt === "" ? [] : IMG_NAMED_ROLES
2580
+ }
2581
+ };
2582
+
2583
+ // ../core/src/html/spec/roles/table.ts
2584
+ var ALLOWED_TABLE_ROLES = ["grid", "treegrid"];
2585
+
2586
+ // ../core/src/html/spec/elements/table.ts
2587
+ var tableElementSpec = {
2588
+ tag: "table",
2589
+ allowedRoles: { kind: "fixed", roles: ALLOWED_TABLE_ROLES }
2590
+ };
2591
+
2402
2592
  // ../core/src/html/role-restrictions.ts
2403
2593
  var ALLOWED_ROLES = {
2404
2594
  article: ["application", "document", "feed", "main", "none", "presentation", "region"],
@@ -2471,86 +2661,17 @@ var ALLOWED_ROLES = {
2471
2661
  "tab",
2472
2662
  "treeitem"
2473
2663
  ],
2474
- table: ["grid", "treegrid"],
2475
2664
  dialog: ["alertdialog"],
2476
2665
  fieldset: ["none", "presentation", "radiogroup"]
2477
2666
  };
2478
- var IMG_NAMED_ROLES = [
2479
- "button",
2480
- "checkbox",
2481
- "link",
2482
- "menuitem",
2483
- "menuitemcheckbox",
2484
- "menuitemradio",
2485
- "option",
2486
- "progressbar",
2487
- "scrollbar",
2488
- "separator",
2489
- "slider",
2490
- "switch",
2491
- "tab",
2492
- "treeitem"
2493
- ];
2494
- var ALLOWED_INPUT_ROLES = {
2495
- checkbox: ["menuitemcheckbox", "option", "switch", "button"],
2496
- radio: ["menuitemradio"],
2497
- range: [],
2498
- number: [],
2499
- search: ["combobox"],
2500
- text: ["combobox", "searchbox", "spinbutton"],
2501
- email: ["combobox"],
2502
- tel: ["combobox"],
2503
- url: ["combobox"],
2504
- button: [
2505
- "link",
2506
- "menuitem",
2507
- "menuitemcheckbox",
2508
- "menuitemradio",
2509
- "option",
2510
- "radio",
2511
- "switch",
2512
- "tab"
2513
- ],
2514
- submit: [
2515
- "link",
2516
- "menuitem",
2517
- "menuitemcheckbox",
2518
- "menuitemradio",
2519
- "option",
2520
- "radio",
2521
- "switch",
2522
- "tab"
2523
- ],
2524
- reset: [
2525
- "link",
2526
- "menuitem",
2527
- "menuitemcheckbox",
2528
- "menuitemradio",
2529
- "option",
2530
- "radio",
2531
- "switch",
2532
- "tab"
2533
- ],
2534
- image: [
2535
- "link",
2536
- "menuitem",
2537
- "menuitemcheckbox",
2538
- "menuitemradio",
2539
- "option",
2540
- "radio",
2541
- "switch",
2542
- "tab"
2543
- ],
2544
- hidden: []
2667
+ var ELEMENT_SPECS = {
2668
+ input: inputElementSpec,
2669
+ img: imgElementSpec,
2670
+ table: tableElementSpec
2545
2671
  };
2546
2672
  function getAllowedRoles(tag, props) {
2547
- if (tag === "input") {
2548
- const type = typeof props.type === "string" ? props.type : "text";
2549
- return ALLOWED_INPUT_ROLES[type];
2550
- }
2551
- if (tag === "img") {
2552
- return props.alt === "" ? [] : IMG_NAMED_ROLES;
2553
- }
2673
+ const spec = ELEMENT_SPECS[tag];
2674
+ if (spec) return resolveAllowedRoles(spec, props);
2554
2675
  return ALLOWED_ROLES[tag];
2555
2676
  }
2556
2677
  var removeRoleFix = {
@@ -2591,21 +2712,24 @@ var removeLandmarkRoleOverride = {
2591
2712
  return { applied: true, next: rest, previous: props };
2592
2713
  }
2593
2714
  };
2594
- function landmarkRoleRule({ tag, props, implicitRole }) {
2595
- if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2596
- const role = props.role;
2597
- if (!role || role === implicitRole) return [];
2598
- const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2599
- return [
2600
- {
2601
- valid: false,
2602
- fixable: true,
2603
- severity: diagnostic.severity,
2604
- fix: removeLandmarkRoleOverride,
2605
- diagnostic
2606
- }
2607
- ];
2608
- }
2715
+ var landmarkRoleRule = Object.assign(
2716
+ ({ tag, props, implicitRole }) => {
2717
+ if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2718
+ const role = props.role;
2719
+ if (!role || role === implicitRole) return [];
2720
+ const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2721
+ return [
2722
+ {
2723
+ valid: false,
2724
+ fixable: true,
2725
+ severity: diagnostic.severity,
2726
+ fix: removeLandmarkRoleOverride,
2727
+ diagnostic
2728
+ }
2729
+ ];
2730
+ },
2731
+ { tags: [...LANDMARK_TAG_SET] }
2732
+ );
2609
2733
  function requireAccessibleName({ tag, props }) {
2610
2734
  if ("aria-label" in props || "aria-labelledby" in props) return [];
2611
2735
  return [
@@ -2618,10 +2742,13 @@ function requireAccessibleName({ tag, props }) {
2618
2742
  ];
2619
2743
  }
2620
2744
  var NAMED_LANDMARK_TAGS = /* @__PURE__ */ new Set(["nav", "aside"]);
2621
- function landmarkNameAdvisory(ctx) {
2622
- if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2623
- return requireAccessibleName(ctx);
2624
- }
2745
+ var landmarkNameAdvisory = Object.assign(
2746
+ (ctx) => {
2747
+ if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2748
+ return requireAccessibleName(ctx);
2749
+ },
2750
+ { tags: [...NAMED_LANDMARK_TAGS] }
2751
+ );
2625
2752
  var HTML_ARIA_RULES = [
2626
2753
  landmarkRoleRule,
2627
2754
  landmarkNameAdvisory,
@@ -2722,6 +2849,62 @@ var figureContract = contract([
2722
2849
  ]);
2723
2850
  var detailsContract = firstChildContract("summary", "summary");
2724
2851
  var fieldsetContract = firstChildContract("legend", "legend");
2852
+ var objectContract = contract([
2853
+ { name: "param", match: isTag("param") },
2854
+ { name: "content", match: isOpenContent("param") }
2855
+ ]);
2856
+ var INTERACTIVE_CONTENT_TAGS = ["a", "button", "input", "select", "textarea", "label"];
2857
+ var buttonContract = closedContract([
2858
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2859
+ ]);
2860
+ var anchorContract = closedContract([
2861
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2862
+ ]);
2863
+ var LABELABLE_TAGS = [
2864
+ "button",
2865
+ "input",
2866
+ "meter",
2867
+ "output",
2868
+ "progress",
2869
+ "select",
2870
+ "textarea"
2871
+ ];
2872
+ var labelContract = contract([
2873
+ { name: "control", match: isTag(...LABELABLE_TAGS), cardinality: { max: 1 } }
2874
+ ]);
2875
+ var P_BLOCKED_TAGS = [
2876
+ "address",
2877
+ "article",
2878
+ "aside",
2879
+ "blockquote",
2880
+ "details",
2881
+ "dialog",
2882
+ "div",
2883
+ "dl",
2884
+ "fieldset",
2885
+ "figure",
2886
+ "footer",
2887
+ "form",
2888
+ "h1",
2889
+ "h2",
2890
+ "h3",
2891
+ "h4",
2892
+ "h5",
2893
+ "h6",
2894
+ "header",
2895
+ "hr",
2896
+ "main",
2897
+ "nav",
2898
+ "ol",
2899
+ "p",
2900
+ "pre",
2901
+ "section",
2902
+ "table",
2903
+ "ul"
2904
+ ];
2905
+ var pContract = closedContract([
2906
+ { name: "content", match: isOpenContent(...P_BLOCKED_TAGS) }
2907
+ ]);
2725
2908
  var mediaContract = contract([
2726
2909
  { name: "source", match: isTag("source") },
2727
2910
  { name: "track", match: isTag("track") },
@@ -2776,6 +2959,11 @@ var htmlContracts = {
2776
2959
  details: detailsContract,
2777
2960
  fieldset: fieldsetContract,
2778
2961
  dialog: dialogContract,
2962
+ object: objectContract,
2963
+ button: buttonContract,
2964
+ a: anchorContract,
2965
+ label: labelContract,
2966
+ p: pContract,
2779
2967
  head: headContract,
2780
2968
  html: htmlContract
2781
2969
  };
@@ -3002,6 +3190,11 @@ function composeNormalizers(normalizers, fn) {
3002
3190
  function whenDefined(key, value) {
3003
3191
  return value === void 0 ? {} : { [key]: value };
3004
3192
  }
3193
+ function mergeAriaRules(aria, rules) {
3194
+ if (!aria?.length) return rules;
3195
+ if (!rules?.length) return aria;
3196
+ return [...aria, ...rules];
3197
+ }
3005
3198
  function resolveFactoryOptions(options = {}) {
3006
3199
  const { styling, enforcement } = options;
3007
3200
  const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
@@ -3022,7 +3215,7 @@ function resolveFactoryOptions(options = {}) {
3022
3215
  ...whenDefined("defaultVariants", styling?.defaults),
3023
3216
  ...whenDefined("compoundVariants", styling?.compounds),
3024
3217
  ...whenDefined("normalizeFn", composedNormalizeFn),
3025
- ...whenDefined("ariaRules", enforcement?.aria),
3218
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
3026
3219
  ...whenDefined("childRules", enforcement?.children),
3027
3220
  ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
3028
3221
  ...whenDefined("allowText", enforcement?.allowText),