praxis-kit 6.2.3 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lit/index.js CHANGED
@@ -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) {
@@ -1056,8 +1072,132 @@ var InvariantBase = class {
1056
1072
  }
1057
1073
  };
1058
1074
 
1059
- // ../../lib/contract/src/aria/polymorphic-validator.ts
1075
+ // ../../lib/contract/src/aria/spec/roles/required-properties.ts
1076
+ var REQUIRED_ARIA_PROPERTIES = {
1077
+ combobox: ["aria-expanded"],
1078
+ option: ["aria-selected"],
1079
+ slider: ["aria-valuenow"],
1080
+ scrollbar: ["aria-controls", "aria-valuenow"],
1081
+ spinbutton: ["aria-valuenow"]
1082
+ };
1083
+
1084
+ // ../../lib/contract/src/aria/spec/roles/name-required.ts
1085
+ var NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1086
+
1087
+ // ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
1060
1088
  var NO_VIOLATIONS = [{ valid: true }];
1089
+ function requiredAttributeByRole(roles, attribute) {
1090
+ return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
1091
+ }
1092
+ function checkRequiredAttributes(requirement, { props, effectiveRole }) {
1093
+ if (!effectiveRole) return NO_VIOLATIONS;
1094
+ const requiredAttributes = requirement.attributesByRole[effectiveRole];
1095
+ if (!requiredAttributes) return NO_VIOLATIONS;
1096
+ const results = [];
1097
+ for (const attribute of requiredAttributes) {
1098
+ if (attribute in props) continue;
1099
+ results.push({
1100
+ valid: false,
1101
+ fixable: false,
1102
+ severity: "warning",
1103
+ attribute,
1104
+ diagnostic: requirement.diagnosticFor(attribute, effectiveRole)
1105
+ });
1106
+ }
1107
+ return results;
1108
+ }
1109
+
1110
+ // ../../lib/contract/src/aria/spec/roles/live-region.ts
1111
+ var LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1112
+ ["alert", "assertive"],
1113
+ ["status", "polite"],
1114
+ ["log", "polite"],
1115
+ ["timer", "off"]
1116
+ ]);
1117
+ var ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
1118
+
1119
+ // ../../lib/contract/src/aria/spec/attributes/aria-value-types.ts
1120
+ var ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1121
+ // Boolean (true | false)
1122
+ ["aria-atomic", { kind: "boolean" }],
1123
+ ["aria-busy", { kind: "boolean" }],
1124
+ ["aria-disabled", { kind: "boolean" }],
1125
+ ["aria-expanded", { kind: "boolean" }],
1126
+ ["aria-hidden", { kind: "boolean" }],
1127
+ ["aria-modal", { kind: "boolean" }],
1128
+ ["aria-multiline", { kind: "boolean" }],
1129
+ ["aria-multiselectable", { kind: "boolean" }],
1130
+ ["aria-readonly", { kind: "boolean" }],
1131
+ ["aria-required", { kind: "boolean" }],
1132
+ ["aria-selected", { kind: "boolean" }],
1133
+ // Tristate (true | false | mixed)
1134
+ ["aria-checked", { kind: "tristate" }],
1135
+ ["aria-pressed", { kind: "tristate" }],
1136
+ // Numeric (any finite number)
1137
+ ["aria-valuenow", { kind: "number" }],
1138
+ ["aria-valuemin", { kind: "number" }],
1139
+ ["aria-valuemax", { kind: "number" }],
1140
+ // Integer with optional range
1141
+ ["aria-level", { kind: "integer", min: 1, max: 6 }],
1142
+ ["aria-posinset", { kind: "integer", min: 1 }],
1143
+ ["aria-setsize", { kind: "integer", min: -1 }],
1144
+ ["aria-rowcount", { kind: "integer", min: -1 }],
1145
+ ["aria-colcount", { kind: "integer", min: -1 }],
1146
+ ["aria-rowindex", { kind: "integer", min: 1 }],
1147
+ ["aria-colindex", { kind: "integer", min: 1 }],
1148
+ ["aria-rowspan", { kind: "integer", min: 0 }],
1149
+ ["aria-colspan", { kind: "integer", min: 0 }],
1150
+ // Enum (specific allowed tokens)
1151
+ ["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
1152
+ [
1153
+ "aria-current",
1154
+ {
1155
+ kind: "enum",
1156
+ values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
1157
+ }
1158
+ ],
1159
+ [
1160
+ "aria-haspopup",
1161
+ {
1162
+ kind: "enum",
1163
+ values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
1164
+ }
1165
+ ],
1166
+ ["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
1167
+ ["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
1168
+ ["aria-orientation", { kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }],
1169
+ ["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
1170
+ ]);
1171
+
1172
+ // ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
1173
+ var VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
1174
+ "additions",
1175
+ "removals",
1176
+ "text",
1177
+ "all"
1178
+ ]);
1179
+
1180
+ // ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
1181
+ var HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1182
+ ["h1", 1],
1183
+ ["h2", 2],
1184
+ ["h3", 3],
1185
+ ["h4", 4],
1186
+ ["h5", 5],
1187
+ ["h6", 6]
1188
+ ]);
1189
+
1190
+ // ../../lib/contract/src/aria/spec/elements/interactive-tags.ts
1191
+ var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
1192
+ "a",
1193
+ "button",
1194
+ "input",
1195
+ "select",
1196
+ "textarea"
1197
+ ]);
1198
+
1199
+ // ../../lib/contract/src/aria/polymorphic-validator.ts
1200
+ var NO_VIOLATIONS2 = [{ valid: true }];
1061
1201
  function isIntrinsicTag(tag) {
1062
1202
  return isString(tag);
1063
1203
  }
@@ -1100,7 +1240,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1100
1240
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1101
1241
  const implicitRole = getImplicitRole(tag, props);
1102
1242
  const hasRole = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1103
