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.
@@ -25,6 +25,9 @@ function isNull(value) {
25
25
  function isNonNull(value) {
26
26
  return value != null;
27
27
  }
28
+ function isNullish(value) {
29
+ return isNull(value) || value === void 0;
30
+ }
28
31
 
29
32
  // ../../lib/primitive/src/utils/type-guards.ts
30
33
  function isObject(value, excludeArrays = false) {
@@ -526,20 +529,29 @@ function isAriaAttributeValidForRole(attr, role) {
526
529
  }
527
530
 
528
531
  // ../../lib/primitive/src/guards/aria/is-aria-role.ts
532
+ function lookupImplicitRole(tag) {
533
+ return IMPLICIT_ROLE_RECORD[tag];
534
+ }
529
535
  function isStrongImplicitRole(tag) {
530
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
531
- return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
536
+ const role = lookupImplicitRole(tag);
537
+ return !isNullish(role) && STRONG_ROLES_SET.has(role);
532
538
  }
533
- function isStandaloneTag(tag) {
534
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
535
- return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
539
+ function hasStandaloneRole(tag) {
540
+ const role = lookupImplicitRole(tag);
541
+ return !isNullish(role) && STANDALONE_ROLES_SET.has(role);
536
542
  }
537
- function getInputImplicitRole(type) {
538
- if (!isString(type) || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
539
- return INPUT_TYPE_ROLE_MAP[type];
543
+ var LIST_ELIGIBLE_INPUT_TYPES = /* @__PURE__ */ new Set(["text", "search", "tel", "url", "email"]);
544
+ function getInputImplicitRole(type, list) {
545
+ if (!isString(type)) return void 0;
546
+ const role = INPUT_TYPE_ROLE_MAP[type];
547
+ if (!role) return void 0;
548
+ if (!isNullish(list) && LIST_ELIGIBLE_INPUT_TYPES.has(type)) {
549
+ return "combobox";
550
+ }
551
+ return role;
540
552
  }
541
553
  function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
542
- const isNamed = isString(ariaLabel) || isString(ariaLabelledBy);
554
+ const isNamed = isString(ariaLabel) && ariaLabel.trim().length > 0 || isString(ariaLabelledBy) && ariaLabelledBy.trim().length > 0;
543
555
  if (!isNamed) return void 0;
544
556
  if (tag === "section") return "region";
545
557
  if (tag === "form") return "form";
@@ -585,7 +597,7 @@ function isTag(...args) {
585
597
  // ../../lib/contract/src/aria/aria-role-policy.ts
586
598
  function getImplicitRole(tag, props) {
587
599
  if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
588
- if (tag === "input") return getInputImplicitRole(props?.type);
600
+ if (tag === "input") return getInputImplicitRole(props?.type, props?.list);
589
601
  if (tag === "img") return props?.alt === "" ? "none" : "img";
590
602
  if (tag === "section" || tag === "form") {
591
603
  return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
@@ -868,7 +880,11 @@ var ATTRIBUTE_IGNORED_CODES = {
868
880
  max: DiagnosticCode3.HtmlInputMaxIgnoredForType,
869
881
  step: DiagnosticCode3.HtmlInputStepIgnoredForType,
870
882
  accept: DiagnosticCode3.HtmlInputAcceptIgnoredForType,
871
- capture: DiagnosticCode3.HtmlInputCaptureIgnoredForType
883
+ capture: DiagnosticCode3.HtmlInputCaptureIgnoredForType,
884
+ size: DiagnosticCode3.HtmlInputSizeIgnoredForType,
885
+ alt: DiagnosticCode3.HtmlInputAltIgnoredForType,
886
+ height: DiagnosticCode3.HtmlInputHeightIgnoredForType,
887
+ width: DiagnosticCode3.HtmlInputWidthIgnoredForType
872
888
  };
873
889
  var HtmlDiagnostics = {
874
890
  emptyRole(tag) {
@@ -1102,8 +1118,132 @@ var InvariantBase = class {
1102
1118
  }
1103
1119
  };
1104
1120
 
1105
- // ../../lib/contract/src/aria/polymorphic-validator.ts
1121
+ // ../../lib/contract/src/aria/spec/roles/required-properties.ts
1122
+ var REQUIRED_ARIA_PROPERTIES = {
1123
+ combobox: ["aria-expanded"],
1124
+ option: ["aria-selected"],
1125
+ slider: ["aria-valuenow"],
1126
+ scrollbar: ["aria-controls", "aria-valuenow"],
1127
+ spinbutton: ["aria-valuenow"]
1128
+ };
1129
+
1130
+ // ../../lib/contract/src/aria/spec/roles/name-required.ts
1131
+ var NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1132
+
1133
+ // ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
1106
1134
  var NO_VIOLATIONS = [{ valid: true }];
1135
+ function requiredAttributeByRole(roles, attribute) {
1136
+ return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
1137
+ }
1138
+ function checkRequiredAttributes(requirement, { props, effectiveRole }) {
1139
+ if (!effectiveRole) return NO_VIOLATIONS;
1140
+ const requiredAttributes = requirement.attributesByRole[effectiveRole];
1141
+ if (!requiredAttributes) return NO_VIOLATIONS;
1142
+ const results = [];
1143
+ for (const attribute of requiredAttributes) {
1144
+ if (attribute in props) continue;
1145
+ results.push({
1146
+ valid: false,
1147
+ fixable: false,
1148
+ severity: "warning",
1149
+ attribute,
1150
+ diagnostic: requirement.diagnosticFor(attribute, effectiveRole)
1151
+ });
1152
+ }
1153
+ return results;
1154
+ }
1155
+
1156
+ // ../../lib/contract/src/aria/spec/roles/live-region.ts
1157
+ var LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1158
+ ["alert", "assertive"],
1159
+ ["status", "polite"],
1160
+ ["log", "polite"],
1161
+ ["timer", "off"]
1162
+ ]);
1163
+ var ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
1164
+
1165
+ // ../../lib/contract/src/aria/spec/attributes/aria-value-types.ts
1166
+ var ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1167
+ // Boolean (true | false)
1168
+ ["aria-atomic", { kind: "boolean" }],
1169
+ ["aria-busy", { kind: "boolean" }],
1170
+ ["aria-disabled", { kind: "boolean" }],
1171
+ ["aria-expanded", { kind: "boolean" }],
1172
+ ["aria-hidden", { kind: "boolean" }],
1173
+ ["aria-modal", { kind: "boolean" }],
1174
+ ["aria-multiline", { kind: "boolean" }],
1175
+ ["aria-multiselectable", { kind: "boolean" }],
1176
+ ["aria-readonly", { kind: "boolean" }],
1177
+ ["aria-required", { kind: "boolean" }],
1178
+ ["aria-selected", { kind: "boolean" }],
1179
+ // Tristate (true | false | mixed)
1180
+ ["aria-checked", { kind: "tristate" }],
1181
+ ["aria-pressed", { kind: "tristate" }],
1182
+ // Numeric (any finite number)
1183
+ ["aria-valuenow", { kind: "number" }],
1184
+ ["aria-valuemin", { kind: "number" }],
1185
+ ["aria-valuemax", { kind: "number" }],
1186
+ // Integer with optional range
1187
+ ["aria-level", { kind: "integer", min: 1, max: 6 }],
1188
+ ["aria-posinset", { kind: "integer", min: 1 }],
1189
+ ["aria-setsize", { kind: "integer", min: -1 }],
1190
+ ["aria-rowcount", { kind: "integer", min: -1 }],
1191
+ ["aria-colcount", { kind: "integer", min: -1 }],
1192
+ ["aria-rowindex", { kind: "integer", min: 1 }],
1193
+ ["aria-colindex", { kind: "integer", min: 1 }],
1194
+ ["aria-rowspan", { kind: "integer", min: 0 }],
1195
+ ["aria-colspan", { kind: "integer", min: 0 }],
1196
+ // Enum (specific allowed tokens)
1197
+ ["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
1198
+ [
1199
+ "aria-current",
1200
+ {
1201
+ kind: "enum",
1202
+ values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
1203
+ }
1204
+ ],
1205
+ [
1206
+ "aria-haspopup",
1207
+ {
1208
+ kind: "enum",
1209
+ values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
1210
+ }
1211
+ ],
1212
+ ["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
1213
+ ["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
1214
+ ["aria-orientation", { kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }],
1215
+ ["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
1216
+ ]);
1217
+
1218
+ // ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
1219
+ var VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
1220
+ "additions",
1221
+ "removals",
1222
+ "text",
1223
+ "all"
1224
+ ]);
1225
+
1226
+ // ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
1227
+ var HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1228
+ ["h1", 1],
1229
+ ["h2", 2],
1230
+ ["h3", 3],
1231
+ ["h4", 4],
1232
+ ["h5", 5],
1233
+ ["h6", 6]
1234
+ ]);
1235
+
1236
+ // ../../lib/contract/src/aria/spec/elements/interactive-tags.ts
1237
+ var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
1238
+ "a",
1239
+ "button",
1240
+ "input",
1241
+ "select",
1242
+ "textarea"
1243
+ ]);
1244
+
1245
+ // ../../lib/contract/src/aria/polymorphic-validator.ts
1246
+ var NO_VIOLATIONS2 = [{ valid: true }];
1107
1247
  function isIntrinsicTag(tag) {
1108
1248
  return isString(tag);
1109
1249
  }
@@ -1146,7 +1286,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1146
1286
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1147
1287
  const implicitRole = getImplicitRole(tag, props);
1148
1288
  const hasRole = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1149
- if (!hasRole) return { proceed: false, result: { props, violations: [] } };
1150
1289
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1151
1290
  const workingProps = normalized.normalized ? normalized.result.props : props;
1152
1291
  const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
@@ -1156,6 +1295,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1156
1295
  tag,
1157
1296
  implicitRole,
1158
1297
  effectiveRole,
1298
+ hasRole,
1159
1299
  props: workingProps,
1160
1300
  preExistingViolations,
1161
1301
  context: { tag, props: workingProps, implicitRole, effectiveRole }
@@ -1165,6 +1305,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1165
1305
  const violations = [];
1166
1306
  const fixes = [];
1167
1307
  iterate.forEach(rules, (rule) => {
1308
+ if (isNonNull(rule.tags) && !rule.tags.includes(context.tag)) return;
1168
1309
  iterate.forEach(rule(context), (result) => {
1169
1310
  if (result.valid) return;
1170
1311
  const {
@@ -1190,7 +1331,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1190
1331
  return { violations, fixes };
1191
1332
  }
1192
1333
  static #getRules(context) {
1193
- if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1334
+ if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && LIVE_REGION_ROLES.has(context.effectiveRole)) {
1194
1335
  return _AriaPolicyEngine.#pipeline;
1195
1336
  }
1196
1337
  return _AriaPolicyEngine.#implicitOnlyRules;
@@ -1198,6 +1339,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1198
1339
  static evaluate(tag, props) {
1199
1340
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1200
1341
  if (!derived.proceed) return derived.result;
1342
+ if (!derived.hasRole)
1343
+ return { props: derived.props, violations: [...derived.preExistingViolations] };
1201
1344
  const {
1202
1345
  tag: narrowedTag,
1203
1346
  implicitRole,
@@ -1222,10 +1365,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1222
1365
  props: workingProps,
1223
1366
  preExistingViolations
1224
1367
  } = derived;
1225
- const { violations, fixes } = _AriaPolicyEngine.#runRules(
1226
- [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1227
- context
1228
- );
1368
+ const rules = derived.hasRole ? [..._AriaPolicyEngine.#getRules(context), ...extraRules] : extraRules;
1369
+ const { violations, fixes } = _AriaPolicyEngine.#runRules(rules, context);
1229
1370
  const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1230
1371
  return { props: next, violations: [...preExistingViolations, ...violations] };
1231
1372
  }
@@ -1392,7 +1533,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1392
1533
  implicitRole
1393
1534
  }) {
1394
1535
  const role = props.role;
1395
- if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
1536
+ if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS2;
1396
1537
  if (isStrongImplicitRole(tag) && role === "region") {
1397
1538
  const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
1398
1539
  return [
@@ -1405,11 +1546,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1405
1546
  }
1406
1547
  ];
1407
1548
  }
1408
- return NO_VIOLATIONS;
1549
+ return NO_VIOLATIONS2;
1409
1550
  }
1410
1551
  static #checkRedundantRole({ tag, props, implicitRole }) {
1411
1552
  const role = props.role;
1412
- if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
1553
+ if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS2;
1413
1554
  const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
1414
1555
  return [
1415
1556
  {
@@ -1423,8 +1564,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1423
1564
  }
1424
1565
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1425
1566
  const role = props.role;
1426
- if (role !== "region") return NO_VIOLATIONS;
1427
- if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
1567
+ if (role !== "region") return NO_VIOLATIONS2;
1568
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS2;
1428
1569
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1429
1570
  return [
1430
1571
  {
@@ -1441,7 +1582,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1441
1582
  props,
1442
1583
  effectiveRole
1443
1584
  }) {
1444
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1585
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1445
1586
  const results = [];
1446
1587
  iterate.forEachEntry(props, (key) => {
1447
1588
  if (!key.startsWith("aria-")) return;
@@ -1459,62 +1600,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1459
1600
  return results;
1460
1601
  }
1461
1602
  // ─── ARIA attribute value validation ──────────────────────────────────────
1462
- // Accepted value shapes for typed ARIA attributes.
1463
- // Attributes not in this map are unconstrained (arbitrary string values permitted).
1464
- static #ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1465
- // Boolean (true | false)
1466
- ["aria-atomic", { kind: "boolean" }],
1467
- ["aria-busy", { kind: "boolean" }],
1468
- ["aria-disabled", { kind: "boolean" }],
1469
- ["aria-expanded", { kind: "boolean" }],
1470
- ["aria-hidden", { kind: "boolean" }],
1471
- ["aria-modal", { kind: "boolean" }],
1472
- ["aria-multiline", { kind: "boolean" }],
1473
- ["aria-multiselectable", { kind: "boolean" }],
1474
- ["aria-readonly", { kind: "boolean" }],
1475
- ["aria-required", { kind: "boolean" }],
1476
- ["aria-selected", { kind: "boolean" }],
1477
- // Tristate (true | false | mixed)
1478
- ["aria-checked", { kind: "tristate" }],
1479
- ["aria-pressed", { kind: "tristate" }],
1480
- // Numeric (any finite number)
1481
- ["aria-valuenow", { kind: "number" }],
1482
- ["aria-valuemin", { kind: "number" }],
1483
- ["aria-valuemax", { kind: "number" }],
1484
- // Integer with optional range
1485
- ["aria-level", { kind: "integer", min: 1, max: 6 }],
1486
- ["aria-posinset", { kind: "integer", min: 1 }],
1487
- ["aria-setsize", { kind: "integer", min: -1 }],
1488
- ["aria-rowcount", { kind: "integer", min: -1 }],
1489
- ["aria-colcount", { kind: "integer", min: -1 }],
1490
- ["aria-rowindex", { kind: "integer", min: 1 }],
1491
- ["aria-colindex", { kind: "integer", min: 1 }],
1492
- ["aria-rowspan", { kind: "integer", min: 0 }],
1493
- ["aria-colspan", { kind: "integer", min: 0 }],
1494
- // Enum (specific allowed tokens)
1495
- ["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
1496
- [
1497
- "aria-current",
1498
- {
1499
- kind: "enum",
1500
- values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
1501
- }
1502
- ],
1503
- [
1504
- "aria-haspopup",
1505
- {
1506
- kind: "enum",
1507
- values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
1508
- }
1509
- ],
1510
- ["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
1511
- ["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
1512
- [
1513
- "aria-orientation",
1514
- { kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }
1515
- ],
1516
- ["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
1517
- ]);
1518
1603
  static #isValidAriaValue(value, type) {
1519
1604
  switch (type.kind) {
1520
1605
  case "boolean":
@@ -1559,11 +1644,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1559
1644
  }
1560
1645
  }
1561
1646
  static #checkAriaAttributeValues({ props, effectiveRole }) {
1562
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1647
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1563
1648
  const results = [];
1564
1649
  iterate.forEachEntry(props, (key, value) => {
1565
1650
  if (!key.startsWith("aria-")) return;
1566
- const type = _AriaPolicyEngine.#ARIA_VALUE_TYPES.get(key);
1651
+ const type = ARIA_VALUE_TYPES.get(key);
1567
1652
  if (!isNonNull(type)) return;
1568
1653
  if (_AriaPolicyEngine.#isValidAriaValue(value, type)) return;
1569
1654
  results.push({
@@ -1582,26 +1667,18 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1582
1667
  return results;
1583
1668
  }
1584
1669
  // ─── Heading implicit level ────────────────────────────────────────────────
1585
- static #HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1586
- ["h1", 1],
1587
- ["h2", 2],
1588
- ["h3", 3],
1589
- ["h4", 4],
1590
- ["h5", 5],
1591
- ["h6", 6]
1592
- ]);
1593
1670
  static #checkRedundantAriaLevel({
1594
1671
  tag,
1595
1672
  props,
1596
1673
  effectiveRole
1597
1674
  }) {
1598
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1599
- const implicitLevel = _AriaPolicyEngine.#HEADING_IMPLICIT_LEVELS.get(tag);
1600
- if (!isNonNull(implicitLevel)) return NO_VIOLATIONS;
1675
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1676
+ const implicitLevel = HEADING_IMPLICIT_LEVELS.get(tag);
1677
+ if (!isNonNull(implicitLevel)) return NO_VIOLATIONS2;
1601
1678
  const raw = props["aria-level"];
1602
- if (!isNonNull(raw)) return NO_VIOLATIONS;
1679
+ if (!isNonNull(raw)) return NO_VIOLATIONS2;
1603
1680
  const n = typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : NaN;
1604
- if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS;
1681
+ if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS2;
1605
1682
  return [
1606
1683
  {
1607
1684
  valid: false,
@@ -1614,20 +1691,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1614
1691
  ];
1615
1692
  }
1616
1693
  // ─── Name-required roles ───────────────────────────────────────────────────
1617
- // Roles that always require an accessible name per WAI-ARIA APG.
1618
- // Dialog and landmark names are enforced via contracts (ariaContract) rather than
1619
- // the built-in pipeline so consumers can opt in; img is built in because role=img
1620
- // on any element (including bare <img>) is definitionally useless without a name.
1621
- static #NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1622
1694
  static #checkNameRequiredRoles({
1623
1695
  tag,
1624
1696
  props,
1625
1697
  effectiveRole
1626
1698
  }) {
1627
- if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole))
1628
- return NO_VIOLATIONS;
1629
- if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS;
1630
- if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS;
1699
+ if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return NO_VIOLATIONS2;
1700
+ if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS2;
1701
+ if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS2;
1631
1702
  return [
1632
1703
  {
1633
1704
  valid: false,
@@ -1637,51 +1708,21 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1637
1708
  }
1638
1709
  ];
1639
1710
  }
1640
- // WAI-ARIA 1.2 required states and properties, keyed by role.
1641
- // Source: https://www.w3.org/TR/wai-aria-1.2/#requiredState
1642
- static #REQUIRED_PROPERTIES = /* @__PURE__ */ new Map([
1643
- ["combobox", ["aria-expanded"]],
1644
- ["option", ["aria-selected"]],
1645
- ["slider", ["aria-valuenow"]],
1646
- ["scrollbar", ["aria-controls", "aria-valuenow"]],
1647
- ["spinbutton", ["aria-valuenow"]]
1648
- ]);
1649
- static #checkRequiredAriaProperties({
1650
- props,
1651
- effectiveRole
1652
- }) {
1653
- if (!effectiveRole) return NO_VIOLATIONS;
1654
- const required = _AriaPolicyEngine.#REQUIRED_PROPERTIES.get(effectiveRole);
1655
- if (!isNonNull(required)) return NO_VIOLATIONS;
1656
- const results = [];
1657
- iterate.forEach(required, (attr) => {
1658
- if (attr in props) return;
1659
- results.push({
1660
- valid: false,
1661
- fixable: false,
1662
- severity: "warning",
1663
- attribute: attr,
1664
- diagnostic: AriaDiagnostics.requiredProperty(attr, effectiveRole)
1665
- });
1666
- });
1667
- return results;
1711
+ static #requiredAriaPropertiesRule = {
1712
+ attributesByRole: REQUIRED_ARIA_PROPERTIES,
1713
+ diagnosticFor: (attribute, role) => AriaDiagnostics.requiredProperty(attribute, role)
1714
+ };
1715
+ static #checkRequiredAriaProperties(context) {
1716
+ return checkRequiredAttributes(_AriaPolicyEngine.#requiredAriaPropertiesRule, context);
1668
1717
  }
1669
- // Natively interactive HTML elements — always keyboard-reachable unless explicitly disabled.
1670
- static #INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
1671
- "a",
1672
- "button",
1673
- "input",
1674
- "select",
1675
- "textarea"
1676
- ]);
1677
1718
  // WAI-ARIA 1.2 §6.6: aria-hidden="true" must not be placed on focusable elements.
