@kbach/react 1.0.0-beta.27 → 1.0.0-beta.29

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.
@@ -23,6 +23,7 @@ __export(vite_plugin_exports, {
23
23
  CSS_SYNC_DEBOUNCE_MS: () => CSS_SYNC_DEBOUNCE_MS,
24
24
  DEFAULT_SCAN_DIRS: () => DEFAULT_SCAN_DIRS,
25
25
  findMainCSSFile: () => findMainCSSFile,
26
+ isConfigFile: () => isConfigFile,
26
27
  kbach: () => kbach,
27
28
  loadConfigFile: () => loadConfigFile,
28
29
  resolveEffectiveTheme: () => resolveEffectiveTheme
@@ -269,6 +270,59 @@ function buildResetCSS(usedTags) {
269
270
  return RESET_RULES.filter((r) => !r.tags || r.tags.some((t) => usedTags.has(t))).map((r) => r.css).join("\n");
270
271
  }
271
272
 
273
+ // src/colorVariables.ts
274
+ function parseHexRgb(hex) {
275
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
276
+ if (!m) return null;
277
+ let h = m[1];
278
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
279
+ const n = parseInt(h, 16);
280
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
281
+ }
282
+ function wrapDarkDecls(strategy, decls) {
283
+ switch (strategy) {
284
+ case "class":
285
+ return `.dark { ${decls} }`;
286
+ case "media":
287
+ return `@media (prefers-color-scheme: dark) { :root { ${decls} } }`;
288
+ case "attribute":
289
+ default:
290
+ return `[data-theme="dark"] { ${decls} }`;
291
+ }
292
+ }
293
+ function modeAwareDecls(theme) {
294
+ const light = [];
295
+ const dark = [];
296
+ for (const [name, value] of Object.entries(theme.colors)) {
297
+ if (typeof value === "string") continue;
298
+ const l = parseHexRgb(value.light);
299
+ const d = parseHexRgb(value.dark);
300
+ if (!l || !d) continue;
301
+ light.push(`--kb-color-${name}: ${l.join(",")};`);
302
+ dark.push(`--kb-color-${name}: ${d.join(",")};`);
303
+ }
304
+ return { light, dark };
305
+ }
306
+ function colorVariablesCss(theme) {
307
+ const { light, dark } = modeAwareDecls(theme);
308
+ if (light.length === 0) return "";
309
+ return `:root { ${light.join(" ")} }
310
+ ${wrapDarkDecls(theme.darkMode, dark.join(" "))}`;
311
+ }
312
+ function withColorVariables(theme) {
313
+ let changed = false;
314
+ const colors = {};
315
+ for (const [name, value] of Object.entries(theme.colors)) {
316
+ if (typeof value !== "string" && parseHexRgb(value.light) && parseHexRgb(value.dark)) {
317
+ colors[name] = `rgb(var(--kb-color-${name}))`;
318
+ changed = true;
319
+ } else {
320
+ colors[name] = value;
321
+ }
322
+ }
323
+ return changed ? { ...theme, colors } : theme;
324
+ }
325
+
272
326
  // src/vite-plugin/format.ts
273
327
  var KBACH_START = "/* kbach:start */";
274
328
  var KBACH_END = "/* kbach:end */";
@@ -288,6 +342,8 @@ function formatKbachCSS(tokenRules, theme, usedTags) {
288
342
  lines.push("", ":root {", ` --kb-dark-mode: ${theme.darkMode};`);
289
343
  for (const [varName, val] of declarations) lines.push(` ${varName}: ${val};`);
290
344
  lines.push("}");
345
+ const colorVars = colorVariablesCss(theme);
346
+ if (colorVars) lines.push("", prettifyCSS(colorVars));
291
347
  lines.push("", prettifyCSS(text));
292
348
  return lines.join("\n");
293
349
  }
@@ -694,16 +750,6 @@ function detectDarkModeStrategyFromDom() {
694
750
  setTheme({ ...activeTheme, darkMode: raw });
695
751
  }
696
752
 
697
- // src/colorVariables.ts
698
- function parseHexRgb(hex) {
699
- const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
700
- if (!m) return null;
701
- let h = m[1];
702
- if (h.length === 3) h = h.split("").map((c) => c + c).join("");
703
- const n = parseInt(h, 16);
704
- return [n >> 16 & 255, n >> 8 & 255, n & 255];
705
- }
706
-
707
753
  // src/darkModeStore.ts
708
754
  var STORAGE_KEY = "kbach-theme";
709
755
  var listeners = /* @__PURE__ */ new Set();
@@ -768,7 +814,13 @@ function applyOpacityToHex(hex, opacityText) {
768
814
  return `rgba(${rgb.join(",")},${clamped})`;
769
815
  }
770
816
  function resolveColorEntry(value, lookup) {
771
- if (typeof value !== "string") return value;
817
+ if (typeof value !== "string") {
818
+ const side = (v, mode2) => {
819
+ const r = resolveColorEntry(v, lookup);
820
+ return typeof r === "string" ? r : r[mode2];
821
+ };
822
+ return { light: side(value.light, "light"), dark: side(value.dark, "dark") };
823
+ }
772
824
  const slash = value.indexOf("/");
773
825
  const name = slash === -1 ? value : value.slice(0, slash);
774
826
  const opacity = slash === -1 ? void 0 : value.slice(slash + 1);
@@ -835,6 +887,9 @@ var import_node = require("@kbach/core-engine/node");
835
887
  function generateCssForToken(token, themeJson) {
836
888
  return JSON.parse((0, import_node.generate_css)(token, themeJson));
837
889
  }
890
+ function suggestClassForToken(token, themeJson) {
891
+ return (0, import_node.suggest_class)(token, themeJson) || null;
892
+ }
838
893
 
839
894
  // src/vite-plugin/scan.ts
840
895
  var import_fs = require("fs");
@@ -1094,17 +1149,18 @@ function isMarkerClass(base) {
1094
1149
  if (slashIdx === -1) return false;
1095
1150
  return MARKER_CLASSES.has(base.slice(0, slashIdx)) && slashIdx < base.length - 1;
1096
1151
  }
1097
- function warnIfUnknownClass(token, filePath, code, isResolvable, projectCssClasses, warnedTokens) {
1152
+ var TAILWIND_SHADE = /-(?:50|[1-9]00|950)(?:\/|$)/;
1153
+ function warnIfUnknownClass(token, filePath, code, isResolvable, projectCssClasses, warnedTokens, suggest) {
1098
1154
  if (isResolvable || warnedTokens.has(token)) return;
1099
1155
  const base = stripModifierPrefix(token);
1100
1156
  if (!base || projectCssClasses.has(base) || isMarkerClass(base)) return;
1101
1157
  warnedTokens.add(token);
1102
1158
  const pos = findLineCol(code, token);
1103
1159
  const location = pos ? `${blue(filePath)}${green(`:${pos.line}:${pos.column}`)}` : blue(filePath);
1104
- console.warn(
1105
- `${kbachTag()} ${gray("Unknown class")} ${highlight(token)} ${gray("\u2014 no Kbach utility or project CSS rule matches it. Typo?")}
1106
- ${bold("at")} ${location}`
1107
- );
1160
+ const suggestion = suggest?.(token) ?? null;
1161
+ const hint = suggestion ? `${gray("\u2014 did you mean")} ${highlight(suggestion)}${gray("?")}${TAILWIND_SHADE.test(token) ? gray(" Kbach color shades run 1\u201312, not 50\u2013950.") : ""}` : gray("\u2014 no Kbach utility or project CSS rule matches it. Typo?");
1162
+ console.warn(`${kbachTag()} ${gray("Unknown class")} ${highlight(token)} ${hint}
1163
+ ${bold("at")} ${location}`);
1108
1164
  }
1109
1165
 
1110
1166
  // src/staticCss/engine.ts
@@ -1114,7 +1170,8 @@ function normPath(p) {
1114
1170
  }
1115
1171
  var SAFELIST_KEY = "\0kbach-safelist";
1116
1172
  function createKbachStaticCssEngine(options) {
1117
- const { root, theme, themeJson, includeDirs, safelist } = options;
1173
+ const { root, theme, includeDirs, safelist } = options;
1174
+ const themeJson = JSON.stringify(withColorVariables(theme));
1118
1175
  const fileTokens = /* @__PURE__ */ new Map();
1119
1176
  const tokenRules = /* @__PURE__ */ new Map();
1120
1177
  const fileTags = /* @__PURE__ */ new Map();
@@ -1132,7 +1189,7 @@ function createKbachStaticCssEngine(options) {
1132
1189
  tokens.add(tok);
1133
1190
  const rules = resolveToken(tok);
1134
1191
  if (process.env.NODE_ENV !== "production") {
1135
- warnIfUnknownClass(tok, filePath, code, rules.length > 0, projectCssClasses, warnedTokens);
1192
+ warnIfUnknownClass(tok, filePath, code, rules.length > 0, projectCssClasses, warnedTokens, (t) => suggestClassForToken(t, themeJson));
1136
1193
  }
1137
1194
  }
1138
1195
  fileTokens.set(normPath(filePath), tokens);
@@ -1217,13 +1274,27 @@ function isEsmRequireFailure(err) {
1217
1274
  return err.code === "ERR_REQUIRE_ESM" || /is not defined in ES module scope/.test(err.message);
1218
1275
  }
1219
1276
  async function importConfigModule(path) {
1220
- const mod = await import((0, import_url.pathToFileURL)(path).href);
1277
+ const url = (0, import_url.pathToFileURL)(path).href;
1278
+ let mod;
1279
+ try {
1280
+ mod = await nativeImport(url + "?t=" + Date.now());
1281
+ } catch (err) {
1282
+ if (err.code !== "ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING") throw err;
1283
+ mod = await import(url);
1284
+ }
1221
1285
  return mod.default ?? mod;
1222
1286
  }
1287
+ var nativeImport = new Function("url", "return import(url)");
1223
1288
  function requireConfigModule(root, path) {
1224
- const mod = (0, import_module.createRequire)(root)(path);
1289
+ const req = (0, import_module.createRequire)(root);
1290
+ delete req.cache[req.resolve(path)];
1291
+ const mod = req(path);
1225
1292
  return mod.default ?? mod;
1226
1293
  }
1294
+ function isConfigFile(root, file) {
1295
+ const norm = (p) => p.replace(/\\/g, "/").toLowerCase();
1296
+ return /^kbach\.config\.(c|m)?js$/.test((0, import_path4.basename)(file)) && norm((0, import_path4.dirname)(file)) === norm(root);
1297
+ }
1227
1298
  async function loadConfigFile(root) {
1228
1299
  const cjsPath = (0, import_path4.join)(root, "kbach.config.cjs");
1229
1300
  if ((0, import_fs3.existsSync)(cjsPath)) return requireConfigModule(root, cjsPath);
@@ -1231,6 +1302,7 @@ async function loadConfigFile(root) {
1231
1302
  if ((0, import_fs3.existsSync)(mjsPath)) return importConfigModule(mjsPath);
1232
1303
  const jsPath = (0, import_path4.join)(root, "kbach.config.js");
1233
1304
  if (!(0, import_fs3.existsSync)(jsPath)) return void 0;
1305
+ if (isEsmProject(root)) return importConfigModule(jsPath);
1234
1306
  try {
1235
1307
  return requireConfigModule(root, jsPath);
1236
1308
  } catch (err) {
@@ -1238,6 +1310,13 @@ async function loadConfigFile(root) {
1238
1310
  return importConfigModule(jsPath);
1239
1311
  }
1240
1312
  }
1313
+ function isEsmProject(root) {
1314
+ try {
1315
+ return JSON.parse((0, import_fs3.readFileSync)((0, import_path4.join)(root, "package.json"), "utf-8")).type === "module";
1316
+ } catch {
1317
+ return false;
1318
+ }
1319
+ }
1241
1320
  async function resolveEffectiveTheme(root, options) {
1242
1321
  if (options.theme) return options.theme;
1243
1322
  if (options.config) return resolveKbachConfig(options.config);
@@ -1249,7 +1328,7 @@ function kbach(options = {}) {
1249
1328
  const safelist = options.safelist ?? [];
1250
1329
  let root = process.cwd();
1251
1330
  let theme = defaultTheme;
1252
- let engine = createKbachStaticCssEngine({ root, theme, themeJson: JSON.stringify(theme), includeDirs, safelist });
1331
+ let engine = createKbachStaticCssEngine({ root, theme, includeDirs, safelist });
1253
1332
  let mainCSSFilePath = (0, import_path4.join)(root, "src", "kbach.css");
1254
1333
  function mainCSSFile() {
1255
1334
  return mainCSSFilePath;
@@ -1266,13 +1345,27 @@ function kbach(options = {}) {
1266
1345
  syncMainCSSFile(server);
1267
1346
  }, CSS_SYNC_DEBOUNCE_MS);
1268
1347
  }
1348
+ async function reloadConfig(server) {
1349
+ if (options.theme || options.config) return;
1350
+ try {
1351
+ theme = await resolveEffectiveTheme(root, options);
1352
+ } catch (err) {
1353
+ console.warn(`${kbachTag()} Couldn't reload kbach.config: ${err.message}`);
1354
+ return;
1355
+ }
1356
+ engine = createKbachStaticCssEngine({ root, theme, includeDirs, safelist });
1357
+ engine.scanProjectCssSelectorsOnce();
1358
+ engine.initialScan();
1359
+ engine.processSafelist();
1360
+ syncMainCSSFile(server);
1361
+ }
1269
1362
  return {
1270
1363
  name: "kbach",
1271
1364
  enforce: "pre",
1272
1365
  async configResolved(resolved) {
1273
1366
  root = resolved.root;
1274
1367
  theme = await resolveEffectiveTheme(root, options);
1275
- engine = createKbachStaticCssEngine({ root, theme, themeJson: JSON.stringify(theme), includeDirs, safelist });
1368
+ engine = createKbachStaticCssEngine({ root, theme, includeDirs, safelist });
1276
1369
  mainCSSFilePath = findMainCSSFile(root, includeDirs, options.cssFile);
1277
1370
  },
1278
1371
  buildStart() {
@@ -1287,6 +1380,10 @@ function kbach(options = {}) {
1287
1380
  configureServer(server) {
1288
1381
  const onAddOrUnlink = (file, isUnlink) => {
1289
1382
  if (file.includes("node_modules")) return;
1383
+ if (isConfigFile(root, file)) {
1384
+ void reloadConfig(server);
1385
+ return;
1386
+ }
1290
1387
  if (!/\.(tsx?|jsx?)$/.test(file)) return;
1291
1388
  if (isUnlink) {
1292
1389
  engine.removeFile(file);
@@ -1302,8 +1399,12 @@ function kbach(options = {}) {
1302
1399
  server.watcher.on("add", (file) => onAddOrUnlink(file, false));
1303
1400
  server.watcher.on("unlink", (file) => onAddOrUnlink(file, true));
1304
1401
  },
1305
- handleHotUpdate({ file, server }) {
1402
+ async handleHotUpdate({ file, server }) {
1306
1403
  if (file.includes("node_modules")) return;
1404
+ if (isConfigFile(root, file)) {
1405
+ await reloadConfig(server);
1406
+ return;
1407
+ }
1307
1408
  if (!/\.(tsx?|jsx?)$/.test(file)) return;
1308
1409
  try {
1309
1410
  engine.processFile(file, readSourceFile(file));
@@ -1319,6 +1420,7 @@ function kbach(options = {}) {
1319
1420
  CSS_SYNC_DEBOUNCE_MS,
1320
1421
  DEFAULT_SCAN_DIRS,
1321
1422
  findMainCSSFile,
1423
+ isConfigFile,
1322
1424
  kbach,
1323
1425
  loadConfigFile,
1324
1426
  resolveEffectiveTheme
@@ -2,16 +2,18 @@ import {
2
2
  CSS_SYNC_DEBOUNCE_MS,
3
3
  DEFAULT_SCAN_DIRS,
4
4
  findMainCSSFile,
5
+ isConfigFile,
5
6
  kbach,
6
7
  loadConfigFile,
7
8
  resolveEffectiveTheme
8
- } from "./chunk-KTRJZDVE.mjs";
9
- import "./chunk-43FOLRQA.mjs";
10
- import "./chunk-OVKVZA7B.mjs";
9
+ } from "./chunk-NHWKWY2T.mjs";
10
+ import "./chunk-UK5S55DF.mjs";
11
+ import "./chunk-FBYS4BI3.mjs";
11
12
  export {
12
13
  CSS_SYNC_DEBOUNCE_MS,
13
14
  DEFAULT_SCAN_DIRS,
14
15
  findMainCSSFile,
16
+ isConfigFile,
15
17
  kbach,
16
18
  loadConfigFile,
17
19
  resolveEffectiveTheme
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "1.0.0-beta.27",
3
+ "version": "1.0.0-beta.29",
4
4
  "description": "A utility-first styling engine for React, powered by a Rust/WASM core: Tailwind CSS-compatible utility class names with Kbach's own color palette.",
5
5
  "keywords": [
6
6
  "css",
@@ -74,7 +74,7 @@
74
74
  "generate:colors": "node ../core-engine/scripts/generate-palette.mjs src/generatedPalette.ts"
75
75
  },
76
76
  "dependencies": {
77
- "@kbach/core-engine": "1.0.0-beta.16"
77
+ "@kbach/core-engine": "1.0.0-beta.18"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@types/react": "^19.2.0",