@kbach/react 1.0.0-beta.4 → 1.0.0-beta.6

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.
@@ -0,0 +1,1340 @@
1
+ import {
2
+ resolveKbachConfig
3
+ } from "./chunk-NKR3PSWO.mjs";
4
+ import {
5
+ buildResetCSS,
6
+ defaultTheme
7
+ } from "./chunk-N5HCQIBB.mjs";
8
+
9
+ // src/vite-plugin/index.ts
10
+ import { writeFileSync, mkdirSync, existsSync } from "fs";
11
+ import { join as join4, dirname } from "path";
12
+ import { createRequire } from "module";
13
+
14
+ // src/vite-plugin/cssVars.ts
15
+ function hexToRgbTriplet(hex) {
16
+ const h = hex.replace("#", "");
17
+ const expand = (s) => s.length === 3 ? s.split("").map((c) => c + c).join("") : s;
18
+ const full = expand(h);
19
+ if (full.length !== 6) return null;
20
+ const r = parseInt(full.slice(0, 2), 16);
21
+ const g = parseInt(full.slice(2, 4), 16);
22
+ const b = parseInt(full.slice(4, 6), 16);
23
+ if ([r, g, b].some((n) => Number.isNaN(n))) return null;
24
+ return `${r},${g},${b}`;
25
+ }
26
+ function buildColorVarMap(theme, cssText) {
27
+ const replacements = /* @__PURE__ */ new Map();
28
+ const declarations = /* @__PURE__ */ new Map();
29
+ for (const [name, value] of Object.entries(theme.colors)) {
30
+ if (typeof value !== "string") continue;
31
+ const varName = `--color-${name}`;
32
+ if (cssText.includes(value)) {
33
+ replacements.set(value, `var(${varName})`);
34
+ declarations.set(varName, value);
35
+ }
36
+ const triplet = hexToRgbTriplet(value);
37
+ if (!triplet) continue;
38
+ const rgbaPrefix = `rgba(${triplet},`;
39
+ if (cssText.includes(rgbaPrefix)) {
40
+ const rgbVarName = `--color-${name}-rgb`;
41
+ replacements.set(rgbaPrefix, `rgba(var(${rgbVarName}),`);
42
+ declarations.set(rgbVarName, triplet);
43
+ }
44
+ }
45
+ return { replacements, declarations };
46
+ }
47
+ var SPACING_PROPS = /* @__PURE__ */ new Set([
48
+ "padding",
49
+ "padding-top",
50
+ "padding-right",
51
+ "padding-bottom",
52
+ "padding-left",
53
+ "margin",
54
+ "margin-top",
55
+ "margin-right",
56
+ "margin-bottom",
57
+ "margin-left",
58
+ "gap",
59
+ "column-gap",
60
+ "row-gap",
61
+ "width",
62
+ "height",
63
+ "min-width",
64
+ "min-height",
65
+ "max-width",
66
+ "max-height",
67
+ "top",
68
+ "right",
69
+ "bottom",
70
+ "left"
71
+ ]);
72
+ function buildSpacingVarMap(theme, cssText) {
73
+ const replacements = /* @__PURE__ */ new Map();
74
+ const declarations = /* @__PURE__ */ new Map();
75
+ const pxToKey = /* @__PURE__ */ new Map();
76
+ for (const [key, val] of Object.entries(theme.spacing)) {
77
+ if (!pxToKey.has(val)) pxToKey.set(val, key);
78
+ }
79
+ const varNameFor = (key) => `--spacing-${key.replace(/\./g, "_")}`;
80
+ const declRe = /([a-z-]+): (\d+(?:\.\d+)?)px/g;
81
+ let m;
82
+ while ((m = declRe.exec(cssText)) !== null) {
83
+ const [, prop, numStr] = m;
84
+ if (!SPACING_PROPS.has(prop)) continue;
85
+ const key = pxToKey.get(parseFloat(numStr));
86
+ if (key === void 0) continue;
87
+ const varName = varNameFor(key);
88
+ replacements.set(`${prop}: ${numStr}px`, `${prop}: var(${varName})`);
89
+ declarations.set(varName, `${numStr}px`);
90
+ }
91
+ return { replacements, declarations };
92
+ }
93
+ function applyVarMap(cssText, ...maps) {
94
+ const replacements = /* @__PURE__ */ new Map();
95
+ const declarations = /* @__PURE__ */ new Map();
96
+ for (const map of maps) {
97
+ for (const [k, v] of map.replacements) replacements.set(k, v);
98
+ for (const [k, v] of map.declarations) declarations.set(k, v);
99
+ }
100
+ const sorted = [...replacements].sort(([a], [b]) => b.length - a.length);
101
+ const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
102
+ const patterns = sorted.map(([pattern, replacement]) => [new RegExp(escapeRegex(pattern), "g"), replacement]);
103
+ const text = cssText.replace(/\{([^{}]*)\}/g, (_whole, inner) => {
104
+ let replaced = inner;
105
+ for (const [re, replacement] of patterns) {
106
+ replaced = replaced.replace(re, () => replacement);
107
+ }
108
+ return `{${replaced}}`;
109
+ });
110
+ return { text, declarations };
111
+ }
112
+
113
+ // src/vite-plugin/mediaMerge.ts
114
+ var RESPONSIVE_MEDIA_RE = /^@media \(min-width: (\d+)px\) \{ (.+) \}$/;
115
+ function mergeResponsiveMediaBlocks(rules) {
116
+ const byWidth = /* @__PURE__ */ new Map();
117
+ const widthOrder = [];
118
+ const unwrapped = [];
119
+ for (const rule of rules) {
120
+ const m = RESPONSIVE_MEDIA_RE.exec(rule);
121
+ if (!m) {
122
+ unwrapped.push(rule);
123
+ continue;
124
+ }
125
+ const width = Number(m[1]);
126
+ if (!byWidth.has(width)) {
127
+ byWidth.set(width, []);
128
+ widthOrder.push(width);
129
+ }
130
+ byWidth.get(width).push(m[2]);
131
+ }
132
+ widthOrder.sort((a, b) => a - b);
133
+ const merged = widthOrder.map((w) => `@media (min-width: ${w}px) { ${byWidth.get(w).join(" ")} }`);
134
+ return [...unwrapped, ...merged];
135
+ }
136
+
137
+ // src/vite-plugin/prettify.ts
138
+ function prettifyCSS(css) {
139
+ const out = [];
140
+ let depth = 0;
141
+ let current = "";
142
+ for (const ch of css) {
143
+ if (ch === "{") {
144
+ const selector = current.trim();
145
+ if (selector) out.push(`${" ".repeat(depth)}${selector} {`);
146
+ depth++;
147
+ current = "";
148
+ } else if (ch === "}") {
149
+ const decl = current.trim();
150
+ if (decl) out.push(`${" ".repeat(depth)}${decl};`);
151
+ depth = Math.max(0, depth - 1);
152
+ out.push(`${" ".repeat(depth)}}`);
153
+ current = "";
154
+ } else if (ch === ";") {
155
+ const decl = current.trim();
156
+ if (decl) out.push(`${" ".repeat(depth)}${decl};`);
157
+ current = "";
158
+ } else {
159
+ current += ch;
160
+ }
161
+ }
162
+ const trailing = current.trim();
163
+ if (trailing) out.push(trailing);
164
+ return out.join("\n");
165
+ }
166
+
167
+ // src/vite-plugin/format.ts
168
+ var KBACH_START = "/* kbach:start */";
169
+ var KBACH_END = "/* kbach:end */";
170
+ function formatKbachCSS(tokenRules, theme, usedTags) {
171
+ const allEntries = [];
172
+ for (const rules of tokenRules.values()) {
173
+ for (const entry of rules) allEntries.push(entry);
174
+ }
175
+ allEntries.sort((a, b) => a.order - b.order);
176
+ const allRules = allEntries.map((e) => e.rule);
177
+ const merged = mergeResponsiveMediaBlocks(allRules);
178
+ const compact = merged.join("\n");
179
+ const colorMap = buildColorVarMap(theme, compact);
180
+ const spacingMap = buildSpacingVarMap(theme, compact);
181
+ const { text, declarations } = applyVarMap(compact, colorMap, spacingMap);
182
+ const lines = ["/* Generated by Kbach \u2014 do not edit */", "", buildResetCSS(usedTags)];
183
+ if (declarations.size > 0) {
184
+ lines.push("", ":root {");
185
+ for (const [varName, val] of declarations) lines.push(` ${varName}: ${val};`);
186
+ lines.push("}");
187
+ }
188
+ lines.push("", prettifyCSS(text));
189
+ return lines.join("\n");
190
+ }
191
+ function writeKbachToFile(filePath, css, readFile, writeFile) {
192
+ let existing = "";
193
+ try {
194
+ existing = readFile(filePath);
195
+ } catch {
196
+ }
197
+ const block = `${KBACH_START}
198
+ ${css}
199
+ ${KBACH_END}`;
200
+ const start = existing.indexOf(KBACH_START);
201
+ const end = existing.indexOf(KBACH_END);
202
+ const next = start !== -1 && end !== -1 && end > start ? existing.slice(0, start) + block + existing.slice(end + KBACH_END.length) : existing ? `${existing}
203
+
204
+ ${block}` : block;
205
+ if (next === existing) return false;
206
+ writeFile(filePath, next);
207
+ return true;
208
+ }
209
+
210
+ // src/vite-plugin/classNameHints.ts
211
+ var STRUCTURAL_TOKENS = [
212
+ // layout.rs
213
+ "flex",
214
+ "flex-row",
215
+ "flex-col",
216
+ "flex-row-reverse",
217
+ "flex-col-reverse",
218
+ "flex-wrap",
219
+ "flex-wrap-reverse",
220
+ "flex-nowrap",
221
+ "flex-1",
222
+ "flex-auto",
223
+ "flex-initial",
224
+ "flex-none",
225
+ "flex-grow",
226
+ "flex-grow-0",
227
+ "flex-shrink",
228
+ "flex-shrink-0",
229
+ "grow",
230
+ "grow-0",
231
+ "shrink",
232
+ "shrink-0",
233
+ "grid",
234
+ "block",
235
+ "inline-block",
236
+ "inline",
237
+ "inline-flex",
238
+ "inline-grid",
239
+ "contents",
240
+ "flow-root",
241
+ "hidden",
242
+ "items-center",
243
+ "items-start",
244
+ "items-end",
245
+ "items-baseline",
246
+ "items-stretch",
247
+ "justify-center",
248
+ "justify-start",
249
+ "justify-end",
250
+ "justify-between",
251
+ "justify-around",
252
+ "justify-evenly",
253
+ "content-start",
254
+ "content-end",
255
+ "content-center",
256
+ "content-between",
257
+ "content-around",
258
+ "content-evenly",
259
+ "content-stretch",
260
+ "self-auto",
261
+ "self-center",
262
+ "self-start",
263
+ "self-end",
264
+ "self-stretch",
265
+ "self-baseline",
266
+ "overflow-hidden",
267
+ "overflow-auto",
268
+ "overflow-scroll",
269
+ "overflow-visible",
270
+ "static",
271
+ "relative",
272
+ "absolute",
273
+ "fixed",
274
+ "sticky",
275
+ "aspect-square",
276
+ "aspect-video",
277
+ "w-full",
278
+ "h-full",
279
+ "min-w-full",
280
+ "max-w-full",
281
+ "min-h-full",
282
+ "max-h-full",
283
+ "w-screen",
284
+ "h-screen",
285
+ "min-w-screen",
286
+ "max-w-screen",
287
+ "min-h-screen",
288
+ "max-h-screen",
289
+ "w-auto",
290
+ "h-auto",
291
+ "m-auto",
292
+ "mx-auto",
293
+ "my-auto",
294
+ // grid.rs — web-only (see the module's own doc comment for why)
295
+ "grid-cols-none",
296
+ "grid-rows-none",
297
+ "grid-flow-row",
298
+ "grid-flow-col",
299
+ "grid-flow-dense",
300
+ "grid-flow-row-dense",
301
+ "grid-flow-col-dense",
302
+ "auto-cols-auto",
303
+ "auto-cols-min",
304
+ "auto-cols-max",
305
+ "auto-cols-fr",
306
+ "auto-rows-auto",
307
+ "auto-rows-min",
308
+ "auto-rows-max",
309
+ "auto-rows-fr",
310
+ "col-span-full",
311
+ "row-span-full",
312
+ "col-start-auto",
313
+ "col-end-auto",
314
+ "row-start-auto",
315
+ "row-end-auto",
316
+ "col",
317
+ "col-auto",
318
+ "row",
319
+ "row-auto",
320
+ "place-items-start",
321
+ "place-items-end",
322
+ "place-items-center",
323
+ "place-items-stretch",
324
+ "place-items-baseline",
325
+ "place-content-start",
326
+ "place-content-end",
327
+ "place-content-center",
328
+ "place-content-stretch",
329
+ "place-content-between",
330
+ "place-content-around",
331
+ "place-content-evenly",
332
+ "place-content-baseline",
333
+ "place-self-auto",
334
+ "place-self-start",
335
+ "place-self-end",
336
+ "place-self-center",
337
+ "place-self-stretch",
338
+ "justify-items-start",
339
+ "justify-items-end",
340
+ "justify-items-center",
341
+ "justify-items-stretch",
342
+ "justify-self-auto",
343
+ "justify-self-start",
344
+ "justify-self-end",
345
+ "justify-self-center",
346
+ "justify-self-stretch",
347
+ // transform.rs — web-only (see the module's own doc comment for why)
348
+ "transform-none",
349
+ "origin-center",
350
+ "origin-top",
351
+ "origin-top-right",
352
+ "origin-right",
353
+ "origin-bottom-right",
354
+ "origin-bottom",
355
+ "origin-bottom-left",
356
+ "origin-left",
357
+ "origin-top-left",
358
+ // filters.rs — web-only (see the module's own doc comment for why)
359
+ "blur",
360
+ "blur-sm",
361
+ "blur-md",
362
+ "blur-lg",
363
+ "blur-xl",
364
+ "blur-2xl",
365
+ "blur-3xl",
366
+ "blur-none",
367
+ "grayscale",
368
+ "grayscale-0",
369
+ "invert",
370
+ "invert-0",
371
+ "sepia",
372
+ "sepia-0",
373
+ "drop-shadow",
374
+ "drop-shadow-sm",
375
+ "drop-shadow-md",
376
+ "drop-shadow-lg",
377
+ "drop-shadow-xl",
378
+ "drop-shadow-2xl",
379
+ "drop-shadow-none",
380
+ "filter-none",
381
+ "backdrop-filter-none",
382
+ "backdrop-blur",
383
+ "backdrop-blur-sm",
384
+ "backdrop-blur-md",
385
+ "backdrop-blur-lg",
386
+ "backdrop-blur-xl",
387
+ "backdrop-blur-2xl",
388
+ "backdrop-blur-3xl",
389
+ "backdrop-blur-none",
390
+ "backdrop-grayscale",
391
+ "backdrop-grayscale-0",
392
+ "backdrop-invert",
393
+ "backdrop-invert-0",
394
+ "backdrop-sepia",
395
+ "backdrop-sepia-0",
396
+ // border.rs
397
+ "border",
398
+ "border-t",
399
+ "border-r",
400
+ "border-b",
401
+ "border-l",
402
+ "rounded",
403
+ "rounded-none",
404
+ "rounded-sm",
405
+ "rounded-md",
406
+ "rounded-lg",
407
+ "rounded-xl",
408
+ "rounded-2xl",
409
+ "rounded-3xl",
410
+ "rounded-full",
411
+ "ring",
412
+ "ring-0",
413
+ "ring-1",
414
+ "ring-2",
415
+ "ring-4",
416
+ "ring-8",
417
+ "ring-inset",
418
+ "ring-offset-0",
419
+ "ring-offset-1",
420
+ "ring-offset-2",
421
+ "ring-offset-4",
422
+ "ring-offset-8",
423
+ "outline",
424
+ "outline-none",
425
+ "border-solid",
426
+ "border-dashed",
427
+ "border-dotted",
428
+ "border-none",
429
+ // typography.rs
430
+ "font-thin",
431
+ "font-normal",
432
+ "font-medium",
433
+ "font-semibold",
434
+ "font-bold",
435
+ "font-extrabold",
436
+ "uppercase",
437
+ "lowercase",
438
+ "capitalize",
439
+ "underline",
440
+ "line-through",
441
+ "no-underline",
442
+ "truncate",
443
+ "text-xs",
444
+ "text-sm",
445
+ "text-base",
446
+ "text-lg",
447
+ "text-xl",
448
+ "text-2xl",
449
+ "text-3xl",
450
+ "text-4xl",
451
+ "text-left",
452
+ "text-center",
453
+ "text-right",
454
+ "text-justify",
455
+ // typography.rs — Phase 20 (typography completeness)
456
+ "italic",
457
+ "not-italic",
458
+ "leading-none",
459
+ "leading-tight",
460
+ "leading-snug",
461
+ "leading-normal",
462
+ "leading-relaxed",
463
+ "leading-loose",
464
+ "tracking-tighter",
465
+ "tracking-tight",
466
+ "tracking-normal",
467
+ "tracking-wide",
468
+ "tracking-wider",
469
+ "tracking-widest",
470
+ "decoration-auto",
471
+ "decoration-from-font",
472
+ "decoration-solid",
473
+ "decoration-dashed",
474
+ "decoration-dotted",
475
+ "decoration-double",
476
+ "decoration-wavy",
477
+ "underline-offset-auto",
478
+ "underline-offset-0",
479
+ "underline-offset-1",
480
+ "underline-offset-2",
481
+ "underline-offset-4",
482
+ "underline-offset-8",
483
+ "text-wrap",
484
+ "text-nowrap",
485
+ "text-balance",
486
+ "text-pretty",
487
+ "whitespace-normal",
488
+ "whitespace-nowrap",
489
+ "whitespace-pre",
490
+ "whitespace-pre-wrap",
491
+ "whitespace-pre-line",
492
+ "break-normal",
493
+ "break-words",
494
+ "break-all",
495
+ "align-baseline",
496
+ "align-top",
497
+ "align-middle",
498
+ "align-bottom",
499
+ "align-text-top",
500
+ "align-text-bottom",
501
+ "align-sub",
502
+ "align-super",
503
+ "list-none",
504
+ "list-disc",
505
+ "list-decimal",
506
+ "list-inside",
507
+ "list-outside",
508
+ "hyphens-none",
509
+ "hyphens-manual",
510
+ "hyphens-auto",
511
+ // effects.rs
512
+ "shadow",
513
+ "shadow-sm",
514
+ "shadow-md",
515
+ "shadow-lg",
516
+ "shadow-xl",
517
+ "shadow-2xl",
518
+ "shadow-inner",
519
+ "shadow-none",
520
+ "transition",
521
+ "transition-none",
522
+ "ease-linear",
523
+ "ease-in",
524
+ "ease-out",
525
+ "ease-in-out",
526
+ "cursor-pointer",
527
+ "cursor-default",
528
+ "cursor-not-allowed",
529
+ "cursor-wait",
530
+ "select-none",
531
+ "select-text",
532
+ "select-all",
533
+ "pointer-events-none",
534
+ "pointer-events-auto",
535
+ // effects.rs — Phase 22 (effects completeness)
536
+ "text-shadow",
537
+ "text-shadow-sm",
538
+ "text-shadow-lg",
539
+ "text-shadow-none",
540
+ "mix-blend-normal",
541
+ "mix-blend-multiply",
542
+ "mix-blend-screen",
543
+ "mix-blend-overlay",
544
+ "mix-blend-darken",
545
+ "mix-blend-lighten",
546
+ "mix-blend-color-dodge",
547
+ "mix-blend-color-burn",
548
+ "mix-blend-hard-light",
549
+ "mix-blend-soft-light",
550
+ "mix-blend-difference",
551
+ "mix-blend-exclusion",
552
+ "mix-blend-hue",
553
+ "mix-blend-saturation",
554
+ "mix-blend-color",
555
+ "mix-blend-luminosity",
556
+ "mix-blend-plus-lighter",
557
+ "bg-blend-normal",
558
+ "bg-blend-multiply",
559
+ "bg-blend-screen",
560
+ "bg-blend-overlay",
561
+ "bg-blend-darken",
562
+ "bg-blend-lighten",
563
+ "bg-blend-color-dodge",
564
+ "bg-blend-color-burn",
565
+ "bg-blend-hard-light",
566
+ "bg-blend-soft-light",
567
+ "bg-blend-difference",
568
+ "bg-blend-exclusion",
569
+ "bg-blend-hue",
570
+ "bg-blend-saturation",
571
+ "bg-blend-color",
572
+ "bg-blend-luminosity",
573
+ "bg-blend-plus-lighter",
574
+ "animate-spin",
575
+ "animate-ping",
576
+ "animate-pulse",
577
+ "animate-bounce",
578
+ "animate-none",
579
+ // interactivity.rs — Phase 23 (interactivity & sizing rest)
580
+ "resize",
581
+ "resize-none",
582
+ "resize-y",
583
+ "resize-x",
584
+ "touch-auto",
585
+ "touch-none",
586
+ "touch-pan-x",
587
+ "touch-pan-left",
588
+ "touch-pan-right",
589
+ "touch-pan-y",
590
+ "touch-pan-up",
591
+ "touch-pan-down",
592
+ "touch-pinch-zoom",
593
+ "touch-manipulation",
594
+ "will-change-auto",
595
+ "will-change-scroll",
596
+ "will-change-contents",
597
+ "will-change-transform",
598
+ "sr-only",
599
+ "not-sr-only",
600
+ "border-collapse",
601
+ "border-separate",
602
+ "table-auto",
603
+ "table-fixed",
604
+ "caption-top",
605
+ "caption-bottom",
606
+ "stroke-width-0",
607
+ "stroke-width-1",
608
+ "stroke-width-2",
609
+ // spacing.rs — Phase 23 (size-* shorthand + fraction-based width/height)
610
+ "size-full",
611
+ "size-auto",
612
+ "size-px",
613
+ "w-1/2",
614
+ "w-1/3",
615
+ "w-2/3",
616
+ "w-1/4",
617
+ "w-2/4",
618
+ "w-3/4",
619
+ "w-1/5",
620
+ "w-2/5",
621
+ "w-3/5",
622
+ "w-4/5",
623
+ "w-1/6",
624
+ "w-5/6",
625
+ "w-1/12",
626
+ "w-5/12",
627
+ "w-7/12",
628
+ "w-11/12",
629
+ "h-1/2",
630
+ "h-1/3",
631
+ "h-2/3",
632
+ "h-1/4",
633
+ "h-2/4",
634
+ "h-3/4",
635
+ "h-1/5",
636
+ "h-2/5",
637
+ "h-3/5",
638
+ "h-4/5",
639
+ "h-1/6",
640
+ "h-5/6",
641
+ // scroll.rs — Phase 23 (scroll-behavior/-margin/-padding, scroll-snap)
642
+ "scroll-auto",
643
+ "scroll-smooth",
644
+ "snap-start",
645
+ "snap-end",
646
+ "snap-center",
647
+ "snap-align-none",
648
+ "snap-normal",
649
+ "snap-always",
650
+ "snap-none",
651
+ "snap-x",
652
+ "snap-y",
653
+ "snap-both",
654
+ "snap-mandatory",
655
+ "snap-proximity",
656
+ // divide.rs
657
+ "divide-x",
658
+ "divide-y",
659
+ // background.rs — web-only (see the module's own doc comment for why)
660
+ "bg-top",
661
+ "bg-bottom",
662
+ "bg-left",
663
+ "bg-right",
664
+ "bg-center",
665
+ "bg-left-top",
666
+ "bg-left-bottom",
667
+ "bg-right-top",
668
+ "bg-right-bottom",
669
+ "bg-auto",
670
+ "bg-cover",
671
+ "bg-contain",
672
+ "bg-repeat",
673
+ "bg-no-repeat",
674
+ "bg-repeat-x",
675
+ "bg-repeat-y",
676
+ "bg-repeat-round",
677
+ "bg-repeat-space",
678
+ "bg-fixed",
679
+ "bg-local",
680
+ "bg-scroll",
681
+ "bg-clip-border",
682
+ "bg-clip-padding",
683
+ "bg-clip-content",
684
+ "bg-clip-text",
685
+ "bg-origin-border",
686
+ "bg-origin-padding",
687
+ "bg-origin-content",
688
+ "bg-none",
689
+ "bg-linear-to-t",
690
+ "bg-linear-to-tr",
691
+ "bg-linear-to-r",
692
+ "bg-linear-to-br",
693
+ "bg-linear-to-b",
694
+ "bg-linear-to-bl",
695
+ "bg-linear-to-l",
696
+ "bg-linear-to-tl",
697
+ "bg-radial",
698
+ "bg-conic",
699
+ // Ancestor/sibling-state marker classes — carry no styles of their own,
700
+ // just a selector target for group-hover:/peer-hover:/etc. below (see
701
+ // unknownClassWarnings.ts's MARKER_CLASSES for why they need special
702
+ // handling there too).
703
+ "group",
704
+ "peer",
705
+ // registry.rs modifiers — bare tokens only, not composed with a
706
+ // following utility (see this module's doc header / the Phase 15 plan).
707
+ "hover:",
708
+ "focus:",
709
+ "focus-visible:",
710
+ "focus-within:",
711
+ "active:",
712
+ "visited:",
713
+ "disabled:",
714
+ "checked:",
715
+ "group-hover:",
716
+ "group-focus:",
717
+ "peer-hover:",
718
+ "peer-focus:",
719
+ "peer-checked:",
720
+ "dark:",
721
+ "motion-safe:",
722
+ "motion-reduce:",
723
+ "print:",
724
+ "sm:",
725
+ "md:",
726
+ "lg:",
727
+ "xl:",
728
+ "2xl:",
729
+ // registry.rs modifiers — Phase 24 (expanded variant system), the
730
+ // FIXED/static additions only. Deliberately excluded: every genuinely
731
+ // parameterized modifier (has-[...]/group-has-[...]/peer-has-[...]/
732
+ // data-[...]/aria-[...]/not-[...]/min-[...]/max-[...], and the
733
+ // generalized group-<pseudo>/peer-<pseudo> beyond the five hardcoded
734
+ // combos above) — these have no finite enumerable set (their whole point
735
+ // is arbitrary user-supplied content), so there's nothing meaningful to
736
+ // suggest beyond the bare prefix, same reasoning as every other
737
+ // arbitrary-value exclusion in this file. `not-hover:`/`not-disabled:`/
738
+ // etc. (wrapping a KNOWN static modifier) are excluded for the same
739
+ // reason — enumerable in principle, but a full cross-product with every
740
+ // modifier above would be more noise than signal for an editor hint list.
741
+ "first:",
742
+ "last:",
743
+ "only:",
744
+ "odd:",
745
+ "even:",
746
+ "open:",
747
+ "inert:",
748
+ "aria-checked:",
749
+ "aria-disabled:",
750
+ "aria-expanded:",
751
+ "aria-hidden:",
752
+ "aria-pressed:",
753
+ "aria-readonly:",
754
+ "aria-required:",
755
+ "aria-selected:",
756
+ "aria-busy:",
757
+ // registry.rs modifiers, added later — same "fixed/static, finite" bar
758
+ // as the Phase 24 block above. Structural -of-type/empty, form-state and
759
+ // validation pseudo-classes, other native pseudo-classes with no aria-*
760
+ // counterpart, a pseudo-element, pointer media variants, and the
761
+ // direct-children/all-descendants variants. "supports-[...]:"/"in-*:" are
762
+ // deliberately excluded here for the same "no finite enumerable set"
763
+ // reason as has-[...]:/group-<pseudo>: above.
764
+ "first-of-type:",
765
+ "last-of-type:",
766
+ "only-of-type:",
767
+ "empty:",
768
+ "enabled:",
769
+ "read-only:",
770
+ "required:",
771
+ "optional:",
772
+ "valid:",
773
+ "invalid:",
774
+ "in-range:",
775
+ "out-of-range:",
776
+ "default:",
777
+ "indeterminate:",
778
+ "placeholder-shown:",
779
+ "autofill:",
780
+ "user-valid:",
781
+ "user-invalid:",
782
+ "target:",
783
+ "fullscreen:",
784
+ "popover-open:",
785
+ "details-content:",
786
+ "pointer-coarse:",
787
+ "pointer-fine:",
788
+ "any-pointer-coarse:",
789
+ "any-pointer-fine:",
790
+ "*:",
791
+ "**:",
792
+ // Phase 25 (container queries & arbitrary properties). "@container" is a
793
+ // real UTILITY (container-type: inline-size), not a modifier — included
794
+ // as a bare token like "flex"/"hidden" above, not with the trailing ":"
795
+ // the modifier tokens below get. "@sm:"/"@md:"/"@lg:"/"@xl:"/"@2xl:" are
796
+ // container-responsive modifiers (registry.rs's `is_container_responsive`),
797
+ // and "starting:" wraps in @starting-style. Deliberately excluded, same
798
+ // "no finite enumerable set" reasoning as Phase 24's exclusions:
799
+ // "@min-[...]:"/"@max-[...]:" (arbitrary container breakpoints) and every
800
+ // `[property:value]` arbitrary property — both are open-ended by design.
801
+ "@container",
802
+ "@sm:",
803
+ "@md:",
804
+ "@lg:",
805
+ "@xl:",
806
+ "@2xl:",
807
+ "starting:"
808
+ ];
809
+ var COLOR_PREFIXES = [
810
+ "bg-",
811
+ "text-",
812
+ "border-",
813
+ "divide-",
814
+ "decoration-",
815
+ "from-",
816
+ "via-",
817
+ "to-",
818
+ "text-shadow-",
819
+ "ring-",
820
+ "ring-offset-",
821
+ "caret-",
822
+ "accent-",
823
+ "fill-",
824
+ "stroke-"
825
+ ];
826
+ var SPACING_PREFIXES = [
827
+ "p-",
828
+ "px-",
829
+ "py-",
830
+ "pt-",
831
+ "pr-",
832
+ "pb-",
833
+ "pl-",
834
+ "m-",
835
+ "mx-",
836
+ "my-",
837
+ "mt-",
838
+ "mr-",
839
+ "mb-",
840
+ "ml-",
841
+ "gap-",
842
+ "gap-x-",
843
+ "gap-y-",
844
+ "w-",
845
+ "h-",
846
+ "min-w-",
847
+ "max-w-",
848
+ "min-h-",
849
+ "max-h-",
850
+ "size-",
851
+ "top-",
852
+ "right-",
853
+ "bottom-",
854
+ "left-",
855
+ "inset-",
856
+ "space-x-",
857
+ "space-y-",
858
+ "indent-",
859
+ "scroll-m-",
860
+ "scroll-mx-",
861
+ "scroll-my-",
862
+ "scroll-mt-",
863
+ "scroll-mr-",
864
+ "scroll-mb-",
865
+ "scroll-ml-",
866
+ "scroll-p-",
867
+ "scroll-px-",
868
+ "scroll-py-",
869
+ "scroll-pt-",
870
+ "scroll-pr-",
871
+ "scroll-pb-",
872
+ "scroll-pl-"
873
+ ];
874
+ var SIZE_PREFIXES = ["w-", "h-", "min-w-", "max-w-", "min-h-", "max-h-", "size-"];
875
+ var NAMED_SIZES = ["sm", "md", "lg", "xl", "2xl", "3xl", "4xl", "5xl", "6xl", "7xl", "8xl", "9xl"];
876
+ function buildClassTokens(theme) {
877
+ const tokens = new Set(STRUCTURAL_TOKENS);
878
+ for (const colorKey of Object.keys(theme.colors)) {
879
+ for (const prefix of COLOR_PREFIXES) tokens.add(`${prefix}${colorKey}`);
880
+ }
881
+ for (const spacingKey of Object.keys(theme.spacing)) {
882
+ for (const prefix of SPACING_PREFIXES) tokens.add(`${prefix}${spacingKey}`);
883
+ }
884
+ for (const size of NAMED_SIZES) {
885
+ for (const prefix of SIZE_PREFIXES) tokens.add(`${prefix}${size}`);
886
+ }
887
+ return Array.from(tokens).sort();
888
+ }
889
+ function buildClassNameHintsDts(tokens) {
890
+ const literals = tokens.map((t) => ` | '${t}'`).join("\n");
891
+ return [
892
+ "// Generated by Kbach \u2014 do not edit",
893
+ "// Editor autocomplete for className \u2014 see packages/react/src/vite-plugin/classNameHints.ts",
894
+ "",
895
+ "type KbachClassToken =",
896
+ literals,
897
+ ";",
898
+ "",
899
+ "export type KbachClassName = KbachClassToken | `${string} ${KbachClassToken}` | (string & {});",
900
+ "",
901
+ "declare module 'react' {",
902
+ " interface HTMLAttributes<T> {",
903
+ " className?: KbachClassName | undefined;",
904
+ " }",
905
+ " interface SVGAttributes<T> {",
906
+ " className?: KbachClassName | undefined;",
907
+ " }",
908
+ "}",
909
+ ""
910
+ ].join("\n");
911
+ }
912
+
913
+ // src/staticCss/engine.ts
914
+ import { readFileSync as readFileSync2 } from "fs";
915
+ import { join as join3 } from "path";
916
+
917
+ // src/vite-plugin/wasmNode.ts
918
+ import { generate_css } from "@kbach/core-engine/node";
919
+ function generateCssForToken(token, themeJson) {
920
+ return JSON.parse(generate_css(token, themeJson));
921
+ }
922
+
923
+ // src/vite-plugin/scan.ts
924
+ import { readdirSync, readFileSync, statSync } from "fs";
925
+ import { join } from "path";
926
+ function stripComments(code) {
927
+ let out = "";
928
+ let i = 0;
929
+ const n = code.length;
930
+ while (i < n) {
931
+ const two = code[i] + (code[i + 1] ?? "");
932
+ if (two === "//") {
933
+ i += 2;
934
+ while (i < n && code[i] !== "\n") i++;
935
+ continue;
936
+ }
937
+ if (two === "/*") {
938
+ i += 2;
939
+ while (i < n && code[i] + (code[i + 1] ?? "") !== "*/") i++;
940
+ i += 2;
941
+ continue;
942
+ }
943
+ const ch = code[i];
944
+ if (ch === '"' || ch === "'") {
945
+ out += ch;
946
+ i++;
947
+ while (i < n && code[i] !== ch) {
948
+ if (code[i] === "\\") {
949
+ out += code[i] + (code[i + 1] ?? "");
950
+ i += 2;
951
+ continue;
952
+ }
953
+ out += code[i];
954
+ i++;
955
+ }
956
+ out += code[i] ?? "";
957
+ i++;
958
+ continue;
959
+ }
960
+ if (ch === "`") {
961
+ out += ch;
962
+ i++;
963
+ let interpolationDepth = 0;
964
+ while (i < n) {
965
+ if (interpolationDepth === 0 && code[i] === "`") {
966
+ out += code[i];
967
+ i++;
968
+ break;
969
+ }
970
+ if (interpolationDepth === 0 && code[i] === "\\") {
971
+ out += code[i] + (code[i + 1] ?? "");
972
+ i += 2;
973
+ continue;
974
+ }
975
+ if (interpolationDepth === 0 && code[i] + (code[i + 1] ?? "") === "${") {
976
+ out += "${";
977
+ i += 2;
978
+ interpolationDepth = 1;
979
+ continue;
980
+ }
981
+ if (interpolationDepth > 0) {
982
+ if (code[i] === "{") interpolationDepth++;
983
+ else if (code[i] === "}") {
984
+ interpolationDepth--;
985
+ if (interpolationDepth === 0) {
986
+ out += "}";
987
+ i++;
988
+ continue;
989
+ }
990
+ }
991
+ }
992
+ out += code[i];
993
+ i++;
994
+ }
995
+ continue;
996
+ }
997
+ out += ch;
998
+ i++;
999
+ }
1000
+ return out;
1001
+ }
1002
+ function splitClassTokens(str) {
1003
+ return str.split(/\s+/).filter(Boolean);
1004
+ }
1005
+ var NOT_A_CLASS_NAME_RE = /[${}]|-$|^[?:&|!"'=<>]+$/;
1006
+ function pushTokens(str, into) {
1007
+ for (const tok of splitClassTokens(str)) {
1008
+ if (tok && !NOT_A_CLASS_NAME_RE.test(tok)) into.add(tok);
1009
+ }
1010
+ }
1011
+ function pushTemplateLiteralBody(body, into) {
1012
+ const interpolationRe = /\$\{(?:[^{}]|\{[^{}]*\})*\}/g;
1013
+ let im;
1014
+ while ((im = interpolationRe.exec(body)) !== null) {
1015
+ pushClassLikeStrings(im[0], into);
1016
+ }
1017
+ pushTokens(body.replace(/\$\{(?:[^{}]|\{[^{}]*\})*\}/g, () => "$"), into);
1018
+ }
1019
+ function pushClassLikeStrings(text, into) {
1020
+ const quotedStringRe = /(["'`])((?:(?!\1).)*)\1/g;
1021
+ let qm;
1022
+ while ((qm = quotedStringRe.exec(text)) !== null) {
1023
+ const [, quote, content] = qm;
1024
+ if (quote === "`") pushTemplateLiteralBody(content, into);
1025
+ else pushTokens(content, into);
1026
+ }
1027
+ }
1028
+ var JSX_TAG_RE = /<([a-z][a-zA-Z0-9]*)[\s/>]/g;
1029
+ function scanUsedTags(code) {
1030
+ const cleaned = stripComments(code);
1031
+ const tags = /* @__PURE__ */ new Set();
1032
+ let m;
1033
+ JSX_TAG_RE.lastIndex = 0;
1034
+ while ((m = JSX_TAG_RE.exec(cleaned)) !== null) tags.add(m[1]);
1035
+ return tags;
1036
+ }
1037
+ function extractClassStrings(rawCode) {
1038
+ const code = stripComments(rawCode);
1039
+ const found = /* @__PURE__ */ new Set();
1040
+ const simpleRe = /(?:className|kb)=(["'])((?:(?!\1).)*)\1/g;
1041
+ let m;
1042
+ while ((m = simpleRe.exec(code)) !== null) pushTokens(m[2], found);
1043
+ const jsxExprRe = /(?:className|kb)=\{/g;
1044
+ while ((m = jsxExprRe.exec(code)) !== null) {
1045
+ let depth = 1;
1046
+ let i = m.index + m[0].length;
1047
+ let block = "";
1048
+ while (i < code.length && depth > 0) {
1049
+ const ch = code[i];
1050
+ if (ch === "{") depth++;
1051
+ else if (ch === "}") {
1052
+ if (--depth === 0) break;
1053
+ }
1054
+ block += ch;
1055
+ i++;
1056
+ }
1057
+ jsxExprRe.lastIndex = i + 1;
1058
+ pushClassLikeStrings(block, found);
1059
+ }
1060
+ const classComposerCallRe = /(?:clsx|cn|classnames|cx|kb)\(/g;
1061
+ while ((m = classComposerCallRe.exec(code)) !== null) {
1062
+ let depth = 1;
1063
+ let i = m.index + m[0].length;
1064
+ let block = "";
1065
+ while (i < code.length && depth > 0) {
1066
+ const ch = code[i];
1067
+ if (ch === "(") depth++;
1068
+ else if (ch === ")") {
1069
+ if (--depth === 0) break;
1070
+ }
1071
+ block += ch;
1072
+ i++;
1073
+ }
1074
+ classComposerCallRe.lastIndex = i + 1;
1075
+ pushClassLikeStrings(block, found);
1076
+ }
1077
+ const templateRe = /`([^`]{1,2000})`/g;
1078
+ while ((m = templateRe.exec(code)) !== null) pushTemplateLiteralBody(m[1], found);
1079
+ return [...found];
1080
+ }
1081
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", ".next", ".output", ".git", ".svn", ".cache", "coverage"]);
1082
+ var MAX_SCAN_DEPTH = 10;
1083
+ var DEFAULT_FILE_MATCH = /\.(tsx?|jsx?)$/;
1084
+ function scanDir(dir, onFile, depth = 0, fileMatch = DEFAULT_FILE_MATCH) {
1085
+ if (depth > MAX_SCAN_DEPTH) return;
1086
+ let entries;
1087
+ try {
1088
+ entries = readdirSync(dir);
1089
+ } catch {
1090
+ return;
1091
+ }
1092
+ for (const entry of entries) {
1093
+ if (entry.startsWith(".") || SKIP_DIRS.has(entry)) continue;
1094
+ const full = join(dir, entry);
1095
+ try {
1096
+ const st = statSync(full);
1097
+ if (st.isDirectory()) scanDir(full, onFile, depth + 1, fileMatch);
1098
+ else if (fileMatch.test(entry)) onFile(full, readFileSync(full, "utf-8"));
1099
+ } catch {
1100
+ }
1101
+ }
1102
+ }
1103
+
1104
+ // src/vite-plugin/unknownClassWarnings.ts
1105
+ import { join as join2 } from "path";
1106
+ var CSS_CLASS_SELECTOR_RE = /\.(-?[a-zA-Z_][a-zA-Z0-9_-]*)/g;
1107
+ var CSS_FILE_RE = /\.(css|scss|sass|less)$/;
1108
+ var useColor = !!process.stdout?.isTTY && !process.env.NO_COLOR;
1109
+ var paint = (code, s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
1110
+ var green = (s) => paint("32", s);
1111
+ var blue = (s) => paint("34", s);
1112
+ var white = (s) => paint("97", s);
1113
+ var gray = (s) => paint("90", s);
1114
+ var bold = (s) => paint("1", s);
1115
+ var ARROW = () => green("\u2192");
1116
+ var TAG = () => bold(green("[kbach]"));
1117
+ var highlight = (s) => bold(white(s));
1118
+ function findLineCol(code, needle) {
1119
+ const index = code.indexOf(needle);
1120
+ if (index === -1) return null;
1121
+ let line = 1;
1122
+ let lastNewline = -1;
1123
+ for (let i = 0; i < index; i++) {
1124
+ if (code.charCodeAt(i) === 10) {
1125
+ line++;
1126
+ lastNewline = i;
1127
+ }
1128
+ }
1129
+ return { line, column: index - lastNewline };
1130
+ }
1131
+ function scanCssFileInto(code, into) {
1132
+ const re = new RegExp(CSS_CLASS_SELECTOR_RE.source, "g");
1133
+ let m;
1134
+ while ((m = re.exec(code)) !== null) into.add(m[1]);
1135
+ }
1136
+ function scanProjectCssSelectors(root, includeDirs) {
1137
+ const projectCssClasses = /* @__PURE__ */ new Set();
1138
+ for (const dir of includeDirs) {
1139
+ scanDir(join2(root, dir), (_filePath, code) => scanCssFileInto(code, projectCssClasses), 0, CSS_FILE_RE);
1140
+ }
1141
+ return projectCssClasses;
1142
+ }
1143
+ function stripModifierPrefix(token) {
1144
+ return token.replace(/^(?:[a-zA-Z0-9_/-]+:)+/, "");
1145
+ }
1146
+ var MARKER_CLASSES = /* @__PURE__ */ new Set(["group", "peer"]);
1147
+ function warnIfUnknownClass(token, filePath, code, isResolvable, projectCssClasses, warnedTokens) {
1148
+ if (isResolvable || warnedTokens.has(token)) return;
1149
+ const base = stripModifierPrefix(token);
1150
+ if (!base || projectCssClasses.has(base) || MARKER_CLASSES.has(base)) return;
1151
+ warnedTokens.add(token);
1152
+ const pos = findLineCol(code, token);
1153
+ const location = pos ? `${blue(filePath)}${green(`:${pos.line}:${pos.column}`)}` : blue(filePath);
1154
+ console.warn(
1155
+ `${ARROW()} ${TAG()} ${gray("Unknown class")} ${highlight(token)} ${gray("\u2014 no Kbach utility or project CSS rule matches it. Typo?")}
1156
+ ${bold("at")} ${location}`
1157
+ );
1158
+ }
1159
+
1160
+ // src/staticCss/engine.ts
1161
+ function normPath(p) {
1162
+ const fwd = p.replace(/\\/g, "/");
1163
+ return process.platform === "win32" ? fwd.toLowerCase() : fwd;
1164
+ }
1165
+ var SAFELIST_KEY = "\0kbach-safelist";
1166
+ function createKbachStaticCssEngine(options) {
1167
+ const { root, theme, themeJson, includeDirs, safelist } = options;
1168
+ const fileTokens = /* @__PURE__ */ new Map();
1169
+ const tokenRules = /* @__PURE__ */ new Map();
1170
+ const fileTags = /* @__PURE__ */ new Map();
1171
+ let projectCssClasses = /* @__PURE__ */ new Set();
1172
+ const warnedTokens = /* @__PURE__ */ new Set();
1173
+ function resolveToken(tok) {
1174
+ if (!tokenRules.has(tok)) {
1175
+ tokenRules.set(tok, generateCssForToken(tok, themeJson).rules);
1176
+ }
1177
+ return tokenRules.get(tok);
1178
+ }
1179
+ function processFile(filePath, code) {
1180
+ const tokens = /* @__PURE__ */ new Set();
1181
+ for (const tok of extractClassStrings(code)) {
1182
+ tokens.add(tok);
1183
+ const rules = resolveToken(tok);
1184
+ if (process.env.NODE_ENV !== "production") {
1185
+ warnIfUnknownClass(tok, filePath, code, rules.length > 0, projectCssClasses, warnedTokens);
1186
+ }
1187
+ }
1188
+ fileTokens.set(normPath(filePath), tokens);
1189
+ fileTags.set(normPath(filePath), scanUsedTags(code));
1190
+ }
1191
+ function removeFile(filePath) {
1192
+ fileTokens.delete(normPath(filePath));
1193
+ fileTags.delete(normPath(filePath));
1194
+ }
1195
+ function processSafelist() {
1196
+ if (safelist.length === 0) return;
1197
+ const tokens = new Set(safelist);
1198
+ for (const tok of tokens) resolveToken(tok);
1199
+ fileTokens.set(SAFELIST_KEY, tokens);
1200
+ }
1201
+ function activeTokens() {
1202
+ const active = /* @__PURE__ */ new Set();
1203
+ for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
1204
+ return active;
1205
+ }
1206
+ function activeTags() {
1207
+ const active = /* @__PURE__ */ new Set();
1208
+ for (const tags of fileTags.values()) for (const t of tags) active.add(t);
1209
+ return active;
1210
+ }
1211
+ function generateCSS() {
1212
+ const active = activeTokens();
1213
+ const view = /* @__PURE__ */ new Map();
1214
+ for (const tok of active) {
1215
+ const rules = tokenRules.get(tok);
1216
+ if (rules) view.set(tok, rules);
1217
+ }
1218
+ for (const tok of tokenRules.keys()) {
1219
+ if (!active.has(tok)) tokenRules.delete(tok);
1220
+ }
1221
+ return formatKbachCSS(view, theme, activeTags());
1222
+ }
1223
+ function initialScan() {
1224
+ for (const dir of includeDirs) {
1225
+ scanDir(join3(root, dir), processFile);
1226
+ }
1227
+ }
1228
+ function scanProjectCssSelectorsOnce() {
1229
+ projectCssClasses = scanProjectCssSelectors(root, includeDirs);
1230
+ }
1231
+ return { processFile, removeFile, initialScan, processSafelist, generateCSS, scanProjectCssSelectorsOnce };
1232
+ }
1233
+ function readSourceFile(path) {
1234
+ return readFileSync2(path, "utf-8");
1235
+ }
1236
+
1237
+ // src/vite-plugin/index.ts
1238
+ var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components"];
1239
+ var CSS_SYNC_DEBOUNCE_MS = 50;
1240
+ function writeFileEnsuringDir(path, content) {
1241
+ mkdirSync(dirname(path), { recursive: true });
1242
+ writeFileSync(path, content, "utf-8");
1243
+ }
1244
+ function loadConfigFile(root) {
1245
+ const path = join4(root, "kbach.config.js");
1246
+ if (!existsSync(path)) return void 0;
1247
+ const mod = createRequire(root)(path);
1248
+ return mod.default ?? mod;
1249
+ }
1250
+ function resolveEffectiveTheme(root, options) {
1251
+ if (options.theme) return options.theme;
1252
+ if (options.config) return resolveKbachConfig(options.config);
1253
+ const discovered = loadConfigFile(root);
1254
+ return discovered ? resolveKbachConfig(discovered) : defaultTheme;
1255
+ }
1256
+ function kbach(options = {}) {
1257
+ const includeDirs = options.include ?? DEFAULT_SCAN_DIRS;
1258
+ const safelist = options.safelist ?? [];
1259
+ let root = process.cwd();
1260
+ let theme = defaultTheme;
1261
+ let engine = createKbachStaticCssEngine({ root, theme, themeJson: JSON.stringify(theme), includeDirs, safelist });
1262
+ function mainCSSFile() {
1263
+ return join4(root, "src", "kbach.css");
1264
+ }
1265
+ function classNameHintsFile() {
1266
+ return join4(root, ".kbach", "kbach-classnames.d.ts");
1267
+ }
1268
+ function writeClassNameHints() {
1269
+ const dts = buildClassNameHintsDts(buildClassTokens(theme));
1270
+ writeFileEnsuringDir(classNameHintsFile(), dts);
1271
+ }
1272
+ function syncMainCSSFile(server) {
1273
+ const changed = writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile, (p, c) => writeFileSync(p, c, "utf-8"));
1274
+ if (changed && server) server.watcher.emit("change", mainCSSFile());
1275
+ }
1276
+ let syncTimer = null;
1277
+ function scheduleSync(server) {
1278
+ if (syncTimer) clearTimeout(syncTimer);
1279
+ syncTimer = setTimeout(() => {
1280
+ syncTimer = null;
1281
+ syncMainCSSFile(server);
1282
+ }, CSS_SYNC_DEBOUNCE_MS);
1283
+ }
1284
+ return {
1285
+ name: "kbach",
1286
+ enforce: "pre",
1287
+ configResolved(resolved) {
1288
+ root = resolved.root;
1289
+ theme = resolveEffectiveTheme(root, options);
1290
+ engine = createKbachStaticCssEngine({ root, theme, themeJson: JSON.stringify(theme), includeDirs, safelist });
1291
+ },
1292
+ buildStart() {
1293
+ engine.scanProjectCssSelectorsOnce();
1294
+ engine.initialScan();
1295
+ engine.processSafelist();
1296
+ writeKbachToFile(mainCSSFile(), engine.generateCSS(), readSourceFile, (p, c) => writeFileSync(p, c, "utf-8"));
1297
+ writeClassNameHints();
1298
+ },
1299
+ // handleHotUpdate covers edits; add/unlink (create/delete) land on the
1300
+ // raw watcher instead — Vite's handleHotUpdate hook only fires for
1301
+ // type === "update".
1302
+ configureServer(server) {
1303
+ const onAddOrUnlink = (file, isUnlink) => {
1304
+ if (file.includes("node_modules")) return;
1305
+ if (!/\.(tsx?|jsx?)$/.test(file)) return;
1306
+ if (isUnlink) {
1307
+ engine.removeFile(file);
1308
+ } else {
1309
+ try {
1310
+ engine.processFile(file, readSourceFile(file));
1311
+ } catch {
1312
+ engine.removeFile(file);
1313
+ }
1314
+ }
1315
+ scheduleSync(server);
1316
+ };
1317
+ server.watcher.on("add", (file) => onAddOrUnlink(file, false));
1318
+ server.watcher.on("unlink", (file) => onAddOrUnlink(file, true));
1319
+ },
1320
+ handleHotUpdate({ file, server }) {
1321
+ if (file.includes("node_modules")) return;
1322
+ if (!/\.(tsx?|jsx?)$/.test(file)) return;
1323
+ try {
1324
+ engine.processFile(file, readSourceFile(file));
1325
+ } catch {
1326
+ engine.removeFile(file);
1327
+ }
1328
+ scheduleSync(server);
1329
+ }
1330
+ };
1331
+ }
1332
+
1333
+ export {
1334
+ createKbachStaticCssEngine,
1335
+ DEFAULT_SCAN_DIRS,
1336
+ CSS_SYNC_DEBOUNCE_MS,
1337
+ loadConfigFile,
1338
+ resolveEffectiveTheme,
1339
+ kbach
1340
+ };