1678
1719
  static #checkAriaHiddenOnFocusable({ tag, props }) {
1679
- if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS;
1680
- const isInteractive = _AriaPolicyEngine.#INTERACTIVE_TAGS.has(tag);
1720
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS2;
1721
+ const isInteractive = INTERACTIVE_TAGS.has(tag);
1681
1722
  if (!isInteractive) {
1682
1723
  const tabindex = props.tabindex;
1683
1724
  const n = typeof tabindex === "number" ? tabindex : typeof tabindex === "string" ? parseInt(tabindex, 10) : NaN;
1684
- if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS;
1725
+ if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS2;
1685
1726
  }
1686
1727
  return [
1687
1728
  {
@@ -1700,7 +1741,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1700
1741
  props,
1701
1742
  effectiveRole
1702
1743
  }) {
1703
- if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS;
1744
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS2;
1704
1745
  const results = [];
1705
1746
  iterate.forEachEntry(props, (key) => {
1706
1747
  if (!key.startsWith("aria-")) return;
@@ -1716,18 +1757,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1716
1757
  });
1717
1758
  return results;
1718
1759
  }
1719
- // WAI-ARIA live region roles and their implied aria-live politeness values.
1720
- static #LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1721
- ["alert", "assertive"],
1722
- ["status", "polite"],
1723
- ["log", "polite"],
1724
- ["timer", "off"]
1725
- ]);
1726
1760
  static #checkMissingLiveRegion({ effectiveRole, props }) {
