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.
@@ -20,6 +20,9 @@ function isNull(value) {
20
20
  function isNonNull(value) {
21
21
  return value != null;
22
22
  }
23
+ function isNullish(value) {
24
+ return isNull(value) || value === void 0;
25
+ }
23
26
 
24
27
  // ../../lib/primitive/src/utils/type-guards.ts
25
28
  function isObject(value, excludeArrays = false) {
@@ -607,20 +610,29 @@ function isAriaAttributeValidForRole(attr, role) {
607
610
  }
608
611
 
609
612
  // ../../lib/primitive/src/guards/aria/is-aria-role.ts
613
+ function lookupImplicitRole(tag) {
614
+ return IMPLICIT_ROLE_RECORD[tag];
615
+ }
610
616
  function isStrongImplicitRole(tag) {
611
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
612
- return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
617
+ const role = lookupImplicitRole(tag);
618
+ return !isNullish(role) && STRONG_ROLES_SET.has(role);
613
619
  }
614
- function isStandaloneTag(tag) {
615
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
616
- return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
620
+ function hasStandaloneRole(tag) {
621
+ const role = lookupImplicitRole(tag);
622
+ return !isNullish(role) && STANDALONE_ROLES_SET.has(role);
617
623
  }
618
- function getInputImplicitRole(type) {
619
- if (!isString(type) || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
620
- return INPUT_TYPE_ROLE_MAP[type];
624
+ var LIST_ELIGIBLE_INPUT_TYPES = /* @__PURE__ */ new Set(["text", "search", "tel", "url", "email"]);
625
+ function getInputImplicitRole(type, list) {
626
+ if (!isString(type)) return void 0;
627
+ const role = INPUT_TYPE_ROLE_MAP[type];
628
+ if (!role) return void 0;
629
+ if (!isNullish(list) && LIST_ELIGIBLE_INPUT_TYPES.has(type)) {
630
+ return "combobox";
631
+ }
632
+ return role;
621
633
  }
622
634
  function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
623
- const isNamed = isString(ariaLabel) || isString(ariaLabelledBy);
635
+ const isNamed = isString(ariaLabel) && ariaLabel.trim().length > 0 || isString(ariaLabelledBy) && ariaLabelledBy.trim().length > 0;
624
636
  if (!isNamed) return void 0;
625
637
  if (tag === "section") return "region";
626
638
  if (tag === "form") return "form";
@@ -681,7 +693,7 @@ function defineContractComponent(options) {
681
693
  // ../../lib/contract/src/aria/aria-role-policy.ts
682
694
  function getImplicitRole(tag, props) {
683
695
  if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
684
- if (tag === "input") return getInputImplicitRole(props?.type);
696
+ if (tag === "input") return getInputImplicitRole(props?.type, props?.list);
685
697
  if (tag === "img") return props?.alt === "" ? "none" : "img";
686
698
  if (tag === "section" || tag === "form") {
687
699
  return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
@@ -964,7 +976,11 @@ var ATTRIBUTE_IGNORED_CODES = {
964
976
  max: DiagnosticCode3.HtmlInputMaxIgnoredForType,
965
977
  step: DiagnosticCode3.HtmlInputStepIgnoredForType,
966
978
  accept: DiagnosticCode3.HtmlInputAcceptIgnoredForType,
967
- capture: DiagnosticCode3.HtmlInputCaptureIgnoredForType
979
+ capture: DiagnosticCode3.HtmlInputCaptureIgnoredForType,
980
+ size: DiagnosticCode3.HtmlInputSizeIgnoredForType,
981
+ alt: DiagnosticCode3.HtmlInputAltIgnoredForType,
982
+ height: DiagnosticCode3.HtmlInputHeightIgnoredForType,
983
+ width: DiagnosticCode3.HtmlInputWidthIgnoredForType
968
984
  };
969
985
  var HtmlDiagnostics = {
970
986
  emptyRole(tag) {
@@ -1198,8 +1214,132 @@ var InvariantBase = class {
1198
1214
  }
1199
1215
  };
1200
1216
 
1201
- // ../../lib/contract/src/aria/polymorphic-validator.ts
1217
+ // ../../lib/contract/src/aria/spec/roles/required-properties.ts
1218
+ var REQUIRED_ARIA_PROPERTIES = {
1219
+ combobox: ["aria-expanded"],
1220
+ option: ["aria-selected"],
1221
+ slider: ["aria-valuenow"],
1222
+ scrollbar: ["aria-controls", "aria-valuenow"],
1223
+ spinbutton: ["aria-valuenow"]
1224
+ };
1225
+
1226
+ // ../../lib/contract/src/aria/spec/roles/name-required.ts
1227
+ var NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1228
+
1229
+ // ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
1202
1230
  var NO_VIOLATIONS = [{ valid: true }];
1231
+ function requiredAttributeByRole(roles, attribute) {
1232
+ return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
1233
+ }
1234
+ function checkRequiredAttributes(requirement, { props, effectiveRole }) {
1235
+ if (!effectiveRole) return NO_VIOLATIONS;
1236
+ const requiredAttributes = requirement.attributesByRole[effectiveRole];
1237
+ if (!requiredAttributes) return NO_VIOLATIONS;
1238
+ const results = [];
1239
+ for (const attribute of requiredAttributes) {
1240
+ if (attribute in props) continue;
1241
+ results.push({
1242
+ valid: false,
1243
+ fixable: false,
1244
+ severity: "warning",
1245
+ attribute,
1246
+ diagnostic: requirement.diagnosticFor(attribute, effectiveRole)
1247
+ });
1248
+ }
1249
+ return results;
1250
+ }
1251
+
1252
+ // ../../lib/contract/src/aria/spec/roles/live-region.ts
1253
+ var LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1254
+ ["alert", "assertive"],
1255
+ ["status", "polite"],
1256
+ ["log", "polite"],
1257
+ ["timer", "off"]
1258
+ ]);
1259
+ var ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
1260
+
1261
+ // ../../lib/contract/src/aria/spec/attributes/aria-value-types.ts
1262
+ var ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1263
+ // Boolean (true | false)
1264
+ ["aria-atomic", { kind: "boolean" }],
1265
+ ["aria-busy", { kind: "boolean" }],
1266
+ ["aria-disabled", { kind: "boolean" }],
1267
+ ["aria-expanded", { kind: "boolean" }],
1268
+ ["aria-hidden", { kind: "boolean" }],
1269
+ ["aria-modal", { kind: "boolean" }],
1270
+ ["aria-multiline", { kind: "boolean" }],
1271
+ ["aria-multiselectable", { kind: "boolean" }],
1272
+ ["aria-readonly", { kind: "boolean" }],
1273
+ ["aria-required", { kind: "boolean" }],
1274
+ ["aria-selected", { kind: "boolean" }],
1275
+ // Tristate (true | false | mixed)
1276
+ ["aria-checked", { kind: "tristate" }],
1277
+ ["aria-pressed", { kind: "tristate" }],
1278
+ // Numeric (any finite number)
1279
+ ["aria-valuenow", { kind: "number" }],
1280
+ ["aria-valuemin", { kind: "number" }],
1281
+ ["aria-valuemax", { kind: "number" }],
1282
+ // Integer with optional range
1283
+ ["aria-level", { kind: "integer", min: 1, max: 6 }],
1284
+ ["aria-posinset", { kind: "integer", min: 1 }],
1285
+ ["aria-setsize", { kind: "integer", min: -1 }],
1286
+ ["aria-rowcount", { kind: "integer", min: -1 }],
1287
+ ["aria-colcount", { kind: "integer", min: -1 }],
1288
+ ["aria-rowindex", { kind: "integer", min: 1 }],
1289
+ ["aria-colindex", { kind: "integer", min: 1 }],
1290
+ ["aria-rowspan", { kind: "integer", min: 0 }],
1291
+ ["aria-colspan", { kind: "integer", min: 0 }],
1292
+ // Enum (specific allowed tokens)
1293
+ ["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
1294
+ [
1295
+ "aria-current",
1296
+ {
1297
+ kind: "enum",
1298
+ values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
1299
+ }
1300
+ ],
1301
+ [
1302
+ "aria-haspopup",
1303
+ {
1304
+ kind: "enum",
1305
+ values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
1306
+ }
1307
+ ],
1308
+ ["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
1309
+ ["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
1310
+ ["aria-orientation", { kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }],
1311
+ ["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
1312
+ ]);
1313
+
1314
+ // ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
1315
+ var VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
1316
+ "additions",
1317
+ "removals",
1318
+ "text",
1319
+ "all"
1320
+ ]);
1321
+
1322
+ // ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
1323
+ var HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1324
+ ["h1", 1],
1325
+ ["h2", 2],
1326
+ ["h3", 3],
1327
+ ["h4", 4],
1328
+ ["h5", 5],
1329
+ ["h6", 6]
1330
+ ]);
1331
+
1332
+ // ../../lib/contract/src/aria/spec/elements/interactive-tags.ts
1333
+ var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
1334
+ "a",
1335
+ "button",
1336
+ "input",
1337
+ "select",
1338
+ "textarea"
1339
+ ]);
1340
+
1341
+ // ../../lib/contract/src/aria/polymorphic-validator.ts
1342
+ var NO_VIOLATIONS2 = [{ valid: true }];
1203
1343
  function isIntrinsicTag(tag) {
1204
1344
  return isString(tag);
1205
1345
  }
@@ -1242,7 +1382,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1242
1382
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1243
1383
  const implicitRole = getImplicitRole(tag, props);
1244
1384
  const hasRole2 = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1245
- if (!hasRole2) return { proceed: false, result: { props, violations: [] } };
1246
1385
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1247
1386
  const workingProps = normalized.normalized ? normalized.result.props : props;
1248
1387
  const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
@@ -1252,6 +1391,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1252
1391
  tag,
1253
1392
  implicitRole,
1254
1393
  effectiveRole,
1394
+ hasRole: hasRole2,
1255
1395
  props: workingProps,
1256
1396
  preExistingViolations,
1257
1397
  context: { tag, props: workingProps, implicitRole, effectiveRole }
@@ -1261,6 +1401,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1261
1401
  const violations = [];
1262
1402
  const fixes = [];
1263
1403
  iterate.forEach(rules, (rule) => {
1404
+ if (isNonNull(rule.tags) && !rule.tags.includes(context.tag)) return;
1264
1405
  iterate.forEach(rule(context), (result) => {
1265
1406
  if (result.valid) return;
1266
1407
  const {
@@ -1286,7 +1427,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1286
1427
  return { violations, fixes };
1287
1428
  }
1288
1429
  static #getRules(context) {
1289
- if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1430
+ if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && LIVE_REGION_ROLES.has(context.effectiveRole)) {
1290
1431
  return _AriaPolicyEngine.#pipeline;
1291
1432
  }
1292
1433
  return _AriaPolicyEngine.#implicitOnlyRules;
@@ -1294,6 +1435,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1294
1435
  static evaluate(tag, props) {
1295
1436
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1296
1437
  if (!derived.proceed) return derived.result;
1438
+ if (!derived.hasRole)
1439
+ return { props: derived.props, violations: [...derived.preExistingViolations] };
1297
1440
  const {
1298
1441
  tag: narrowedTag,
1299
1442
  implicitRole,
@@ -1318,10 +1461,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1318
1461
  props: workingProps,
1319
1462
  preExistingViolations
1320
1463
  } = derived;
1321
- const { violations, fixes } = _AriaPolicyEngine.#runRules(
1322
- [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1323
- context
1324
- );
1464
+ const rules = derived.hasRole ? [..._AriaPolicyEngine.#getRules(context), ...extraRules] : extraRules;
1465
+ const { violations, fixes } = _AriaPolicyEngine.#runRules(rules, context);
1325
1466
  const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1326
1467
  return { props: next, violations: [...preExistingViolations, ...violations] };
1327
1468
  }
@@ -1488,7 +1629,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1488
1629
  implicitRole
1489
1630
  }) {
1490
1631
  const role = props.role;
1491
- if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
1632
+ if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS2;
1492
1633
  if (isStrongImplicitRole(tag) && role === "region") {
1493
1634
  const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
1494
1635
  return [
@@ -1501,11 +1642,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1501
1642
  }
1502
1643
  ];
1503
1644
  }
1504
- return NO_VIOLATIONS;
1645
+ return NO_VIOLATIONS2;
1505
1646
  }
1506
1647
  static #checkRedundantRole({ tag, props, implicitRole }) {
1507
1648
  const role = props.role;
1508
- if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
1649
+ if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS2;
1509
1650
  const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
1510
1651
  return [
1511
1652
  {
@@ -1519,8 +1660,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1519
1660
  }
1520
1661
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1521
1662
  const role = props.role;
1522
- if (role !== "region") return NO_VIOLATIONS;
1523
- if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
1663
+ if (role !== "region") return NO_VIOLATIONS2;
1664
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS2;
1524
1665
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1525
1666
  return [
1526
1667
  {
@@ -1537,7 +1678,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1537
1678
  props,
1538
1679
  effectiveRole
1539
1680
  }) {
1540
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1681
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1541
1682
  const results = [];
1542
1683
  iterate.forEachEntry(props, (key) => {
1543
1684
  if (!key.startsWith("aria-")) return;
@@ -1555,62 +1696,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1555
1696
  return results;
1556
1697
  }
1557
1698
  // ─── ARIA attribute value validation ──────────────────────────────────────
1558
- // Accepted value shapes for typed ARIA attributes.
1559
- // Attributes not in this map are unconstrained (arbitrary string values permitted).
1560
- static #ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1561
- // Boolean (true | false)
1562
- ["aria-atomic", { kind: "boolean" }],
1563
- ["aria-busy", { kind: "boolean" }],
1564
- ["aria-disabled", { kind: "boolean" }],
1565
- ["aria-expanded", { kind: "boolean" }],
1566
- ["aria-hidden", { kind: "boolean" }],
1567
- ["aria-modal", { kind: "boolean" }],
1568
- ["aria-multiline", { kind: "boolean" }],
1569
- ["aria-multiselectable", { kind: "boolean" }],
1570
- ["aria-readonly", { kind: "boolean" }],
1571
- ["aria-required", { kind: "boolean" }],
1572
- ["aria-selected", { kind: "boolean" }],
1573
- // Tristate (true | false | mixed)
1574
- ["aria-checked", { kind: "tristate" }],
1575
- ["aria-pressed", { kind: "tristate" }],
1576
- // Numeric (any finite number)
1577
- ["aria-valuenow", { kind: "number" }],
1578
- ["aria-valuemin", { kind: "number" }],
1579
- ["aria-valuemax", { kind: "number" }],
1580
- // Integer with optional range
1581
- ["aria-level", { kind: "integer", min: 1, max: 6 }],
1582
- ["aria-posinset", { kind: "integer", min: 1 }],
1583
- ["aria-setsize", { kind: "integer", min: -1 }],
1584
- ["aria-rowcount", { kind: "integer", min: -1 }],
1585
- ["aria-colcount", { kind: "integer", min: -1 }],
1586
- ["aria-rowindex", { kind: "integer", min: 1 }],
1587
- ["aria-colindex", { kind: "integer", min: 1 }],
1588
- ["aria-rowspan", { kind: "integer", min: 0 }],
1589
- ["aria-colspan", { kind: "integer", min: 0 }],
1590
- // Enum (specific allowed tokens)
1591
- ["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
1592
- [
1593
- "aria-current",
1594
- {
1595
- kind: "enum",
1596
- values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
1597
- }
1598
- ],
1599
- [
1600
- "aria-haspopup",
1601
- {
1602
- kind: "enum",
1603
- values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
1604
- }
1605
- ],
1606
- ["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
1607
- ["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
1608
- [
1609
- "aria-orientation",
1610
- { kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }
1611
- ],
1612
- ["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
1613
- ]);
1614
1699
  static #isValidAriaValue(value, type) {
1615
1700
  switch (type.kind) {
1616
1701
  case "boolean":
@@ -1655,11 +1740,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1655
1740
  }
1656
1741
  }
1657
1742
  static #checkAriaAttributeValues({ props, effectiveRole }) {
1658
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1743
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1659
1744
  const results = [];
1660
1745
  iterate.forEachEntry(props, (key, value) => {
1661
1746
  if (!key.startsWith("aria-")) return;
1662
- const type = _AriaPolicyEngine.#ARIA_VALUE_TYPES.get(key);
1747
+ const type = ARIA_VALUE_TYPES.get(key);
1663
1748
  if (!isNonNull(type)) return;
1664
1749
  if (_AriaPolicyEngine.#isValidAriaValue(value, type)) return;
1665
1750
  results.push({
@@ -1678,26 +1763,18 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1678
1763
  return results;
1679
1764
  }
1680
1765
  // ─── Heading implicit level ────────────────────────────────────────────────
1681
- static #HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1682
- ["h1", 1],
1683
- ["h2", 2],
1684
- ["h3", 3],
1685
- ["h4", 4],
1686
- ["h5", 5],
1687
- ["h6", 6]
1688
- ]);
1689
1766
  static #checkRedundantAriaLevel({
1690
1767
  tag,
1691
1768
  props,
1692
1769
  effectiveRole
1693
1770
  }) {
1694
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1695
- const implicitLevel = _AriaPolicyEngine.#HEADING_IMPLICIT_LEVELS.get(tag);
1696
- if (!isNonNull(implicitLevel)) return NO_VIOLATIONS;
1771
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1772
+ const implicitLevel = HEADING_IMPLICIT_LEVELS.get(tag);
1773
+ if (!isNonNull(implicitLevel)) return NO_VIOLATIONS2;
1697
1774
  const raw = props["aria-level"];
1698
- if (!isNonNull(raw)) return NO_VIOLATIONS;
1775
+ if (!isNonNull(raw)) return NO_VIOLATIONS2;
1699
1776
  const n = typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : NaN;
1700
- if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS;
1777
+ if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS2;
1701
1778
  return [
1702
1779
  {
1703
1780
  valid: false,
@@ -1710,20 +1787,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1710
1787
  ];
1711
1788
  }
1712
1789
  // ─── Name-required roles ───────────────────────────────────────────────────
1713
- // Roles that always require an accessible name per WAI-ARIA APG.
1714
- // Dialog and landmark names are enforced via contracts (ariaContract) rather than
1715
- // the built-in pipeline so consumers can opt in; img is built in because role=img
1716
- // on any element (including bare <img>) is definitionally useless without a name.
1717
- static #NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1718
1790
  static #checkNameRequiredRoles({
1719
1791
  tag,
1720
1792
  props,
1721
1793
  effectiveRole
1722
1794
  }) {
1723
- if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole))
1724
- return NO_VIOLATIONS;
1725
- if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS;
1726
- if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS;
1795
+ if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return NO_VIOLATIONS2;
1796
+ if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS2;
1797
+ if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS2;
1727
1798
  return [
1728
1799
  {
1729
1800
  valid: false,
@@ -1733,51 +1804,21 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1733
1804
  }
1734
1805
  ];
1735
1806
  }
1736
- // WAI-ARIA 1.2 required states and properties, keyed by role.
1737
- // Source: https://www.w3.org/TR/wai-aria-1.2/#requiredState
1738
- static #REQUIRED_PROPERTIES = /* @__PURE__ */ new Map([
1739
- ["combobox", ["aria-expanded"]],
1740
- ["option", ["aria-selected"]],
1741
- ["slider", ["aria-valuenow"]],
1742
- ["scrollbar", ["aria-controls", "aria-valuenow"]],
1743
- ["spinbutton", ["aria-valuenow"]]
1744
- ]);
1745
- static #checkRequiredAriaProperties({
1746
- props,
1747
- effectiveRole
1748
- }) {
1749
- if (!effectiveRole) return NO_VIOLATIONS;
1750
- const required = _AriaPolicyEngine.#REQUIRED_PROPERTIES.get(effectiveRole);
1751
- if (!isNonNull(required)) return NO_VIOLATIONS;
1752
- const results = [];
1753
- iterate.forEach(required, (attr) => {
1754
- if (attr in props) return;
1755
- results.push({
1756
- valid: false,
1757
- fixable: false,
1758
- severity: "warning",
1759
- attribute: attr,
1760
- diagnostic: AriaDiagnostics.requiredProperty(attr, effectiveRole)
1761
- });
1762
- });
1763
- return results;
1807
+ static #requiredAriaPropertiesRule = {
1808
+ attributesByRole: REQUIRED_ARIA_PROPERTIES,
1809
+ diagnosticFor: (attribute, role) => AriaDiagnostics.requiredProperty(attribute, role)
1810
+ };
1811
+ static #checkRequiredAriaProperties(context) {
1812
+ return checkRequiredAttributes(_AriaPolicyEngine.#requiredAriaPropertiesRule, context);
1764
1813
  }
1765
- // Natively interactive HTML elements — always keyboard-reachable unless explicitly disabled.
1766
- static #INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
1767
- "a",
1768
- "button",
1769
- "input",
1770
- "select",
1771
- "textarea"
1772
- ]);
1773
1814
  // WAI-ARIA 1.2 §6.6: aria-hidden="true" must not be placed on focusable elements.
