praxis-kit 6.5.0 → 6.6.1

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.
@@ -232,6 +232,16 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
232
232
  */
233
233
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
234
234
  readonly aria?: readonly AriaRule[];
235
+ /**
236
+ * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
237
+ * (`AriaRule`'s `readsProps`, fixable `AriaFix` results) but have no
238
+ * relationship to ARIA semantics — an HTML fact or a security check like a
239
+ * dangerous-URL-scheme guard, for example. Evaluated together with `aria`
240
+ * (both run through the same engine, merged into one rule set) — this is a
241
+ * separate bucket purely so a non-ARIA rule doesn't have to sit under the
242
+ * misleading `aria` name to get the machinery it needs.
243
+ */
244
+ readonly rules?: readonly AriaRule[];
235
245
  readonly children?: readonly ChildRuleInput[];
236
246
  /**
237
247
  * When true, only children matching a `children` rule (or text, per `allowText`)
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"]);
@@ -1228,7 +1240,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1228
1240
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1229
1241
  const implicitRole = getImplicitRole(tag, props);
1230
1242
  const hasRole = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1231
- if (!hasRole) return { proceed: false, result: { props, violations: [] } };
1232
1243
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1233
1244
  const workingProps = normalized.normalized ? normalized.result.props : props;
1234
1245
  const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
@@ -1238,6 +1249,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1238
1249
  tag,
1239
1250
  implicitRole,
1240
1251
  effectiveRole,
1252
+ hasRole,
1241
1253
  props: workingProps,
1242
1254
  preExistingViolations,
1243
1255
  context: { tag, props: workingProps, implicitRole, effectiveRole }
@@ -1281,6 +1293,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1281
1293
  static evaluate(tag, props) {
1282
1294
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1283
1295
  if (!derived.proceed) return derived.result;
1296
+ if (!derived.hasRole)
1297
+ return { props: derived.props, violations: [...derived.preExistingViolations] };
1284
1298
  const {
1285
1299
  tag: narrowedTag,
1286
1300
  implicitRole,
@@ -1305,10 +1319,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1305
1319
  props: workingProps,
1306
1320
  preExistingViolations
1307
1321
  } = derived;
1308
- const { violations, fixes } = _AriaPolicyEngine.#runRules(
1309
- [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1310
- context
1311
- );
1322
+ const rules = derived.hasRole ? [..._AriaPolicyEngine.#getRules(context), ...extraRules] : extraRules;
1323
+ const { violations, fixes } = _AriaPolicyEngine.#runRules(rules, context);
1312
1324
  const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1313
1325
  return { props: next, violations: [...preExistingViolations, ...violations] };
1314
1326
  }
@@ -1507,7 +1519,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1507
1519
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1508
1520
  const role = props.role;
1509
1521
  if (role !== "region") return NO_VIOLATIONS2;
1510
- if (!isStandaloneTag(tag)) return NO_VIOLATIONS2;
1522
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS2;
1511
1523
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1512
1524
  return [
1513
1525
  {
@@ -2090,6 +2102,93 @@ var readonlyProps = ({
2090
2102
  // ../core/src/html/evaluators.ts
2091
2103
  import { warnDiagnostics as warnDiagnostics2 } from "../_shared/diagnostics.js";
2092
2104
 
2105
+ // ../core/src/html/contracts/categories.ts
2106
+ var METADATA_TAGS = ["script", "template"];
2107
+ var VOID_TAGS = [
2108
+ "area",
2109
+ "base",
2110
+ "br",
2111
+ "col",
2112
+ "embed",
2113
+ "hr",
2114
+ "img",
2115
+ "input",
2116
+ "link",
2117
+ "meta",
2118
+ "param",
2119
+ "source",
2120
+ "track",
2121
+ "wbr"
2122
+ ];
2123
+ var TEXT_ONLY_TAGS = [
2124
+ "option",
2125
+ "script",
2126
+ "style",
2127
+ "textarea",
2128
+ "title"
2129
+ ];
2130
+ var LANDMARK_TAGS = [
2131
+ "article",
2132
+ "aside",
2133
+ "footer",
2134
+ "header",
2135
+ "main",
2136
+ "nav"
2137
+ ];
2138
+ var INTERACTIVE_CONTENT_TAGS = [
2139
+ "a",
2140
+ "button",
2141
+ "input",
2142
+ "select",
2143
+ "textarea",
2144
+ "label"
2145
+ ];
2146
+ var LABELABLE_TAGS = [
2147
+ "button",
2148
+ "input",
2149
+ "meter",
2150
+ "output",
2151
+ "progress",
2152
+ "select",
2153
+ "textarea"
2154
+ ];
2155
+ var OTHER_INTERACTIVE_TAGS = [
2156
+ "a",
2157
+ "details",
2158
+ "embed",
2159
+ "iframe"
2160
+ ];
2161
+ var P_BLOCKED_TAGS = [
2162
+ "address",
2163
+ "article",
2164
+ "aside",
2165
+ "blockquote",
2166
+ "details",
2167
+ "dialog",
2168
+ "div",
2169
+ "dl",
2170
+ "fieldset",
2171
+ "figure",
2172
+ "footer",
2173
+ "form",
2174
+ "h1",
2175
+ "h2",
2176
+ "h3",
2177
+ "h4",
2178
+ "h5",
2179
+ "h6",
2180
+ "header",
2181
+ "hr",
2182
+ "main",
2183
+ "nav",
2184
+ "ol",
2185
+ "p",
2186
+ "pre",
2187
+ "section",
2188
+ "table",
2189
+ "ul"
2190
+ ];
2191
+
2093
2192
  // ../core/src/html/spec/vocabulary/input.ts
2094
2193
  var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2095
2194
  var NUMERIC_INPUT_TYPES = [
@@ -2469,7 +2568,11 @@ var ALLOWED_ROLES = {
2469
2568
  "treeitem"
2470
2569
  ],
2471
2570
  dialog: ["alertdialog"],
2472
- fieldset: ["none", "presentation", "radiogroup"]
2571
+ fieldset: ["none", "presentation", "radiogroup"],
2572
+ // `<label>` has no implicit role and no documented alternates — its native labeling
2573
+ // semantics (control association, accessible-name contribution) aren't reproducible via
2574
+ // ARIA, so any explicit `role` should be avoided rather than substituted.
2575
+ label: []
2473
2576
  };
2474
2577
  var ELEMENT_SPECS = {
2475
2578
  input: inputElementSpec,
@@ -2510,7 +2613,10 @@ var roleNotPermittedRule = Object.assign(
2510
2613
  );
2511
2614
 
2512
2615
  // ../core/src/html/aria-rules.ts
2513
- var LANDMARK_TAG_SET = /* @__PURE__ */ new Set(["article", "aside", "footer", "header", "main", "nav"]);
2616
+ function defineAriaRule(tags, rule) {
2617
+ return Object.assign(rule, { tags });
2618
+ }
2619
+ var LANDMARK_TAG_SET = new Set(LANDMARK_TAGS);
2514
2620
  var removeLandmarkRoleOverride = {
2515
2621
  kind: "removeRole",
2516
2622
  apply: ({ props }) => {
@@ -2519,10 +2625,11 @@ var removeLandmarkRoleOverride = {
2519
2625
  return { applied: true, next: rest, previous: props };
2520
2626
  }
2521
2627
  };
2522
- var landmarkRoleRule = Object.assign(
2628
+ var landmarkRoleRule = defineAriaRule(
2629
+ LANDMARK_TAGS,
2523
2630
  ({ tag, props, implicitRole }) => {
2524
2631
  if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2525
- const role = props.role;
2632
+ const { role } = props;
2526
2633
  if (!role || role === implicitRole) return [];
2527
2634
  const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2528
2635
  return [
@@ -2534,8 +2641,7 @@ var landmarkRoleRule = Object.assign(
2534
2641
  diagnostic
2535
2642
  }
2536
2643
  ];
2537
- },
2538
- { tags: [...LANDMARK_TAG_SET] }
2644
+ }
2539
2645
  );
2540
2646
  function requireAccessibleName({ tag, props }) {
2541
2647
  if ("aria-label" in props || "aria-labelledby" in props) return [];
@@ -2548,38 +2654,44 @@ function requireAccessibleName({ tag, props }) {
2548
2654
  }
2549
2655
  ];
2550
2656
  }
