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.
package/dist/vue/index.js CHANGED
@@ -23,6 +23,9 @@ function isNull(value) {
23
23
  function isNonNull(value) {
24
24
  return value != null;
25
25
  }
26
+ function isNullish(value) {
27
+ return isNull(value) || value === void 0;
28
+ }
26
29
 
27
30
  // ../../lib/primitive/src/utils/type-guards.ts
28
31
  function isObject(value, excludeArrays = false) {
@@ -610,20 +613,29 @@ function isAriaAttributeValidForRole(attr, role) {
610
613
  }
611
614
 
612
615
  // ../../lib/primitive/src/guards/aria/is-aria-role.ts
616
+ function lookupImplicitRole(tag) {
617
+ return IMPLICIT_ROLE_RECORD[tag];
618
+ }
613
619
  function isStrongImplicitRole(tag) {
614
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
615
- return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
620
+ const role = lookupImplicitRole(tag);
621
+ return !isNullish(role) && STRONG_ROLES_SET.has(role);
616
622
  }
617
- function isStandaloneTag(tag) {
618
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
619
- return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
623
+ function hasStandaloneRole(tag) {
624
+ const role = lookupImplicitRole(tag);
625
+ return !isNullish(role) && STANDALONE_ROLES_SET.has(role);
620
626
  }
621
- function getInputImplicitRole(type) {
622
- if (!isString(type) || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
623
- return INPUT_TYPE_ROLE_MAP[type];
627
+ var LIST_ELIGIBLE_INPUT_TYPES = /* @__PURE__ */ new Set(["text", "search", "tel", "url", "email"]);
628
+ function getInputImplicitRole(type, list) {
629
+ if (!isString(type)) return void 0;
630
+ const role = INPUT_TYPE_ROLE_MAP[type];
631
+ if (!role) return void 0;
632
+ if (!isNullish(list) && LIST_ELIGIBLE_INPUT_TYPES.has(type)) {
633
+ return "combobox";
634
+ }
635
+ return role;
624
636
  }
625
637
  function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
626
- const isNamed = isString(ariaLabel) || isString(ariaLabelledBy);
638
+ const isNamed = isString(ariaLabel) && ariaLabel.trim().length > 0 || isString(ariaLabelledBy) && ariaLabelledBy.trim().length > 0;
627
639
  if (!isNamed) return void 0;
628
640
  if (tag === "section") return "region";
629
641
  if (tag === "form") return "form";
@@ -692,7 +704,7 @@ function defineContractComponent(options) {
692
704
  // ../../lib/contract/src/aria/aria-role-policy.ts
693
705
  function getImplicitRole(tag, props) {
694
706
  if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
695
- if (tag === "input") return getInputImplicitRole(props?.type);
707
+ if (tag === "input") return getInputImplicitRole(props?.type, props?.list);
696
708
  if (tag === "img") return props?.alt === "" ? "none" : "img";
697
709
  if (tag === "section" || tag === "form") {
698
710
  return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
@@ -975,7 +987,11 @@ var ATTRIBUTE_IGNORED_CODES = {
975
987
  max: DiagnosticCode3.HtmlInputMaxIgnoredForType,
976
988
  step: DiagnosticCode3.HtmlInputStepIgnoredForType,
977
989
  accept: DiagnosticCode3.HtmlInputAcceptIgnoredForType,
978
- capture: DiagnosticCode3.HtmlInputCaptureIgnoredForType
990
+ capture: DiagnosticCode3.HtmlInputCaptureIgnoredForType,
991
+ size: DiagnosticCode3.HtmlInputSizeIgnoredForType,
992
+ alt: DiagnosticCode3.HtmlInputAltIgnoredForType,
993
+ height: DiagnosticCode3.HtmlInputHeightIgnoredForType,
994
+ width: DiagnosticCode3.HtmlInputWidthIgnoredForType
979
995
  };
980
996
  var HtmlDiagnostics = {
981
997
  emptyRole(tag) {
@@ -1209,8 +1225,132 @@ var InvariantBase = class {
1209
1225
  }
1210
1226
  };
1211
1227
 
1212
- // ../../lib/contract/src/aria/polymorphic-validator.ts
1228
+ // ../../lib/contract/src/aria/spec/roles/required-properties.ts
1229
+ var REQUIRED_ARIA_PROPERTIES = {
1230
+ combobox: ["aria-expanded"],
1231
+ option: ["aria-selected"],
1232
+ slider: ["aria-valuenow"],
1233
+ scrollbar: ["aria-controls", "aria-valuenow"],
1234
+ spinbutton: ["aria-valuenow"]
1235
+ };
1236
+
1237
+ // ../../lib/contract/src/aria/spec/roles/name-required.ts
1238
+ var NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1239
+
1240
+ // ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
1213
1241
  var NO_VIOLATIONS = [{ valid: true }];
1242
+ function requiredAttributeByRole(roles, attribute) {
1243
+ return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
1244
+ }
1245
+ function checkRequiredAttributes(requirement, { props, effectiveRole }) {
1246
+ if (!effectiveRole) return NO_VIOLATIONS;
1247
+ const requiredAttributes = requirement.attributesByRole[effectiveRole];
1248
+ if (!requiredAttributes) return NO_VIOLATIONS;
1249
+ const results = [];
1250
+ for (const attribute of requiredAttributes) {
1251
+ if (attribute in props) continue;
1252
+ results.push({
1253
+ valid: false,
1254
+ fixable: false,
1255
+ severity: "warning",
1256
+ attribute,
1257
+ diagnostic: requirement.diagnosticFor(attribute, effectiveRole)
1258
+ });
1259
+ }
1260
+ return results;
1261
+ }
1262
+
1263
+ // ../../lib/contract/src/aria/spec/roles/live-region.ts
1264
+ var LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1265
+ ["alert", "assertive"],
1266
+ ["status", "polite"],
1267
+ ["log", "polite"],
1268
+ ["timer", "off"]
1269
+ ]);
1270
+ var ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
1271
+
1272
+ // ../../lib/contract/src/aria/spec/attributes/aria-value-types.ts
1273
+ var ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1274
+ // Boolean (true | false)
1275
+ ["aria-atomic", { kind: "boolean" }],
1276
+ ["aria-busy", { kind: "boolean" }],
1277
+ ["aria-disabled", { kind: "boolean" }],
1278
+ ["aria-expanded", { kind: "boolean" }],
1279
+ ["aria-hidden", { kind: "boolean" }],
1280
+ ["aria-modal", { kind: "boolean" }],
1281
+ ["aria-multiline", { kind: "boolean" }],
1282
+ ["aria-multiselectable", { kind: "boolean" }],
1283
+ ["aria-readonly", { kind: "boolean" }],
1284
+ ["aria-required", { kind: "boolean" }],
1285
+ ["aria-selected", { kind: "boolean" }],
1286
+ // Tristate (true | false | mixed)
1287
+ ["aria-checked", { kind: "tristate" }],
1288
+ ["aria-pressed", { kind: "tristate" }],
1289
+ // Numeric (any finite number)
1290
+ ["aria-valuenow", { kind: "number" }],
1291
+ ["aria-valuemin", { kind: "number" }],
1292
+ ["aria-valuemax", { kind: "number" }],
1293
+ // Integer with optional range
1294
+ ["aria-level", { kind: "integer", min: 1, max: 6 }],
1295
+ ["aria-posinset", { kind: "integer", min: 1 }],
1296
+ ["aria-setsize", { kind: "integer", min: -1 }],
1297
+ ["aria-rowcount", { kind: "integer", min: -1 }],
1298
+ ["aria-colcount", { kind: "integer", min: -1 }],
1299
+ ["aria-rowindex", { kind: "integer", min: 1 }],
1300
+ ["aria-colindex", { kind: "integer", min: 1 }],
1301
+ ["aria-rowspan", { kind: "integer", min: 0 }],
1302
+ ["aria-colspan", { kind: "integer", min: 0 }],
1303
+ // Enum (specific allowed tokens)
1304
+ ["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
1305
+ [
1306
+ "aria-current",
1307
+ {
1308
+ kind: "enum",
1309
+ values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
1310
+ }
1311
+ ],
1312
+ [
1313
+ "aria-haspopup",
1314
+ {
1315
+ kind: "enum",
1316
+ values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
1317
+ }
1318
+ ],
1319
+ ["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
1320
+ ["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
1321
+ ["aria-orientation", { kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }],
1322
+ ["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
1323
+ ]);
1324
+
1325
+ // ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
1326
+ var VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
1327
+ "additions",
1328
+ "removals",
1329
+ "text",
1330
+ "all"
1331
+ ]);
1332
+
1333
+ // ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
1334
+ var HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1335
+ ["h1", 1],
1336
+ ["h2", 2],
1337
+ ["h3", 3],
1338
+ ["h4", 4],
1339
+ ["h5", 5],
1340
+ ["h6", 6]
1341
+ ]);
1342
+
1343
+ // ../../lib/contract/src/aria/spec/elements/interactive-tags.ts
1344
+ var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
1345
+ "a",
1346
+ "button",
1347
+ "input",
1348
+ "select",
1349
+ "textarea"
1350
+ ]);
1351
+
1352
+ // ../../lib/contract/src/aria/polymorphic-validator.ts
1353
+ var NO_VIOLATIONS2 = [{ valid: true }];
1214
1354
  function isIntrinsicTag(tag) {
1215
1355
  return isString(tag);
1216
1356
  }
@@ -1253,7 +1393,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1253
1393
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1254
1394
  const implicitRole = getImplicitRole(tag, props);
1255
1395
  const hasRole2 = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1256
- if (!hasRole2) return { proceed: false, result: { props, violations: [] } };
1257
1396
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1258
1397
  const workingProps = normalized.normalized ? normalized.result.props : props;
1259
1398
  const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
@@ -1263,6 +1402,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1263
1402
  tag,
1264
1403
  implicitRole,
1265
1404
  effectiveRole,
1405
+ hasRole: hasRole2,
1266
1406
  props: workingProps,
1267
1407
  preExistingViolations,
1268
1408
  context: { tag, props: workingProps, implicitRole, effectiveRole }
@@ -1272,6 +1412,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1272
1412
  const violations = [];
1273
1413
  const fixes = [];
1274
1414
  iterate.forEach(rules, (rule) => {
1415
+ if (isNonNull(rule.tags) && !rule.tags.includes(context.tag)) return;
1275
1416
  iterate.forEach(rule(context), (result) => {
1276
1417
  if (result.valid) return;
1277
1418
  const {
@@ -1297,7 +1438,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1297
1438
  return { violations, fixes };
1298
1439
  }
1299
1440
  static #getRules(context) {
1300
- if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1441
+ if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && LIVE_REGION_ROLES.has(context.effectiveRole)) {
1301
1442
  return _AriaPolicyEngine.#pipeline;
1302
1443
  }
1303
1444
  return _AriaPolicyEngine.#implicitOnlyRules;
@@ -1305,6 +1446,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1305
1446
  static evaluate(tag, props) {
1306
1447
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1307
1448
  if (!derived.proceed) return derived.result;
1449
+ if (!derived.hasRole)
1450
+ return { props: derived.props, violations: [...derived.preExistingViolations] };
1308
1451
  const {
1309
1452
  tag: narrowedTag,
1310
1453
  implicitRole,
@@ -1329,10 +1472,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1329
1472
  props: workingProps,
1330
1473
  preExistingViolations
1331
1474
  } = derived;
1332
- const { violations, fixes } = _AriaPolicyEngine.#runRules(
1333
- [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1334
- context
1335
- );
1475
+ const rules = derived.hasRole ? [..._AriaPolicyEngine.#getRules(context), ...extraRules] : extraRules;
1476
+ const { violations, fixes } = _AriaPolicyEngine.#runRules(rules, context);
1336
1477
  const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1337
1478
  return { props: next, violations: [...preExistingViolations, ...violations] };
1338
1479
  }
@@ -1499,7 +1640,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1499
1640
  implicitRole
1500
1641
  }) {
1501
1642
  const role = props.role;
1502
- if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
1643
+ if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS2;
1503
1644
  if (isStrongImplicitRole(tag) && role === "region") {
1504
1645
  const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
1505
1646
  return [
@@ -1512,11 +1653,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1512
1653
  }
1513
1654
  ];
1514
1655
  }
1515
- return NO_VIOLATIONS;
1656
+ return NO_VIOLATIONS2;
1516
1657
  }
1517
1658
  static #checkRedundantRole({ tag, props, implicitRole }) {
1518
1659
  const role = props.role;
1519
- if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
1660
+ if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS2;
1520
1661
  const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
1521
1662
  return [
1522
1663
  {
@@ -1530,8 +1671,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1530
1671
  }
1531
1672
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1532
1673
  const role = props.role;
1533
- if (role !== "region") return NO_VIOLATIONS;
1534
- if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
1674
+ if (role !== "region") return NO_VIOLATIONS2;
1675
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS2;
1535
1676
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1536
1677
  return [
1537
1678
  {
@@ -1548,7 +1689,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1548
1689
  props,
1549
1690
  effectiveRole
1550
1691
  }) {
1551
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1692
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1552
1693
  const results = [];
1553
1694
  iterate.forEachEntry(props, (key) => {
1554
1695
  if (!key.startsWith("aria-")) return;
@@ -1566,62 +1707,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1566
1707
  return results;
1567
1708
  }
1568
1709
  // ─── ARIA attribute value validation ──────────────────────────────────────
1569
- // Accepted value shapes for typed ARIA attributes.
1570
- // Attributes not in this map are unconstrained (arbitrary string values permitted).
1571
- static #ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1572
- // Boolean (true | false)
1573
- ["aria-atomic", { kind: "boolean" }],
1574
- ["aria-busy", { kind: "boolean" }],
1575
- ["aria-disabled", { kind: "boolean" }],
1576
- ["aria-expanded", { kind: "boolean" }],
1577
- ["aria-hidden", { kind: "boolean" }],
1578
- ["aria-modal", { kind: "boolean" }],
1579
- ["aria-multiline", { kind: "boolean" }],
1580
- ["aria-multiselectable", { kind: "boolean" }],
1581
- ["aria-readonly", { kind: "boolean" }],
1582
- ["aria-required", { kind: "boolean" }],
1583
- ["aria-selected", { kind: "boolean" }],
1584
- // Tristate (true | false | mixed)
1585
- ["aria-checked", { kind: "tristate" }],
1586
- ["aria-pressed", { kind: "tristate" }],
1587
- // Numeric (any finite number)
1588
- ["aria-valuenow", { kind: "number" }],
1589
- ["aria-valuemin", { kind: "number" }],
1590
- ["aria-valuemax", { kind: "number" }],
1591
- // Integer with optional range
1592
- ["aria-level", { kind: "integer", min: 1, max: 6 }],
1593
- ["aria-posinset", { kind: "integer", min: 1 }],
1594
- ["aria-setsize", { kind: "integer", min: -1 }],
1595
- ["aria-rowcount", { kind: "integer", min: -1 }],
1596
- ["aria-colcount", { kind: "integer", min: -1 }],
1597
- ["aria-rowindex", { kind: "integer", min: 1 }],
1598
- ["aria-colindex", { kind: "integer", min: 1 }],
1599
- ["aria-rowspan", { kind: "integer", min: 0 }],
1600
- ["aria-colspan", { kind: "integer", min: 0 }],
1601
- // Enum (specific allowed tokens)
1602
- ["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
1603
- [
1604
- "aria-current",
1605
- {
1606
- kind: "enum",
1607
- values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
1608
- }
1609
- ],
1610
- [
1611
- "aria-haspopup",
1612
- {
1613
- kind: "enum",
1614
- values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
1615
- }
1616
- ],
1617
- ["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
1618
- ["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
1619
- [
1620
- "aria-orientation",
1621
- { kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }
1622
- ],
1623
- ["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
1624
- ]);
1625
1710
  static #isValidAriaValue(value, type) {
1626
1711
  switch (type.kind) {
1627
1712
  case "boolean":
@@ -1666,11 +1751,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1666
1751
  }
1667
1752
  }
1668
1753
  static #checkAriaAttributeValues({ props, effectiveRole }) {
1669
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1754
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1670
1755
  const results = [];
1671
1756
  iterate.forEachEntry(props, (key, value) => {
1672
1757
  if (!key.startsWith("aria-")) return;
1673
- const type = _AriaPolicyEngine.#ARIA_VALUE_TYPES.get(key);
1758
+ const type = ARIA_VALUE_TYPES.get(key);
1674
1759
  if (!isNonNull(type)) return;
1675
1760
  if (_AriaPolicyEngine.#isValidAriaValue(value, type)) return;
1676
1761
  results.push({
@@ -1689,26 +1774,18 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1689
1774
  return results;
1690
1775
  }
1691
1776
  // ─── Heading implicit level ────────────────────────────────────────────────
1692
- static #HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1693
- ["h1", 1],
1694
- ["h2", 2],
1695
- ["h3", 3],
1696
- ["h4", 4],
1697
- ["h5", 5],
1698
- ["h6", 6]
1699
- ]);
1700
1777
  static #checkRedundantAriaLevel({
1701
1778
  tag,
1702
1779
  props,
1703
1780
  effectiveRole
1704
1781
  }) {
1705
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1706
- const implicitLevel = _AriaPolicyEngine.#HEADING_IMPLICIT_LEVELS.get(tag);
1707
- if (!isNonNull(implicitLevel)) return NO_VIOLATIONS;
1782
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1783
+ const implicitLevel = HEADING_IMPLICIT_LEVELS.get(tag);
1784
+ if (!isNonNull(implicitLevel)) return NO_VIOLATIONS2;
1708
1785
  const raw = props["aria-level"];
1709
- if (!isNonNull(raw)) return NO_VIOLATIONS;
1786
+ if (!isNonNull(raw)) return NO_VIOLATIONS2;
1710
1787
  const n = typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : NaN;
1711
- if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS;
1788
+ if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS2;
1712
1789
  return [
1713
1790
  {
1714
1791
  valid: false,
@@ -1721,20 +1798,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1721
1798
  ];
1722
1799
  }
1723
1800
  // ─── Name-required roles ───────────────────────────────────────────────────
1724
- // Roles that always require an accessible name per WAI-ARIA APG.
1725
- // Dialog and landmark names are enforced via contracts (ariaContract) rather than
1726
- // the built-in pipeline so consumers can opt in; img is built in because role=img
1727
- // on any element (including bare <img>) is definitionally useless without a name.
1728
- static #NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1729
1801
  static #checkNameRequiredRoles({
1730
1802
  tag,
1731
1803
  props,
1732
1804
  effectiveRole
1733
1805
  }) {
1734
- if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole))
1735
- return NO_VIOLATIONS;
1736
- if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS;
1737
- if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS;
1806
+ if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return NO_VIOLATIONS2;
1807
+ if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS2;
1808
+ if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS2;
1738
1809
  return [
1739
1810
  {
1740
1811
  valid: false,
@@ -1744,51 +1815,21 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1744
1815
  }
1745
1816
  ];
1746
1817
  }
1747
- // WAI-ARIA 1.2 required states and properties, keyed by role.
1748
- // Source: https://www.w3.org/TR/wai-aria-1.2/#requiredState
1749
- static #REQUIRED_PROPERTIES = /* @__PURE__ */ new Map([
1750
- ["combobox", ["aria-expanded"]],
1751
- ["option", ["aria-selected"]],
1752
- ["slider", ["aria-valuenow"]],
1753
- ["scrollbar", ["aria-controls", "aria-valuenow"]],
1754
- ["spinbutton", ["aria-valuenow"]]
1755
- ]);
1756
- static #checkRequiredAriaProperties({
1757
- props,
1758
- effectiveRole
1759
- }) {
1760
- if (!effectiveRole) return NO_VIOLATIONS;
1761
- const required = _AriaPolicyEngine.#REQUIRED_PROPERTIES.get(effectiveRole);
1762
- if (!isNonNull(required)) return NO_VIOLATIONS;
1763
- const results = [];
1764
- iterate.forEach(required, (attr) => {
1765
- if (attr in props) return;
1766
- results.push({
1767
- valid: false,
1768
- fixable: false,
1769
- severity: "warning",
1770
- attribute: attr,
1771
- diagnostic: AriaDiagnostics.requiredProperty(attr, effectiveRole)
1772
- });
1773
- });
1774
- return results;
1818
+ static #requiredAriaPropertiesRule = {
1819
+ attributesByRole: REQUIRED_ARIA_PROPERTIES,
1820
+ diagnosticFor: (attribute, role) => AriaDiagnostics.requiredProperty(attribute, role)
1821
+ };
1822
+ static #checkRequiredAriaProperties(context) {
1823
+ return checkRequiredAttributes(_AriaPolicyEngine.#requiredAriaPropertiesRule, context);
1775
1824
  }
1776
- // Natively interactive HTML elements — always keyboard-reachable unless explicitly disabled.
1777
- static #INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
1778
- "a",
1779
- "button",
1780
- "input",
1781
- "select",
1782
- "textarea"
1783
- ]);
1784
1825
  // WAI-ARIA 1.2 §6.6: aria-hidden="true" must not be placed on focusable elements.