1774
1815
  static #checkAriaHiddenOnFocusable({ tag, props }) {
1775
- if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS;
1776
- const isInteractive = _AriaPolicyEngine.#INTERACTIVE_TAGS.has(tag);
1816
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS2;
1817
+ const isInteractive = INTERACTIVE_TAGS.has(tag);
1777
1818
  if (!isInteractive) {
1778
1819
  const tabindex = props.tabindex;
1779
1820
  const n = typeof tabindex === "number" ? tabindex : typeof tabindex === "string" ? parseInt(tabindex, 10) : NaN;
1780
- if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS;
1821
+ if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS2;
1781
1822
  }
1782
1823
  return [
1783
1824
  {
@@ -1796,7 +1837,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1796
1837
  props,
1797
1838
  effectiveRole
1798
1839
  }) {
1799
- if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS;
1840
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS2;
1800
1841
  const results = [];
1801
1842
  iterate.forEachEntry(props, (key) => {
1802
1843
  if (!key.startsWith("aria-")) return;
@@ -1812,18 +1853,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1812
1853
  });
1813
1854
  return results;
1814
1855
  }
1815
- // WAI-ARIA live region roles and their implied aria-live politeness values.
1816
- static #LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1817
- ["alert", "assertive"],
1818
- ["status", "polite"],
1819
- ["log", "polite"],
1820
- ["timer", "off"]
1821
- ]);
1822
1856
  static #checkMissingLiveRegion({ effectiveRole, props }) {