1727
- if (!effectiveRole) return NO_VIOLATIONS;
1728
- const impliedLive = _AriaPolicyEngine.#LIVE_REGION_ROLES.get(effectiveRole);
1729
- if (!impliedLive) return NO_VIOLATIONS;
1730
- if ("aria-live" in props) return NO_VIOLATIONS;
1761
+ if (!effectiveRole) return NO_VIOLATIONS2;
1762
+ const impliedLive = LIVE_REGION_ROLES.get(effectiveRole);
1763
+ if (!impliedLive) return NO_VIOLATIONS2;
1764
+ if ("aria-live" in props) return NO_VIOLATIONS2;
1731
1765
  const injectLive = {
1732
1766
  kind: `injectLive:${effectiveRole}`,
1733
1767
  apply: (ctx) => ({
@@ -1746,20 +1780,13 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1746
1780
  }
1747
1781
  ];
1748
1782
  }
1749
- static #checkMissingAtomic({ effectiveRole, props }) {
1750
- if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole))
1751
- return NO_VIOLATIONS;
1752
- if ("aria-atomic" in props) return NO_VIOLATIONS;
1753
- return [
1754
- {
1755
- valid: false,
1756
- fixable: false,
1757
- severity: "warning",
1758
- diagnostic: AriaDiagnostics.missingAtomic(effectiveRole)
1759
- }
1760
- ];
1783
+ static #missingAtomicRule = {
1784
+ attributesByRole: ATOMIC_REQUIREMENTS,
1785
+ diagnosticFor: (_attribute, role) => AriaDiagnostics.missingAtomic(role)
1786
+ };
1787
+ static #checkMissingAtomic(context) {
1788
+ return checkRequiredAttributes(_AriaPolicyEngine.#missingAtomicRule, context);
1761
1789
  }
