praxis-kit 5.0.0 → 6.1.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)}`);
@@ -653,6 +666,45 @@ var iterate = Object.freeze({
653
666
  values
654
667
  });
655
668
 
669
+ // ../../lib/primitive/src/utils/lru-cache.ts
670
+ var LRUCache = class {
671
+ #maxSize;
672
+ #store = /* @__PURE__ */ new Map();
673
+ constructor(maxSize) {
674
+ if (!Number.isInteger(maxSize) || maxSize < 1) {
675
+ throw new RangeError("LRUCache maxSize must be a positive integer.");
676
+ }
677
+ this.#maxSize = maxSize;
678
+ }
679
+ get(key) {
680
+ if (!this.#store.has(key)) return void 0;
681
+ const value = this.#store.get(key);
682
+ this.#store.delete(key);
683
+ this.#store.set(key, value);
684
+ return value;
685
+ }
686
+ set(key, value) {
687
+ this.#store.delete(key);
688
+ this.#store.set(key, value);
689
+ if (this.#store.size > this.#maxSize) {
690
+ const lru = this.#store.keys().next().value;
691
+ if (lru !== void 0) this.#store.delete(lru);
692
+ }
693
+ }
694
+ has(key) {
695
+ return this.#store.has(key);
696
+ }
697
+ delete(key) {
698
+ return this.#store.delete(key);
699
+ }
700
+ get size() {
701
+ return this.#store.size;
702
+ }
703
+ clear() {
704
+ this.#store.clear();
705
+ }
706
+ };
707
+
656
708
  // ../../lib/primitive/src/utils/merge-refs.ts
657
709
  function mergeRefsCore(...refs) {
658
710
  const active = refs.filter((r) => r != null);
@@ -677,28 +729,6 @@ function mergeProps(defaultProps, props) {
677
729
  };
678
730
  }
679
731
 
680
- // ../../lib/contract/src/strict/invariant-base.ts
681
- var InvariantBase = class {
682
- diagnostics;
683
- constructor(diagnostics) {
684
- this.diagnostics = diagnostics;
685
- }
686
- get active() {
687
- return this.diagnostics.active;
688
- }
689
- violate(input) {
690
- this.diagnostics.error(input);
691
- }
692
- // Always caps at warn — never throws. ARIA 'warning' violations route here
693
- // so they surface even in strict='throw' mode without aborting a render.
694
- warn(input) {
695
- this.diagnostics.warn(input);
696
- }
697
- invariant(condition, input) {
698
- if (!condition) this.violate(input);
699
- }
700
- };
701
-
702
732
  // ../../lib/contract/src/diagnostics/aria.ts
703
733
  import { DiagnosticCategory, DiagnosticCode } from "../_shared/diagnostics.js";
704
734
  var AriaDiagnostics = {
@@ -1055,6 +1085,28 @@ var SlotDiagnostics = {
1055
1085
  }
1056
1086
  };
1057
1087
 
1088
+ // ../../lib/contract/src/strict/invariant-base.ts
1089
+ var InvariantBase = class {
1090
+ diagnostics;
1091
+ constructor(diagnostics) {
1092
+ this.diagnostics = diagnostics;
1093
+ }
1094
+ get active() {
1095
+ return this.diagnostics.active;
1096
+ }
1097
+ violate(input) {
1098
+ this.diagnostics.error(input);
1099
+ }
1100
+ // Always caps at warn — never throws. ARIA 'warning' violations route here
1101
+ // so they surface even in strict='throw' mode without aborting a render.
1102
+ warn(input) {
1103
+ this.diagnostics.warn(input);
1104
+ }
1105
+ invariant(condition, input) {
1106
+ if (!condition) this.violate(input);
1107
+ }
1108
+ };
1109
+
1058
1110
  // ../../lib/contract/src/aria/polymorphic-validator.ts
1059
1111
  var VALID = [{ valid: true }];
1060
1112
  function isIntrinsicTag(tag) {
@@ -1066,8 +1118,7 @@ function omitProp(obj, key) {
1066
1118
  }
1067
1119
  var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1068
1120
  #extraRules;
1069
- #planCache = /* @__PURE__ */ new Map();
1070
- static #MAX_CACHE = 100;
1121
+ #planCache = new LRUCache(100);
1071
1122
  // Memoized AriaFix objects keyed by attribute name — the ARIA attribute set is
1072
1123
  // finite so this Map is bounded and avoids recreating closures on every cache miss.
1073
1124
  static #removeAttributeFixCache = /* @__PURE__ */ new Map();
@@ -1223,8 +1274,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1223
1274
  if (!isNull(key)) {
1224
1275
  const cached = this.#planCache.get(key);
1225
1276
  if (cached !== void 0) {
1226
- this.#planCache.delete(key);
1227
- this.#planCache.set(key, cached);
1228
1277
  if (cached.violations.length > 0) this.report(cached.violations);
1229
1278
  return {
1230
1279
  props: _AriaPolicyEngine.#applyPlan(props, cached.removals, cached.updates),
@@ -1241,10 +1290,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1241
1290
  );
1242
1291
  const plan = { removals, updates, violations: result.violations };
1243
1292
  this.#planCache.set(key, plan);
1244
- if (this.#planCache.size > _AriaPolicyEngine.#MAX_CACHE) {
1245
- const lru = this.#planCache.keys().next().value;
1246
- if (lru !== void 0) this.#planCache.delete(lru);
1247
- }
1248
1293
  }
1249
1294
  return result;
1250
1295
  }
@@ -1900,10 +1945,22 @@ var RuleValidator = class _RuleValidator extends InvariantBase {
1900
1945
 
1901
1946
  // ../../lib/contract/src/children/children-evaluator.ts
1902
1947
  var isTextLike = (child) => isString(child) || isNumber(child);
1948
+ function checkPositionCardinalityInvariant(rules, context) {
1949
+ iterate.forEach(rules, (rule) => {
1950
+ const { name, position, cardinality } = rule;
1951
+ if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1952
+ throw new RangeError(
1953
+ `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1954
+ );
1955
+ }
1956
+ });
1957
+ }
1903
1958
  var ChildrenEvaluator = class extends InvariantBase {
1904
1959
  #context;
1905
- #rules;
1906
- #ruleNames;
1960
+ /** Rules with a static cardinality — normalized once, reused on every evaluate() call. */
1961
+ #staticRules;
1962
+ /** Rules whose cardinality is `dynamic(...)` — re-resolved against a ChildRuleContext per call. */
1963
+ #dynamicRuleInputs;
1907
1964
  #matcher;
1908
1965
  #ruleValidator;
1909
1966
  #exclusiveChildren;
@@ -1911,29 +1968,39 @@ var ChildrenEvaluator = class extends InvariantBase {
1911
1968
  constructor(rules, diagnostics, context = "Component", options = {}) {
1912
1969
  super(diagnostics);
1913
1970
  this.#context = context;
1914
- this.#rules = rules.map((r) => normalizeChildRule(r));
1915
- this.#ruleNames = this.#rules.map((r) => r.name);
1916
1971
  this.#exclusiveChildren = options.exclusiveChildren ?? false;
1917
1972
  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
- }
1973
+ const staticRuleInputs = [];
1974
+ const dynamicRuleInputs = [];
1975
+ iterate.forEach(rules, (rule) => {
1976
+ if (isDynamicRule(rule.cardinality)) dynamicRuleInputs.push(rule);
1977
+ else staticRuleInputs.push(rule);
1925
1978
  });
1926
- this.#matcher = new RuleMatcher(this.#rules);
1979
+ this.#dynamicRuleInputs = dynamicRuleInputs;
1980
+ this.#staticRules = staticRuleInputs.map(
1981
+ (r) => normalizeChildRule(r)
1982
+ );
1927
1983
  this.#ruleValidator = new RuleValidator(context, diagnostics);
1984
+ if (this.#dynamicRuleInputs.length === 0) {
1985
+ checkPositionCardinalityInvariant(this.#staticRules, context);
1986
+ this.#matcher = new RuleMatcher(this.#staticRules);
1987
+ } else {
1988
+ this.#matcher = void 0;
1989
+ }
1928
1990
  }
1929
- evaluate(children) {
1991
+ /**
1992
+ * @param context Required when any rule has a `dynamic(...)` cardinality —
1993
+ * supplies the resolved tag/props those rules are evaluated against.
1994
+ */
1995
+ evaluate(children, context) {
1930
1996
  if (!this.active) return;
1997
+ const { rules, matcher } = this.#resolveRules(context);
1931
1998
  const {
1932
1999
  matrix,
1933
2000
  unexpectedIndices: rawUnexpectedIndices,
1934
2001
  ambiguousIndices
1935
- } = this.#matcher.match(children);
1936
- this.#ruleValidator.validate(this.#rules, matrix, children.length);
2002
+ } = matcher.match(children);
2003
+ this.#ruleValidator.validate(rules, matrix, children.length);
1937
2004
  const unexpectedIndices = new Set(
1938
2005
  [...rawUnexpectedIndices].filter(
1939
2006
  (ci) => isTextLike(children[ci]) ? this.#allowText === false : this.#exclusiveChildren
@@ -1947,11 +2014,28 @@ var ChildrenEvaluator = class extends InvariantBase {
1947
2014
  this.violate(ContractDiagnostics.unexpectedChild(typeName, ci, this.#context));
1948
2015
  } else {
1949
2016
  const matches = matrix.childToRules.forward.get(ci);
1950
- const names = [...matches].map((ri) => this.#ruleNames[ri] ?? `#${ri}`);
2017
+ const names = [...matches].map((ri) => rules[ri]?.name ?? `#${ri}`);
1951
2018
  this.violate(ContractDiagnostics.ambiguousChild(typeName, ci, names, this.#context));
1952
2019
  }
1953
2020
  });
1954
2021
  }
2022
+ #resolveRules(context) {
2023
+ if (this.#dynamicRuleInputs.length === 0) {
2024
+ return { rules: this.#staticRules, matcher: this.#matcher };
2025
+ }
2026
+ if (context === void 0) {
2027
+ throw new RangeError(
2028
+ `ChildrenEvaluator [${this.#context}]: rule(s) have a dynamic(...) cardinality \u2014 evaluate() requires a context argument to resolve them.`
2029
+ );
2030
+ }
2031
+ const resolvedDynamicRules = this.#dynamicRuleInputs.map((r) => {
2032
+ const cardinality = resolveRule(r.cardinality, context);
2033
+ return normalizeChildRule({ ...r, cardinality });
2034
+ });
2035
+ checkPositionCardinalityInvariant(resolvedDynamicRules, this.#context);
2036
+ const rules = [...this.#staticRules, ...resolvedDynamicRules];
2037
+ return { rules, matcher: new RuleMatcher(rules) };
2038
+ }
1955
2039
  };
1956
2040
 
1957
2041
  // ../../lib/contract/src/props/get-disabled-props.ts
@@ -2280,7 +2364,7 @@ function cva2(base, config) {
2280
2364
  // ../../lib/styling/src/static-class-resolver.ts
2281
2365
  var StaticClassResolver = class {
2282
2366
  #baseClass;
2283
- #cache = /* @__PURE__ */ new Map();
2367
+ #cache = new LRUCache(200);
2284
2368
  #resolveTag;
2285
2369
  constructor(baseClass, tagMap) {
2286
2370
  this.#baseClass = Array.isArray(baseClass) ? baseClass.join(" ") : baseClass;
@@ -2294,17 +2378,9 @@ var StaticClassResolver = class {
2294
2378
  resolve(tag, skipTagMap = false) {
2295
2379
  if (typeof tag !== "string" || skipTagMap) return this.#baseClass;
2296
2380
  const cached = this.#cache.get(tag);
2297
- if (cached !== void 0) {
2298
- this.#cache.delete(tag);
2299
- this.#cache.set(tag, cached);
2300
- return cached;
2301
- }
2381
+ if (cached !== void 0) return cached;
2302
2382
  const result = this.#resolveTag(tag);
2303
2383
  this.#cache.set(tag, result);
2304
- if (this.#cache.size > 200) {
2305
- const lru = this.#cache.keys().next().value;
2306
- if (lru !== void 0) this.#cache.delete(lru);
2307
- }
2308
2384
  return result;
2309
2385
  }
2310
2386
  };
@@ -2315,7 +2391,7 @@ var VariantClassResolver = class _VariantClassResolver {
2315
2391
  #recipeMap;
2316
2392
  #variantKeys;
2317
2393
  #precomputedClasses;
2318
- #cache = /* @__PURE__ */ new Map();
2394
+ #cache = new LRUCache(1e3);
2319
2395
  constructor(cvaFn, recipeMap, variantKeys, precomputedClasses) {
2320
2396
  this.#cvaFn = cvaFn ?? null;
2321
2397
  this.#recipeMap = Object.freeze(recipeMap ?? {});
@@ -2330,17 +2406,9 @@ var VariantClassResolver = class _VariantClassResolver {
2330
2406
  if (precomputed !== void 0) return precomputed;
2331
2407
  }
2332
2408
  const cached = this.#cache.get(cacheKey);
2333
- if (cached !== void 0) {
2334
- this.#cache.delete(cacheKey);
2335
- this.#cache.set(cacheKey, cached);
2336
- return cached;
2337
- }
2409
+ if (cached !== void 0) return cached;
2338
2410
  const result = this.#compute(props, recipe);
2339
2411
  this.#cache.set(cacheKey, result);
2340
- if (this.#cache.size > 1e3) {
2341
- const lru = this.#cache.keys().next().value;
2342
- if (lru !== void 0) this.#cache.delete(lru);
2343
- }
2344
2412
  return result;
2345
2413
  }
2346
2414
  #compute(props, recipe) {
@@ -2837,8 +2905,14 @@ var Slot = forwardRef(function Slot2({ children, ...slotProps }, ref) {
2837
2905
  Object.assign(Slot, { displayName: SLOT_NAME });
2838
2906
 
2839
2907
  // ../../adapters/preact/src/render.tsx
2840
- function buildRenderState(tag, directives, props, className, children) {
2841
- const state = { tag, directives, props, className };
2908
+ function buildRenderState(tag, directives, props, normalizedProps, className, children) {
2909
+ const state = {
2910
+ tag,
2911
+ directives,
2912
+ props,
2913
+ normalizedProps,
2914
+ className
2915
+ };
2842
2916
  if (children !== void 0) state.children = children;
2843
2917
  return state;
2844
2918
  }
@@ -2863,7 +2937,7 @@ function prepareRenderState(runtime, props, filterProps) {
2863
2937
  ...as !== void 0 && { as },
2864
2938
  ...asChild !== void 0 && { asChild }
2865
2939
  };
2866
- return buildRenderState(tag, directives, filteredProps, resolvedClass, children);
2940
+ return buildRenderState(tag, directives, filteredProps, normalizedProps, resolvedClass, children);
2867
2941
  }
2868
2942
  function warnDiscardedChildren(originalChildren, normalizedChildren, validator) {
2869
2943
  if (!Array.isArray(originalChildren)) return;
@@ -2941,7 +3015,10 @@ function render({
2941
3015
  let normalizedChildren;
2942
3016
  const getNormalizedChildren = () => normalizedChildren ??= normalizeChildren2(state.children);
2943
3017
  if (process.env.NODE_ENV !== "production") {
2944
- childrenEvaluator?.evaluate(getNormalizedChildren());
3018
+ childrenEvaluator?.evaluate(getNormalizedChildren(), {
3019
+ tag: state.tag,
3020
+ props: state.normalizedProps
3021
+ });
2945
3022
  runtime.options.htmlChildrenEvaluatorFn?.(state.tag)?.evaluate(getNormalizedChildren());
2946
3023
  }
2947
3024
  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';
@@ -12,7 +12,7 @@ import {
12
12
  makeCloneSlotChild,
13
13
  mergeRefs,
14
14
  render
15
- } from "../chunk-SKAY22CZ.js";
15
+ } from "../chunk-VUULGK5M.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-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, l as defineContractComponent, m as mergeRefs } from '../react-options-D6rRB2gJ.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-SKAY22CZ.js";
16
+ } from "../chunk-VUULGK5M.js";
17
17
 
18
18
  // ../../adapters/react/src/legacy/create-contract-component.ts
19
19
  import { forwardRef as forwardRef2 } from "react";
@@ -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.
@@ -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.