@kbach/react 0.5.42 → 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) {
@@ -2822,9 +2827,9 @@ var GROUPS = [
2822
2827
  ["Effects", /^(shadow|opacity|blur|brightness|contrast|grayscale|transition|duration|ease|delay|animate|scale|rotate|translate|transform|cursor|pointer|select-)/]
2823
2828
  ];
2824
2829
  var GROUP_ORDER = ["Layout", "Sizing", "Spacing", "Typography", "Colors", "Borders", "Effects", "Utilities", "Dark Mode", "Responsive"];
2825
- function classifyToken(token) {
2830
+ function classifyToken(token, responsiveRe) {
2826
2831
  if (/^dark:/.test(token)) return "Dark Mode";
2827
- if (/^(sm|md|lg|xl|2xl):/.test(token)) return "Responsive";
2832
+ if (responsiveRe.test(token)) return "Responsive";
2828
2833
  const base = token.replace(/^(hover|focus|active|group-hover|peer-hover):/, "");
2829
2834
  for (const [label, re] of GROUPS) {
2830
2835
  if (re.test(base)) return label;
@@ -2849,7 +2854,7 @@ function buildColorVarMap(theme, usedCSS) {
2849
2854
  }
2850
2855
  return map;
2851
2856
  }
2852
- function formatKbachCSS(tokenCSS, theme) {
2857
+ function formatKbachCSS(tokenCSS, theme, responsiveRe) {
2853
2858
  const allRaw = [...tokenCSS.values()].filter(Boolean).join("\n");
2854
2859
  const colorVars = buildColorVarMap(theme, allRaw);
2855
2860
  function applyVars(css) {
@@ -2860,7 +2865,7 @@ function formatKbachCSS(tokenCSS, theme) {
2860
2865
  const groups = new Map(GROUP_ORDER.map((g) => [g, []]));
2861
2866
  for (const [token, css] of tokenCSS) {
2862
2867
  if (!css) continue;
2863
- const label = classifyToken(token);
2868
+ const label = classifyToken(token, responsiveRe);
2864
2869
  if (!groups.has(label)) groups.set(label, []);
2865
2870
  groups.get(label).push(applyVars(css));
2866
2871
  }
@@ -2889,16 +2894,50 @@ function buildThemeGlobal(theme) {
2889
2894
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
2890
2895
  return `body { font-family: ${family}; }`;
2891
2896
  }
2892
- function findMainCSSFile(root) {
2893
- for (const c of [
2894
- (0, import_path.join)(root, "app", "app.css"),
2895
- (0, import_path.join)(root, "src", "index.css"),
2896
- (0, import_path.join)(root, "src", "app.css"),
2897
- (0, import_path.join)(root, "app", "index.css")
2898
- ]) {
2899
- 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
+ }
2900
2931
  }
2901
- 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];
2902
2941
  }
2903
2942
  function writeKbachToFile(filePath, css) {
2904
2943
  let existing = "";
@@ -2914,7 +2953,9 @@ ${KBACH_END}`;
2914
2953
  const next = start !== -1 && end !== -1 ? existing.slice(0, start) + block + existing.slice(end + KBACH_END.length) : existing ? `${existing}
2915
2954
 
2916
2955
  ${block}` : block;
2917
- 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;
2918
2959
  }
2919
2960
  function toNumericScreens(screens) {
2920
2961
  const out = {};
@@ -2928,11 +2969,21 @@ function extractClassStrings(code) {
2928
2969
  const simpleRe = /(?:className|kb)=["']([^"']+)["']/g;
2929
2970
  let m;
2930
2971
  while ((m = simpleRe.exec(code)) !== null) pushTokens(m[1], found);
2931
- const exprLiteralRe = /(?:className|kb)=\{["'`]([^"'`}]+)["'`]\}/g;
2932
- while ((m = exprLiteralRe.exec(code)) !== null) pushTokens(m[1], found);
2933
- const ternaryBlockRe = /(?:className|kb)=\{([^}]{1,500})\}/g;
2934
- while ((m = ternaryBlockRe.exec(code)) !== null) {
2935
- 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;
2936
2987
  const innerStrings = block.match(/["'`]([^"'`]{1,300})["'`]/g) ?? [];
2937
2988
  for (const s of innerStrings) pushTokens(s.slice(1, -1), found);
2938
2989
  }
@@ -2955,23 +3006,31 @@ function pushTokens(str, into) {
2955
3006
  }
2956
3007
  }
2957
3008
  function scanDir(dir, onFile) {
3009
+ let entries;
2958
3010
  try {
2959
- for (const entry of (0, import_fs.readdirSync)(dir)) {
2960
- if (entry.startsWith(".") || entry === "node_modules") continue;
2961
- const full = (0, import_path.join)(dir, entry);
2962
- try {
2963
- const st = (0, import_fs.statSync)(full);
2964
- if (st.isDirectory()) scanDir(full, onFile);
2965
- else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(full, (0, import_fs.readFileSync)(full, "utf-8"));
2966
- } catch {
2967
- }
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);
2968
3025
  }
2969
- } catch {
2970
3026
  }
2971
3027
  }
2972
3028
  function kbach(userConfig) {
2973
3029
  const cfg = buildConfig(userConfig ?? {});
2974
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}):`);
2975
3034
  let root = process.cwd();
2976
3035
  let mainCSSFile = null;
2977
3036
  const fileTokens = /* @__PURE__ */ new Map();
@@ -3005,7 +3064,7 @@ function kbach(userConfig) {
3005
3064
  }
3006
3065
  }
3007
3066
  function generateCSS() {
3008
- return formatKbachCSS(buildTokenCSSView(), cfg.theme);
3067
+ return formatKbachCSS(buildTokenCSSView(), cfg.theme, responsiveRe);
3009
3068
  }
3010
3069
  return {
3011
3070
  name: "kbach",
@@ -3014,18 +3073,26 @@ function kbach(userConfig) {
3014
3073
  root = resolved.root;
3015
3074
  },
3016
3075
  buildStart() {
3017
- mainCSSFile = findMainCSSFile(root);
3076
+ mainCSSFile = findKbachCSS(root);
3018
3077
  initialScan();
3019
3078
  if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
3020
3079
  },
3021
- handleHotUpdate({ file }) {
3080
+ handleHotUpdate({ file, server }) {
3022
3081
  if (file.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(file)) return;
3023
3082
  try {
3024
3083
  processFile(file, (0, import_fs.readFileSync)(file, "utf-8"));
3025
3084
  } catch {
3026
3085
  fileTokens.delete(normPath(file));
3027
3086
  }
3028
- if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
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);
3095
+ }
3029
3096
  }
3030
3097
  };
3031
3098
  }
@@ -3,10 +3,10 @@ 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
11
  function normPath(p) {
12
12
  const fwd = p.replace(/\\/g, "/");
@@ -25,9 +25,9 @@ var GROUPS = [
25
25
  ["Effects", /^(shadow|opacity|blur|brightness|contrast|grayscale|transition|duration|ease|delay|animate|scale|rotate|translate|transform|cursor|pointer|select-)/]
26
26
  ];
27
27
  var GROUP_ORDER = ["Layout", "Sizing", "Spacing", "Typography", "Colors", "Borders", "Effects", "Utilities", "Dark Mode", "Responsive"];
28
- function classifyToken(token) {
28
+ function classifyToken(token, responsiveRe) {
29
29
  if (/^dark:/.test(token)) return "Dark Mode";
30
- if (/^(sm|md|lg|xl|2xl):/.test(token)) return "Responsive";
30
+ if (responsiveRe.test(token)) return "Responsive";
31
31
  const base = token.replace(/^(hover|focus|active|group-hover|peer-hover):/, "");
32
32
  for (const [label, re] of GROUPS) {
33
33
  if (re.test(base)) return label;
@@ -52,7 +52,7 @@ function buildColorVarMap(theme, usedCSS) {
52
52
  }
53
53
  return map;
54
54
  }
55
- function formatKbachCSS(tokenCSS, theme) {
55
+ function formatKbachCSS(tokenCSS, theme, responsiveRe) {
56
56
  const allRaw = [...tokenCSS.values()].filter(Boolean).join("\n");
57
57
  const colorVars = buildColorVarMap(theme, allRaw);
58
58
  function applyVars(css) {
@@ -63,7 +63,7 @@ function formatKbachCSS(tokenCSS, theme) {
63
63
  const groups = new Map(GROUP_ORDER.map((g) => [g, []]));
64
64
  for (const [token, css] of tokenCSS) {
65
65
  if (!css) continue;
66
- const label = classifyToken(token);
66
+ const label = classifyToken(token, responsiveRe);
67
67
  if (!groups.has(label)) groups.set(label, []);
68
68
  groups.get(label).push(applyVars(css));
69
69
  }
@@ -92,16 +92,50 @@ function buildThemeGlobal(theme) {
92
92
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
93
93
  return `body { font-family: ${family}; }`;
94
94
  }
95
- function findMainCSSFile(root) {
96
- for (const c of [
97
- join(root, "app", "app.css"),
98
- join(root, "src", "index.css"),
99
- join(root, "src", "app.css"),
100
- join(root, "app", "index.css")
101
- ]) {
102
- if (existsSync(c)) return c;
103
- }
104
- 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];
105
139
  }
106
140
  function writeKbachToFile(filePath, css) {
107
141
  let existing = "";
@@ -117,7 +151,9 @@ ${KBACH_END}`;
117
151
  const next = start !== -1 && end !== -1 ? existing.slice(0, start) + block + existing.slice(end + KBACH_END.length) : existing ? `${existing}
118
152
 
119
153
  ${block}` : block;
120
- if (next !== existing) writeFileSync(filePath, next, "utf-8");
154
+ if (next === existing) return false;
155
+ writeFileSync(filePath, next, "utf-8");
156
+ return true;
121
157
  }
122
158
  function toNumericScreens(screens) {
123
159
  const out = {};
@@ -131,11 +167,21 @@ function extractClassStrings(code) {
131
167
  const simpleRe = /(?:className|kb)=["']([^"']+)["']/g;
132
168
  let m;
133
169
  while ((m = simpleRe.exec(code)) !== null) pushTokens(m[1], found);
134
- const exprLiteralRe = /(?:className|kb)=\{["'`]([^"'`}]+)["'`]\}/g;
135
- while ((m = exprLiteralRe.exec(code)) !== null) pushTokens(m[1], found);
136
- const ternaryBlockRe = /(?:className|kb)=\{([^}]{1,500})\}/g;
137
- while ((m = ternaryBlockRe.exec(code)) !== null) {
138
- 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;
139
185
  const innerStrings = block.match(/["'`]([^"'`]{1,300})["'`]/g) ?? [];
140
186
  for (const s of innerStrings) pushTokens(s.slice(1, -1), found);
141
187
  }
@@ -158,23 +204,31 @@ function pushTokens(str, into) {
158
204
  }
159
205
  }
160
206
  function scanDir(dir, onFile) {
207
+ let entries;
161
208
  try {
162
- for (const entry of readdirSync(dir)) {
163
- if (entry.startsWith(".") || entry === "node_modules") continue;
164
- const full = join(dir, entry);
165
- try {
166
- const st = statSync(full);
167
- if (st.isDirectory()) scanDir(full, onFile);
168
- else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(full, readFileSync(full, "utf-8"));
169
- } catch {
170
- }
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);
171
223
  }
172
- } catch {
173
224
  }
174
225
  }
175
226
  function kbach(userConfig) {
176
227
  const cfg = buildConfig(userConfig ?? {});
177
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}):`);
178
232
  let root = process.cwd();
179
233
  let mainCSSFile = null;
180
234
  const fileTokens = /* @__PURE__ */ new Map();
@@ -208,7 +262,7 @@ function kbach(userConfig) {
208
262
  }
209
263
  }
210
264
  function generateCSS() {
211
- return formatKbachCSS(buildTokenCSSView(), cfg.theme);
265
+ return formatKbachCSS(buildTokenCSSView(), cfg.theme, responsiveRe);
212
266
  }
213
267
  return {
214
268
  name: "kbach",
@@ -217,18 +271,26 @@ function kbach(userConfig) {
217
271
  root = resolved.root;
218
272
  },
219
273
  buildStart() {
220
- mainCSSFile = findMainCSSFile(root);
274
+ mainCSSFile = findKbachCSS(root);
221
275
  initialScan();
222
276
  if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
223
277
  },
224
- handleHotUpdate({ file }) {
278
+ handleHotUpdate({ file, server }) {
225
279
  if (file.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(file)) return;
226
280
  try {
227
281
  processFile(file, readFileSync(file, "utf-8"));
228
282
  } catch {
229
283
  fileTokens.delete(normPath(file));
230
284
  }
231
- if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
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);
293
+ }
232
294
  }
233
295
  };
234
296
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.5.42",
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
  },