- if (!hasRole) return { proceed: false, result: { props, violations: [] } };
1104
1243
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1105
1244
  const workingProps = normalized.normalized ? normalized.result.props : props;
1106
1245
  const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
@@ -1110,6 +1249,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1110
1249
  tag,
1111
1250
  implicitRole,
1112
1251
  effectiveRole,
1252
+ hasRole,
1113
1253
  props: workingProps,
1114
1254
  preExistingViolations,
1115
1255
  context: { tag, props: workingProps, implicitRole, effectiveRole }
@@ -1119,6 +1259,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1119
1259
  const violations = [];
1120
1260
  const fixes = [];
1121
1261
  iterate.forEach(rules, (rule) => {
1262
+ if (isNonNull(rule.tags) && !rule.tags.includes(context.tag)) return;
1122
1263
  iterate.forEach(rule(context), (result) => {
1123
1264
  if (result.valid) return;
1124
1265
  const {
@@ -1144,7 +1285,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1144
1285
  return { violations, fixes };
1145
1286
  }
1146
1287
  static #getRules(context) {
1147
- if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && _AriaPolicyEngine.#LIVE_REGION_ROLES.has(context.effectiveRole)) {
1288
+ if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && LIVE_REGION_ROLES.has(context.effectiveRole)) {
1148
1289
  return _AriaPolicyEngine.#pipeline;
1149
1290
  }
1150
1291
  return _AriaPolicyEngine.#implicitOnlyRules;
@@ -1152,6 +1293,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1152
1293
  static evaluate(tag, props) {
1153
1294
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1154
1295
  if (!derived.proceed) return derived.result;
1296
+ if (!derived.hasRole)
1297
+ return { props: derived.props, violations: [...derived.preExistingViolations] };
1155
1298
  const {
1156
1299
  tag: narrowedTag,
1157
1300
  implicitRole,
@@ -1176,10 +1319,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1176
1319
  props: workingProps,
1177
1320
  preExistingViolations
1178
1321
  } = derived;
1179
- const { violations, fixes } = _AriaPolicyEngine.#runRules(
1180
- [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1181
- context
1182
- );
1322
+ const rules = derived.hasRole ? [..._AriaPolicyEngine.#getRules(context), ...extraRules] : extraRules;
1323
+ const { violations, fixes } = _AriaPolicyEngine.#runRules(rules, context);
1183
1324
  const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1184
1325
  return { props: next, violations: [...preExistingViolations, ...violations] };
1185
1326
  }
@@ -1346,7 +1487,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1346
1487
  implicitRole
1347
1488
  }) {
1348
1489
  const role = props.role;
1349
- if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
1490
+ if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS2;
1350
1491
  if (isStrongImplicitRole(tag) && role === "region") {
1351
1492
  const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
1352
1493
  return [
@@ -1359,11 +1500,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1359
1500
  }
1360
1501
  ];
1361
1502
  }
1362
- return NO_VIOLATIONS;
1503
+ return NO_VIOLATIONS2;
1363
1504
  }
1364
1505
  static #checkRedundantRole({ tag, props, implicitRole }) {
1365
1506
  const role = props.role;
1366
- if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
1507
+ if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS2;
1367
1508
  const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
1368
1509
  return [
1369
1510
  {
@@ -1377,8 +1518,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1377
1518
  }
1378
1519
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1379
1520
  const role = props.role;
1380
- if (role !== "region") return NO_VIOLATIONS;
1381
- if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
1521
+ if (role !== "region") return NO_VIOLATIONS2;
1522
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS2;
1382
1523
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1383
1524
  return [
1384
1525
  {
@@ -1395,7 +1536,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1395
1536
  props,
1396
1537
  effectiveRole
1397
1538
  }) {
1398
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1539
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1399
1540
  const results = [];
1400
1541
  iterate.forEachEntry(props, (key) => {
1401
1542
  if (!key.startsWith("aria-")) return;
@@ -1413,62 +1554,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1413
1554
  return results;
1414
1555
  }
1415
1556
  // ─── ARIA attribute value validation ──────────────────────────────────────
1416
- // Accepted value shapes for typed ARIA attributes.
1417
- // Attributes not in this map are unconstrained (arbitrary string values permitted).
1418
- static #ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
1419
- // Boolean (true | false)
1420
- ["aria-atomic", { kind: "boolean" }],
1421
- ["aria-busy", { kind: "boolean" }],
1422
- ["aria-disabled", { kind: "boolean" }],
1423
- ["aria-expanded", { kind: "boolean" }],
1424
- ["aria-hidden", { kind: "boolean" }],
1425
- ["aria-modal", { kind: "boolean" }],
1426
- ["aria-multiline", { kind: "boolean" }],
1427
- ["aria-multiselectable", { kind: "boolean" }],
1428
- ["aria-readonly", { kind: "boolean" }],
1429
- ["aria-required", { kind: "boolean" }],
1430
- ["aria-selected", { kind: "boolean" }],
1431
- // Tristate (true | false | mixed)
1432
- ["aria-checked", { kind: "tristate" }],
1433
- ["aria-pressed", { kind: "tristate" }],
1434
- // Numeric (any finite number)
1435
- ["aria-valuenow", { kind: "number" }],
1436
- ["aria-valuemin", { kind: "number" }],
1437
- ["aria-valuemax", { kind: "number" }],
1438
- // Integer with optional range
1439
- ["aria-level", { kind: "integer", min: 1, max: 6 }],
1440
- ["aria-posinset", { kind: "integer", min: 1 }],
1441
- ["aria-setsize", { kind: "integer", min: -1 }],
1442
- ["aria-rowcount", { kind: "integer", min: -1 }],
1443
- ["aria-colcount", { kind: "integer", min: -1 }],
1444
- ["aria-rowindex", { kind: "integer", min: 1 }],
1445
- ["aria-colindex", { kind: "integer", min: 1 }],
1446
- ["aria-rowspan", { kind: "integer", min: 0 }],
1447
- ["aria-colspan", { kind: "integer", min: 0 }],
1448
- // Enum (specific allowed tokens)
1449
- ["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
1450
- [
1451
- "aria-current",
1452
- {
1453
- kind: "enum",
1454
- values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
1455
- }
1456
- ],
1457
- [
1458
- "aria-haspopup",
1459
- {
1460
- kind: "enum",
1461
- values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
1462
- }
1463
- ],
1464
- ["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
1465
- ["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
1466
- [
1467
- "aria-orientation",
1468
- { kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }
1469
- ],
1470
- ["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
1471
- ]);
1472
1557
  static #isValidAriaValue(value, type) {
1473
1558
  switch (type.kind) {
1474
1559
  case "boolean":
@@ -1513,11 +1598,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1513
1598
  }
1514
1599
  }
1515
1600
  static #checkAriaAttributeValues({ props, effectiveRole }) {
1516
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1601
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1517
1602
  const results = [];
1518
1603
  iterate.forEachEntry(props, (key, value) => {
1519
1604
  if (!key.startsWith("aria-")) return;
1520
- const type = _AriaPolicyEngine.#ARIA_VALUE_TYPES.get(key);
1605
+ const type = ARIA_VALUE_TYPES.get(key);
1521
1606
  if (!isNonNull(type)) return;
1522
1607
  if (_AriaPolicyEngine.#isValidAriaValue(value, type)) return;
1523
1608
  results.push({
@@ -1536,26 +1621,18 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1536
1621
  return results;
1537
1622
  }
1538
1623
  // ─── Heading implicit level ────────────────────────────────────────────────
1539
- static #HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
1540
- ["h1", 1],
1541
- ["h2", 2],
1542
- ["h3", 3],
1543
- ["h4", 4],
1544
- ["h5", 5],
1545
- ["h6", 6]
1546
- ]);
1547
1624
  static #checkRedundantAriaLevel({
1548
1625
  tag,
1549
1626
  props,
1550
1627
  effectiveRole
1551
1628
  }) {
1552
- if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS;
1553
- const implicitLevel = _AriaPolicyEngine.#HEADING_IMPLICIT_LEVELS.get(tag);
1554
- if (!isNonNull(implicitLevel)) return NO_VIOLATIONS;
1629
+ if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
1630
+ const implicitLevel = HEADING_IMPLICIT_LEVELS.get(tag);
1631
+ if (!isNonNull(implicitLevel)) return NO_VIOLATIONS2;
1555
1632
  const raw = props["aria-level"];
1556
- if (!isNonNull(raw)) return NO_VIOLATIONS;
1633
+ if (!isNonNull(raw)) return NO_VIOLATIONS2;
1557
1634
  const n = typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : NaN;
1558
- if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS;
1635
+ if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS2;
1559
1636
  return [
1560
1637
  {
1561
1638
  valid: false,
@@ -1568,20 +1645,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1568
1645
  ];
1569
1646
  }
1570
1647
  // ─── Name-required roles ───────────────────────────────────────────────────
1571
- // Roles that always require an accessible name per WAI-ARIA APG.
1572
- // Dialog and landmark names are enforced via contracts (ariaContract) rather than
1573
- // the built-in pipeline so consumers can opt in; img is built in because role=img
1574
- // on any element (including bare <img>) is definitionally useless without a name.
1575
- static #NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
1576
1648
  static #checkNameRequiredRoles({
1577
1649
  tag,
1578
1650
  props,
1579
1651
  effectiveRole
1580
1652
  }) {
1581
- if (!effectiveRole || !_AriaPolicyEngine.#NAME_REQUIRED_ROLES.has(effectiveRole))
1582
- return NO_VIOLATIONS;
1583
- if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS;
1584
- if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS;
1653
+ if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return NO_VIOLATIONS2;
1654
+ if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS2;
1655
+ if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS2;
1585
1656
  return [
1586
1657
  {
1587
1658
  valid: false,
@@ -1591,51 +1662,21 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1591
1662
  }
1592
1663
  ];
1593
1664
  }
1594
- // WAI-ARIA 1.2 required states and properties, keyed by role.
1595
- // Source: https://www.w3.org/TR/wai-aria-1.2/#requiredState
1596
- static #REQUIRED_PROPERTIES = /* @__PURE__ */ new Map([
1597
- ["combobox", ["aria-expanded"]],
1598
- ["option", ["aria-selected"]],
1599
- ["slider", ["aria-valuenow"]],
1600
- ["scrollbar", ["aria-controls", "aria-valuenow"]],
1601
- ["spinbutton", ["aria-valuenow"]]
1602
- ]);
1603
- static #checkRequiredAriaProperties({
1604
- props,
1605
- effectiveRole
1606
- }) {
1607
- if (!effectiveRole) return NO_VIOLATIONS;
1608
- const required = _AriaPolicyEngine.#REQUIRED_PROPERTIES.get(effectiveRole);
1609
- if (!isNonNull(required)) return NO_VIOLATIONS;
1610
- const results = [];
1611
- iterate.forEach(required, (attr) => {
1612
- if (attr in props) return;
1613
- results.push({
1614
- valid: false,
1615
- fixable: false,
1616
- severity: "warning",
1617
- attribute: attr,
1618
- diagnostic: AriaDiagnostics.requiredProperty(attr, effectiveRole)
1619
- });
1620
- });
1621
- return results;
1665
+ static #requiredAriaPropertiesRule = {
1666
+ attributesByRole: REQUIRED_ARIA_PROPERTIES,
1667
+ diagnosticFor: (attribute, role) => AriaDiagnostics.requiredProperty(attribute, role)
1668
+ };
1669
+ static #checkRequiredAriaProperties(context) {
1670
+ return checkRequiredAttributes(_AriaPolicyEngine.#requiredAriaPropertiesRule, context);
1622
1671
  }
1623
- // Natively interactive HTML elements — always keyboard-reachable unless explicitly disabled.
1624
- static #INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
1625
- "a",
1626
- "button",
1627
- "input",
1628
- "select",
1629
- "textarea"
1630
- ]);
1631
1672
  // WAI-ARIA 1.2 §6.6: aria-hidden="true" must not be placed on focusable elements.
