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.
package/dist/vue/index.js CHANGED
@@ -456,6 +456,19 @@ function makeResolveTag(defaultTag) {
456
456
  };
457
457
  }
458
458
 
459
+ // ../../lib/primitive/src/rule/rule-brand.ts
460
+ var RULE_BRAND = /* @__PURE__ */ Symbol("praxis-kit/dynamic-rule");
461
+
462
+ // ../../lib/primitive/src/rule/is-dynamic-rule.ts
463
+ function isDynamicRule(rule) {
464
+ return isObject(rule, true) && rule[RULE_BRAND] === true;
465
+ }
466
+
467
+ // ../../lib/primitive/src/rule/resolve-rule.ts
468
+ function resolveRule(rule, context) {
469
+ return isDynamicRule(rule) ? rule.resolve(context) : rule;
470
+ }
471
+
459
472
  // ../../lib/primitive/src/utils/assert-never.ts
460
473
  function assertNever(value) {
461
474
  throw new Error(`Unexpected value: ${String(value)}`);
@@ -1867,32 +1880,68 @@ var RuleValidator = class _RuleValidator extends InvariantBase {
1867
1880
  };
1868
1881
 
1869
1882
  // ../../lib/contract/src/children/children-evaluator.ts
1883
+ var isTextLike = (child) => isString(child) || isNumber(child);
1884
+ function checkPositionCardinalityInvariant(rules, context) {
1885
+ iterate.forEach(rules, (rule) => {
1886
+ const { name, position, cardinality } = rule;
1887
+ if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1888
+ throw new RangeError(
1889
+ `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1890
+ );
1891
+ }
1892
+ });
1893
+ }
1870
1894
  var ChildrenEvaluator = class extends InvariantBase {
1871
1895
  #context;
1872
- #rules;
1873
- #ruleNames;
1896
+ /** Rules with a static cardinality — normalized once, reused on every evaluate() call. */
1897
+ #staticRules;
1898
+ /** Rules whose cardinality is `dynamic(...)` — re-resolved against a ChildRuleContext per call. */
1899
+ #dynamicRuleInputs;
1874
1900
  #matcher;
1875
1901
  #ruleValidator;
1876
- constructor(rules, diagnostics, context = "Component") {
1902
+ #exclusiveChildren;
1903
+ #allowText;
1904
+ constructor(rules, diagnostics, context = "Component", options = {}) {
1877
1905
  super(diagnostics);
1878
1906
  this.#context = context;
1879
- this.#rules = rules.map((r) => normalizeChildRule(r));
1880
- this.#ruleNames = this.#rules.map((r) => r.name);
1881
- iterate.forEach(this.#rules, (rule) => {
1882
- const { name, position, cardinality } = rule;
1883
- if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1884
- throw new RangeError(
1885
- `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1886
- );
1887
- }
1907
+ this.#exclusiveChildren = options.exclusiveChildren ?? false;
1908
+ this.#allowText = options.allowText ?? true;
1909
+ const staticRuleInputs = [];
1910
+ const dynamicRuleInputs = [];
1911
+ iterate.forEach(rules, (rule) => {
1912
+ if (isDynamicRule(rule.cardinality)) dynamicRuleInputs.push(rule);
1913
+ else staticRuleInputs.push(rule);
1888
1914
  });
1889
- this.#matcher = new RuleMatcher(this.#rules);
1915
+ this.#dynamicRuleInputs = dynamicRuleInputs;
1916
+ this.#staticRules = staticRuleInputs.map(
1917
+ (r) => normalizeChildRule(r)
1918
+ );
1890
1919
  this.#ruleValidator = new RuleValidator(context, diagnostics);
1920
+ if (this.#dynamicRuleInputs.length === 0) {
1921
+ checkPositionCardinalityInvariant(this.#staticRules, context);
1922
+ this.#matcher = new RuleMatcher(this.#staticRules);
1923
+ } else {
1924
+ this.#matcher = void 0;
1925
+ }
1891
1926
  }
1892
- evaluate(children) {
1927
+ /**
1928
+ * @param context Required when any rule has a `dynamic(...)` cardinality —
1929
+ * supplies the resolved tag/props those rules are evaluated against.
1930
+ */
1931
+ evaluate(children, context) {
1893
1932
  if (!this.active) return;
1894
- const { matrix, unexpectedIndices, ambiguousIndices } = this.#matcher.match(children);
1895
- this.#ruleValidator.validate(this.#rules, matrix, children.length);
1933
+ const { rules, matcher } = this.#resolveRules(context);
1934
+ const {
1935
+ matrix,
1936
+ unexpectedIndices: rawUnexpectedIndices,
1937
+ ambiguousIndices
1938
+ } = matcher.match(children);
1939
+ this.#ruleValidator.validate(rules, matrix, children.length);
1940
+ const unexpectedIndices = new Set(
1941
+ [...rawUnexpectedIndices].filter(
1942
+ (ci) => isTextLike(children[ci]) ? this.#allowText === false : this.#exclusiveChildren
1943
+ )
1944
+ );
1896
1945
  if (unexpectedIndices.size === 0 && ambiguousIndices.size === 0) return;
1897
1946
  const violating = [...unexpectedIndices, ...ambiguousIndices].sort((a, b) => a - b);
1898
1947
  iterate.forEach(violating, (ci) => {
@@ -1901,11 +1950,28 @@ var ChildrenEvaluator = class extends InvariantBase {
1901
1950
  this.violate(ContractDiagnostics.unexpectedChild(typeName, ci, this.#context));
1902
1951
  } else {
1903
1952
  const matches = matrix.childToRules.forward.get(ci);
1904
- const names = [...matches].map((ri) => this.#ruleNames[ri] ?? `#${ri}`);
1953
+ const names = [...matches].map((ri) => rules[ri]?.name ?? `#${ri}`);
1905
1954
  this.violate(ContractDiagnostics.ambiguousChild(typeName, ci, names, this.#context));
1906
1955
  }
1907
1956
  });
1908
1957
  }
1958
+ #resolveRules(context) {
1959
+ if (this.#dynamicRuleInputs.length === 0) {
1960
+ return { rules: this.#staticRules, matcher: this.#matcher };
1961
+ }
1962
+ if (context === void 0) {
1963
+ throw new RangeError(
1964
+ `ChildrenEvaluator [${this.#context}]: rule(s) have a dynamic(...) cardinality \u2014 evaluate() requires a context argument to resolve them.`
1965
+ );
1966
+ }
1967
+ const resolvedDynamicRules = this.#dynamicRuleInputs.map((r) => {
1968
+ const cardinality = resolveRule(r.cardinality, context);
1969
+ return normalizeChildRule({ ...r, cardinality });
1970
+ });
1971
+ checkPositionCardinalityInvariant(resolvedDynamicRules, this.#context);
1972
+ const rules = [...this.#staticRules, ...resolvedDynamicRules];
1973
+ return { rules, matcher: new RuleMatcher(rules) };
1974
+ }
1909
1975
  };
1910
1976
 
1911
1977
  // ../../lib/contract/src/props/get-disabled-props.ts
@@ -2014,8 +2080,11 @@ function metadata(name = "metadata") {
2014
2080
  function firstOptional(name, tag) {
2015
2081
  return { name, match: isTag(tag), cardinality: { max: 1 }, position: "first" };
2016
2082
  }
2017
- function contract(children) {
2018
- return { diagnostics: warnDiagnostics, children };
2083
+ function contract(children, options) {
2084
+ return { diagnostics: warnDiagnostics, children, ...options };
2085
+ }
2086
+ function closedContract(children) {
2087
+ return contract(children, { exclusiveChildren: true });
2019
2088
  }
2020
2089
  function ariaContract(aria) {
2021
2090
  return { diagnostics: warnDiagnostics, aria };
@@ -2041,8 +2110,10 @@ var VOID_TAGS = [
2041
2110
  ];
2042
2111
  var TEXT_ONLY_TAGS = ["option", "script", "style", "textarea", "title"];
2043
2112
  var LANDMARK_TAGS = ["article", "aside", "footer", "header", "main", "nav"];
2044
- var listContract = contract([{ name: "list-item", match: isTag("li", ...METADATA_TAGS) }]);
2045
- var tableContract = contract([
2113
+ var listContract = closedContract([
2114
+ { name: "list-item", match: isTag("li", ...METADATA_TAGS) }
2115
+ ]);
2116
+ var tableContract = closedContract([
2046
2117
  firstOptional("caption", "caption"),
2047
2118
  { name: "colgroup", match: isTag("colgroup") },
2048
2119
  { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
@@ -2050,29 +2121,31 @@ var tableContract = contract([
2050
2121
  { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
2051
2122
  { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2052
2123
  ]);
2053
- var tableBodyContract = contract([
2124
+ var tableBodyContract = closedContract([
2054
2125
  { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
2055
2126
  ]);
2056
- var tableRowContract = contract([
2127
+ var tableRowContract = closedContract([
2057
2128
  { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
2058
2129
  ]);
2059
- var colgroupContract = contract([{ name: "column", match: isTag("col", "template") }]);
2060
- var dlContract = contract([
2130
+ var colgroupContract = closedContract([
2131
+ { name: "column", match: isTag("col", "template") }
2132
+ ]);
2133
+ var dlContract = closedContract([
2061
2134
  { name: "term", match: isTag("dt") },
2062
2135
  { name: "description", match: isTag("dd") },
2063
2136
  { name: "group", match: isTag("div") },
2064
2137
  metadata()
2065
2138
  ]);
2066
- var selectContract = contract([
2139
+ var selectContract = closedContract([
2067
2140
  { name: "option", match: isTag("option", "optgroup", "hr", ...METADATA_TAGS) }
2068
2141
  ]);
2069
- var optgroupContract = contract([
2142
+ var optgroupContract = closedContract([
2070
2143
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
2071
2144
  ]);
2072
- var datalistContract = contract([
2145
+ var datalistContract = closedContract([
2073
2146
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
2074
2147
  ]);
2075
- var pictureContract = contract([
2148
+ var pictureContract = closedContract([
2076
2149
  { name: "source", match: isTag("source", ...METADATA_TAGS) },
2077
2150
  { name: "image", match: isTag("img"), cardinality: { min: 1, max: 1 }, position: "last" }
2078
2151
  ]);
@@ -2088,23 +2161,18 @@ var mediaContract = contract([
2088
2161
  metadata(),
2089
2162
  { name: "content", match: isOpenContent("source", "track", ...METADATA_TAGS) }
2090
2163
  ]);
2091
- var headContract = contract([
2164
+ var headContract = closedContract([
2092
2165
  {
2093
2166
  name: "metadata",
2094
2167
  match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
2095
2168
  }
2096
2169
  ]);
2097
- var htmlContract = contract([
2170
+ var htmlContract = closedContract([
2098
2171
  { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
2099
2172
  { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
2100
2173
  ]);
2101
- var voidContract = contract([]);
2102
- var textOnlyContract = contract([
2103
- {
2104
- name: "text",
2105
- match: (child) => isString(child) || isNumber(child)
2106
- }
2107
- ]);
2174
+ var voidContract = contract([], { exclusiveChildren: true, allowText: false });
2175
+ var textOnlyContract = contract([], { exclusiveChildren: true });
2108
2176
  var landmarkContract = ariaContract([landmarkRoleRule, landmarkNameAdvisory]);
2109
2177
  var dialogContract = ariaContract([requireAccessibleName]);
2110
2178
  var menuContract = ariaContract([requireAccessibleName]);
@@ -2149,9 +2217,15 @@ var htmlContracts = {
2149
2217
  var htmlDiagnostics = warnDiagnostics2;
2150
2218
  function buildEvaluatorMap() {
2151
2219
  const map2 = /* @__PURE__ */ new Map();
2152
- iterate.forEachEntry(htmlContracts, (tag, { children }) => {
2153
- if (children?.length) {
2154
- map2.set(tag, new ChildrenEvaluator(children, htmlDiagnostics, `<${tag}>`));
2220
+ iterate.forEachEntry(htmlContracts, (tag, { children, exclusiveChildren, allowText }) => {
2221
+ if (children?.length || exclusiveChildren || allowText === false) {
2222
+ map2.set(
2223
+ tag,
2224
+ new ChildrenEvaluator(children ?? [], htmlDiagnostics, `<${tag}>`, {
2225
+ exclusiveChildren,
2226
+ allowText
2227
+ })
2228
+ );
2155
2229
  }
2156
2230
  });
2157
2231
  return map2;
@@ -2362,7 +2436,7 @@ function definePipeline(factory) {
2362
2436
  }
2363
2437
 
2364
2438
  // ../core/src/options/resolve-factory-options.ts
2365
- import { silentDiagnostics } from "../_shared/diagnostics.js";
2439
+ import { resolveDiagnostics, silentDiagnostics } from "../_shared/diagnostics.js";
2366
2440
  var EMPTY_VARIANT_KEYS = /* @__PURE__ */ new Set();
2367
2441
  function composeNormalizers(normalizers, fn) {
2368
2442
  if (!normalizers?.length) return fn;
@@ -2383,7 +2457,7 @@ function resolveFactoryOptions(options = {}) {
2383
2457
  const variantKeys = styling?.variants === void 0 ? EMPTY_VARIANT_KEYS : new Set(Object.keys(styling.variants));
2384
2458
  return Object.freeze({
2385
2459
  defaultTag: options.tag ?? "div",
2386
- diagnostics: enforcement?.diagnostics ?? silentDiagnostics,
2460
+ diagnostics: resolveDiagnostics(enforcement?.diagnostics, silentDiagnostics),
2387
2461
  variantKeys,
2388
2462
  ...whenDefined("displayName", options.name),
2389
2463
  ...whenDefined("defaultProps", options.defaults),
@@ -2396,6 +2470,8 @@ function resolveFactoryOptions(options = {}) {
2396
2470
  ...whenDefined("normalizeFn", composedNormalizeFn),
2397
2471
  ...whenDefined("ariaRules", enforcement?.aria),
2398
2472
  ...whenDefined("childRules", enforcement?.children),
2473
+ ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
2474
+ ...whenDefined("allowText", enforcement?.allowText),
2399
2475
  ...whenDefined("allowedAs", enforcement?.allowedAs),
2400
2476
  ...whenDefined("precomputedClasses", styling?.precomputedClasses)
2401
2477
  });
@@ -2580,16 +2656,22 @@ function buildCoreRuntime(normalized) {
2580
2656
  }
2581
2657
 
2582
2658
  // ../../lib/adapter-utils/src/runtime/build-engines.ts
2583
- function buildEngines(diagnostics, childRules, context) {
2584
- return childRules?.length ? { childrenEvaluator: new ChildrenEvaluator(childRules, diagnostics, context) } : {};
2659
+ function buildEngines(diagnostics, childRules, context, childrenOptions) {
2660
+ const { exclusiveChildren, allowText } = childrenOptions ?? {};
2661
+ return childRules?.length || exclusiveChildren || allowText === false ? {
2662
+ childrenEvaluator: new ChildrenEvaluator(childRules ?? [], diagnostics, context, {
2663
+ exclusiveChildren,
2664
+ allowText
2665
+ })
2666
+ } : {};
2585
2667
  }
2586
2668
 
2587
2669
  // ../../lib/adapter-utils/src/runtime/resolve-adapter-common-options.ts
2588
- import { throwDiagnostics as throwDiagnostics2, silentDiagnostics as silentDiagnostics3 } from "../_shared/diagnostics.js";
2670
+ import { throwDiagnostics as throwDiagnostics2, silentDiagnostics as silentDiagnostics3, resolveDiagnostics as resolveDiagnostics2 } from "../_shared/diagnostics.js";
2589
2671
  function resolveAdapterCommonOptions(options, defaultName = "PolymorphicComponent", defaultDiagnostics = throwDiagnostics2) {
2590
2672
  return {
2591
2673
  name: options.name ?? defaultName,
2592
- diagnostics: options.enforcement?.diagnostics ?? defaultDiagnostics
2674
+ diagnostics: resolveDiagnostics2(options.enforcement?.diagnostics, defaultDiagnostics)
2593
2675
  };
2594
2676
  }
2595
2677
 
@@ -2646,7 +2728,10 @@ function buildRuntime(options) {
2646
2728
  const normalized = normalizeOptions(options);
2647
2729
  const { runtime, ownedKeys } = buildCoreRuntime(normalized);
2648
2730
  const { diagnostics, enforcement, filterProps: userFilter, name } = normalized;
2649
- const { childrenEvaluator } = buildEngines(diagnostics, enforcement?.children, name);
2731
+ const { childrenEvaluator } = buildEngines(diagnostics, enforcement?.children, name, {
2732
+ exclusiveChildren: enforcement?.exclusiveChildren,
2733
+ allowText: enforcement?.allowText
2734
+ });
2650
2735
  const filterProps = composeFilter(ownedKeys, userFilter);
2651
2736
  const slotValidator = new SlotValidator(name, diagnostics, "VNode");
2652
2737
  const built = {
@@ -2752,6 +2837,7 @@ function prepareRenderState(runtime, attrs, filterProps) {
2752
2837
  ...asChild !== void 0 && { asChild }
2753
2838
  },
2754
2839
  props: filteredProps,
2840
+ normalizedProps,
2755
2841
  className
2756
2842
  };
2757
2843
  }
@@ -2798,7 +2884,8 @@ function render({
2798
2884
  }) {
2799
2885
  const { vnodes: children, discarded } = normalizeChildren(slots);
2800
2886
  if (process.env.NODE_ENV !== "production") {
2801
- childrenEvaluator?.evaluate(children);
2887
+ childrenEvaluator?.evaluate(children, { tag: state.tag, props: state.normalizedProps });
2888
+ runtime.options.htmlChildrenEvaluatorFn?.(state.tag)?.evaluate(children);
2802
2889
  }
2803
2890
  const slotResult = tryRenderAsChild(state, children, discarded, slotValidator);
2804
2891
  return slotResult ?? renderIntrinsic(state, runtime, slots);
@@ -1,5 +1,5 @@
1
1
  import { RequireAtLeastOne, Simplify, ReadonlyDeep } from 'type-fest';
2
- import { Diagnostics, DiagnosticInput } from '../_shared/diagnostics.js';
2
+ import { Diagnostics, DiagnosticInput, DiagnosticsMode } from '../_shared/diagnostics.js';
3
3
 
4
4
  type StringMap<T = unknown> = Record<string, T>;
5
5
  type AnyRecord = StringMap<unknown>;
@@ -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.
@@ -195,9 +221,25 @@ type AriaRule<C extends AriaContext = AriaContext> = (context: C) => readonly Ar
195
221
  type PropNormalizer = (props: Readonly<AnyRecord & IntrinsicProps>) => Partial<AnyRecord & IntrinsicProps>;
196
222
 
197
223
  type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
198
- readonly diagnostics?: Diagnostics;
224
+ /**
225
+ * Accepts a preset name (`'warn'`, `'throw'`, `'silent'`) or a full `Diagnostics`
226
+ * instance for custom reporting/policy. The string form needs no import from
227
+ * `@praxis-kit/diagnostics`.
228
+ */
229
+ readonly diagnostics?: Diagnostics | DiagnosticsMode;
199
230
  readonly aria?: readonly AriaRule[];
200
231
  readonly children?: readonly ChildRuleInput[];
232
+ /**
233
+ * When true, only children matching a `children` rule (or text, per `allowText`)
234
+ * are valid — anything else is rejected. Default: false (open — children not
235
+ * matching any rule are allowed).
236
+ */
237
+ readonly exclusiveChildren?: boolean;
238
+ /**
239
+ * When false, text/number child nodes are rejected regardless of exclusiveChildren
240
+ * or any listed rule. Default: true.
241
+ */
242
+ readonly allowText?: boolean;
201
243
  readonly props?: readonly PropNormalizer[];
202
244
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
203
245
  readonly allowedAs?: readonly TAllowed[];