@kbach/react 0.5.41 → 0.5.43

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.
@@ -4,7 +4,7 @@ import {
4
4
  hasResponsiveBuckets,
5
5
  useConditionalWidth,
6
6
  useGlobalDarkMode
7
- } from "./chunk-GOQNOKFN.mjs";
7
+ } from "./chunk-SX43SDOD.mjs";
8
8
  import {
9
9
  flatten,
10
10
  getActiveBreakpoints,
@@ -16,7 +16,7 @@ import {
16
16
  isNative,
17
17
  normalizeClassString,
18
18
  resolve
19
- } from "./chunk-VCNP57PR.mjs";
19
+ } from "./chunk-IBAKFGFG.mjs";
20
20
 
21
21
  // src/jsx-runtime.tsx
22
22
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -2351,6 +2351,10 @@ function parseClass(className) {
2351
2351
  const value = remaining.slice(bracketStart + 2, -1).replace(/_/g, " ");
2352
2352
  return { original: trimmed, modifiers, negative, important, utility, value, isArbitrary: true };
2353
2353
  }
2354
+ if (remaining.startsWith("[") && remaining.endsWith("]")) {
2355
+ const value = remaining.slice(1, -1).replace(/_/g, " ");
2356
+ return { original: trimmed, modifiers, negative, important, utility: "", value, isArbitrary: true };
2357
+ }
2354
2358
  if (getBuiltinStandaloneNames().has(remaining)) {
2355
2359
  return { original: trimmed, modifiers, negative, important, utility: remaining, value: "", isArbitrary: false };
2356
2360
  }