1632
1673
  static #checkAriaHiddenOnFocusable({ tag, props }) {
1633
- if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS;
1634
- const isInteractive = _AriaPolicyEngine.#INTERACTIVE_TAGS.has(tag);
1674
+ if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS2;
1675
+ const isInteractive = INTERACTIVE_TAGS.has(tag);
1635
1676
  if (!isInteractive) {
1636
1677
  const tabindex = props.tabindex;
1637
1678
  const n = typeof tabindex === "number" ? tabindex : typeof tabindex === "string" ? parseInt(tabindex, 10) : NaN;
1638
- if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS;
1679
+ if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS2;
1639
1680
  }
1640
1681
  return [
1641
1682
  {
@@ -1654,7 +1695,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1654
1695
  props,
1655
1696
  effectiveRole
1656
1697
  }) {
1657
- if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS;
1698
+ if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS2;
1658
1699
  const results = [];
1659
1700
  iterate.forEachEntry(props, (key) => {
1660
1701
  if (!key.startsWith("aria-")) return;
@@ -1670,18 +1711,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1670
1711
  });
1671
1712
  return results;
1672
1713
  }
1673
- // WAI-ARIA live region roles and their implied aria-live politeness values.
1674
- static #LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
1675
- ["alert", "assertive"],
1676
- ["status", "polite"],
1677
- ["log", "polite"],
1678
- ["timer", "off"]
1679
- ]);
1680
1714
  static #checkMissingLiveRegion({ effectiveRole, props }) {
