praxis-kit 4.1.2 → 5.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.
@@ -1760,17 +1760,22 @@ var RuleValidator = class _RuleValidator extends InvariantBase {
1760
1760
  };
1761
1761
 
1762
1762
  // ../../lib/contract/src/children/children-evaluator.ts
1763
+ var isTextLike = (child) => isString(child) || isNumber(child);
1763
1764
  var ChildrenEvaluator = class extends InvariantBase {
1764
1765
  #context;
1765
1766
  #rules;
1766
1767
  #ruleNames;
1767
1768
  #matcher;
1768
1769
  #ruleValidator;
1769
- constructor(rules, diagnostics, context = "Component") {
1770
+ #exclusiveChildren;
1771
+ #allowText;
1772
+ constructor(rules, diagnostics, context = "Component", options = {}) {
1770
1773
  super(diagnostics);
1771
1774
  this.#context = context;
1772
1775
  this.#rules = rules.map((r) => normalizeChildRule(r));
1773
1776
  this.#ruleNames = this.#rules.map((r) => r.name);
1777
+ this.#exclusiveChildren = options.exclusiveChildren ?? false;
1778
+ this.#allowText = options.allowText ?? true;
1774
1779
  iterate.forEach(this.#rules, (rule) => {
1775
1780
  const { name, position, cardinality } = rule;
1776
1781
  if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
@@ -1784,8 +1789,17 @@ var ChildrenEvaluator = class extends InvariantBase {
1784
1789
  }
1785
1790
  evaluate(children) {
1786
1791
  if (!this.active) return;
1787
- const { matrix, unexpectedIndices, ambiguousIndices } = this.#matcher.match(children);
1792
+ const {
1793
+ matrix,
1794
+ unexpectedIndices: rawUnexpectedIndices,
1795
+ ambiguousIndices
1796
+ } = this.#matcher.match(children);
1788
1797
  this.#ruleValidator.validate(this.#rules, matrix, children.length);
1798
+ const unexpectedIndices = new Set(
1799
+ [...rawUnexpectedIndices].filter(
1800
+ (ci) => isTextLike(children[ci]) ? this.#allowText === false : this.#exclusiveChildren
1801
+ )
1802
+ );
1789
1803
  if (unexpectedIndices.size === 0 && ambiguousIndices.size === 0) return;
1790
1804
  const violating = [...unexpectedIndices, ...ambiguousIndices].sort((a, b) => a - b);
1791
1805
  iterate.forEach(violating, (ci) => {
@@ -1907,8 +1921,11 @@ function metadata(name = "metadata") {
1907
1921
  function firstOptional(name, tag) {
1908
1922
  return { name, match: isTag(tag), cardinality: { max: 1 }, position: "first" };
1909
1923
  }
1910
- function contract(children) {
1911
- return { diagnostics: warnDiagnostics, children };
1924
+ function contract(children, options) {
1925
+ return { diagnostics: warnDiagnostics, children, ...options };
1926
+ }
1927
+ function closedContract(children) {
1928
+ return contract(children, { exclusiveChildren: true });
1912
1929
  }
1913
1930
  function ariaContract(aria) {
1914
1931
  return { diagnostics: warnDiagnostics, aria };
@@ -1934,8 +1951,10 @@ var VOID_TAGS = [
1934
1951
  ];
1935
1952
  var TEXT_ONLY_TAGS = ["option", "script", "style", "textarea", "title"];
1936
1953
  var LANDMARK_TAGS = ["article", "aside", "footer", "header", "main", "nav"];
1937
- var listContract = contract([{ name: "list-item", match: isTag("li", ...METADATA_TAGS) }]);
1938
- var tableContract = contract([
1954
+ var listContract = closedContract([
1955
+ { name: "list-item", match: isTag("li", ...METADATA_TAGS) }
1956
+ ]);
1957
+ var tableContract = closedContract([
1939
1958
  firstOptional("caption", "caption"),
1940
1959
  { name: "colgroup", match: isTag("colgroup") },
1941
1960
  { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
@@ -1943,29 +1962,31 @@ var tableContract = contract([
1943
1962
  { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
1944
1963
  { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
1945
1964
  ]);
1946
- var tableBodyContract = contract([
1965
+ var tableBodyContract = closedContract([
1947
1966
  { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
1948
1967
  ]);
1949
- var tableRowContract = contract([
1968
+ var tableRowContract = closedContract([
1950
1969
  { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
1951
1970
  ]);
1952
- var colgroupContract = contract([{ name: "column", match: isTag("col", "template") }]);
1953
- var dlContract = contract([
1971
+ var colgroupContract = closedContract([
1972
+ { name: "column", match: isTag("col", "template") }
1973
+ ]);
1974
+ var dlContract = closedContract([
1954
1975
  { name: "term", match: isTag("dt") },
1955
1976
  { name: "description", match: isTag("dd") },
1956
1977
  { name: "group", match: isTag("div") },
1957
1978
  metadata()
1958
1979
  ]);
1959
- var selectContract = contract([
1980
+ var selectContract = closedContract([
1960
1981
  { name: "option", match: isTag("option", "optgroup", "hr", ...METADATA_TAGS) }
1961
1982
  ]);
1962
- var optgroupContract = contract([
1983
+ var optgroupContract = closedContract([
1963
1984
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
1964
1985
  ]);
1965
- var datalistContract = contract([
1986
+ var datalistContract = closedContract([
1966
1987
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
1967
1988
  ]);
1968
- var pictureContract = contract([
1989
+ var pictureContract = closedContract([
1969
1990
  { name: "source", match: isTag("source", ...METADATA_TAGS) },
1970
1991
  { name: "image", match: isTag("img"), cardinality: { min: 1, max: 1 }, position: "last" }
1971
1992
  ]);
@@ -1981,23 +2002,18 @@ var mediaContract = contract([
1981
2002
  metadata(),
1982
2003
  { name: "content", match: isOpenContent("source", "track", ...METADATA_TAGS) }
1983
2004
  ]);
1984
- var headContract = contract([
2005
+ var headContract = closedContract([
1985
2006
  {
1986
2007
  name: "metadata",
1987
2008
  match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
1988
2009
  }
1989
2010
  ]);
1990
- var htmlContract = contract([
2011
+ var htmlContract = closedContract([
1991
2012
  { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
1992
2013
  { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
1993
2014
  ]);
1994
- var voidContract = contract([]);
1995
- var textOnlyContract = contract([
1996
- {
1997
- name: "text",
1998
- match: (child) => isString(child) || isNumber(child)
1999
- }
2000
- ]);
2015
+ var voidContract = contract([], { exclusiveChildren: true, allowText: false });
2016
+ var textOnlyContract = contract([], { exclusiveChildren: true });
2001
2017
  var landmarkContract = ariaContract([landmarkRoleRule, landmarkNameAdvisory]);
2002
2018
  var dialogContract = ariaContract([requireAccessibleName]);
2003
2019
  var menuContract = ariaContract([requireAccessibleName]);
@@ -2042,9 +2058,15 @@ var htmlContracts = {
2042
2058
  var htmlDiagnostics = warnDiagnostics2;
2043
2059
  function buildEvaluatorMap() {
2044
2060
  const map2 = /* @__PURE__ */ new Map();
2045
- iterate.forEachEntry(htmlContracts, (tag, { children }) => {
2046
- if (children?.length) {
2047
- map2.set(tag, new ChildrenEvaluator(children, htmlDiagnostics, `<${tag}>`));
2061
+ iterate.forEachEntry(htmlContracts, (tag, { children, exclusiveChildren, allowText }) => {
2062
+ if (children?.length || exclusiveChildren || allowText === false) {
2063
+ map2.set(
2064
+ tag,
2065
+ new ChildrenEvaluator(children ?? [], htmlDiagnostics, `<${tag}>`, {
2066
+ exclusiveChildren,
2067
+ allowText
2068
+ })
2069
+ );
2048
2070
  }
2049
2071
  });
2050
2072
  return map2;
@@ -2255,7 +2277,7 @@ function definePipeline(factory) {
2255
2277
  }
2256
2278
 
2257
2279
  // ../core/src/options/resolve-factory-options.ts
2258
- import { silentDiagnostics } from "../_shared/diagnostics.js";
2280
+ import { resolveDiagnostics, silentDiagnostics } from "../_shared/diagnostics.js";
2259
2281
  var EMPTY_VARIANT_KEYS = /* @__PURE__ */ new Set();
2260
2282
  function composeNormalizers(normalizers, fn) {
2261
2283
  if (!normalizers?.length) return fn;
@@ -2276,7 +2298,7 @@ function resolveFactoryOptions(options = {}) {
2276
2298
  const variantKeys = styling?.variants === void 0 ? EMPTY_VARIANT_KEYS : new Set(Object.keys(styling.variants));
2277
2299
  return Object.freeze({
2278
2300
  defaultTag: options.tag ?? "div",
2279
- diagnostics: enforcement?.diagnostics ?? silentDiagnostics,
2301
+ diagnostics: resolveDiagnostics(enforcement?.diagnostics, silentDiagnostics),
2280
2302
  variantKeys,
2281
2303
  ...whenDefined("displayName", options.name),
2282
2304
  ...whenDefined("defaultProps", options.defaults),
@@ -2289,6 +2311,8 @@ function resolveFactoryOptions(options = {}) {
2289
2311
  ...whenDefined("normalizeFn", composedNormalizeFn),
2290
2312
  ...whenDefined("ariaRules", enforcement?.aria),
2291
2313
  ...whenDefined("childRules", enforcement?.children),
2314
+ ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
2315
+ ...whenDefined("allowText", enforcement?.allowText),
2292
2316
  ...whenDefined("allowedAs", enforcement?.allowedAs),
2293
2317
  ...whenDefined("precomputedClasses", styling?.precomputedClasses)
2294
2318
  });
@@ -2464,16 +2488,22 @@ function buildCoreRuntime(normalized) {
2464
2488
  }
2465
2489
 
2466
2490
  // ../../lib/adapter-utils/src/runtime/build-engines.ts
2467
- function buildEngines(diagnostics, childRules, context) {
2468
- return childRules?.length ? { childrenEvaluator: new ChildrenEvaluator(childRules, diagnostics, context) } : {};
2491
+ function buildEngines(diagnostics, childRules, context, childrenOptions) {
2492
+ const { exclusiveChildren, allowText } = childrenOptions ?? {};
2493
+ return childRules?.length || exclusiveChildren || allowText === false ? {
2494
+ childrenEvaluator: new ChildrenEvaluator(childRules ?? [], diagnostics, context, {
2495
+ exclusiveChildren,
2496
+ allowText
2497
+ })
2498
+ } : {};
2469
2499
  }
2470
2500
 
2471
2501
  // ../../lib/adapter-utils/src/runtime/resolve-adapter-common-options.ts
2472
- import { throwDiagnostics as throwDiagnostics2, silentDiagnostics as silentDiagnostics2 } from "../_shared/diagnostics.js";
2502
+ import { throwDiagnostics as throwDiagnostics2, silentDiagnostics as silentDiagnostics2, resolveDiagnostics as resolveDiagnostics2 } from "../_shared/diagnostics.js";
2473
2503
  function resolveAdapterCommonOptions(options, defaultName = "PolymorphicComponent", defaultDiagnostics = throwDiagnostics2) {
2474
2504
  return {
2475
2505
  name: options.name ?? defaultName,
2476
- diagnostics: options.enforcement?.diagnostics ?? defaultDiagnostics
2506
+ diagnostics: resolveDiagnostics2(options.enforcement?.diagnostics, defaultDiagnostics)
2477
2507
  };
2478
2508
  }
2479
2509
 
@@ -2518,7 +2548,11 @@ function buildRuntime(options) {
2518
2548
  const { childrenEvaluator } = buildEngines(
2519
2549
  normalized.diagnostics,
2520
2550
  normalized.enforcement?.children,
2521
- normalized.name
2551
+ normalized.name,
2552
+ {
2553
+ exclusiveChildren: normalized.enforcement?.exclusiveChildren,
2554
+ allowText: normalized.enforcement?.allowText
2555
+ }
2522
2556
  );
2523
2557
  const filterProps = composeFilter(ownedKeys, normalized.filterProps);
2524
2558
  const slotValidator = new SlotValidator(normalized.name, normalized.diagnostics, "Snippet");
@@ -0,0 +1,20 @@
1
+ /*
2
+ * `createTailwindPipeline` assembles display-mode classes (flex, inline-flex,
3
+ * grid, hidden, etc.) at runtime from boolean props — they never appear as a
4
+ * literal string anywhere in scanned source, so Tailwind v4's content
5
+ * detection never generates their CSS on its own.
6
+ *
7
+ * `@source inline(...)` forces Tailwind to generate these utilities
8
+ * regardless of what its scanner finds. This file is copied verbatim into
9
+ * `praxis-kit`'s published dist (see packages/kit/scripts/postbuild.mjs) and
10
+ * exposed as the `praxis-kit/tailwind.css` subpath — `@praxis-kit/tailwind`
11
+ * itself is a private, unpublished workspace package. Import it alongside
12
+ * `tailwindcss` in your app's main CSS entry point:
13
+ *
14
+ * @import "tailwindcss";
15
+ * @import "praxis-kit/tailwind.css";
16
+ *
17
+ * Keep this list in sync with `layout-keys.ts` — enforced by
18
+ * `tailwind-safelist.test.ts`.
19
+ */
20
+ @source inline("flex inline-flex grid inline-grid block inline-block inline hidden contents flow-root list-item table inline-table table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row-group table-row");
@@ -165,6 +165,13 @@ type ComponentConstraint = {
165
165
  defaultTag?: string;
166
166
  /** True when `enforcement.aria` is a non-empty array literal in the factory call. */
167
167
  hasAriaRules: boolean;
168
+ /**
169
+ * True when `enforcement.exclusiveChildren` is statically `true` in the factory call.
170
+ * Only when this is true does an "over the max" child count constitute a real
171
+ * violation — enforcement.children is open-by-default, so extra unmatched children
172
+ * are otherwise allowed regardless of `totalMax`.
173
+ */
174
+ exclusiveChildren: boolean;
168
175
  };
169
176
 
170
177
  /** A diagnostic produced by the static analysis pass. */
@@ -208,6 +215,11 @@ type PluginOptions = {
208
215
  * are skipped — their child count is unknowable at build time.
209
216
  * - Named const declarations: `export const X = factory(...)` and destructured
210
217
  * patterns are not collected; cross-file analysis is future work.
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * const diagnostics = analyze(source, 'Button.tsx', { severity: 'error' })
222
+ * ```
211
223
  */
212
224
  declare function analyze(code: string, filename: string, options?: PluginOptions): Diagnostic[];
213
225
 
@@ -228,6 +240,25 @@ declare function analyze(code: string, filename: string, options?: PluginOptions
228
240
  * - `styling.compounds` contains non-literal conditions or class values
229
241
  * - The total number of combinations exceeds MAX_COMBINATIONS
230
242
  * - The styling object already has a `precomputedClasses` property
243
+ *
244
+ * @example
245
+ * Before:
246
+ * ```ts
247
+ * styling: {
248
+ * variants: { size: { sm: 'text-sm', lg: 'text-lg' } },
249
+ * }
250
+ * ```
251
+ * After:
252
+ * ```ts
253
+ * styling: {
254
+ * variants: { size: { sm: 'text-sm', lg: 'text-lg' } },
255
+ * precomputedClasses: {
256
+ * '__none__:': '',
257
+ * '__none__:size:s:sm': 'text-sm',
258
+ * '__none__:size:s:lg': 'text-lg',
259
+ * },
260
+ * }
261
+ * ```
231
262
  */
232
263
 
233
264
  /**
@@ -189,7 +189,7 @@ function asArray(node) {
189
189
  if (!node) return void 0;
190
190
  return ts.isArrayLiteralExpression(node) ? node : void 0;
191
191
  }
192
- function asPositiveInt(node) {
192
+ function asNonNegativeInt(node) {
193
193
  if (!node) return void 0;
194
194
  if (ts.isNumericLiteral(node)) {
195
195
  const n = Number(node.text);
@@ -197,6 +197,17 @@ function asPositiveInt(node) {
197
197
  }
198
198
  return void 0;
199
199
  }
200
+ function asBooleanLiteral(node) {
201
+ if (!node) return void 0;
202
+ switch (node.kind) {
203
+ case ts.SyntaxKind.TrueKeyword:
204
+ return true;
205
+ case ts.SyntaxKind.FalseKeyword:
206
+ return false;
207
+ default:
208
+ return void 0;
209
+ }
210
+ }
200
211
  function isFactoryCall(call, names) {
201
212
  const { expression } = call;
202
213
  if (ts.isIdentifier(expression)) return names.has(expression.text);
@@ -204,7 +215,7 @@ function isFactoryCall(call, names) {
204
215
  return false;
205
216
  }
206
217
  function firstObjectArg(call) {
207
- const [first] = call.arguments;
218
+ const first = call.arguments[0];
208
219
  return first && ts.isObjectLiteralExpression(first) ? first : void 0;
209
220
  }
210
221
  function* walk(node) {
@@ -316,11 +327,11 @@ function enumerateCombinations(variantMap) {
316
327
  return total <= MAX_COMBINATIONS;
317
328
  });
318
329
  if (!totalUnderMaxLimit) return null;
319
- function rec(remaining) {
330
+ function enumerateRecursive(remaining) {
320
331
  if (remaining.length === 0) return [{}];
321
332
  const first = remaining[0];
322
333
  const rest = remaining.slice(1);
323
- const restCombos = rec(rest);
334
+ const restCombos = enumerateRecursive(rest);
324
335
  const valueKeys = Object.keys(variantMap[first]);
325
336
  const out = [];
326
337
  iterate.forEach(restCombos, (combo) => {
@@ -331,7 +342,7 @@ function enumerateCombinations(variantMap) {
331
342
  });
332
343
  return out;
333
344
  }
334
- return rec(keys2);
345
+ return enumerateRecursive(keys2);
335
346
  }
336
347
  function buildCacheKey(props) {
337
348
  const parts = Object.keys(props).sort().map((k) => `${k}:s:${props[k]}`);
@@ -459,8 +470,8 @@ function extractBound(element) {
459
470
  if (cardObj) {
460
471
  const minNode = getProperty(cardObj, "min");
461
472
  const maxNode = getProperty(cardObj, "max");
462
- const min = asPositiveInt(minNode) ?? 0;
463
- const max = asPositiveInt(maxNode);
473
+ const min = asNonNegativeInt(minNode) ?? 0;
474
+ const max = asNonNegativeInt(maxNode);
464
475
  if (min === 0 && max === void 0) {
465
476
  cardinality = { kind: "unbounded" };
466
477
  } else {
@@ -497,6 +508,8 @@ function processVariableStatement(node, calleeNames, out) {
497
508
  const ariaNode = enfObj ? getProperty(enfObj, "aria") : void 0;
498
509
  const ariaArr = asArray(ariaNode);
499
510
  const hasAriaRules = ariaArr !== void 0 && ariaArr.elements.length > 0;
511
+ const exclusiveChildrenNode = enfObj ? getProperty(enfObj, "exclusiveChildren") : void 0;
512
+ const exclusiveChildren = asBooleanLiteral(exclusiveChildrenNode) ?? false;
500
513
  const childrenProp = enfObj ? getProperty(enfObj, "children") : void 0;
501
514
  const childrenArr = asArray(childrenProp);
502
515
  const rules = [];
@@ -506,7 +519,7 @@ function processVariableStatement(node, calleeNames, out) {
506
519
  if (bound) rules.push(bound);
507
520
  });
508
521
  }
509
- if (rules.length === 0 && !hasAriaRules && !defaultTag) return;
522
+ if (rules.length === 0 && !hasAriaRules && !defaultTag && !exclusiveChildren) return;
510
523
  let totalMin = 0;
511
524
  let totalMax = 0;
512
525
  iterate.forEach(rules, (rule) => {
@@ -522,7 +535,8 @@ function processVariableStatement(node, calleeNames, out) {
522
535
  totalMin,
523
536
  totalMax,
524
537
  ...defaultTag !== void 0 && { defaultTag },
525
- hasAriaRules
538
+ hasAriaRules,
539
+ exclusiveChildren
526
540
  });
527
541
  });
528
542
  }
@@ -734,7 +748,7 @@ function analyzeJsxSites(source, constraints, severity) {
734
748
  const getPos = () => pos ??= source.getLineAndCharacterOfPosition(node.getStart(source));
735
749
  if (count !== void 0) {
736
750
  const c = byName.get(tagName);
737
- if (c && (count.max < c.totalMin || count.min > c.totalMax)) {
751
+ if (c && (count.max < c.totalMin || c.exclusiveChildren && count.min > c.totalMax)) {
738
752
  const { line, character } = getPos();
739
753
  diagnostics.push({
740
754
  diagnostic: ViteDiagnostics.cardinalityViolation(
@@ -868,8 +882,8 @@ function diagnoseUsages(source, constraints, severity) {
868
882
  const constraint = byName.get(tagName);
869
883
  if (!constraint) return;
870
884
  if (count === void 0) return;
871
- const { totalMin, totalMax, name } = constraint;
872
- if (count.max < totalMin || count.min > totalMax) {
885
+ const { totalMin, totalMax, name, exclusiveChildren } = constraint;
886
+ if (count.max < totalMin || exclusiveChildren && count.min > totalMax) {
873
887
  const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
874
888
  diagnostics.push({
875
889
  diagnostic: ViteDiagnostics.cardinalityViolation(
@@ -948,9 +962,11 @@ var ConstraintRegistry = class {
948
962
  if (count === void 0) return;
949
963
  const constraint = this.resolveConstraint(fileId, tagName);
950
964
  if (!constraint) return;
951
- const { totalMin, totalMax, name } = constraint;
965
+ const { totalMin, totalMax, name, exclusiveChildren } = constraint;
952
966
  const { min, max } = count;
953
- if (max >= totalMin && min <= totalMax) return;
967
+ const tooFew = max < totalMin;
968
+ const tooMany = exclusiveChildren && min > totalMax;
969
+ if (!tooFew && !tooMany) return;
954
970
  result.push({
955
971
  fileId,
956
972
  diagnostic: ViteDiagnostics.cardinalityViolation(name, totalMin, totalMax, min, max),
@@ -1379,8 +1395,9 @@ function composeStatically(source, calleeNames, importedComponents = /* @__PURE_
1379
1395
  }
1380
1396
 
1381
1397
  // ../../plugins/vite/src/analyze.ts
1398
+ import { extname } from "path";
1382
1399
  function analyze(code, filename, options) {
1383
- const ext = filename.split(".").pop() ?? "";
1400
+ const ext = extname(filename).slice(1);
1384
1401
  if (!JSX_EXTS.has(ext)) return [];
1385
1402
  const calleeNames = new Set(options?.calleeNames ?? DEFAULT_CALLEE_NAMES);
1386
1403
  const severity = options?.severity ?? "warning";
@@ -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 * as vue from 'vue';
4
4
  import { AllowedComponentProps } from 'vue';
5
5
 
@@ -214,9 +214,25 @@ type AriaRule<C extends AriaContext = AriaContext> = (context: C) => readonly Ar
214
214
  type PropNormalizer = (props: Readonly<AnyRecord & IntrinsicProps>) => Partial<AnyRecord & IntrinsicProps>;
215
215
 
216
216
  type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
217
- readonly diagnostics?: Diagnostics;
217
+ /**
218
+ * Accepts a preset name (`'warn'`, `'throw'`, `'silent'`) or a full `Diagnostics`
219
+ * instance for custom reporting/policy. The string form needs no import from
220
+ * `@praxis-kit/diagnostics`.
221
+ */
222
+ readonly diagnostics?: Diagnostics | DiagnosticsMode;
218
223
  readonly aria?: readonly AriaRule[];
219
224
  readonly children?: readonly ChildRuleInput[];
225
+ /**
226
+ * When true, only children matching a `children` rule (or text, per `allowText`)
227
+ * are valid — anything else is rejected. Default: false (open — children not
228
+ * matching any rule are allowed).
229
+ */
230
+ readonly exclusiveChildren?: boolean;
231
+ /**
232
+ * When false, text/number child nodes are rejected regardless of exclusiveChildren
233
+ * or any listed rule. Default: true.
234
+ */
235
+ readonly allowText?: boolean;
220
236
  readonly props?: readonly PropNormalizer[];
221
237
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
222
238
  readonly allowedAs?: readonly TAllowed[];