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.
@@ -20,6 +20,9 @@ function isNull(value) {
20
20
  function isNonNull(value) {
21
21
  return value != null;
22
22
  }
23
+ function isNullish(value) {
24
+ return isNull(value) || value === void 0;
25
+ }
23
26
 
24
27
  // ../../lib/primitive/src/utils/type-guards.ts
25
28
  function isObject(value, excludeArrays = false) {
@@ -607,20 +610,29 @@ function isAriaAttributeValidForRole(attr, role) {
607
610
  }
608
611
 
609
612
  // ../../lib/primitive/src/guards/aria/is-aria-role.ts
613
+ function lookupImplicitRole(tag) {
614
+ return IMPLICIT_ROLE_RECORD[tag];
615
+ }
610
616
  function isStrongImplicitRole(tag) {
611
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
612
- return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
617
+ const role = lookupImplicitRole(tag);
618
+ return !isNullish(role) && STRONG_ROLES_SET.has(role);
613
619
  }
614
- function isStandaloneTag(tag) {
615
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
616
- return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
620
+ function hasStandaloneRole(tag) {
621
+ const role = lookupImplicitRole(tag);
622
+ return !isNullish(role) && STANDALONE_ROLES_SET.has(role);
617
623
  }
618
- function getInputImplicitRole(type) {
619
- if (!isString(type) || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
620
- return INPUT_TYPE_ROLE_MAP[type];
624
+ var LIST_ELIGIBLE_INPUT_TYPES = /* @__PURE__ */ new Set(["text", "search", "tel", "url", "email"]);
625
+ function getInputImplicitRole(type, list) {
626
+ if (!isString(type)) return void 0;
627
+ const role = INPUT_TYPE_ROLE_MAP[type];
628
+ if (!role) return void 0;
629
+ if (!isNullish(list) && LIST_ELIGIBLE_INPUT_TYPES.has(type)) {
630
+ return "combobox";
631
+ }
632
+ return role;
621
633
  }
622
634
  function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
623
- const isNamed = isString(ariaLabel) || isString(ariaLabelledBy);
635
+ const isNamed = isString(ariaLabel) && ariaLabel.trim().length > 0 || isString(ariaLabelledBy) && ariaLabelledBy.trim().length > 0;
624
636
  if (!isNamed) return void 0;
625
637
  if (tag === "section") return "region";
626
638
  if (tag === "form") return "form";
@@ -681,7 +693,7 @@ function defineContractComponent(options) {
681
693
  // ../../lib/contract/src/aria/aria-role-policy.ts
682
694
  function getImplicitRole(tag, props) {
683
695
  if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
684
- if (tag === "input") return getInputImplicitRole(props?.type);
696
+ if (tag === "input") return getInputImplicitRole(props?.type, props?.list);
685
697
  if (tag === "img") return props?.alt === "" ? "none" : "img";
686
698
  if (tag === "section" || tag === "form") {
687
699
  return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
@@ -1370,7 +1382,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1370
1382
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1371
1383
  const implicitRole = getImplicitRole(tag, props);
1372
1384
  const hasRole2 = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1373
- if (!hasRole2) return { proceed: false, result: { props, violations: [] } };
1374
1385
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1375
1386
  const workingProps = normalized.normalized ? normalized.result.props : props;
1376
1387
  const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
@@ -1380,6 +1391,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1380
1391
  tag,
1381
1392
  implicitRole,
1382
1393
  effectiveRole,
1394
+ hasRole: hasRole2,
1383
1395
  props: workingProps,
1384
1396
  preExistingViolations,
1385
1397
  context: { tag, props: workingProps, implicitRole, effectiveRole }
@@ -1423,6 +1435,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1423
1435
  static evaluate(tag, props) {
1424
1436
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1425
1437
  if (!derived.proceed) return derived.result;
1438
+ if (!derived.hasRole)
1439
+ return { props: derived.props, violations: [...derived.preExistingViolations] };
1426
1440
  const {
1427
1441
  tag: narrowedTag,
1428
1442
  implicitRole,
@@ -1447,10 +1461,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1447
1461
  props: workingProps,
1448
1462
  preExistingViolations
1449
1463
  } = derived;
1450
- const { violations, fixes } = _AriaPolicyEngine.#runRules(
1451
- [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1452
- context
1453
- );
1464
+ const rules = derived.hasRole ? [..._AriaPolicyEngine.#getRules(context), ...extraRules] : extraRules;
1465
+ const { violations, fixes } = _AriaPolicyEngine.#runRules(rules, context);
1454
1466
  const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1455
1467
  return { props: next, violations: [...preExistingViolations, ...violations] };
1456
1468
  }
@@ -1649,7 +1661,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1649
1661
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1650
1662
  const role = props.role;
1651
1663
  if (role !== "region") return NO_VIOLATIONS2;
1652
- if (!isStandaloneTag(tag)) return NO_VIOLATIONS2;
1664
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS2;
1653
1665
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1654
1666
  return [
1655
1667
  {
@@ -2232,6 +2244,93 @@ var readonlyProps = ({
2232
2244
  // ../core/src/html/evaluators.ts
2233
2245
  import { warnDiagnostics as warnDiagnostics2 } from "../_shared/diagnostics.js";
2234
2246
 
2247
+ // ../core/src/html/contracts/categories.ts
2248
+ var METADATA_TAGS = ["script", "template"];
2249
+ var VOID_TAGS = [
2250
+ "area",
2251
+ "base",
2252
+ "br",
2253
+ "col",
2254
+ "embed",
2255
+ "hr",
2256
+ "img",
2257
+ "input",
2258
+ "link",
2259
+ "meta",
2260
+ "param",
2261
+ "source",
2262
+ "track",
2263
+ "wbr"
2264
+ ];
2265
+ var TEXT_ONLY_TAGS = [
2266
+ "option",
2267
+ "script",
2268
+ "style",
2269
+ "textarea",
2270
+ "title"
2271
+ ];
2272
+ var LANDMARK_TAGS = [
2273
+ "article",
2274
+ "aside",
2275
+ "footer",
2276
+ "header",
2277
+ "main",
2278
+ "nav"
2279
+ ];
2280
+ var INTERACTIVE_CONTENT_TAGS = [
2281
+ "a",
2282
+ "button",
2283
+ "input",
2284
+ "select",
2285
+ "textarea",
2286
+ "label"
2287
+ ];
2288
+ var LABELABLE_TAGS = [
2289
+ "button",
2290
+ "input",
2291
+ "meter",
2292
+ "output",
2293
+ "progress",
2294
+ "select",
2295
+ "textarea"
2296
+ ];
2297
+ var OTHER_INTERACTIVE_TAGS = [
2298
+ "a",
2299
+ "details",
2300
+ "embed",
2301
+ "iframe"
2302
+ ];
2303
+ var P_BLOCKED_TAGS = [
2304
+ "address",
2305
+ "article",
2306
+ "aside",
2307
+ "blockquote",
2308
+ "details",
2309
+ "dialog",
2310
+ "div",
2311
+ "dl",
2312
+ "fieldset",
2313
+ "figure",
2314
+ "footer",
2315
+ "form",
2316
+ "h1",
2317
+ "h2",
2318
+ "h3",
2319
+ "h4",
2320
+ "h5",
2321
+ "h6",
2322
+ "header",
2323
+ "hr",
2324
+ "main",
2325
+ "nav",
2326
+ "ol",
2327
+ "p",
2328
+ "pre",
2329
+ "section",
2330
+ "table",
2331
+ "ul"
2332
+ ];
2333
+
2235
2334
  // ../core/src/html/spec/vocabulary/input.ts
2236
2335
  var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2237
2336
  var NUMERIC_INPUT_TYPES = [
@@ -2611,7 +2710,11 @@ var ALLOWED_ROLES = {
2611
2710
  "treeitem"
2612
2711
  ],
2613
2712
  dialog: ["alertdialog"],
2614
- fieldset: ["none", "presentation", "radiogroup"]
2713
+ fieldset: ["none", "presentation", "radiogroup"],
2714
+ // `<label>` has no implicit role and no documented alternates — its native labeling
2715
+ // semantics (control association, accessible-name contribution) aren't reproducible via
2716
+ // ARIA, so any explicit `role` should be avoided rather than substituted.
2717
+ label: []
2615
2718
  };
2616
2719
  var ELEMENT_SPECS = {
2617
2720
  input: inputElementSpec,
@@ -2652,7 +2755,10 @@ var roleNotPermittedRule = Object.assign(
2652
2755
  );
2653
2756
 
2654
2757
  // ../core/src/html/aria-rules.ts
2655
- var LANDMARK_TAG_SET = /* @__PURE__ */ new Set(["article", "aside", "footer", "header", "main", "nav"]);
2758
+ function defineAriaRule(tags, rule) {
2759
+ return Object.assign(rule, { tags });
2760
+ }
2761
+ var LANDMARK_TAG_SET = new Set(LANDMARK_TAGS);
2656
2762
  var removeLandmarkRoleOverride = {
2657
2763
  kind: "removeRole",
2658
2764
  apply: ({ props }) => {
@@ -2661,10 +2767,11 @@ var removeLandmarkRoleOverride = {
2661
2767
  return { applied: true, next: rest, previous: props };
2662
2768
  }
2663
2769
  };
2664
- var landmarkRoleRule = Object.assign(
2770
+ var landmarkRoleRule = defineAriaRule(
2771
+ LANDMARK_TAGS,
2665
2772
  ({ tag, props, implicitRole }) => {
2666
2773
  if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2667
- const role = props.role;
2774
+ const { role } = props;
2668
2775
  if (!role || role === implicitRole) return [];
2669
2776
  const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2670
2777
  return [
@@ -2676,8 +2783,7 @@ var landmarkRoleRule = Object.assign(
2676
2783
  diagnostic
2677
2784
  }
2678
2785
  ];
2679
- },
2680
- { tags: [...LANDMARK_TAG_SET] }
2786
+ }
2681
2787
  );
2682
2788
  function requireAccessibleName({ tag, props }) {
2683
2789
  if ("aria-label" in props || "aria-labelledby" in props) return [];
@@ -2690,38 +2796,44 @@ function requireAccessibleName({ tag, props }) {
2690
2796
  }
2691
2797
  ];
2692
2798
  }
2693
- var NAMED_LANDMARK_TAGS = /* @__PURE__ */ new Set(["nav", "aside"]);
2694
- var landmarkNameAdvisory = Object.assign(
2799
+ var NAMED_LANDMARK_TAGS = ["nav", "aside"];
2800
+ var NAMED_LANDMARK_TAG_SET = new Set(NAMED_LANDMARK_TAGS);
2801
+ var landmarkAccessibleNameRule = defineAriaRule(
2802
+ NAMED_LANDMARK_TAGS,
2695
2803
  (ctx) => {
2696
- if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2804
+ if (!ctx.implicitRole || !NAMED_LANDMARK_TAG_SET.has(ctx.tag)) return [];
2697
2805
  return requireAccessibleName(ctx);
2698
- },
2699
- { tags: [...NAMED_LANDMARK_TAGS] }
2806
+ }
2700
2807
  );
2701
2808
  var HTML_ARIA_RULES = [
2702
2809
  landmarkRoleRule,
2703
- landmarkNameAdvisory,
2810
+ landmarkAccessibleNameRule,
2704
2811
  roleNotPermittedRule,
2705
2812
  ...INPUT_RULES
2706
2813
  ];
2707
2814
 
2708
- // ../core/src/html/contracts.ts
2815
+ // ../core/src/html/contracts/helpers.ts
2709
2816
  import { warnDiagnostics } from "../_shared/diagnostics.js";
2817
+ function isVNodeLike(child) {
2818
+ return isObject(child) && "type" in child;
2819
+ }
2710
2820
  function isOpenContent(...blockedTags) {
2711
2821
  const set2 = new Set(blockedTags);
2712
2822
  return (child) => {
2713
- if (!isObject(child) || !("type" in child)) return false;
2823
+ if (!isVNodeLike(child)) return false;
2714
2824
  const tag = getTag(child);
2715
2825
  return tag === void 0 || !set2.has(tag);
2716
2826
  };
2717
2827
  }
2718
- var METADATA_TAGS = ["script", "template"];
2719
2828
  var metadataMatch = isTag(...METADATA_TAGS);
2720
2829
  function metadata(name = "metadata") {
2721
2830
  return { name, match: metadataMatch };
2722
2831
  }
2832
+ function optional(name, tag) {
2833
+ return { name, match: isTag(tag), cardinality: { max: 1 } };
2834
+ }
2723
2835
  function firstOptional(name, tag) {
2724
- return { name, match: isTag(tag), cardinality: { max: 1 }, position: "first" };
2836
+ return { ...optional(name, tag), position: "first" };
2725
2837
  }
2726
2838
  function contract(children, options) {
2727
2839
  return { diagnostics: warnDiagnostics, children, ...options };
@@ -2735,50 +2847,41 @@ function ariaContract(aria) {
2735
2847
  function firstChildContract(name, tag) {
2736
2848
  return contract([firstOptional(name, tag), { name: "content", match: isOpenContent(tag) }]);
2737
2849
  }
2738
- var VOID_TAGS = [
2739
- "area",
2740
- "base",
2741
- "br",
2742
- "col",
2743
- "embed",
2744
- "hr",
2745
- "img",
2746
- "input",
2747
- "link",
2748
- "meta",
2749
- "param",
2750
- "source",
2751
- "track",
2752
- "wbr"
2753
- ];
2754
- var TEXT_ONLY_TAGS = ["option", "script", "style", "textarea", "title"];
2755
- var LANDMARK_TAGS = ["article", "aside", "footer", "header", "main", "nav"];
2756
- var listContract = closedContract([
2757
- { name: "list-item", match: isTag("li", ...METADATA_TAGS) }
2758
- ]);
2759
- var tableContract = closedContract([
2760
- firstOptional("caption", "caption"),
2761
- { name: "colgroup", match: isTag("colgroup") },
2762
- { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
2763
- { name: "tbody", match: isTag("tbody") },
2764
- { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
2765
- { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2766
- ]);
2767
- var tableBodyContract = closedContract([
2768
- { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2769
- ]);
2770
- var tableRowContract = closedContract([
2771
- { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
2772
- ]);
2773
- var colgroupContract = closedContract([
2774
- { name: "column", match: isTag("col", "template") }
2850
+ function getChildProp(child, key) {
2851
+ if (!isVNodeLike(child)) return void 0;
2852
+ const { props } = child;
2853
+ if (!isObject(props)) return void 0;
2854
+ return props[key];
2855
+ }
2856
+
2857
+ // ../core/src/html/contracts/aria/landmarks.ts
2858
+ var landmarkContract = ariaContract([landmarkRoleRule, landmarkAccessibleNameRule]);
2859
+
2860
+ // ../core/src/html/contracts/aria/widgets.ts
2861
+ var dialogContract = ariaContract([requireAccessibleName]);
2862
+ var menuContract = ariaContract([requireAccessibleName]);
2863
+ var menubarContract = ariaContract([requireAccessibleName]);
2864
+ var treeContract = ariaContract([requireAccessibleName]);
2865
+ var gridContract = ariaContract([requireAccessibleName]);
2866
+ var listboxContract = ariaContract([requireAccessibleName]);
2867
+ var tablistContract = ariaContract([requireAccessibleName]);
2868
+ var radiogroupContract = ariaContract([requireAccessibleName]);
2869
+
2870
+ // ../core/src/html/contracts/html/document.ts
2871
+ var headContract = closedContract([
2872
+ {
2873
+ name: "metadata",
2874
+ match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
2875
+ }
2775
2876
  ]);
2776
- var dlContract = closedContract([
2777
- { name: "term", match: isTag("dt") },
2778
- { name: "description", match: isTag("dd") },
2779
- { name: "group", match: isTag("div") },
2780
- metadata()
2877
+ var htmlContract = closedContract([
2878
+ { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
2879
+ { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
2781
2880
  ]);
2881
+ var voidContract = contract([], { exclusiveChildren: true, allowText: false });
2882
+ var textOnlyContract = contract([], { exclusiveChildren: true });
2883
+
2884
+ // ../core/src/html/contracts/html/forms.ts
2782
2885
  var selectContract = closedContract([
2783
2886
  { name: "option", match: isTag("option", "optgroup", "hr", ...METADATA_TAGS) }
2784
2887
  ]);
@@ -2788,6 +2891,45 @@ var optgroupContract = closedContract([
2788
2891
  var datalistContract = closedContract([
2789
2892
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
2790
2893
  ]);
2894
+ var buttonContract = closedContract([
2895
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2896
+ ]);
2897
+ var anchorContract = closedContract([
2898
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2899
+ ]);
2900
+ var labelableTagMatch = isTag(...LABELABLE_TAGS);
2901
+ function isLabelableControl(child) {
2902
+ if (!labelableTagMatch(child)) return false;
2903
+ if (getTag(child) !== "input") return true;
2904
+ const type = getChildProp(child, "type");
2905
+ return !isString(type) || type !== "hidden";
2906
+ }
2907
+ var labelContract = contract([
2908
+ { name: "control", match: isLabelableControl, cardinality: { max: 1 } },
2909
+ { name: "nested-label", match: isTag("label"), cardinality: { max: 0 } },
2910
+ {
2911
+ name: "other-interactive-content",
2912
+ match: isTag(...OTHER_INTERACTIVE_TAGS),
2913
+ cardinality: { max: 0 }
2914
+ }
2915
+ ]);
2916
+
2917
+ // ../core/src/html/contracts/html/grouping.ts
2918
+ var detailsContract = firstChildContract("summary", "summary");
2919
+ var fieldsetContract = firstChildContract("legend", "legend");
2920
+
2921
+ // ../core/src/html/contracts/html/lists.ts
2922
+ var listContract = closedContract([
2923
+ { name: "list-item", match: isTag("li", ...METADATA_TAGS) }
2924
+ ]);
2925
+ var dlContract = closedContract([
2926
+ { name: "term", match: isTag("dt") },
2927
+ { name: "description", match: isTag("dd") },
2928
+ { name: "group", match: isTag("div") },
2929
+ metadata()
2930
+ ]);
2931
+
2932
+ // ../core/src/html/contracts/html/media.ts
2791
2933
  var pictureContract = closedContract([
2792
2934
  { name: "source", match: isTag("source", ...METADATA_TAGS) },
2793
2935
  { name: "image", match: isTag("img"), cardinality: { min: 1, max: 1 }, position: "last" }
@@ -2796,91 +2938,49 @@ var figureContract = contract([
2796
2938
  { name: "caption", match: isTag("figcaption"), cardinality: { max: 1 } },
2797
2939
  { name: "content", match: isOpenContent("figcaption") }
2798
2940
  ]);
2799
- var detailsContract = firstChildContract("summary", "summary");
2800
- var fieldsetContract = firstChildContract("legend", "legend");
2801
2941
  var objectContract = contract([
2802
2942
  { name: "param", match: isTag("param") },
2803
2943
  { name: "content", match: isOpenContent("param") }
2804
2944
  ]);
2805
- var INTERACTIVE_CONTENT_TAGS = ["a", "button", "input", "select", "textarea", "label"];
2806
- var buttonContract = closedContract([
2807
- { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2808
- ]);
2809
- var anchorContract = closedContract([
2810
- { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2811
- ]);
2812
- var LABELABLE_TAGS = [
2813
- "button",
2814
- "input",
2815
- "meter",
2816
- "output",
2817
- "progress",
2818
- "select",
2819
- "textarea"
2820
- ];
2821
- var labelContract = contract([
2822
- { name: "control", match: isTag(...LABELABLE_TAGS), cardinality: { max: 1 } }
2823
- ]);
2824
- var P_BLOCKED_TAGS = [
2825
- "address",
2826
- "article",
2827
- "aside",
2828
- "blockquote",
2829
- "details",
2830
- "dialog",
2831
- "div",
2832
- "dl",
2833
- "fieldset",
2834
- "figure",
2835
- "footer",
2836
- "form",
2837
- "h1",
2838
- "h2",
2839
- "h3",
2840
- "h4",
2841
- "h5",
2842
- "h6",
2843
- "header",
2844
- "hr",
2845
- "main",
2846
- "nav",
2847
- "ol",
2848
- "p",
2849
- "pre",
2850
- "section",
2851
- "table",
2852
- "ul"
2853
- ];
2854
- var pContract = closedContract([
2855
- { name: "content", match: isOpenContent(...P_BLOCKED_TAGS) }
2856
- ]);
2857
2945
  var mediaContract = contract([
2858
2946
  { name: "source", match: isTag("source") },
2859
2947
  { name: "track", match: isTag("track") },
2860
2948
  metadata(),
2861
2949
  { name: "content", match: isOpenContent("source", "track", ...METADATA_TAGS) }
2862
2950
  ]);
2863
- var headContract = closedContract([
2864
- {
2865
- name: "metadata",
2866
- match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
2867
- }
2951
+
2952
+ // ../core/src/html/contracts/html/tables.ts
2953
+ var tableContract = closedContract([
2954
+ firstOptional("caption", "caption"),
2955
+ { name: "colgroup", match: isTag("colgroup") },
2956
+ { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
2957
+ { name: "tbody", match: isTag("tbody") },
2958
+ { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
2959
+ { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2868
2960
  ]);
2869
- var htmlContract = closedContract([
2870
- { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
2871
- { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
2961
+ var tableBodyContract = closedContract([
2962
+ { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2872
2963
  ]);
2873
- var voidContract = contract([], { exclusiveChildren: true, allowText: false });
2874
- var textOnlyContract = contract([], { exclusiveChildren: true });
2875
- var landmarkContract = ariaContract([landmarkRoleRule, landmarkNameAdvisory]);
2876
- var dialogContract = ariaContract([requireAccessibleName]);
2877
- var menuContract = ariaContract([requireAccessibleName]);
2878
- var menubarContract = ariaContract([requireAccessibleName]);
2879
- var treeContract = ariaContract([requireAccessibleName]);
2880
- var gridContract = ariaContract([requireAccessibleName]);
2881
- var listboxContract = ariaContract([requireAccessibleName]);
2882
- var tablistContract = ariaContract([requireAccessibleName]);
2883
- var radiogroupContract = ariaContract([requireAccessibleName]);
2964
+ var tableRowContract = closedContract([
2965
+ { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
2966
+ ]);
2967
+ var colgroupContract = closedContract([
2968
+ { name: "column", match: isTag("col", "template") }
2969
+ ]);
2970
+
2971
+ // ../core/src/html/contracts/html/text.ts
2972
+ var pContract = closedContract([
2973
+ { name: "content", match: isOpenContent(...P_BLOCKED_TAGS) }
2974
+ ]);
2975
+
2976
+ // ../core/src/html/contracts/build-map.ts
2977
+ function buildMap(groups) {
2978
+ return Object.fromEntries(
2979
+ groups.flatMap(([keys2, value]) => keys2.map((key) => [key, value]))
2980
+ );
2981
+ }
2982
+
2983
+ // ../core/src/html/contracts/maps.ts
2884
2984
  var CONTRACT_GROUPS = [
2885
2985
  [VOID_TAGS, voidContract],
2886
2986
  [TEXT_ONLY_TAGS, textOnlyContract],
@@ -2889,13 +2989,8 @@ var CONTRACT_GROUPS = [
2889
2989
  [["audio", "video"], mediaContract],
2890
2990
  [["thead", "tbody", "tfoot"], tableBodyContract]
2891
2991
  ];
2892
- function contractMap(groups) {
2893
- return Object.fromEntries(
2894
- groups.flatMap(([tags, enforcement]) => tags.map((tag) => [tag, enforcement]))
2895
- );
2896
- }
2897
2992
  var htmlContracts = {
2898
- ...contractMap(CONTRACT_GROUPS),
2993
+ ...buildMap(CONTRACT_GROUPS),
2899
2994
  table: tableContract,
2900
2995
  tr: tableRowContract,
2901
2996
  colgroup: colgroupContract,
@@ -3139,6 +3234,11 @@ function composeNormalizers(normalizers, fn) {
3139
3234
  function whenDefined(key, value) {
3140
3235
  return value === void 0 ? {} : { [key]: value };
3141
3236
  }
3237
+ function mergeAriaRules(aria, rules) {
3238
+ if (!aria?.length) return rules;
3239
+ if (!rules?.length) return aria;
3240
+ return [...aria, ...rules];
3241
+ }
3142
3242
  function resolveFactoryOptions(options = {}) {
3143
3243
  const { styling, enforcement } = options;
3144
3244
  const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
@@ -3159,7 +3259,7 @@ function resolveFactoryOptions(options = {}) {
3159
3259
  ...whenDefined("defaultVariants", styling?.defaults),
3160
3260
  ...whenDefined("compoundVariants", styling?.compounds),
3161
3261
  ...whenDefined("normalizeFn", composedNormalizeFn),
3162
- ...whenDefined("ariaRules", enforcement?.aria),
3262
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
3163
3263
  ...whenDefined("childRules", enforcement?.children),
3164
3264
  ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
3165
3265
  ...whenDefined("allowText", enforcement?.allowText),
@@ -253,6 +253,16 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
253
253
  */
254
254
  readonly diagnostics?: Diagnostics | DiagnosticsMode;
255
255
  readonly aria?: readonly AriaRule[];
256
+ /**
257
+ * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
258
+ * (`AriaRule`'s `readsProps`, fixable `AriaFix` results) but have no
259
+ * relationship to ARIA semantics — an HTML fact or a security check like a
260
+ * dangerous-URL-scheme guard, for example. Evaluated together with `aria`
261
+ * (both run through the same engine, merged into one rule set) — this is a
262
+ * separate bucket purely so a non-ARIA rule doesn't have to sit under the
263
+ * misleading `aria` name to get the machinery it needs.
264
+ */
265
+ readonly rules?: readonly AriaRule[];
256
266
  readonly children?: readonly ChildRuleInput[];
257
267
  /**
258
268
  * When true, only children matching a `children` rule (or text, per `allowText`)