@@ -2916,7 +2920,7 @@ function buildConfig(userConfig) {
2916
2920
  for (const plugin of resolved.plugins) {
2917
2921
  plugin(pluginAPI);
2918
2922
  }
2919
- const rawSans = theme.fontFamily.sans;
2923
+ const rawSans = theme.fontFamily?.sans;
2920
2924
  const sansFontValue = Array.isArray(rawSans) ? rawSans[0] : rawSans;
2921
2925
  setDefaultFontFamily(sansFontValue && sansFontValue !== "System" ? sansFontValue : void 0);
2922
2926
  injectGlobalStyles(resolved.theme);
@@ -2940,7 +2944,8 @@ function makePluginAPI(theme) {
2940
2944
  },
2941
2945
  addVariant(name, selectorOrDef) {
2942
2946
  const def = typeof selectorOrDef === "string" ? _selectorToModifierDef(selectorOrDef) : selectorOrDef;
2943
- customVariants[name] = typeof selectorOrDef === "string" ? selectorOrDef : name;
2947
+ const selector = typeof selectorOrDef === "string" ? selectorOrDef : selectorOrDef.pseudo ?? selectorOrDef.mediaQuery ?? selectorOrDef.ancestorSelector ?? name;
2948
+ customVariants[name] = selector;
2944
2949
  registerModifier(name, def);
2945
2950
  },
2946
2951
  theme(path, defaultValue) {
@@ -6,7 +6,7 @@ import {
6
6
  isNative,
7
7
  isWeb,
8
8
  subscribeGlobalWidth
9
- } from "./chunk-VCNP57PR.mjs";
9
+ } from "./chunk-IBAKFGFG.mjs";
10
10
 
11
11
  // src/core/darkModeStore.ts
12
12
  var KEY = "__kbach_dark_store__";
package/dist/index.js CHANGED
@@ -2521,6 +2521,10 @@ function parseClass(className) {
2521
2521
  const value = remaining.slice(bracketStart + 2, -1).replace(/_/g, " ");
2522
2522
  return { original: trimmed, modifiers, negative, important, utility, value, isArbitrary: true };
2523
2523
  }
2524
+ if (remaining.startsWith("[") && remaining.endsWith("]")) {
2525
+ const value = remaining.slice(1, -1).replace(/_/g, " ");
2526
+ return { original: trimmed, modifiers, negative, important, utility: "", value, isArbitrary: true };
2527
+ }
2524
2528
  if (getBuiltinStandaloneNames().has(remaining)) {
2525
2529
  return { original: trimmed, modifiers, negative, important, utility: remaining, value: "", isArbitrary: false };
2526
2530
  }
@@ -3036,7 +3040,7 @@ function buildConfig(userConfig) {
3036
3040
  for (const plugin of resolved.plugins) {
3037
3041
  plugin(pluginAPI);
3038
3042
  }
3039
- const rawSans = theme.fontFamily.sans;
3043
+ const rawSans = theme.fontFamily?.sans;
3040
3044
  const sansFontValue = Array.isArray(rawSans) ? rawSans[0] : rawSans;
3041
3045
  setDefaultFontFamily(sansFontValue && sansFontValue !== "System" ? sansFontValue : void 0);
3042
3046
  injectGlobalStyles(resolved.theme);
@@ -3060,7 +3064,8 @@ function makePluginAPI(theme) {
3060
3064
  },
3061
3065
  addVariant(name, selectorOrDef) {
3062
3066
  const def = typeof selectorOrDef === "string" ? _selectorToModifierDef(selectorOrDef) : selectorOrDef;
3063
- customVariants[name] = typeof selectorOrDef === "string" ? selectorOrDef : name;
3067
+ const selector = typeof selectorOrDef === "string" ? selectorOrDef : selectorOrDef.pseudo ?? selectorOrDef.mediaQuery ?? selectorOrDef.ancestorSelector ?? name;
3068
+ customVariants[name] = selector;
3064
3069
  registerModifier(name, def);
3065
3070
  },
3066
3071
  theme(path, defaultValue) {
package/dist/index.mjs CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  useConditionalWidth,
8
8
  useGlobalDarkMode,
9
9
  useGlobalWidth
10
- } from "./chunk-GOQNOKFN.mjs";
10
+ } from "./chunk-SX43SDOD.mjs";
11
11
  import {
12
12
  __require,
13
13
  buildConfig,
@@ -28,7 +28,7 @@ import {
28
28
  syncGlobalScreens,
29
29
  syncGlobalWidth,
30
30
  updateConfig
31
- } from "./chunk-VCNP57PR.mjs";
31
+ } from "./chunk-IBAKFGFG.mjs";
32
32
 
33
33
  // src/context.tsx
34
34
  import { createContext, useContext } from "react";
@@ -2422,6 +2422,10 @@ function parseClass(className) {
2422
2422
  const value = remaining.slice(bracketStart + 2, -1).replace(/_/g, " ");
2423
2423
  return { original: trimmed, modifiers, negative, important, utility, value, isArbitrary: true };
2424
2424
  }
2425
+ if (remaining.startsWith("[") && remaining.endsWith("]")) {
2426
+ const value = remaining.slice(1, -1).replace(/_/g, " ");
2427
+ return { original: trimmed, modifiers, negative, important, utility: "", value, isArbitrary: true };
2428
+ }
2425
2429
  if (getBuiltinStandaloneNames().has(remaining)) {
2426
2430
  return { original: trimmed, modifiers, negative, important, utility: remaining, value: "", isArbitrary: false };
2427
2431
  }
@@ -2918,7 +2922,7 @@ function buildConfig(userConfig) {
2918
2922
  for (const plugin of resolved.plugins) {
2919
2923
  plugin(pluginAPI);
2920
2924
  }
2921
- const rawSans = theme.fontFamily.sans;
2925
+ const rawSans = theme.fontFamily?.sans;
2922
2926
  const sansFontValue = Array.isArray(rawSans) ? rawSans[0] : rawSans;
2923
2927
  setDefaultFontFamily(sansFontValue && sansFontValue !== "System" ? sansFontValue : void 0);
2924
2928
  injectGlobalStyles(resolved.theme);
@@ -2942,7 +2946,8 @@ function makePluginAPI(theme) {
2942
2946
  },
2943
2947
  addVariant(name, selectorOrDef) {
2944
2948
  const def = typeof selectorOrDef === "string" ? _selectorToModifierDef(selectorOrDef) : selectorOrDef;
2945
- customVariants[name] = typeof selectorOrDef === "string" ? selectorOrDef : name;
2949
+ const selector = typeof selectorOrDef === "string" ? selectorOrDef : selectorOrDef.pseudo ?? selectorOrDef.mediaQuery ?? selectorOrDef.ancestorSelector ?? name;
2950
+ customVariants[name] = selector;
2946
2951
  registerModifier(name, def);
2947
2952
  },
2948
2953
  theme(path, defaultValue) {
@@ -2,9 +2,9 @@ import {
2
2
  Fragment,
3
3
  jsx,
4
4
  jsxs
5
- } from "./chunk-AAPA3CRV.mjs";
6
- import "./chunk-GOQNOKFN.mjs";
7
- import "./chunk-VCNP57PR.mjs";
5
+ } from "./chunk-4NTWFAHW.mjs";
6
+ import "./chunk-SX43SDOD.mjs";
7
+ import "./chunk-IBAKFGFG.mjs";
8
8
 
9
9
  // src/jsx-dev-runtime.tsx
10
10
  function jsxDEV(type, props, key, isStaticChildren, source, self) {
@@ -2422,6 +2422,10 @@ function parseClass(className) {
2422
2422
  const value = remaining.slice(bracketStart + 2, -1).replace(/_/g, " ");
2423
2423
  return { original: trimmed, modifiers, negative, important, utility, value, isArbitrary: true };
2424
2424
  }
2425
+ if (remaining.startsWith("[") && remaining.endsWith("]")) {
2426
+ const value = remaining.slice(1, -1).replace(/_/g, " ");
2427
+ return { original: trimmed, modifiers, negative, important, utility: "", value, isArbitrary: true };
2428
+ }
2425
2429
  if (getBuiltinStandaloneNames().has(remaining)) {
2426
2430
  return { original: trimmed, modifiers, negative, important, utility: remaining, value: "", isArbitrary: false };
2427
2431
  }
@@ -2918,7 +2922,7 @@ function buildConfig(userConfig) {
2918
2922
  for (const plugin of resolved.plugins) {
2919
2923
  plugin(pluginAPI);
2920
2924
  }
2921
- const rawSans = theme.fontFamily.sans;
2925
+ const rawSans = theme.fontFamily?.sans;
2922
2926
  const sansFontValue = Array.isArray(rawSans) ? rawSans[0] : rawSans;
2923
2927
  setDefaultFontFamily(sansFontValue && sansFontValue !== "System" ? sansFontValue : void 0);
2924
2928
  injectGlobalStyles(resolved.theme);
@@ -2942,7 +2946,8 @@ function makePluginAPI(theme) {
2942
2946
  },
2943
2947
  addVariant(name, selectorOrDef) {
2944
2948
  const def = typeof selectorOrDef === "string" ? _selectorToModifierDef(selectorOrDef) : selectorOrDef;
2945
- customVariants[name] = typeof selectorOrDef === "string" ? selectorOrDef : name;
2949
+ const selector = typeof selectorOrDef === "string" ? selectorOrDef : selectorOrDef.pseudo ?? selectorOrDef.mediaQuery ?? selectorOrDef.ancestorSelector ?? name;
2950
+ customVariants[name] = selector;
2946
2951
  registerModifier(name, def);
2947
2952
  },
2948
2953
  theme(path, defaultValue) {
@@ -3,9 +3,9 @@ import {
3
3
  _invalidateDefaultFontCache,
4
4
  jsx,
5
5
  jsxs
6
- } from "./chunk-AAPA3CRV.mjs";
7
- import "./chunk-GOQNOKFN.mjs";
8
- import "./chunk-VCNP57PR.mjs";
6
+ } from "./chunk-4NTWFAHW.mjs";
7
+ import "./chunk-SX43SDOD.mjs";
8
+ import "./chunk-IBAKFGFG.mjs";
9
9
  export {
10
10
  Fragment,
11
11
  _invalidateDefaultFontCache,
@@ -2345,6 +2345,10 @@ function parseClass(className) {
2345
2345
  const value = remaining.slice(bracketStart + 2, -1).replace(/_/g, " ");
2346
2346
  return { original: trimmed, modifiers, negative, important, utility, value, isArbitrary: true };
2347
2347
  }
2348
+ if (remaining.startsWith("[") && remaining.endsWith("]")) {
2349
+ const value = remaining.slice(1, -1).replace(/_/g, " ");
2350
+ return { original: trimmed, modifiers, negative, important, utility: "", value, isArbitrary: true };
2351
+ }
2348
2352
  if (getBuiltinStandaloneNames().has(remaining)) {
2349
2353
  return { original: trimmed, modifiers, negative, important, utility: remaining, value: "", isArbitrary: false };
2350
2354
  }
@@ -2742,7 +2746,7 @@ function buildConfig(userConfig) {
2742
2746
  for (const plugin of resolved.plugins) {
2743
2747
  plugin(pluginAPI);
2744
2748
  }
2745
- const rawSans = theme.fontFamily.sans;
2749
+ const rawSans = theme.fontFamily?.sans;
2746
2750
  const sansFontValue = Array.isArray(rawSans) ? rawSans[0] : rawSans;
2747
2751
  setDefaultFontFamily(sansFontValue && sansFontValue !== "System" ? sansFontValue : void 0);
2748
2752
  injectGlobalStyles(resolved.theme);
@@ -2766,7 +2770,8 @@ function makePluginAPI(theme) {
2766
2770
  },
2767
2771
  addVariant(name, selectorOrDef) {
2768
2772
  const def = typeof selectorOrDef === "string" ? _selectorToModifierDef(selectorOrDef) : selectorOrDef;
2769
- customVariants[name] = typeof selectorOrDef === "string" ? selectorOrDef : name;
2773
+ const selector = typeof selectorOrDef === "string" ? selectorOrDef : selectorOrDef.pseudo ?? selectorOrDef.mediaQuery ?? selectorOrDef.ancestorSelector ?? name;
2774
+ customVariants[name] = selector;
2770
2775
  registerModifier(name, def);
2771
2776
  },
2772
2777
  theme(path, defaultValue) {
@@ -2805,6 +2810,10 @@ var customVariants = new Proxy(
2805
2810
  );
2806
2811
 
2807
2812
  // src/vite-plugin.ts
2813
+ function normPath(p) {
2814
+ const fwd = p.replace(/\\/g, "/");
2815
+ return process.platform === "win32" ? fwd.toLowerCase() : fwd;
2816
+ }
2808
2817
  setCSSGenMode(true);
2809
2818
  var KBACH_START = "/* kbach:start */";
2810
2819
  var KBACH_END = "/* kbach:end */";
@@ -2818,9 +2827,9 @@ var GROUPS = [
2818
2827
  ["Effects", /^(shadow|opacity|blur|brightness|contrast|grayscale|transition|duration|ease|delay|animate|scale|rotate|translate|transform|cursor|pointer|select-)/]
2819
2828
  ];
2820
2829
  var GROUP_ORDER = ["Layout", "Sizing", "Spacing", "Typography", "Colors", "Borders", "Effects", "Utilities", "Dark Mode", "Responsive"];
2821
- function classifyToken(token) {
2830
+ function classifyToken(token, responsiveRe) {
2822
2831
  if (/^dark:/.test(token)) return "Dark Mode";
2823
- if (/^(sm|md|lg|xl|2xl):/.test(token)) return "Responsive";
2832
+ if (responsiveRe.test(token)) return "Responsive";
2824
2833
  const base = token.replace(/^(hover|focus|active|group-hover|peer-hover):/, "");
2825
2834
  for (const [label, re] of GROUPS) {
2826
2835
  if (re.test(base)) return label;
@@ -2845,7 +2854,7 @@ function buildColorVarMap(theme, usedCSS) {
2845
2854
  }
2846
2855
  return map;
2847
2856
  }
2848
- function formatKbachCSS(tokenCSS, theme) {
2857
+ function formatKbachCSS(tokenCSS, theme, responsiveRe) {
2849
2858
  const allRaw = [...tokenCSS.values()].filter(Boolean).join("\n");
2850
2859
  const colorVars = buildColorVarMap(theme, allRaw);
2851
2860
  function applyVars(css) {
@@ -2856,7 +2865,7 @@ function formatKbachCSS(tokenCSS, theme) {
2856
2865
  const groups = new Map(GROUP_ORDER.map((g) => [g, []]));
2857
2866
  for (const [token, css] of tokenCSS) {
2858
2867
  if (!css) continue;
2859
- const label = classifyToken(token);
2868
+ const label = classifyToken(token, responsiveRe);
2860
2869
  if (!groups.has(label)) groups.set(label, []);
2861
2870
  groups.get(label).push(applyVars(css));
2862
2871
  }
@@ -2885,16 +2894,50 @@ function buildThemeGlobal(theme) {
2885
2894
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
2886
2895
  return `body { font-family: ${family}; }`;
2887
2896
  }
2888
- function findMainCSSFile(root) {
2889
- for (const c of [
2890
- (0, import_path.join)(root, "app", "app.css"),
2891
- (0, import_path.join)(root, "src", "index.css"),
2892
- (0, import_path.join)(root, "src", "app.css"),
2893
- (0, import_path.join)(root, "app", "index.css")
2894
- ]) {
2895
- if ((0, import_fs.existsSync)(c)) return c;
2897
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
2898
+ "node_modules",
2899
+ "dist",
2900
+ "build",
2901
+ "out",
2902
+ ".next",
2903
+ ".nuxt",
2904
+ ".output",
2905
+ ".git",
2906
+ ".svn",
2907
+ ".cache",
2908
+ "coverage",
2909
+ "__pycache__"
2910
+ ]);
2911
+ function findKbachCSS(root) {
2912
+ const found = [];
2913
+ function scan(dir, depth) {
2914
+ if (depth > 10) return;
2915
+ let entries;
2916
+ try {
2917
+ entries = (0, import_fs.readdirSync)(dir);
2918
+ } catch {
2919
+ return;
2920
+ }
2921
+ for (const entry of entries) {
2922
+ if (entry.startsWith(".") || SKIP_DIRS.has(entry)) continue;
2923
+ const full = (0, import_path.join)(dir, entry);
2924
+ try {
2925
+ const st = (0, import_fs.statSync)(full);
2926
+ if (st.isDirectory()) scan(full, depth + 1);
2927
+ else if (entry === "kbach.css") found.push(full);
2928
+ } catch {
2929
+ }
2930
+ }
2896
2931
  }
2897
- return null;
2932
+ scan(root, 0);
2933
+ if (found.length === 0) return null;
2934
+ found.sort((a, b) => a.split(/[\\/]/g).length - b.split(/[\\/]/g).length);
2935
+ if (found.length > 1) {
2936
+ console.warn(
2937
+ "[kbach] Multiple kbach.css files found \u2014 only the shallowest will receive generated styles.\n Remove duplicates to avoid confusion:\n" + found.map((f) => ` ${f}`).join("\n")
2938
+ );
2939
+ }
2940
+ return found[0];
2898
2941
  }
2899
2942
  function writeKbachToFile(filePath, css) {
2900
2943
  let existing = "";
@@ -2910,7 +2953,9 @@ ${KBACH_END}`;
2910
2953
  const next = start !== -1 && end !== -1 ? existing.slice(0, start) + block + existing.slice(end + KBACH_END.length) : existing ? `${existing}
2911
2954
 
2912
2955
  ${block}` : block;
2913
- if (next !== existing) (0, import_fs.writeFileSync)(filePath, next, "utf-8");
2956
+ if (next === existing) return false;
2957
+ (0, import_fs.writeFileSync)(filePath, next, "utf-8");
2958
+ return true;
2914
2959
  }
2915
2960
  function toNumericScreens(screens) {
2916
2961
  const out = {};
@@ -2924,11 +2969,21 @@ function extractClassStrings(code) {
2924
2969
  const simpleRe = /(?:className|kb)=["']([^"']+)["']/g;
2925
2970
  let m;
2926
2971
  while ((m = simpleRe.exec(code)) !== null) pushTokens(m[1], found);
2927
- const exprLiteralRe = /(?:className|kb)=\{["'`]([^"'`}]+)["'`]\}/g;
2928
- while ((m = exprLiteralRe.exec(code)) !== null) pushTokens(m[1], found);
2929
- const ternaryBlockRe = /(?:className|kb)=\{([^}]{1,500})\}/g;
2930
- while ((m = ternaryBlockRe.exec(code)) !== null) {
2931
- const block = m[1];
2972
+ const jsxExprRe = /(?:className|kb)=\{/g;
2973
+ while ((m = jsxExprRe.exec(code)) !== null) {
2974
+ let depth = 1;
2975
+ let i = m.index + m[0].length;
2976
+ let block = "";
2977
+ while (i < code.length && depth > 0) {
2978
+ const ch = code[i];
2979
+ if (ch === "{") depth++;
2980
+ else if (ch === "}") {
2981
+ if (--depth === 0) break;
2982
+ }
2983
+ block += ch;
2984
+ i++;
2985
+ }
2986
+ jsxExprRe.lastIndex = i + 1;
2932
2987
  const innerStrings = block.match(/["'`]([^"'`]{1,300})["'`]/g) ?? [];
2933
2988
  for (const s of innerStrings) pushTokens(s.slice(1, -1), found);
2934
2989
  }
@@ -2951,28 +3006,37 @@ function pushTokens(str, into) {
2951
3006
  }
2952
3007
  }
2953
3008
  function scanDir(dir, onFile) {
3009
+ let entries;
2954
3010
  try {
2955
- for (const entry of (0, import_fs.readdirSync)(dir)) {
2956
- if (entry.startsWith(".") || entry === "node_modules") continue;
2957
- const full = (0, import_path.join)(dir, entry);
2958
- try {
2959
- const st = (0, import_fs.statSync)(full);
2960
- if (st.isDirectory()) scanDir(full, onFile);
2961
- else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(full, (0, import_fs.readFileSync)(full, "utf-8"));
2962
- } catch {
2963
- }
3011
+ entries = (0, import_fs.readdirSync)(dir);
3012
+ } catch (err) {
3013
+ console.warn(`[kbach] Cannot read directory ${dir}:`, err.message);
3014
+ return;
3015
+ }
3016
+ for (const entry of entries) {
3017
+ if (entry.startsWith(".") || entry === "node_modules") continue;
3018
+ const full = (0, import_path.join)(dir, entry);
3019
+ try {
3020
+ const st = (0, import_fs.statSync)(full);
3021
+ if (st.isDirectory()) scanDir(full, onFile);
3022
+ else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(full, (0, import_fs.readFileSync)(full, "utf-8"));
3023
+ } catch (err) {
3024
+ console.warn(`[kbach] Cannot process ${full}:`, err.message);
2964
3025
  }
2965
- } catch {
2966
3026
  }
2967
3027
  }
2968
3028
  function kbach(userConfig) {
2969
3029
  const cfg = buildConfig(userConfig ?? {});
2970
3030
  const screens = toNumericScreens(cfg.theme.screens ?? {});
3031
+ const screenKeys = Object.keys(cfg.theme.screens ?? {});
3032
+ const responsivePattern = screenKeys.length ? screenKeys.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|") : "sm|md|lg|xl|2xl";
3033
+ const responsiveRe = new RegExp(`^(${responsivePattern}):`);
2971
3034
  let root = process.cwd();
2972
3035
  let mainCSSFile = null;
2973
3036
  const fileTokens = /* @__PURE__ */ new Map();
2974
3037
  const tokenCSS = /* @__PURE__ */ new Map();
2975
3038
  function processFile(filePath, code) {
3039
+ const key = normPath(filePath);
2976
3040
  const tokens = /* @__PURE__ */ new Set();
2977
3041
  for (const cls of extractClassStrings(code)) {
2978
3042
  for (const tok of splitClassTokens(cls)) {
@@ -2982,7 +3046,7 @@ function kbach(userConfig) {
2982
3046
  }
2983
3047
  }
2984
3048
  }
2985
- fileTokens.set(filePath, tokens);
3049
+ fileTokens.set(key, tokens);
2986
3050
  }
2987
3051
  function buildTokenCSSView() {
2988
3052
  const active = /* @__PURE__ */ new Set();
@@ -3000,7 +3064,7 @@ function kbach(userConfig) {
3000
3064
  }
3001
3065
  }
3002
3066
  function generateCSS() {
3003
- return formatKbachCSS(buildTokenCSSView(), cfg.theme);
3067
+ return formatKbachCSS(buildTokenCSSView(), cfg.theme, responsiveRe);
3004
3068
  }
3005
3069
  return {
3006
3070
  name: "kbach",
@@ -3009,18 +3073,26 @@ function kbach(userConfig) {
3009
3073
  root = resolved.root;
3010
3074
  },
3011
3075
  buildStart() {
3012
- mainCSSFile = findMainCSSFile(root);
3076
+ mainCSSFile = findKbachCSS(root);
3013
3077
  initialScan();
3014
3078
  if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
3015
3079
  },
3016
- handleHotUpdate({ file }) {
3080
+ handleHotUpdate({ file, server }) {
3017
3081
  if (file.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(file)) return;
3018
3082
  try {
3019
3083
  processFile(file, (0, import_fs.readFileSync)(file, "utf-8"));
3020
3084
  } catch {
3021
- fileTokens.delete(file);
3085
+ fileTokens.delete(normPath(file));
3086
+ }
3087
+ const active = /* @__PURE__ */ new Set();
3088
+ for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
3089
+ for (const tok of tokenCSS.keys()) {
3090
+ if (!active.has(tok)) tokenCSS.delete(tok);
3091
+ }
3092
+ if (mainCSSFile) {
3093
+ const changed = writeKbachToFile(mainCSSFile, generateCSS());
3094
+ if (changed) server.watcher.emit("change", mainCSSFile);
3022
3095
  }
3023
- if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
3024
3096
  }
3025
3097
  };
3026
3098
  }
@@ -3,11 +3,15 @@ import {
3
3
  generateClassCSS,
4
4
  setCSSGenMode,
5
5
  splitClassTokens
6
- } from "./chunk-VCNP57PR.mjs";
6
+ } from "./chunk-IBAKFGFG.mjs";
7
7
 
8
8
  // src/vite-plugin.ts
9
- import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "fs";
9
+ import { readFileSync, writeFileSync, readdirSync, statSync } from "fs";
10
10
  import { join } from "path";
11
+ function normPath(p) {
12
+ const fwd = p.replace(/\\/g, "/");
13
+ return process.platform === "win32" ? fwd.toLowerCase() : fwd;
14
+ }
11
15
  setCSSGenMode(true);
12
16
  var KBACH_START = "/* kbach:start */";
13
17
  var KBACH_END = "/* kbach:end */";
@@ -21,9 +25,9 @@ var GROUPS = [
21
25
  ["Effects", /^(shadow|opacity|blur|brightness|contrast|grayscale|transition|duration|ease|delay|animate|scale|rotate|translate|transform|cursor|pointer|select-)/]
22
26
  ];
23
27
  var GROUP_ORDER = ["Layout", "Sizing", "Spacing", "Typography", "Colors", "Borders", "Effects", "Utilities", "Dark Mode", "Responsive"];
24
- function classifyToken(token) {
28
+ function classifyToken(token, responsiveRe) {
25
29
  if (/^dark:/.test(token)) return "Dark Mode";
26
- if (/^(sm|md|lg|xl|2xl):/.test(token)) return "Responsive";
30
+ if (responsiveRe.test(token)) return "Responsive";
27
31
  const base = token.replace(/^(hover|focus|active|group-hover|peer-hover):/, "");
28
32
  for (const [label, re] of GROUPS) {
29
33
  if (re.test(base)) return label;
@@ -48,7 +52,7 @@ function buildColorVarMap(theme, usedCSS) {
48
52
  }
49
53
  return map;
50
54
  }
51
- function formatKbachCSS(tokenCSS, theme) {
55
+ function formatKbachCSS(tokenCSS, theme, responsiveRe) {
52
56
  const allRaw = [...tokenCSS.values()].filter(Boolean).join("\n");
53
57
  const colorVars = buildColorVarMap(theme, allRaw);
54
58
  function applyVars(css) {
@@ -59,7 +63,7 @@ function formatKbachCSS(tokenCSS, theme) {
59
63
  const groups = new Map(GROUP_ORDER.map((g) => [g, []]));
60
64
  for (const [token, css] of tokenCSS) {
61
65
  if (!css) continue;
62
- const label = classifyToken(token);
66
+ const label = classifyToken(token, responsiveRe);
63
67
  if (!groups.has(label)) groups.set(label, []);
64
68
  groups.get(label).push(applyVars(css));
65
69
  }
@@ -88,16 +92,50 @@ function buildThemeGlobal(theme) {
88
92
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
89
93
  return `body { font-family: ${family}; }`;
90
94
  }
91
- function findMainCSSFile(root) {
92
- for (const c of [
93
- join(root, "app", "app.css"),
94
- join(root, "src", "index.css"),
95
- join(root, "src", "app.css"),
96
- join(root, "app", "index.css")
97
- ]) {
98
- if (existsSync(c)) return c;
99
- }
100
- return null;
95
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
96
+ "node_modules",
97
+ "dist",
98
+ "build",
99
+ "out",
100
+ ".next",
101
+ ".nuxt",
102
+ ".output",
103
+ ".git",
104
+ ".svn",
105
+ ".cache",
106
+ "coverage",
107
+ "__pycache__"
108
+ ]);
109
+ function findKbachCSS(root) {
110
+ const found = [];
111
+ function scan(dir, depth) {
112
+ if (depth > 10) return;
113
+ let entries;
114
+ try {
115
+ entries = readdirSync(dir);
116
+ } catch {
117
+ return;
118
+ }
119
+ for (const entry of entries) {
120
+ if (entry.startsWith(".") || SKIP_DIRS.has(entry)) continue;
121
+ const full = join(dir, entry);
122
+ try {
123
+ const st = statSync(full);
124
+ if (st.isDirectory()) scan(full, depth + 1);
125
+ else if (entry === "kbach.css") found.push(full);
126
+ } catch {
127
+ }
128
+ }
129
+ }
130
+ scan(root, 0);
131
+ if (found.length === 0) return null;
132
+ found.sort((a, b) => a.split(/[\\/]/g).length - b.split(/[\\/]/g).length);
133
+ if (found.length > 1) {
134
+ console.warn(
135
+ "[kbach] Multiple kbach.css files found \u2014 only the shallowest will receive generated styles.\n Remove duplicates to avoid confusion:\n" + found.map((f) => ` ${f}`).join("\n")
136
+ );
137
+ }
138
+ return found[0];
101
139
  }
102
140
  function writeKbachToFile(filePath, css) {
103
141
  let existing = "";
@@ -113,7 +151,9 @@ ${KBACH_END}`;
113
151
  const next = start !== -1 && end !== -1 ? existing.slice(0, start) + block + existing.slice(end + KBACH_END.length) : existing ? `${existing}
114
152
 
115
153
  ${block}` : block;
116
- if (next !== existing) writeFileSync(filePath, next, "utf-8");
154
+ if (next === existing) return false;
155
+ writeFileSync(filePath, next, "utf-8");
156
+ return true;
117
157
  }
118
158
  function toNumericScreens(screens) {
119
159
  const out = {};
@@ -127,11 +167,21 @@ function extractClassStrings(code) {
127
167
  const simpleRe = /(?:className|kb)=["']([^"']+)["']/g;
128
168
  let m;
129
169
  while ((m = simpleRe.exec(code)) !== null) pushTokens(m[1], found);
130
- const exprLiteralRe = /(?:className|kb)=\{["'`]([^"'`}]+)["'`]\}/g;
131
- while ((m = exprLiteralRe.exec(code)) !== null) pushTokens(m[1], found);
132
- const ternaryBlockRe = /(?:className|kb)=\{([^}]{1,500})\}/g;
133
- while ((m = ternaryBlockRe.exec(code)) !== null) {
134
- const block = m[1];
170
+ const jsxExprRe = /(?:className|kb)=\{/g;
171
+ while ((m = jsxExprRe.exec(code)) !== null) {
172
+ let depth = 1;
173
+ let i = m.index + m[0].length;
174
+ let block = "";
175
+ while (i < code.length && depth > 0) {
176
+ const ch = code[i];
177
+ if (ch === "{") depth++;
178
+ else if (ch === "}") {
179
+ if (--depth === 0) break;
180
+ }
181
+ block += ch;
182
+ i++;
183
+ }
184
+ jsxExprRe.lastIndex = i + 1;
135
185
  const innerStrings = block.match(/["'`]([^"'`]{1,300})["'`]/g) ?? [];
136
186
  for (const s of innerStrings) pushTokens(s.slice(1, -1), found);
137
187
  }
@@ -154,28 +204,37 @@ function pushTokens(str, into) {
154
204
  }
155
205
  }
156
206
  function scanDir(dir, onFile) {
207
+ let entries;
157
208
  try {
158
- for (const entry of readdirSync(dir)) {
159
- if (entry.startsWith(".") || entry === "node_modules") continue;
160
- const full = join(dir, entry);
161
- try {
162
- const st = statSync(full);
163
- if (st.isDirectory()) scanDir(full, onFile);
164
- else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(full, readFileSync(full, "utf-8"));
165
- } catch {
166
- }
209
+ entries = readdirSync(dir);
210
+ } catch (err) {
211
+ console.warn(`[kbach] Cannot read directory ${dir}:`, err.message);
212
+ return;
213
+ }
214
+ for (const entry of entries) {
215
+ if (entry.startsWith(".") || entry === "node_modules") continue;
216
+ const full = join(dir, entry);
217
+ try {
218
+ const st = statSync(full);
219
+ if (st.isDirectory()) scanDir(full, onFile);
220
+ else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(full, readFileSync(full, "utf-8"));
221
+ } catch (err) {
222
+ console.warn(`[kbach] Cannot process ${full}:`, err.message);
167
223
  }
168
- } catch {
169
224
  }
170
225
  }
171
226
  function kbach(userConfig) {
172
227
  const cfg = buildConfig(userConfig ?? {});
173
228
  const screens = toNumericScreens(cfg.theme.screens ?? {});
229
+ const screenKeys = Object.keys(cfg.theme.screens ?? {});
230
+ const responsivePattern = screenKeys.length ? screenKeys.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|") : "sm|md|lg|xl|2xl";
231
+ const responsiveRe = new RegExp(`^(${responsivePattern}):`);
174
232
  let root = process.cwd();
175
233
  let mainCSSFile = null;
176
234
  const fileTokens = /* @__PURE__ */ new Map();
177
235
  const tokenCSS = /* @__PURE__ */ new Map();
178
236
  function processFile(filePath, code) {
237
+ const key = normPath(filePath);
179
238
  const tokens = /* @__PURE__ */ new Set();
180
239
  for (const cls of extractClassStrings(code)) {
181
240
  for (const tok of splitClassTokens(cls)) {
@@ -185,7 +244,7 @@ function kbach(userConfig) {
185
244
  }
186
245
  }
187
246
  }
188
- fileTokens.set(filePath, tokens);
247
+ fileTokens.set(key, tokens);
189
248
  }
190
249
  function buildTokenCSSView() {
191
250
  const active = /* @__PURE__ */ new Set();
@@ -203,7 +262,7 @@ function kbach(userConfig) {
203
262
  }
204
263
  }
205
264
  function generateCSS() {
206
- return formatKbachCSS(buildTokenCSSView(), cfg.theme);
265
+ return formatKbachCSS(buildTokenCSSView(), cfg.theme, responsiveRe);
207
266
  }
208
267
  return {
209
268
  name: "kbach",
@@ -212,18 +271,26 @@ function kbach(userConfig) {
212
271
  root = resolved.root;
213
272
  },
214
273
  buildStart() {
215
- mainCSSFile = findMainCSSFile(root);
274
+ mainCSSFile = findKbachCSS(root);
216
275
  initialScan();
217
276
  if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
218
277
  },
219
- handleHotUpdate({ file }) {
278
+ handleHotUpdate({ file, server }) {
220
279
  if (file.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(file)) return;
221
280
  try {
222
281
  processFile(file, readFileSync(file, "utf-8"));
223
282
  } catch {
224
- fileTokens.delete(file);
283
+ fileTokens.delete(normPath(file));
284
+ }
285
+ const active = /* @__PURE__ */ new Set();
286
+ for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
287
+ for (const tok of tokenCSS.keys()) {
288
+ if (!active.has(tok)) tokenCSS.delete(tok);
289
+ }
290
+ if (mainCSSFile) {
291
+ const changed = writeKbachToFile(mainCSSFile, generateCSS());
292
+ if (changed) server.watcher.emit("change", mainCSSFile);
225
293
  }
226
- if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
227
294
  }
228
295
  };
229
296
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.5.41",
3
+ "version": "0.5.43",
4
4
  "description": "React / React Native components and hooks for the Kbach framework",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./dist/index.js",
@@ -54,7 +54,7 @@
54
54
  "vitest": "^1.0.0"
55
55
  },
56
56
  "peerDependencies": {
57
- "react": ">=17.0.0",
57
+ "react": ">=18.0.0",
58
58
  "react-native": ">=0.70.0",
59
59
  "vite": ">=4.0.0"
60
60
  },