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.
@@ -28,6 +28,9 @@ function isNull(value) {
28
28
  function isNonNull(value) {
29
29
  return value != null;
30
30
  }
31
+ function isNullish(value) {
32
+ return isNull(value) || value === void 0;
33
+ }
31
34
 
32
35
  // ../../lib/primitive/src/utils/type-guards.ts
33
36
  function isObject(value, excludeArrays = false) {
@@ -655,20 +658,29 @@ function isAriaAttributeValidForRole(attr, role) {
655
658
  }
656
659
 
657
660
  // ../../lib/primitive/src/guards/aria/is-aria-role.ts
661
+ function lookupImplicitRole(tag) {
662
+ return IMPLICIT_ROLE_RECORD[tag];
663
+ }
658
664
  function isStrongImplicitRole(tag) {
659
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
660
- return STRONG_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
665
+ const role = lookupImplicitRole(tag);
666
+ return !isNullish(role) && STRONG_ROLES_SET.has(role);
661
667
  }
662
- function isStandaloneTag(tag) {
663
- if (!(tag in IMPLICIT_ROLE_RECORD)) return false;
664
- return STANDALONE_ROLES_SET.has(IMPLICIT_ROLE_RECORD[tag]);
668
+ function hasStandaloneRole(tag) {
669
+ const role = lookupImplicitRole(tag);
670
+ return !isNullish(role) && STANDALONE_ROLES_SET.has(role);
665
671
  }
666
- function getInputImplicitRole(type) {
667
- if (!isString(type) || !(type in INPUT_TYPE_ROLE_MAP)) return void 0;
668
- return INPUT_TYPE_ROLE_MAP[type];
672
+ var LIST_ELIGIBLE_INPUT_TYPES = /* @__PURE__ */ new Set(["text", "search", "tel", "url", "email"]);
673
+ function getInputImplicitRole(type, list) {
674
+ if (!isString(type)) return void 0;
675
+ const role = INPUT_TYPE_ROLE_MAP[type];
676
+ if (!role) return void 0;
677
+ if (!isNullish(list) && LIST_ELIGIBLE_INPUT_TYPES.has(type)) {
678
+ return "combobox";
679
+ }
680
+ return role;
669
681
  }
670
682
  function getConditionalImplicitRole(tag, ariaLabel, ariaLabelledBy) {
671
- const isNamed = isString(ariaLabel) || isString(ariaLabelledBy);
683
+ const isNamed = isString(ariaLabel) && ariaLabel.trim().length > 0 || isString(ariaLabelledBy) && ariaLabelledBy.trim().length > 0;
672
684
  if (!isNamed) return void 0;
673
685
  if (tag === "section") return "region";
674
686
  if (tag === "form") return "form";
@@ -720,7 +732,7 @@ function isTag(...args) {
720
732
  // ../../lib/contract/src/aria/aria-role-policy.ts
721
733
  function getImplicitRole(tag, props) {
722
734
  if (tag in IMPLICIT_ROLE_RECORD) return IMPLICIT_ROLE_RECORD[tag];
723
- if (tag === "input") return getInputImplicitRole(props?.type);
735
+ if (tag === "input") return getInputImplicitRole(props?.type, props?.list);
724
736
  if (tag === "img") return props?.alt === "" ? "none" : "img";
725
737
  if (tag === "section" || tag === "form") {
726
738
  return getConditionalImplicitRole(tag, props?.["aria-label"], props?.["aria-labelledby"]);
@@ -1409,7 +1421,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1409
1421
  if (!isIntrinsicTag(tag)) return { proceed: false, result: { props, violations: [] } };
1410
1422
  const implicitRole = getImplicitRole(tag, props);
1411
1423
  const hasRole2 = isNonNull(implicitRole) || isString(props.role) && props.role.length > 0;
1412
- if (!hasRole2) return { proceed: false, result: { props, violations: [] } };
1413
1424
  const normalized = _AriaPolicyEngine.#normalizeEmptyRole(tag, props);
1414
1425
  const workingProps = normalized.normalized ? normalized.result.props : props;
1415
1426
  const preExistingViolations = normalized.normalized ? normalized.result.violations : [];
@@ -1419,6 +1430,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1419
1430
  tag,
1420
1431
  implicitRole,
1421
1432
  effectiveRole,
1433
+ hasRole: hasRole2,
1422
1434
  props: workingProps,
1423
1435
  preExistingViolations,
1424
1436
  context: { tag, props: workingProps, implicitRole, effectiveRole }
@@ -1462,6 +1474,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1462
1474
  static evaluate(tag, props) {
1463
1475
  const derived = _AriaPolicyEngine.#deriveContext(tag, props);
1464
1476
  if (!derived.proceed) return derived.result;
1477
+ if (!derived.hasRole)
1478
+ return { props: derived.props, violations: [...derived.preExistingViolations] };
1465
1479
  const {
1466
1480
  tag: narrowedTag,
1467
1481
  implicitRole,
@@ -1486,10 +1500,8 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1486
1500
  props: workingProps,
1487
1501
  preExistingViolations
1488
1502
  } = derived;
1489
- const { violations, fixes } = _AriaPolicyEngine.#runRules(
1490
- [..._AriaPolicyEngine.#getRules(context), ...extraRules],
1491
- context
1492
- );
1503
+ const rules = derived.hasRole ? [..._AriaPolicyEngine.#getRules(context), ...extraRules] : extraRules;
1504
+ const { violations, fixes } = _AriaPolicyEngine.#runRules(rules, context);
1493
1505
  const next = _AriaPolicyEngine.#applyFixes(narrowedTag, implicitRole, workingProps, fixes);
1494
1506
  return { props: next, violations: [...preExistingViolations, ...violations] };
1495
1507
  }
@@ -1688,7 +1700,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1688
1700
  static #checkStandaloneRegion({ tag, props, implicitRole }) {
1689
1701
  const role = props.role;
1690
1702
  if (role !== "region") return NO_VIOLATIONS2;
1691
- if (!isStandaloneTag(tag)) return NO_VIOLATIONS2;
1703
+ if (!hasStandaloneRole(tag)) return NO_VIOLATIONS2;
1692
1704
  const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1693
1705
  return [
1694
1706
  {
@@ -2271,6 +2283,93 @@ var readonlyProps = ({
2271
2283
  // ../core/src/html/evaluators.ts
2272
2284
  import { warnDiagnostics as warnDiagnostics2 } from "./_shared/diagnostics.js";
2273
2285
 
2286
+ // ../core/src/html/contracts/categories.ts
2287
+ var METADATA_TAGS = ["script", "template"];
2288
+ var VOID_TAGS = [
2289
+ "area",
2290
+ "base",
2291
+ "br",
2292
+ "col",
2293
+ "embed",
2294
+ "hr",
2295
+ "img",
2296
+ "input",
2297
+ "link",
2298
+ "meta",
2299
+ "param",
2300
+ "source",
2301
+ "track",
2302
+ "wbr"
2303
+ ];
2304
+ var TEXT_ONLY_TAGS = [
2305
+ "option",
2306
+ "script",
2307
+ "style",
2308
+ "textarea",
2309
+ "title"
2310
+ ];
2311
+ var LANDMARK_TAGS = [
2312
+ "article",
2313
+ "aside",
2314
+ "footer",
2315
+ "header",
2316
+ "main",
2317
+ "nav"
2318
+ ];
2319
+ var INTERACTIVE_CONTENT_TAGS = [
2320
+ "a",
2321
+ "button",
2322
+ "input",
2323
+ "select",
2324
+ "textarea",
2325
+ "label"
2326
+ ];
2327
+ var LABELABLE_TAGS = [
2328
+ "button",
2329
+ "input",
2330
+ "meter",
2331
+ "output",
2332
+ "progress",
2333
+ "select",
2334
+ "textarea"
2335
+ ];
2336
+ var OTHER_INTERACTIVE_TAGS = [
2337
+ "a",
2338
+ "details",
2339
+ "embed",
2340
+ "iframe"
2341
+ ];
2342
+ var P_BLOCKED_TAGS = [
2343
+ "address",
2344
+ "article",
2345
+ "aside",
2346
+ "blockquote",
2347
+ "details",
2348
+ "dialog",
2349
+ "div",
2350
+ "dl",
2351
+ "fieldset",
2352
+ "figure",
2353
+ "footer",
2354
+ "form",
2355
+ "h1",
2356
+ "h2",
2357
+ "h3",
2358
+ "h4",
2359
+ "h5",
2360
+ "h6",
2361
+ "header",
2362
+ "hr",
2363
+ "main",
2364
+ "nav",
2365
+ "ol",
2366
+ "p",
2367
+ "pre",
2368
+ "section",
2369
+ "table",
2370
+ "ul"
2371
+ ];
2372
+
2274
2373
  // ../core/src/html/spec/vocabulary/input.ts
2275
2374
  var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2276
2375
  var NUMERIC_INPUT_TYPES = [
@@ -2650,7 +2749,11 @@ var ALLOWED_ROLES = {
2650
2749
  "treeitem"
2651
2750
  ],
2652
2751
  dialog: ["alertdialog"],
2653
- fieldset: ["none", "presentation", "radiogroup"]
2752
+ fieldset: ["none", "presentation", "radiogroup"],
2753
+ // `<label>` has no implicit role and no documented alternates — its native labeling
2754
+ // semantics (control association, accessible-name contribution) aren't reproducible via
2755
+ // ARIA, so any explicit `role` should be avoided rather than substituted.
2756
+ label: []
2654
2757
  };
2655
2758
  var ELEMENT_SPECS = {
2656
2759
  input: inputElementSpec,
@@ -2691,7 +2794,10 @@ var roleNotPermittedRule = Object.assign(
2691
2794
  );
2692
2795
 
2693
2796
  // ../core/src/html/aria-rules.ts
2694
- var LANDMARK_TAG_SET = /* @__PURE__ */ new Set(["article", "aside", "footer", "header", "main", "nav"]);
2797
+ function defineAriaRule(tags, rule) {
2798
+ return Object.assign(rule, { tags });
2799
+ }
2800
+ var LANDMARK_TAG_SET = new Set(LANDMARK_TAGS);
2695
2801
  var removeLandmarkRoleOverride = {
2696
2802
  kind: "removeRole",
2697
2803
  apply: ({ props }) => {
@@ -2700,10 +2806,11 @@ var removeLandmarkRoleOverride = {
2700
2806
  return { applied: true, next: rest, previous: props };
2701
2807
  }
2702
2808
  };
2703
- var landmarkRoleRule = Object.assign(
2809
+ var landmarkRoleRule = defineAriaRule(
2810
+ LANDMARK_TAGS,
2704
2811
  ({ tag, props, implicitRole }) => {
2705
2812
  if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
2706
- const role = props.role;
2813
+ const { role } = props;
2707
2814
  if (!role || role === implicitRole) return [];
2708
2815
  const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
2709
2816
  return [
@@ -2715,8 +2822,7 @@ var landmarkRoleRule = Object.assign(
2715
2822
  diagnostic
2716
2823
  }
2717
2824
  ];
2718
- },
2719
- { tags: [...LANDMARK_TAG_SET] }
2825
+ }
2720
2826
  );
2721
2827
  function requireAccessibleName({ tag, props }) {
2722
2828
  if ("aria-label" in props || "aria-labelledby" in props) return [];
@@ -2729,38 +2835,44 @@ function requireAccessibleName({ tag, props }) {
2729
2835
  }
2730
2836
  ];
2731
2837
  }
2732
- var NAMED_LANDMARK_TAGS = /* @__PURE__ */ new Set(["nav", "aside"]);
2733
- var landmarkNameAdvisory = Object.assign(
2838
+ var NAMED_LANDMARK_TAGS = ["nav", "aside"];
2839
+ var NAMED_LANDMARK_TAG_SET = new Set(NAMED_LANDMARK_TAGS);
2840
+ var landmarkAccessibleNameRule = defineAriaRule(
2841
+ NAMED_LANDMARK_TAGS,
2734
2842
  (ctx) => {
2735
- if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
2843
+ if (!ctx.implicitRole || !NAMED_LANDMARK_TAG_SET.has(ctx.tag)) return [];
2736
2844
  return requireAccessibleName(ctx);
2737
- },
2738
- { tags: [...NAMED_LANDMARK_TAGS] }
2845
+ }
2739
2846
  );
2740
2847
  var HTML_ARIA_RULES = [
2741
2848
  landmarkRoleRule,
2742
- landmarkNameAdvisory,
2849
+ landmarkAccessibleNameRule,
2743
2850
  roleNotPermittedRule,
2744
2851
  ...INPUT_RULES
2745
2852
  ];
2746
2853
 
2747
- // ../core/src/html/contracts.ts
2854
+ // ../core/src/html/contracts/helpers.ts
2748
2855
  import { warnDiagnostics } from "./_shared/diagnostics.js";
2856
+ function isVNodeLike(child) {
2857
+ return isObject(child) && "type" in child;
2858
+ }
2749
2859
  function isOpenContent(...blockedTags) {
2750
2860
  const set2 = new Set(blockedTags);
2751
2861
  return (child) => {
2752
- if (!isObject(child) || !("type" in child)) return false;
2862
+ if (!isVNodeLike(child)) return false;
2753
2863
  const tag = getTag(child);
2754
2864
  return tag === void 0 || !set2.has(tag);
2755
2865
  };
2756
2866
  }
2757
- var METADATA_TAGS = ["script", "template"];
2758
2867
  var metadataMatch = isTag(...METADATA_TAGS);
2759
2868
  function metadata(name = "metadata") {
2760
2869
  return { name, match: metadataMatch };
2761
2870
  }
2871
+ function optional(name, tag) {
2872
+ return { name, match: isTag(tag), cardinality: { max: 1 } };
2873
+ }
2762
2874
  function firstOptional(name, tag) {
2763
- return { name, match: isTag(tag), cardinality: { max: 1 }, position: "first" };
2875
+ return { ...optional(name, tag), position: "first" };
2764
2876
  }
2765
2877
  function contract(children, options) {
2766
2878
  return { diagnostics: warnDiagnostics, children, ...options };
@@ -2774,50 +2886,41 @@ function ariaContract(aria) {
2774
2886
  function firstChildContract(name, tag) {
2775
2887
  return contract([firstOptional(name, tag), { name: "content", match: isOpenContent(tag) }]);
2776
2888
  }
2777
- var VOID_TAGS = [
2778
- "area",
2779
- "base",
2780
- "br",
2781
- "col",
2782
- "embed",
2783
- "hr",
2784
- "img",
2785
- "input",
2786
- "link",
2787
- "meta",
2788
- "param",
2789
- "source",
2790
- "track",
2791
- "wbr"
2792
- ];
2793
- var TEXT_ONLY_TAGS = ["option", "script", "style", "textarea", "title"];
2794
- var LANDMARK_TAGS = ["article", "aside", "footer", "header", "main", "nav"];
2795
- var listContract = closedContract([
2796
- { name: "list-item", match: isTag("li", ...METADATA_TAGS) }
2797
- ]);
2798
- var tableContract = closedContract([
2799
- firstOptional("caption", "caption"),
2800
- { name: "colgroup", match: isTag("colgroup") },
2801
- { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
2802
- { name: "tbody", match: isTag("tbody") },
2803
- { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
2804
- { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2805
- ]);
2806
- var tableBodyContract = closedContract([
2807
- { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2808
- ]);
2809
- var tableRowContract = closedContract([
2810
- { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
2811
- ]);
2812
- var colgroupContract = closedContract([
2813
- { name: "column", match: isTag("col", "template") }
2889
+ function getChildProp(child, key) {
2890
+ if (!isVNodeLike(child)) return void 0;
2891
+ const { props } = child;
2892
+ if (!isObject(props)) return void 0;
2893
+ return props[key];
2894
+ }
2895
+
2896
+ // ../core/src/html/contracts/aria/landmarks.ts
2897
+ var landmarkContract = ariaContract([landmarkRoleRule, landmarkAccessibleNameRule]);
2898
+
2899
+ // ../core/src/html/contracts/aria/widgets.ts
2900
+ var dialogContract = ariaContract([requireAccessibleName]);
2901
+ var menuContract = ariaContract([requireAccessibleName]);
2902
+ var menubarContract = ariaContract([requireAccessibleName]);
2903
+ var treeContract = ariaContract([requireAccessibleName]);
2904
+ var gridContract = ariaContract([requireAccessibleName]);
2905
+ var listboxContract = ariaContract([requireAccessibleName]);
2906
+ var tablistContract = ariaContract([requireAccessibleName]);
2907
+ var radiogroupContract = ariaContract([requireAccessibleName]);
2908
+
2909
+ // ../core/src/html/contracts/html/document.ts
2910
+ var headContract = closedContract([
2911
+ {
2912
+ name: "metadata",
2913
+ match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
2914
+ }
2814
2915
  ]);
2815
- var dlContract = closedContract([
2816
- { name: "term", match: isTag("dt") },
2817
- { name: "description", match: isTag("dd") },
2818
- { name: "group", match: isTag("div") },
2819
- metadata()
2916
+ var htmlContract = closedContract([
2917
+ { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
2918
+ { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
2820
2919
  ]);
2920
+ var voidContract = contract([], { exclusiveChildren: true, allowText: false });
2921
+ var textOnlyContract = contract([], { exclusiveChildren: true });
2922
+
2923
+ // ../core/src/html/contracts/html/forms.ts
2821
2924
  var selectContract = closedContract([
2822
2925
  { name: "option", match: isTag("option", "optgroup", "hr", ...METADATA_TAGS) }
2823
2926
  ]);
@@ -2827,6 +2930,45 @@ var optgroupContract = closedContract([
2827
2930
  var datalistContract = closedContract([
2828
2931
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
2829
2932
  ]);
2933
+ var buttonContract = closedContract([
2934
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2935
+ ]);
2936
+ var anchorContract = closedContract([
2937
+ { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2938
+ ]);
2939
+ var labelableTagMatch = isTag(...LABELABLE_TAGS);
2940
+ function isLabelableControl(child) {
2941
+ if (!labelableTagMatch(child)) return false;
2942
+ if (getTag(child) !== "input") return true;
2943
+ const type = getChildProp(child, "type");
2944
+ return !isString(type) || type !== "hidden";
2945
+ }
2946
+ var labelContract = contract([
2947
+ { name: "control", match: isLabelableControl, cardinality: { max: 1 } },
2948
+ { name: "nested-label", match: isTag("label"), cardinality: { max: 0 } },
2949
+ {
2950
+ name: "other-interactive-content",
2951
+ match: isTag(...OTHER_INTERACTIVE_TAGS),
2952
+ cardinality: { max: 0 }
2953
+ }
2954
+ ]);
2955
+
2956
+ // ../core/src/html/contracts/html/grouping.ts
2957
+ var detailsContract = firstChildContract("summary", "summary");
2958
+ var fieldsetContract = firstChildContract("legend", "legend");
2959
+
2960
+ // ../core/src/html/contracts/html/lists.ts
2961
+ var listContract = closedContract([
2962
+ { name: "list-item", match: isTag("li", ...METADATA_TAGS) }
2963
+ ]);
2964
+ var dlContract = closedContract([
2965
+ { name: "term", match: isTag("dt") },
2966
+ { name: "description", match: isTag("dd") },
2967
+ { name: "group", match: isTag("div") },
2968
+ metadata()
2969
+ ]);
2970
+
2971
+ // ../core/src/html/contracts/html/media.ts
2830
2972
  var pictureContract = closedContract([
2831
2973
  { name: "source", match: isTag("source", ...METADATA_TAGS) },
2832
2974
  { name: "image", match: isTag("img"), cardinality: { min: 1, max: 1 }, position: "last" }
@@ -2835,91 +2977,49 @@ var figureContract = contract([
2835
2977
  { name: "caption", match: isTag("figcaption"), cardinality: { max: 1 } },
2836
2978
  { name: "content", match: isOpenContent("figcaption") }
2837
2979
  ]);
2838
- var detailsContract = firstChildContract("summary", "summary");
2839
- var fieldsetContract = firstChildContract("legend", "legend");
2840
2980
  var objectContract = contract([
2841
2981
  { name: "param", match: isTag("param") },
2842
2982
  { name: "content", match: isOpenContent("param") }
2843
2983
  ]);
2844
- var INTERACTIVE_CONTENT_TAGS = ["a", "button", "input", "select", "textarea", "label"];
2845
- var buttonContract = closedContract([
2846
- { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2847
- ]);
2848
- var anchorContract = closedContract([
2849
- { name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
2850
- ]);
2851
- var LABELABLE_TAGS = [
2852
- "button",
2853
- "input",
2854
- "meter",
2855
- "output",
2856
- "progress",
2857
- "select",
2858
- "textarea"
2859
- ];
2860
- var labelContract = contract([
2861
- { name: "control", match: isTag(...LABELABLE_TAGS), cardinality: { max: 1 } }
2862
- ]);
2863
- var P_BLOCKED_TAGS = [
2864
- "address",
2865
- "article",
2866
- "aside",
2867
- "blockquote",
2868
- "details",
2869
- "dialog",
2870
- "div",
2871
- "dl",
2872
- "fieldset",
2873
- "figure",
2874
- "footer",
2875
- "form",
2876
- "h1",
2877
- "h2",
2878
- "h3",
2879
- "h4",
2880
- "h5",
2881
- "h6",
2882
- "header",
2883
- "hr",
2884
- "main",
2885
- "nav",
2886
- "ol",
2887
- "p",
2888
- "pre",
2889
- "section",
2890
- "table",
2891
- "ul"
2892
- ];
2893
- var pContract = closedContract([
2894
- { name: "content", match: isOpenContent(...P_BLOCKED_TAGS) }
2895
- ]);
2896
2984
  var mediaContract = contract([
2897
2985
  { name: "source", match: isTag("source") },
2898
2986
  { name: "track", match: isTag("track") },
2899
2987
  metadata(),
2900
2988
  { name: "content", match: isOpenContent("source", "track", ...METADATA_TAGS) }
2901
2989
  ]);
2902
- var headContract = closedContract([
2903
- {
2904
- name: "metadata",
2905
- match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
2906
- }
2990
+
2991
+ // ../core/src/html/contracts/html/tables.ts
2992
+ var tableContract = closedContract([
2993
+ firstOptional("caption", "caption"),
2994
+ { name: "colgroup", match: isTag("colgroup") },
2995
+ { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
2996
+ { name: "tbody", match: isTag("tbody") },
2997
+ { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
2998
+ { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2907
2999
  ]);
2908
- var htmlContract = closedContract([
2909
- { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
2910
- { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
3000
+ var tableBodyContract = closedContract([
3001
+ { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2911
3002
  ]);
2912
- var voidContract = contract([], { exclusiveChildren: true, allowText: false });
2913
- var textOnlyContract = contract([], { exclusiveChildren: true });
2914
- var landmarkContract = ariaContract([landmarkRoleRule, landmarkNameAdvisory]);
2915
- var dialogContract = ariaContract([requireAccessibleName]);
2916
- var menuContract = ariaContract([requireAccessibleName]);
2917
- var menubarContract = ariaContract([requireAccessibleName]);
2918
- var treeContract = ariaContract([requireAccessibleName]);
2919
- var gridContract = ariaContract([requireAccessibleName]);
2920
- var listboxContract = ariaContract([requireAccessibleName]);
2921
- var tablistContract = ariaContract([requireAccessibleName]);
2922
- var radiogroupContract = ariaContract([requireAccessibleName]);
3003
+ var tableRowContract = closedContract([
3004
+ { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
3005
+ ]);
3006
+ var colgroupContract = closedContract([
3007
+ { name: "column", match: isTag("col", "template") }
3008
+ ]);
3009
+
3010
+ // ../core/src/html/contracts/html/text.ts
3011
+ var pContract = closedContract([
3012
+ { name: "content", match: isOpenContent(...P_BLOCKED_TAGS) }
3013
+ ]);
3014
+
3015
+ // ../core/src/html/contracts/build-map.ts
3016
+ function buildMap(groups) {
3017
+ return Object.fromEntries(
3018
+ groups.flatMap(([keys2, value]) => keys2.map((key) => [key, value]))
3019
+ );
3020
+ }
3021
+
3022
+ // ../core/src/html/contracts/maps.ts
2923
3023
  var CONTRACT_GROUPS = [
2924
3024
  [VOID_TAGS, voidContract],
2925
3025
  [TEXT_ONLY_TAGS, textOnlyContract],
@@ -2928,13 +3028,8 @@ var CONTRACT_GROUPS = [
2928
3028
  [["audio", "video"], mediaContract],
2929
3029
  [["thead", "tbody", "tfoot"], tableBodyContract]
2930
3030
  ];
2931
- function contractMap(groups) {
2932
- return Object.fromEntries(
2933
- groups.flatMap(([tags, enforcement]) => tags.map((tag) => [tag, enforcement]))
2934
- );
2935
- }
2936
3031
  var htmlContracts = {
2937
- ...contractMap(CONTRACT_GROUPS),
3032
+ ...buildMap(CONTRACT_GROUPS),
2938
3033
  table: tableContract,
2939
3034
  tr: tableRowContract,
2940
3035
  colgroup: colgroupContract,
@@ -3178,6 +3273,11 @@ function composeNormalizers(normalizers, fn) {
3178
3273
  function whenDefined(key, value) {
3179
3274
  return value === void 0 ? {} : { [key]: value };
3180
3275
  }
3276
+ function mergeAriaRules(aria, rules) {
3277
+ if (!aria?.length) return rules;
3278
+ if (!rules?.length) return aria;
3279
+ return [...aria, ...rules];
3280
+ }
3181
3281
  function resolveFactoryOptions(options = {}) {
3182
3282
  const { styling, enforcement } = options;
3183
3283
  const composedNormalizeFn = composeNormalizers(enforcement?.props, options.normalize);
@@ -3198,7 +3298,7 @@ function resolveFactoryOptions(options = {}) {
3198
3298
  ...whenDefined("defaultVariants", styling?.defaults),
3199
3299
  ...whenDefined("compoundVariants", styling?.compounds),
3200
3300
  ...whenDefined("normalizeFn", composedNormalizeFn),
3201
- ...whenDefined("ariaRules", enforcement?.aria),
3301
+ ...whenDefined("ariaRules", mergeAriaRules(enforcement?.aria, enforcement?.rules)),
3202
3302
  ...whenDefined("childRules", enforcement?.children),
3203
3303
  ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
3204
3304
  ...whenDefined("allowText", enforcement?.allowText),
@@ -231,6 +231,16 @@ type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
231
231
  */
232
232
  readonly diagnostics?: Diagnostics$1 | DiagnosticsMode;
233
233
  readonly aria?: readonly AriaRule[];
234
+ /**
235
+ * Rules that need `AriaPolicyEngine`'s fix-application/caching machinery
236
+ * (`AriaRule`'s `readsProps`, fixable `AriaFix` results) but have no
237
+ * relationship to ARIA semantics — an HTML fact or a security check like a
238
+ * dangerous-URL-scheme guard, for example. Evaluated together with `aria`
239
+ * (both run through the same engine, merged into one rule set) — this is a
240
+ * separate bucket purely so a non-ARIA rule doesn't have to sit under the
241
+ * misleading `aria` name to get the machinery it needs.
242
+ */
243
+ readonly rules?: readonly AriaRule[];
234
244
  readonly children?: readonly ChildRuleInput[];
235
245
  /**
236
246
  * When true, only children matching a `children` rule (or text, per `allowText`)
@@ -306,4 +316,4 @@ declare function mergeContracts(...contracts: readonly EnforcementOptions[]): En
306
316
 
307
317
  type Diagnostics = Diagnostics$1;
308
318
 
309
- export { type AnyFactoryOptions, type AriaContext, type AriaFix, type AriaFixResult, type AriaPhase, type AriaResult, type AriaRule, type Diagnostics, type FixKind, type AriaInvalidResult as InvalidResult, type AriaInvalidWithFix as InvalidWithFix, type AriaInvalidWithoutFix as InvalidWithoutFix, type PropNormalizer, type RemoveAttributeFixKind, type Severity, type ValidResult, activeContract, activeProps, disabledContract, disabledProps, expandedContract, expandedProps, invalidContract, invalidProps, loadingContract, loadingProps, mergeContracts, pressedContract, pressedProps, readonlyContract, readonlyProps, selectedContract, selectedProps };
319
+ export { type AnyFactoryOptions, type AriaContext, type AriaFix, type AriaFixResult, type AriaPhase, type AriaResult, type AriaRule, type Diagnostics, type FixKind, type IntrinsicProps, type AriaInvalidResult as InvalidResult, type AriaInvalidWithFix as InvalidWithFix, type AriaInvalidWithoutFix as InvalidWithoutFix, type NormalizeFn, type PropNormalizer, type RemoveAttributeFixKind, type Severity, type ValidResult, activeContract, activeProps, disabledContract, disabledProps, expandedContract, expandedProps, invalidContract, invalidProps, loadingContract, loadingProps, mergeContracts, pressedContract, pressedProps, readonlyContract, readonlyProps, selectedContract, selectedProps };
@@ -313,6 +313,7 @@ var selectedContract = stateContract([selectedProps]);
313
313
  function mergeContracts(...contracts) {
314
314
  const props = contracts.flatMap((c) => c.props ?? []);
315
315
  const aria = contracts.flatMap((c) => c.aria ?? []);
316
+ const rules = contracts.flatMap((c) => c.rules ?? []);
316
317
  const children = contracts.flatMap((c) => c.children ?? []);
317
318
  let diagnostics;
318
319
  let allowedAs;
@@ -323,6 +324,7 @@ function mergeContracts(...contracts) {
323
324
  return {
324
325
  ...props.length > 0 && { props },
325
326
  ...aria.length > 0 && { aria },
327
+ ...rules.length > 0 && { rules },
326
328
  ...children.length > 0 && { children },
327
329
  ...diagnostics !== void 0 && { diagnostics },
328
330
  ...allowedAs !== void 0 && { allowedAs }
@@ -22,15 +22,24 @@ type TagChild = {
22
22
  };
23
23
  declare function isTag(tag: string, ...tags: readonly string[]): (child: unknown) => child is TagChild;
24
24
  declare function isTag(child: unknown, tag: string, ...tags: readonly string[]): boolean;
25
+ /**
26
+ * A vnode (or text node) that qualifies as flow content: text nodes
27
+ * (string/number) always qualify, and elements/components qualify unless
28
+ * their resolved tag is blocked — see `isFlowContent`.
29
+ */
30
+ type FlowContentChild = string | number | TagChild;
25
31
  /**
26
32
  * Checks whether a vnode is flow content per the HTML content model: text nodes
27
33
  * (string/number) always qualify, and elements/components qualify unless their
28
34
  * resolved tag is in the blocked set.
35
+ *
36
+ * Returns a type guard (not a plain boolean predicate) so it can be used
37
+ * directly as a `ChildRuleInput.match`, which requires the narrowed type.
29
38
  */
30
- declare function isFlowContent(...blockedTags: readonly string[]): (child: unknown) => boolean;
39
+ declare function isFlowContent(...blockedTags: readonly string[]): (child: unknown) => child is FlowContentChild;
31
40
 
32
41
  declare function isObject(value: unknown, excludeArrays: true): value is AnyRecord;
33
42
  declare function isObject(value: unknown, excludeArrays?: false): value is object;
34
43
  declare function isString(value: unknown): value is string;
35
44
 
36
- export { getTag, isFlowContent, isObject, isString, isTag };
45
+ export { type FlowContentChild, type TagChild, getTag, isFlowContent, isObject, isString, isTag };