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.
@@ -358,6 +358,14 @@ function createClassPipeline(resolved) {
358
358
  };
359
359
  }
360
360
 
361
+ // ../../lib/pipeline-kit/src/core/compose-pipelines.ts
362
+ function composePipelines(first, second) {
363
+ return (...args) => second(first(...args));
364
+ }
365
+
366
+ // ../../lib/tailwind/src/create-tailwind-pipeline.ts
367
+ import { ConsoleReporter, DefaultPolicy, Diagnostics, Severity } from "../_shared/diagnostics.js";
368
+
361
369
  // ../../lib/tailwind/src/class-builder.ts
362
370
  var ClassBuilder = class {
363
371
  build(tokens) {
@@ -371,6 +379,7 @@ var ClassBuilder = class {
371
379
  }
372
380
  case "utility":
373
381
  case "gap":
382
+ case "shared":
374
383
  case "conditional": {
375
384
  normal.push(token.raw);
376
385
  break;
@@ -444,6 +453,16 @@ var CONDITIONALS = {
444
453
  "[&.flex": "flex",
445
454
  "[&.grid": "grid"
446
455
  };
456
+ var SHARED_PREFIXES = [
457
+ /^order/,
458
+ /^justify-(?!items-|self-)/,
459
+ /^content-/,
460
+ /^items-/,
461
+ /^self-/,
462
+ /^place-content-/,
463
+ /^place-items-/,
464
+ /^place-self-/
465
+ ];
447
466
  var ClassClassifier = class _ClassClassifier {
448
467
  static #getBaseUtility(token) {
449
468
  let depth = 0;
@@ -476,23 +495,16 @@ var ClassClassifier = class _ClassClassifier {
476
495
  }
477
496
  );
478
497
  if (conditional !== null) return conditional;
479
- return base === "gap" || base.startsWith("gap-") ? {
480
- kind: "gap",
481
- raw: token
482
- } : {
483
- kind: "utility",
484
- base,
485
- raw: token
486
- };
498
+ if (base === "gap" || base.startsWith("gap-")) {
499
+ return { kind: "gap", raw: token };
500
+ }
501
+ if (SHARED_PREFIXES.some((rule) => rule.test(base))) {
502
+ return { kind: "shared", raw: token };
503
+ }
504
+ return { kind: "utility", base, raw: token };
487
505
  }
488
506
  };
489
507
 
490
- // ../../lib/tailwind/src/dependency-rules.ts
491
- var defaultDependencyRules = {
492
- flex: [/^flex-/, /^grow/, /^shrink/, /^basis-/],
493
- grid: [/^grid-/, /^col-/, /^row-/, /^auto-cols-/, /^auto-rows-/]
494
- };
495
-
496
508
  // ../../lib/tailwind/src/dependency-evaluator.ts
497
509
  var DependencyEvaluator = class {
498
510
  constructor(rules) {
@@ -513,7 +525,8 @@ var DependencyEvaluator = class {
513
525
  (layout) => this.rules[layout].some((rule) => rule.test(token.base)) ? state.family === layout : null
514
526
  ) ?? true;
515
527
  }
516
- case "gap": {
528
+ case "gap":
529
+ case "shared": {
517
530
  return state.family !== "none";
518
531
  }
519
532
  default:
@@ -522,26 +535,22 @@ var DependencyEvaluator = class {
522
535
  }
523
536
  };
524
537
 
525
- // ../../lib/tailwind/src/layout-state.ts
526
- var LayoutState = class {
527
- #mode;
528
- #family;
529
- constructor(mode) {
530
- this.#mode = mode;
531
- this.#family = mode === "none" ? "none" : LAYOUT_FAMILY_MAP[mode];
532
- Object.freeze(this);
533
- }
534
- get mode() {
535
- return this.#mode;
536
- }
537
- get family() {
538
- return this.#family;
539
- }
538
+ // ../../lib/tailwind/src/dependency-rules.ts
539
+ var defaultDependencyRules = {
540
+ flex: [/^flex-/, /^grow/, /^shrink/, /^basis-/],
541
+ grid: [
542
+ /^grid-/,
543
+ /^col-/,
544
+ /^row-/,
545
+ /^auto-cols-/,
546
+ /^auto-rows-/,
547
+ // justify-items/-self are no-ops on flex containers per the CSS box
548
+ // alignment spec (flex items ignore them), so treat as grid-only.
549
+ /^justify-items-/,
550
+ /^justify-self-/
551
+ ]
540
552
  };
541
553
 
542
- // ../../lib/tailwind/src/create-tailwind-pipeline.ts
543
- import { ConsoleReporter, Diagnostics, DefaultPolicy, Severity } from "../_shared/diagnostics.js";
544
-
545
554
  // ../../lib/tailwind/src/diagnostics.ts
546
555
  import { DiagnosticCategory, DiagnosticCode } from "../_shared/diagnostics.js";
547
556
  var TailwindDiagnostics = {
@@ -568,6 +577,23 @@ var TailwindDiagnostics = {
568
577
  }
569
578
  };
570
579
 
580
+ // ../../lib/tailwind/src/layout-state.ts
581
+ var LayoutState = class {
582
+ #mode;
583
+ #family;
584
+ constructor(mode) {
585
+ this.#mode = mode;
586
+ this.#family = mode === "none" ? "none" : LAYOUT_FAMILY_MAP[mode];
587
+ Object.freeze(this);
588
+ }
589
+ get mode() {
590
+ return this.#mode;
591
+ }
592
+ get family() {
593
+ return this.#family;
594
+ }
595
+ };
596
+
571
597
  // ../../lib/tailwind/src/create-tailwind-pipeline.ts
572
598
  var DEV = process.env.NODE_ENV !== "production";
573
599
  var devDiagnostics = new Diagnostics(
@@ -648,23 +674,36 @@ function warnDeadVariants(diagnostics, options, compoundDims, props, recipe, sta
648
674
  });
649
675
  }
650
676
  function createTailwindPipeline(options, diagnostics) {
651
- const pipeline = createClassPipeline(options);
677
+ const innerPipeline = createClassPipeline(options);
652
678
  const compoundDims = compoundDimensions(getCompoundVariants(options));
679
+ const resolveLayoutContext = (tag, props, className, recipe) => {
680
+ const mode = resolveLayout(devDiagnostics, props);
681
+ const resolvedClasses = innerPipeline(tag, props, className, recipe);
682
+ const tokens = classifyTokens(resolvedClasses);
683
+ const state = new LayoutState(mode);
684
+ const filtered = tokens.filter((token) => evaluator.evaluate(token, state));
685
+ return { mode, state, filtered, tokens, props, recipe };
686
+ };
687
+ const buildClassString = (ctx) => {
688
+ const built = builder.build(ctx.filtered);
689
+ if (ctx.mode === "none") return built;
690
+ return ctx.filtered.some((t) => t.kind === "layout" && t.value === ctx.mode) ? built : cn(ctx.mode, built);
691
+ };
692
+ const emitDiagnosticsFromContext = (ctx) => {
693
+ if (!DEV) return;
694
+ warnReservedLayoutLiterals(diagnostics, ctx.tokens);
695
+ warnDeadVariants(diagnostics, options, compoundDims, ctx.props, ctx.recipe, ctx.state);
696
+ };
697
+ const combined = (ctx) => {
698
+ const built = buildClassString(ctx);
699
+ emitDiagnosticsFromContext(ctx);
700
+ return built;
701
+ };
702
+ const mainPipeline = composePipelines(resolveLayoutContext, combined);
653
703
  return {
654
704
  ownedKeys: LAYOUT_OWNED_KEYS,
655
705
  pipeline(tag, props, className, recipe) {
656
- const mode = resolveLayout(devDiagnostics, props);
657
- const raw = pipeline(tag, props, className, recipe);
658
- const tokens = classifyTokens(raw);
659
- const state = new LayoutState(mode);
660
- if (DEV) {
661
- warnReservedLayoutLiterals(diagnostics, tokens);
662
- warnDeadVariants(diagnostics, options, compoundDims, props, recipe, state);
663
- }
664
- const filtered = tokens.filter((token) => evaluator.evaluate(token, state));
665
- const built = builder.build(filtered);
666
- if (mode === "none") return built;
667
- return filtered.some((t) => t.kind === "layout" && t.value === mode) ? built : cn(mode, built);
706
+ return mainPipeline(tag, props, className, recipe);
668
707
  }
669
708
  };
670
709
  }
@@ -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
 
@@ -31,6 +31,26 @@ type MinMax = {
31
31
  };
32
32
  type CardinalityInput = Partial<MinMax>;
33
33
 
34
+ /**
35
+ * Resolved per-instance state available to a dynamic (`dynamic(...)`) child
36
+ * rule field — the same tag/props every adapter already computes before
37
+ * evaluating children, exposed so a rule can vary by them (e.g. cardinality
38
+ * that depends on the resolved `as` tag).
39
+ */
40
+ type ChildRuleContext = {
41
+ readonly tag: unknown;
42
+ readonly props: Readonly<AnyRecord>;
43
+ };
44
+
45
+ declare const RULE_BRAND: unique symbol;
46
+
47
+ type DynamicRule<T, C = unknown> = {
48
+ readonly [RULE_BRAND]: true;
49
+ resolve(context: C): T;
50
+ };
51
+
52
+ type Rule<T, C = unknown> = T | DynamicRule<T, C>;
53
+
34
54
  type ChildRuleMatch<T, U extends T = T> = (child: T) => child is U;
35
55
 
36
56
  type ChildRulePosition = 'first' | 'last' | 'any';
@@ -38,7 +58,13 @@ type ChildRulePosition = 'first' | 'last' | 'any';
38
58
  type ChildRuleInput<T = unknown, U extends T = T> = {
39
59
  name: string;
40
60
  match: ChildRuleMatch<T, U>;
41
- cardinality?: CardinalityInput;
61
+ /**
62
+ * Either a static cardinality, or `dynamic((ctx) => ...)` to derive it from
63
+ * the resolved tag/props (e.g. a different max depending on `as`). `match`
64
+ * stays static-only — it's already a function, so a dynamic wrapper would
65
+ * be indistinguishable from the predicate itself without one.
66
+ */
67
+ cardinality?: Rule<CardinalityInput, ChildRuleContext>;
42
68
  position?: ChildRulePosition;
43
69
  /**
44
70
  * Optional component-type reference for O(1) dispatch index.
@@ -214,9 +240,25 @@ type AriaRule<C extends AriaContext = AriaContext> = (context: C) => readonly Ar
214
240
  type PropNormalizer = (props: Readonly<AnyRecord & IntrinsicProps>) => Partial<AnyRecord & IntrinsicProps>;
215
241
 
216
242
  type EnforcementOptions<TAllowed extends ElementType = ElementType> = {
217
- readonly diagnostics?: Diagnostics;
243
+ /**
244
+ * Accepts a preset name (`'warn'`, `'throw'`, `'silent'`) or a full `Diagnostics`
245
+ * instance for custom reporting/policy. The string form needs no import from
246
+ * `@praxis-kit/diagnostics`.
247
+ */
248
+ readonly diagnostics?: Diagnostics | DiagnosticsMode;
218
249
  readonly aria?: readonly AriaRule[];
219
250
  readonly children?: readonly ChildRuleInput[];
251
+ /**
252
+ * When true, only children matching a `children` rule (or text, per `allowText`)
253
+ * are valid — anything else is rejected. Default: false (open — children not
254
+ * matching any rule are allowed).
255
+ */
256
+ readonly exclusiveChildren?: boolean;
257
+ /**
258
+ * When false, text/number child nodes are rejected regardless of exclusiveChildren
259
+ * or any listed rule. Default: true.
260
+ */
261
+ readonly allowText?: boolean;
220
262
  readonly props?: readonly PropNormalizer[];
221
263
  /** Restricts the `as` prop to this set of tags. Violations route through diagnostics. */
222
264
  readonly allowedAs?: readonly TAllowed[];