@kbach/react 0.5.56 → 0.5.58

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.
@@ -44,7 +44,7 @@ interface ModifierDef {
44
44
 
45
45
  type ThemeMode = 'light' | 'dark' | 'system';
46
46
  interface StyleValue {
47
- [key: string]: string | number | undefined | null | StyleValue | unknown[];
47
+ [key: string]: string | number | undefined | null | StyleValue | StyleValue[];
48
48
  }
49
49
  interface ParsedClass {
50
50
  /** The original class string, e.g. "dark:hover:bg-[#fff]" */
@@ -83,11 +83,16 @@ interface ThemeConfig {
83
83
  lineHeight: Record<string, number | string>;
84
84
  letterSpacing: Record<string, number | string>;
85
85
  zIndex: Record<string, number | string>;
86
- flex: Record<string, number>;
86
+ flex: Record<string, number | string>;
87
87
  shadow: Record<string, StyleValue>;
88
88
  screens: Record<string, string | number>;
89
89
  [key: string]: unknown;
90
90
  }
91
+ /**
92
+ * 'class' — toggles .dark class on <html>
93
+ * 'media' — uses prefers-color-scheme media query
94
+ * 'attribute' — uses data-theme="dark" attribute on <html>
95
+ */
91
96
  type DarkMode = 'attribute' | 'class' | 'media';
92
97
  interface PluginAPI {
93
98
  addUtility(name: string, styles: StyleValue): void;
@@ -1,6 +1,11 @@
1
1
  import { Plugin } from 'vite';
2
- import { F as FrameworkConfig } from './types-D-24rbXu.mjs';
2
+ import { F as FrameworkConfig } from './types-BESBQANS.mjs';
3
3
 
4
- declare function kbach(userConfig?: FrameworkConfig): Plugin;
4
+ interface KbachPluginOptions {
5
+ framework?: FrameworkConfig;
6
+ /** Directories to scan for class strings (relative to Vite root). Defaults to common source dirs. */
7
+ include?: string[];
8
+ }
9
+ declare function kbach(userConfigOrOptions?: FrameworkConfig | KbachPluginOptions): Plugin;
5
10
 
6
- export { kbach };
11
+ export { type KbachPluginOptions, kbach };
@@ -1,6 +1,11 @@
1
1
  import { Plugin } from 'vite';
2
- import { F as FrameworkConfig } from './types-D-24rbXu.js';
2
+ import { F as FrameworkConfig } from './types-BESBQANS.js';
3
3
 
4
- declare function kbach(userConfig?: FrameworkConfig): Plugin;
4
+ interface KbachPluginOptions {
5
+ framework?: FrameworkConfig;
6
+ /** Directories to scan for class strings (relative to Vite root). Defaults to common source dirs. */
7
+ include?: string[];
8
+ }
9
+ declare function kbach(userConfigOrOptions?: FrameworkConfig | KbachPluginOptions): Plugin;
5
10
 
6
- export { kbach };
11
+ export { type KbachPluginOptions, kbach };
@@ -28,7 +28,7 @@ var import_path = require("path");
28
28
 
29
29
  // src/core/platform.ts
30
30
  var isWeb = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
31
- var isNative = !isWeb && (typeof globalThis.HermesInternal !== "undefined" || typeof globalThis.__fbBatchedBridge !== "undefined" || typeof navigator !== "undefined" && navigator.product === "ReactNative");
31
+ var isNative = !isWeb && (typeof globalThis.HermesInternal !== "undefined" || typeof globalThis.__fbBatchedBridge !== "undefined" || typeof navigator !== "undefined" && navigator.product === "ReactNative" || typeof globalThis.__REACT_NATIVE__ !== "undefined");
32
32
  var _cssGenMode = false;
33
33
  function setCSSGenMode(on) {
34
34
  _cssGenMode = on;
@@ -44,7 +44,7 @@ function toNativeValue(raw) {
44
44
  return raw;
45
45
  }
46
46
  function escapeCSSSelector(cls) {
47
- return cls.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
47
+ return cls.replace(/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
48
48
  }
49
49
 
50
50
  // src/core/reset.ts
@@ -504,14 +504,15 @@ var defaultTheme = {
504
504
  normal: 1.5,
505
505
  relaxed: 1.625,
506
506
  loose: 2,
507
- 3: 12,
508
- 4: 16,
509
- 5: 20,
510
- 6: 24,
511
- 7: 28,
512
- 8: 32,
513
- 9: 36,
514
- 10: 40
507
+ // Pixel values stored as strings; styleValueToCSS passes them through, toNativeValue strips the unit
508
+ 3: "12px",
509
+ 4: "16px",
510
+ 5: "20px",
511
+ 6: "24px",
512
+ 7: "28px",
513
+ 8: "32px",
514
+ 9: "36px",
515
+ 10: "40px"
515
516
  },
516
517
  letterSpacing: {
517
518
  tighter: -0.8,
@@ -532,9 +533,12 @@ var defaultTheme = {
532
533
  },
533
534
  flex: {
534
535
  1: 1,
535
- auto: 1,
536
- initial: 0,
537
- none: 0
536
+ auto: "auto",
537
+ // CSS: flex: auto = 1 1 auto; native: mapped to 1 in resolver
538
+ initial: "initial",
539
+ // CSS: flex: initial = 0 1 auto; native: mapped to 1
540
+ none: "none"
541
+ // CSS: flex: none = 0 0 auto; native: mapped to 0
538
542
  },
539
543
  shadow: {
540
544
  sm: {
@@ -643,7 +647,7 @@ function resolveColor(value, colors, isArbitrary) {
643
647
  }
644
648
  if (!hex) return null;
645
649
  if (!opacityPart) {
646
- if (!isWeb && hex.startsWith("#") && hex.length === 9) {
650
+ if (!getEffectiveIsWeb() && hex.startsWith("#") && hex.length === 9) {
647
651
  return hexToRgba(hex, Math.round(parseInt(hex.slice(7, 9), 16) / 255 * 1e3) / 1e3);
648
652
  }
649
653
  return hex;
@@ -677,7 +681,10 @@ function resolveSpacing(value, negative, spacing, isArbitrary) {
677
681
  const resolved = getEffectiveIsWeb() ? value : toNativeValue(value);
678
682
  if (negative) {
679
683
  if (typeof resolved === "number") return -resolved;
680
- if (typeof resolved === "string" && /^\d/.test(resolved)) return `-${resolved}`;
684
+ if (typeof resolved === "string") {
685
+ if (/^\d/.test(resolved)) return `-${resolved}`;
686
+ if (/^(calc|var|min|max|clamp|env)\s*\(/.test(resolved)) return `calc(-1 * (${resolved}))`;
687
+ }
681
688
  }
682
689
  return resolved;
683
690
  }
@@ -1183,7 +1190,11 @@ var RESOLVERS = {
1183
1190
  const color = resolveColor(value, colors, false);
1184
1191
  if (color) return { borderColor: color };
1185
1192
  const w = borderWidth[value] ?? spacing[value];
1186
- if (w !== void 0) return { borderWidth: typeof w === "number" ? w : parseFloat(String(w)) };
1193
+ if (w !== void 0) {
1194
+ const numW = typeof w === "number" ? w : parseFloat(String(w));
1195
+ if (isNaN(numW)) return null;
1196
+ return { borderWidth: numW };
1197
+ }
1187
1198
  return null;
1188
1199
  },
1189
1200
  "border-t": ({ value, isArbitrary }, { colors, borderWidth }) => {
@@ -1886,7 +1897,7 @@ var RESOLVERS = {
1886
1897
  },
1887
1898
  // ── Tint color (native-only, for Image and icon components) ──────────────
1888
1899
  tint: ({ value, isArbitrary }, { colors }) => {
1889
- if (isWeb) return null;
1900
+ if (getEffectiveIsWeb()) return null;
1890
1901
  const color = resolveColor(value, colors, isArbitrary);
1891
1902
  return color ? { tintColor: color } : null;
1892
1903
  },
@@ -1958,7 +1969,15 @@ var RESOLVERS = {
1958
1969
  const v = parseFloat(value);
1959
1970
  return isNaN(v) ? null : { flex: v };
1960
1971
  }
1961
- if (value in flex) return { flex: flex[value] };
1972
+ if (value in flex) {
1973
+ const v = flex[value];
1974
+ if (typeof v === "string" && !getEffectiveIsWeb()) {
1975
+ if (v === "auto" || v === "initial") return { flex: 1 };
1976
+ if (v === "none") return { flex: 0 };
1977
+ return null;
1978
+ }
1979
+ return { flex: v };
1980
+ }
1962
1981
  const n = parseFloat(value);
1963
1982
  return isNaN(n) ? null : { flex: n };
1964
1983
  },
@@ -2061,7 +2080,9 @@ var RESOLVERS = {
2061
2080
  leading: ({ value, isArbitrary }, { lineHeight }) => {
2062
2081
  if (isArbitrary) return { lineHeight: getEffectiveIsWeb() ? value : toNativeValue(value) };
2063
2082
  const v = lineHeight[value];
2064
- return v !== void 0 ? { lineHeight: v } : null;
2083
+ if (v === void 0) return null;
2084
+ if (typeof v === "string" && !getEffectiveIsWeb()) return { lineHeight: toNativeValue(v) };
2085
+ return { lineHeight: v };
2065
2086
  },
2066
2087
  // ── Letter spacing ─────────────────────────────────────────────────────────
2067
2088
  tracking: ({ value, isArbitrary }, { letterSpacing }) => {
@@ -2137,7 +2158,7 @@ var RESOLVERS = {
2137
2158
  transition: ({ value }) => {
2138
2159
  if (!getEffectiveIsWeb()) return null;
2139
2160
  const presets = {
2140
- "": "color 150ms, background-color 150ms, border-color 150ms, opacity 150ms, box-shadow 150ms",
2161
+ "": "color 150ms, background-color 150ms, border-color 150ms, text-decoration-color 150ms, fill 150ms, stroke 150ms, opacity 150ms, box-shadow 150ms, transform 150ms, filter 150ms, backdrop-filter 150ms",
2141
2162
  all: "all 150ms",
2142
2163
  none: "none",
2143
2164
  colors: "color 150ms, background-color 150ms, border-color 150ms",
@@ -2150,11 +2171,13 @@ var RESOLVERS = {
2150
2171
  },
2151
2172
  duration: ({ value, isArbitrary }) => {
2152
2173
  if (!getEffectiveIsWeb()) return null;
2174
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2153
2175
  const ms = isArbitrary ? value : `${value}ms`;
2154
2176
  return { transitionDuration: ms };
2155
2177
  },
2156
2178
  delay: ({ value, isArbitrary }) => {
2157
2179
  if (!getEffectiveIsWeb()) return null;
2180
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2158
2181
  const ms = isArbitrary ? value : `${value}ms`;
2159
2182
  return { transitionDelay: ms };
2160
2183
  },
@@ -2197,14 +2220,20 @@ var RESOLVERS = {
2197
2220
  boxShadow: offsetWidth === 0 ? `0 0 0 ${DEFAULT_RING_WIDTH}px ${DEFAULT_RING_COLOR}` : `0 0 0 ${offsetWidth}px #fff, 0 0 0 ${offsetWidth + DEFAULT_RING_WIDTH}px ${DEFAULT_RING_COLOR}`
2198
2221
  };
2199
2222
  },
2200
- // ── Space between (uses CSS gap or margin fallback) ────────────────────────
2223
+ // ── Space between ─────────────────────────────────────────────────────────
2224
+ // On web: emits __spaceX/__spaceY markers → resolver generates > * + * CSS rules.
2225
+ // On native (RN 0.71+): uses columnGap/rowGap directly.
2201
2226
  "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
2202
2227
  const v = resolveSpacing(value, negative, spacing, isArbitrary);
2203
- return v !== null ? { columnGap: v } : null;
2228
+ if (v === null) return null;
2229
+ if (getEffectiveIsWeb()) return { __spaceX: v };
2230
+ return { columnGap: v };
2204
2231
  },
2205
2232
  "space-y": ({ value, negative, isArbitrary }, { spacing }) => {
2206
2233
  const v = resolveSpacing(value, negative, spacing, isArbitrary);
2207
- return v !== null ? { rowGap: v } : null;
2234
+ if (v === null) return null;
2235
+ if (getEffectiveIsWeb()) return { __spaceY: v };
2236
+ return { rowGap: v };
2208
2237
  }
2209
2238
  };
2210
2239
  function resolveUtility(parsed, theme) {
@@ -2317,6 +2346,10 @@ function _invalidate() {
2317
2346
  _responsiveNames = null;
2318
2347
  }
2319
2348
  function registerModifier(name, def) {
2349
+ if (process.env.NODE_ENV !== "production" && name in BUILTIN_MODIFIERS) {
2350
+ console.warn(`[kbach] registerModifier: "${name}" is a built-in modifier and cannot be overridden. Use a different name.`);
2351
+ return;
2352
+ }
2320
2353
  _pluginModifiers[name] = def;
2321
2354
  _invalidate();
2322
2355
  }
@@ -2355,12 +2388,28 @@ function parseClass(className) {
2355
2388
  negative = true;
2356
2389
  remaining = remaining.slice(1);
2357
2390
  }
2358
- const bracketStart = remaining.lastIndexOf("-[");
2359
- if (bracketStart > 0 && remaining.endsWith("]")) {
2360
- const utility = remaining.slice(0, bracketStart);
2361
- const value = remaining.slice(bracketStart + 2, -1).replace(/_/g, " ");
2391
+ let bracketDepth = 0;
2392
+ let bracketStart = -1;
2393
+ for (let i = remaining.length - 1; i >= 0; i--) {
2394
+ if (remaining[i] === "]") bracketDepth++;
2395
+ else if (remaining[i] === "[") {
2396
+ bracketDepth--;
2397
+ if (bracketDepth === 0) {
2398
+ bracketStart = i;
2399
+ break;
2400
+ }
2401
+ }
2402
+ }
2403
+ if (bracketStart > 0 && remaining[bracketStart - 1] === "-" && remaining.endsWith("]")) {
2404
+ const utility = remaining.slice(0, bracketStart - 1);
2405
+ const value = remaining.slice(bracketStart + 1, -1).replace(/_/g, " ");
2362
2406
  return { original: trimmed, modifiers, negative, important, utility, value, isArbitrary: true };
2363
2407
  }
2408
+ if (remaining.endsWith("]") && bracketStart === -1) {
2409
+ if (process.env.NODE_ENV !== "production") {
2410
+ console.warn(`[kbach] Malformed class string: "${trimmed}" \u2014 unbalanced brackets`);
2411
+ }
2412
+ }
2364
2413
  if (remaining.startsWith("[") && remaining.endsWith("]")) {
2365
2414
  const value = remaining.slice(1, -1).replace(/_/g, " ");
2366
2415
  return { original: trimmed, modifiers, negative, important, utility: "", value, isArbitrary: true };
@@ -2395,6 +2444,7 @@ function splitClassTokens(classString) {
2395
2444
  const tokens = [];
2396
2445
  let current = "";
2397
2446
  let depth = 0;
2447
+ let parenDepth = 0;
2398
2448
  for (let i = 0; i < classString.length; i++) {
2399
2449
  const ch = classString[i];
2400
2450
  if (ch === "[") {
@@ -2407,8 +2457,18 @@ function splitClassTokens(classString) {
2407
2457
  current += ch;
2408
2458
  continue;
2409
2459
  }
2460
+ if (ch === "(") {
2461
+ parenDepth++;
2462
+ current += ch;
2463
+ continue;
2464
+ }
2465
+ if (ch === ")") {
2466
+ parenDepth--;
2467
+ current += ch;
2468
+ continue;
2469
+ }
2410
2470
  if (/\s/.test(ch)) {
2411
- if (depth === 0) {
2471
+ if (depth === 0 && parenDepth === 0) {
2412
2472
  if (current) {
2413
2473
  tokens.push(current);
2414
2474
  current = "";
@@ -2444,6 +2504,7 @@ function findOuterColon(s) {
2444
2504
  var LRUCache = class {
2445
2505
  constructor(capacity = 1e4) {
2446
2506
  this.capacity = capacity;
2507
+ if (capacity <= 0) throw new Error("LRUCache: capacity must be a positive integer");
2447
2508
  this.cache = /* @__PURE__ */ new Map();
2448
2509
  }
2449
2510
  get(key) {
@@ -2463,11 +2524,7 @@ var LRUCache = class {
2463
2524
  return this;
2464
2525
  }
2465
2526
  has(key) {
2466
- if (!this.cache.has(key)) return false;
2467
- const value = this.cache.get(key);
2468
- this.cache.delete(key);
2469
- this.cache.set(key, value);
2470
- return true;
2527
+ return this.cache.has(key);
2471
2528
  }
2472
2529
  delete(key) {
2473
2530
  return this.cache.delete(key);
@@ -2510,24 +2567,41 @@ function injectGlobalStyles(theme) {
2510
2567
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2511
2568
  const rules = [BASE_RESET];
2512
2569
  const ff = theme.fontFamily;
2570
+ if (!ff) return;
2513
2571
  if (ff.sans && ff.sans !== "System") {
2514
2572
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
2515
2573
  rules.push(`body { font-family: ${family}; }`);
2516
2574
  }
2517
2575
  getGlobalStyleEl().textContent = rules.join("\n");
2518
2576
  }
2519
- function buildDivideDecls(styles) {
2577
+ function buildDivideDecls(styles, forceImportant) {
2578
+ const imp = forceImportant ? " !important" : "";
2520
2579
  const parts = [];
2521
2580
  if ("__divideX" in styles) {
2522
2581
  const w = Number(styles.__divideX);
2523
- parts.push(`border-left-width: ${w}px`, "border-right-width: 0px");
2582
+ parts.push(`border-left-width: ${w}px${imp}`, `border-right-width: 0px${imp}`);
2524
2583
  }
2525
2584
  if ("__divideY" in styles) {
2526
2585
  const w = Number(styles.__divideY);
2527
- parts.push(`border-top-width: ${w}px`, "border-bottom-width: 0px");
2586
+ parts.push(`border-top-width: ${w}px${imp}`, `border-bottom-width: 0px${imp}`);
2587
+ }
2588
+ if ("__divideColor" in styles) parts.push(`border-color: ${String(styles.__divideColor)}${imp}`);
2589
+ if ("__divideStyle" in styles) parts.push(`border-style: ${String(styles.__divideStyle)}${imp}`);
2590
+ return parts.join("; ");
2591
+ }
2592
+ function buildSpaceDecls(styles, forceImportant) {
2593
+ const imp = forceImportant ? " !important" : "";
2594
+ const parts = [];
2595
+ if ("__spaceX" in styles) {
2596
+ const v = styles.__spaceX;
2597
+ const val = typeof v === "number" ? `${v}px` : String(v);
2598
+ parts.push(`margin-left: ${val}${imp}`);
2599
+ }
2600
+ if ("__spaceY" in styles) {
2601
+ const v = styles.__spaceY;
2602
+ const val = typeof v === "number" ? `${v}px` : String(v);
2603
+ parts.push(`margin-top: ${val}${imp}`);
2528
2604
  }
2529
- if ("__divideColor" in styles) parts.push(`border-color: ${String(styles.__divideColor)}`);
2530
- if ("__divideStyle" in styles) parts.push(`border-style: ${String(styles.__divideStyle)}`);
2531
2605
  return parts.join("; ");
2532
2606
  }
2533
2607
  var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
@@ -2547,7 +2621,9 @@ var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
2547
2621
  "__divideY",
2548
2622
  "__divideColor",
2549
2623
  "__divideStyle",
2550
- "__keyframe"
2624
+ "__keyframe",
2625
+ "__spaceX",
2626
+ "__spaceY"
2551
2627
  ]);
2552
2628
  var CSS_UNITLESS = /* @__PURE__ */ new Set([
2553
2629
  "opacity",
@@ -2600,10 +2676,12 @@ function buildClassCSSRules(cls, bucketKey, styles, darkMode, important, screens
2600
2676
  rules.push(styles.__keyframe);
2601
2677
  }
2602
2678
  const isDivide = "__divideX" in styles || "__divideY" in styles || "__divideColor" in styles || "__divideStyle" in styles;
2679
+ const isSpace = "__spaceX" in styles || "__spaceY" in styles;
2680
+ const isChildCombinator = isDivide || isSpace;
2603
2681
  const escaped = escapeCSSSelector(cls);
2604
- const childSuffix = isDivide ? " > * + *" : "";
2682
+ const childSuffix = isChildCombinator ? " > * + *" : "";
2605
2683
  if (bucketKey === "base") {
2606
- const decls2 = isDivide ? buildDivideDecls(styles) : styleValueToCSS(styles, important);
2684
+ const decls2 = isDivide ? buildDivideDecls(styles, important) : isSpace ? buildSpaceDecls(styles, important) : styleValueToCSS(styles, important);
2607
2685
  if (!decls2) return rules;
2608
2686
  rules.push(`.${escaped}${childSuffix} { ${decls2} }`);
2609
2687
  return rules;
@@ -2644,7 +2722,7 @@ function buildClassCSSRules(cls, bucketKey, styles, darkMode, important, screens
2644
2722
  const ancestorPrefix = ancestorParts.join("");
2645
2723
  const dirPrefix = dirParts.join("");
2646
2724
  const selector = `${dirPrefix}${ancestorPrefix}${elementSelector}`;
2647
- const decls = isDivide ? buildDivideDecls(styles) : styleValueToCSS(styles, needsImportant);
2725
+ const decls = isDivide ? buildDivideDecls(styles, needsImportant) : isSpace ? buildSpaceDecls(styles, needsImportant) : styleValueToCSS(styles, needsImportant);
2648
2726
  if (!decls) return rules;
2649
2727
  let rule;
2650
2728
  if (darkScheme === "dark") {
@@ -2737,19 +2815,24 @@ function buildConfig(userConfig) {
2737
2815
  theme = deepMerge(theme, userConfig.theme);
2738
2816
  }
2739
2817
  if (userConfig.extend) {
2740
- const { theme: nestedTheme, ...directKeys } = userConfig.extend;
2818
+ const extendConfig = userConfig.extend;
2819
+ const { theme: nestedTheme, ...directKeys } = extendConfig;
2741
2820
  const extSources = [];
2742
2821
  if (nestedTheme) extSources.push(nestedTheme);
2743
2822
  if (Object.keys(directKeys).length) extSources.push(directKeys);
2744
2823
  for (const ext of extSources) {
2745
2824
  for (const [key, value] of Object.entries(ext)) {
2825
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2826
+ if (process.env.NODE_ENV !== "production") {
2827
+ console.warn(`[kbach] config.extend.${key} must be a plain object, got ${typeof value}. Skipping.`);
2828
+ }
2829
+ continue;
2830
+ }
2746
2831
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
2747
2832
  theme[key] = deepMerge(
2748
2833
  theme[key] ?? {},
2749
2834
  value
2750
2835
  );
2751
- } else if (value !== void 0) {
2752
- theme[key] = value;
2753
2836
  }
2754
2837
  }
2755
2838
  }
@@ -2760,8 +2843,10 @@ function buildConfig(userConfig) {
2760
2843
  theme,
2761
2844
  plugins: userConfig.plugins ?? []
2762
2845
  };
2763
- clearPluginUtilities();
2764
- clearPluginModifiers();
2846
+ if (userConfig.plugins !== void 0) {
2847
+ clearPluginUtilities();
2848
+ clearPluginModifiers();
2849
+ }
2765
2850
  const _store = getConfigStore();
2766
2851
  for (const k of Object.keys(_store.customVariants)) delete _store.customVariants[k];
2767
2852
  const pluginAPI = makePluginAPI(resolved.theme);
@@ -2779,7 +2864,7 @@ function _selectorToModifierDef(selector) {
2779
2864
  if (s.startsWith("@media ")) {
2780
2865
  return { mediaQuery: s.slice(7), jsBehavior: "css-only", forcesImportant: true };
2781
2866
  }
2782
- if (s.startsWith("::") || s.startsWith(":")) {
2867
+ if (s.startsWith("::") || s.startsWith(":") && !s.includes(" ") && !s.includes("&") && !s.includes("(")) {
2783
2868
  return { pseudo: s, jsBehavior: "css-only", forcesImportant: true };
2784
2869
  }
2785
2870
  return { ancestorSelector: s.endsWith(" ") ? s : `${s} `, jsBehavior: "css-only", forcesImportant: true };
@@ -2792,8 +2877,8 @@ function makePluginAPI(theme) {
2792
2877
  },
2793
2878
  addVariant(name, selectorOrDef) {
2794
2879
  const def = typeof selectorOrDef === "string" ? _selectorToModifierDef(selectorOrDef) : selectorOrDef;
2795
- const selector = typeof selectorOrDef === "string" ? selectorOrDef : selectorOrDef.pseudo ?? selectorOrDef.mediaQuery ?? selectorOrDef.ancestorSelector ?? name;
2796
- customVariants[name] = selector;
2880
+ const selector = typeof selectorOrDef === "string" ? selectorOrDef : selectorOrDef.pseudo ?? selectorOrDef.mediaQuery ?? selectorOrDef.ancestorSelector ?? null;
2881
+ if (selector !== null && selector !== name) customVariants[name] = selector;
2797
2882
  registerModifier(name, def);
2798
2883
  },
2799
2884
  theme(path, defaultValue) {
@@ -2975,7 +3060,7 @@ ${css}
2975
3060
  ${KBACH_END}`;
2976
3061
  const start = existing.indexOf(KBACH_START);
2977
3062
  const end = existing.indexOf(KBACH_END);
2978
- const next = start !== -1 && end !== -1 ? existing.slice(0, start) + block + existing.slice(end + KBACH_END.length) : existing ? `${existing}
3063
+ const next = start !== -1 && end !== -1 && end > start ? existing.slice(0, start) + block + existing.slice(end + KBACH_END.length) : existing ? `${existing}
2979
3064
 
2980
3065
  ${block}` : block;
2981
3066
  if (next === existing) return false;
@@ -3032,7 +3117,7 @@ function extractClassStrings(code) {
3032
3117
  }
3033
3118
  const templateRe = /`([^`]{1,2000})`/g;
3034
3119
  while ((m = templateRe.exec(code)) !== null) {
3035
- const staticParts = m[1].replace(/\$\{[^}]*\}/g, " ");
3120
+ const staticParts = m[1].replace(/\$\{(?:[^{}]|\{[^{}]*\})*\}/g, " ");
3036
3121
  pushTokens(staticParts, found);
3037
3122
  }
3038
3123
  return [...found];
@@ -3064,7 +3149,11 @@ function scanDir(dir, onFile) {
3064
3149
  }
3065
3150
  }
3066
3151
  }
3067
- function kbach(userConfig) {
3152
+ var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components", "views", "layouts"];
3153
+ function kbach(userConfigOrOptions) {
3154
+ const isOptions = userConfigOrOptions != null && ("framework" in userConfigOrOptions || "include" in userConfigOrOptions);
3155
+ const userConfig = isOptions ? userConfigOrOptions.framework : userConfigOrOptions;
3156
+ const includeDirs = (isOptions ? userConfigOrOptions.include : void 0) ?? DEFAULT_SCAN_DIRS;
3068
3157
  const cfg = buildConfig(userConfig ?? {});
3069
3158
  const screens = toNumericScreens(cfg.theme.screens ?? {});
3070
3159
  const screenKeys = Object.keys(cfg.theme.screens ?? {});
@@ -3096,7 +3185,7 @@ function kbach(userConfig) {
3096
3185
  return view;
3097
3186
  }
3098
3187
  function initialScan() {
3099
- for (const dir of ["src", "app", "pages", "components", "views", "layouts"]) {
3188
+ for (const dir of includeDirs) {
3100
3189
  scanDir((0, import_path.join)(root, dir), processFile);
3101
3190
  }
3102
3191
  }
@@ -3113,7 +3202,7 @@ function kbach(userConfig) {
3113
3202
  // so styles come solely from the static stylesheet — no duplicate injection.
3114
3203
  transform(code, id) {
3115
3204
  if (!/\.(tsx?|jsx?|mjs?)$/.test(id)) return;
3116
- if (!/['"].*kbach\.css['"]/i.test(code)) return;
3205
+ if (!/\bimport\b[^;]*['"].*kbach\.css['"]/i.test(code)) return;
3117
3206
  return {
3118
3207
  code: `import { disableRuntimeCSS } from '@kbach/react';
3119
3208
  disableRuntimeCSS();
@@ -4,7 +4,7 @@ import {
4
4
  generateClassCSS,
5
5
  setCSSGenMode,
6
6
  splitClassTokens
7
- } from "./chunk-JBV2R655.mjs";
7
+ } from "./chunk-2HWRNTJG.mjs";
8
8
 
9
9
  // src/vite-plugin.ts
10
10
  import { readFileSync, writeFileSync, readdirSync, statSync } from "fs";
@@ -152,7 +152,7 @@ ${css}
152
152
  ${KBACH_END}`;
153
153
  const start = existing.indexOf(KBACH_START);
154
154
  const end = existing.indexOf(KBACH_END);
155
- const next = start !== -1 && end !== -1 ? existing.slice(0, start) + block + existing.slice(end + KBACH_END.length) : existing ? `${existing}
155
+ const next = start !== -1 && end !== -1 && end > start ? existing.slice(0, start) + block + existing.slice(end + KBACH_END.length) : existing ? `${existing}
156
156
 
157
157
  ${block}` : block;
158
158
  if (next === existing) return false;
@@ -209,7 +209,7 @@ function extractClassStrings(code) {
209
209
  }
210
210
  const templateRe = /`([^`]{1,2000})`/g;
211
211
  while ((m = templateRe.exec(code)) !== null) {
212
- const staticParts = m[1].replace(/\$\{[^}]*\}/g, " ");
212
+ const staticParts = m[1].replace(/\$\{(?:[^{}]|\{[^{}]*\})*\}/g, " ");
213
213
  pushTokens(staticParts, found);
214
214
  }
215
215
  return [...found];
@@ -241,7 +241,11 @@ function scanDir(dir, onFile) {
241
241
  }
242
242
  }
243
243
  }
244
- function kbach(userConfig) {
244
+ var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components", "views", "layouts"];
245
+ function kbach(userConfigOrOptions) {
246
+ const isOptions = userConfigOrOptions != null && ("framework" in userConfigOrOptions || "include" in userConfigOrOptions);
247
+ const userConfig = isOptions ? userConfigOrOptions.framework : userConfigOrOptions;
248
+ const includeDirs = (isOptions ? userConfigOrOptions.include : void 0) ?? DEFAULT_SCAN_DIRS;
245
249
  const cfg = buildConfig(userConfig ?? {});
246
250
  const screens = toNumericScreens(cfg.theme.screens ?? {});
247
251
  const screenKeys = Object.keys(cfg.theme.screens ?? {});
@@ -273,7 +277,7 @@ function kbach(userConfig) {
273
277
  return view;
274
278
  }
275
279
  function initialScan() {
276
- for (const dir of ["src", "app", "pages", "components", "views", "layouts"]) {
280
+ for (const dir of includeDirs) {
277
281
  scanDir(join(root, dir), processFile);
278
282
  }
279
283
  }
@@ -290,7 +294,7 @@ function kbach(userConfig) {
290
294
  // so styles come solely from the static stylesheet — no duplicate injection.
291
295
  transform(code, id) {
292
296
  if (!/\.(tsx?|jsx?|mjs?)$/.test(id)) return;
293
- if (!/['"].*kbach\.css['"]/i.test(code)) return;
297
+ if (!/\bimport\b[^;]*['"].*kbach\.css['"]/i.test(code)) return;
294
298
  return {
295
299
  code: `import { disableRuntimeCSS } from '@kbach/react';
296
300
  disableRuntimeCSS();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.5.56",
3
+ "version": "0.5.58",
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",