1823
- if (!effectiveRole) return NO_VIOLATIONS;
1824
- const impliedLive = _AriaPolicyEngine.#LIVE_REGION_ROLES.get(effectiveRole);
1825
- if (!impliedLive) return NO_VIOLATIONS;
1826
- if ("aria-live" in props) return NO_VIOLATIONS;
1857
+ if (!effectiveRole) return NO_VIOLATIONS2;
1858
+ const impliedLive = LIVE_REGION_ROLES.get(effectiveRole);
1859
+ if (!impliedLive) return NO_VIOLATIONS2;
1860
+ if ("aria-live" in props) return NO_VIOLATIONS2;
1827
1861
  const injectLive = {
1828
1862
  kind: `injectLive:${effectiveRole}`,
1829
1863
  apply: (ctx) => ({
@@ -1842,20 +1876,13 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1842
1876
  }
1843
1877
  ];
1844
1878
  }
1845
- static #checkMissingAtomic({ effectiveRole, props }) {
1846
- if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole))
1847
- return NO_VIOLATIONS;
1848
- if ("aria-atomic" in props) return NO_VIOLATIONS;
1849
- return [
1850
- {
1851
- valid: false,
1852
- fixable: false,
1853
- severity: "warning",
1854
- diagnostic: AriaDiagnostics.missingAtomic(effectiveRole)
1855
- }
1856
- ];
1879
+ static #missingAtomicRule = {
1880
+ attributesByRole: ATOMIC_REQUIREMENTS,
1881
+ diagnosticFor: (_attribute, role) => AriaDiagnostics.missingAtomic(role)
1882
+ };
1883
+ static #checkMissingAtomic(context) {
1884
+ return checkRequiredAttributes(_AriaPolicyEngine.#missingAtomicRule, context);
1857
1885
  }