1681
- if (!effectiveRole) return NO_VIOLATIONS;
1682
- const impliedLive = _AriaPolicyEngine.#LIVE_REGION_ROLES.get(effectiveRole);
1683
- if (!impliedLive) return NO_VIOLATIONS;
1684
- if ("aria-live" in props) return NO_VIOLATIONS;
1715
+ if (!effectiveRole) return NO_VIOLATIONS2;
1716
+ const impliedLive = LIVE_REGION_ROLES.get(effectiveRole);
1717
+ if (!impliedLive) return NO_VIOLATIONS2;
1718
+ if ("aria-live" in props) return NO_VIOLATIONS2;
1685
1719
  const injectLive = {
1686
1720
  kind: `injectLive:${effectiveRole}`,
1687
1721
  apply: (ctx) => ({
@@ -1700,20 +1734,13 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1700
1734
  }
1701
1735
  ];
1702
1736
  }
1703
- static #checkMissingAtomic({ effectiveRole, props }) {
1704
- if (!effectiveRole || !_AriaPolicyEngine.#LIVE_REGION_ROLES.has(effectiveRole))
1705
- return NO_VIOLATIONS;
1706
- if ("aria-atomic" in props) return NO_VIOLATIONS;
1707
- return [
1708
- {
1709
- valid: false,
1710
- fixable: false,
1711
- severity: "warning",
1712
- diagnostic: AriaDiagnostics.missingAtomic(effectiveRole)
1713
- }
1714
- ];
1737
+ static #missingAtomicRule = {
1738
+ attributesByRole: ATOMIC_REQUIREMENTS,
1739
+ diagnosticFor: (_attribute, role) => AriaDiagnostics.missingAtomic(role)
1740
+ };
1741
+ static #checkMissingAtomic(context) {
1742
+ return checkRequiredAttributes(_AriaPolicyEngine.#missingAtomicRule, context);
1715
1743
  }
1716
- static #VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set(["additions", "removals", "text", "all"]);
1717
1744
  // Custom fix rules passed via `options.rules` must be pure functions of (tag, props) — the cache
1718
1745
  // replays stored fixes against new prop objects, so fixes that close over external state will
1719
1746
  // produce inconsistent results on cache hits.
@@ -1727,10 +1754,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1727
1754
  };
1728
1755
  static #checkInvalidAriaRelevant({ props }) {
1729
1756
  const relevant = props["aria-relevant"];
1730
- if (relevant === void 0) return NO_VIOLATIONS;
1731
- if (typeof relevant !== "string") return NO_VIOLATIONS;
1757
+ if (relevant === void 0) return NO_VIOLATIONS2;
1758
+ if (typeof relevant !== "string") return NO_VIOLATIONS2;
1732
1759
  const tokens = relevant.trim().split(/\s+/);
1733
- const invalid = tokens.filter((t) => !_AriaPolicyEngine.#VALID_RELEVANT_TOKENS.has(t));
1760
+ const invalid = tokens.filter((t) => !VALID_RELEVANT_TOKENS.has(t));
1734
1761
  if (invalid.length > 0) {
1735
1762
  return [
1736
1763
  {
@@ -1755,7 +1782,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1755
1782
  }
1756
1783
  ];
1757
1784
  }
1758
- return NO_VIOLATIONS;
1785
+ return NO_VIOLATIONS2;
1759
1786
  }
1760
1787
  };
1761
1788
 