2551
- var NAMED_LANDMARK_TAGS = /* @__PURE__ */ new Set(["nav", "aside"]);
2552
- var landmarkNameAdvisory = Object.assign(
2657
+ var NAMED_LANDMARK_TAGS = ["nav", "aside"];
2658
+ var NAMED_LANDMARK_TAG_SET = new Set(NAMED_LANDMARK_TAGS);
2659
+ var landmarkAccessibleNameRule = defineAriaRule(
2660
+ NAMED_LANDMARK_TAGS,
2553
2661
  (ctx) => {
2554
- if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2662
+ if (!ctx.implicitRole || !NAMED_LANDMARK_TAG_SET.has(ctx.tag)) return [];
2555
2663
  return requireAccessibleName(ctx);
2556
- },
2557
- { tags: [...NAMED_LANDMARK_TAGS] }
2664
+ }
2558
2665
  );
2559
2666
  var HTML_ARIA_RULES = [
2560
2667
  landmarkRoleRule,
2561
- landmarkNameAdvisory,
2668
+ landmarkAccessibleNameRule,
2562
2669
  roleNotPermittedRule,
2563
2670
  ...INPUT_RULES
2564
2671
  ];
2565
2672
 
2566
- // ../core/src/html/contracts.ts
2673
+ // ../core/src/html/contracts/helpers.ts
2567
2674
  import { warnDiagnostics } from "../_shared/diagnostics.js";
