praxis-kit 5.0.0 → 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.
@@ -420,6 +420,19 @@ function makeResolveTag(defaultTag) {
420
420
  };
421
421
  }
422
422
 
423
+ // ../../lib/primitive/src/rule/rule-brand.ts
424
+ var RULE_BRAND = /* @__PURE__ */ Symbol("praxis-kit/dynamic-rule");
425
+
426
+ // ../../lib/primitive/src/rule/is-dynamic-rule.ts
427
+ function isDynamicRule(rule) {
428
+ return isObject(rule, true) && rule[RULE_BRAND] === true;
429
+ }
430
+
431
+ // ../../lib/primitive/src/rule/resolve-rule.ts
432
+ function resolveRule(rule, context) {
433
+ return isDynamicRule(rule) ? rule.resolve(context) : rule;
434
+ }
435
+
423
436
  // ../../lib/primitive/src/utils/assert-never.ts
424
437
  function assertNever(value) {
425
438
  throw new Error(`Unexpected value: ${String(value)}`);
@@ -1885,10 +1898,22 @@ var RuleValidator = class _RuleValidator extends InvariantBase {
1885
1898
 
1886
1899
  // ../../lib/contract/src/children/children-evaluator.ts
1887
1900
  var isTextLike = (child) => isString(child) || isNumber(child);
1901
+ function checkPositionCardinalityInvariant(rules, context) {
1902
+ iterate.forEach(rules, (rule) => {
1903
+ const { name, position, cardinality } = rule;
1904
+ if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1905
+ throw new RangeError(
1906
+ `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1907
+ );
1908
+ }
1909
+ });
1910
+ }
1888
1911
  var ChildrenEvaluator = class extends InvariantBase {
1889
1912
  #context;
1890
- #rules;
1891
- #ruleNames;
1913
+ /** Rules with a static cardinality — normalized once, reused on every evaluate() call. */
1914
+ #staticRules;
1915
+ /** Rules whose cardinality is `dynamic(...)` — re-resolved against a ChildRuleContext per call. */
1916
+ #dynamicRuleInputs;
1892
1917
  #matcher;
1893
1918
  #ruleValidator;
1894
1919
  #exclusiveChildren;
@@ -1896,29 +1921,39 @@ var ChildrenEvaluator = class extends InvariantBase {
1896
1921
  constructor(rules, diagnostics, context = "Component", options = {}) {
1897
1922
  super(diagnostics);
1898
1923
  this.#context = context;
1899
- this.#rules = rules.map((r) => normalizeChildRule(r));
1900
- this.#ruleNames = this.#rules.map((r) => r.name);
1901
1924
  this.#exclusiveChildren = options.exclusiveChildren ?? false;
1902
1925
  this.#allowText = options.allowText ?? true;
1903
- iterate.forEach(this.#rules, (rule) => {
1904
- const { name, position, cardinality } = rule;
1905
- if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1906
- throw new RangeError(
1907
- `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1908
- );
1909
- }
1926
+ const staticRuleInputs = [];
1927
+ const dynamicRuleInputs = [];
1928
+ iterate.forEach(rules, (rule) => {
1929
+ if (isDynamicRule(rule.cardinality)) dynamicRuleInputs.push(rule);
1930
+ else staticRuleInputs.push(rule);
1910
1931
  });
1911
- this.#matcher = new RuleMatcher(this.#rules);
1932
+ this.#dynamicRuleInputs = dynamicRuleInputs;
1933
+ this.#staticRules = staticRuleInputs.map(
1934
+ (r) => normalizeChildRule(r)
1935
+ );
1912
1936
  this.#ruleValidator = new RuleValidator(context, diagnostics);
1937
+ if (this.#dynamicRuleInputs.length === 0) {
1938
+ checkPositionCardinalityInvariant(this.#staticRules, context);
1939
+ this.#matcher = new RuleMatcher(this.#staticRules);
1940
+ } else {
1941
+ this.#matcher = void 0;
1942
+ }
1913
1943
  }
1914
- evaluate(children) {
1944
+ /**
1945
+ * @param context Required when any rule has a `dynamic(...)` cardinality —
1946
+ * supplies the resolved tag/props those rules are evaluated against.
1947
+ */
1948
+ evaluate(children, context) {
1915
1949
  if (!this.active) return;
1950
+ const { rules, matcher } = this.#resolveRules(context);
1916
1951
  const {
1917
1952
  matrix,
1918
1953
  unexpectedIndices: rawUnexpectedIndices,
1919
1954
  ambiguousIndices
1920
- } = this.#matcher.match(children);
1921
- this.#ruleValidator.validate(this.#rules, matrix, children.length);
1955
+ } = matcher.match(children);
1956
+ this.#ruleValidator.validate(rules, matrix, children.length);
1922
1957
  const unexpectedIndices = new Set(
1923
1958
  [...rawUnexpectedIndices].filter(
1924
1959
  (ci) => isTextLike(children[ci]) ? this.#allowText === false : this.#exclusiveChildren
@@ -1932,11 +1967,28 @@ var ChildrenEvaluator = class extends InvariantBase {
1932
1967
  this.violate(ContractDiagnostics.unexpectedChild(typeName, ci, this.#context));
1933
1968
  } else {
1934
1969
  const matches = matrix.childToRules.forward.get(ci);
1935
- const names = [...matches].map((ri) => this.#ruleNames[ri] ?? `#${ri}`);
1970
+ const names = [...matches].map((ri) => rules[ri]?.name ?? `#${ri}`);
1936
1971
  this.violate(ContractDiagnostics.ambiguousChild(typeName, ci, names, this.#context));
1937
1972
  }
1938
1973
  });
1939
1974
  }
1975
+ #resolveRules(context) {
1976
+ if (this.#dynamicRuleInputs.length === 0) {
1977
+ return { rules: this.#staticRules, matcher: this.#matcher };
1978
+ }
1979
+ if (context === void 0) {
1980
+ throw new RangeError(
1981
+ `ChildrenEvaluator [${this.#context}]: rule(s) have a dynamic(...) cardinality \u2014 evaluate() requires a context argument to resolve them.`
1982
+ );
1983
+ }
1984
+ const resolvedDynamicRules = this.#dynamicRuleInputs.map((r) => {
1985
+ const cardinality = resolveRule(r.cardinality, context);
1986
+ return normalizeChildRule({ ...r, cardinality });
1987
+ });
1988
+ checkPositionCardinalityInvariant(resolvedDynamicRules, this.#context);
1989
+ const rules = [...this.#staticRules, ...resolvedDynamicRules];
1990
+ return { rules, matcher: new RuleMatcher(rules) };
1991
+ }
1940
1992
  };
1941
1993
 
1942
1994
  // ../../lib/contract/src/props/get-disabled-props.ts
@@ -2846,8 +2898,14 @@ function applyDisplayName(component, name) {
2846
2898
  // ../../adapters/react/src/shared/render.ts
2847
2899
  import { createElement as createElement2 } from "react";
2848
2900
  import { jsx as jsx2 } from "react/jsx-runtime";
2849
- function buildRenderState(tag, directives, props, className, children) {
2850
- const state = { tag, directives, props, className };
2901
+ function buildRenderState(tag, directives, props, normalizedProps, className, children) {
2902
+ const state = {
2903
+ tag,
2904
+ directives,
2905
+ props,
2906
+ normalizedProps,
2907
+ className
2908
+ };
2851
2909
  if (children !== void 0) state.children = children;
2852
2910
  return state;
2853
2911
  }
@@ -2872,7 +2930,7 @@ function prepareRenderState(runtime, props, filterProps) {
2872
2930
  ...as !== void 0 && { as },
2873
2931
  ...asChild !== void 0 && { asChild }
2874
2932
  };
2875
- return buildRenderState(tag, directives, filteredProps, resolvedClass, children);
2933
+ return buildRenderState(tag, directives, filteredProps, normalizedProps, resolvedClass, children);
2876
2934
  }
2877
2935
  function warnDiscardedChildren(originalChildren, normalizedChildren, validator) {
2878
2936
  if (!Array.isArray(originalChildren)) return;
@@ -2950,7 +3008,10 @@ function render({
2950
3008
  let cached;
2951
3009
  const getNormalizedChildren = () => cached ??= normalizeChildren(state.children);
2952
3010
  if (process.env.NODE_ENV !== "production") {
2953
- childrenEvaluator?.evaluate(getNormalizedChildren());
3011
+ childrenEvaluator?.evaluate(getNormalizedChildren(), {
3012
+ tag: state.tag,
3013
+ props: state.normalizedProps
3014
+ });
2954
3015
  runtime.options.htmlChildrenEvaluatorFn?.(state.tag)?.evaluate(getNormalizedChildren());
2955
3016
  }
2956
3017
  if (typeof props.render === "function") {
@@ -29,6 +29,26 @@ type MinMax = {
29
29
  };
30
30
  type CardinalityInput = Partial<MinMax>;
31
31
 
32
+ /**
33
+ * Resolved per-instance state available to a dynamic (`dynamic(...)`) child
34
+ * rule field — the same tag/props every adapter already computes before
35
+ * evaluating children, exposed so a rule can vary by them (e.g. cardinality
36
+ * that depends on the resolved `as` tag).
37
+ */
38
+ type ChildRuleContext = {
39
+ readonly tag: unknown;
40
+ readonly props: Readonly<AnyRecord>;
41
+ };
42
+
43
+ declare const RULE_BRAND: unique symbol;
44
+
45
+ type DynamicRule<T, C = unknown> = {
46
+ readonly [RULE_BRAND]: true;
47
+ resolve(context: C): T;
48
+ };
49
+
50
+ type Rule<T, C = unknown> = T | DynamicRule<T, C>;
51
+
32
52
  type ChildRuleMatch<T, U extends T = T> = (child: T) => child is U;
33
53
 
34
54
  type ChildRulePosition = 'first' | 'last' | 'any';
@@ -36,7 +56,13 @@ type ChildRulePosition = 'first' | 'last' | 'any';
36
56
  type ChildRuleInput<T = unknown, U extends T = T> = {
37
57
  name: string;
38
58
  match: ChildRuleMatch<T, U>;
39
- cardinality?: CardinalityInput;
59
+ /**
60
+ * Either a static cardinality, or `dynamic((ctx) => ...)` to derive it from
61
+ * the resolved tag/props (e.g. a different max depending on `as`). `match`
62
+ * stays static-only — it's already a function, so a dynamic wrapper would
63
+ * be indistinguishable from the predicate itself without one.
64
+ */
65
+ cardinality?: Rule<CardinalityInput, ChildRuleContext>;
40
66
  position?: ChildRulePosition;
41
67
  /**
42
68
  * Optional component-type reference for O(1) dispatch index.
@@ -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.
package/dist/lit/index.js CHANGED
@@ -313,6 +313,19 @@ function makeResolveTag(defaultTag) {
313
313
  };
314
314
  }
315
315
 
316
+ // ../../lib/primitive/src/rule/rule-brand.ts
317
+ var RULE_BRAND = /* @__PURE__ */ Symbol("praxis-kit/dynamic-rule");
318
+
319
+ // ../../lib/primitive/src/rule/is-dynamic-rule.ts
320
+ function isDynamicRule(rule) {
321
+ return isObject(rule, true) && rule[RULE_BRAND] === true;
322
+ }
323
+
324
+ // ../../lib/primitive/src/rule/resolve-rule.ts
325
+ function resolveRule(rule, context) {
326
+ return isDynamicRule(rule) ? rule.resolve(context) : rule;
327
+ }
328
+
316
329
  // ../../lib/primitive/src/utils/assert-never.ts
317
330
  function assertNever(value) {
318
331
  throw new Error(`Unexpected value: ${String(value)}`);
@@ -1715,10 +1728,22 @@ var RuleValidator = class _RuleValidator extends InvariantBase {
1715
1728
 
1716
1729
  // ../../lib/contract/src/children/children-evaluator.ts
1717
1730
  var isTextLike = (child) => isString(child) || isNumber(child);
1731
+ function checkPositionCardinalityInvariant(rules, context) {
1732
+ iterate.forEach(rules, (rule) => {
1733
+ const { name, position, cardinality } = rule;
1734
+ if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1735
+ throw new RangeError(
1736
+ `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1737
+ );
1738
+ }
1739
+ });
1740
+ }
1718
1741
  var ChildrenEvaluator = class extends InvariantBase {
1719
1742
  #context;
1720
- #rules;
1721
- #ruleNames;
1743
+ /** Rules with a static cardinality — normalized once, reused on every evaluate() call. */
1744
+ #staticRules;
1745
+ /** Rules whose cardinality is `dynamic(...)` — re-resolved against a ChildRuleContext per call. */
1746
+ #dynamicRuleInputs;
1722
1747
  #matcher;
1723
1748
  #ruleValidator;
1724
1749
  #exclusiveChildren;
@@ -1726,29 +1751,39 @@ var ChildrenEvaluator = class extends InvariantBase {
1726
1751
  constructor(rules, diagnostics, context = "Component", options = {}) {
1727
1752
  super(diagnostics);
1728
1753
  this.#context = context;
1729
- this.#rules = rules.map((r) => normalizeChildRule(r));
1730
- this.#ruleNames = this.#rules.map((r) => r.name);
1731
1754
  this.#exclusiveChildren = options.exclusiveChildren ?? false;
1732
1755
  this.#allowText = options.allowText ?? true;
1733
- iterate.forEach(this.#rules, (rule) => {
1734
- const { name, position, cardinality } = rule;
1735
- if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1736
- throw new RangeError(
1737
- `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1738
- );
1739
- }
1756
+ const staticRuleInputs = [];
1757
+ const dynamicRuleInputs = [];
1758
+ iterate.forEach(rules, (rule) => {
1759
+ if (isDynamicRule(rule.cardinality)) dynamicRuleInputs.push(rule);
1760
+ else staticRuleInputs.push(rule);
1740
1761
  });
1741
- this.#matcher = new RuleMatcher(this.#rules);
1762
+ this.#dynamicRuleInputs = dynamicRuleInputs;
1763
+ this.#staticRules = staticRuleInputs.map(
1764
+ (r) => normalizeChildRule(r)
1765
+ );
1742
1766
  this.#ruleValidator = new RuleValidator(context, diagnostics);
1767
+ if (this.#dynamicRuleInputs.length === 0) {
1768
+ checkPositionCardinalityInvariant(this.#staticRules, context);
1769
+ this.#matcher = new RuleMatcher(this.#staticRules);
1770
+ } else {
1771
+ this.#matcher = void 0;
1772
+ }
1743
1773
  }
1744
- evaluate(children) {
1774
+ /**
1775
+ * @param context Required when any rule has a `dynamic(...)` cardinality —
1776
+ * supplies the resolved tag/props those rules are evaluated against.
1777
+ */
1778
+ evaluate(children, context) {
1745
1779
  if (!this.active) return;
1780
+ const { rules, matcher } = this.#resolveRules(context);
1746
1781
  const {
1747
1782
  matrix,
1748
1783
  unexpectedIndices: rawUnexpectedIndices,
1749
1784
  ambiguousIndices
1750
- } = this.#matcher.match(children);
1751
- this.#ruleValidator.validate(this.#rules, matrix, children.length);
1785
+ } = matcher.match(children);
1786
+ this.#ruleValidator.validate(rules, matrix, children.length);
1752
1787
  const unexpectedIndices = new Set(
1753
1788
  [...rawUnexpectedIndices].filter(
1754
1789
  (ci) => isTextLike(children[ci]) ? this.#allowText === false : this.#exclusiveChildren
@@ -1762,11 +1797,28 @@ var ChildrenEvaluator = class extends InvariantBase {
1762
1797
  this.violate(ContractDiagnostics.unexpectedChild(typeName, ci, this.#context));
1763
1798
  } else {
1764
1799
  const matches = matrix.childToRules.forward.get(ci);
1765
- const names = [...matches].map((ri) => this.#ruleNames[ri] ?? `#${ri}`);
1800
+ const names = [...matches].map((ri) => rules[ri]?.name ?? `#${ri}`);
1766
1801
  this.violate(ContractDiagnostics.ambiguousChild(typeName, ci, names, this.#context));
1767
1802
  }
1768
1803
  });
1769
1804
  }
1805
+ #resolveRules(context) {
1806
+ if (this.#dynamicRuleInputs.length === 0) {
1807
+ return { rules: this.#staticRules, matcher: this.#matcher };
1808
+ }
1809
+ if (context === void 0) {
1810
+ throw new RangeError(
1811
+ `ChildrenEvaluator [${this.#context}]: rule(s) have a dynamic(...) cardinality \u2014 evaluate() requires a context argument to resolve them.`
1812
+ );
1813
+ }
1814
+ const resolvedDynamicRules = this.#dynamicRuleInputs.map((r) => {
1815
+ const cardinality = resolveRule(r.cardinality, context);
1816
+ return normalizeChildRule({ ...r, cardinality });
1817
+ });
1818
+ checkPositionCardinalityInvariant(resolvedDynamicRules, this.#context);
1819
+ const rules = [...this.#staticRules, ...resolvedDynamicRules];
1820
+ return { rules, matcher: new RuleMatcher(rules) };
1821
+ }
1770
1822
  };
1771
1823
 
1772
1824
  // ../../lib/contract/src/props/get-disabled-props.ts
@@ -2558,13 +2610,10 @@ function toLooseBundle(bundle) {
2558
2610
  }
2559
2611
  return bundle;
2560
2612
  }
2561
- function resolveHostState(bundle, props) {
2562
- const { as, className, recipe, ...rest } = props;
2563
- const { options, resolveAria, resolveClasses, resolveProps, resolveTag: resolveTag2 } = bundle.runtime;
2613
+ function resolveTagAndNormalizedProps(bundle, props) {
2614
+ const { as, className: _className, recipe: _recipe, ...rest } = props;
2615
+ const { options, resolveProps, resolveTag: resolveTag2 } = bundle.runtime;
2564
2616
  const tag = resolveTag2(as);
2565
- if (options.allowedAs !== void 0) {
2566
- enforceAllowedAs(tag, options.allowedAs, options.diagnostics, options.displayName);
2567
- }
2568
2617
  const mergedProps = resolveProps(rest);
2569
2618
  const normalizedProps = typeof options.normalizeFn === "function" ? options.normalizeFn(mergedProps) : mergedProps;
2570
2619
  const htmlNormalizers = options.htmlPropNormalizersFn?.(tag);
@@ -2574,6 +2623,15 @@ function resolveHostState(bundle, props) {
2574
2623
  Object.assign(finalProps, normalize(finalProps));
2575
2624
  }
2576
2625
  }
2626
+ return { tag, normalizedProps: finalProps };
2627
+ }
2628
+ function resolveHostState(bundle, props) {
2629
+ const { className, recipe } = props;
2630
+ const { options, resolveAria, resolveClasses } = bundle.runtime;
2631
+ const { tag, normalizedProps: finalProps } = resolveTagAndNormalizedProps(bundle, props);
2632
+ if (options.allowedAs !== void 0) {
2633
+ enforceAllowedAs(tag, options.allowedAs, options.diagnostics, options.displayName);
2634
+ }
2577
2635
  const resolvedClass = resolveClasses(
2578
2636
  tag,
2579
2637
  finalProps,
@@ -2582,7 +2640,7 @@ function resolveHostState(bundle, props) {
2582
2640
  );
2583
2641
  const ariaResult = resolveAria(tag, finalProps);
2584
2642
  const attributes = applyFilter(ariaResult.props, bundle.filterProps, options.variantKeys);
2585
- return { className: resolvedClass, attributes };
2643
+ return { className: resolvedClass, attributes, tag, normalizedProps: finalProps };
2586
2644
  }
2587
2645
  function diffAndApplyAttributes(host, state, prevPipelineAttrs, incomingProps) {
2588
2646
  const hasOwn2 = Object.hasOwn;
@@ -2721,7 +2779,11 @@ function createContractComponent(options) {
2721
2779
  get _self() {
2722
2780
  return this;
2723
2781
  }
2724
- _applyPraxis() {
2782
+ // Start with all current DOM attributes so the ARIA engine sees role,
2783
+ // aria-*, and any other pass-through attributes. Shared by render() and
2784
+ // _applyPraxis(), which run at different points in the same update cycle
2785
+ // and can't share a local variable.
2786
+ _buildProps() {
2725
2787
  const self = this._self;
2726
2788
  const props = {};
2727
2789
  iterate.forEach(iterate.items(this.attributes), (attr) => {
@@ -2735,14 +2797,18 @@ function createContractComponent(options) {
2735
2797
  const val = self[key];
2736
2798
  if (val != null) props[key] = val;
2737
2799
  });
2800
+ return props;
2801
+ }
2802
+ _applyPraxis() {
2803
+ const props = this._buildProps();
2738
2804
  diffAndApplyAttributes(this, resolveHostState(looseBundle, props), this._pipelineAttrs, props);
2739
2805
  }
2740
2806
  render() {
2741
2807
  const children = Array.from(this.childNodes);
2808
+ const { tag, normalizedProps } = resolveTagAndNormalizedProps(looseBundle, this._buildProps());
2742
2809
  if (bundle.childrenEvaluator) {
2743
- bundle.childrenEvaluator.evaluate(children);
2810
+ bundle.childrenEvaluator.evaluate(children, { tag, props: normalizedProps });
2744
2811
  }
2745
- const tag = bundle.runtime.resolveTag(this._self.as);
2746
2812
  bundle.runtime.options.htmlChildrenEvaluatorFn?.(tag)?.evaluate(children);
2747
2813
  return html`<slot></slot>`;
2748
2814
  }
@@ -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.
@@ -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)}`);
@@ -1900,10 +1913,22 @@ var RuleValidator = class _RuleValidator extends InvariantBase {
1900
1913
 
1901
1914
  // ../../lib/contract/src/children/children-evaluator.ts
1902
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
+ }
1903
1926
  var ChildrenEvaluator = class extends InvariantBase {
1904
1927
  #context;
1905
- #rules;
1906
- #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;
1907
1932
  #matcher;
1908
1933
  #ruleValidator;
1909
1934
  #exclusiveChildren;
@@ -1911,29 +1936,39 @@ var ChildrenEvaluator = class extends InvariantBase {
1911
1936
  constructor(rules, diagnostics, context = "Component", options = {}) {
1912
1937
  super(diagnostics);
1913
1938
  this.#context = context;
1914
- this.#rules = rules.map((r) => normalizeChildRule(r));
1915
- this.#ruleNames = this.#rules.map((r) => r.name);
1916
1939
  this.#exclusiveChildren = options.exclusiveChildren ?? false;
1917
1940
  this.#allowText = options.allowText ?? true;
1918
- iterate.forEach(this.#rules, (rule) => {
1919
- const { name, position, cardinality } = rule;
1920
- if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1921
- throw new RangeError(
1922
- `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1923
- );
1924
- }
1941
+ const staticRuleInputs = [];
1942
+ const dynamicRuleInputs = [];
1943
+ iterate.forEach(rules, (rule) => {
1944
+ if (isDynamicRule(rule.cardinality)) dynamicRuleInputs.push(rule);
1945
+ else staticRuleInputs.push(rule);
1925
1946
  });
1926
- this.#matcher = new RuleMatcher(this.#rules);
1947
+ this.#dynamicRuleInputs = dynamicRuleInputs;
1948
+ this.#staticRules = staticRuleInputs.map(
1949
+ (r) => normalizeChildRule(r)
1950
+ );
1927
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
+ }
1928
1958
  }
1929
- 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) {
1930
1964
  if (!this.active) return;
1965
+ const { rules, matcher } = this.#resolveRules(context);
1931
1966
  const {
1932
1967
  matrix,
1933
1968
  unexpectedIndices: rawUnexpectedIndices,
1934
1969
  ambiguousIndices
1935
- } = this.#matcher.match(children);
1936
- this.#ruleValidator.validate(this.#rules, matrix, children.length);
1970
+ } = matcher.match(children);
1971
+ this.#ruleValidator.validate(rules, matrix, children.length);
1937
1972
  const unexpectedIndices = new Set(
1938
1973
  [...rawUnexpectedIndices].filter(
1939
1974
  (ci) => isTextLike(children[ci]) ? this.#allowText === false : this.#exclusiveChildren
@@ -1947,11 +1982,28 @@ var ChildrenEvaluator = class extends InvariantBase {
1947
1982
  this.violate(ContractDiagnostics.unexpectedChild(typeName, ci, this.#context));
1948
1983
  } else {
1949
1984
  const matches = matrix.childToRules.forward.get(ci);
1950
- const names = [...matches].map((ri) => this.#ruleNames[ri] ?? `#${ri}`);
1985
+ const names = [...matches].map((ri) => rules[ri]?.name ?? `#${ri}`);
1951
1986
  this.violate(ContractDiagnostics.ambiguousChild(typeName, ci, names, this.#context));
1952
1987
  }
1953
1988
  });
1954
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
+ }
1955
2007
  };
1956
2008
 
1957
2009
  // ../../lib/contract/src/props/get-disabled-props.ts
@@ -2837,8 +2889,14 @@ var Slot = forwardRef(function Slot2({ children, ...slotProps }, ref) {
2837
2889
  Object.assign(Slot, { displayName: SLOT_NAME });
2838
2890
 
2839
2891
  // ../../adapters/preact/src/render.tsx
2840
- function buildRenderState(tag, directives, props, className, children) {
2841
- 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
+ };
2842
2900
  if (children !== void 0) state.children = children;
2843
2901
  return state;
2844
2902
  }
@@ -2863,7 +2921,7 @@ function prepareRenderState(runtime, props, filterProps) {
2863
2921
  ...as !== void 0 && { as },
2864
2922
  ...asChild !== void 0 && { asChild }
2865
2923
  };
2866
- return buildRenderState(tag, directives, filteredProps, resolvedClass, children);
2924
+ return buildRenderState(tag, directives, filteredProps, normalizedProps, resolvedClass, children);
2867
2925
  }
2868
2926
  function warnDiscardedChildren(originalChildren, normalizedChildren, validator) {
2869
2927
  if (!Array.isArray(originalChildren)) return;
@@ -2941,7 +2999,10 @@ function render({
2941
2999
  let normalizedChildren;
2942
3000
  const getNormalizedChildren = () => normalizedChildren ??= normalizeChildren2(state.children);
2943
3001
  if (process.env.NODE_ENV !== "production") {
2944
- childrenEvaluator?.evaluate(getNormalizedChildren());
3002
+ childrenEvaluator?.evaluate(getNormalizedChildren(), {
3003
+ tag: state.tag,
3004
+ props: state.normalizedProps
3005
+ });
2945
3006
  runtime.options.htmlChildrenEvaluatorFn?.(state.tag)?.evaluate(getNormalizedChildren());
2946
3007
  }
2947
3008
  const slotResult = tryRenderAsChild(
@@ -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-D6rRB2gJ.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-D6rRB2gJ.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';