1858
- static #VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set(["additions", "removals", "text", "all"]);
1859
1886
  // Custom fix rules passed via `options.rules` must be pure functions of (tag, props) — the cache
1860
1887
  // replays stored fixes against new prop objects, so fixes that close over external state will
1861
1888
  // produce inconsistent results on cache hits.
@@ -1869,10 +1896,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1869
1896
  };
1870
1897
  static #checkInvalidAriaRelevant({ props }) {
1871
1898
  const relevant = props["aria-relevant"];
1872
- if (relevant === void 0) return NO_VIOLATIONS;
1873
- if (typeof relevant !== "string") return NO_VIOLATIONS;
1899
+ if (relevant === void 0) return NO_VIOLATIONS2;
1900
+ if (typeof relevant !== "string") return NO_VIOLATIONS2;
1874
1901
  const tokens = relevant.trim().split(/\s+/);
1875
- const invalid = tokens.filter((t) => !_AriaPolicyEngine.#VALID_RELEVANT_TOKENS.has(t));
1902
+ const invalid = tokens.filter((t) => !VALID_RELEVANT_TOKENS.has(t));
1876
1903
  if (invalid.length > 0) {
1877
1904
  return [
1878
1905
  {
@@ -1897,7 +1924,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1897
1924
  }
1898
1925
  ];
1899
1926
  }
1900
- return NO_VIOLATIONS;
1927
+ return NO_VIOLATIONS2;
1901
1928
  }