2675
+ function isVNodeLike(child) {
2676
+ return isObject(child) && "type" in child;
2677
+ }
2568
2678
  function isOpenContent(...blockedTags) {
2569
2679
  const set2 = new Set(blockedTags);
2570
2680
  return (child) => {
2571
- if (!isObject(child) || !("type" in child)) return false;
2681
+ if (!isVNodeLike(child)) return false;
2572
2682
  const tag = getTag(child);
2573
2683
  return tag === void 0 || !set2.has(tag);
2574
2684
  };
2575
2685
  }
2576
- var METADATA_TAGS = ["script", "template"];
2577
2686
  var metadataMatch = isTag(...METADATA_TAGS);
2578
2687
  function metadata(name = "metadata") {
2579
2688
  return { name, match: metadataMatch };
2580
2689
  }
2690
+ function optional(name, tag) {
2691
+ return { name, match: isTag(tag), cardinality: { max: 1 } };
2692
+ }
2581
2693
  function firstOptional(name, tag) {
2582
- return { name, match: isTag(tag), cardinality: { max: 1 }, position: "first" };
2694
+ return { ...optional(name, tag), position: "first" };
2583
2695
  }
2584
2696
  function contract(children, options) {
2585
2697
  return { diagnostics: warnDiagnostics, children, ...options };
@@ -2593,50 +2705,41 @@ function ariaContract(aria) {
2593
2705
  function firstChildContract(name, tag) {
2594
2706
  return contract([firstOptional(name, tag), { name: "content", match: isOpenContent(tag) }]);
2595
2707
  }
2596
- var VOID_TAGS = [
2597
- "area",
2598
- "base",
2599
- "br",
2600
- "col",
2601
- "embed",
2602
- "hr",
2603
- "img",
2604
- "input",
2605
- "link",
2606
- "meta",
2607
- "param",
2608
- "source",
2609
- "track",
2610
- "wbr"
2611
- ];
2612
- var TEXT_ONLY_TAGS = ["option", "script", "style", "textarea", "title"];
2613
- var LANDMARK_TAGS = ["article", "aside", "footer", "header", "main", "nav"];
2614
- var listContract = closedContract([
2615
- { name: "list-item", match: isTag("li", ...METADATA_TAGS) }
2616
- ]);
2617
- var tableContract = closedContract([
2618
- firstOptional("caption", "caption"),
2619
- { name: "colgroup", match: isTag("colgroup") },
2620
- { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
2621
- { name: "tbody", match: isTag("tbody") },
2622
- { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
2623
- { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2624
- ]);
2625
- var tableBodyContract = closedContract([
2626
- { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2627
- ]);
2628
- var tableRowContract = closedContract([
2629
- { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
2630
- ]);
2631
- var colgroupContract = closedContract([
2632
- { name: "column", match: isTag("col", "template") }
2708
+ function getChildProp(child, key) {
2709
+ if (!isVNodeLike(child)) return void 0;
2710
+ const { props } = child;
2711
+ if (!isObject(props)) return void 0;
2712
+ return props[key];
2713
+ }
2714
+
2715
+ // ../core/src/html/contracts/aria/landmarks.ts
2716
+ var landmarkContract = ariaContract([landmarkRoleRule, landmarkAccessibleNameRule]);
2717
+
2718
+ // ../core/src/html/contracts/aria/widgets.ts
2719
+ var dialogContract = ariaContract([requireAccessibleName]);
2720
+ var menuContract = ariaContract([requireAccessibleName]);
2721
+ var menubarContract = ariaContract([requireAccessibleName]);
2722
+ var treeContract = ariaContract([requireAccessibleName]);
2723
+ var gridContract = ariaContract([requireAccessibleName]);
2724
+ var listboxContract = ariaContract([requireAccessibleName]);
2725
+ var tablistContract = ariaContract([requireAccessibleName]);
2726
+ var radiogroupContract = ariaContract([requireAccessibleName]);
2727
+
2728
+ // ../core/src/html/contracts/html/document.ts
2729
+ var headContract = closedContract([
2730
+ {
2731
+ name: "metadata",
2732
+ match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
2733
+ }
2633
2734
  ]);
2634
- var dlContract = closedContract([
2635
- { name: "term", match: isTag("dt") },
2636
- { name: "description", match: isTag("dd") },
2637
- { name: "group", match: isTag("div") },
2638
- metadata()
2735
+ var htmlContract = closedContract([
2736
+ { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
2737
+ { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
2639
2738
  ]);
2739
+ var voidContract = contract([], { exclusiveChildren: true, allowText: false });
2740
+ var textOnlyContract = contract([], { exclusiveChildren: true });
2741
+
2742
+ // ../core/src/html/contracts/html/forms.ts
2640
2743
  var selectContract = closedContract([
2641
2744
  { name: "option", match: isTag("option", "optgroup", "hr", ...METADATA_TAGS) }
2642
2745
  ]);
@@ -2646,6 +2749,45 @@ var optgroupContract = closedContract([
2646
2749
  var datalistContract = closedContract([
2647
2750
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
2648
2751
  ]);
2752
+ var buttonContract = closedContract([
2753
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2754
+ ]);
2755
+ var anchorContract = closedContract([
2756
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2757
+ ]);
2758
+ var labelableTagMatch = isTag(...LABELABLE_TAGS);
2759
+ function isLabelableControl(child) {
2760
+ if (!labelableTagMatch(child)) return false;
2761
+ if (getTag(child) !== "input") return true;
2762
+ const type = getChildProp(child, "type");
2763
+ return !isString(type) || type !== "hidden";
2764
+ }
2765
+ var labelContract = contract([
2766
+ { name: "control", match: isLabelableControl, cardinality: { max: 1 } },
2767
+ { name: "nested-label", match: isTag("label"), cardinality: { max: 0 } },
2768
+ {
2769
+ name: "other-interactive-content",
2770
+ match: isTag(...OTHER_INTERACTIVE_TAGS),
2771
+ cardinality: { max: 0 }
2772
+ }
2773
+ ]);
2774
+
2775
+ // ../core/src/html/contracts/html/grouping.ts
2776
+ var detailsContract = firstChildContract("summary", "summary");
2777
+ var fieldsetContract = firstChildContract("legend", "legend");
2778
+
2779
+ // ../core/src/html/contracts/html/lists.ts
2780
+ var listContract = closedContract([
2781
+ { name: "list-item", match: isTag("li", ...METADATA_TAGS) }
2782
+ ]);
2783
+ var dlContract = closedContract([
2784
+ { name: "term", match: isTag("dt") },
2785
+ { name: "description", match: isTag("dd") },
2786
+ { name: "group", match: isTag("div") },
2787
+ metadata()
2788
+ ]);
2789
+
2790
+ // ../core/src/html/contracts/html/media.ts
2649
2791
  var pictureContract = closedContract([
2650
2792
  { name: "source", match: isTag("source", ...METADATA_TAGS) },
2651
2793
  { name: "image", match: isTag("img"), cardinality: { min: 1, max: 1 }, position: "last" }
@@ -2654,91 +2796,49 @@ var figureContract = contract([
2654
2796
  { name: "caption", match: isTag("figcaption"), cardinality: { max: 1 } },
2655
2797
  { name: "content", match: isOpenContent("figcaption") }
2656
2798
  ]);
2657
- var detailsContract = firstChildContract("summary", "summary");
2658
- var fieldsetContract = firstChildContract("legend", "legend");
2659
2799
  var objectContract = contract([
2660
2800
  { name: "param", match: isTag("param") },
2661
2801
  { name: "content", match: isOpenContent("param") }
2662
2802
  ]);
2663
- var INTERACTIVE_CONTENT_TAGS = ["a", "button", "input", "select", "textarea", "label"];
2664
- var buttonContract = closedContract([
2665
- { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2666
- ]);
2667
- var anchorContract = closedContract([
2668
- { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2669
- ]);
2670
- var LABELABLE_TAGS = [
2671
- "button",
2672
- "input",
2673
- "meter",
2674
- "output",
2675
- "progress",
2676
- "select",
2677
- "textarea"
2678
- ];
2679
- var labelContract = contract([
2680
- { name: "control", match: isTag(...LABELABLE_TAGS), cardinality: { max: 1 } }
2681
- ]);
2682
- var P_BLOCKED_TAGS = [
2683
- "address",
2684
- "article",
2685
- "aside",
2686
- "blockquote",
2687
- "details",
2688
- "dialog",
2689
- "div",
2690
- "dl",
2691
- "fieldset",
2692
- "figure",
2693
- "footer",
2694
- "form",
2695
- "h1",
2696
- "h2",
2697
- "h3",
2698
- "h4",
2699
- "h5",
2700
- "h6",
2701
- "header",
2702
- "hr",
2703
- "main",
2704
- "nav",
2705
- "ol",
2706
- "p",
2707
- "pre",
2708
- "section",
2709
- "table",
2710
- "ul"
2711
- ];
2712
- var pContract = closedContract([
2713
- { name: "content", match: isOpenContent(...P_BLOCKED_TAGS) }
2714
- ]);
2715
2803
  var mediaContract = contract([
2716
2804
  { name: "source", match: isTag("source") },
2717
2805
  { name: "track", match: isTag("track") },
2718
2806
  metadata(),
2719
2807
  { name: "content", match: isOpenContent("source", "track", ...METADATA_TAGS) }
2720
2808
  ]);
2721
- var headContract = closedContract([
2722
- {
2723
- name: "metadata",
2724
- match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
2725
- }
2809
+
2810
+ // ../core/src/html/contracts/html/tables.ts
2811
+ var tableContract = closedContract([
2812
+ firstOptional("caption", "caption"),
2813
+ { name: "colgroup", match: isTag("colgroup") },
2814
+ { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
2815
+ { name: "tbody", match: isTag("tbody") },
2816
+ { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
2817
+ { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2726
2818
  ]);
2727
- var htmlContract = closedContract([
2728
- { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
2729
- { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
2819
+ var tableBodyContract = closedContract([
2820
+ { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2730
2821
  ]);
2731
- var voidContract = contract([], { exclusiveChildren: true, allowText: false });
2732
- var textOnlyContract = contract([], { exclusiveChildren: true });
2733
- var landmarkContract = ariaContract([landmarkRoleRule, landmarkNameAdvisory]);
2734
- var dialogContract = ariaContract([requireAccessibleName]);
2735
- var menuContract = ariaContract([requireAccessibleName]);
2736
- var menubarContract = ariaContract([requireAccessibleName]);
2737
- var treeContract = ariaContract([requireAccessibleName]);
2738
- var gridContract = ariaContract([requireAccessibleName]);
2739
- var listboxContract = ariaContract([requireAccessibleName]);
2740
- var tablistContract = ariaContract([requireAccessibleName]);
2741
- var radiogroupContract = ariaContract([requireAccessibleName]);
2822
+ var tableRowContract = closedContract([
2823
+ { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
2824
+ ]);
2825
+ var colgroupContract = closedContract([
2826
+ { name: "column", match: isTag("col", "template") }
2827
+ ]);
2828
+
2829
+ // ../core/src/html/contracts/html/text.ts
2830
+ var pContract = closedContract([
2831
+ { name: "content", match: isOpenContent(...P_BLOCKED_TAGS) }
2832
+ ]);
2833
+
2834
+ // ../core/src/html/contracts/build-map.ts
2835
+ function buildMap(groups) {
2836
+ return Object.fromEntries(
2837
+ groups.flatMap(([keys2, value]) => keys2.map((key) => [key, value]))
2838
+ );
2839
+ }
2840
+
2841
+ // ../core/src/html/contracts/maps.ts
2742
2842
  var CONTRACT_GROUPS = [
2743
2843
  [VOID_TAGS, voidContract],
2744
2844
  [TEXT_ONLY_TAGS, textOnlyContract],
@@ -2747,13 +2847,8 @@ var CONTRACT_GROUPS = [
2747
2847
  [["audio", "video"], mediaContract],
2748
2848
  [["thead", "tbody", "tfoot"], tableBodyContract]
2749
2849
  ];
2750
- function contractMap(groups) {
2751
- return Object.fromEntries(
2752
- groups.flatMap(([tags, enforcement]) => tags.map((tag) => [tag, enforcement]))
2753
- );
2754
- }
2755
2850
  var htmlContracts = {
2756
- ...contractMap(CONTRACT_GROUPS),
2851
+ ...buildMap(CONTRACT_GROUPS),
2757
2852
  table: tableContract,
2758
2853
  tr: tableRowContract,
2759
2854
  colgroup: colgroupContract,
@@ -2997,6 +3092,11 @@ function composeNormalizers(normalizers, fn) {
2997
3092
  function whenDefined(key, value) {
2998
3093
  return value === void 0 ? {} : { [key]: value };
2999
3094
  }
3095
+ function mergeAriaRules(aria, rules) {
3096
+ if (!aria?.length) return rules;
3097
+ if (!rules?.length) return aria;
3098
+ return [...aria, ...rules];
3099
+ }
3000
3100
  function resolveFactoryOptions(options = {}) {
3001
3101
  const { styling, enforcement } = options;
3002
3102
  const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
@@ -3017,7 +3117,7 @@ function resolveFactoryOptions(options = {}) {
3017
3117
  ...whenDefined("defaultVariants", styling?.defaults),
3018
3118
  ...whenDefined("compoundVariants", styling?.compounds),
3019
3119
  ...whenDefined("normalizeFn", composedNormalizeFn),
3020
- ...whenDefined("ariaRules", enforcement?.aria),
3120
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
3021
3121
  ...whenDefined("childRules", enforcement?.children),
3022
3122
  ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
3023
3123
  ...whenDefined("allowText", enforcement?.allowText),
@@ -249,6 +249,16 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
249
249
  */
250
250
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
251
251
  readonly aria?: readonly AriaRule[];
252
+ /**
253
+ * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
254
+ * (`AriaRule`'s `readsProps`, fixable `AriaFix` results) but have no
255
+ * relationship to ARIA semantics — an HTML fact or a security check like a
256
+ * dangerous-URL-scheme guard, for example. Evaluated together with `aria`
257
+ * (both run through the same engine, merged into one rule set) — this is a
258
+ * separate bucket purely so a non-ARIA rule doesn't have to sit under the
259
+ * misleading `aria` name to get the machinery it needs.
260
+ */
261
+ readonly rules?: readonly AriaRule[];
252
262
  readonly children?: readonly ChildRuleInput[];
253
263
  /**
254
264
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -337,6 +347,15 @@ type PolymorphicWithAsChild<G extends PolymorphicGenerics, TAs extends ElementTy
337
347
  type PolymorphicComponent<G extends PolymorphicGenerics> = {
338
348
  <TAs extends ElementType = DefaultOf<G>>(props: PolymorphicWithAsChild<G, TAs>): AnyVNode;
339
349
  <TAs extends ElementType = DefaultOf<G>>(props: PolymorphicProps<G, TAs>): AnyVNode;
350
+ /**
351
+ * Non-generic fallback overload used for type extraction.
352
+ *
353
+ * TypeScript resolves conditional types such as
354
+ * `ComponentProps<typeof Component>` against only the final overload.
355
+ * Anchoring that overload to the default element preserves correct prop
356
+ * inference for tools such as Storybook and `ComponentProps`.
357
+ */
358
+ (props: PolymorphicProps<G, DefaultOf<G>>): AnyVNode;
340
359
  displayName?: string;
341
360
  };
342
361