@@ -2075,7 +2102,59 @@ var readonlyProps = ({
2075
2102
  // ../core/src/html/evaluators.ts
2076
2103
  import { warnDiagnostics as warnDiagnostics2 } from "../_shared/diagnostics.js";
2077
2104
 
2078
- // ../core/src/html/input-rules.ts
2105
+ // ../core/src/html/spec/vocabulary/input.ts
2106
+ var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2107
+ var NUMERIC_INPUT_TYPES = [
2108
+ "number",
2109
+ "range",
2110
+ "date",
2111
+ "month",
2112
+ "week",
2113
+ "time",
2114
+ "datetime-local"
2115
+ ];
2116
+ var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
2117
+ ...TEXT_INPUT_TYPES,
2118
+ ...NUMERIC_INPUT_TYPES,
2119
+ "checkbox",
2120
+ "radio",
2121
+ "file",
2122
+ "color",
2123
+ "hidden",
2124
+ "button",
2125
+ "submit",
2126
+ "reset",
2127
+ "image"
2128
+ ]);
2129
+
2130
+ // ../core/src/html/spec/attributes/input.ts
2131
+ var INPUT_ATTRIBUTE_TYPE_POLICIES = [
2132
+ { attribute: "checked", allowedTypes: ["checkbox", "radio"] },
2133
+ { attribute: "multiple", allowedTypes: ["email", "file"] },
2134
+ { attribute: "maxLength", allowedTypes: TEXT_INPUT_TYPES },
2135
+ { attribute: "minLength", allowedTypes: TEXT_INPUT_TYPES },
2136
+ { attribute: "pattern", allowedTypes: TEXT_INPUT_TYPES },
2137
+ { attribute: "min", allowedTypes: NUMERIC_INPUT_TYPES },
2138
+ { attribute: "max", allowedTypes: NUMERIC_INPUT_TYPES },
2139
+ { attribute: "step", allowedTypes: NUMERIC_INPUT_TYPES },
2140
+ { attribute: "accept", allowedTypes: ["file"] },
2141
+ { attribute: "capture", allowedTypes: ["file"] },
2142
+ { attribute: "size", allowedTypes: TEXT_INPUT_TYPES },
2143
+ { attribute: "alt", allowedTypes: ["image"] },
2144
+ { attribute: "height", allowedTypes: ["image"] },
2145
+ { attribute: "width", allowedTypes: ["image"] }
2146
+ ];
2147
+
2148
+ // ../core/src/html/spec/constraints/input.ts
2149
+ var REQUIRED_READONLY_CONFLICT = {
2150
+ props: ["required", "readOnly"],
2151
+ diagnostic: () => InputAccessibilityDiagnostics.requiredReadOnlyConflict()
2152
+ };
2153
+ var INPUT_MUTUALLY_EXCLUSIVE_POLICIES = [
2154
+ REQUIRED_READONLY_CONFLICT
2155
+ ];
2156
+
2157
+ // ../core/src/html/spec/validators/attribute-type-validator.ts
2079
2158
  var DEFAULT_INPUT_TYPE = "text";
2080
2159
  function omit(props, key) {
2081
2160
  const next = { ...props };
@@ -2091,7 +2170,10 @@ function removeAttributeFix(attribute) {
2091
2170
  }
2092
2171
  };
2093
2172
  }
2094
- function inputAttributeRequiresType(attribute, allowedTypes) {
2173
+ function createInputAttributeTypeRule({
2174
+ attribute,
2175
+ allowedTypes
2176
+ }) {
2095
2177
  const rule = ({ tag, props }) => {
2096
2178
  if (tag !== "input" || !(attribute in props)) return [];
2097
2179
  const type = typeof props.type === "string" ? props.type : DEFAULT_INPUT_TYPE;
@@ -2107,31 +2189,30 @@ function inputAttributeRequiresType(attribute, allowedTypes) {
2107
2189
  }
2108
2190
  ];
2109
2191
  };
2110
- return Object.assign(rule, { readsProps: ["type", attribute] });
2192
+ return Object.assign(rule, { readsProps: ["type", attribute], tags: ["input"] });
2193
+ }
2194
+
2195
+ // ../core/src/html/spec/validators/mutually-exclusive-validator.ts
2196
+ function createMutuallyExclusiveRule({
2197
+ props: conflictingProps,
2198
+ diagnostic: createDiagnostic
2199
+ }) {
2200
+ const [first, second] = conflictingProps;
2201
+ const rule = ({ tag, props }) => {
2202
+ if (tag !== "input" || !props[first] || !props[second]) return [];
2203
+ const diagnostic = createDiagnostic();
2204
+ return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2205
+ };
2206
+ return Object.assign(rule, { readsProps: conflictingProps, tags: ["input"] });
2207
+ }
2208
+
2209
+ // ../core/src/html/input-rules.ts
2210
+ var policyByAttribute = Object.fromEntries(
2211
+ INPUT_ATTRIBUTE_TYPE_POLICIES.map((policy) => [policy.attribute, policy])
2212
+ );
2213
+ function policyFor(attribute) {
2214
+ return policyByAttribute[attribute];
2111
2215
  }
2112
- var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2113
- var NUMERIC_INPUT_TYPES = [
2114
- "number",
2115
- "range",
2116
- "date",
2117
- "month",
2118
- "week",
2119
- "time",
2120
- "datetime-local"
2121
- ];
2122
- var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
2123
- ...TEXT_INPUT_TYPES,
2124
- ...NUMERIC_INPUT_TYPES,
2125
- "checkbox",
2126
- "radio",
2127
- "file",
2128
- "color",
2129
- "hidden",
2130
- "button",
2131
- "submit",
2132
- "reset",
2133
- "image"
2134
- ]);
2135
2216
  var supportedInputTypeRule = Object.assign(
2136
2217
  ({ tag, props }) => {
2137
2218
  if (tag !== "input" || typeof props.type !== "string") return [];
@@ -2147,30 +2228,22 @@ var supportedInputTypeRule = Object.assign(
2147
2228
  }
2148
2229
  ];
2149
2230
  },
2150
- { readsProps: ["type"] }
2151
- );
2152
- var checkedRequiresCheckableTypeRule = inputAttributeRequiresType("checked", [
2153
- "checkbox",
2154
- "radio"
2155
- ]);
2156
- var multipleRequiresSupportedTypeRule = inputAttributeRequiresType("multiple", [
2157
- "email",
2158
- "file"
2159
- ]);
2160
- var maxLengthRequiresTextTypeRule = inputAttributeRequiresType(
2161
- "maxLength",
2162
- TEXT_INPUT_TYPES
2163
- );
2164
- var minLengthRequiresTextTypeRule = inputAttributeRequiresType(
2165
- "minLength",
2166
- TEXT_INPUT_TYPES
2231
+ { readsProps: ["type"], tags: ["input"] }
2167
2232
  );