1785
1826
  static #checkAriaHiddenOnFocusable({ tag, props }) {
1786
- if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS;
1787
- const isInteractive = _AriaPolicyEngine.#INTERACTIVE_TAGS.has(tag);
1827
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS2;
1828
+ const isInteractive = INTERACTIVE_TAGS.has(tag);
1788
1829
  if (!isInteractive) {
1789
1830
  const tabindex = props.tabindex;
1790
1831
  const n = typeof tabindex === "number" ? tabindex : typeof tabindex === "string" ? parseInt(tabindex, 10) : NaN;
1791
- if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS;
1832
+ if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS2;
1792
1833
  }
1793
1834
  return [
1794
1835
  {
@@ -1807,7 +1848,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1807
1848
  props,
1808
1849
  effectiveRole
1809
1850
  }) {
1810
- if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS;
1851
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS2;
1811
1852
  const results = [];
1812
1853
  iterate.forEachEntry(props, (key) => {
1813
1854
  if (!key.startsWith("aria-")) return;
@@ -1823,18 +1864,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1823
1864
  });
1824
1865
  return results;
1825
1866
  }
1826
- // WAI-ARIA live region roles and their implied aria-live politeness values.
1827
- static #LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1828
- ["alert", "assertive"],
1829
- ["status", "polite"],
1830
- ["log", "polite"],
1831
- ["timer", "off"]
1832
- ]);
1833
1867
  static #checkMissingLiveRegion({ effectiveRole, props }) {