1762
- static #VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set(["additions", "removals", "text", "all"]);
1763
1790
  // Custom fix rules passed via `options.rules` must be pure functions of (tag, props) — the cache
1764
1791
  // replays stored fixes against new prop objects, so fixes that close over external state will
1765
1792
  // produce inconsistent results on cache hits.
@@ -1773,10 +1800,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1773
1800
  };
1774
1801
  static #checkInvalidAriaRelevant({ props }) {
1775
1802
  const relevant = props["aria-relevant"];
1776
- if (relevant === void 0) return NO_VIOLATIONS;
1777
- if (typeof relevant !== "string") return NO_VIOLATIONS;
1803
+ if (relevant === void 0) return NO_VIOLATIONS2;
1804
+ if (typeof relevant !== "string") return NO_VIOLATIONS2;
1778
1805
  const tokens = relevant.trim().split(/\s+/);
1779
- const invalid = tokens.filter((t) => !_AriaPolicyEngine.#VALID_RELEVANT_TOKENS.has(t));
1806
+ const invalid = tokens.filter((t) => !VALID_RELEVANT_TOKENS.has(t));
1780
1807
  if (invalid.length > 0) {
1781
1808
  return [
1782
1809
  {
@@ -1801,7 +1828,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1801
1828
  }
1802
1829
  ];
1803
1830
  }
1804
- return NO_VIOLATIONS;
1831
+ return NO_VIOLATIONS2;
1805
1832
  }