2168
- var patternRequiresTextTypeRule = inputAttributeRequiresType("pattern", TEXT_INPUT_TYPES);
2169
- var minRequiresNumericTypeRule = inputAttributeRequiresType("min", NUMERIC_INPUT_TYPES);
2170
- var maxRequiresNumericTypeRule = inputAttributeRequiresType("max", NUMERIC_INPUT_TYPES);
2171
- var stepRequiresNumericTypeRule = inputAttributeRequiresType("step", NUMERIC_INPUT_TYPES);
2172
- var acceptRequiresFileTypeRule = inputAttributeRequiresType("accept", ["file"]);
2173
- var captureRequiresFileTypeRule = inputAttributeRequiresType("capture", ["file"]);
2233
+ var checkedRequiresCheckableTypeRule = createInputAttributeTypeRule(policyFor("checked"));
2234
+ var multipleRequiresSupportedTypeRule = createInputAttributeTypeRule(policyFor("multiple"));
2235
+ var maxLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("maxLength"));
2236
+ var minLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("minLength"));
2237
+ var patternRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("pattern"));
2238
+ var minRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("min"));
2239
+ var maxRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("max"));
2240
+ var stepRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("step"));
2241
+ var acceptRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("accept"));
2242
+ var captureRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("capture"));
2243
+ var sizeRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("size"));
2244
+ var altRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("alt"));
2245
+ var heightRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("height"));
2246
+ var widthRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("width"));
2174
2247
  var inputAccessibleNameRule = Object.assign(
2175
2248
  ({ tag, props }) => {
2176
2249
  if (tag !== "input" || props.type === "hidden") return [];
@@ -2179,7 +2252,10 @@ var inputAccessibleNameRule = Object.assign(
2179
2252
  const diagnostic = hasPlaceholder ? InputAccessibilityDiagnostics.placeholderIsNotLabel() : InputAccessibilityDiagnostics.missingAccessibleName();
2180
2253
  return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2181
2254
  },
2182
- { readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"] }
2255
+ {
2256
+ readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"],
2257
+ tags: ["input"]
2258
+ }
2183
2259
  );
2184
2260
  var PASSWORD_AUTOCOMPLETE_VALUES = ["current-password", "new-password"];
2185
2261
  var passwordAutocompleteRule = Object.assign(
@@ -2191,16 +2267,9 @@ var passwordAutocompleteRule = Object.assign(
2191
2267
  const diagnostic = InputAccessibilityDiagnostics.passwordMissingAutocomplete();
2192
2268
  return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2193
2269
  },
2194
- { readsProps: ["type", "autoComplete"] }
2195
- );
2196
- var requiredReadOnlyConflictRule = Object.assign(
2197
- ({ tag, props }) => {
2198
- if (tag !== "input" || !props.required || !props.readOnly) return [];
2199
- const diagnostic = InputAccessibilityDiagnostics.requiredReadOnlyConflict();
2200
- return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2201
- },
2202
- { readsProps: ["required", "readOnly"] }
2270
+ { readsProps: ["type", "autoComplete"], tags: ["input"] }
2203
2271
  );
2272
+ var requiredReadOnlyConflictRule = createMutuallyExclusiveRule(REQUIRED_READONLY_CONFLICT);
2204
2273
  var INPUT_RULES = [
2205
2274
  supportedInputTypeRule,
2206
2275
  checkedRequiresCheckableTypeRule,
@@ -2213,11 +2282,132 @@ var INPUT_RULES = [
2213
2282
  stepRequiresNumericTypeRule,
2214
2283
  acceptRequiresFileTypeRule,
2215
2284
  captureRequiresFileTypeRule,
2285
+ sizeRequiresTextTypeRule,
2286
+ altRequiresImageTypeRule,
2287
+ heightRequiresImageTypeRule,
2288
+ widthRequiresImageTypeRule,
2216
2289
  inputAccessibleNameRule,
2217
2290
  passwordAutocompleteRule,
2218
2291
  requiredReadOnlyConflictRule
2219
2292
  ];
2220
2293
 
2294
+ // ../core/src/html/spec/types.ts
2295
+ function definePropRolePolicy(prop, map2, fallback) {
2296
+ return { kind: "byProp", prop, map: map2, fallback };
2297
+ }
2298
+ function resolveAllowedRoles(spec, props) {
2299
+ const policy = spec.allowedRoles;
2300
+ if (!policy) return void 0;
2301
+ switch (policy.kind) {
2302
+ case "fixed":
2303
+ return policy.roles;
2304
+ case "byProp": {
2305
+ const value = typeof props[policy.prop] === "string" ? props[policy.prop] : policy.fallback;
2306
+ return policy.map[value];
2307
+ }
2308
+ case "dynamic":
2309
+ return policy.resolve({ props });
2310
+ }
2311
+ }
2312
+
2313
+ // ../core/src/html/spec/roles/input.ts
2314
+ var ALLOWED_INPUT_ROLES = {
2315
+ checkbox: ["menuitemcheckbox", "option", "switch", "button"],
2316
+ radio: ["menuitemradio"],
2317
+ range: [],
2318
+ number: [],
2319
+ search: ["combobox"],
2320
+ text: ["combobox", "searchbox", "spinbutton"],
2321
+ email: ["combobox"],
2322
+ tel: ["combobox"],
2323
+ url: ["combobox"],
2324
+ button: [
2325
+ "link",
2326
+ "menuitem",
2327
+ "menuitemcheckbox",
2328
+ "menuitemradio",
2329
+ "option",
2330
+ "radio",
2331
+ "switch",
2332
+ "tab"
2333
+ ],
2334
+ submit: [
2335
+ "link",
2336
+ "menuitem",
2337
+ "menuitemcheckbox",
2338
+ "menuitemradio",
2339
+ "option",
2340
+ "radio",
2341
+ "switch",
2342
+ "tab"
2343
+ ],
2344
+ reset: [
2345
+ "link",
2346
+ "menuitem",
2347
+ "menuitemcheckbox",
2348
+ "menuitemradio",
2349
+ "option",
2350
+ "radio",
2351
+ "switch",
2352
+ "tab"
2353
+ ],
2354
+ image: [
2355
+ "link",
2356
+ "menuitem",
2357
+ "menuitemcheckbox",
2358
+ "menuitemradio",
2359
+ "option",
2360
+ "radio",
2361
+ "switch",
2362
+ "tab"
2363
+ ],
2364
+ hidden: []
2365
+ };
2366
+
2367
+ // ../core/src/html/spec/elements/input.ts
2368
+ var inputElementSpec = {
2369
+ tag: "input",
2370
+ allowedRoles: definePropRolePolicy("type", ALLOWED_INPUT_ROLES, "text"),
2371
+ attributes: INPUT_ATTRIBUTE_TYPE_POLICIES,
2372
+ mutuallyExclusive: INPUT_MUTUALLY_EXCLUSIVE_POLICIES
2373
+ };
2374
+
2375
+ // ../core/src/html/spec/roles/img.ts
2376
+ var IMG_NAMED_ROLES = [
2377
+ "button",
2378
+ "checkbox",
2379
+ "link",
2380
+ "menuitem",
2381
+ "menuitemcheckbox",
2382
+ "menuitemradio",
2383
+ "option",
2384
+ "progressbar",
2385
+ "scrollbar",
2386
+ "separator",
2387
+ "slider",
2388
+ "switch",
2389
+ "tab",
2390
+ "treeitem"
2391
+ ];
2392
+
2393
+ // ../core/src/html/spec/elements/img.ts
2394
+ var imgElementSpec = {
2395
+ tag: "img",
2396
+ allowedRoles: {
2397
+ kind: "dynamic",
2398
+ resolve: ({ props }) => props.alt === "" ? [] : IMG_NAMED_ROLES
2399
+ }
2400
+ };
2401
+
2402
+ // ../core/src/html/spec/roles/table.ts
2403
+ var ALLOWED_TABLE_ROLES = ["grid", "treegrid"];
2404
+
2405
+ // ../core/src/html/spec/elements/table.ts
2406
+ var tableElementSpec = {
2407
+ tag: "table",
2408
+ allowedRoles: { kind: "fixed", roles: ALLOWED_TABLE_ROLES }
2409
+ };
2410
+
2221
2411
  // ../core/src/html/role-restrictions.ts
2222
2412
  var ALLOWED_ROLES = {
2223
2413
  article: ["application", "document", "feed", "main", "none", "presentation", "region"],
@@ -2290,86 +2480,17 @@ var ALLOWED_ROLES = {
2290
2480
  "tab",
2291
2481
  "treeitem"
2292
2482
  ],
2293
- table: ["grid", "treegrid"],
2294
2483
  dialog: ["alertdialog"],
2295
2484
  fieldset: ["none", "presentation", "radiogroup"]
2296
2485
  };
2297
- var IMG_NAMED_ROLES = [
2298
- "button",
2299
- "checkbox",
2300
- "link",
2301
- "menuitem",
2302
- "menuitemcheckbox",
2303
- "menuitemradio",
2304
- "option",
2305
- "progressbar",
2306
- "scrollbar",
2307
- "separator",
2308
- "slider",
2309
- "switch",
2310
- "tab",
2311
- "treeitem"
2312
- ];
2313
- var ALLOWED_INPUT_ROLES = {
2314
- checkbox: ["menuitemcheckbox", "option", "switch", "button"],
2315
- radio: ["menuitemradio"],
2316
- range: [],
2317
- number: [],
2318
- search: ["combobox"],
2319
- text: ["combobox", "searchbox", "spinbutton"],
2320
- email: ["combobox"],
2321
- tel: ["combobox"],
2322
- url: ["combobox"],
2323
- button: [
2324
- "link",
2325
- "menuitem",
2326
- "menuitemcheckbox",
2327
- "menuitemradio",
2328
- "option",
2329
- "radio",
2330
- "switch",
2331
- "tab"
2332
- ],
2333
- submit: [
2334
- "link",
2335
- "menuitem",
2336
- "menuitemcheckbox",
2337
- "menuitemradio",
2338
- "option",
2339
- "radio",
2340
- "switch",
2341
- "tab"
2342
- ],
2343
- reset: [
2344
- "link",
2345
- "menuitem",
2346
- "menuitemcheckbox",
2347
- "menuitemradio",
2348
- "option",
2349
- "radio",
2350
- "switch",
2351
- "tab"
2352
- ],
2353
- image: [
2354
- "link",
2355
- "menuitem",
2356
- "menuitemcheckbox",
2357
- "menuitemradio",
2358
- "option",
2359
- "radio",
2360
- "switch",
2361
- "tab"
2362
- ],
2363
- hidden: []
2486
+ var ELEMENT_SPECS = {
2487
+ input: inputElementSpec,
2488
+ img: imgElementSpec,
2489
+ table: tableElementSpec
2364
2490
  };
2365
2491
  function getAllowedRoles(tag, props) {
2366
- if (tag === "input") {
2367
- const type = typeof props.type === "string" ? props.type : "text";
2368
- return ALLOWED_INPUT_ROLES[type];
2369
- }
2370
- if (tag === "img") {
2371
- return props.alt === "" ? [] : IMG_NAMED_ROLES;
2372
- }
2492
+ const spec = ELEMENT_SPECS[tag];
2493
+ if (spec) return resolveAllowedRoles(spec, props);
2373
2494
  return ALLOWED_ROLES[tag];
2374
2495
  }
2375
2496
  var removeRoleFix = {
@@ -2410,21 +2531,24 @@ var removeLandmarkRoleOverride = {
2410
2531
  return { applied: true, next: rest, previous: props };
2411
2532
  }
2412
2533
  };
2413
- function landmarkRoleRule({ tag, props, implicitRole }) {
2414
- if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2415
- const role = props.role;
2416
- if (!role || role === implicitRole) return [];
2417
- const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2418
- return [
2419
- {
2420
- valid: false,
2421
- fixable: true,
2422
- severity: diagnostic.severity,
2423
- fix: removeLandmarkRoleOverride,
2424
- diagnostic
2425
- }
2426
- ];
2427
- }
2534
+ var landmarkRoleRule = Object.assign(
2535
+ ({ tag, props, implicitRole }) => {
2536
+ if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2537
+ const role = props.role;
2538
+ if (!role || role === implicitRole) return [];
2539
+ const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2540
+ return [
2541
+ {
2542
+ valid: false,
2543
+ fixable: true,
2544
+ severity: diagnostic.severity,
2545
+ fix: removeLandmarkRoleOverride,
2546
+ diagnostic
2547
+ }
2548
+ ];
2549
+ },
2550
+ { tags: [...LANDMARK_TAG_SET] }
2551
+ );
2428
2552
  function requireAccessibleName({ tag, props }) {
2429
2553
  if ("aria-label" in props || "aria-labelledby" in props) return [];
2430
2554
  return [
@@ -2437,10 +2561,13 @@ function requireAccessibleName({ tag, props }) {
2437
2561
  ];
2438
2562
  }
2439
2563
  var NAMED_LANDMARK_TAGS = /* @__PURE__ */ new Set(["nav", "aside"]);
2440
- function landmarkNameAdvisory(ctx) {
2441
- if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2442
- return requireAccessibleName(ctx);
2443
- }
2564
+ var landmarkNameAdvisory = Object.assign(
2565
+ (ctx) => {
2566
+ if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2567
+ return requireAccessibleName(ctx);
2568
+ },
2569
+ { tags: [...NAMED_LANDMARK_TAGS] }
2570
+ );
2444
2571
  var HTML_ARIA_RULES = [
2445
2572
  landmarkRoleRule,
2446
2573
  landmarkNameAdvisory,
@@ -2541,6 +2668,62 @@ var figureContract = contract([
2541
2668
  ]);
2542
2669
  var detailsContract = firstChildContract("summary", "summary");
2543
2670
  var fieldsetContract = firstChildContract("legend", "legend");
2671
+ var objectContract = contract([
2672
+ { name: "param", match: isTag("param") },
2673
+ { name: "content", match: isOpenContent("param") }
2674
+ ]);
2675
+ var INTERACTIVE_CONTENT_TAGS = ["a", "button", "input", "select", "textarea", "label"];
2676
+ var buttonContract = closedContract([
2677
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2678
+ ]);
2679
+ var anchorContract = closedContract([
2680
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2681
+ ]);
2682
+ var LABELABLE_TAGS = [
2683
+ "button",
2684
+ "input",
2685
+ "meter",
2686
+ "output",
2687
+ "progress",
2688
+ "select",
2689
+ "textarea"
2690
+ ];
2691
+ var labelContract = contract([
2692
+ { name: "control", match: isTag(...LABELABLE_TAGS), cardinality: { max: 1 } }
2693
+ ]);
2694
+ var P_BLOCKED_TAGS = [
2695
+ "address",
2696
+ "article",
2697
+ "aside",
2698
+ "blockquote",
2699
+ "details",
2700
+ "dialog",
2701
+ "div",
2702
+ "dl",
2703
+ "fieldset",
2704
+ "figure",
2705
+ "footer",
2706
+ "form",
2707
+ "h1",
2708
+ "h2",
2709
+ "h3",
2710
+ "h4",
2711
+ "h5",
2712
+ "h6",
2713
+ "header",
2714
+ "hr",
2715
+ "main",
2716
+ "nav",
2717
+ "ol",
2718
+ "p",
2719
+ "pre",
2720
+ "section",
2721
+ "table",
2722
+ "ul"
2723
+ ];
2724
+ var pContract = closedContract([
2725
+ { name: "content", match: isOpenContent(...P_BLOCKED_TAGS) }
2726
+ ]);
2544
2727
  var mediaContract = contract([
2545
2728
  { name: "source", match: isTag("source") },
2546
2729
  { name: "track", match: isTag("track") },
@@ -2595,6 +2778,11 @@ var htmlContracts = {
2595
2778
  details: detailsContract,
2596
2779
  fieldset: fieldsetContract,
2597
2780
  dialog: dialogContract,
2781
+ object: objectContract,
2782
+ button: buttonContract,
2783
+ a: anchorContract,
2784
+ label: labelContract,
2785
+ p: pContract,
2598
2786
  head: headContract,
2599
2787
  html: htmlContract
2600
2788
  };
@@ -2821,6 +3009,11 @@ function composeNormalizers(normalizers, fn) {
2821
3009
  function whenDefined(key, value) {
2822
3010
  return value === void 0 ? {} : { [key]: value };
2823
3011
  }
3012
+ function mergeAriaRules(aria, rules) {
3013
+ if (!aria?.length) return rules;
3014
+ if (!rules?.length) return aria;
3015
+ return [...aria, ...rules];
3016
+ }
2824
3017
  function resolveFactoryOptions(options = {}) {
2825
3018
  const { styling, enforcement } = options;
2826
3019
  const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
@@ -2841,7 +3034,7 @@ function resolveFactoryOptions(options = {}) {
2841
3034
  ...whenDefined("defaultVariants", styling?.defaults),
2842
3035
  ...whenDefined("compoundVariants", styling?.compounds),
2843
3036
  ...whenDefined("normalizeFn", composedNormalizeFn),
2844
- ...whenDefined("ariaRules", enforcement?.aria),
3037
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
2845
3038
  ...whenDefined("childRules", enforcement?.children),
2846
3039
  ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
2847
3040
  ...whenDefined("allowText", enforcement?.allowText),