1834
- if (!effectiveRole) return NO_VIOLATIONS;
1835
- const impliedLive = _AriaPolicyEngine.#LIVE_REGION_ROLES.get(effectiveRole);
1836
- if (!impliedLive) return NO_VIOLATIONS;
1837
- if ("aria-live" in props) return NO_VIOLATIONS;
1868
+ if (!effectiveRole) return NO_VIOLATIONS2;
1869
+ const impliedLive = LIVE_REGION_ROLES.get(effectiveRole);
1870
+ if (!impliedLive) return NO_VIOLATIONS2;
1871
+ if ("aria-live" in props) return NO_VIOLATIONS2;
1838
1872
  const injectLive = {
1839
1873
  kind: `injectLive:${effectiveRole}`,
1840
1874
  apply: (ctx) => ({
@@ -1853,20 +1887,13 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1853
1887
  }
1854
1888
  ];
1855
1889
  }
1856
- static #checkMissingAtomic({ effectiveRole, props }) {
1857
- if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole))
1858
- return NO_VIOLATIONS;
1859
- if ("aria-atomic" in props) return NO_VIOLATIONS;
1860
- return [
1861
- {
1862
- valid: false,
1863
- fixable: false,
1864
- severity: "warning",
1865
- diagnostic: AriaDiagnostics.missingAtomic(effectiveRole)
1866
- }
1867
- ];
1890
+ static #missingAtomicRule = {
1891
+ attributesByRole: ATOMIC_REQUIREMENTS,
1892
+ diagnosticFor: (_attribute, role) => AriaDiagnostics.missingAtomic(role)
1893
+ };
1894
+ static #checkMissingAtomic(context) {
1895
+ return checkRequiredAttributes(_AriaPolicyEngine.#missingAtomicRule, context);
1868
1896
  }