1806
1833
  };
1807
1834
 
@@ -2121,7 +2148,59 @@ var readonlyProps = ({
2121
2148
  // ../core/src/html/evaluators.ts
2122
2149
  import { warnDiagnostics as warnDiagnostics2 } from "../_shared/diagnostics.js";
2123
2150
 
2124
- // ../core/src/html/input-rules.ts
2151
+ // ../core/src/html/spec/vocabulary/input.ts
2152
+ var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2153
+ var NUMERIC_INPUT_TYPES = [
2154
+ "number",
2155
+ "range",
2156
+ "date",
2157
+ "month",
2158
+ "week",
2159
+ "time",
2160
+ "datetime-local"
2161
+ ];
2162
+ var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
2163
+ ...TEXT_INPUT_TYPES,
2164
+ ...NUMERIC_INPUT_TYPES,
2165
+ "checkbox",
2166
+ "radio",
2167
+ "file",
2168
+ "color",
2169
+ "hidden",
2170
+ "button",
2171
+ "submit",
2172
+ "reset",
2173
+ "image"
2174
+ ]);
2175
+
2176
+ // ../core/src/html/spec/attributes/input.ts
2177
+ var INPUT_ATTRIBUTE_TYPE_POLICIES = [
2178
+ { attribute: "checked", allowedTypes: ["checkbox", "radio"] },
2179
+ { attribute: "multiple", allowedTypes: ["email", "file"] },
2180
+ { attribute: "maxLength", allowedTypes: TEXT_INPUT_TYPES },
2181
+ { attribute: "minLength", allowedTypes: TEXT_INPUT_TYPES },
2182
+ { attribute: "pattern", allowedTypes: TEXT_INPUT_TYPES },
2183
+ { attribute: "min", allowedTypes: NUMERIC_INPUT_TYPES },
2184
+ { attribute: "max", allowedTypes: NUMERIC_INPUT_TYPES },
2185
+ { attribute: "step", allowedTypes: NUMERIC_INPUT_TYPES },
2186
+ { attribute: "accept", allowedTypes: ["file"] },
2187
+ { attribute: "capture", allowedTypes: ["file"] },
2188
+ { attribute: "size", allowedTypes: TEXT_INPUT_TYPES },
2189
+ { attribute: "alt", allowedTypes: ["image"] },
2190
+ { attribute: "height", allowedTypes: ["image"] },
2191
+ { attribute: "width", allowedTypes: ["image"] }
2192
+ ];
2193
+
2194
+ // ../core/src/html/spec/constraints/input.ts
2195
+ var REQUIRED_READONLY_CONFLICT = {
2196
+ props: ["required", "readOnly"],
2197
+ diagnostic: () => InputAccessibilityDiagnostics.requiredReadOnlyConflict()
2198
+ };
2199
+ var INPUT_MUTUALLY_EXCLUSIVE_POLICIES = [
2200
+ REQUIRED_READONLY_CONFLICT
2201
+ ];
2202
+
2203
+ // ../core/src/html/spec/validators/attribute-type-validator.ts
2125
2204
  var DEFAULT_INPUT_TYPE = "text";
2126
2205
  function omit(props, key) {
2127
2206
  const next = { ...props };
@@ -2137,7 +2216,10 @@ function removeAttributeFix(attribute) {
2137
2216
  }
2138
2217
  };
2139
2218
  }
2140
- function inputAttributeRequiresType(attribute, allowedTypes) {
2219
+ function createInputAttributeTypeRule({
2220
+ attribute,
2221
+ allowedTypes
2222
+ }) {
2141
2223
  const rule = ({ tag, props }) => {
2142
2224
  if (tag !== "input" || !(attribute in props)) return [];
2143
2225
  const type = typeof props.type === "string" ? props.type : DEFAULT_INPUT_TYPE;
@@ -2153,31 +2235,30 @@ function inputAttributeRequiresType(attribute, allowedTypes) {
2153
2235
  }
2154
2236
  ];
2155
2237
  };
2156
- return Object.assign(rule, { readsProps: ["type", attribute] });
2238
+ return Object.assign(rule, { readsProps: ["type", attribute], tags: ["input"] });
2239
+ }
2240
+
2241
+ // ../core/src/html/spec/validators/mutually-exclusive-validator.ts
2242
+ function createMutuallyExclusiveRule({
2243
+ props: conflictingProps,
2244
+ diagnostic: createDiagnostic
2245
+ }) {
2246
+ const [first, second] = conflictingProps;
2247
+ const rule = ({ tag, props }) => {
2248
+ if (tag !== "input" || !props[first] || !props[second]) return [];
2249
+ const diagnostic = createDiagnostic();
2250
+ return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2251
+ };
2252
+ return Object.assign(rule, { readsProps: conflictingProps, tags: ["input"] });
2253
+ }
2254
+
2255
+ // ../core/src/html/input-rules.ts
2256
+ var policyByAttribute = Object.fromEntries(
2257
+ INPUT_ATTRIBUTE_TYPE_POLICIES.map((policy) => [policy.attribute, policy])
2258
+ );
2259
+ function policyFor(attribute) {
2260
+ return policyByAttribute[attribute];
2157
2261
  }
