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.
@@ -28,9 +28,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  mod
29
29
  ));
30
30
 
31
- // ../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/deepMerge.js
31
+ // ../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/deepMerge.js
32
32
  var require_deepMerge = __commonJS({
33
- "../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/deepMerge.js"(exports) {
33
+ "../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/deepMerge.js"(exports) {
34
34
  "use strict";
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.isObjectNotArray = isObjectNotArray;
@@ -63,9 +63,9 @@ var require_deepMerge = __commonJS({
63
63
  }
64
64
  });
65
65
 
66
- // ../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/applyDefault.js
66
+ // ../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/applyDefault.js
67
67
  var require_applyDefault = __commonJS({
68
- "../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/applyDefault.js"(exports) {
68
+ "../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/applyDefault.js"(exports) {
69
69
  "use strict";
70
70
  Object.defineProperty(exports, "__esModule", { value: true });
71
71
  exports.applyDefault = applyDefault;
@@ -90,9 +90,9 @@ var require_applyDefault = __commonJS({
90
90
  }
91
91
  });
92
92
 
93
- // ../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/parserSeemsToBeTSESLint.js
93
+ // ../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/parserSeemsToBeTSESLint.js
94
94
  var require_parserSeemsToBeTSESLint = __commonJS({
95
- "../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/parserSeemsToBeTSESLint.js"(exports) {
95
+ "../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/parserSeemsToBeTSESLint.js"(exports) {
96
96
  "use strict";
97
97
  Object.defineProperty(exports, "__esModule", { value: true });
98
98
  exports.parserSeemsToBeTSESLint = parserSeemsToBeTSESLint;
@@ -102,9 +102,9 @@ var require_parserSeemsToBeTSESLint = __commonJS({
102
102
  }
103
103
  });
104
104
 
105
- // ../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/getParserServices.js
105
+ // ../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/getParserServices.js
106
106
  var require_getParserServices = __commonJS({
107
- "../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/getParserServices.js"(exports) {
107
+ "../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/getParserServices.js"(exports) {
108
108
  "use strict";
109
109
  Object.defineProperty(exports, "__esModule", { value: true });
110
110
  exports.getParserServices = getParserServices;
@@ -135,17 +135,17 @@ var require_getParserServices = __commonJS({
135
135
  }
136
136
  });
137
137
 
138
- // ../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/InferTypesFromRule.js
138
+ // ../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/InferTypesFromRule.js
139
139
  var require_InferTypesFromRule = __commonJS({
140
- "../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/InferTypesFromRule.js"(exports) {
140
+ "../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/InferTypesFromRule.js"(exports) {
141
141
  "use strict";
142
142
  Object.defineProperty(exports, "__esModule", { value: true });
143
143
  }
144
144
  });
145
145
 
146
- // ../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/nullThrows.js
146
+ // ../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/nullThrows.js
147
147
  var require_nullThrows = __commonJS({
148
- "../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/nullThrows.js"(exports) {
148
+ "../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/nullThrows.js"(exports) {
149
149
  "use strict";
150
150
  Object.defineProperty(exports, "__esModule", { value: true });
151
151
  exports.NullThrowsReasons = void 0;
@@ -163,9 +163,9 @@ var require_nullThrows = __commonJS({
163
163
  }
164
164
  });
165
165
 
166
- // ../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/RuleCreator.js
166
+ // ../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/RuleCreator.js
167
167
  var require_RuleCreator = __commonJS({
168
- "../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/RuleCreator.js"(exports) {
168
+ "../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/RuleCreator.js"(exports) {
169
169
  "use strict";
170
170
  Object.defineProperty(exports, "__esModule", { value: true });
171
171
  exports.RuleCreator = RuleCreator8;
@@ -211,9 +211,9 @@ var require_RuleCreator = __commonJS({
211
211
  }
212
212
  });
213
213
 
214
- // ../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/index.js
214
+ // ../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/index.js
215
215
  var require_eslint_utils = __commonJS({
216
- "../../node_modules/.pnpm/@typescript-eslint+utils@8.60.0_eslint@10.6.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/index.js"(exports) {
216
+ "../../node_modules/.pnpm/@typescript-eslint+utils@8.63.0_eslint@10.7.0_jiti@2.7.0__typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/index.js"(exports) {
217
217
  "use strict";
218
218
  var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
219
219
  if (k2 === void 0) k2 = k;
@@ -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
  import { LitElement } from 'lit';
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.
@@ -196,9 +222,25 @@ type AriaRule<C extends AriaContext = AriaContext> = (context: C) => readonly Ar
196
222
  type PropNormalizer = (props: Readonly<AnyRecord & IntrinsicProps>) => Partial<AnyRecord & IntrinsicProps>;
197
223
 
198
224
  type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
199
- readonly diagnostics?: Diagnostics;
225
+ /**
226
+ * Accepts a preset name (`'warn'`, `'throw'`, `'silent'`) or a full `Diagnostics`
227
+ * instance for custom reporting/policy. The string form needs no import from
228
+ * `@praxis-kit/diagnostics`.
229
+ */
230
+ readonly diagnostics?: Diagnostics | DiagnosticsMode;
200
231
  readonly aria?: readonly AriaRule[];
201
232
  readonly children?: readonly ChildRuleInput[];
233
+ /**
234
+ * When true, only children matching a `children` rule (or text, per `allowText`)
235
+ * are valid — anything else is rejected. Default: false (open — children not
236
+ * matching any rule are allowed).
237
+ */
238
+ readonly exclusiveChildren?: boolean;
239
+ /**
240
+ * When false, text/number child nodes are rejected regardless of exclusiveChildren
241
+ * or any listed rule. Default: true.
242
+ */
243
+ readonly allowText?: boolean;
202
244
  readonly props?: readonly PropNormalizer[];
203
245
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
204
246
  readonly allowedAs?: readonly TAllowed[];
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)}`);
@@ -1714,32 +1727,68 @@ var RuleValidator = class _RuleValidator extends InvariantBase {
1714
1727
  };
1715
1728
 
1716
1729
  // ../../lib/contract/src/children/children-evaluator.ts
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
+ }
1717
1741
  var ChildrenEvaluator = class extends InvariantBase {
1718
1742
  #context;
1719
- #rules;
1720
- #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;
1721
1747
  #matcher;
1722
1748
  #ruleValidator;
1723
- constructor(rules, diagnostics, context = "Component") {
1749
+ #exclusiveChildren;
1750
+ #allowText;
1751
+ constructor(rules, diagnostics, context = "Component", options = {}) {
1724
1752
  super(diagnostics);
1725
1753
  this.#context = context;
1726
- this.#rules = rules.map((r) => normalizeChildRule(r));
1727
- this.#ruleNames = this.#rules.map((r) => r.name);
1728
- iterate.forEach(this.#rules, (rule) => {
1729
- const { name, position, cardinality } = rule;
1730
- if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1731
- throw new RangeError(
1732
- `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1733
- );
1734
- }
1754
+ this.#exclusiveChildren = options.exclusiveChildren ?? false;
1755
+ this.#allowText = options.allowText ?? true;
1756
+ const staticRuleInputs = [];
1757
+ const dynamicRuleInputs = [];
1758
+ iterate.forEach(rules, (rule) => {
1759
+ if (isDynamicRule(rule.cardinality)) dynamicRuleInputs.push(rule);
1760
+ else staticRuleInputs.push(rule);
1735
1761
  });
1736
- this.#matcher = new RuleMatcher(this.#rules);
1762
+ this.#dynamicRuleInputs = dynamicRuleInputs;
1763
+ this.#staticRules = staticRuleInputs.map(
1764
+ (r) => normalizeChildRule(r)
1765
+ );
1737
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
+ }
1738
1773
  }
1739
- 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) {
1740
1779
  if (!this.active) return;
1741
- const { matrix, unexpectedIndices, ambiguousIndices } = this.#matcher.match(children);
1742
- this.#ruleValidator.validate(this.#rules, matrix, children.length);
1780
+ const { rules, matcher } = this.#resolveRules(context);
1781
+ const {
1782
+ matrix,
1783
+ unexpectedIndices: rawUnexpectedIndices,
1784
+ ambiguousIndices
1785
+ } = matcher.match(children);
1786
+ this.#ruleValidator.validate(rules, matrix, children.length);
1787
+ const unexpectedIndices = new Set(
1788
+ [...rawUnexpectedIndices].filter(
1789
+ (ci) => isTextLike(children[ci]) ? this.#allowText === false : this.#exclusiveChildren
1790
+ )
1791
+ );
1743
1792
  if (unexpectedIndices.size === 0 && ambiguousIndices.size === 0) return;
1744
1793
  const violating = [...unexpectedIndices, ...ambiguousIndices].sort((a, b) => a - b);
1745
1794
  iterate.forEach(violating, (ci) => {
@@ -1748,11 +1797,28 @@ var ChildrenEvaluator = class extends InvariantBase {
1748
1797
  this.violate(ContractDiagnostics.unexpectedChild(typeName, ci, this.#context));
1749
1798
  } else {
1750
1799
  const matches = matrix.childToRules.forward.get(ci);
1751
- const names = [...matches].map((ri) => this.#ruleNames[ri] ?? `#${ri}`);
1800
+ const names = [...matches].map((ri) => rules[ri]?.name ?? `#${ri}`);
1752
1801
  this.violate(ContractDiagnostics.ambiguousChild(typeName, ci, names, this.#context));
1753
1802
  }
1754
1803
  });
1755
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
+ }
1756
1822
  };
1757
1823
 
1758
1824
  // ../../lib/contract/src/props/get-disabled-props.ts
@@ -1861,8 +1927,11 @@ function metadata(name = "metadata") {
1861
1927
  function firstOptional(name, tag) {
1862
1928
  return { name, match: isTag(tag), cardinality: { max: 1 }, position: "first" };
1863
1929
  }
1864
- function contract(children) {
1865
- return { diagnostics: warnDiagnostics, children };
1930
+ function contract(children, options) {
1931
+ return { diagnostics: warnDiagnostics, children, ...options };
1932
+ }
1933
+ function closedContract(children) {
1934
+ return contract(children, { exclusiveChildren: true });
1866
1935
  }
1867
1936
  function ariaContract(aria) {
1868
1937
  return { diagnostics: warnDiagnostics, aria };
@@ -1888,8 +1957,10 @@ var VOID_TAGS = [
1888
1957
  ];
1889
1958
  var TEXT_ONLY_TAGS = ["option", "script", "style", "textarea", "title"];
1890
1959
  var LANDMARK_TAGS = ["article", "aside", "footer", "header", "main", "nav"];
1891
- var listContract = contract([{ name: "list-item", match: isTag("li", ...METADATA_TAGS) }]);
1892
- var tableContract = contract([
1960
+ var listContract = closedContract([
1961
+ { name: "list-item", match: isTag("li", ...METADATA_TAGS) }
1962
+ ]);
1963
+ var tableContract = closedContract([
1893
1964
  firstOptional("caption", "caption"),
1894
1965
  { name: "colgroup", match: isTag("colgroup") },
1895
1966
  { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
@@ -1897,29 +1968,31 @@ var tableContract = contract([
1897
1968
  { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
1898
1969
  { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
1899
1970
  ]);
1900
- var tableBodyContract = contract([
1971
+ var tableBodyContract = closedContract([
1901
1972
  { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
1902
1973
  ]);
1903
- var tableRowContract = contract([
1974
+ var tableRowContract = closedContract([
1904
1975
  { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
1905
1976
  ]);
1906
- var colgroupContract = contract([{ name: "column", match: isTag("col", "template") }]);
1907
- var dlContract = contract([
1977
+ var colgroupContract = closedContract([
1978
+ { name: "column", match: isTag("col", "template") }
1979
+ ]);
1980
+ var dlContract = closedContract([
1908
1981
  { name: "term", match: isTag("dt") },
1909
1982
  { name: "description", match: isTag("dd") },
1910
1983
  { name: "group", match: isTag("div") },
1911
1984
  metadata()
1912
1985
  ]);
1913
- var selectContract = contract([
1986
+ var selectContract = closedContract([
1914
1987
  { name: "option", match: isTag("option", "optgroup", "hr", ...METADATA_TAGS) }
1915
1988
  ]);
1916
- var optgroupContract = contract([
1989
+ var optgroupContract = closedContract([
1917
1990
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
1918
1991
  ]);
1919
- var datalistContract = contract([
1992
+ var datalistContract = closedContract([
1920
1993
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
1921
1994
  ]);
1922
- var pictureContract = contract([
1995
+ var pictureContract = closedContract([
1923
1996
  { name: "source", match: isTag("source", ...METADATA_TAGS) },
1924
1997
  { name: "image", match: isTag("img"), cardinality: { min: 1, max: 1 }, position: "last" }
1925
1998
  ]);
@@ -1935,23 +2008,18 @@ var mediaContract = contract([
1935
2008
  metadata(),
1936
2009
  { name: "content", match: isOpenContent("source", "track", ...METADATA_TAGS) }
1937
2010
  ]);
1938
- var headContract = contract([
2011
+ var headContract = closedContract([
1939
2012
  {
1940
2013
  name: "metadata",
1941
2014
  match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
1942
2015
  }
1943
2016
  ]);
1944
- var htmlContract = contract([
2017
+ var htmlContract = closedContract([
1945
2018
  { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
1946
2019
  { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
1947
2020
  ]);
1948
- var voidContract = contract([]);
1949
- var textOnlyContract = contract([
1950
- {
1951
- name: "text",
1952
- match: (child) => isString(child) || isNumber(child)
1953
- }
1954
- ]);
2021
+ var voidContract = contract([], { exclusiveChildren: true, allowText: false });
2022
+ var textOnlyContract = contract([], { exclusiveChildren: true });
1955
2023
  var landmarkContract = ariaContract([landmarkRoleRule, landmarkNameAdvisory]);
1956
2024
  var dialogContract = ariaContract([requireAccessibleName]);
1957
2025
  var menuContract = ariaContract([requireAccessibleName]);
@@ -1996,9 +2064,15 @@ var htmlContracts = {
1996
2064
  var htmlDiagnostics = warnDiagnostics2;
1997
2065
  function buildEvaluatorMap() {
1998
2066
  const map2 = /* @__PURE__ */ new Map();
1999
- iterate.forEachEntry(htmlContracts, (tag, { children }) => {
2000
- if (children?.length) {
2001
- map2.set(tag, new ChildrenEvaluator(children, htmlDiagnostics, `<${tag}>`));
2067
+ iterate.forEachEntry(htmlContracts, (tag, { children, exclusiveChildren, allowText }) => {
2068
+ if (children?.length || exclusiveChildren || allowText === false) {
2069
+ map2.set(
2070
+ tag,
2071
+ new ChildrenEvaluator(children ?? [], htmlDiagnostics, `<${tag}>`, {
2072
+ exclusiveChildren,
2073
+ allowText
2074
+ })
2075
+ );
2002
2076
  }
2003
2077
  });
2004
2078
  return map2;
@@ -2209,7 +2283,7 @@ function definePipeline(factory) {
2209
2283
  }
2210
2284
 
2211
2285
  // ../core/src/options/resolve-factory-options.ts
2212
- import { silentDiagnostics } from "../_shared/diagnostics.js";
2286
+ import { resolveDiagnostics, silentDiagnostics } from "../_shared/diagnostics.js";
2213
2287
  var EMPTY_VARIANT_KEYS = /* @__PURE__ */ new Set();
2214
2288
  function composeNormalizers(normalizers, fn) {
2215
2289
  if (!normalizers?.length) return fn;
@@ -2230,7 +2304,7 @@ function resolveFactoryOptions(options = {}) {
2230
2304
  const variantKeys = styling?.variants === void 0 ? EMPTY_VARIANT_KEYS : new Set(Object.keys(styling.variants));
2231
2305
  return Object.freeze({
2232
2306
  defaultTag: options.tag ?? "div",
2233
- diagnostics: enforcement?.diagnostics ?? silentDiagnostics,
2307
+ diagnostics: resolveDiagnostics(enforcement?.diagnostics, silentDiagnostics),
2234
2308
  variantKeys,
2235
2309
  ...whenDefined("displayName", options.name),
2236
2310
  ...whenDefined("defaultProps", options.defaults),
@@ -2243,6 +2317,8 @@ function resolveFactoryOptions(options = {}) {
2243
2317
  ...whenDefined("normalizeFn", composedNormalizeFn),
2244
2318
  ...whenDefined("ariaRules", enforcement?.aria),
2245
2319
  ...whenDefined("childRules", enforcement?.children),
2320
+ ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
2321
+ ...whenDefined("allowText", enforcement?.allowText),
2246
2322
  ...whenDefined("allowedAs", enforcement?.allowedAs),
2247
2323
  ...whenDefined("precomputedClasses", styling?.precomputedClasses)
2248
2324
  });
@@ -2427,16 +2503,22 @@ function buildCoreRuntime(normalized) {
2427
2503
  }
2428
2504
 
2429
2505
  // ../../lib/adapter-utils/src/runtime/build-engines.ts
2430
- function buildEngines(diagnostics, childRules, context) {
2431
- return childRules?.length ? { childrenEvaluator: new ChildrenEvaluator(childRules, diagnostics, context) } : {};
2506
+ function buildEngines(diagnostics, childRules, context, childrenOptions) {
2507
+ const { exclusiveChildren, allowText } = childrenOptions ?? {};
2508
+ return childRules?.length || exclusiveChildren || allowText === false ? {
2509
+ childrenEvaluator: new ChildrenEvaluator(childRules ?? [], diagnostics, context, {
2510
+ exclusiveChildren,
2511
+ allowText
2512
+ })
2513
+ } : {};
2432
2514
  }
2433
2515
 
2434
2516
  // ../../lib/adapter-utils/src/runtime/resolve-adapter-common-options.ts
2435
- import { throwDiagnostics as throwDiagnostics2, silentDiagnostics as silentDiagnostics3 } from "../_shared/diagnostics.js";
2517
+ import { throwDiagnostics as throwDiagnostics2, silentDiagnostics as silentDiagnostics3, resolveDiagnostics as resolveDiagnostics2 } from "../_shared/diagnostics.js";
2436
2518
  function resolveAdapterCommonOptions(options, defaultName = "PolymorphicComponent", defaultDiagnostics = throwDiagnostics2) {
2437
2519
  return {
2438
2520
  name: options.name ?? defaultName,
2439
- diagnostics: options.enforcement?.diagnostics ?? defaultDiagnostics
2521
+ diagnostics: resolveDiagnostics2(options.enforcement?.diagnostics, defaultDiagnostics)
2440
2522
  };
2441
2523
  }
2442
2524
 
@@ -2528,13 +2610,10 @@ function toLooseBundle(bundle) {
2528
2610
  }
2529
2611
  return bundle;
2530
2612
  }
2531
- function resolveHostState(bundle, props) {
2532
- const { as, className, recipe, ...rest } = props;
2533
- 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;
2534
2616
  const tag = resolveTag2(as);
2535
- if (options.allowedAs !== void 0) {
2536
- enforceAllowedAs(tag, options.allowedAs, options.diagnostics, options.displayName);
2537
- }
2538
2617
  const mergedProps = resolveProps(rest);
2539
2618
  const normalizedProps = typeof options.normalizeFn === "function" ? options.normalizeFn(mergedProps) : mergedProps;
2540
2619
  const htmlNormalizers = options.htmlPropNormalizersFn?.(tag);
@@ -2544,6 +2623,15 @@ function resolveHostState(bundle, props) {
2544
2623
  Object.assign(finalProps, normalize(finalProps));
2545
2624
  }
2546
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
+ }
2547
2635
  const resolvedClass = resolveClasses(
2548
2636
  tag,
2549
2637
  finalProps,
@@ -2552,7 +2640,7 @@ function resolveHostState(bundle, props) {
2552
2640
  );
2553
2641
  const ariaResult = resolveAria(tag, finalProps);
2554
2642
  const attributes = applyFilter(ariaResult.props, bundle.filterProps, options.variantKeys);
2555
- return { className: resolvedClass, attributes };
2643
+ return { className: resolvedClass, attributes, tag, normalizedProps: finalProps };
2556
2644
  }
2557
2645
  function diffAndApplyAttributes(host, state, prevPipelineAttrs, incomingProps) {
2558
2646
  const hasOwn2 = Object.hasOwn;
@@ -2600,7 +2688,8 @@ function buildRuntime(options) {
2600
2688
  const { childrenEvaluator } = buildEngines(
2601
2689
  normalized.diagnostics,
2602
2690
  enforcement?.children,
2603
- normalized.name
2691
+ normalized.name,
2692
+ { exclusiveChildren: enforcement?.exclusiveChildren, allowText: enforcement?.allowText }
2604
2693
  );
2605
2694
  const filterProps = composeFilter(ownedKeys, customFilter);
2606
2695
  return {
@@ -2690,7 +2779,11 @@ function createContractComponent(options) {
2690
2779
  get _self() {
2691
2780
  return this;
2692
2781
  }
2693
- _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() {
2694
2787
  const self = this._self;
2695
2788
  const props = {};
2696
2789
  iterate.forEach(iterate.items(this.attributes), (attr) => {
@@ -2704,14 +2797,18 @@ function createContractComponent(options) {
2704
2797
  const val = self[key];
2705
2798
  if (val != null) props[key] = val;
2706
2799
  });
2800
+ return props;
2801
+ }
2802
+ _applyPraxis() {
2803
+ const props = this._buildProps();
2707
2804
  diffAndApplyAttributes(this, resolveHostState(looseBundle, props), this._pipelineAttrs, props);
2708
2805
  }
2709
2806
  render() {
2710
2807
  const children = Array.from(this.childNodes);
2808
+ const { tag, normalizedProps } = resolveTagAndNormalizedProps(looseBundle, this._buildProps());
2711
2809
  if (bundle.childrenEvaluator) {
2712
- bundle.childrenEvaluator.evaluate(children);
2810
+ bundle.childrenEvaluator.evaluate(children, { tag, props: normalizedProps });
2713
2811
  }
2714
- const tag = bundle.runtime.resolveTag(this._self.as);
2715
2812
  bundle.runtime.options.htmlChildrenEvaluatorFn?.(tag)?.evaluate(children);
2716
2813
  return html`<slot></slot>`;
2717
2814
  }
@@ -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 { ComponentChildren, VNode, ComponentType, JSX, Ref } from 'preact';
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[];