1869
- static #VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set(["additions", "removals", "text", "all"]);
1870
1897
  // Custom fix rules passed via `options.rules` must be pure functions of (tag, props) — the cache
1871
1898
  // replays stored fixes against new prop objects, so fixes that close over external state will
1872
1899
  // produce inconsistent results on cache hits.
@@ -1880,10 +1907,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1880
1907
  };
1881
1908
  static #checkInvalidAriaRelevant({ props }) {
1882
1909
  const relevant = props["aria-relevant"];
1883
- if (relevant === void 0) return NO_VIOLATIONS;
1884
- if (typeof relevant !== "string") return NO_VIOLATIONS;
1910
+ if (relevant === void 0) return NO_VIOLATIONS2;
1911
+ if (typeof relevant !== "string") return NO_VIOLATIONS2;
1885
1912
  const tokens = relevant.trim().split(/\s+/);
1886
- const invalid = tokens.filter((t) => !_AriaPolicyEngine.#VALID_RELEVANT_TOKENS.has(t));
1913
+ const invalid = tokens.filter((t) => !VALID_RELEVANT_TOKENS.has(t));
1887
1914
  if (invalid.length > 0) {
1888
1915
  return [
1889
1916
  {
@@ -1908,7 +1935,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1908
1935
  }
1909
1936
  ];
1910
1937
  }
1911
- return NO_VIOLATIONS;
1938
+ return NO_VIOLATIONS2;
1912
1939
  }
