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.
@@ -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>;
@@ -36,6 +36,26 @@ type ChildrenEvaluator$1 = {
36
36
  evaluate: (children: unknown[]) => void;
37
37
  };
38
38
 
39
+ /**
40
+ * Resolved per-instance state available to a dynamic (`dynamic(...)`) child
41
+ * rule field — the same tag/props every adapter already computes before
42
+ * evaluating children, exposed so a rule can vary by them (e.g. cardinality
43
+ * that depends on the resolved `as` tag).
44
+ */
45
+ type ChildRuleContext = {
46
+ readonly tag: unknown;
47
+ readonly props: Readonly<AnyRecord>;
48
+ };
49
+
50
+ declare const RULE_BRAND: unique symbol;
51
+
52
+ type DynamicRule<T, C = unknown> = {
53
+ readonly [RULE_BRAND]: true;
54
+ resolve(context: C): T;
55
+ };
56
+
57
+ type Rule<T, C = unknown> = T | DynamicRule<T, C>;
58
+
39
59
  type ChildRuleMatch<T, U extends T = T> = (child: T) => child is U;
40
60
 
41
61
  type ChildRulePosition = 'first' | 'last' | 'any';
@@ -43,7 +63,13 @@ type ChildRulePosition = 'first' | 'last' | 'any';
43
63
  type ChildRuleInput<T = unknown, U extends T = T> = {
44
64
  name: string;
45
65
  match: ChildRuleMatch<T, U>;
46
- cardinality?: CardinalityInput;
66
+ /**
67
+ * Either a static cardinality, or `dynamic((ctx) => ...)` to derive it from
68
+ * the resolved tag/props (e.g. a different max depending on `as`). `match`
69
+ * stays static-only — it's already a function, so a dynamic wrapper would
70
+ * be indistinguishable from the predicate itself without one.
71
+ */
72
+ cardinality?: Rule<CardinalityInput, ChildRuleContext>;
47
73
  position?: ChildRulePosition;
48
74
  /**
49
75
  * Optional component-type reference for O(1) dispatch index.
@@ -217,9 +243,25 @@ type AriaRule<C extends AriaContext = AriaContext> = (context: C) => readonly Ar
217
243
  type PropNormalizer = (props: Readonly<AnyRecord & IntrinsicProps>) => Partial<AnyRecord & IntrinsicProps>;
218
244
 
219
245
  type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
220
- readonly diagnostics?: Diagnostics;
246
+ /**
247
+ * Accepts a preset name (`'warn'`, `'throw'`, `'silent'`) or a full `Diagnostics`
248
+ * instance for custom reporting/policy. The string form needs no import from
249
+ * `@praxis-kit/diagnostics`.
250
+ */
251
+ readonly diagnostics?: Diagnostics | DiagnosticsMode;
221
252
  readonly aria?: readonly AriaRule[];
222
253
  readonly children?: readonly ChildRuleInput[];
254
+ /**
255
+ * When true, only children matching a `children` rule (or text, per `allowText`)
256
+ * are valid — anything else is rejected. Default: false (open — children not
257
+ * matching any rule are allowed).
258
+ */
259
+ readonly exclusiveChildren?: boolean;
260
+ /**
261
+ * When false, text/number child nodes are rejected regardless of exclusiveChildren
262
+ * or any listed rule. Default: true.
263
+ */
264
+ readonly allowText?: boolean;
223
265
  readonly props?: readonly PropNormalizer[];
224
266
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
225
267
  readonly allowedAs?: readonly TAllowed[];
@@ -265,6 +307,8 @@ type ResolvedFactoryOptions<TDefault extends ElementType = ElementType, Props ex
265
307
  readonly htmlPropNormalizersFn?: (tag: unknown) => readonly PropNormalizer[] | undefined;
266
308
  readonly htmlChildrenEvaluatorFn?: (tag: unknown) => ChildrenEvaluator$1 | undefined;
267
309
  readonly childRules?: readonly ChildRuleInput[];
310
+ readonly exclusiveChildren?: boolean;
311
+ readonly allowText?: boolean;
268
312
  readonly ariaRules?: readonly AriaRule[];
269
313
  readonly allowedAs?: readonly ElementType[];
270
314
  readonly precomputedClasses?: Readonly<Record<string, string>>;
@@ -302,10 +346,27 @@ declare abstract class InvariantBase {
302
346
  protected invariant(condition: unknown, input: DiagnosticInput): void;
303
347
  }
304
348
 
349
+ type ChildrenEvaluatorOptions = {
350
+ /**
351
+ * When true, only children matching a rule (or text, per `allowText`) are
352
+ * valid — anything else is rejected. Default: false (open — children not
353
+ * matching any rule are allowed).
354
+ */
355
+ readonly exclusiveChildren?: boolean | undefined;
356
+ /**
357
+ * When false, text/number child nodes are rejected regardless of
358
+ * exclusiveChildren or any listed rule. Default: true.
359
+ */
360
+ readonly allowText?: boolean | undefined;
361
+ };
305
362
  declare class ChildrenEvaluator extends InvariantBase {
306
363
  #private;
307
- constructor(rules: readonly ChildRuleInput[], diagnostics: Diagnostics, context?: string);
308
- evaluate(children: unknown[]): void;
364
+ constructor(rules: readonly ChildRuleInput[], diagnostics: Diagnostics, context?: string, options?: ChildrenEvaluatorOptions);
365
+ /**
366
+ * @param context Required when any rule has a `dynamic(...)` cardinality —
367
+ * supplies the resolved tag/props those rules are evaluated against.
368
+ */
369
+ evaluate(children: unknown[], context?: ChildRuleContext): void;
309
370
  }
310
371
 
311
372
  declare function createPolymorphic2<TDefault extends ElementType, Props extends AnyRecord, Variants extends Readonly<VariantMap>, TPreset extends RecipeMap<Variants> = Readonly<EmptyRecord>, TPlugin extends AnyClassPluginFactory = AnyClassPluginFactory>(options?: FactoryOptions<TDefault, Props, Variants, TPreset, TPlugin>): PolymorphicRuntime<TDefault, Props, Variants, Extract<keyof TPreset, string>, TPreset, PluginInstance<TPlugin>>;
@@ -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)}`);
@@ -1760,32 +1773,68 @@ var RuleValidator = class _RuleValidator extends InvariantBase {
1760
1773
  };
1761
1774
 
1762
1775
  // ../../lib/contract/src/children/children-evaluator.ts
1776
+ var isTextLike = (child) => isString(child) || isNumber(child);
1777
+ function checkPositionCardinalityInvariant(rules, context) {
1778
+ iterate.forEach(rules, (rule) => {
1779
+ const { name, position, cardinality } = rule;
1780
+ if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1781
+ throw new RangeError(
1782
+ `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1783
+ );
1784
+ }
1785
+ });
1786
+ }
1763
1787
  var ChildrenEvaluator = class extends InvariantBase {
1764
1788
  #context;
1765
- #rules;
1766
- #ruleNames;
1789
+ /** Rules with a static cardinality — normalized once, reused on every evaluate() call. */
1790
+ #staticRules;
1791
+ /** Rules whose cardinality is `dynamic(...)` — re-resolved against a ChildRuleContext per call. */
1792
+ #dynamicRuleInputs;
1767
1793
  #matcher;
1768
1794
  #ruleValidator;
1769
- constructor(rules, diagnostics, context = "Component") {
1795
+ #exclusiveChildren;
1796
+ #allowText;
1797
+ constructor(rules, diagnostics, context = "Component", options = {}) {
1770
1798
  super(diagnostics);
1771
1799
  this.#context = context;
1772
- this.#rules = rules.map((r) => normalizeChildRule(r));
1773
- this.#ruleNames = this.#rules.map((r) => r.name);
1774
- iterate.forEach(this.#rules, (rule) => {
1775
- const { name, position, cardinality } = rule;
1776
- if ((position === "first" || position === "last") && (cardinality.kind === "unbounded" || cardinality.max > 1)) {
1777
- throw new RangeError(
1778
- `ChildrenEvaluator [${context}]: rule "${name}" sets position="${position}" with an unbound or >1 max. position="first|last" implies max=1.`
1779
- );
1780
- }
1800
+ this.#exclusiveChildren = options.exclusiveChildren ?? false;
1801
+ this.#allowText = options.allowText ?? true;
1802
+ const staticRuleInputs = [];
1803
+ const dynamicRuleInputs = [];
1804
+ iterate.forEach(rules, (rule) => {
1805
+ if (isDynamicRule(rule.cardinality)) dynamicRuleInputs.push(rule);
1806
+ else staticRuleInputs.push(rule);
1781
1807
  });
1782
- this.#matcher = new RuleMatcher(this.#rules);
1808
+ this.#dynamicRuleInputs = dynamicRuleInputs;
1809
+ this.#staticRules = staticRuleInputs.map(
1810
+ (r) => normalizeChildRule(r)
1811
+ );
1783
1812
  this.#ruleValidator = new RuleValidator(context, diagnostics);
1813
+ if (this.#dynamicRuleInputs.length === 0) {
1814
+ checkPositionCardinalityInvariant(this.#staticRules, context);
1815
+ this.#matcher = new RuleMatcher(this.#staticRules);
1816
+ } else {
1817
+ this.#matcher = void 0;
1818
+ }
1784
1819
  }
1785
- evaluate(children) {
1820
+ /**
1821
+ * @param context Required when any rule has a `dynamic(...)` cardinality —
1822
+ * supplies the resolved tag/props those rules are evaluated against.
1823
+ */
1824
+ evaluate(children, context) {
1786
1825
  if (!this.active) return;
1787
- const { matrix, unexpectedIndices, ambiguousIndices } = this.#matcher.match(children);
1788
- this.#ruleValidator.validate(this.#rules, matrix, children.length);
1826
+ const { rules, matcher } = this.#resolveRules(context);
1827
+ const {
1828
+ matrix,
1829
+ unexpectedIndices: rawUnexpectedIndices,
1830
+ ambiguousIndices
1831
+ } = matcher.match(children);
1832
+ this.#ruleValidator.validate(rules, matrix, children.length);
1833
+ const unexpectedIndices = new Set(
1834
+ [...rawUnexpectedIndices].filter(
1835
+ (ci) => isTextLike(children[ci]) ? this.#allowText === false : this.#exclusiveChildren
1836
+ )
1837
+ );
1789
1838
  if (unexpectedIndices.size === 0 && ambiguousIndices.size === 0) return;
1790
1839
  const violating = [...unexpectedIndices, ...ambiguousIndices].sort((a, b) => a - b);
1791
1840
  iterate.forEach(violating, (ci) => {
@@ -1794,11 +1843,28 @@ var ChildrenEvaluator = class extends InvariantBase {
1794
1843
  this.violate(ContractDiagnostics.unexpectedChild(typeName, ci, this.#context));
1795
1844
  } else {
1796
1845
  const matches = matrix.childToRules.forward.get(ci);
1797
- const names = [...matches].map((ri) => this.#ruleNames[ri] ?? `#${ri}`);
1846
+ const names = [...matches].map((ri) => rules[ri]?.name ?? `#${ri}`);
1798
1847
  this.violate(ContractDiagnostics.ambiguousChild(typeName, ci, names, this.#context));
1799
1848
  }
1800
1849
  });
1801
1850
  }
1851
+ #resolveRules(context) {
1852
+ if (this.#dynamicRuleInputs.length === 0) {
1853
+ return { rules: this.#staticRules, matcher: this.#matcher };
1854
+ }
1855
+ if (context === void 0) {
1856
+ throw new RangeError(
1857
+ `ChildrenEvaluator [${this.#context}]: rule(s) have a dynamic(...) cardinality \u2014 evaluate() requires a context argument to resolve them.`
1858
+ );
1859
+ }
1860
+ const resolvedDynamicRules = this.#dynamicRuleInputs.map((r) => {
1861
+ const cardinality = resolveRule(r.cardinality, context);
1862
+ return normalizeChildRule({ ...r, cardinality });
1863
+ });
1864
+ checkPositionCardinalityInvariant(resolvedDynamicRules, this.#context);
1865
+ const rules = [...this.#staticRules, ...resolvedDynamicRules];
1866
+ return { rules, matcher: new RuleMatcher(rules) };
1867
+ }
1802
1868
  };
1803
1869
 
1804
1870
  // ../../lib/contract/src/props/get-disabled-props.ts
@@ -1907,8 +1973,11 @@ function metadata(name = "metadata") {
1907
1973
  function firstOptional(name, tag) {
1908
1974
  return { name, match: isTag(tag), cardinality: { max: 1 }, position: "first" };
1909
1975
  }
1910
- function contract(children) {
1911
- return { diagnostics: warnDiagnostics, children };
1976
+ function contract(children, options) {
1977
+ return { diagnostics: warnDiagnostics, children, ...options };
1978
+ }
1979
+ function closedContract(children) {
1980
+ return contract(children, { exclusiveChildren: true });
1912
1981
  }
1913
1982
  function ariaContract(aria) {
1914
1983
  return { diagnostics: warnDiagnostics, aria };
@@ -1934,8 +2003,10 @@ var VOID_TAGS = [
1934
2003
  ];
1935
2004
  var TEXT_ONLY_TAGS = ["option", "script", "style", "textarea", "title"];
1936
2005
  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([
2006
+ var listContract = closedContract([
2007
+ { name: "list-item", match: isTag("li", ...METADATA_TAGS) }
2008
+ ]);
2009
+ var tableContract = closedContract([
1939
2010
  firstOptional("caption", "caption"),
1940
2011
  { name: "colgroup", match: isTag("colgroup") },
1941
2012
  { name: "thead", match: isTag("thead"), cardinality: { max: 1 } },
@@ -1943,29 +2014,31 @@ var tableContract = contract([
1943
2014
  { name: "tfoot", match: isTag("tfoot"), cardinality: { max: 1 } },
1944
2015
  { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
1945
2016
  ]);
1946
- var tableBodyContract = contract([
2017
+ var tableBodyContract = closedContract([
1947
2018
  { name: "table-row", match: isTag("tr", ...METADATA_TAGS) }
1948
2019
  ]);
1949
- var tableRowContract = contract([
2020
+ var tableRowContract = closedContract([
1950
2021
  { name: "table-cell", match: isTag("td", "th", ...METADATA_TAGS) }
1951
2022
  ]);
1952
- var colgroupContract = contract([{ name: "column", match: isTag("col", "template") }]);
1953
- var dlContract = contract([
2023
+ var colgroupContract = closedContract([
2024
+ { name: "column", match: isTag("col", "template") }
2025
+ ]);
2026
+ var dlContract = closedContract([
1954
2027
  { name: "term", match: isTag("dt") },
1955
2028
  { name: "description", match: isTag("dd") },
1956
2029
  { name: "group", match: isTag("div") },
1957
2030
  metadata()
1958
2031
  ]);
1959
- var selectContract = contract([
2032
+ var selectContract = closedContract([
1960
2033
  { name: "option", match: isTag("option", "optgroup", "hr", ...METADATA_TAGS) }
1961
2034
  ]);
1962
- var optgroupContract = contract([
2035
+ var optgroupContract = closedContract([
1963
2036
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
1964
2037
  ]);
1965
- var datalistContract = contract([
2038
+ var datalistContract = closedContract([
1966
2039
  { name: "option", match: isTag("option", ...METADATA_TAGS) }
1967
2040
  ]);
1968
- var pictureContract = contract([
2041
+ var pictureContract = closedContract([
1969
2042
  { name: "source", match: isTag("source", ...METADATA_TAGS) },
1970
2043
  { name: "image", match: isTag("img"), cardinality: { min: 1, max: 1 }, position: "last" }
1971
2044
  ]);
@@ -1981,23 +2054,18 @@ var mediaContract = contract([
1981
2054
  metadata(),
1982
2055
  { name: "content", match: isOpenContent("source", "track", ...METADATA_TAGS) }
1983
2056
  ]);
1984
- var headContract = contract([
2057
+ var headContract = closedContract([
1985
2058
  {
1986
2059
  name: "metadata",
1987
2060
  match: isTag("base", "link", "meta", "noscript", "script", "style", "template", "title")
1988
2061
  }
1989
2062
  ]);
1990
- var htmlContract = contract([
2063
+ var htmlContract = closedContract([
1991
2064
  { name: "head", match: isTag("head"), cardinality: { min: 1, max: 1 }, position: "first" },
1992
2065
  { name: "body", match: isTag("body"), cardinality: { min: 1, max: 1 } }
1993
2066
  ]);
1994
- var voidContract = contract([]);
1995
- var textOnlyContract = contract([
1996
- {
1997
- name: "text",
1998
- match: (child) => isString(child) || isNumber(child)
1999
- }
2000
- ]);
2067
+ var voidContract = contract([], { exclusiveChildren: true, allowText: false });
2068
+ var textOnlyContract = contract([], { exclusiveChildren: true });
2001
2069
  var landmarkContract = ariaContract([landmarkRoleRule, landmarkNameAdvisory]);
2002
2070
  var dialogContract = ariaContract([requireAccessibleName]);
2003
2071
  var menuContract = ariaContract([requireAccessibleName]);
@@ -2042,9 +2110,15 @@ var htmlContracts = {
2042
2110
  var htmlDiagnostics = warnDiagnostics2;
2043
2111
  function buildEvaluatorMap() {
2044
2112
  const map2 = /* @__PURE__ */ new Map();
2045
- iterate.forEachEntry(htmlContracts, (tag, { children }) => {
2046
- if (children?.length) {
2047
- map2.set(tag, new ChildrenEvaluator(children, htmlDiagnostics, `<${tag}>`));
2113
+ iterate.forEachEntry(htmlContracts, (tag, { children, exclusiveChildren, allowText }) => {
2114
+ if (children?.length || exclusiveChildren || allowText === false) {
2115
+ map2.set(
2116
+ tag,
2117
+ new ChildrenEvaluator(children ?? [], htmlDiagnostics, `<${tag}>`, {
2118
+ exclusiveChildren,
2119
+ allowText
2120
+ })
2121
+ );
2048
2122
  }
2049
2123
  });
2050
2124
  return map2;
@@ -2255,7 +2329,7 @@ function definePipeline(factory) {
2255
2329
  }
2256
2330
 
2257
2331
  // ../core/src/options/resolve-factory-options.ts
2258
- import { silentDiagnostics } from "../_shared/diagnostics.js";
2332
+ import { resolveDiagnostics, silentDiagnostics } from "../_shared/diagnostics.js";
2259
2333
  var EMPTY_VARIANT_KEYS = /* @__PURE__ */ new Set();
2260
2334
  function composeNormalizers(normalizers, fn) {
2261
2335
  if (!normalizers?.length) return fn;
@@ -2276,7 +2350,7 @@ function resolveFactoryOptions(options = {}) {
2276
2350
  const variantKeys = styling?.variants === void 0 ? EMPTY_VARIANT_KEYS : new Set(Object.keys(styling.variants));
2277
2351
  return Object.freeze({
2278
2352
  defaultTag: options.tag ?? "div",
2279
- diagnostics: enforcement?.diagnostics ?? silentDiagnostics,
2353
+ diagnostics: resolveDiagnostics(enforcement?.diagnostics, silentDiagnostics),
2280
2354
  variantKeys,
2281
2355
  ...whenDefined("displayName", options.name),
2282
2356
  ...whenDefined("defaultProps", options.defaults),
@@ -2289,6 +2363,8 @@ function resolveFactoryOptions(options = {}) {
2289
2363
  ...whenDefined("normalizeFn", composedNormalizeFn),
2290
2364
  ...whenDefined("ariaRules", enforcement?.aria),
2291
2365
  ...whenDefined("childRules", enforcement?.children),
2366
+ ...whenDefined("exclusiveChildren", enforcement?.exclusiveChildren),
2367
+ ...whenDefined("allowText", enforcement?.allowText),
2292
2368
  ...whenDefined("allowedAs", enforcement?.allowedAs),
2293
2369
  ...whenDefined("precomputedClasses", styling?.precomputedClasses)
2294
2370
  });
@@ -2464,16 +2540,22 @@ function buildCoreRuntime(normalized) {
2464
2540
  }
2465
2541
 
2466
2542
  // ../../lib/adapter-utils/src/runtime/build-engines.ts
2467
- function buildEngines(diagnostics, childRules, context) {
2468
- return childRules?.length ? { childrenEvaluator: new ChildrenEvaluator(childRules, diagnostics, context) } : {};
2543
+ function buildEngines(diagnostics, childRules, context, childrenOptions) {
2544
+ const { exclusiveChildren, allowText } = childrenOptions ?? {};
2545
+ return childRules?.length || exclusiveChildren || allowText === false ? {
2546
+ childrenEvaluator: new ChildrenEvaluator(childRules ?? [], diagnostics, context, {
2547
+ exclusiveChildren,
2548
+ allowText
2549
+ })
2550
+ } : {};
2469
2551
  }
2470
2552
 
2471
2553
  // ../../lib/adapter-utils/src/runtime/resolve-adapter-common-options.ts
2472
- import { throwDiagnostics as throwDiagnostics2, silentDiagnostics as silentDiagnostics2 } from "../_shared/diagnostics.js";
2554
+ import { throwDiagnostics as throwDiagnostics2, silentDiagnostics as silentDiagnostics2, resolveDiagnostics as resolveDiagnostics2 } from "../_shared/diagnostics.js";
2473
2555
  function resolveAdapterCommonOptions(options, defaultName = "PolymorphicComponent", defaultDiagnostics = throwDiagnostics2) {
2474
2556
  return {
2475
2557
  name: options.name ?? defaultName,
2476
- diagnostics: options.enforcement?.diagnostics ?? defaultDiagnostics
2558
+ diagnostics: resolveDiagnostics2(options.enforcement?.diagnostics, defaultDiagnostics)
2477
2559
  };
2478
2560
  }
2479
2561
 
@@ -2518,7 +2600,11 @@ function buildRuntime(options) {
2518
2600
  const { childrenEvaluator } = buildEngines(
2519
2601
  normalized.diagnostics,
2520
2602
  normalized.enforcement?.children,
2521
- normalized.name
2603
+ normalized.name,
2604
+ {
2605
+ exclusiveChildren: normalized.enforcement?.exclusiveChildren,
2606
+ allowText: normalized.enforcement?.allowText
2607
+ }
2522
2608
  );
2523
2609
  const filterProps = composeFilter(ownedKeys, normalized.filterProps);
2524
2610
  const slotValidator = new SlotValidator(normalized.name, normalized.diagnostics, "Snippet");
@@ -1,5 +1,21 @@
1
1
  import { Diagnostics } from '../_shared/diagnostics.js';
2
- import { RequireAtLeastOne, Simplify } from 'type-fest';
2
+ import { RequireAtLeastOne, Simplify, ValueOf } from 'type-fest';
3
+
4
+ /**
5
+ * Canonical list of reserved layout prop names.
6
+ *
7
+ * This array is the single source of truth for every supported CSS `display`
8
+ * value exposed as a boolean prop. The `LayoutKey` type is derived directly
9
+ * from this list.
10
+ *
11
+ * To add a new display mode:
12
+ * 1. Add the display value here.
13
+ * 2. Register its layout family in `LAYOUT_FAMILY_MAP`.
14
+ *
15
+ * Prop names intentionally match the corresponding Tailwind/CSS display
16
+ * utilities, so no additional prop-to-class mapping is required.
17
+ */
18
+ declare const layoutKeys: readonly ["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"];
3
19
 
4
20
  type StringMap<T = unknown> = Record<string, T>;
5
21
  type AnyRecord = StringMap<unknown>;
@@ -94,35 +110,19 @@ type ClassPlugin<TProps extends AnyRecord = EmptyRecord> = Readonly<{
94
110
  readonly _pluginProps?: TProps;
95
111
  }>;
96
112
 
97
- /**
98
- * Canonical list of reserved layout prop names.
99
- *
100
- * This array is the single source of truth for every supported CSS `display`
101
- * value exposed as a boolean prop. The `LayoutKey` type is derived directly
102
- * from this list.
103
- *
104
- * To add a new display mode:
105
- * 1. Add the display value here.
106
- * 2. Register its layout family in `LAYOUT_FAMILY_MAP`.
107
- *
108
- * Prop names intentionally match the corresponding Tailwind/CSS display
109
- * utilities, so no additional prop-to-class mapping is required.
110
- */
111
- declare const layoutKeys: readonly ["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"];
112
-
113
113
  type TupleValues<T extends readonly unknown[]> = T[number];
114
- type ValueOf<T> = T[keyof T];
115
114
  type LayoutKey<T extends readonly string[]> = TupleValues<T>;
116
115
  type LayoutFamily<M extends StringMap<string>> = ValueOf<M>;
117
- type LayoutMode<T extends readonly string[]> = LayoutKey<T> | 'none';
116
+ type ResolvedLayout<T extends readonly string[]> = LayoutKey<T> | 'none';
118
117
  type ExclusiveTrueProp<K extends PropertyKey> = {
119
118
  [P in K]: Simplify<Record<P, true> & Partial<Record<Exclude<K, P>, never>>>;
120
119
  }[K];
121
120
  /**
122
- * Mutually exclusive display shorthand props.
121
+ * Mutually exclusive layout shorthand props.
123
122
  *
124
- * At most one key may be `true`. Passing multiple is a compile-time error; the
125
- * runtime also warns and lets the first key in declaration order take precedence.
123
+ * Exactly one layout prop may be `true`, or none at all. Multiple `true`
124
+ * props produce a compile-time error. If invalid props reach runtime, the
125
+ * resolver warns and uses the first declared layout.
126
126
  */
127
127
  type LayoutProps<T extends readonly string[]> = ExclusiveTrueProp<LayoutKey<T>> | Partial<Record<LayoutKey<T>, never>>;
128
128
 
@@ -155,23 +155,76 @@ type Token<TKind extends string, TData extends object = EmptyRecord> = {
155
155
  kind: TKind;
156
156
  raw: string;
157
157
  } & TData;
158
- type LayoutToken = Token<'layout', {
159
- value: LayoutKey<typeof layoutKeys>;
160
- }>;
161
- type ConditionalToken = Token<'conditional', {
162
- requires: Exclude<LayoutFamily<typeof LAYOUT_FAMILY_MAP>, 'none'>;
163
- }>;
164
- type GapToken = Token<'gap'>;
165
- type UtilityToken = Token<'utility', {
166
- base: string;
167
- }>;
168
- type ClassifiedToken = Simplify<LayoutToken | ConditionalToken | GapToken | UtilityToken>;
169
-
170
- type VariantSelection = Record<string, string>;
158
+ type TokenData = {
159
+ layout: {
160
+ value: LayoutKey<typeof layoutKeys>;
161
+ };
162
+ conditional: {
163
+ requires: Exclude<LayoutFamily<typeof LAYOUT_FAMILY_MAP>, 'none'>;
164
+ };
165
+ gap: EmptyRecord;
166
+ shared: EmptyRecord;
167
+ utility: {
168
+ base: string;
169
+ };
170
+ };
171
+ type TokenMap = {
172
+ [K in keyof TokenData]: Token<K, TokenData[K]>;
173
+ };
174
+ type LayoutToken = TokenMap['layout'];
175
+ type ConditionalToken = TokenMap['conditional'];
176
+ type GapToken = TokenMap['gap'];
177
+ type SharedToken = TokenMap['shared'];
178
+ type UtilityToken = TokenMap['utility'];
179
+ type ClassifiedToken = Simplify<ValueOf<TokenMap>>;
180
+
181
+ type VariantSelection = StringMap<string>;
171
182
  type CompoundVariant = {
172
183
  class?: unknown;
173
184
  } & AnyRecord;
174
185
 
186
+ /**
187
+ * The resolved display mode for a single render.
188
+ *
189
+ * Mode is owned by the display props; a display class literal in the resolved
190
+ * class string is a reserved-literal authoring mistake. Defaults to `'none'`
191
+ * when no prop is set.
192
+ *
193
+ * `family` is derived from mode: `'flex'` for flex/inline-flex, `'grid'` for
194
+ * grid/inline-grid, `'none'` for all other values (and when no prop is set).
195
+ * The evaluator uses family — not mode — for utility and gap filtering.
196
+ */
197
+ declare class LayoutState {
198
+ #private;
199
+ constructor(mode: ResolvedLayout<typeof layoutKeys>);
200
+ get mode(): ResolvedLayout<typeof layoutKeys>;
201
+ get family(): LayoutFamily<typeof LAYOUT_FAMILY_MAP>;
202
+ }
203
+
204
+ type PipelineLayoutProps = LayoutProps<typeof layoutKeys>;
205
+ type PipelineLayout = ResolvedLayout<typeof layoutKeys>;
206
+ type PipelineProps = PipelineLayoutProps & AnyRecord;
207
+ interface PipelineInputs {
208
+ readonly props: PipelineProps;
209
+ readonly recipe: string | undefined;
210
+ }
211
+ interface PipelineResolution {
212
+ readonly mode: PipelineLayout;
213
+ readonly state: LayoutState;
214
+ }
215
+ interface PipelineTokens {
216
+ readonly filtered: ClassifiedToken[];
217
+ readonly tokens: ClassifiedToken[];
218
+ }
219
+ type TailwindPipelineArgs = [
220
+ tag: unknown,
221
+ props: PipelineInputs['props'],
222
+ className: ClassName | undefined,
223
+ recipe: PipelineInputs['recipe']
224
+ ];
225
+ interface TailwindPipelineContext extends PipelineInputs, PipelineResolution, PipelineTokens {
226
+ }
227
+
175
228
  /**
176
229
  * Layout-aware class pipeline for Tailwind CSS utility class strings.
177
230
  *
@@ -220,28 +273,10 @@ declare class ClassClassifier {
220
273
  type DependencyRules = Record<Exclude<LayoutFamily<typeof LAYOUT_FAMILY_MAP>, 'none'>, readonly RegExp[]>;
221
274
  declare const defaultDependencyRules: DependencyRules;
222
275
 
223
- /**
224
- * The resolved display mode for a single render.
225
- *
226
- * Mode is owned by the display props; a display class literal in the resolved
227
- * class string is a reserved-literal authoring mistake. Defaults to `'none'`
228
- * when no prop is set.
229
- *
230
- * `family` is derived from mode: `'flex'` for flex/inline-flex, `'grid'` for
231
- * grid/inline-grid, `'none'` for all other values (and when no prop is set).
232
- * The evaluator uses family — not mode — for utility and gap filtering.
233
- */
234
- declare class LayoutState {
235
- #private;
236
- constructor(mode: LayoutMode<typeof layoutKeys>);
237
- get mode(): LayoutMode<typeof layoutKeys>;
238
- get family(): LayoutFamily<typeof LAYOUT_FAMILY_MAP>;
239
- }
240
-
241
276
  declare class DependencyEvaluator {
242
277
  private readonly rules;
243
278
  constructor(rules: DependencyRules);
244
279
  evaluate(token: ClassifiedToken, state: LayoutState): boolean;
245
280
  }
246
281
 
247
- export { ClassBuilder, ClassClassifier, type ClassToken, type ClassifiedToken, type CompoundVariant, type ConditionalToken, DependencyEvaluator, type DependencyRules, type GapToken, type LayoutFamily, type LayoutKey, type LayoutMode, type LayoutProps, LayoutState, type LayoutToken, type UtilityToken, type VariantSelection, createTailwindPipeline, defaultDependencyRules, layoutKeys };
282
+ export { ClassBuilder, ClassClassifier, type ClassToken, type ClassifiedToken, type CompoundVariant, type ConditionalToken, DependencyEvaluator, type DependencyRules, type GapToken, type LayoutFamily, type LayoutKey, type LayoutProps, LayoutState, type LayoutToken, type ResolvedLayout, type SharedToken, type TailwindPipelineArgs, type TailwindPipelineContext, type UtilityToken, type VariantSelection, createTailwindPipeline, defaultDependencyRules, layoutKeys };