2158
- var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2159
- var NUMERIC_INPUT_TYPES = [
2160
- "number",
2161
- "range",
2162
- "date",
2163
- "month",
2164
- "week",
2165
- "time",
2166
- "datetime-local"
2167
- ];
2168
- var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
2169
- ...TEXT_INPUT_TYPES,
2170
- ...NUMERIC_INPUT_TYPES,
2171
- "checkbox",
2172
- "radio",
2173
- "file",
2174
- "color",
2175
- "hidden",
2176
- "button",
2177
- "submit",
2178
- "reset",
2179
- "image"
2180
- ]);
2181
2262
  var supportedInputTypeRule = Object.assign(
2182
2263
  ({ tag, props }) => {
2183
2264
  if (tag !== "input" || typeof props.type !== "string") return [];
@@ -2193,30 +2274,22 @@ var supportedInputTypeRule = Object.assign(
2193
2274
  }
2194
2275
  ];
2195
2276
  },
2196
- { readsProps: ["type"] }
2197
- );
2198
- var checkedRequiresCheckableTypeRule = inputAttributeRequiresType("checked", [
2199
- "checkbox",
2200
- "radio"
2201
- ]);
2202
- var multipleRequiresSupportedTypeRule = inputAttributeRequiresType("multiple", [
2203
- "email",
2204
- "file"
2205
- ]);
2206
- var maxLengthRequiresTextTypeRule = inputAttributeRequiresType(
2207
- "maxLength",
2208
- TEXT_INPUT_TYPES
2209
- );
2210
- var minLengthRequiresTextTypeRule = inputAttributeRequiresType(
2211
- "minLength",
2212
- TEXT_INPUT_TYPES
2277
+ { readsProps: ["type"], tags: ["input"] }
2213
2278
  );