1913
1940
  };
1914
1941
 
@@ -2228,7 +2255,59 @@ var readonlyProps = ({
2228
2255
  // ../core/src/html/evaluators.ts
2229
2256
  import { warnDiagnostics as warnDiagnostics2 } from "../_shared/diagnostics.js";
2230
2257
 
2231
- // ../core/src/html/input-rules.ts
2258
+ // ../core/src/html/spec/vocabulary/input.ts
2259
+ var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2260
+ var NUMERIC_INPUT_TYPES = [
2261
+ "number",
2262
+ "range",
2263
+ "date",
2264
+ "month",
2265
+ "week",
2266
+ "time",
2267
+ "datetime-local"
2268
+ ];
2269
+ var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
2270
+ ...TEXT_INPUT_TYPES,
2271
+ ...NUMERIC_INPUT_TYPES,
2272
+ "checkbox",
2273
+ "radio",
2274
+ "file",
2275
+ "color",
2276
+ "hidden",
2277
+ "button",
2278
+ "submit",
2279
+ "reset",
2280
+ "image"
2281
+ ]);
2282
+
2283
+ // ../core/src/html/spec/attributes/input.ts
2284
+ var INPUT_ATTRIBUTE_TYPE_POLICIES = [
2285
+ { attribute: "checked", allowedTypes: ["checkbox", "radio"] },
2286
+ { attribute: "multiple", allowedTypes: ["email", "file"] },
2287
+ { attribute: "maxLength", allowedTypes: TEXT_INPUT_TYPES },
2288
+ { attribute: "minLength", allowedTypes: TEXT_INPUT_TYPES },
2289
+ { attribute: "pattern", allowedTypes: TEXT_INPUT_TYPES },
2290
+ { attribute: "min", allowedTypes: NUMERIC_INPUT_TYPES },
2291
+ { attribute: "max", allowedTypes: NUMERIC_INPUT_TYPES },
2292
+ { attribute: "step", allowedTypes: NUMERIC_INPUT_TYPES },
2293
+ { attribute: "accept", allowedTypes: ["file"] },
2294
+ { attribute: "capture", allowedTypes: ["file"] },
2295
+ { attribute: "size", allowedTypes: TEXT_INPUT_TYPES },
2296
+ { attribute: "alt", allowedTypes: ["image"] },
2297
+ { attribute: "height", allowedTypes: ["image"] },
2298
+ { attribute: "width", allowedTypes: ["image"] }
2299
+ ];
2300
+
2301
+ // ../core/src/html/spec/constraints/input.ts
2302
+ var REQUIRED_READONLY_CONFLICT = {
2303
+ props: ["required", "readOnly"],
2304
+ diagnostic: () => InputAccessibilityDiagnostics.requiredReadOnlyConflict()
2305
+ };
2306
+ var INPUT_MUTUALLY_EXCLUSIVE_POLICIES = [
2307
+ REQUIRED_READONLY_CONFLICT
2308
+ ];
2309
+
2310
+ // ../core/src/html/spec/validators/attribute-type-validator.ts
2232
2311
  var DEFAULT_INPUT_TYPE = "text";
2233
2312
  function omit(props, key) {
2234
2313
  const next = { ...props };
@@ -2244,7 +2323,10 @@ function removeAttributeFix(attribute) {
2244
2323
  }
2245
2324
  };
2246
2325
  }
2247
- function inputAttributeRequiresType(attribute, allowedTypes) {
2326
+ function createInputAttributeTypeRule({
2327
+ attribute,
2328
+ allowedTypes
2329
+ }) {
2248
2330
  const rule = ({ tag, props }) => {
2249
2331
  if (tag !== "input" || !(attribute in props)) return [];
2250
2332
  const type = typeof props.type === "string" ? props.type : DEFAULT_INPUT_TYPE;
@@ -2260,31 +2342,30 @@ function inputAttributeRequiresType(attribute, allowedTypes) {
2260
2342
  }
2261
2343
  ];
2262
2344
  };
2263
- return Object.assign(rule, { readsProps: ["type", attribute] });
2345
+ return Object.assign(rule, { readsProps: ["type", attribute], tags: ["input"] });
2346
+ }
2347
+
2348
+ // ../core/src/html/spec/validators/mutually-exclusive-validator.ts
2349
+ function createMutuallyExclusiveRule({
2350
+ props: conflictingProps,
2351
+ diagnostic: createDiagnostic
2352
+ }) {
2353
+ const [first, second] = conflictingProps;
2354
+ const rule = ({ tag, props }) => {
2355
+ if (tag !== "input" || !props[first] || !props[second]) return [];
2356
+ const diagnostic = createDiagnostic();
2357
+ return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2358
+ };
2359
+ return Object.assign(rule, { readsProps: conflictingProps, tags: ["input"] });
2360
+ }
2361
+
2362
+ // ../core/src/html/input-rules.ts
2363
+ var policyByAttribute = Object.fromEntries(
2364
+ INPUT_ATTRIBUTE_TYPE_POLICIES.map((policy) => [policy.attribute, policy])
2365
+ );
2366
+ function policyFor(attribute) {
2367
+ return policyByAttribute[attribute];
2264
2368
  }
