@kbach/react 1.0.0-beta.5 → 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.
- package/README.md +103 -7
- package/dist/chunk-GPLCUNFX.mjs +1340 -0
- package/dist/postcss-plugin.d.mts +36 -0
- package/dist/postcss-plugin.d.ts +36 -0
- package/dist/postcss-plugin.js +1119 -0
- package/dist/postcss-plugin.mjs +79 -0
- package/dist/vite-plugin.d.mts +36 -1
- package/dist/vite-plugin.d.ts +36 -1
- package/dist/vite-plugin.js +326 -309
- package/dist/vite-plugin.mjs +12 -1320
- package/package.json +12 -1
|
@@ -0,0 +1,1119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/postcss-plugin/index.ts
|
|
31
|
+
var postcss_plugin_exports = {};
|
|
32
|
+
__export(postcss_plugin_exports, {
|
|
33
|
+
default: () => kbachPostcss
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(postcss_plugin_exports);
|
|
36
|
+
var import_postcss = __toESM(require("postcss"));
|
|
37
|
+
var import_path5 = require("path");
|
|
38
|
+
|
|
39
|
+
// src/staticCss/engine.ts
|
|
40
|
+
var import_path3 = require("path");
|
|
41
|
+
|
|
42
|
+
// src/vite-plugin/wasmNode.ts
|
|
43
|
+
var import_node = require("@kbach/core-engine/node");
|
|
44
|
+
function generateCssForToken(token, themeJson) {
|
|
45
|
+
return JSON.parse((0, import_node.generate_css)(token, themeJson));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/vite-plugin/scan.ts
|
|
49
|
+
var import_fs = require("fs");
|
|
50
|
+
var import_path = require("path");
|
|
51
|
+
function stripComments(code) {
|
|
52
|
+
let out = "";
|
|
53
|
+
let i = 0;
|
|
54
|
+
const n = code.length;
|
|
55
|
+
while (i < n) {
|
|
56
|
+
const two = code[i] + (code[i + 1] ?? "");
|
|
57
|
+
if (two === "//") {
|
|
58
|
+
i += 2;
|
|
59
|
+
while (i < n && code[i] !== "\n") i++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (two === "/*") {
|
|
63
|
+
i += 2;
|
|
64
|
+
while (i < n && code[i] + (code[i + 1] ?? "") !== "*/") i++;
|
|
65
|
+
i += 2;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const ch = code[i];
|
|
69
|
+
if (ch === '"' || ch === "'") {
|
|
70
|
+
out += ch;
|
|
71
|
+
i++;
|
|
72
|
+
while (i < n && code[i] !== ch) {
|
|
73
|
+
if (code[i] === "\\") {
|
|
74
|
+
out += code[i] + (code[i + 1] ?? "");
|
|
75
|
+
i += 2;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
out += code[i];
|
|
79
|
+
i++;
|
|
80
|
+
}
|
|
81
|
+
out += code[i] ?? "";
|
|
82
|
+
i++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (ch === "`") {
|
|
86
|
+
out += ch;
|
|
87
|
+
i++;
|
|
88
|
+
let interpolationDepth = 0;
|
|
89
|
+
while (i < n) {
|
|
90
|
+
if (interpolationDepth === 0 && code[i] === "`") {
|
|
91
|
+
out += code[i];
|
|
92
|
+
i++;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
if (interpolationDepth === 0 && code[i] === "\\") {
|
|
96
|
+
out += code[i] + (code[i + 1] ?? "");
|
|
97
|
+
i += 2;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (interpolationDepth === 0 && code[i] + (code[i + 1] ?? "") === "${") {
|
|
101
|
+
out += "${";
|
|
102
|
+
i += 2;
|
|
103
|
+
interpolationDepth = 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (interpolationDepth > 0) {
|
|
107
|
+
if (code[i] === "{") interpolationDepth++;
|
|
108
|
+
else if (code[i] === "}") {
|
|
109
|
+
interpolationDepth--;
|
|
110
|
+
if (interpolationDepth === 0) {
|
|
111
|
+
out += "}";
|
|
112
|
+
i++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
out += code[i];
|
|
118
|
+
i++;
|
|
119
|
+
}
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
out += ch;
|
|
123
|
+
i++;
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
function splitClassTokens(str) {
|
|
128
|
+
return str.split(/\s+/).filter(Boolean);
|
|
129
|
+
}
|
|
130
|
+
var NOT_A_CLASS_NAME_RE = /[${}]|-$|^[?:&|!"'=<>]+$/;
|
|
131
|
+
function pushTokens(str, into) {
|
|
132
|
+
for (const tok of splitClassTokens(str)) {
|
|
133
|
+
if (tok && !NOT_A_CLASS_NAME_RE.test(tok)) into.add(tok);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function pushTemplateLiteralBody(body, into) {
|
|
137
|
+
const interpolationRe = /\$\{(?:[^{}]|\{[^{}]*\})*\}/g;
|
|
138
|
+
let im;
|
|
139
|
+
while ((im = interpolationRe.exec(body)) !== null) {
|
|
140
|
+
pushClassLikeStrings(im[0], into);
|
|
141
|
+
}
|
|
142
|
+
pushTokens(body.replace(/\$\{(?:[^{}]|\{[^{}]*\})*\}/g, () => "$"), into);
|
|
143
|
+
}
|
|
144
|
+
function pushClassLikeStrings(text, into) {
|
|
145
|
+
const quotedStringRe = /(["'`])((?:(?!\1).)*)\1/g;
|
|
146
|
+
let qm;
|
|
147
|
+
while ((qm = quotedStringRe.exec(text)) !== null) {
|
|
148
|
+
const [, quote, content] = qm;
|
|
149
|
+
if (quote === "`") pushTemplateLiteralBody(content, into);
|
|
150
|
+
else pushTokens(content, into);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
var JSX_TAG_RE = /<([a-z][a-zA-Z0-9]*)[\s/>]/g;
|
|
154
|
+
function scanUsedTags(code) {
|
|
155
|
+
const cleaned = stripComments(code);
|
|
156
|
+
const tags = /* @__PURE__ */ new Set();
|
|
157
|
+
let m;
|
|
158
|
+
JSX_TAG_RE.lastIndex = 0;
|
|
159
|
+
while ((m = JSX_TAG_RE.exec(cleaned)) !== null) tags.add(m[1]);
|
|
160
|
+
return tags;
|
|
161
|
+
}
|
|
162
|
+
function extractClassStrings(rawCode) {
|
|
163
|
+
const code = stripComments(rawCode);
|
|
164
|
+
const found = /* @__PURE__ */ new Set();
|
|
165
|
+
const simpleRe = /(?:className|kb)=(["'])((?:(?!\1).)*)\1/g;
|
|
166
|
+
let m;
|
|
167
|
+
while ((m = simpleRe.exec(code)) !== null) pushTokens(m[2], found);
|
|
168
|
+
const jsxExprRe = /(?:className|kb)=\{/g;
|
|
169
|
+
while ((m = jsxExprRe.exec(code)) !== null) {
|
|
170
|
+
let depth = 1;
|
|
171
|
+
let i = m.index + m[0].length;
|
|
172
|
+
let block = "";
|
|
173
|
+
while (i < code.length && depth > 0) {
|
|
174
|
+
const ch = code[i];
|
|
175
|
+
if (ch === "{") depth++;
|
|
176
|
+
else if (ch === "}") {
|
|
177
|
+
if (--depth === 0) break;
|
|
178
|
+
}
|
|
179
|
+
block += ch;
|
|
180
|
+
i++;
|
|
181
|
+
}
|
|
182
|
+
jsxExprRe.lastIndex = i + 1;
|
|
183
|
+
pushClassLikeStrings(block, found);
|
|
184
|
+
}
|
|
185
|
+
const classComposerCallRe = /(?:clsx|cn|classnames|cx|kb)\(/g;
|
|
186
|
+
while ((m = classComposerCallRe.exec(code)) !== null) {
|
|
187
|
+
let depth = 1;
|
|
188
|
+
let i = m.index + m[0].length;
|
|
189
|
+
let block = "";
|
|
190
|
+
while (i < code.length && depth > 0) {
|
|
191
|
+
const ch = code[i];
|
|
192
|
+
if (ch === "(") depth++;
|
|
193
|
+
else if (ch === ")") {
|
|
194
|
+
if (--depth === 0) break;
|
|
195
|
+
}
|
|
196
|
+
block += ch;
|
|
197
|
+
i++;
|
|
198
|
+
}
|
|
199
|
+
classComposerCallRe.lastIndex = i + 1;
|
|
200
|
+
pushClassLikeStrings(block, found);
|
|
201
|
+
}
|
|
202
|
+
const templateRe = /`([^`]{1,2000})`/g;
|
|
203
|
+
while ((m = templateRe.exec(code)) !== null) pushTemplateLiteralBody(m[1], found);
|
|
204
|
+
return [...found];
|
|
205
|
+
}
|
|
206
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", ".next", ".output", ".git", ".svn", ".cache", "coverage"]);
|
|
207
|
+
var MAX_SCAN_DEPTH = 10;
|
|
208
|
+
var DEFAULT_FILE_MATCH = /\.(tsx?|jsx?)$/;
|
|
209
|
+
function scanDir(dir, onFile, depth = 0, fileMatch = DEFAULT_FILE_MATCH) {
|
|
210
|
+
if (depth > MAX_SCAN_DEPTH) return;
|
|
211
|
+
let entries;
|
|
212
|
+
try {
|
|
213
|
+
entries = (0, import_fs.readdirSync)(dir);
|
|
214
|
+
} catch {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
for (const entry of entries) {
|
|
218
|
+
if (entry.startsWith(".") || SKIP_DIRS.has(entry)) continue;
|
|
219
|
+
const full = (0, import_path.join)(dir, entry);
|
|
220
|
+
try {
|
|
221
|
+
const st = (0, import_fs.statSync)(full);
|
|
222
|
+
if (st.isDirectory()) scanDir(full, onFile, depth + 1, fileMatch);
|
|
223
|
+
else if (fileMatch.test(entry)) onFile(full, (0, import_fs.readFileSync)(full, "utf-8"));
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/vite-plugin/cssVars.ts
|
|
230
|
+
function hexToRgbTriplet(hex) {
|
|
231
|
+
const h = hex.replace("#", "");
|
|
232
|
+
const expand = (s) => s.length === 3 ? s.split("").map((c) => c + c).join("") : s;
|
|
233
|
+
const full = expand(h);
|
|
234
|
+
if (full.length !== 6) return null;
|
|
235
|
+
const r = parseInt(full.slice(0, 2), 16);
|
|
236
|
+
const g = parseInt(full.slice(2, 4), 16);
|
|
237
|
+
const b = parseInt(full.slice(4, 6), 16);
|
|
238
|
+
if ([r, g, b].some((n) => Number.isNaN(n))) return null;
|
|
239
|
+
return `${r},${g},${b}`;
|
|
240
|
+
}
|
|
241
|
+
function buildColorVarMap(theme, cssText) {
|
|
242
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
243
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
244
|
+
for (const [name, value] of Object.entries(theme.colors)) {
|
|
245
|
+
if (typeof value !== "string") continue;
|
|
246
|
+
const varName = `--color-${name}`;
|
|
247
|
+
if (cssText.includes(value)) {
|
|
248
|
+
replacements.set(value, `var(${varName})`);
|
|
249
|
+
declarations.set(varName, value);
|
|
250
|
+
}
|
|
251
|
+
const triplet = hexToRgbTriplet(value);
|
|
252
|
+
if (!triplet) continue;
|
|
253
|
+
const rgbaPrefix = `rgba(${triplet},`;
|
|
254
|
+
if (cssText.includes(rgbaPrefix)) {
|
|
255
|
+
const rgbVarName = `--color-${name}-rgb`;
|
|
256
|
+
replacements.set(rgbaPrefix, `rgba(var(${rgbVarName}),`);
|
|
257
|
+
declarations.set(rgbVarName, triplet);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return { replacements, declarations };
|
|
261
|
+
}
|
|
262
|
+
var SPACING_PROPS = /* @__PURE__ */ new Set([
|
|
263
|
+
"padding",
|
|
264
|
+
"padding-top",
|
|
265
|
+
"padding-right",
|
|
266
|
+
"padding-bottom",
|
|
267
|
+
"padding-left",
|
|
268
|
+
"margin",
|
|
269
|
+
"margin-top",
|
|
270
|
+
"margin-right",
|
|
271
|
+
"margin-bottom",
|
|
272
|
+
"margin-left",
|
|
273
|
+
"gap",
|
|
274
|
+
"column-gap",
|
|
275
|
+
"row-gap",
|
|
276
|
+
"width",
|
|
277
|
+
"height",
|
|
278
|
+
"min-width",
|
|
279
|
+
"min-height",
|
|
280
|
+
"max-width",
|
|
281
|
+
"max-height",
|
|
282
|
+
"top",
|
|
283
|
+
"right",
|
|
284
|
+
"bottom",
|
|
285
|
+
"left"
|
|
286
|
+
]);
|
|
287
|
+
function buildSpacingVarMap(theme, cssText) {
|
|
288
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
289
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
290
|
+
const pxToKey = /* @__PURE__ */ new Map();
|
|
291
|
+
for (const [key, val] of Object.entries(theme.spacing)) {
|
|
292
|
+
if (!pxToKey.has(val)) pxToKey.set(val, key);
|
|
293
|
+
}
|
|
294
|
+
const varNameFor = (key) => `--spacing-${key.replace(/\./g, "_")}`;
|
|
295
|
+
const declRe = /([a-z-]+): (\d+(?:\.\d+)?)px/g;
|
|
296
|
+
let m;
|
|
297
|
+
while ((m = declRe.exec(cssText)) !== null) {
|
|
298
|
+
const [, prop, numStr] = m;
|
|
299
|
+
if (!SPACING_PROPS.has(prop)) continue;
|
|
300
|
+
const key = pxToKey.get(parseFloat(numStr));
|
|
301
|
+
if (key === void 0) continue;
|
|
302
|
+
const varName = varNameFor(key);
|
|
303
|
+
replacements.set(`${prop}: ${numStr}px`, `${prop}: var(${varName})`);
|
|
304
|
+
declarations.set(varName, `${numStr}px`);
|
|
305
|
+
}
|
|
306
|
+
return { replacements, declarations };
|
|
307
|
+
}
|
|
308
|
+
function applyVarMap(cssText, ...maps) {
|
|
309
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
310
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
311
|
+
for (const map of maps) {
|
|
312
|
+
for (const [k, v] of map.replacements) replacements.set(k, v);
|
|
313
|
+
for (const [k, v] of map.declarations) declarations.set(k, v);
|
|
314
|
+
}
|
|
315
|
+
const sorted = [...replacements].sort(([a], [b]) => b.length - a.length);
|
|
316
|
+
const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
317
|
+
const patterns = sorted.map(([pattern, replacement]) => [new RegExp(escapeRegex(pattern), "g"), replacement]);
|
|
318
|
+
const text = cssText.replace(/\{([^{}]*)\}/g, (_whole, inner) => {
|
|
319
|
+
let replaced = inner;
|
|
320
|
+
for (const [re, replacement] of patterns) {
|
|
321
|
+
replaced = replaced.replace(re, () => replacement);
|
|
322
|
+
}
|
|
323
|
+
return `{${replaced}}`;
|
|
324
|
+
});
|
|
325
|
+
return { text, declarations };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/vite-plugin/mediaMerge.ts
|
|
329
|
+
var RESPONSIVE_MEDIA_RE = /^@media \(min-width: (\d+)px\) \{ (.+) \}$/;
|
|
330
|
+
function mergeResponsiveMediaBlocks(rules) {
|
|
331
|
+
const byWidth = /* @__PURE__ */ new Map();
|
|
332
|
+
const widthOrder = [];
|
|
333
|
+
const unwrapped = [];
|
|
334
|
+
for (const rule of rules) {
|
|
335
|
+
const m = RESPONSIVE_MEDIA_RE.exec(rule);
|
|
336
|
+
if (!m) {
|
|
337
|
+
unwrapped.push(rule);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const width = Number(m[1]);
|
|
341
|
+
if (!byWidth.has(width)) {
|
|
342
|
+
byWidth.set(width, []);
|
|
343
|
+
widthOrder.push(width);
|
|
344
|
+
}
|
|
345
|
+
byWidth.get(width).push(m[2]);
|
|
346
|
+
}
|
|
347
|
+
widthOrder.sort((a, b) => a - b);
|
|
348
|
+
const merged = widthOrder.map((w) => `@media (min-width: ${w}px) { ${byWidth.get(w).join(" ")} }`);
|
|
349
|
+
return [...unwrapped, ...merged];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// src/vite-plugin/prettify.ts
|
|
353
|
+
function prettifyCSS(css) {
|
|
354
|
+
const out = [];
|
|
355
|
+
let depth = 0;
|
|
356
|
+
let current = "";
|
|
357
|
+
for (const ch of css) {
|
|
358
|
+
if (ch === "{") {
|
|
359
|
+
const selector = current.trim();
|
|
360
|
+
if (selector) out.push(`${" ".repeat(depth)}${selector} {`);
|
|
361
|
+
depth++;
|
|
362
|
+
current = "";
|
|
363
|
+
} else if (ch === "}") {
|
|
364
|
+
const decl = current.trim();
|
|
365
|
+
if (decl) out.push(`${" ".repeat(depth)}${decl};`);
|
|
366
|
+
depth = Math.max(0, depth - 1);
|
|
367
|
+
out.push(`${" ".repeat(depth)}}`);
|
|
368
|
+
current = "";
|
|
369
|
+
} else if (ch === ";") {
|
|
370
|
+
const decl = current.trim();
|
|
371
|
+
if (decl) out.push(`${" ".repeat(depth)}${decl};`);
|
|
372
|
+
current = "";
|
|
373
|
+
} else {
|
|
374
|
+
current += ch;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const trailing = current.trim();
|
|
378
|
+
if (trailing) out.push(trailing);
|
|
379
|
+
return out.join("\n");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/reset.ts
|
|
383
|
+
var RESET_RULES = [
|
|
384
|
+
// border-style: solid means border-N utilities show a visible border without an extra border-solid class.
|
|
385
|
+
// border-width: 0 keeps all elements borderless by default.
|
|
386
|
+
{ css: "*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }" },
|
|
387
|
+
{ css: "body { margin: 0; padding: 0; }" },
|
|
388
|
+
{ css: "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }", tags: ["h1", "h2", "h3", "h4", "h5", "h6"] },
|
|
389
|
+
{ css: "p { margin: 0; }", tags: ["p"] },
|
|
390
|
+
{ css: "a { color: inherit; text-decoration: none; }", tags: ["a"] },
|
|
391
|
+
{ css: "ul, ol { margin: 0; padding: 0; list-style: none; }", tags: ["ul", "ol"] },
|
|
392
|
+
// svg here only covers a literal <svg> tag in JSX — an icon imported as
|
|
393
|
+
// a component (e.g. `<ArrowIcon />`) never appears as the text "svg" in
|
|
394
|
+
// source, so this rule won't be pulled in by that usage alone. Same
|
|
395
|
+
// caveat applies to every tag-gated rule below: detection is a literal
|
|
396
|
+
// source-text scan (see vite-plugin/scan.ts's scanUsedTags), not a real
|
|
397
|
+
// DOM/type check.
|
|
398
|
+
{ css: "img, video, svg { display: block; max-width: 100%; }", tags: ["img", "video", "svg"] },
|
|
399
|
+
// appearance: none is deliberately NOT applied to checkbox/radio/select below —
|
|
400
|
+
// stripping it hides their native checkmark/arrow with nothing rendered in its
|
|
401
|
+
// place, leaving an invisible checkbox or an arrow-less <select> that looks like
|
|
402
|
+
// plain text. Text-like inputs, textarea, and button don't have that problem
|
|
403
|
+
// (their native chrome is just a skin around content utilities can fully
|
|
404
|
+
// restyle), so they keep the blank-canvas treatment.
|
|
405
|
+
// :where() wraps the :not() exclusions so they contribute ZERO specificity
|
|
406
|
+
// (unlike a bare `input:not([type='checkbox']):not([type='radio'])`, whose
|
|
407
|
+
// two :not([attr]) clauses each add a class-level specificity point — (0,2,1)
|
|
408
|
+
// total, MORE than any single utility class (0,1,0)). Without :where(), this
|
|
409
|
+
// reset's `color: inherit` always won the cascade over a text-* color
|
|
410
|
+
// utility applied directly to a <input>/<textarea> regardless of source
|
|
411
|
+
// order, since author rules only override on a tie or higher specificity —
|
|
412
|
+
// confirmed on a real app: a TextInput's own text color utility resolved
|
|
413
|
+
// and injected correctly, class and all, but silently never painted.
|
|
414
|
+
{
|
|
415
|
+
css: "input:where(:not([type='checkbox']):not([type='radio'])), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
|
|
416
|
+
tags: ["input", "textarea"]
|
|
417
|
+
},
|
|
418
|
+
// Native checkbox/radio/range still get typography + spacing normalized, and
|
|
419
|
+
// accent-color re-themes their native indicator to the current text color
|
|
420
|
+
// instead of the browser/OS default blue, so they stay on-brand without
|
|
421
|
+
// needing to be rebuilt from scratch. Wrapped in :where() for the exact
|
|
422
|
+
// same reason as the input:not(...) exclusions above — a bare
|
|
423
|
+
// `input[type='range']` attribute selector has specificity (0,1,1),
|
|
424
|
+
// MORE than any single utility class (0,1,0), so an `accent-*` utility
|
|
425
|
+
// (Phase 23) applied directly to a checkbox/radio/range would resolve and
|
|
426
|
+
// inject correctly but silently never paint, losing the cascade to this
|
|
427
|
+
// reset regardless of source order — confirmed by hand via
|
|
428
|
+
// getComputedStyle on a real `accent-red-6` range input, which reported
|
|
429
|
+
// `currentColor`'s inherited value instead. `:where()` zeroes this
|
|
430
|
+
// selector's specificity contribution entirely, so any `accent-*`
|
|
431
|
+
// utility trivially wins once applied.
|
|
432
|
+
{
|
|
433
|
+
css: ":where(input[type='checkbox'], input[type='radio'], input[type='range']) { margin: 0; font: inherit; accent-color: currentColor; }",
|
|
434
|
+
tags: ["input"]
|
|
435
|
+
},
|
|
436
|
+
// select keeps its native chrome (border/background/arrow are all part of the
|
|
437
|
+
// same OS-drawn widget that appearance: none would blank out) — only typography
|
|
438
|
+
// and spacing are normalized so it still matches surrounding text.
|
|
439
|
+
{ css: "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }", tags: ["select"] },
|
|
440
|
+
{ css: "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }", tags: ["button"] },
|
|
441
|
+
// Also covers any element opted into the button role (`role="button"` on
|
|
442
|
+
// a <div>/<span>) — but only when a literal <button> tag is ALSO
|
|
443
|
+
// somewhere in the scanned source, since detection here is purely
|
|
444
|
+
// tag-name-based; a project using role="button" exclusively, with no
|
|
445
|
+
// real <button> anywhere, won't pull this rule in. Acceptable tradeoff
|
|
446
|
+
// for this project's scope (rare pattern) — cheap enough that scanning
|
|
447
|
+
// for it separately isn't worth the added complexity.
|
|
448
|
+
{ css: "button, [role='button'] { cursor: pointer; }", tags: ["button"] },
|
|
449
|
+
{ css: ":disabled { cursor: default; }", tags: ["button", "input", "select", "textarea"] },
|
|
450
|
+
{ css: "textarea { resize: vertical; }", tags: ["textarea"] },
|
|
451
|
+
// Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
|
|
452
|
+
// normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
|
|
453
|
+
{ css: "::placeholder { opacity: 1; }", tags: ["input", "textarea"] },
|
|
454
|
+
{ css: "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }", tags: ["input"] },
|
|
455
|
+
{ css: "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }", tags: ["input"] },
|
|
456
|
+
{ css: "fieldset { padding: 0; margin: 0; }", tags: ["fieldset"] },
|
|
457
|
+
{ css: "table { border-collapse: collapse; border-spacing: 0; }", tags: ["table"] }
|
|
458
|
+
];
|
|
459
|
+
var BASE_RESET = RESET_RULES.map((r) => r.css).join("\n");
|
|
460
|
+
function buildResetCSS(usedTags) {
|
|
461
|
+
if (!usedTags) return BASE_RESET;
|
|
462
|
+
return RESET_RULES.filter((r) => !r.tags || r.tags.some((t) => usedTags.has(t))).map((r) => r.css).join("\n");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// src/vite-plugin/format.ts
|
|
466
|
+
function formatKbachCSS(tokenRules, theme, usedTags) {
|
|
467
|
+
const allEntries = [];
|
|
468
|
+
for (const rules of tokenRules.values()) {
|
|
469
|
+
for (const entry of rules) allEntries.push(entry);
|
|
470
|
+
}
|
|
471
|
+
allEntries.sort((a, b) => a.order - b.order);
|
|
472
|
+
const allRules = allEntries.map((e) => e.rule);
|
|
473
|
+
const merged = mergeResponsiveMediaBlocks(allRules);
|
|
474
|
+
const compact = merged.join("\n");
|
|
475
|
+
const colorMap = buildColorVarMap(theme, compact);
|
|
476
|
+
const spacingMap = buildSpacingVarMap(theme, compact);
|
|
477
|
+
const { text, declarations } = applyVarMap(compact, colorMap, spacingMap);
|
|
478
|
+
const lines = ["/* Generated by Kbach \u2014 do not edit */", "", buildResetCSS(usedTags)];
|
|
479
|
+
if (declarations.size > 0) {
|
|
480
|
+
lines.push("", ":root {");
|
|
481
|
+
for (const [varName, val] of declarations) lines.push(` ${varName}: ${val};`);
|
|
482
|
+
lines.push("}");
|
|
483
|
+
}
|
|
484
|
+
lines.push("", prettifyCSS(text));
|
|
485
|
+
return lines.join("\n");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// src/vite-plugin/unknownClassWarnings.ts
|
|
489
|
+
var import_path2 = require("path");
|
|
490
|
+
var CSS_CLASS_SELECTOR_RE = /\.(-?[a-zA-Z_][a-zA-Z0-9_-]*)/g;
|
|
491
|
+
var CSS_FILE_RE = /\.(css|scss|sass|less)$/;
|
|
492
|
+
var useColor = !!process.stdout?.isTTY && !process.env.NO_COLOR;
|
|
493
|
+
var paint = (code, s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
|
|
494
|
+
var green = (s) => paint("32", s);
|
|
495
|
+
var blue = (s) => paint("34", s);
|
|
496
|
+
var white = (s) => paint("97", s);
|
|
497
|
+
var gray = (s) => paint("90", s);
|
|
498
|
+
var bold = (s) => paint("1", s);
|
|
499
|
+
var ARROW = () => green("\u2192");
|
|
500
|
+
var TAG = () => bold(green("[kbach]"));
|
|
501
|
+
var highlight = (s) => bold(white(s));
|
|
502
|
+
function findLineCol(code, needle) {
|
|
503
|
+
const index = code.indexOf(needle);
|
|
504
|
+
if (index === -1) return null;
|
|
505
|
+
let line = 1;
|
|
506
|
+
let lastNewline = -1;
|
|
507
|
+
for (let i = 0; i < index; i++) {
|
|
508
|
+
if (code.charCodeAt(i) === 10) {
|
|
509
|
+
line++;
|
|
510
|
+
lastNewline = i;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return { line, column: index - lastNewline };
|
|
514
|
+
}
|
|
515
|
+
function scanCssFileInto(code, into) {
|
|
516
|
+
const re = new RegExp(CSS_CLASS_SELECTOR_RE.source, "g");
|
|
517
|
+
let m;
|
|
518
|
+
while ((m = re.exec(code)) !== null) into.add(m[1]);
|
|
519
|
+
}
|
|
520
|
+
function scanProjectCssSelectors(root, includeDirs) {
|
|
521
|
+
const projectCssClasses = /* @__PURE__ */ new Set();
|
|
522
|
+
for (const dir of includeDirs) {
|
|
523
|
+
scanDir((0, import_path2.join)(root, dir), (_filePath, code) => scanCssFileInto(code, projectCssClasses), 0, CSS_FILE_RE);
|
|
524
|
+
}
|
|
525
|
+
return projectCssClasses;
|
|
526
|
+
}
|
|
527
|
+
function stripModifierPrefix(token) {
|
|
528
|
+
return token.replace(/^(?:[a-zA-Z0-9_/-]+:)+/, "");
|
|
529
|
+
}
|
|
530
|
+
var MARKER_CLASSES = /* @__PURE__ */ new Set(["group", "peer"]);
|
|
531
|
+
function warnIfUnknownClass(token, filePath, code, isResolvable, projectCssClasses, warnedTokens) {
|
|
532
|
+
if (isResolvable || warnedTokens.has(token)) return;
|
|
533
|
+
const base = stripModifierPrefix(token);
|
|
534
|
+
if (!base || projectCssClasses.has(base) || MARKER_CLASSES.has(base)) return;
|
|
535
|
+
warnedTokens.add(token);
|
|
536
|
+
const pos = findLineCol(code, token);
|
|
537
|
+
const location = pos ? `${blue(filePath)}${green(`:${pos.line}:${pos.column}`)}` : blue(filePath);
|
|
538
|
+
console.warn(
|
|
539
|
+
`${ARROW()} ${TAG()} ${gray("Unknown class")} ${highlight(token)} ${gray("\u2014 no Kbach utility or project CSS rule matches it. Typo?")}
|
|
540
|
+
${bold("at")} ${location}`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// src/staticCss/engine.ts
|
|
545
|
+
function normPath(p) {
|
|
546
|
+
const fwd = p.replace(/\\/g, "/");
|
|
547
|
+
return process.platform === "win32" ? fwd.toLowerCase() : fwd;
|
|
548
|
+
}
|
|
549
|
+
var SAFELIST_KEY = "\0kbach-safelist";
|
|
550
|
+
function createKbachStaticCssEngine(options) {
|
|
551
|
+
const { root, theme, themeJson, includeDirs, safelist } = options;
|
|
552
|
+
const fileTokens = /* @__PURE__ */ new Map();
|
|
553
|
+
const tokenRules = /* @__PURE__ */ new Map();
|
|
554
|
+
const fileTags = /* @__PURE__ */ new Map();
|
|
555
|
+
let projectCssClasses = /* @__PURE__ */ new Set();
|
|
556
|
+
const warnedTokens = /* @__PURE__ */ new Set();
|
|
557
|
+
function resolveToken(tok) {
|
|
558
|
+
if (!tokenRules.has(tok)) {
|
|
559
|
+
tokenRules.set(tok, generateCssForToken(tok, themeJson).rules);
|
|
560
|
+
}
|
|
561
|
+
return tokenRules.get(tok);
|
|
562
|
+
}
|
|
563
|
+
function processFile(filePath, code) {
|
|
564
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
565
|
+
for (const tok of extractClassStrings(code)) {
|
|
566
|
+
tokens.add(tok);
|
|
567
|
+
const rules = resolveToken(tok);
|
|
568
|
+
if (process.env.NODE_ENV !== "production") {
|
|
569
|
+
warnIfUnknownClass(tok, filePath, code, rules.length > 0, projectCssClasses, warnedTokens);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
fileTokens.set(normPath(filePath), tokens);
|
|
573
|
+
fileTags.set(normPath(filePath), scanUsedTags(code));
|
|
574
|
+
}
|
|
575
|
+
function removeFile(filePath) {
|
|
576
|
+
fileTokens.delete(normPath(filePath));
|
|
577
|
+
fileTags.delete(normPath(filePath));
|
|
578
|
+
}
|
|
579
|
+
function processSafelist() {
|
|
580
|
+
if (safelist.length === 0) return;
|
|
581
|
+
const tokens = new Set(safelist);
|
|
582
|
+
for (const tok of tokens) resolveToken(tok);
|
|
583
|
+
fileTokens.set(SAFELIST_KEY, tokens);
|
|
584
|
+
}
|
|
585
|
+
function activeTokens() {
|
|
586
|
+
const active = /* @__PURE__ */ new Set();
|
|
587
|
+
for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
|
|
588
|
+
return active;
|
|
589
|
+
}
|
|
590
|
+
function activeTags() {
|
|
591
|
+
const active = /* @__PURE__ */ new Set();
|
|
592
|
+
for (const tags of fileTags.values()) for (const t of tags) active.add(t);
|
|
593
|
+
return active;
|
|
594
|
+
}
|
|
595
|
+
function generateCSS() {
|
|
596
|
+
const active = activeTokens();
|
|
597
|
+
const view = /* @__PURE__ */ new Map();
|
|
598
|
+
for (const tok of active) {
|
|
599
|
+
const rules = tokenRules.get(tok);
|
|
600
|
+
if (rules) view.set(tok, rules);
|
|
601
|
+
}
|
|
602
|
+
for (const tok of tokenRules.keys()) {
|
|
603
|
+
if (!active.has(tok)) tokenRules.delete(tok);
|
|
604
|
+
}
|
|
605
|
+
return formatKbachCSS(view, theme, activeTags());
|
|
606
|
+
}
|
|
607
|
+
function initialScan() {
|
|
608
|
+
for (const dir of includeDirs) {
|
|
609
|
+
scanDir((0, import_path3.join)(root, dir), processFile);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function scanProjectCssSelectorsOnce() {
|
|
613
|
+
projectCssClasses = scanProjectCssSelectors(root, includeDirs);
|
|
614
|
+
}
|
|
615
|
+
return { processFile, removeFile, initialScan, processSafelist, generateCSS, scanProjectCssSelectorsOnce };
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// src/vite-plugin/index.ts
|
|
619
|
+
var import_fs2 = require("fs");
|
|
620
|
+
var import_path4 = require("path");
|
|
621
|
+
var import_module = require("module");
|
|
622
|
+
|
|
623
|
+
// src/generatedPalette.ts
|
|
624
|
+
var PALETTE = {
|
|
625
|
+
"amber-1": "#fffbeb",
|
|
626
|
+
"amber-10": "#78350f",
|
|
627
|
+
"amber-11": "#451a03",
|
|
628
|
+
"amber-12": "#291002",
|
|
629
|
+
"amber-2": "#fef3c7",
|
|
630
|
+
"amber-3": "#fde68a",
|
|
631
|
+
"amber-4": "#fcd34d",
|
|
632
|
+
"amber-5": "#fbbf24",
|
|
633
|
+
"amber-6": "#f59e0b",
|
|
634
|
+
"amber-7": "#d97706",
|
|
635
|
+
"amber-8": "#b45309",
|
|
636
|
+
"amber-9": "#92400e",
|
|
637
|
+
"black": "#000000",
|
|
638
|
+
"blue-1": "#eff6ff",
|
|
639
|
+
"blue-10": "#1e3a8a",
|
|
640
|
+
"blue-11": "#172554",
|
|
641
|
+
"blue-12": "#0d1633",
|
|
642
|
+
"blue-2": "#dbeafe",
|
|
643
|
+
"blue-3": "#bfdbfe",
|
|
644
|
+
"blue-4": "#93c5fd",
|
|
645
|
+
"blue-5": "#60a5fa",
|
|
646
|
+
"blue-6": "#3b82f6",
|
|
647
|
+
"blue-7": "#2563eb",
|
|
648
|
+
"blue-8": "#1d4ed8",
|
|
649
|
+
"blue-9": "#1e40af",
|
|
650
|
+
"bronze": "#cd7f32",
|
|
651
|
+
"coal": "#1c1c1c",
|
|
652
|
+
"copper": "#b87333",
|
|
653
|
+
"current": "currentColor",
|
|
654
|
+
"cyan-1": "#ecfeff",
|
|
655
|
+
"cyan-10": "#164e63",
|
|
656
|
+
"cyan-11": "#083344",
|
|
657
|
+
"cyan-12": "#041e28",
|
|
658
|
+
"cyan-2": "#cffafe",
|
|
659
|
+
"cyan-3": "#a5f3fc",
|
|
660
|
+
"cyan-4": "#67e8f9",
|
|
661
|
+
"cyan-5": "#22d3ee",
|
|
662
|
+
"cyan-6": "#06b6d4",
|
|
663
|
+
"cyan-7": "#0891b2",
|
|
664
|
+
"cyan-8": "#0e7490",
|
|
665
|
+
"cyan-9": "#155e75",
|
|
666
|
+
"emerald-1": "#ecfdf5",
|
|
667
|
+
"emerald-10": "#064e3b",
|
|
668
|
+
"emerald-11": "#022c22",
|
|
669
|
+
"emerald-12": "#011a14",
|
|
670
|
+
"emerald-2": "#d1fae5",
|
|
671
|
+
"emerald-3": "#a7f3d0",
|
|
672
|
+
"emerald-4": "#6ee7b7",
|
|
673
|
+
"emerald-5": "#34d399",
|
|
674
|
+
"emerald-6": "#10b981",
|
|
675
|
+
"emerald-7": "#059669",
|
|
676
|
+
"emerald-8": "#047857",
|
|
677
|
+
"emerald-9": "#065f46",
|
|
678
|
+
"fuchsia-1": "#fdf4ff",
|
|
679
|
+
"fuchsia-10": "#701a75",
|
|
680
|
+
"fuchsia-11": "#4a044e",
|
|
681
|
+
"fuchsia-12": "#2d022f",
|
|
682
|
+
"fuchsia-2": "#fae8ff",
|
|
683
|
+
"fuchsia-3": "#f5d0fe",
|
|
684
|
+
"fuchsia-4": "#f0abfc",
|
|
685
|
+
"fuchsia-5": "#e879f9",
|
|
686
|
+
"fuchsia-6": "#d946ef",
|
|
687
|
+
"fuchsia-7": "#c026d3",
|
|
688
|
+
"fuchsia-8": "#a21caf",
|
|
689
|
+
"fuchsia-9": "#86198f",
|
|
690
|
+
"gold": "#d4af37",
|
|
691
|
+
"gray-1": "#f9fafb",
|
|
692
|
+
"gray-10": "#111827",
|
|
693
|
+
"gray-11": "#030712",
|
|
694
|
+
"gray-12": "#020409",
|
|
695
|
+
"gray-2": "#f3f4f6",
|
|
696
|
+
"gray-3": "#e5e7eb",
|
|
697
|
+
"gray-4": "#d1d5db",
|
|
698
|
+
"gray-5": "#9ca3af",
|
|
699
|
+
"gray-6": "#6b7280",
|
|
700
|
+
"gray-7": "#4b5563",
|
|
701
|
+
"gray-8": "#374151",
|
|
702
|
+
"gray-9": "#1f2937",
|
|
703
|
+
"green-1": "#f0fdf4",
|
|
704
|
+
"green-10": "#14532d",
|
|
705
|
+
"green-11": "#052e16",
|
|
706
|
+
"green-12": "#031b0d",
|
|
707
|
+
"green-2": "#dcfce7",
|
|
708
|
+
"green-3": "#bbf7d0",
|
|
709
|
+
"green-4": "#86efac",
|
|
710
|
+
"green-5": "#4ade80",
|
|
711
|
+
"green-6": "#22c55e",
|
|
712
|
+
"green-7": "#16a34a",
|
|
713
|
+
"green-8": "#15803d",
|
|
714
|
+
"green-9": "#166534",
|
|
715
|
+
"indigo-1": "#eef2ff",
|
|
716
|
+
"indigo-10": "#312e81",
|
|
717
|
+
"indigo-11": "#1e1b4b",
|
|
718
|
+
"indigo-12": "#12102d",
|
|
719
|
+
"indigo-2": "#e0e7ff",
|
|
720
|
+
"indigo-3": "#c7d2fe",
|
|
721
|
+
"indigo-4": "#a5b4fc",
|
|
722
|
+
"indigo-5": "#818cf8",
|
|
723
|
+
"indigo-6": "#6366f1",
|
|
724
|
+
"indigo-7": "#4f46e5",
|
|
725
|
+
"indigo-8": "#4338ca",
|
|
726
|
+
"indigo-9": "#3730a3",
|
|
727
|
+
"lime-1": "#f7fee7",
|
|
728
|
+
"lime-10": "#365314",
|
|
729
|
+
"lime-11": "#1a2e05",
|
|
730
|
+
"lime-12": "#0f1b03",
|
|
731
|
+
"lime-2": "#ecfccb",
|
|
732
|
+
"lime-3": "#d9f99d",
|
|
733
|
+
"lime-4": "#bef264",
|
|
734
|
+
"lime-5": "#a3e635",
|
|
735
|
+
"lime-6": "#84cc16",
|
|
736
|
+
"lime-7": "#65a30d",
|
|
737
|
+
"lime-8": "#4d7c0f",
|
|
738
|
+
"lime-9": "#3f6212",
|
|
739
|
+
"neutral-1": "#fafafa",
|
|
740
|
+
"neutral-10": "#171717",
|
|
741
|
+
"neutral-11": "#0a0a0a",
|
|
742
|
+
"neutral-12": "#050505",
|
|
743
|
+
"neutral-2": "#f5f5f5",
|
|
744
|
+
"neutral-3": "#e5e5e5",
|
|
745
|
+
"neutral-4": "#d4d4d4",
|
|
746
|
+
"neutral-5": "#a3a3a3",
|
|
747
|
+
"neutral-6": "#737373",
|
|
748
|
+
"neutral-7": "#525252",
|
|
749
|
+
"neutral-8": "#404040",
|
|
750
|
+
"neutral-9": "#262626",
|
|
751
|
+
"orange-1": "#fff7ed",
|
|
752
|
+
"orange-10": "#7c2d12",
|
|
753
|
+
"orange-11": "#431407",
|
|
754
|
+
"orange-12": "#270c04",
|
|
755
|
+
"orange-2": "#ffedd5",
|
|
756
|
+
"orange-3": "#fed7aa",
|
|
757
|
+
"orange-4": "#fdba74",
|
|
758
|
+
"orange-5": "#fb923c",
|
|
759
|
+
"orange-6": "#f97316",
|
|
760
|
+
"orange-7": "#ea580c",
|
|
761
|
+
"orange-8": "#c2410c",
|
|
762
|
+
"orange-9": "#9a3412",
|
|
763
|
+
"pink-1": "#fdf2f8",
|
|
764
|
+
"pink-10": "#831843",
|
|
765
|
+
"pink-11": "#500724",
|
|
766
|
+
"pink-12": "#300415",
|
|
767
|
+
"pink-2": "#fce7f3",
|
|
768
|
+
"pink-3": "#fbcfe8",
|
|
769
|
+
"pink-4": "#f9a8d4",
|
|
770
|
+
"pink-5": "#f472b6",
|
|
771
|
+
"pink-6": "#ec4899",
|
|
772
|
+
"pink-7": "#db2777",
|
|
773
|
+
"pink-8": "#be185d",
|
|
774
|
+
"pink-9": "#9d174d",
|
|
775
|
+
"purple-1": "#faf5ff",
|
|
776
|
+
"purple-10": "#581c87",
|
|
777
|
+
"purple-11": "#3b0764",
|
|
778
|
+
"purple-12": "#23043c",
|
|
779
|
+
"purple-2": "#f3e8ff",
|
|
780
|
+
"purple-3": "#e9d5ff",
|
|
781
|
+
"purple-4": "#d8b4fe",
|
|
782
|
+
"purple-5": "#c084fc",
|
|
783
|
+
"purple-6": "#a855f7",
|
|
784
|
+
"purple-7": "#9333ea",
|
|
785
|
+
"purple-8": "#7e22ce",
|
|
786
|
+
"purple-9": "#6b21a8",
|
|
787
|
+
"red-1": "#fef2f2",
|
|
788
|
+
"red-10": "#7f1d1d",
|
|
789
|
+
"red-11": "#450a0a",
|
|
790
|
+
"red-12": "#280606",
|
|
791
|
+
"red-2": "#fee2e2",
|
|
792
|
+
"red-3": "#fecaca",
|
|
793
|
+
"red-4": "#fca5a5",
|
|
794
|
+
"red-5": "#f87171",
|
|
795
|
+
"red-6": "#ef4444",
|
|
796
|
+
"red-7": "#dc2626",
|
|
797
|
+
"red-8": "#b91c1c",
|
|
798
|
+
"red-9": "#991b1b",
|
|
799
|
+
"rose-1": "#fff1f2",
|
|
800
|
+
"rose-10": "#881337",
|
|
801
|
+
"rose-11": "#4c0519",
|
|
802
|
+
"rose-12": "#2d030e",
|
|
803
|
+
"rose-2": "#ffe4e6",
|
|
804
|
+
"rose-3": "#fecdd3",
|
|
805
|
+
"rose-4": "#fda4af",
|
|
806
|
+
"rose-5": "#fb7185",
|
|
807
|
+
"rose-6": "#f43f5e",
|
|
808
|
+
"rose-7": "#e11d48",
|
|
809
|
+
"rose-8": "#be123c",
|
|
810
|
+
"rose-9": "#9f1239",
|
|
811
|
+
"silver": "#c0c0c0",
|
|
812
|
+
"sky-1": "#f0f9ff",
|
|
813
|
+
"sky-10": "#0c4a6e",
|
|
814
|
+
"sky-11": "#082f49",
|
|
815
|
+
"sky-12": "#041b2b",
|
|
816
|
+
"sky-2": "#e0f2fe",
|
|
817
|
+
"sky-3": "#bae6fd",
|
|
818
|
+
"sky-4": "#7dd3fc",
|
|
819
|
+
"sky-5": "#38bdf8",
|
|
820
|
+
"sky-6": "#0ea5e9",
|
|
821
|
+
"sky-7": "#0284c7",
|
|
822
|
+
"sky-8": "#0369a1",
|
|
823
|
+
"sky-9": "#075985",
|
|
824
|
+
"slate-1": "#f8fafc",
|
|
825
|
+
"slate-10": "#0f172a",
|
|
826
|
+
"slate-11": "#020617",
|
|
827
|
+
"slate-12": "#01020a",
|
|
828
|
+
"slate-2": "#f1f5f9",
|
|
829
|
+
"slate-3": "#e2e8f0",
|
|
830
|
+
"slate-4": "#cbd5e1",
|
|
831
|
+
"slate-5": "#94a3b8",
|
|
832
|
+
"slate-6": "#64748b",
|
|
833
|
+
"slate-7": "#475569",
|
|
834
|
+
"slate-8": "#334155",
|
|
835
|
+
"slate-9": "#1e293b",
|
|
836
|
+
"stone-1": "#fafaf9",
|
|
837
|
+
"stone-10": "#1c1917",
|
|
838
|
+
"stone-11": "#0c0a09",
|
|
839
|
+
"stone-12": "#070605",
|
|
840
|
+
"stone-2": "#f5f5f4",
|
|
841
|
+
"stone-3": "#e7e5e4",
|
|
842
|
+
"stone-4": "#d6d3d1",
|
|
843
|
+
"stone-5": "#a8a29e",
|
|
844
|
+
"stone-6": "#78716c",
|
|
845
|
+
"stone-7": "#57534e",
|
|
846
|
+
"stone-8": "#44403c",
|
|
847
|
+
"stone-9": "#292524",
|
|
848
|
+
"teal-1": "#f0fdfa",
|
|
849
|
+
"teal-10": "#134e4a",
|
|
850
|
+
"teal-11": "#042f2e",
|
|
851
|
+
"teal-12": "#021c1b",
|
|
852
|
+
"teal-2": "#ccfbf1",
|
|
853
|
+
"teal-3": "#99f6e4",
|
|
854
|
+
"teal-4": "#5eead4",
|
|
855
|
+
"teal-5": "#2dd4bf",
|
|
856
|
+
"teal-6": "#14b8a6",
|
|
857
|
+
"teal-7": "#0d9488",
|
|
858
|
+
"teal-8": "#0f766e",
|
|
859
|
+
"teal-9": "#115e59",
|
|
860
|
+
"transparent": "transparent",
|
|
861
|
+
"violet-1": "#f5f3ff",
|
|
862
|
+
"violet-10": "#4c1d95",
|
|
863
|
+
"violet-11": "#2e1065",
|
|
864
|
+
"violet-12": "#1c0a3d",
|
|
865
|
+
"violet-2": "#ede9fe",
|
|
866
|
+
"violet-3": "#ddd6fe",
|
|
867
|
+
"violet-4": "#c4b5fd",
|
|
868
|
+
"violet-5": "#a78bfa",
|
|
869
|
+
"violet-6": "#8b5cf6",
|
|
870
|
+
"violet-7": "#7c3aed",
|
|
871
|
+
"violet-8": "#6d28d9",
|
|
872
|
+
"violet-9": "#5b21b6",
|
|
873
|
+
"white": "#ffffff",
|
|
874
|
+
"yellow-1": "#fefce8",
|
|
875
|
+
"yellow-10": "#713f12",
|
|
876
|
+
"yellow-11": "#422006",
|
|
877
|
+
"yellow-12": "#271304",
|
|
878
|
+
"yellow-2": "#fef9c3",
|
|
879
|
+
"yellow-3": "#fef08a",
|
|
880
|
+
"yellow-4": "#fde047",
|
|
881
|
+
"yellow-5": "#facc15",
|
|
882
|
+
"yellow-6": "#eab308",
|
|
883
|
+
"yellow-7": "#ca8a04",
|
|
884
|
+
"yellow-8": "#a16207",
|
|
885
|
+
"yellow-9": "#854d0e",
|
|
886
|
+
"zinc-1": "#fafafa",
|
|
887
|
+
"zinc-10": "#18181b",
|
|
888
|
+
"zinc-11": "#09090b",
|
|
889
|
+
"zinc-12": "#050506",
|
|
890
|
+
"zinc-2": "#f4f4f5",
|
|
891
|
+
"zinc-3": "#e4e4e7",
|
|
892
|
+
"zinc-4": "#d4d4d8",
|
|
893
|
+
"zinc-5": "#a1a1aa",
|
|
894
|
+
"zinc-6": "#71717a",
|
|
895
|
+
"zinc-7": "#52525b",
|
|
896
|
+
"zinc-8": "#3f3f46",
|
|
897
|
+
"zinc-9": "#27272a"
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
// src/theme.ts
|
|
901
|
+
var defaultTheme = {
|
|
902
|
+
colors: {
|
|
903
|
+
...PALETTE,
|
|
904
|
+
surface: { light: "#f9fafb", dark: "#111827" }
|
|
905
|
+
},
|
|
906
|
+
// Real Tailwind's default spacing scale in full — every step is exactly
|
|
907
|
+
// `n * 4px` (Tailwind's base spacing unit), including the fractional
|
|
908
|
+
// `.5` steps (`1.5` -> 6px, `2.5` -> 10px, `3.5` -> 14px) and the "px"
|
|
909
|
+
// literal (a bare 1px, for `p-px`-style hairline spacing). This table is
|
|
910
|
+
// only the "usual stops", though — the Rust/JS engines' shared
|
|
911
|
+
// `spacing_px`/`spacingPx` helper falls back to Tailwind v4's own live
|
|
912
|
+
// `n * 4px` formula for any bare numeric step NOT listed here (`p-0.25`,
|
|
913
|
+
// `gap-1.75`, `w-13`, ...), so this table exists to name the common
|
|
914
|
+
// values and give a theme customization something to override, not to
|
|
915
|
+
// enumerate every resolvable step. "px" is the one exception that must
|
|
916
|
+
// stay listed explicitly — it isn't a number, so the formula can't
|
|
917
|
+
// derive it.
|
|
918
|
+
spacing: {
|
|
919
|
+
"0": 0,
|
|
920
|
+
"px": 1,
|
|
921
|
+
"0.5": 2,
|
|
922
|
+
"1": 4,
|
|
923
|
+
"1.5": 6,
|
|
924
|
+
"2": 8,
|
|
925
|
+
"2.5": 10,
|
|
926
|
+
"3": 12,
|
|
927
|
+
"3.5": 14,
|
|
928
|
+
"4": 16,
|
|
929
|
+
"5": 20,
|
|
930
|
+
"6": 24,
|
|
931
|
+
"7": 28,
|
|
932
|
+
"8": 32,
|
|
933
|
+
"9": 36,
|
|
934
|
+
"10": 40,
|
|
935
|
+
"11": 44,
|
|
936
|
+
"12": 48,
|
|
937
|
+
"14": 56,
|
|
938
|
+
"16": 64,
|
|
939
|
+
"20": 80,
|
|
940
|
+
"24": 96,
|
|
941
|
+
"28": 112,
|
|
942
|
+
"32": 128,
|
|
943
|
+
"36": 144,
|
|
944
|
+
"40": 160,
|
|
945
|
+
"44": 176,
|
|
946
|
+
"48": 192,
|
|
947
|
+
"52": 208,
|
|
948
|
+
"56": 224,
|
|
949
|
+
"60": 240,
|
|
950
|
+
"64": 256,
|
|
951
|
+
"72": 288,
|
|
952
|
+
"80": 320,
|
|
953
|
+
"96": 384
|
|
954
|
+
},
|
|
955
|
+
screens: {
|
|
956
|
+
sm: 640,
|
|
957
|
+
md: 768,
|
|
958
|
+
lg: 1024,
|
|
959
|
+
xl: 1280,
|
|
960
|
+
"2xl": 1536
|
|
961
|
+
},
|
|
962
|
+
// Byte-for-byte the same three stacks
|
|
963
|
+
// packages/core-engine/src/resolvers/typography.rs's `font_family_value`
|
|
964
|
+
// falls back to — kept here too (rather than leaving this empty and
|
|
965
|
+
// relying purely on the Rust-side fallback) so `defaultTheme.fontFamily.sans`
|
|
966
|
+
// is a real, readable value for anything that wants to extend rather
|
|
967
|
+
// than replace it (see `KbachConfig.extend.fontFamily` in config.ts).
|
|
968
|
+
fontFamily: {
|
|
969
|
+
sans: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
|
|
970
|
+
serif: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
|
|
971
|
+
mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
|
|
972
|
+
},
|
|
973
|
+
darkMode: "attribute",
|
|
974
|
+
// Empty by default, matching real Tailwind's own defaults (no forced
|
|
975
|
+
// centering/padding) — see `ContainerConfig`'s own doc comment.
|
|
976
|
+
container: {}
|
|
977
|
+
};
|
|
978
|
+
var activeThemeJson = JSON.stringify(defaultTheme);
|
|
979
|
+
|
|
980
|
+
// src/colorVariables.ts
|
|
981
|
+
function parseHexRgb(hex) {
|
|
982
|
+
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
|
|
983
|
+
if (!m) return null;
|
|
984
|
+
let h = m[1];
|
|
985
|
+
if (h.length === 3) h = h.split("").map((c) => c + c).join("");
|
|
986
|
+
const n = parseInt(h, 16);
|
|
987
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// src/config.ts
|
|
991
|
+
function applyOpacityToHex(hex, opacityText) {
|
|
992
|
+
const rgb = parseHexRgb(hex);
|
|
993
|
+
if (!rgb) return hex;
|
|
994
|
+
const a = Number(opacityText) / 100;
|
|
995
|
+
const clamped = Number.isFinite(a) ? Math.min(1, Math.max(0, a)) : 1;
|
|
996
|
+
return `rgba(${rgb.join(",")},${clamped})`;
|
|
997
|
+
}
|
|
998
|
+
function resolveColorEntry(value, lookup) {
|
|
999
|
+
if (typeof value !== "string") return value;
|
|
1000
|
+
const slash = value.indexOf("/");
|
|
1001
|
+
const name = slash === -1 ? value : value.slice(0, slash);
|
|
1002
|
+
const opacity = slash === -1 ? void 0 : value.slice(slash + 1);
|
|
1003
|
+
const base = lookup(name);
|
|
1004
|
+
if (base === void 0) return value;
|
|
1005
|
+
if (opacity === void 0) return base;
|
|
1006
|
+
return typeof base === "string" ? applyOpacityToHex(base, opacity) : { light: applyOpacityToHex(base.light, opacity), dark: applyOpacityToHex(base.dark, opacity) };
|
|
1007
|
+
}
|
|
1008
|
+
function resolveColors(extend, existing) {
|
|
1009
|
+
const resolved = {};
|
|
1010
|
+
const lookup = (name) => resolved[name] ?? existing[name];
|
|
1011
|
+
for (const [name, value] of Object.entries(extend)) {
|
|
1012
|
+
resolved[name] = resolveColorEntry(value, lookup);
|
|
1013
|
+
}
|
|
1014
|
+
return resolved;
|
|
1015
|
+
}
|
|
1016
|
+
function resolveKbachConfig(config) {
|
|
1017
|
+
let theme = { ...defaultTheme, ...config.theme };
|
|
1018
|
+
if (config.extend?.colors) {
|
|
1019
|
+
theme = { ...theme, colors: { ...theme.colors, ...resolveColors(config.extend.colors, theme.colors) } };
|
|
1020
|
+
}
|
|
1021
|
+
if (config.extend?.spacing) {
|
|
1022
|
+
theme = { ...theme, spacing: { ...theme.spacing, ...config.extend.spacing } };
|
|
1023
|
+
}
|
|
1024
|
+
if (config.extend?.screens) {
|
|
1025
|
+
theme = { ...theme, screens: { ...theme.screens, ...config.extend.screens } };
|
|
1026
|
+
}
|
|
1027
|
+
if (config.extend?.fontFamily) {
|
|
1028
|
+
theme = { ...theme, fontFamily: { ...theme.fontFamily, ...config.extend.fontFamily } };
|
|
1029
|
+
}
|
|
1030
|
+
if (config.extend?.container) {
|
|
1031
|
+
theme = { ...theme, container: { ...theme.container, ...config.extend.container } };
|
|
1032
|
+
}
|
|
1033
|
+
if (config.darkMode) {
|
|
1034
|
+
theme = { ...theme, darkMode: config.darkMode };
|
|
1035
|
+
}
|
|
1036
|
+
return theme;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// src/vite-plugin/index.ts
|
|
1040
|
+
var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components"];
|
|
1041
|
+
function loadConfigFile(root) {
|
|
1042
|
+
const path = (0, import_path4.join)(root, "kbach.config.js");
|
|
1043
|
+
if (!(0, import_fs2.existsSync)(path)) return void 0;
|
|
1044
|
+
const mod = (0, import_module.createRequire)(root)(path);
|
|
1045
|
+
return mod.default ?? mod;
|
|
1046
|
+
}
|
|
1047
|
+
function resolveEffectiveTheme(root, options) {
|
|
1048
|
+
if (options.theme) return options.theme;
|
|
1049
|
+
if (options.config) return resolveKbachConfig(options.config);
|
|
1050
|
+
const discovered = loadConfigFile(root);
|
|
1051
|
+
return discovered ? resolveKbachConfig(discovered) : defaultTheme;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// src/postcss-plugin/index.ts
|
|
1055
|
+
var KBACH_START_TEXT = "kbach:start";
|
|
1056
|
+
var KBACH_END_TEXT = "kbach:end";
|
|
1057
|
+
function isKbachMarker(node, text) {
|
|
1058
|
+
return !!node && node.type === "comment" && node.text === text;
|
|
1059
|
+
}
|
|
1060
|
+
function spliceGeneratedCss(root, generatedCss) {
|
|
1061
|
+
const nodes = root.nodes;
|
|
1062
|
+
let startIdx = -1;
|
|
1063
|
+
let endIdx = -1;
|
|
1064
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
1065
|
+
if (startIdx === -1 && isKbachMarker(nodes[i], KBACH_START_TEXT)) {
|
|
1066
|
+
startIdx = i;
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
if (startIdx !== -1 && isKbachMarker(nodes[i], KBACH_END_TEXT)) {
|
|
1070
|
+
endIdx = i;
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
1075
|
+
console.warn(
|
|
1076
|
+
`[kbach] postcss plugin: no "/* kbach:start */" / "/* kbach:end */" marker pair found \u2014 add both to the file this plugin processes (see @kbach/react/vite's own convention). Nothing was generated.`
|
|
1077
|
+
);
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
for (const stale of nodes.slice(startIdx + 1, endIdx)) stale.remove();
|
|
1081
|
+
const parsed = import_postcss.default.parse(generatedCss);
|
|
1082
|
+
const newNodes = [...parsed.nodes];
|
|
1083
|
+
let after = nodes[startIdx];
|
|
1084
|
+
for (const node of newNodes) {
|
|
1085
|
+
root.insertAfter(after, node);
|
|
1086
|
+
after = node;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
function kbachPostcss(options = {}) {
|
|
1090
|
+
return {
|
|
1091
|
+
postcssPlugin: "kbach",
|
|
1092
|
+
Once(root, { result }) {
|
|
1093
|
+
const projectRoot = options.root ?? process.cwd();
|
|
1094
|
+
const theme = resolveEffectiveTheme(projectRoot, options);
|
|
1095
|
+
const includeDirs = options.include ?? DEFAULT_SCAN_DIRS;
|
|
1096
|
+
const safelist = options.safelist ?? [];
|
|
1097
|
+
const engine = createKbachStaticCssEngine({
|
|
1098
|
+
root: projectRoot,
|
|
1099
|
+
theme,
|
|
1100
|
+
themeJson: JSON.stringify(theme),
|
|
1101
|
+
includeDirs,
|
|
1102
|
+
safelist
|
|
1103
|
+
});
|
|
1104
|
+
engine.scanProjectCssSelectorsOnce();
|
|
1105
|
+
engine.initialScan();
|
|
1106
|
+
engine.processSafelist();
|
|
1107
|
+
for (const dir of includeDirs) {
|
|
1108
|
+
result.messages.push({
|
|
1109
|
+
type: "dir-dependency",
|
|
1110
|
+
plugin: "kbach",
|
|
1111
|
+
dir: (0, import_path5.resolve)(projectRoot, dir),
|
|
1112
|
+
glob: "**/*"
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
spliceGeneratedCss(root, engine.generateCSS());
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
kbachPostcss.postcss = true;
|