1902
1929
  };
1903
1930
 
@@ -2217,7 +2244,59 @@ var readonlyProps = ({
2217
2244
  // ../core/src/html/evaluators.ts
2218
2245
  import { warnDiagnostics as warnDiagnostics2 } from "../_shared/diagnostics.js";
2219
2246
 
2220
- // ../core/src/html/input-rules.ts
2247
+ // ../core/src/html/spec/vocabulary/input.ts
2248
+ var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2249
+ var NUMERIC_INPUT_TYPES = [
2250
+ "number",
2251
+ "range",
2252
+ "date",
2253
+ "month",
2254
+ "week",
2255
+ "time",
2256
+ "datetime-local"
2257
+ ];
2258
+ var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
2259
+ ...TEXT_INPUT_TYPES,
2260
+ ...NUMERIC_INPUT_TYPES,
2261
+ "checkbox",
2262
+ "radio",
2263
+ "file",
2264
+ "color",
2265
+ "hidden",
2266
+ "button",
2267
+ "submit",
2268
+ "reset",
2269
+ "image"
2270
+ ]);
2271
+
2272
+ // ../core/src/html/spec/attributes/input.ts
2273
+ var INPUT_ATTRIBUTE_TYPE_POLICIES = [
2274
+ { attribute: "checked", allowedTypes: ["checkbox", "radio"] },
2275
+ { attribute: "multiple", allowedTypes: ["email", "file"] },
2276
+ { attribute: "maxLength", allowedTypes: TEXT_INPUT_TYPES },
2277
+ { attribute: "minLength", allowedTypes: TEXT_INPUT_TYPES },
2278
+ { attribute: "pattern", allowedTypes: TEXT_INPUT_TYPES },
2279
+ { attribute: "min", allowedTypes: NUMERIC_INPUT_TYPES },
2280
+ { attribute: "max", allowedTypes: NUMERIC_INPUT_TYPES },
2281
+ { attribute: "step", allowedTypes: NUMERIC_INPUT_TYPES },
2282
+ { attribute: "accept", allowedTypes: ["file"] },
2283
+ { attribute: "capture", allowedTypes: ["file"] },
2284
+ { attribute: "size", allowedTypes: TEXT_INPUT_TYPES },
2285
+ { attribute: "alt", allowedTypes: ["image"] },
2286
+ { attribute: "height", allowedTypes: ["image"] },
2287
+ { attribute: "width", allowedTypes: ["image"] }
2288
+ ];
2289
+
2290
+ // ../core/src/html/spec/constraints/input.ts
2291
+ var REQUIRED_READONLY_CONFLICT = {
2292
+ props: ["required", "readOnly"],
2293
+ diagnostic: () => InputAccessibilityDiagnostics.requiredReadOnlyConflict()
2294
+ };
2295
+ var INPUT_MUTUALLY_EXCLUSIVE_POLICIES = [
2296
+ REQUIRED_READONLY_CONFLICT
2297
+ ];
2298
+
2299
+ // ../core/src/html/spec/validators/attribute-type-validator.ts
2221
2300
  var DEFAULT_INPUT_TYPE = "text";
2222
2301
  function omit(props, key) {
2223
2302
  const next = { ...props };
@@ -2233,7 +2312,10 @@ function removeAttributeFix(attribute) {
2233
2312
  }
2234
2313
  };
2235
2314
  }
2236
- function inputAttributeRequiresType(attribute, allowedTypes) {
2315
+ function createInputAttributeTypeRule({
2316
+ attribute,
2317
+ allowedTypes
2318
+ }) {
2237
2319
  const rule = ({ tag, props }) => {
2238
2320
  if (tag !== "input" || !(attribute in props)) return [];
2239
2321
  const type = typeof props.type === "string" ? props.type : DEFAULT_INPUT_TYPE;
@@ -2249,31 +2331,30 @@ function inputAttributeRequiresType(attribute, allowedTypes) {
2249
2331
  }
2250
2332
  ];
2251
2333
  };
2252
- return Object.assign(rule, { readsProps: ["type", attribute] });
2334
+ return Object.assign(rule, { readsProps: ["type", attribute], tags: ["input"] });
2335
+ }
2336
+
2337
+ // ../core/src/html/spec/validators/mutually-exclusive-validator.ts
2338
+ function createMutuallyExclusiveRule({
2339
+ props: conflictingProps,
2340
+ diagnostic: createDiagnostic
2341
+ }) {
2342
+ const [first, second] = conflictingProps;
2343
+ const rule = ({ tag, props }) => {
2344
+ if (tag !== "input" || !props[first] || !props[second]) return [];
2345
+ const diagnostic = createDiagnostic();
2346
+ return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2347
+ };
2348
+ return Object.assign(rule, { readsProps: conflictingProps, tags: ["input"] });
2349
+ }
2350
+
2351
+ // ../core/src/html/input-rules.ts
2352
+ var policyByAttribute = Object.fromEntries(
2353
+ INPUT_ATTRIBUTE_TYPE_POLICIES.map((policy) => [policy.attribute, policy])
2354
+ );
2355
+ function policyFor(attribute) {
2356
+ return policyByAttribute[attribute];
2253
2357
  }