2265
- var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2266
- var NUMERIC_INPUT_TYPES = [
2267
- "number",
2268
- "range",
2269
- "date",
2270
- "month",
2271
- "week",
2272
- "time",
2273
- "datetime-local"
2274
- ];
2275
- var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
2276
- ...TEXT_INPUT_TYPES,
2277
- ...NUMERIC_INPUT_TYPES,
2278
- "checkbox",
2279
- "radio",
2280
- "file",
2281
- "color",
2282
- "hidden",
2283
- "button",
2284
- "submit",
2285
- "reset",
2286
- "image"
2287
- ]);
2288
2369
  var supportedInputTypeRule = Object.assign(
2289
2370
  ({ tag, props }) => {
2290
2371
  if (tag !== "input" || typeof props.type !== "string") return [];
@@ -2300,30 +2381,22 @@ var supportedInputTypeRule = Object.assign(
2300
2381
  }
2301
2382
  ];
2302
2383
  },
2303
- { readsProps: ["type"] }
2384
+ { readsProps: ["type"], tags: ["input"] }
2304
2385
  );
2305
- var checkedRequiresCheckableTypeRule = inputAttributeRequiresType("checked", [
2306
- "checkbox",
2307
- "radio"
2308
- ]);
2309
- var multipleRequiresSupportedTypeRule = inputAttributeRequiresType("multiple", [
2310
- "email",
2311
- "file"
2312
- ]);
2313
- var maxLengthRequiresTextTypeRule = inputAttributeRequiresType(
2314
- "maxLength",
2315
- TEXT_INPUT_TYPES
2316
- );
2317
- var minLengthRequiresTextTypeRule = inputAttributeRequiresType(
2318
- "minLength",
2319
- TEXT_INPUT_TYPES
2320
- );
2321
- var patternRequiresTextTypeRule = inputAttributeRequiresType("pattern", TEXT_INPUT_TYPES);
2322
- var minRequiresNumericTypeRule = inputAttributeRequiresType("min", NUMERIC_INPUT_TYPES);
2323
- var maxRequiresNumericTypeRule = inputAttributeRequiresType("max", NUMERIC_INPUT_TYPES);
2324
- var stepRequiresNumericTypeRule = inputAttributeRequiresType("step", NUMERIC_INPUT_TYPES);
2325
- var acceptRequiresFileTypeRule = inputAttributeRequiresType("accept", ["file"]);
2326
- var captureRequiresFileTypeRule = inputAttributeRequiresType("capture", ["file"]);
2386
+ var checkedRequiresCheckableTypeRule = createInputAttributeTypeRule(policyFor("checked"));
2387
+ var multipleRequiresSupportedTypeRule = createInputAttributeTypeRule(policyFor("multiple"));
2388
+ var maxLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("maxLength"));
2389
+ var minLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("minLength"));
2390
+ var patternRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("pattern"));
2391
+ var minRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("min"));
2392
+ var maxRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("max"));
2393
+ var stepRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("step"));
2394
+ var acceptRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("accept"));
2395
+ var captureRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("capture"));
2396
+ var sizeRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("size"));
2397
+ var altRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("alt"));
2398
+ var heightRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("height"));
2399
+ var widthRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("width"));
2327
2400
  var inputAccessibleNameRule = Object.assign(
2328
2401
  ({ tag, props }) => {
2329
2402
  if (tag !== "input" || props.type === "hidden") return [];
@@ -2332,7 +2405,10 @@ var inputAccessibleNameRule = Object.assign(
2332
2405
  const diagnostic = hasPlaceholder ? InputAccessibilityDiagnostics.placeholderIsNotLabel() : InputAccessibilityDiagnostics.missingAccessibleName();
2333
2406
  return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2334
2407
  },
2335
- { readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"] }
2408
+ {
2409
+ readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"],
2410
+ tags: ["input"]
2411
+ }
2336
2412
  );
2337
2413
  var PASSWORD_AUTOCOMPLETE_VALUES = ["current-password", "new-password"];
2338
2414
  var passwordAutocompleteRule = Object.assign(
@@ -2344,16 +2420,9 @@ var passwordAutocompleteRule = Object.assign(
2344
2420
  const diagnostic = InputAccessibilityDiagnostics.passwordMissingAutocomplete();
2345
2421
  return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2346
2422
  },
2347
- { readsProps: ["type", "autoComplete"] }
2348
- );
2349
- var requiredReadOnlyConflictRule = Object.assign(
2350
- ({ tag, props }) => {
2351
- if (tag !== "input" || !props.required || !props.readOnly) return [];
2352
- const diagnostic = InputAccessibilityDiagnostics.requiredReadOnlyConflict();
2353
- return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2354
- },
2355
- { readsProps: ["required", "readOnly"] }
2423
+ { readsProps: ["type", "autoComplete"], tags: ["input"] }
2356
2424
  );
2425
+ var requiredReadOnlyConflictRule = createMutuallyExclusiveRule(REQUIRED_READONLY_CONFLICT);
2357
2426
  var INPUT_RULES = [
2358
2427
  supportedInputTypeRule,
2359
2428
  checkedRequiresCheckableTypeRule,
@@ -2366,11 +2435,132 @@ var INPUT_RULES = [
2366
2435
  stepRequiresNumericTypeRule,
2367
2436
  acceptRequiresFileTypeRule,
2368
2437
  captureRequiresFileTypeRule,
2438
+ sizeRequiresTextTypeRule,
2439
+ altRequiresImageTypeRule,
2440
+ heightRequiresImageTypeRule,
2441
+ widthRequiresImageTypeRule,
2369
2442
  inputAccessibleNameRule,
2370
2443
  passwordAutocompleteRule,
2371
2444
  requiredReadOnlyConflictRule
2372
2445
  ];
2373
2446
 
2447
+ // ../core/src/html/spec/types.ts
2448
+ function definePropRolePolicy(prop, map2, fallback) {
2449
+ return { kind: "byProp", prop, map: map2, fallback };
2450
+ }
2451
+ function resolveAllowedRoles(spec, props) {
2452
+ const policy = spec.allowedRoles;
2453
+ if (!policy) return void 0;
2454
+ switch (policy.kind) {
2455
+ case "fixed":
2456
+ return policy.roles;
2457
+ case "byProp": {
2458
+ const value = typeof props[policy.prop] === "string" ? props[policy.prop] : policy.fallback;
2459
+ return policy.map[value];
2460
+ }
2461
+ case "dynamic":
2462
+ return policy.resolve({ props });
2463
+ }
2464
+ }
2465
+
2466
+ // ../core/src/html/spec/roles/input.ts
2467
+ var ALLOWED_INPUT_ROLES = {
2468
+ checkbox: ["menuitemcheckbox", "option", "switch", "button"],
2469
+ radio: ["menuitemradio"],
2470
+ range: [],
2471
+ number: [],
2472
+ search: ["combobox"],
2473
+ text: ["combobox", "searchbox", "spinbutton"],
2474
+ email: ["combobox"],
2475
+ tel: ["combobox"],
2476
+ url: ["combobox"],
2477
+ button: [
2478
+ "link",
2479
+ "menuitem",
2480
+ "menuitemcheckbox",
2481
+ "menuitemradio",
2482
+ "option",
2483
+ "radio",
2484
+ "switch",
2485
+ "tab"
2486
+ ],
2487
+ submit: [
2488
+ "link",
2489
+ "menuitem",
2490
+ "menuitemcheckbox",
2491
+ "menuitemradio",
2492
+ "option",
2493
+ "radio",
2494
+ "switch",
2495
+ "tab"
2496
+ ],
2497
+ reset: [
2498
+ "link",
2499
+ "menuitem",
2500
+ "menuitemcheckbox",
2501
+ "menuitemradio",
2502
+ "option",
2503
+ "radio",
2504
+ "switch",
2505
+ "tab"
2506
+ ],
2507
+ image: [
2508
+ "link",
2509
+ "menuitem",
2510
+ "menuitemcheckbox",
2511
+ "menuitemradio",
2512
+ "option",
2513
+ "radio",
2514
+ "switch",
2515
+ "tab"
2516
+ ],
2517
+ hidden: []
2518
+ };
2519
+
2520
+ // ../core/src/html/spec/elements/input.ts
2521
+ var inputElementSpec = {
2522
+ tag: "input",
2523
+ allowedRoles: definePropRolePolicy("type", ALLOWED_INPUT_ROLES, "text"),
2524
+ attributes: INPUT_ATTRIBUTE_TYPE_POLICIES,
2525
+ mutuallyExclusive: INPUT_MUTUALLY_EXCLUSIVE_POLICIES
2526
+ };
2527
+
2528
+ // ../core/src/html/spec/roles/img.ts
2529
+ var IMG_NAMED_ROLES = [
2530
+ "button",
2531
+ "checkbox",
2532
+ "link",
2533
+ "menuitem",
2534
+ "menuitemcheckbox",
2535
+ "menuitemradio",
2536
+ "option",
2537
+ "progressbar",
2538
+ "scrollbar",
2539
+ "separator",
2540
+ "slider",
2541
+ "switch",
2542
+ "tab",
2543
+ "treeitem"
2544
+ ];
2545
+
2546
+ // ../core/src/html/spec/elements/img.ts
2547
+ var imgElementSpec = {
2548
+ tag: "img",
2549
+ allowedRoles: {
2550
+ kind: "dynamic",
2551
+ resolve: ({ props }) => props.alt === "" ? [] : IMG_NAMED_ROLES
2552
+ }
2553
+ };
2554
+
2555
+ // ../core/src/html/spec/roles/table.ts
2556
+ var ALLOWED_TABLE_ROLES = ["grid", "treegrid"];
2557
+
2558
+ // ../core/src/html/spec/elements/table.ts
2559
+ var tableElementSpec = {
2560
+ tag: "table",
2561
+ allowedRoles: { kind: "fixed", roles: ALLOWED_TABLE_ROLES }
2562
+ };
2563
+
2374
2564
  // ../core/src/html/role-restrictions.ts
2375
2565
  var ALLOWED_ROLES = {
2376
2566
  article: ["application", "document", "feed", "main", "none", "presentation", "region"],
@@ -2443,86 +2633,17 @@ var ALLOWED_ROLES = {
2443
2633
  "tab",
2444
2634
  "treeitem"
2445
2635
  ],
2446
- table: ["grid", "treegrid"],
2447
2636
  dialog: ["alertdialog"],
2448
2637
  fieldset: ["none", "presentation", "radiogroup"]
2449
2638
  };
2450
- var IMG_NAMED_ROLES = [
2451
- "button",
2452
- "checkbox",
2453
- "link",
2454
- "menuitem",
2455
- "menuitemcheckbox",
2456
- "menuitemradio",
2457
- "option",
2458
- "progressbar",
2459
- "scrollbar",
2460
- "separator",
2461
- "slider",
2462
- "switch",
2463
- "tab",
2464
- "treeitem"
2465
- ];
2466
- var ALLOWED_INPUT_ROLES = {
2467
- checkbox: ["menuitemcheckbox", "option", "switch", "button"],
2468
- radio: ["menuitemradio"],
2469
- range: [],
2470
- number: [],
2471
- search: ["combobox"],
2472
- text: ["combobox", "searchbox", "spinbutton"],
2473
- email: ["combobox"],
2474
- tel: ["combobox"],
2475
- url: ["combobox"],
2476
- button: [
2477
- "link",
2478
- "menuitem",
2479
- "menuitemcheckbox",
2480
- "menuitemradio",
2481
- "option",
2482
- "radio",
2483
- "switch",
2484
- "tab"
2485
- ],
2486
- submit: [
2487
- "link",
2488
- "menuitem",
2489
- "menuitemcheckbox",
2490
- "menuitemradio",
2491
- "option",
2492
- "radio",
2493
- "switch",
2494
- "tab"
2495
- ],
2496
- reset: [
2497
- "link",
2498
- "menuitem",
2499
- "menuitemcheckbox",
2500
- "menuitemradio",
2501
- "option",
2502
- "radio",
2503
- "switch",
2504
- "tab"
2505
- ],
2506
- image: [
2507
- "link",
2508
- "menuitem",
2509
- "menuitemcheckbox",
2510
- "menuitemradio",
2511
- "option",
2512
- "radio",
2513
- "switch",
2514
- "tab"
2515
- ],
2516
- hidden: []
2639
+ var ELEMENT_SPECS = {
2640
+ input: inputElementSpec,
2641
+ img: imgElementSpec,
2642
+ table: tableElementSpec
2517
2643
  };
2518
2644
  function getAllowedRoles(tag, props) {
2519
- if (tag === "input") {
2520
- const type = typeof props.type === "string" ? props.type : "text";
2521
- return ALLOWED_INPUT_ROLES[type];
2522
- }
2523
- if (tag === "img") {
2524
- return props.alt === "" ? [] : IMG_NAMED_ROLES;
2525
- }
2645
+ const spec = ELEMENT_SPECS[tag];
2646
+ if (spec) return resolveAllowedRoles(spec, props);
2526
2647
  return ALLOWED_ROLES[tag];
2527
2648
  }
2528
2649
  var removeRoleFix = {
@@ -2563,21 +2684,24 @@ var removeLandmarkRoleOverride = {
2563
2684
  return { applied: true, next: rest, previous: props };
2564
2685
  }
2565
2686
  };
2566
- function landmarkRoleRule({ tag, props, implicitRole }) {
2567
- if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2568
- const role = props.role;
2569
- if (!role || role === implicitRole) return [];
2570
- const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2571
- return [
2572
- {
2573
- valid: false,
2574
- fixable: true,
2575
- severity: diagnostic.severity,
2576
- fix: removeLandmarkRoleOverride,
2577
- diagnostic
2578
- }
2579
- ];
2580
- }
2687
+ var landmarkRoleRule = Object.assign(
2688
+ ({ tag, props, implicitRole }) => {
2689
+ if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2690
+ const role = props.role;
2691
+ if (!role || role === implicitRole) return [];
2692
+ const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2693
+ return [
2694
+ {
2695
+ valid: false,
2696
+ fixable: true,
2697
+ severity: diagnostic.severity,
2698
+ fix: removeLandmarkRoleOverride,
2699
+ diagnostic
2700
+ }
2701
+ ];
2702
+ },
2703
+ { tags: [...LANDMARK_TAG_SET] }
2704
+ );
2581
2705
  function requireAccessibleName({ tag, props }) {
2582
2706
  if ("aria-label" in props || "aria-labelledby" in props) return [];
2583
2707
  return [
@@ -2590,10 +2714,13 @@ function requireAccessibleName({ tag, props }) {
2590
2714
  ];
2591
2715
  }
2592
2716
  var NAMED_LANDMARK_TAGS = /* @__PURE__ */ new Set(["nav", "aside"]);
2593
- function landmarkNameAdvisory(ctx) {
2594
- if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2595
- return requireAccessibleName(ctx);
2596
- }
2717
+ var landmarkNameAdvisory = Object.assign(
2718
+ (ctx) => {
2719
+ if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2720
+ return requireAccessibleName(ctx);
2721
+ },
2722
+ { tags: [...NAMED_LANDMARK_TAGS] }
2723
+ );
2597
2724
  var HTML_ARIA_RULES = [
2598
2725
  landmarkRoleRule,
2599
2726
  landmarkNameAdvisory,
@@ -2694,6 +2821,62 @@ var figureContract = contract([
2694
2821
  ]);
2695
2822
  var detailsContract = firstChildContract("summary", "summary");
2696
2823
  var fieldsetContract = firstChildContract("legend", "legend");
2824
+ var objectContract = contract([
2825
+ { name: "param", match: isTag("param") },
2826
+ { name: "content", match: isOpenContent("param") }
2827
+ ]);
2828
+ var INTERACTIVE_CONTENT_TAGS = ["a", "button", "input", "select", "textarea", "label"];
2829
+ var buttonContract = closedContract([
2830
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2831
+ ]);
2832
+ var anchorContract = closedContract([
2833
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2834
+ ]);
2835
+ var LABELABLE_TAGS = [
2836
+ "button",
2837
+ "input",
2838
+ "meter",
2839
+ "output",
2840
+ "progress",
2841
+ "select",
2842
+ "textarea"
2843
+ ];
2844
+ var labelContract = contract([
2845
+ { name: "control", match: isTag(...LABELABLE_TAGS), cardinality: { max: 1 } }
2846
+ ]);
2847
+ var P_BLOCKED_TAGS = [
2848
+ "address",
2849
+ "article",
2850
+ "aside",
2851
+ "blockquote",
2852
+ "details",
2853
+ "dialog",
2854
+ "div",
2855
+ "dl",
2856
+ "fieldset",
2857
+ "figure",
2858
+ "footer",
2859
+ "form",
2860
+ "h1",
2861
+ "h2",
2862
+ "h3",
2863
+ "h4",
2864
+ "h5",
2865
+ "h6",
2866
+ "header",
2867
+ "hr",
2868
+ "main",
2869
+ "nav",
2870
+ "ol",
2871
+ "p",
2872
+ "pre",
2873
+ "section",
2874
+ "table",
2875
+ "ul"
2876
+ ];
2877
+ var pContract = closedContract([
2878
+ { name: "content", match: isOpenContent(...P_BLOCKED_TAGS) }
2879
+ ]);
2697
2880
  var mediaContract = contract([
2698
2881
  { name: "source", match: isTag("source") },
2699
2882
  { name: "track", match: isTag("track") },
@@ -2748,6 +2931,11 @@ var htmlContracts = {
2748
2931
  details: detailsContract,
2749
2932
  fieldset: fieldsetContract,
2750
2933
  dialog: dialogContract,
2934
+ object: objectContract,
2935
+ button: buttonContract,
2936
+ a: anchorContract,
2937
+ label: labelContract,
2938
+ p: pContract,
2751
2939
  head: headContract,
2752
2940
  html: htmlContract
2753
2941
  };
@@ -2974,6 +3162,11 @@ function composeNormalizers(normalizers, fn) {
2974
3162
  function whenDefined(key, value) {
2975
3163
  return value === void 0 ? {} : { [key]: value };
2976
3164
  }
3165
+ function mergeAriaRules(aria, rules) {
3166
+ if (!aria?.length) return rules;
3167
+ if (!rules?.length) return aria;
3168
+ return [...aria, ...rules];
3169
+ }
2977
3170
  function resolveFactoryOptions(options = {}) {
2978
3171
  const { styling, enforcement } = options;
2979
3172
  const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
@@ -2994,7 +3187,7 @@ function resolveFactoryOptions(options = {}) {
2994
3187
  ...whenDefined("defaultVariants", styling?.defaults),
2995
3188
  ...whenDefined("compoundVariants", styling?.compounds),
2996
3189
  ...whenDefined("normalizeFn", composedNormalizeFn),
2997
- ...whenDefined("ariaRules", enforcement?.aria),
3190
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
2998
3191
  ...whenDefined("childRules", enforcement?.children),
2999
3192
  ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
3000
3193
  ...whenDefined("allowText", enforcement?.allowText),