praxis-kit 4.1.3 → 6.0.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.
@@ -472,6 +472,19 @@ function makeResolveTag(defaultTag) {
472
472
  };
473
473
  }
474
474
 
475
+ // ../../lib/primitive/src/rule/rule-brand.ts
476
+ var RULE_BRAND = /* @__PURE__ */ Symbol("praxis-kit/dynamic-rule");
477
+
478
+ // ../../lib/primitive/src/rule/is-dynamic-rule.ts
479
+ function isDynamicRule(rule) {
480
+ return isObject(rule, true) && rule[RULE_BRAND] === true;
481
+ }
482
+
483
+ // ../../lib/primitive/src/rule/resolve-rule.ts
484
+ function resolveRule(rule, context) {
485
+ return isDynamicRule(rule) ? rule.resolve(context) : rule;
486
+ }
487
+
475
488
  // ../../lib/primitive/src/utils/assert-never.ts
476
489
  function assertNever(value) {
477
490
  throw new Error(`Unexpected value: ${String(value)}`);
@@ -1899,32 +1912,68 @@ var RuleValidator = class _RuleValidator extends InvariantBase {
1899
1912
  };
1900
1913
 
1901
1914
  // ../../lib/contract/src/children/children-evaluator.ts
1915
+ var isTextLike = (child) => isString(child) || isNumber(child);
1916
+ function checkPositionCardinalityInvariant(rules, context) {
1917
+ iterate.forEach(rules, (rule) => {
1918
+ const { name, position, cardinality } = rule;
1919
+ if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1920
+ throw new RangeError(
1921
+ `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1922
+ );
1923
+ }
1924
+ });
1925
+ }
1902
1926
  var ChildrenEvaluator = class extends InvariantBase {
1903
1927
  #context;
1904
- #rules;
1905
- #ruleNames;
1928
+ /** Rules with a static cardinality — normalized once, reused on every evaluate() call. */
1929
+ #staticRules;
1930
+ /** Rules whose cardinality is `dynamic(...)` — re-resolved against a ChildRuleContext per call. */
1931
+ #dynamicRuleInputs;
1906
1932
  #matcher;
1907
1933
  #ruleValidator;
1908
- constructor(rules, diagnostics, context = "Component") {
1934
+ #exclusiveChildren;
1935
+ #allowText;
1936
+ constructor(rules, diagnostics, context = "Component", options = {}) {
1909
1937
  super(diagnostics);
1910
1938
  this.#context = context;
1911
- this.#rules = rules.map((r) => normalizeChildRule(r));
1912
- this.#ruleNames = this.#rules.map((r) => r.name);
1913
- iterate.forEach(this.#rules, (rule) => {
1914
- const { name, position, cardinality } = rule;
1915
- if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1916
- throw new RangeError(
1917
- `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1918
- );
1919
- }
1939
+ this.#exclusiveChildren = options.exclusiveChildren ?? false;
1940
+ this.#allowText = options.allowText ?? true;
1941
+ const staticRuleInputs = [];
1942
+ const dynamicRuleInputs = [];
1943
+ iterate.forEach(rules, (rule) => {
1944
+ if (isDynamicRule(rule.cardinality)) dynamicRuleInputs.push(rule);
1945
+ else staticRuleInputs.push(rule);
1920
1946
  });
1921
- this.#matcher = new RuleMatcher(this.#rules);
1947
+ this.#dynamicRuleInputs = dynamicRuleInputs;
1948
+ this.#staticRules = staticRuleInputs.map(
1949
+ (r) => normalizeChildRule(r)
1950
+ );
1922
1951
  this.#ruleValidator = new RuleValidator(context, diagnostics);
1952
+ if (this.#dynamicRuleInputs.length === 0) {
1953
+ checkPositionCardinalityInvariant(this.#staticRules, context);
1954
+ this.#matcher = new RuleMatcher(this.#staticRules);
1955
+ } else {
1956
+ this.#matcher = void 0;
1957
+ }
1923
1958
  }
1924
- evaluate(children) {
1959
+ /**
1960
+ * @param context Required when any rule has a `dynamic(...)` cardinality —
1961
+ * supplies the resolved tag/props those rules are evaluated against.
1962
+ */
1963
+ evaluate(children, context) {
1925
1964
  if (!this.active) return;
1926
- const { matrix, unexpectedIndices, ambiguousIndices } = this.#matcher.match(children);
1927
- this.#ruleValidator.validate(this.#rules, matrix, children.length);
1965
+ const { rules, matcher } = this.#resolveRules(context);
1966
+ const {
1967
+ matrix,
1968
+ unexpectedIndices: rawUnexpectedIndices,
1969
+ ambiguousIndices
1970
+ } = matcher.match(children);
1971
+ this.#ruleValidator.validate(rules, matrix, children.length);
1972
+ const unexpectedIndices = new Set(
1973
+ [...rawUnexpectedIndices].filter(
1974
+ (ci) => isTextLike(children[ci]) ? this.#allowText === false : this.#exclusiveChildren
1975
+ )
1976
+ );
1928
1977
  if (unexpectedIndices.size === 0 && ambiguousIndices.size === 0) return;
1929
1978
  const violating = [...unexpectedIndices, ...ambiguousIndices].sort((a, b) => a - b);
1930
1979
  iterate.forEach(violating, (ci) => {
@@ -1933,11 +1982,28 @@ var ChildrenEvaluator = class extends InvariantBase {
1933
1982
  this.violate(ContractDiagnostics.unexpectedChild(typeName, ci, this.#context));
1934
1983
  } else {
1935
1984
  const matches = matrix.childToRules.forward.get(ci);
1936
- const names = [...matches].map((ri) => this.#ruleNames[ri] ?? `#${ri}`);
1985
+ const names = [...matches].map((ri) => rules[ri]?.name ?? `#${ri}`);
1937
1986
  this.violate(ContractDiagnostics.ambiguousChild(typeName, ci, names, this.#context));
1938
1987
  }
1939
1988
  });
1940
1989
  }
1990
+ #resolveRules(context) {
1991
+ if (this.#dynamicRuleInputs.length === 0) {
1992
+ return { rules: this.#staticRules, matcher: this.#matcher };
1993
+ }
1994
+ if (context === void 0) {
1995
+ throw new RangeError(
1996
+ `ChildrenEvaluator [${this.#context}]: rule(s) have a dynamic(...) cardinality \u2014 evaluate() requires a context argument to resolve them.`
1997
+ );
1998
+ }
1999
+ const resolvedDynamicRules = this.#dynamicRuleInputs.map((r) => {
2000
+ const cardinality = resolveRule(r.cardinality, context);
2001
+ return normalizeChildRule({ ...r, cardinality });
2002
+ });
2003
+ checkPositionCardinalityInvariant(resolvedDynamicRules, this.#context);
2004
+ const rules = [...this.#staticRules, ...resolvedDynamicRules];
2005
+ return { rules, matcher: new RuleMatcher(rules) };
2006
+ }
1941
2007
  };
1942
2008
 
1943
2009
  // ../../lib/contract/src/props/get-disabled-props.ts
@@ -2046,8 +2112,11 @@ function metadata(name = "metadata") {
2046
2112
  function firstOptional(name, tag) {
2047
2113
  return { name, match: isTag(tag), cardinality: { max: 1 }, position: "first" };
2048
2114
  }
2049
- function contract(children) {
2050
- return { diagnostics: warnDiagnostics, children };
2115
+ function contract(children, options) {
2116
+ return { diagnostics: warnDiagnostics, children, ...options };
2117
+ }
2118
+ function closedContract(children) {
2119
+ return contract(children, { exclusiveChildren: true });
2051
2120
  }
2052
2121
  function ariaContract(aria) {
2053
2122
  return { diagnostics: warnDiagnostics, aria };
@@ -2073,8 +2142,10 @@ var VOID_TAGS = [
2073
2142
  ];
2074
2143
  var TEXT_ONLY_TAGS = ["option", "script", "style", "textarea", "title"];
2075
2144
  var LANDMARK_TAGS = ["article", "aside", "footer", "header", "main", "nav"];
2076
- var listContract = contract([{ name: "list-item", match: isTag("li", ...METADATA_TAGS) }]);
2077
- var tableContract = contract([
2145
+ var listContract = closedContract([
2146
+ { name: "list-item", match: isTag("li", ...METADATA_TAGS) }
2147
+ ]);
2148
+ var tableContract = closedContract([
2078
2149
  firstOptional("caption", "caption"),
2079
2150
  { name: "colgroup", match: isTag("colgroup") },
2080
2151
  { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
@@ -2082,29 +2153,31 @@ var tableContract = contract([
2082
2153
  { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
2083
2154
  { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2084
2155
  ]);
2085
- var tableBodyContract = contract([
2156
+ var tableBodyContract = closedContract([
2086
2157
  { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2087
2158
  ]);
2088
- var tableRowContract = contract([
2159
+ var tableRowContract = closedContract([
2089
2160
  { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
2090
2161
  ]);
2091
- var colgroupContract = contract([{ name: "column", match: isTag("col", "template") }]);
2092
- var dlContract = contract([
2162
+ var colgroupContract = closedContract([
2163
+ { name: "column", match: isTag("col", "template") }
2164
+ ]);
2165
+ var dlContract = closedContract([
2093
2166
  { name: "term", match: isTag("dt") },
2094
2167
  { name: "description", match: isTag("dd") },
2095
2168
  { name: "group", match: isTag("div") },
2096
2169
  metadata()
2097
2170
  ]);
2098
- var selectContract = contract([
2171
+ var selectContract = closedContract([
2099
2172
  { name: "option", match: isTag("option", "optgroup", "hr", ...METADATA_TAGS) }
2100
2173
  ]);
2101
- var optgroupContract = contract([
2174
+ var optgroupContract = closedContract([
2102
2175
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
2103
2176
  ]);
2104
- var datalistContract = contract([
2177
+ var datalistContract = closedContract([
2105
2178
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
2106
2179
  ]);
2107
- var pictureContract = contract([
2180
+ var pictureContract = closedContract([
2108
2181
  { name: "source", match: isTag("source", ...METADATA_TAGS) },
2109
2182
  { name: "image", match: isTag("img"), cardinality: { min: 1, max: 1 }, position: "last" }
2110
2183
  ]);
@@ -2120,23 +2193,18 @@ var mediaContract = contract([
2120
2193
  metadata(),
2121
2194
  { name: "content", match: isOpenContent("source", "track", ...METADATA_TAGS) }
2122
2195
  ]);
2123
- var headContract = contract([
2196
+ var headContract = closedContract([
2124
2197
  {
2125
2198
  name: "metadata",
2126
2199
  match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
2127
2200
  }
2128
2201
  ]);
2129
- var htmlContract = contract([
2202
+ var htmlContract = closedContract([
2130
2203
  { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
2131
2204
  { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
2132
2205
  ]);
2133
- var voidContract = contract([]);
2134
- var textOnlyContract = contract([
2135
- {
2136
- name: "text",
2137
- match: (child) => isString(child) || isNumber(child)
2138
- }
2139
- ]);
2206
+ var voidContract = contract([], { exclusiveChildren: true, allowText: false });
2207
+ var textOnlyContract = contract([], { exclusiveChildren: true });
2140
2208
  var landmarkContract = ariaContract([landmarkRoleRule, landmarkNameAdvisory]);
2141
2209
  var dialogContract = ariaContract([requireAccessibleName]);
2142
2210
  var menuContract = ariaContract([requireAccessibleName]);
@@ -2181,9 +2249,15 @@ var htmlContracts = {
2181
2249
  var htmlDiagnostics = warnDiagnostics2;
2182
2250
  function buildEvaluatorMap() {
2183
2251
  const map2 = /* @__PURE__ */ new Map();
2184
- iterate.forEachEntry(htmlContracts, (tag, { children }) => {
2185
- if (children?.length) {
2186
- map2.set(tag, new ChildrenEvaluator(children, htmlDiagnostics, `<${tag}>`));
2252
+ iterate.forEachEntry(htmlContracts, (tag, { children, exclusiveChildren, allowText }) => {
2253
+ if (children?.length || exclusiveChildren || allowText === false) {
2254
+ map2.set(
2255
+ tag,
2256
+ new ChildrenEvaluator(children ?? [], htmlDiagnostics, `<${tag}>`, {
2257
+ exclusiveChildren,
2258
+ allowText
2259
+ })
2260
+ );
2187
2261
  }
2188
2262
  });
2189
2263
  return map2;
@@ -2394,7 +2468,7 @@ function definePipeline(factory) {
2394
2468
  }
2395
2469
 
2396
2470
  // ../core/src/options/resolve-factory-options.ts
2397
- import { silentDiagnostics } from "../_shared/diagnostics.js";
2471
+ import { resolveDiagnostics, silentDiagnostics } from "../_shared/diagnostics.js";
2398
2472
  var EMPTY_VARIANT_KEYS = /* @__PURE__ */ new Set();
2399
2473
  function composeNormalizers(normalizers, fn) {
2400
2474
  if (!normalizers?.length) return fn;
@@ -2415,7 +2489,7 @@ function resolveFactoryOptions(options = {}) {
2415
2489
  const variantKeys = styling?.variants === void 0 ? EMPTY_VARIANT_KEYS : new Set(Object.keys(styling.variants));
2416
2490
  return Object.freeze({
2417
2491
  defaultTag: options.tag ?? "div",
2418
- diagnostics: enforcement?.diagnostics ?? silentDiagnostics,
2492
+ diagnostics: resolveDiagnostics(enforcement?.diagnostics, silentDiagnostics),
2419
2493
  variantKeys,
2420
2494
  ...whenDefined("displayName", options.name),
2421
2495
  ...whenDefined("defaultProps", options.defaults),
@@ -2428,6 +2502,8 @@ function resolveFactoryOptions(options = {}) {
2428
2502
  ...whenDefined("normalizeFn", composedNormalizeFn),
2429
2503
  ...whenDefined("ariaRules", enforcement?.aria),
2430
2504
  ...whenDefined("childRules", enforcement?.children),
2505
+ ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
2506
+ ...whenDefined("allowText", enforcement?.allowText),
2431
2507
  ...whenDefined("allowedAs", enforcement?.allowedAs),
2432
2508
  ...whenDefined("precomputedClasses", styling?.precomputedClasses)
2433
2509
  });
@@ -2612,16 +2688,22 @@ function buildCoreRuntime(normalized) {
2612
2688
  }
2613
2689
 
2614
2690
  // ../../lib/adapter-utils/src/runtime/build-engines.ts
2615
- function buildEngines(diagnostics, childRules, context) {
2616
- return childRules?.length ? { childrenEvaluator: new ChildrenEvaluator(childRules, diagnostics, context) } : {};
2691
+ function buildEngines(diagnostics, childRules, context, childrenOptions) {
2692
+ const { exclusiveChildren, allowText } = childrenOptions ?? {};
2693
+ return childRules?.length || exclusiveChildren || allowText === false ? {
2694
+ childrenEvaluator: new ChildrenEvaluator(childRules ?? [], diagnostics, context, {
2695
+ exclusiveChildren,
2696
+ allowText
2697
+ })
2698
+ } : {};
2617
2699
  }
2618
2700
 
2619
2701
  // ../../lib/adapter-utils/src/runtime/resolve-adapter-common-options.ts
2620
- import { throwDiagnostics as throwDiagnostics2, silentDiagnostics as silentDiagnostics3 } from "../_shared/diagnostics.js";
2702
+ import { throwDiagnostics as throwDiagnostics2, silentDiagnostics as silentDiagnostics3, resolveDiagnostics as resolveDiagnostics2 } from "../_shared/diagnostics.js";
2621
2703
  function resolveAdapterCommonOptions(options, defaultName = "PolymorphicComponent", defaultDiagnostics = throwDiagnostics2) {
2622
2704
  return {
2623
2705
  name: options.name ?? defaultName,
2624
- diagnostics: options.enforcement?.diagnostics ?? defaultDiagnostics
2706
+ diagnostics: resolveDiagnostics2(options.enforcement?.diagnostics, defaultDiagnostics)
2625
2707
  };
2626
2708
  }
2627
2709
 
@@ -2807,8 +2889,14 @@ var Slot = forwardRef(function Slot2({ children, ...slotProps }, ref) {
2807
2889
  Object.assign(Slot, { displayName: SLOT_NAME });
2808
2890
 
2809
2891
  // ../../adapters/preact/src/render.tsx
2810
- function buildRenderState(tag, directives, props, className, children) {
2811
- const state = { tag, directives, props, className };
2892
+ function buildRenderState(tag, directives, props, normalizedProps, className, children) {
2893
+ const state = {
2894
+ tag,
2895
+ directives,
2896
+ props,
2897
+ normalizedProps,
2898
+ className
2899
+ };
2812
2900
  if (children !== void 0) state.children = children;
2813
2901
  return state;
2814
2902
  }
@@ -2833,7 +2921,7 @@ function prepareRenderState(runtime, props, filterProps) {
2833
2921
  ...as !== void 0 && { as },
2834
2922
  ...asChild !== void 0 && { asChild }
2835
2923
  };
2836
- return buildRenderState(tag, directives, filteredProps, resolvedClass, children);
2924
+ return buildRenderState(tag, directives, filteredProps, normalizedProps, resolvedClass, children);
2837
2925
  }
2838
2926
  function warnDiscardedChildren(originalChildren, normalizedChildren, validator) {
2839
2927
  if (!Array.isArray(originalChildren)) return;
@@ -2911,7 +2999,10 @@ function render({
2911
2999
  let normalizedChildren;
2912
3000
  const getNormalizedChildren = () => normalizedChildren ??= normalizeChildren2(state.children);
2913
3001
  if (process.env.NODE_ENV !== "production") {
2914
- childrenEvaluator?.evaluate(getNormalizedChildren());
3002
+ childrenEvaluator?.evaluate(getNormalizedChildren(), {
3003
+ tag: state.tag,
3004
+ props: state.normalizedProps
3005
+ });
2915
3006
  runtime.options.htmlChildrenEvaluatorFn?.(state.tag)?.evaluate(getNormalizedChildren());
2916
3007
  }
2917
3008
  const slotResult = tryRenderAsChild(
@@ -2944,7 +3035,10 @@ function buildRuntime(options) {
2944
3035
  const normalized = normalizeOptions(options);
2945
3036
  const { runtime, ownedKeys } = buildCoreRuntime(normalized);
2946
3037
  const { diagnostics, enforcement, filterProps: userFilter, name, slotComponent } = normalized;
2947
- const { childrenEvaluator } = buildEngines(diagnostics, enforcement?.children, name);
3038
+ const { childrenEvaluator } = buildEngines(diagnostics, enforcement?.children, name, {
3039
+ exclusiveChildren: enforcement?.exclusiveChildren,
3040
+ allowText: enforcement?.allowText
3041
+ });
2948
3042
  const filterProps = composeFilter(ownedKeys, userFilter);
2949
3043
  const slotValidator = new SlotValidator(name, diagnostics, "Preact element");
2950
3044
  const built = {
@@ -1,5 +1,5 @@
1
- import { U as UnknownProps, E as ElementType, a as EmptyRecord, V as VariantMap, R as RecipeMap, A as AnyClassPluginFactory, b as ReactFactoryOptions, P as PolymorphicComponent, c as PolymorphicGenerics, d as ExtractPluginProps } from '../react-options-XrRof248.js';
2
- export { e as AnyFactoryOptions, f as ElementRef, F as FactoryOptions, g as PolymorphicProps, h as PolymorphicWithAsChild, i as PolymorphicWithRender, j as RenderCallbackProps, S as Slottable, k as SlottableProps, m as composeRefs, l as defineContractComponent, m as mergeRefs } from '../react-options-XrRof248.js';
1
+ import { U as UnknownProps, E as ElementType, a as EmptyRecord, V as VariantMap, R as RecipeMap, A as AnyClassPluginFactory, b as ReactFactoryOptions, P as PolymorphicComponent, c as PolymorphicGenerics, d as ExtractPluginProps } from '../react-options-AitAOrje.js';
2
+ export { e as AnyFactoryOptions, f as ElementRef, F as FactoryOptions, g as PolymorphicProps, h as PolymorphicWithAsChild, i as PolymorphicWithRender, j as RenderCallbackProps, S as Slottable, k as SlottableProps, m as composeRefs, l as defineContractComponent, m as mergeRefs } from '../react-options-AitAOrje.js';
3
3
  import * as react from 'react';
4
4
  import { ReactElement, Ref } from 'react';
5
5
  import 'type-fest';
@@ -12,7 +12,7 @@ import {
12
12
  makeCloneSlotChild,
13
13
  mergeRefs,
14
14
  render
15
- } from "../chunk-R2RKHZNX.js";
15
+ } from "../chunk-P5ZJBPAR.js";
16
16
 
17
17
  // ../../adapters/react/src/current/slot/composeRefs.ts
18
18
  function getChildRef(element) {
@@ -1,5 +1,5 @@
1
- import { E as ElementType, U as UnknownProps, a as EmptyRecord, V as VariantMap, R as RecipeMap, A as AnyClassPluginFactory, b as ReactFactoryOptions, P as PolymorphicComponent, c as PolymorphicGenerics, d as ExtractPluginProps } from '../react-options-XrRof248.js';
2
- export { e as AnyFactoryOptions, f as ElementRef, F as FactoryOptions, g as PolymorphicProps, h as PolymorphicWithAsChild, i as PolymorphicWithRender, j as RenderCallbackProps, S as Slottable, k as SlottableProps, l as defineContractComponent, m as mergeRefs } from '../react-options-XrRof248.js';
1
+ import { E as ElementType, U as UnknownProps, a as EmptyRecord, V as VariantMap, R as RecipeMap, A as AnyClassPluginFactory, b as ReactFactoryOptions, P as PolymorphicComponent, c as PolymorphicGenerics, d as ExtractPluginProps } from '../react-options-AitAOrje.js';
2
+ export { e as AnyFactoryOptions, f as ElementRef, F as FactoryOptions, g as PolymorphicProps, h as PolymorphicWithAsChild, i as PolymorphicWithRender, j as RenderCallbackProps, S as Slottable, k as SlottableProps, l as defineContractComponent, m as mergeRefs } from '../react-options-AitAOrje.js';
3
3
  import * as react from 'react';
4
4
  import 'type-fest';
5
5
  import '../_shared/diagnostics.js';
@@ -13,7 +13,7 @@ import {
13
13
  makeCloneSlotChild,
14
14
  mergeRefs,
15
15
  render
16
- } from "../chunk-R2RKHZNX.js";
16
+ } from "../chunk-P5ZJBPAR.js";
17
17
 
18
18
  // ../../adapters/react/src/legacy/create-contract-component.ts
19
19
  import { forwardRef as forwardRef2 } from "react";
@@ -1,6 +1,6 @@
1
1
  import { Ref, PropsWithChildren, ReactElement, ComponentType, JSX, ReactNode } from 'react';
2
2
  import { RequireAtLeastOne, Simplify, ReadonlyDeep, NonEmptyTuple } from 'type-fest';
3
- import { Diagnostics, DiagnosticInput } from './_shared/diagnostics.js';
3
+ import { Diagnostics, DiagnosticInput, DiagnosticsMode } from './_shared/diagnostics.js';
4
4
 
5
5
  type StringMap<T = unknown> = Record<string, T>;
6
6
  type AnyRecord = StringMap<unknown>;
@@ -30,6 +30,26 @@ type MinMax = {
30
30
  };
31
31
  type CardinalityInput = Partial<MinMax>;
32
32
 
33
+ /**
34
+ * Resolved per-instance state available to a dynamic (`dynamic(...)`) child
35
+ * rule field — the same tag/props every adapter already computes before
36
+ * evaluating children, exposed so a rule can vary by them (e.g. cardinality
37
+ * that depends on the resolved `as` tag).
38
+ */
39
+ type ChildRuleContext = {
40
+ readonly tag: unknown;
41
+ readonly props: Readonly<AnyRecord>;
42
+ };
43
+
44
+ declare const RULE_BRAND: unique symbol;
45
+
46
+ type DynamicRule<T, C = unknown> = {
47
+ readonly [RULE_BRAND]: true;
48
+ resolve(context: C): T;
49
+ };
50
+
51
+ type Rule<T, C = unknown> = T | DynamicRule<T, C>;
52
+
33
53
  type ChildRuleMatch<T, U extends T = T> = (child: T) => child is U;
34
54
 
35
55
  type ChildRulePosition = 'first' | 'last' | 'any';
@@ -37,7 +57,13 @@ type ChildRulePosition = 'first' | 'last' | 'any';
37
57
  type ChildRuleInput<T = unknown, U extends T = T> = {
38
58
  name: string;
39
59
  match: ChildRuleMatch<T, U>;
40
- cardinality?: CardinalityInput;
60
+ /**
61
+ * Either a static cardinality, or `dynamic((ctx) => ...)` to derive it from
62
+ * the resolved tag/props (e.g. a different max depending on `as`). `match`
63
+ * stays static-only — it's already a function, so a dynamic wrapper would
64
+ * be indistinguishable from the predicate itself without one.
65
+ */
66
+ cardinality?: Rule<CardinalityInput, ChildRuleContext>;
41
67
  position?: ChildRulePosition;
42
68
  /**
43
69
  * Optional component-type reference for O(1) dispatch index.
@@ -214,9 +240,25 @@ type AriaRule<C extends AriaContext = AriaContext> = (context: C) => readonly Ar
214
240
  type PropNormalizer = (props: Readonly<AnyRecord & IntrinsicProps>) => Partial<AnyRecord & IntrinsicProps>;
215
241
 
216
242
  type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
217
- readonly diagnostics?: Diagnostics;
243
+ /**
244
+ * Accepts a preset name (`'warn'`, `'throw'`, `'silent'`) or a full `Diagnostics`
245
+ * instance for custom reporting/policy. The string form needs no import from
246
+ * `@praxis-kit/diagnostics`.
247
+ */
248
+ readonly diagnostics?: Diagnostics | DiagnosticsMode;
218
249
  readonly aria?: readonly AriaRule[];
219
250
  readonly children?: readonly ChildRuleInput[];
251
+ /**
252
+ * When true, only children matching a `children` rule (or text, per `allowText`)
253
+ * are valid — anything else is rejected. Default: false (open — children not
254
+ * matching any rule are allowed).
255
+ */
256
+ readonly exclusiveChildren?: boolean;
257
+ /**
258
+ * When false, text/number child nodes are rejected regardless of exclusiveChildren
259
+ * or any listed rule. Default: true.
260
+ */
261
+ readonly allowText?: boolean;
220
262
  readonly props?: readonly PropNormalizer[];
221
263
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
222
264
  readonly allowedAs?: readonly TAllowed[];
@@ -1,5 +1,5 @@
1
1
  import { RequireAtLeastOne, Simplify, ReadonlyDeep, OmitIndexSignature } from 'type-fest';
2
- import { Diagnostics, DiagnosticInput } from '../_shared/diagnostics.js';
2
+ import { Diagnostics, DiagnosticInput, DiagnosticsMode } from '../_shared/diagnostics.js';
3
3
  import { JSX } from 'solid-js';
4
4
 
5
5
  type StringMap<T = unknown> = Record<string, T>;
@@ -30,6 +30,26 @@ type MinMax = {
30
30
  };
31
31
  type CardinalityInput = Partial<MinMax>;
32
32
 
33
+ /**
34
+ * Resolved per-instance state available to a dynamic (`dynamic(...)`) child
35
+ * rule field — the same tag/props every adapter already computes before
36
+ * evaluating children, exposed so a rule can vary by them (e.g. cardinality
37
+ * that depends on the resolved `as` tag).
38
+ */
39
+ type ChildRuleContext = {
40
+ readonly tag: unknown;
41
+ readonly props: Readonly<AnyRecord>;
42
+ };
43
+
44
+ declare const RULE_BRAND: unique symbol;
45
+
46
+ type DynamicRule<T, C = unknown> = {
47
+ readonly [RULE_BRAND]: true;
48
+ resolve(context: C): T;
49
+ };
50
+
51
+ type Rule<T, C = unknown> = T | DynamicRule<T, C>;
52
+
33
53
  type ChildRuleMatch<T, U extends T = T> = (child: T) => child is U;
34
54
 
35
55
  type ChildRulePosition = 'first' | 'last' | 'any';
@@ -37,7 +57,13 @@ type ChildRulePosition = 'first' | 'last' | 'any';
37
57
  type ChildRuleInput<T = unknown, U extends T = T> = {
38
58
  name: string;
39
59
  match: ChildRuleMatch<T, U>;
40
- cardinality?: CardinalityInput;
60
+ /**
61
+ * Either a static cardinality, or `dynamic((ctx) => ...)` to derive it from
62
+ * the resolved tag/props (e.g. a different max depending on `as`). `match`
63
+ * stays static-only — it's already a function, so a dynamic wrapper would
64
+ * be indistinguishable from the predicate itself without one.
65
+ */
66
+ cardinality?: Rule<CardinalityInput, ChildRuleContext>;
41
67
  position?: ChildRulePosition;
42
68
  /**
43
69
  * Optional component-type reference for O(1) dispatch index.
@@ -213,9 +239,25 @@ type AriaRule<C extends AriaContext = AriaContext> = (context: C) => readonly Ar
213
239
  type PropNormalizer = (props: Readonly<AnyRecord & IntrinsicProps>) => Partial<AnyRecord & IntrinsicProps>;
214
240
 
215
241
  type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
216
- readonly diagnostics?: Diagnostics;
242
+ /**
243
+ * Accepts a preset name (`'warn'`, `'throw'`, `'silent'`) or a full `Diagnostics`
244
+ * instance for custom reporting/policy. The string form needs no import from
245
+ * `@praxis-kit/diagnostics`.
246
+ */
247
+ readonly diagnostics?: Diagnostics | DiagnosticsMode;
217
248
  readonly aria?: readonly AriaRule[];
218
249
  readonly children?: readonly ChildRuleInput[];
250
+ /**
251
+ * When true, only children matching a `children` rule (or text, per `allowText`)
252
+ * are valid — anything else is rejected. Default: false (open — children not
253
+ * matching any rule are allowed).
254
+ */
255
+ readonly exclusiveChildren?: boolean;
256
+ /**
257
+ * When false, text/number child nodes are rejected regardless of exclusiveChildren
258
+ * or any listed rule. Default: true.
259
+ */
260
+ readonly allowText?: boolean;
219
261
  readonly props?: readonly PropNormalizer[];
220
262
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
221
263
  readonly allowedAs?: readonly TAllowed[];