2254
- var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2255
- var NUMERIC_INPUT_TYPES = [
2256
- "number",
2257
- "range",
2258
- "date",
2259
- "month",
2260
- "week",
2261
- "time",
2262
- "datetime-local"
2263
- ];
2264
- var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
2265
- ...TEXT_INPUT_TYPES,
2266
- ...NUMERIC_INPUT_TYPES,
2267
- "checkbox",
2268
- "radio",
2269
- "file",
2270
- "color",
2271
- "hidden",
2272
- "button",
2273
- "submit",
2274
- "reset",
2275
- "image"
2276
- ]);
2277
2358
  var supportedInputTypeRule = Object.assign(
2278
2359
  ({ tag, props }) => {
2279
2360
  if (tag !== "input" || typeof props.type !== "string") return [];
@@ -2289,30 +2370,22 @@ var supportedInputTypeRule = Object.assign(
2289
2370
  }
2290
2371
  ];
2291
2372
  },
2292
- { readsProps: ["type"] }
2373
+ { readsProps: ["type"], tags: ["input"] }
2293
2374
  );
2294
- var checkedRequiresCheckableTypeRule = inputAttributeRequiresType("checked", [
2295
- "checkbox",
2296
- "radio"
2297
- ]);
2298
- var multipleRequiresSupportedTypeRule = inputAttributeRequiresType("multiple", [
2299
- "email",
2300
- "file"
2301
- ]);
2302
- var maxLengthRequiresTextTypeRule = inputAttributeRequiresType(
2303
- "maxLength",
2304
- TEXT_INPUT_TYPES
2305
- );
2306
- var minLengthRequiresTextTypeRule = inputAttributeRequiresType(
2307
- "minLength",
2308
- TEXT_INPUT_TYPES
2309
- );
2310
- var patternRequiresTextTypeRule = inputAttributeRequiresType("pattern", TEXT_INPUT_TYPES);
2311
- var minRequiresNumericTypeRule = inputAttributeRequiresType("min", NUMERIC_INPUT_TYPES);
2312
- var maxRequiresNumericTypeRule = inputAttributeRequiresType("max", NUMERIC_INPUT_TYPES);
2313
- var stepRequiresNumericTypeRule = inputAttributeRequiresType("step", NUMERIC_INPUT_TYPES);
2314
- var acceptRequiresFileTypeRule = inputAttributeRequiresType("accept", ["file"]);
2315
- var captureRequiresFileTypeRule = inputAttributeRequiresType("capture", ["file"]);
2375
+ var checkedRequiresCheckableTypeRule = createInputAttributeTypeRule(policyFor("checked"));
2376
+ var multipleRequiresSupportedTypeRule = createInputAttributeTypeRule(policyFor("multiple"));
2377
+ var maxLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("maxLength"));
2378
+ var minLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("minLength"));
2379
+ var patternRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("pattern"));
2380
+ var minRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("min"));
2381
+ var maxRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("max"));
2382
+ var stepRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("step"));
2383
+ var acceptRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("accept"));
2384
+ var captureRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("capture"));
2385
+ var sizeRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("size"));
2386
+ var altRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("alt"));
2387
+ var heightRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("height"));
2388
+ var widthRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("width"));
2316
2389
  var inputAccessibleNameRule = Object.assign(
2317
2390
  ({ tag, props }) => {
2318
2391
  if (tag !== "input" || props.type === "hidden") return [];
@@ -2321,7 +2394,10 @@ var inputAccessibleNameRule = Object.assign(
2321
2394
  const diagnostic = hasPlaceholder ? InputAccessibilityDiagnostics.placeholderIsNotLabel() : InputAccessibilityDiagnostics.missingAccessibleName();
2322
2395
  return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2323
2396
  },
2324
- { readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"] }
2397
+ {
2398
+ readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"],
2399
+ tags: ["input"]
2400
+ }
2325
2401
  );
2326
2402
  var PASSWORD_AUTOCOMPLETE_VALUES = ["current-password", "new-password"];
2327
2403
  var passwordAutocompleteRule = Object.assign(
@@ -2333,16 +2409,9 @@ var passwordAutocompleteRule = Object.assign(
2333
2409
  const diagnostic = InputAccessibilityDiagnostics.passwordMissingAutocomplete();
2334
2410
  return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2335
2411
  },
2336
- { readsProps: ["type", "autoComplete"] }
2337
- );
2338
- var requiredReadOnlyConflictRule = Object.assign(
2339
- ({ tag, props }) => {
2340
- if (tag !== "input" || !props.required || !props.readOnly) return [];
2341
- const diagnostic = InputAccessibilityDiagnostics.requiredReadOnlyConflict();
2342
- return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2343
- },
2344
- { readsProps: ["required", "readOnly"] }
2412
+ { readsProps: ["type", "autoComplete"], tags: ["input"] }
2345
2413
  );
2414
+ var requiredReadOnlyConflictRule = createMutuallyExclusiveRule(REQUIRED_READONLY_CONFLICT);
2346
2415
  var INPUT_RULES = [
2347
2416
  supportedInputTypeRule,
2348
2417
  checkedRequiresCheckableTypeRule,
@@ -2355,11 +2424,132 @@ var INPUT_RULES = [
2355
2424
  stepRequiresNumericTypeRule,
2356
2425
  acceptRequiresFileTypeRule,
2357
2426
  captureRequiresFileTypeRule,
2427
+ sizeRequiresTextTypeRule,
2428
+ altRequiresImageTypeRule,
2429
+ heightRequiresImageTypeRule,
2430
+ widthRequiresImageTypeRule,
2358
2431
  inputAccessibleNameRule,
2359
2432
  passwordAutocompleteRule,
2360
2433
  requiredReadOnlyConflictRule
2361
2434
  ];
2362
2435
 
2436
+ // ../core/src/html/spec/types.ts
2437
+ function definePropRolePolicy(prop, map2, fallback) {
2438
+ return { kind: "byProp", prop, map: map2, fallback };
2439
+ }
2440
+ function resolveAllowedRoles(spec, props) {
2441
+ const policy = spec.allowedRoles;
2442
+ if (!policy) return void 0;
2443
+ switch (policy.kind) {
2444
+ case "fixed":
2445
+ return policy.roles;
2446
+ case "byProp": {
2447
+ const value = typeof props[policy.prop] === "string" ? props[policy.prop] : policy.fallback;
2448
+ return policy.map[value];
2449
+ }
2450
+ case "dynamic":
2451
+ return policy.resolve({ props });
2452
+ }
2453
+ }
2454
+
2455
+ // ../core/src/html/spec/roles/input.ts
2456
+ var ALLOWED_INPUT_ROLES = {
2457
+ checkbox: ["menuitemcheckbox", "option", "switch", "button"],
2458
+ radio: ["menuitemradio"],
2459
+ range: [],
2460
+ number: [],
2461
+ search: ["combobox"],
2462
+ text: ["combobox", "searchbox", "spinbutton"],
2463
+ email: ["combobox"],
2464
+ tel: ["combobox"],
2465
+ url: ["combobox"],
2466
+ button: [
2467
+ "link",
2468
+ "menuitem",
2469
+ "menuitemcheckbox",
2470
+ "menuitemradio",
2471
+ "option",
2472
+ "radio",
2473
+ "switch",
2474
+ "tab"
2475
+ ],
2476
+ submit: [
2477
+ "link",
2478
+ "menuitem",
2479
+ "menuitemcheckbox",
2480
+ "menuitemradio",
2481
+ "option",
2482
+ "radio",
2483
+ "switch",
2484
+ "tab"
2485
+ ],
2486
+ reset: [
2487
+ "link",
2488
+ "menuitem",
2489
+ "menuitemcheckbox",
2490
+ "menuitemradio",
2491
+ "option",
2492
+ "radio",
2493
+ "switch",
2494
+ "tab"
2495
+ ],
2496
+ image: [
2497
+ "link",
2498
+ "menuitem",
2499
+ "menuitemcheckbox",
2500
+ "menuitemradio",
2501
+ "option",
2502
+ "radio",
2503
+ "switch",
2504
+ "tab"
2505
+ ],
2506
+ hidden: []
2507
+ };
2508
+
2509
+ // ../core/src/html/spec/elements/input.ts
2510
+ var inputElementSpec = {
2511
+ tag: "input",
2512
+ allowedRoles: definePropRolePolicy("type", ALLOWED_INPUT_ROLES, "text"),
2513
+ attributes: INPUT_ATTRIBUTE_TYPE_POLICIES,
2514
+ mutuallyExclusive: INPUT_MUTUALLY_EXCLUSIVE_POLICIES
2515
+ };
2516
+
2517
+ // ../core/src/html/spec/roles/img.ts
2518
+ var IMG_NAMED_ROLES = [
2519
+ "button",
2520
+ "checkbox",
2521
+ "link",
2522
+ "menuitem",
2523
+ "menuitemcheckbox",
2524
+ "menuitemradio",
2525
+ "option",
2526
+ "progressbar",
2527
+ "scrollbar",
2528
+ "separator",
2529
+ "slider",
2530
+ "switch",
2531
+ "tab",
2532
+ "treeitem"
2533
+ ];
2534
+
2535
+ // ../core/src/html/spec/elements/img.ts
2536
+ var imgElementSpec = {
2537
+ tag: "img",
2538
+ allowedRoles: {
2539
+ kind: "dynamic",
2540
+ resolve: ({ props }) => props.alt === "" ? [] : IMG_NAMED_ROLES
2541
+ }
2542
+ };
2543
+
2544
+ // ../core/src/html/spec/roles/table.ts
2545
+ var ALLOWED_TABLE_ROLES = ["grid", "treegrid"];
2546
+
2547
+ // ../core/src/html/spec/elements/table.ts
2548
+ var tableElementSpec = {
2549
+ tag: "table",
2550
+ allowedRoles: { kind: "fixed", roles: ALLOWED_TABLE_ROLES }
2551
+ };
2552
+
2363
2553
  // ../core/src/html/role-restrictions.ts
2364
2554
  var ALLOWED_ROLES = {
2365
2555
  article: ["application", "document", "feed", "main", "none", "presentation", "region"],
@@ -2432,86 +2622,17 @@ var ALLOWED_ROLES = {
2432
2622
  "tab",
2433
2623
  "treeitem"
2434
2624
  ],
2435
- table: ["grid", "treegrid"],
2436
2625
  dialog: ["alertdialog"],
2437
2626
  fieldset: ["none", "presentation", "radiogroup"]
2438
2627
  };
2439
- var IMG_NAMED_ROLES = [
2440
- "button",
2441
- "checkbox",
2442
- "link",
2443
- "menuitem",
2444
- "menuitemcheckbox",
2445
- "menuitemradio",
2446
- "option",
2447
- "progressbar",
2448
- "scrollbar",
2449
- "separator",
2450
- "slider",
2451
- "switch",
2452
- "tab",
2453
- "treeitem"
2454
- ];
2455
- var ALLOWED_INPUT_ROLES = {
2456
- checkbox: ["menuitemcheckbox", "option", "switch", "button"],
2457
- radio: ["menuitemradio"],
2458
- range: [],
2459
- number: [],
2460
- search: ["combobox"],
2461
- text: ["combobox", "searchbox", "spinbutton"],
2462
- email: ["combobox"],
2463
- tel: ["combobox"],
2464
- url: ["combobox"],
2465
- button: [
2466
- "link",
2467
- "menuitem",
2468
- "menuitemcheckbox",
2469
- "menuitemradio",
2470
- "option",
2471
- "radio",
2472
- "switch",
2473
- "tab"
2474
- ],
2475
- submit: [
2476
- "link",
2477
- "menuitem",
2478
- "menuitemcheckbox",
2479
- "menuitemradio",
2480
- "option",
2481
- "radio",
2482
- "switch",
2483
- "tab"
2484
- ],
2485
- reset: [
2486
- "link",
2487
- "menuitem",
2488
- "menuitemcheckbox",
2489
- "menuitemradio",
2490
- "option",
2491
- "radio",
2492
- "switch",
2493
- "tab"
2494
- ],
2495
- image: [
2496
- "link",
2497
- "menuitem",
2498
- "menuitemcheckbox",
2499
- "menuitemradio",
2500
- "option",
2501
- "radio",
2502
- "switch",
2503
- "tab"
2504
- ],
2505
- hidden: []
2628
+ var ELEMENT_SPECS = {
2629
+ input: inputElementSpec,
2630
+ img: imgElementSpec,
2631
+ table: tableElementSpec
2506
2632
  };
2507
2633
  function getAllowedRoles(tag, props) {
2508
- if (tag === "input") {
2509
- const type = typeof props.type === "string" ? props.type : "text";
2510
- return ALLOWED_INPUT_ROLES[type];
2511
- }
2512
- if (tag === "img") {
2513
- return props.alt === "" ? [] : IMG_NAMED_ROLES;
2514
- }
2634
+ const spec = ELEMENT_SPECS[tag];
2635
+ if (spec) return resolveAllowedRoles(spec, props);
2515
2636
  return ALLOWED_ROLES[tag];
2516
2637
  }
2517
2638
  var removeRoleFix = {
@@ -2552,21 +2673,24 @@ var removeLandmarkRoleOverride = {
2552
2673
  return { applied: true, next: rest, previous: props };
2553
2674
  }
2554
2675
  };
2555
- function landmarkRoleRule({ tag, props, implicitRole }) {
2556
- if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2557
- const role = props.role;
2558
- if (!role || role === implicitRole) return [];
2559
- const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2560
- return [
2561
- {
2562
- valid: false,
2563
- fixable: true,
2564
- severity: diagnostic.severity,
2565
- fix: removeLandmarkRoleOverride,
2566
- diagnostic
2567
- }
2568
- ];
2569
- }
2676
+ var landmarkRoleRule = Object.assign(
2677
+ ({ tag, props, implicitRole }) => {
2678
+ if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2679
+ const role = props.role;
2680
+ if (!role || role === implicitRole) return [];
2681
+ const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2682
+ return [
2683
+ {
2684
+ valid: false,
2685
+ fixable: true,
2686
+ severity: diagnostic.severity,
2687
+ fix: removeLandmarkRoleOverride,
2688
+ diagnostic
2689
+ }
2690
+ ];
2691
+ },
2692
+ { tags: [...LANDMARK_TAG_SET] }
2693
+ );
2570
2694
  function requireAccessibleName({ tag, props }) {
2571
2695
  if ("aria-label" in props || "aria-labelledby" in props) return [];
2572
2696
  return [
@@ -2579,10 +2703,13 @@ function requireAccessibleName({ tag, props }) {
2579
2703
  ];
2580
2704
  }
2581
2705
  var NAMED_LANDMARK_TAGS = /* @__PURE__ */ new Set(["nav", "aside"]);
2582
- function landmarkNameAdvisory(ctx) {
2583
- if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2584
- return requireAccessibleName(ctx);
2585
- }
2706
+ var landmarkNameAdvisory = Object.assign(
2707
+ (ctx) => {
2708
+ if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2709
+ return requireAccessibleName(ctx);
2710
+ },
2711
+ { tags: [...NAMED_LANDMARK_TAGS] }
2712
+ );
2586
2713
  var HTML_ARIA_RULES = [
2587
2714
  landmarkRoleRule,
2588
2715
  landmarkNameAdvisory,
@@ -2683,6 +2810,62 @@ var figureContract = contract([
2683
2810
  ]);
2684
2811
  var detailsContract = firstChildContract("summary", "summary");
2685
2812
  var fieldsetContract = firstChildContract("legend", "legend");
2813
+ var objectContract = contract([
2814
+ { name: "param", match: isTag("param") },
2815
+ { name: "content", match: isOpenContent("param") }
2816
+ ]);
2817
+ var INTERACTIVE_CONTENT_TAGS = ["a", "button", "input", "select", "textarea", "label"];
2818
+ var buttonContract = closedContract([
2819
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2820
+ ]);
2821
+ var anchorContract = closedContract([
2822
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2823
+ ]);
2824
+ var LABELABLE_TAGS = [
2825
+ "button",
2826
+ "input",
2827
+ "meter",
2828
+ "output",
2829
+ "progress",
2830
+ "select",
2831
+ "textarea"
2832
+ ];
2833
+ var labelContract = contract([
2834
+ { name: "control", match: isTag(...LABELABLE_TAGS), cardinality: { max: 1 } }
2835
+ ]);
2836
+ var P_BLOCKED_TAGS = [
2837
+ "address",
2838
+ "article",
2839
+ "aside",
2840
+ "blockquote",
2841
+ "details",
2842
+ "dialog",
2843
+ "div",
2844
+ "dl",
2845
+ "fieldset",
2846
+ "figure",
2847
+ "footer",
2848
+ "form",
2849
+ "h1",
2850
+ "h2",
2851
+ "h3",
2852
+ "h4",
2853
+ "h5",
2854
+ "h6",
2855
+ "header",
2856
+ "hr",
2857
+ "main",
2858
+ "nav",
2859
+ "ol",
2860
+ "p",
2861
+ "pre",
2862
+ "section",
2863
+ "table",
2864
+ "ul"
2865
+ ];
2866
+ var pContract = closedContract([
2867
+ { name: "content", match: isOpenContent(...P_BLOCKED_TAGS) }
2868
+ ]);
2686
2869
  var mediaContract = contract([
2687
2870
  { name: "source", match: isTag("source") },
2688
2871
  { name: "track", match: isTag("track") },
@@ -2737,6 +2920,11 @@ var htmlContracts = {
2737
2920
  details: detailsContract,
2738
2921
  fieldset: fieldsetContract,
2739
2922
  dialog: dialogContract,
2923
+ object: objectContract,
2924
+ button: buttonContract,
2925
+ a: anchorContract,
2926
+ label: labelContract,
2927
+ p: pContract,
2740
2928
  head: headContract,
2741
2929
  html: htmlContract
2742
2930
  };
@@ -2963,6 +3151,11 @@ function composeNormalizers(normalizers, fn) {
2963
3151
  function whenDefined(key, value) {
2964
3152
  return value === void 0 ? {} : { [key]: value };
2965
3153
  }
3154
+ function mergeAriaRules(aria, rules) {
3155
+ if (!aria?.length) return rules;
3156
+ if (!rules?.length) return aria;
3157
+ return [...aria, ...rules];
3158
+ }
2966
3159
  function resolveFactoryOptions(options = {}) {
2967
3160
  const { styling, enforcement } = options;
2968
3161
  const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
@@ -2983,7 +3176,7 @@ function resolveFactoryOptions(options = {}) {
2983
3176
  ...whenDefined("defaultVariants", styling?.defaults),
2984
3177
  ...whenDefined("compoundVariants", styling?.compounds),
2985
3178
  ...whenDefined("normalizeFn", composedNormalizeFn),
2986
- ...whenDefined("ariaRules", enforcement?.aria),
3179
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
2987
3180
  ...whenDefined("childRules", enforcement?.children),
2988
3181
  ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
2989
3182
  ...whenDefined("allowText", enforcement?.allowText),