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