2214
- var patternRequiresTextTypeRule = inputAttributeRequiresType("pattern", TEXT_INPUT_TYPES);
2215
- var minRequiresNumericTypeRule = inputAttributeRequiresType("min", NUMERIC_INPUT_TYPES);
2216
- var maxRequiresNumericTypeRule = inputAttributeRequiresType("max", NUMERIC_INPUT_TYPES);
2217
- var stepRequiresNumericTypeRule = inputAttributeRequiresType("step", NUMERIC_INPUT_TYPES);
2218
- var acceptRequiresFileTypeRule = inputAttributeRequiresType("accept", ["file"]);
2219
- var captureRequiresFileTypeRule = inputAttributeRequiresType("capture", ["file"]);
2279
+ var checkedRequiresCheckableTypeRule = createInputAttributeTypeRule(policyFor("checked"));
2280
+ var multipleRequiresSupportedTypeRule = createInputAttributeTypeRule(policyFor("multiple"));
2281
+ var maxLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("maxLength"));
2282
+ var minLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("minLength"));
2283
+ var patternRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("pattern"));
2284
+ var minRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("min"));
2285
+ var maxRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("max"));
2286
+ var stepRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("step"));
2287
+ var acceptRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("accept"));
2288
+ var captureRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("capture"));
2289
+ var sizeRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("size"));
2290
+ var altRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("alt"));
2291
+ var heightRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("height"));
2292
+ var widthRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("width"));
2220
2293
  var inputAccessibleNameRule = Object.assign(
2221
2294
  ({ tag, props }) => {
2222
2295
  if (tag !== "input" || props.type === "hidden") return [];
@@ -2225,7 +2298,10 @@ var inputAccessibleNameRule = Object.assign(
2225
2298
  const diagnostic = hasPlaceholder ? InputAccessibilityDiagnostics.placeholderIsNotLabel() : InputAccessibilityDiagnostics.missingAccessibleName();
2226
2299
  return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2227
2300
  },
2228
- { readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"] }
2301
+ {
2302
+ readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"],
2303
+ tags: ["input"]
2304
+ }
2229
2305
  );
2230
2306
  var PASSWORD_AUTOCOMPLETE_VALUES = ["current-password", "new-password"];
2231
2307
  var passwordAutocompleteRule = Object.assign(
@@ -2237,16 +2313,9 @@ var passwordAutocompleteRule = Object.assign(
2237
2313
  const diagnostic = InputAccessibilityDiagnostics.passwordMissingAutocomplete();
2238
2314
  return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2239
2315
  },
2240
- { readsProps: ["type", "autoComplete"] }
2241
- );
2242
- var requiredReadOnlyConflictRule = Object.assign(
2243
- ({ tag, props }) => {
2244
- if (tag !== "input" || !props.required || !props.readOnly) return [];
2245
- const diagnostic = InputAccessibilityDiagnostics.requiredReadOnlyConflict();
2246
- return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2247
- },
2248
- { readsProps: ["required", "readOnly"] }
2316
+ { readsProps: ["type", "autoComplete"], tags: ["input"] }
2249
2317
  );
2318
+ var requiredReadOnlyConflictRule = createMutuallyExclusiveRule(REQUIRED_READONLY_CONFLICT);
2250
2319
  var INPUT_RULES = [
2251
2320
  supportedInputTypeRule,
2252
2321
  checkedRequiresCheckableTypeRule,
@@ -2259,11 +2328,132 @@ var INPUT_RULES = [
2259
2328
  stepRequiresNumericTypeRule,
2260
2329
  acceptRequiresFileTypeRule,
2261
2330
  captureRequiresFileTypeRule,
2331
+ sizeRequiresTextTypeRule,
2332
+ altRequiresImageTypeRule,
2333
+ heightRequiresImageTypeRule,
2334
+ widthRequiresImageTypeRule,
2262
2335
  inputAccessibleNameRule,
2263
2336
  passwordAutocompleteRule,
2264
2337
  requiredReadOnlyConflictRule
2265
2338
  ];
2266
2339
 
2340
+ // ../core/src/html/spec/types.ts
2341
+ function definePropRolePolicy(prop, map2, fallback) {
2342
+ return { kind: "byProp", prop, map: map2, fallback };
2343
+ }
2344
+ function resolveAllowedRoles(spec, props) {
2345
+ const policy = spec.allowedRoles;
2346
+ if (!policy) return void 0;
2347
+ switch (policy.kind) {
2348
+ case "fixed":
2349
+ return policy.roles;
2350
+ case "byProp": {
2351
+ const value = typeof props[policy.prop] === "string" ? props[policy.prop] : policy.fallback;
2352
+ return policy.map[value];
2353
+ }
2354
+ case "dynamic":
2355
+ return policy.resolve({ props });
2356
+ }
2357
+ }
2358
+
2359
+ // ../core/src/html/spec/roles/input.ts
2360
+ var ALLOWED_INPUT_ROLES = {
2361
+ checkbox: ["menuitemcheckbox", "option", "switch", "button"],
2362
+ radio: ["menuitemradio"],
2363
+ range: [],
2364
+ number: [],
2365
+ search: ["combobox"],
2366
+ text: ["combobox", "searchbox", "spinbutton"],
2367
+ email: ["combobox"],
2368
+ tel: ["combobox"],
2369
+ url: ["combobox"],
2370
+ button: [
2371
+ "link",
2372
+ "menuitem",
2373
+ "menuitemcheckbox",
2374
+ "menuitemradio",
2375
+ "option",
2376
+ "radio",
2377
+ "switch",
2378
+ "tab"
2379
+ ],
2380
+ submit: [
2381
+ "link",
2382
+ "menuitem",
2383
+ "menuitemcheckbox",
2384
+ "menuitemradio",
2385
+ "option",
2386
+ "radio",
2387
+ "switch",
2388
+ "tab"
2389
+ ],
2390
+ reset: [
2391
+ "link",
2392
+ "menuitem",
2393
+ "menuitemcheckbox",
2394
+ "menuitemradio",
2395
+ "option",
2396
+ "radio",
2397
+ "switch",
2398
+ "tab"
2399
+ ],
2400
+ image: [
2401
+ "link",
2402
+ "menuitem",
2403
+ "menuitemcheckbox",
2404
+ "menuitemradio",
2405
+ "option",
2406
+ "radio",
2407
+ "switch",
2408
+ "tab"
2409
+ ],
2410
+ hidden: []
2411
+ };
2412
+
2413
+ // ../core/src/html/spec/elements/input.ts
2414
+ var inputElementSpec = {
2415
+ tag: "input",
2416
+ allowedRoles: definePropRolePolicy("type", ALLOWED_INPUT_ROLES, "text"),
2417
+ attributes: INPUT_ATTRIBUTE_TYPE_POLICIES,
2418
+ mutuallyExclusive: INPUT_MUTUALLY_EXCLUSIVE_POLICIES
2419
+ };
2420
+
2421
+ // ../core/src/html/spec/roles/img.ts
2422
+ var IMG_NAMED_ROLES = [
2423
+ "button",
2424
+ "checkbox",
2425
+ "link",
2426
+ "menuitem",
2427
+ "menuitemcheckbox",
2428
+ "menuitemradio",
2429
+ "option",
2430
+ "progressbar",
2431
+ "scrollbar",
2432
+ "separator",
2433
+ "slider",
2434
+ "switch",
2435
+ "tab",
2436
+ "treeitem"
2437
+ ];
2438
+
2439
+ // ../core/src/html/spec/elements/img.ts
2440
+ var imgElementSpec = {
2441
+ tag: "img",
2442
+ allowedRoles: {
2443
+ kind: "dynamic",
2444
+ resolve: ({ props }) => props.alt === "" ? [] : IMG_NAMED_ROLES
2445
+ }
2446
+ };
2447
+
2448
+ // ../core/src/html/spec/roles/table.ts
2449
+ var ALLOWED_TABLE_ROLES = ["grid", "treegrid"];
2450
+
2451
+ // ../core/src/html/spec/elements/table.ts
2452
+ var tableElementSpec = {
2453
+ tag: "table",
2454
+ allowedRoles: { kind: "fixed", roles: ALLOWED_TABLE_ROLES }
2455
+ };
2456
+
2267
2457
  // ../core/src/html/role-restrictions.ts
2268
2458
  var ALLOWED_ROLES = {
2269
2459
  article: ["application", "document", "feed", "main", "none", "presentation", "region"],
@@ -2336,86 +2526,17 @@ var ALLOWED_ROLES = {
2336
2526
  "tab",
2337
2527
  "treeitem"
2338
2528
  ],
2339
- table: ["grid", "treegrid"],
2340
2529
  dialog: ["alertdialog"],
2341
2530
  fieldset: ["none", "presentation", "radiogroup"]
2342
2531
  };
2343
- var IMG_NAMED_ROLES = [
2344
- "button",
2345
- "checkbox",
2346
- "link",
2347
- "menuitem",
2348
- "menuitemcheckbox",
2349
- "menuitemradio",
2350
- "option",
2351
- "progressbar",
2352
- "scrollbar",
2353
- "separator",
2354
- "slider",
2355
- "switch",
2356
- "tab",
2357
- "treeitem"
2358
- ];
2359
- var ALLOWED_INPUT_ROLES = {
2360
- checkbox: ["menuitemcheckbox", "option", "switch", "button"],
2361
- radio: ["menuitemradio"],
2362
- range: [],
2363
- number: [],
2364
- search: ["combobox"],
2365
- text: ["combobox", "searchbox", "spinbutton"],
2366
- email: ["combobox"],
2367
- tel: ["combobox"],
2368
- url: ["combobox"],
2369
- button: [
2370
- "link",
2371
- "menuitem",
2372
- "menuitemcheckbox",
2373
- "menuitemradio",
2374
- "option",
2375
- "radio",
2376
- "switch",
2377
- "tab"
2378
- ],
2379
- submit: [
2380
- "link",
2381
- "menuitem",
2382
- "menuitemcheckbox",
2383
- "menuitemradio",
2384
- "option",
2385
- "radio",
2386
- "switch",
2387
- "tab"
2388
- ],
2389
- reset: [
2390
- "link",
2391
- "menuitem",
2392
- "menuitemcheckbox",
2393
- "menuitemradio",
2394
- "option",
2395
- "radio",
2396
- "switch",
2397
- "tab"
2398
- ],
2399
- image: [
2400
- "link",
2401
- "menuitem",
2402
- "menuitemcheckbox",
2403
- "menuitemradio",
2404
- "option",
2405
- "radio",
2406
- "switch",
2407
- "tab"
2408
- ],
2409
- hidden: []
2532
+ var ELEMENT_SPECS = {
2533
+ input: inputElementSpec,
2534
+ img: imgElementSpec,
2535
+ table: tableElementSpec
2410
2536
  };
2411
2537
  function getAllowedRoles(tag, props) {
2412
- if (tag === "input") {
2413
- const type = typeof props.type === "string" ? props.type : "text";
2414
- return ALLOWED_INPUT_ROLES[type];
2415
- }
2416
- if (tag === "img") {
2417
- return props.alt === "" ? [] : IMG_NAMED_ROLES;
2418
- }
2538
+ const spec = ELEMENT_SPECS[tag];
2539
+ if (spec) return resolveAllowedRoles(spec, props);
2419
2540
  return ALLOWED_ROLES[tag];
2420
2541
  }
2421
2542
  var removeRoleFix = {
@@ -2456,21 +2577,24 @@ var removeLandmarkRoleOverride = {
2456
2577
  return { applied: true, next: rest, previous: props };
2457
2578
  }
2458
2579
  };
2459
- function landmarkRoleRule({ tag, props, implicitRole }) {
2460
- if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2461
- const role = props.role;
2462
- if (!role || role === implicitRole) return [];
2463
- const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2464
- return [
2465
- {
2466
- valid: false,
2467
- fixable: true,
2468
- severity: diagnostic.severity,
2469
- fix: removeLandmarkRoleOverride,
2470
- diagnostic
2471
- }
2472
- ];
2473
- }
2580
+ var landmarkRoleRule = Object.assign(
2581
+ ({ tag, props, implicitRole }) => {
2582
+ if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2583
+ const role = props.role;
2584
+ if (!role || role === implicitRole) return [];
2585
+ const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2586
+ return [
2587
+ {
2588
+ valid: false,
2589
+ fixable: true,
2590
+ severity: diagnostic.severity,
2591
+ fix: removeLandmarkRoleOverride,
2592
+ diagnostic
2593
+ }
2594
+ ];
2595
+ },
2596
+ { tags: [...LANDMARK_TAG_SET] }
2597
+ );
2474
2598
  function requireAccessibleName({ tag, props }) {
2475
2599
  if ("aria-label" in props || "aria-labelledby" in props) return [];
2476
2600
  return [
@@ -2483,10 +2607,13 @@ function requireAccessibleName({ tag, props }) {
2483
2607
  ];
2484
2608
  }
2485
2609
  var NAMED_LANDMARK_TAGS = /* @__PURE__ */ new Set(["nav", "aside"]);
2486
- function landmarkNameAdvisory(ctx) {
2487
- if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2488
- return requireAccessibleName(ctx);
2489
- }
2610
+ var landmarkNameAdvisory = Object.assign(
2611
+ (ctx) => {
2612
+ if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2613
+ return requireAccessibleName(ctx);
2614
+ },
2615
+ { tags: [...NAMED_LANDMARK_TAGS] }
2616
+ );
2490
2617
  var HTML_ARIA_RULES = [
2491
2618
  landmarkRoleRule,
2492
2619
  landmarkNameAdvisory,
@@ -2587,6 +2714,62 @@ var figureContract = contract([
2587
2714
  ]);
2588
2715
  var detailsContract = firstChildContract("summary", "summary");
2589
2716
  var fieldsetContract = firstChildContract("legend", "legend");
2717
+ var objectContract = contract([
2718
+ { name: "param", match: isTag("param") },
2719
+ { name: "content", match: isOpenContent("param") }
2720
+ ]);
2721
+ var INTERACTIVE_CONTENT_TAGS = ["a", "button", "input", "select", "textarea", "label"];
2722
+ var buttonContract = closedContract([
2723
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2724
+ ]);
2725
+ var anchorContract = closedContract([
2726
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2727
+ ]);
2728
+ var LABELABLE_TAGS = [
2729
+ "button",
2730
+ "input",
2731
+ "meter",
2732
+ "output",
2733
+ "progress",
2734
+ "select",
2735
+ "textarea"
2736
+ ];
2737
+ var labelContract = contract([
2738
+ { name: "control", match: isTag(...LABELABLE_TAGS), cardinality: { max: 1 } }
2739
+ ]);
2740
+ var P_BLOCKED_TAGS = [
2741
+ "address",
2742
+ "article",
2743
+ "aside",
2744
+ "blockquote",
2745
+ "details",
2746
+ "dialog",
2747
+ "div",
2748
+ "dl",
2749
+ "fieldset",
2750
+ "figure",
2751
+ "footer",
2752
+ "form",
2753
+ "h1",
2754
+ "h2",
2755
+ "h3",
2756
+ "h4",
2757
+ "h5",
2758
+ "h6",
2759
+ "header",
2760
+ "hr",
2761
+ "main",
2762
+ "nav",
2763
+ "ol",
2764
+ "p",
2765
+ "pre",
2766
+ "section",
2767
+ "table",
2768
+ "ul"
2769
+ ];
2770
+ var pContract = closedContract([
2771
+ { name: "content", match: isOpenContent(...P_BLOCKED_TAGS) }
2772
+ ]);
2590
2773
  var mediaContract = contract([
2591
2774
  { name: "source", match: isTag("source") },
2592
2775
  { name: "track", match: isTag("track") },
@@ -2641,6 +2824,11 @@ var htmlContracts = {
2641
2824
  details: detailsContract,
2642
2825
  fieldset: fieldsetContract,
2643
2826
  dialog: dialogContract,
2827
+ object: objectContract,
2828
+ button: buttonContract,
2829
+ a: anchorContract,
2830
+ label: labelContract,
2831
+ p: pContract,
2644
2832
  head: headContract,
2645
2833
  html: htmlContract
2646
2834
  };
@@ -2867,6 +3055,11 @@ function composeNormalizers(normalizers, fn) {
2867
3055
  function whenDefined(key, value) {
2868
3056
  return value === void 0 ? {} : { [key]: value };
2869
3057
  }
3058
+ function mergeAriaRules(aria, rules) {
3059
+ if (!aria?.length) return rules;
3060
+ if (!rules?.length) return aria;
3061
+ return [...aria, ...rules];
3062
+ }
2870
3063
  function resolveFactoryOptions(options = {}) {
2871
3064
  const { styling, enforcement } = options;
2872
3065
  const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
@@ -2887,7 +3080,7 @@ function resolveFactoryOptions(options = {}) {
2887
3080
  ...whenDefined("defaultVariants", styling?.defaults),
2888
3081
  ...whenDefined("compoundVariants", styling?.compounds),
2889
3082
  ...whenDefined("normalizeFn", composedNormalizeFn),
2890
- ...whenDefined("ariaRules", enforcement?.aria),
3083
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
2891
3084
  ...whenDefined("childRules", enforcement?.children),
2892
3085
  ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
2893
3086
  ...whenDefined("allowText", enforcement?.allowText),