@kbach/react 0.6.35 → 0.6.36

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.
@@ -2203,10 +2203,6 @@ function expandModeAwareColorClasses(classString, colors) {
2203
2203
  out.push(token);
2204
2204
  continue;
2205
2205
  }
2206
- if (parsed.modifiers.includes("dark") || parsed.modifiers.includes("light")) {
2207
- out.push(token);
2208
- continue;
2209
- }
2210
2206
  const slashIdx = parsed.value.indexOf("/");
2211
2207
  const colorPart = slashIdx > 0 ? parsed.value.slice(0, slashIdx) : parsed.value;
2212
2208
  const opacitySuffix = slashIdx > 0 ? parsed.value.slice(slashIdx) : "";
@@ -2218,6 +2214,12 @@ function expandModeAwareColorClasses(classString, colors) {
2218
2214
  changed = true;
2219
2215
  const bang = parsed.important ? "!" : "";
2220
2216
  const modPrefix = parsed.modifiers.map((m) => `${m}:`).join("");
2217
+ const explicitScheme = parsed.modifiers.map((m) => getModifier(m)?.darkScheme).find((s) => s);
2218
+ if (explicitScheme) {
2219
+ const side = explicitScheme === "dark" ? pair.dark : pair.light;
2220
+ out.push(`${bang}${modPrefix}${parsed.utility}-[${side}]${opacitySuffix}`);
2221
+ continue;
2222
+ }
2221
2223
  out.push(`${bang}${modPrefix}${parsed.utility}-[${pair.light}]${opacitySuffix}`);
2222
2224
  out.push(`${bang}dark:${modPrefix}${parsed.utility}-[${pair.dark}]${opacitySuffix}`);
2223
2225
  }
@@ -2773,6 +2775,48 @@ var defaultTheme = {
2773
2775
  animation: {}
2774
2776
  };
2775
2777
 
2778
+ // src/core/generateTypesDts.ts
2779
+ var HEADER = `// AUTO-GENERATED by Kbach from your kbach.config.js \u2014 do not edit by hand.
2780
+ // Regenerated automatically every time your dev server / Metro picks up a
2781
+ // change to that file. Safe (and recommended) to add to .gitignore.
2782
+ //
2783
+ // Gives useColors()/useSpacing() full autocomplete and typo-catching for your
2784
+ // custom colors/spacing keys with zero manual setup \u2014 see KbachCustomColors'
2785
+ // doc comment in @kbach/react if you'd rather hand-author this instead.
2786
+ import '@kbach/react';
2787
+ `;
2788
+ var IDENT_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
2789
+ function propKey(name) {
2790
+ return IDENT_RE.test(name) ? name : JSON.stringify(name);
2791
+ }
2792
+ function generateKbachTypesDts(theme) {
2793
+ const colorLines = [];
2794
+ for (const [name, value] of Object.entries(theme.colors)) {
2795
+ if (name in defaultColors) continue;
2796
+ const isFlat = typeof value === "string" || isModeAwareColor(value);
2797
+ colorLines.push(` ${propKey(name)}: ${isFlat ? "string" : "ColorScale"};`);
2798
+ }
2799
+ const spacingLines = [];
2800
+ for (const key of Object.keys(theme.spacing)) {
2801
+ if (key in defaultTheme.spacing) continue;
2802
+ spacingLines.push(` ${propKey(key)}: true;`);
2803
+ }
2804
+ if (colorLines.length === 0 && spacingLines.length === 0) return "";
2805
+ const body = [
2806
+ colorLines.length > 0 ? ` interface KbachCustomColors {
2807
+ ${colorLines.join("\n")}
2808
+ }` : null,
2809
+ spacingLines.length > 0 ? ` interface KbachCustomSpacing {
2810
+ ${spacingLines.join("\n")}
2811
+ }` : null
2812
+ ].filter((s) => s !== null).join("\n\n");
2813
+ return `${HEADER}
2814
+ declare module '@kbach/react' {
2815
+ ${body}
2816
+ }
2817
+ `;
2818
+ }
2819
+
2776
2820
  // src/core/reset.ts
2777
2821
  var RESET_STYLE_ID = "kbach-reset";
2778
2822
  var BASE_RESET = [
@@ -3941,6 +3985,7 @@ export {
3941
3985
  expandModeAwareColorClasses,
3942
3986
  defaultColors,
3943
3987
  defaultTheme,
3988
+ generateKbachTypesDts,
3944
3989
  RESET_STYLE_ID,
3945
3990
  BASE_RESET,
3946
3991
  syncGlobalWidth,
@@ -26,7 +26,7 @@ import {
26
26
  transformToWebProps,
27
27
  useConditionalGlobalDarkMode,
28
28
  useConditionalWidth
29
- } from "./chunk-O7GFDFVJ.mjs";
29
+ } from "./chunk-ATZF2VI7.mjs";
30
30
 
31
31
  // src/jsx-runtime.tsx
32
32
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -190,6 +190,20 @@ declare function isModeAwareColor(v: unknown): v is {
190
190
  * normal parsing — e.g. `bg-surface` becomes `bg-[#ffffff] dark:bg-[#111827]`.
191
191
  * `bg-surface/50` becomes `bg-[#ffffff]/50 dark:bg-[#111827]/50` (the arbitrary-
192
192
  * color-plus-opacity composition already resolveColor() already supports).
193
+ * `hover:bg-surface` becomes `hover:bg-[#ffffff] dark:hover:bg-[#111827]` —
194
+ * every other modifier already present on the token carries through onto
195
+ * both halves of the pair unchanged.
196
+ *
197
+ * A token that ALREADY carries an explicit dark:/light:/not-dark:/not-light:
198
+ * modifier (someone writes `dark:hover:bg-primary` on a `primary` that's
199
+ * already mode-aware, usually out of habit from before it was) isn't split
200
+ * into a pair — that would be redundant on top of an already-explicit
201
+ * choice. Instead the matching side is substituted in place and every
202
+ * modifier, including the dark:/light: itself, is left exactly as written,
203
+ * so `dark:hover:bg-primary` still only applies in dark mode, using the
204
+ * dark side (not the resolveColor()-level fallback's light side, which is
205
+ * only ever reached by a mode-aware pair that skipped this expansion
206
+ * entirely — not a path normal className resolution takes).
193
207
  *
194
208
  * Runs once, upfront, purely as a string rewrite — everything downstream
195
209
  * (CSS generation, native bucketing/flatten(), the existing dark: reactivity
@@ -200,6 +214,19 @@ declare function isModeAwareColor(v: unknown): v is {
200
214
  */
201
215
  declare function expandModeAwareColorClasses(classString: string, colors: ThemeColors): string;
202
216
 
217
+ /**
218
+ * Generates the KbachCustomColors/KbachCustomSpacing module-augmentation
219
+ * source for every color/spacing key `extend.theme` added beyond the built-in
220
+ * defaults. Called by both the Vite plugin and @kbach/native's Babel plugin —
221
+ * kept here, shared, so "which colors count as custom" and "flat string vs.
222
+ * ColorScale" can't drift between the two.
223
+ *
224
+ * Returns '' (nothing to write) when the theme adds no custom colors or
225
+ * spacing keys at all, so callers can skip writing/deleting a file for the
226
+ * common case of a project still on the stock theme.
227
+ */
228
+ declare function generateKbachTypesDts(theme: ThemeConfig): string;
229
+
203
230
  declare const defaultColors: {
204
231
  transparent: string;
205
232
  current: string;
@@ -1308,4 +1335,4 @@ declare function subscribeGlobalWidth(listener: WidthListener): () => void;
1308
1335
  */
1309
1336
  declare function getActiveBreakpoints(width?: number, screens?: Record<string, number>): Set<string>;
1310
1337
 
1311
- export { BASE_RESET, type ColorShades, type ColorValue, type DarkMode, type DefaultColorName, type DefaultSpacingKey, type FrameworkConfig, LRUCache, type ParsedClass, type Platform, type PluginAPI, RESET_STYLE_ID, type ResolvedConfig, type ResolvedStyle, type StyleValue, type ThemeColors, type ThemeConfig, type ThemeMode, type ThemeSpacing, buildConfig, clearCache, clearPluginModifiers, customVariants, defaultColors, defaultTheme, disableRuntimeCSS, escapeCSSSelector, expandModeAwareColorClasses, flatten, generateClassCSS, getActiveBreakpoints, getAllModifierNames, getBuiltinStandaloneNames, getBuiltinUtilityPrefixes, getConfig, getDefaultFontFamily, getEffectiveIsWeb, getGlobalDarkMode, getGlobalScreens, getGlobalWidth, getInteractiveModifiers, getModeModifiers, getModifier, getResponsiveModifiers, initConfig, isKnownModifier, isKnownUtility, isModeAwareColor, isNative, isRuntimeCSSDisabled, isWeb, kbachWarn, matchModifier, normalizeClassString, onConfigChange, parseClass, parseClasses, parseHexRgb, registerModifier, resetConfig, resolve, resolveColor, resolveSizing, resolveSpacing, resolveUtility, setDefaultFontFamily, setGlobalDarkMode, setGlobalWidth, setResolveTarget, splitClassTokens, subscribeGlobalDarkMode, subscribeGlobalWidth, syncGlobalDarkMode, syncGlobalScreens, syncGlobalWidth, toNativeValue, updateConfig };
1338
+ export { BASE_RESET, type ColorShades, type ColorValue, type DarkMode, type DefaultColorName, type DefaultSpacingKey, type FrameworkConfig, LRUCache, type ParsedClass, type Platform, type PluginAPI, RESET_STYLE_ID, type ResolvedConfig, type ResolvedStyle, type StyleValue, type ThemeColors, type ThemeConfig, type ThemeMode, type ThemeSpacing, buildConfig, clearCache, clearPluginModifiers, customVariants, defaultColors, defaultTheme, disableRuntimeCSS, escapeCSSSelector, expandModeAwareColorClasses, flatten, generateClassCSS, generateKbachTypesDts, getActiveBreakpoints, getAllModifierNames, getBuiltinStandaloneNames, getBuiltinUtilityPrefixes, getConfig, getDefaultFontFamily, getEffectiveIsWeb, getGlobalDarkMode, getGlobalScreens, getGlobalWidth, getInteractiveModifiers, getModeModifiers, getModifier, getResponsiveModifiers, initConfig, isKnownModifier, isKnownUtility, isModeAwareColor, isNative, isRuntimeCSSDisabled, isWeb, kbachWarn, matchModifier, normalizeClassString, onConfigChange, parseClass, parseClasses, parseHexRgb, registerModifier, resetConfig, resolve, resolveColor, resolveSizing, resolveSpacing, resolveUtility, setDefaultFontFamily, setGlobalDarkMode, setGlobalWidth, setResolveTarget, splitClassTokens, subscribeGlobalDarkMode, subscribeGlobalWidth, syncGlobalDarkMode, syncGlobalScreens, syncGlobalWidth, toNativeValue, updateConfig };
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  expandModeAwareColorClasses: () => expandModeAwareColorClasses,
35
35
  flatten: () => flatten,
36
36
  generateClassCSS: () => generateClassCSS,
37
+ generateKbachTypesDts: () => generateKbachTypesDts,
37
38
  getActiveBreakpoints: () => getActiveBreakpoints,
38
39
  getAllModifierNames: () => getAllModifierNames,
39
40
  getBuiltinStandaloneNames: () => getBuiltinStandaloneNames,
@@ -2291,10 +2292,6 @@ function expandModeAwareColorClasses(classString, colors) {
2291
2292
  out.push(token);
2292
2293
  continue;
2293
2294
  }
2294
- if (parsed.modifiers.includes("dark") || parsed.modifiers.includes("light")) {
2295
- out.push(token);
2296
- continue;
2297
- }
2298
2295
  const slashIdx = parsed.value.indexOf("/");
2299
2296
  const colorPart = slashIdx > 0 ? parsed.value.slice(0, slashIdx) : parsed.value;
2300
2297
  const opacitySuffix = slashIdx > 0 ? parsed.value.slice(slashIdx) : "";
@@ -2306,6 +2303,12 @@ function expandModeAwareColorClasses(classString, colors) {
2306
2303
  changed = true;
2307
2304
  const bang = parsed.important ? "!" : "";
2308
2305
  const modPrefix = parsed.modifiers.map((m) => `${m}:`).join("");
2306
+ const explicitScheme = parsed.modifiers.map((m) => getModifier(m)?.darkScheme).find((s) => s);
2307
+ if (explicitScheme) {
2308
+ const side = explicitScheme === "dark" ? pair.dark : pair.light;
2309
+ out.push(`${bang}${modPrefix}${parsed.utility}-[${side}]${opacitySuffix}`);
2310
+ continue;
2311
+ }
2309
2312
  out.push(`${bang}${modPrefix}${parsed.utility}-[${pair.light}]${opacitySuffix}`);
2310
2313
  out.push(`${bang}dark:${modPrefix}${parsed.utility}-[${pair.dark}]${opacitySuffix}`);
2311
2314
  }
@@ -2861,6 +2864,48 @@ var defaultTheme = {
2861
2864
  animation: {}
2862
2865
  };
2863
2866
 
2867
+ // src/core/generateTypesDts.ts
2868
+ var HEADER = `// AUTO-GENERATED by Kbach from your kbach.config.js \u2014 do not edit by hand.
2869
+ // Regenerated automatically every time your dev server / Metro picks up a
2870
+ // change to that file. Safe (and recommended) to add to .gitignore.
2871
+ //
2872
+ // Gives useColors()/useSpacing() full autocomplete and typo-catching for your
2873
+ // custom colors/spacing keys with zero manual setup \u2014 see KbachCustomColors'
2874
+ // doc comment in @kbach/react if you'd rather hand-author this instead.
2875
+ import '@kbach/react';
2876
+ `;
2877
+ var IDENT_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
2878
+ function propKey(name) {
2879
+ return IDENT_RE.test(name) ? name : JSON.stringify(name);
2880
+ }
2881
+ function generateKbachTypesDts(theme) {
2882
+ const colorLines = [];
2883
+ for (const [name, value] of Object.entries(theme.colors)) {
2884
+ if (name in defaultColors) continue;
2885
+ const isFlat = typeof value === "string" || isModeAwareColor(value);
2886
+ colorLines.push(` ${propKey(name)}: ${isFlat ? "string" : "ColorScale"};`);
2887
+ }
2888
+ const spacingLines = [];
2889
+ for (const key of Object.keys(theme.spacing)) {
2890
+ if (key in defaultTheme.spacing) continue;
2891
+ spacingLines.push(` ${propKey(key)}: true;`);
2892
+ }
2893
+ if (colorLines.length === 0 && spacingLines.length === 0) return "";
2894
+ const body = [
2895
+ colorLines.length > 0 ? ` interface KbachCustomColors {
2896
+ ${colorLines.join("\n")}
2897
+ }` : null,
2898
+ spacingLines.length > 0 ? ` interface KbachCustomSpacing {
2899
+ ${spacingLines.join("\n")}
2900
+ }` : null
2901
+ ].filter((s) => s !== null).join("\n\n");
2902
+ return `${HEADER}
2903
+ declare module '@kbach/react' {
2904
+ ${body}
2905
+ }
2906
+ `;
2907
+ }
2908
+
2864
2909
  // src/core/reset.ts
2865
2910
  var RESET_STYLE_ID = "kbach-reset";
2866
2911
  var BASE_RESET = [
@@ -3502,6 +3547,7 @@ function subscribeGlobalDarkMode(callback) {
3502
3547
  expandModeAwareColorClasses,
3503
3548
  flatten,
3504
3549
  generateClassCSS,
3550
+ generateKbachTypesDts,
3505
3551
  getActiveBreakpoints,
3506
3552
  getAllModifierNames,
3507
3553
  getBuiltinStandaloneNames,
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as React from 'react';
2
2
  import React__default, { ReactNode, ComponentType, ForwardRefExoticComponent } from 'react';
3
3
  import * as __core from './core';
4
4
  import { ThemeMode, ResolvedConfig, DefaultColorName, defaultColors, ThemeColors, DefaultSpacingKey, ThemeSpacing, FrameworkConfig, StyleValue, ResolvedStyle } from './core';
5
- export { DefaultColorName, DefaultSpacingKey, FrameworkConfig, ParsedClass, PluginAPI, ResolvedConfig, ResolvedStyle, StyleValue, ThemeConfig, ThemeMode, buildConfig, clearCache, defaultColors, defaultTheme, disableRuntimeCSS, flatten, getConfig, initConfig, normalizeClassString, parseClass, parseClasses, resolve, setResolveTarget, splitClassTokens, updateConfig } from './core';
5
+ export { DefaultColorName, DefaultSpacingKey, FrameworkConfig, ParsedClass, PluginAPI, ResolvedConfig, ResolvedStyle, StyleValue, ThemeConfig, ThemeMode, buildConfig, clearCache, defaultColors, defaultTheme, disableRuntimeCSS, flatten, generateKbachTypesDts, getConfig, initConfig, normalizeClassString, parseClass, parseClasses, resolve, setResolveTarget, splitClassTokens, updateConfig } from './core';
6
6
  export { r as registerWebElement } from './web-substitute-CfpYFnE7.js';
7
7
 
8
8
  interface ThemeContextValue {
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ __export(index_exports, {
42
42
  defaultTheme: () => import_core12.defaultTheme,
43
43
  disableRuntimeCSS: () => import_core12.disableRuntimeCSS,
44
44
  flatten: () => import_core12.flatten,
45
+ generateKbachTypesDts: () => import_core12.generateKbachTypesDts,
45
46
  getConfig: () => import_core12.getConfig,
46
47
  initConfig: () => import_core12.initConfig,
47
48
  kb: () => kb,
@@ -1113,6 +1114,7 @@ var import_core12 = require("./core");
1113
1114
  defaultTheme,
1114
1115
  disableRuntimeCSS,
1115
1116
  flatten,
1117
+ generateKbachTypesDts,
1116
1118
  getConfig,
1117
1119
  initConfig,
1118
1120
  kb,
package/dist/index.mjs CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  defaultTheme,
12
12
  disableRuntimeCSS,
13
13
  flatten,
14
+ generateKbachTypesDts,
14
15
  getActiveBreakpoints,
15
16
  getConfig,
16
17
  getEffectiveIsWeb,
@@ -44,7 +45,7 @@ import {
44
45
  useGlobalDarkMode,
45
46
  useGlobalWidth,
46
47
  useSyncExternalStore
47
- } from "./chunk-O7GFDFVJ.mjs";
48
+ } from "./chunk-ATZF2VI7.mjs";
48
49
 
49
50
  // src/context.tsx
50
51
  import { createContext, useContext } from "react";
@@ -549,6 +550,7 @@ export {
549
550
  defaultTheme,
550
551
  disableRuntimeCSS,
551
552
  flatten,
553
+ generateKbachTypesDts,
552
554
  getConfig,
553
555
  initConfig,
554
556
  kb,
@@ -3,8 +3,8 @@ import {
3
3
  Fragment,
4
4
  jsx,
5
5
  jsxs
6
- } from "./chunk-Y6JF3TZM.mjs";
7
- import "./chunk-O7GFDFVJ.mjs";
6
+ } from "./chunk-YDLZI5J6.mjs";
7
+ import "./chunk-ATZF2VI7.mjs";
8
8
 
9
9
  // src/jsx-dev-runtime.tsx
10
10
  function jsxDEV(type, props, key, isStaticChildren, source, self) {
@@ -4,10 +4,10 @@ import {
4
4
  _invalidateDefaultFontCache,
5
5
  jsx,
6
6
  jsxs
7
- } from "./chunk-Y6JF3TZM.mjs";
7
+ } from "./chunk-YDLZI5J6.mjs";
8
8
  import {
9
9
  registerWebElement
10
- } from "./chunk-O7GFDFVJ.mjs";
10
+ } from "./chunk-ATZF2VI7.mjs";
11
11
  export {
12
12
  Fragment,
13
13
  _invalidateDefaultFontCache,
@@ -2800,10 +2800,6 @@ function expandModeAwareColorClasses(classString, colors) {
2800
2800
  out.push(token);
2801
2801
  continue;
2802
2802
  }
2803
- if (parsed.modifiers.includes("dark") || parsed.modifiers.includes("light")) {
2804
- out.push(token);
2805
- continue;
2806
- }
2807
2803
  const slashIdx = parsed.value.indexOf("/");
2808
2804
  const colorPart = slashIdx > 0 ? parsed.value.slice(0, slashIdx) : parsed.value;
2809
2805
  const opacitySuffix = slashIdx > 0 ? parsed.value.slice(slashIdx) : "";
@@ -2815,6 +2811,12 @@ function expandModeAwareColorClasses(classString, colors) {
2815
2811
  changed = true;
2816
2812
  const bang = parsed.important ? "!" : "";
2817
2813
  const modPrefix = parsed.modifiers.map((m) => `${m}:`).join("");
2814
+ const explicitScheme = parsed.modifiers.map((m) => getModifier(m)?.darkScheme).find((s) => s);
2815
+ if (explicitScheme) {
2816
+ const side = explicitScheme === "dark" ? pair.dark : pair.light;
2817
+ out.push(`${bang}${modPrefix}${parsed.utility}-[${side}]${opacitySuffix}`);
2818
+ continue;
2819
+ }
2818
2820
  out.push(`${bang}${modPrefix}${parsed.utility}-[${pair.light}]${opacitySuffix}`);
2819
2821
  out.push(`${bang}dark:${modPrefix}${parsed.utility}-[${pair.dark}]${opacitySuffix}`);
2820
2822
  }
@@ -3196,6 +3198,48 @@ function makePluginAPI(theme) {
3196
3198
  }
3197
3199
  var customVariants = configStore.customVariants;
3198
3200
 
3201
+ // src/core/generateTypesDts.ts
3202
+ var HEADER = `// AUTO-GENERATED by Kbach from your kbach.config.js \u2014 do not edit by hand.
3203
+ // Regenerated automatically every time your dev server / Metro picks up a
3204
+ // change to that file. Safe (and recommended) to add to .gitignore.
3205
+ //
3206
+ // Gives useColors()/useSpacing() full autocomplete and typo-catching for your
3207
+ // custom colors/spacing keys with zero manual setup \u2014 see KbachCustomColors'
3208
+ // doc comment in @kbach/react if you'd rather hand-author this instead.
3209
+ import '@kbach/react';
3210
+ `;
3211
+ var IDENT_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
3212
+ function propKey(name) {
3213
+ return IDENT_RE.test(name) ? name : JSON.stringify(name);
3214
+ }
3215
+ function generateKbachTypesDts(theme) {
3216
+ const colorLines = [];
3217
+ for (const [name, value] of Object.entries(theme.colors)) {
3218
+ if (name in defaultColors) continue;
3219
+ const isFlat = typeof value === "string" || isModeAwareColor(value);
3220
+ colorLines.push(` ${propKey(name)}: ${isFlat ? "string" : "ColorScale"};`);
3221
+ }
3222
+ const spacingLines = [];
3223
+ for (const key of Object.keys(theme.spacing)) {
3224
+ if (key in defaultTheme.spacing) continue;
3225
+ spacingLines.push(` ${propKey(key)}: true;`);
3226
+ }
3227
+ if (colorLines.length === 0 && spacingLines.length === 0) return "";
3228
+ const body = [
3229
+ colorLines.length > 0 ? ` interface KbachCustomColors {
3230
+ ${colorLines.join("\n")}
3231
+ }` : null,
3232
+ spacingLines.length > 0 ? ` interface KbachCustomSpacing {
3233
+ ${spacingLines.join("\n")}
3234
+ }` : null
3235
+ ].filter((s) => s !== null).join("\n\n");
3236
+ return `${HEADER}
3237
+ declare module '@kbach/react' {
3238
+ ${body}
3239
+ }
3240
+ `;
3241
+ }
3242
+
3199
3243
  // src/vite-plugin.ts
3200
3244
  var process2 = import_node_process.default;
3201
3245
  function normPath(p) {
@@ -3216,6 +3260,9 @@ function warn(message) {
3216
3260
  function warnBlock(header, ...lines) {
3217
3261
  console.warn([`${TAG()} ${yellow(header)}`, ...lines].join("\n"));
3218
3262
  }
3263
+ function log(message) {
3264
+ console.log(`${TAG()} ${message}`);
3265
+ }
3219
3266
  function findLineCol(code, needle) {
3220
3267
  const index = code.indexOf(needle);
3221
3268
  if (index === -1) return null;
@@ -3481,6 +3528,28 @@ ${block}` : block;
3481
3528
  (0, import_fs.writeFileSync)(filePath, next, "utf-8");
3482
3529
  return true;
3483
3530
  }
3531
+ function writeKbachTypesDts(root, theme) {
3532
+ const filePath = (0, import_path.join)(root, "kbach-types.d.ts");
3533
+ const content = generateKbachTypesDts(theme);
3534
+ let existing = null;
3535
+ try {
3536
+ existing = (0, import_fs.readFileSync)(filePath, "utf-8");
3537
+ } catch {
3538
+ }
3539
+ if (content === (existing ?? "")) return;
3540
+ try {
3541
+ if (content === "") {
3542
+ (0, import_fs.unlinkSync)(filePath);
3543
+ } else {
3544
+ const isNew = existing === null;
3545
+ (0, import_fs.writeFileSync)(filePath, content, "utf-8");
3546
+ if (isNew) {
3547
+ log(`Generated kbach-types.d.ts \u2014 gives useColors()/useSpacing() autocomplete for your custom colors/spacing keys. Safe to add to .gitignore.`);
3548
+ }
3549
+ }
3550
+ } catch {
3551
+ }
3552
+ }
3484
3553
  function toNumericScreens(screens) {
3485
3554
  const out = {};
3486
3555
  for (const [k, v] of Object.entries(screens)) {
@@ -3700,6 +3769,7 @@ function kbach(userConfigOrOptions) {
3700
3769
  },
3701
3770
  configResolved(resolved) {
3702
3771
  root = resolved.root;
3772
+ writeKbachTypesDts(root, cfg.theme);
3703
3773
  const names = new Set(resolved.plugins.map((p) => p.name));
3704
3774
  if (names.has("react-router") && names.has("vite:react-refresh")) {
3705
3775
  warnBlock(
@@ -1,5 +1,5 @@
1
1
  // src/vite-plugin.ts
2
- import { readFileSync, writeFileSync, readdirSync, statSync } from "fs";
2
+ import { readFileSync, writeFileSync, unlinkSync, readdirSync, statSync } from "fs";
3
3
  import { join, relative } from "path";
4
4
  import processImport from "process";
5
5
 
@@ -2766,10 +2766,6 @@ function expandModeAwareColorClasses(classString, colors) {
2766
2766
  out.push(token);
2767
2767
  continue;
2768
2768
  }
2769
- if (parsed.modifiers.includes("dark") || parsed.modifiers.includes("light")) {
2770
- out.push(token);
2771
- continue;
2772
- }
2773
2769
  const slashIdx = parsed.value.indexOf("/");
2774
2770
  const colorPart = slashIdx > 0 ? parsed.value.slice(0, slashIdx) : parsed.value;
2775
2771
  const opacitySuffix = slashIdx > 0 ? parsed.value.slice(slashIdx) : "";
@@ -2781,6 +2777,12 @@ function expandModeAwareColorClasses(classString, colors) {
2781
2777
  changed = true;
2782
2778
  const bang = parsed.important ? "!" : "";
2783
2779
  const modPrefix = parsed.modifiers.map((m) => `${m}:`).join("");
2780
+ const explicitScheme = parsed.modifiers.map((m) => getModifier(m)?.darkScheme).find((s) => s);
2781
+ if (explicitScheme) {
2782
+ const side = explicitScheme === "dark" ? pair.dark : pair.light;
2783
+ out.push(`${bang}${modPrefix}${parsed.utility}-[${side}]${opacitySuffix}`);
2784
+ continue;
2785
+ }
2784
2786
  out.push(`${bang}${modPrefix}${parsed.utility}-[${pair.light}]${opacitySuffix}`);
2785
2787
  out.push(`${bang}dark:${modPrefix}${parsed.utility}-[${pair.dark}]${opacitySuffix}`);
2786
2788
  }
@@ -3162,6 +3164,48 @@ function makePluginAPI(theme) {
3162
3164
  }
3163
3165
  var customVariants = configStore.customVariants;
3164
3166
 
3167
+ // src/core/generateTypesDts.ts
3168
+ var HEADER = `// AUTO-GENERATED by Kbach from your kbach.config.js \u2014 do not edit by hand.
3169
+ // Regenerated automatically every time your dev server / Metro picks up a
3170
+ // change to that file. Safe (and recommended) to add to .gitignore.
3171
+ //
3172
+ // Gives useColors()/useSpacing() full autocomplete and typo-catching for your
3173
+ // custom colors/spacing keys with zero manual setup \u2014 see KbachCustomColors'
3174
+ // doc comment in @kbach/react if you'd rather hand-author this instead.
3175
+ import '@kbach/react';
3176
+ `;
3177
+ var IDENT_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
3178
+ function propKey(name) {
3179
+ return IDENT_RE.test(name) ? name : JSON.stringify(name);
3180
+ }
3181
+ function generateKbachTypesDts(theme) {
3182
+ const colorLines = [];
3183
+ for (const [name, value] of Object.entries(theme.colors)) {
3184
+ if (name in defaultColors) continue;
3185
+ const isFlat = typeof value === "string" || isModeAwareColor(value);
3186
+ colorLines.push(` ${propKey(name)}: ${isFlat ? "string" : "ColorScale"};`);
3187
+ }
3188
+ const spacingLines = [];
3189
+ for (const key of Object.keys(theme.spacing)) {
3190
+ if (key in defaultTheme.spacing) continue;
3191
+ spacingLines.push(` ${propKey(key)}: true;`);
3192
+ }
3193
+ if (colorLines.length === 0 && spacingLines.length === 0) return "";
3194
+ const body = [
3195
+ colorLines.length > 0 ? ` interface KbachCustomColors {
3196
+ ${colorLines.join("\n")}
3197
+ }` : null,
3198
+ spacingLines.length > 0 ? ` interface KbachCustomSpacing {
3199
+ ${spacingLines.join("\n")}
3200
+ }` : null
3201
+ ].filter((s) => s !== null).join("\n\n");
3202
+ return `${HEADER}
3203
+ declare module '@kbach/react' {
3204
+ ${body}
3205
+ }
3206
+ `;
3207
+ }
3208
+
3165
3209
  // src/vite-plugin.ts
3166
3210
  var process2 = processImport;
3167
3211
  function normPath(p) {
@@ -3182,6 +3226,9 @@ function warn(message) {
3182
3226
  function warnBlock(header, ...lines) {
3183
3227
  console.warn([`${TAG()} ${yellow(header)}`, ...lines].join("\n"));
3184
3228
  }
3229
+ function log(message) {
3230
+ console.log(`${TAG()} ${message}`);
3231
+ }
3185
3232
  function findLineCol(code, needle) {
3186
3233
  const index = code.indexOf(needle);
3187
3234
  if (index === -1) return null;
@@ -3447,6 +3494,28 @@ ${block}` : block;
3447
3494
  writeFileSync(filePath, next, "utf-8");
3448
3495
  return true;
3449
3496
  }
3497
+ function writeKbachTypesDts(root, theme) {
3498
+ const filePath = join(root, "kbach-types.d.ts");
3499
+ const content = generateKbachTypesDts(theme);
3500
+ let existing = null;
3501
+ try {
3502
+ existing = readFileSync(filePath, "utf-8");
3503
+ } catch {
3504
+ }
3505
+ if (content === (existing ?? "")) return;
3506
+ try {
3507
+ if (content === "") {
3508
+ unlinkSync(filePath);
3509
+ } else {
3510
+ const isNew = existing === null;
3511
+ writeFileSync(filePath, content, "utf-8");
3512
+ if (isNew) {
3513
+ log(`Generated kbach-types.d.ts \u2014 gives useColors()/useSpacing() autocomplete for your custom colors/spacing keys. Safe to add to .gitignore.`);
3514
+ }
3515
+ }
3516
+ } catch {
3517
+ }
3518
+ }
3450
3519
  function toNumericScreens(screens) {
3451
3520
  const out = {};
3452
3521
  for (const [k, v] of Object.entries(screens)) {
@@ -3666,6 +3735,7 @@ function kbach(userConfigOrOptions) {
3666
3735
  },
3667
3736
  configResolved(resolved) {
3668
3737
  root = resolved.root;
3738
+ writeKbachTypesDts(root, cfg.theme);
3669
3739
  const names = new Set(resolved.plugins.map((p) => p.name));
3670
3740
  if (names.has("react-router") && names.has("vite:react-refresh")) {
3671
3741
  warnBlock(
package/kbach-react.md CHANGED
@@ -189,7 +189,9 @@ colors.white // '#ffffff'
189
189
  colors['white/20'] // 'rgba(255,255,255,0.2)'
190
190
  colors.alpha('#ff6b35', 60) // 'rgba(255,107,53,0.6)'
191
191
  ```
192
- Typed against the built-in theme by default — `colors.blu` (typo) is a compile error. Custom `kbach.config.js` colors aren't visible to TypeScript (runtime-loaded plain `.js`), so either widen per call — `useColors<DefaultColorName | 'brand'>()` — or, better, augment once project-wide in any `.d.ts` your tsconfig includes:
192
+ Typed against the built-in theme by default — `colors.blu` (typo) is a compile error. Custom `kbach.config.js` colors work too, with **zero setup**: the Vite plugin (and `@kbach/native`'s Babel plugin) automatically generate a `kbach-types.d.ts` next to your config, kept in sync every time the dev server / Metro picks up an edit to it — safe to add to `.gitignore`. `useColors()`/`useSpacing()` see your custom names immediately, no type parameter needed, same typo-catching as the built-in ones.
193
+
194
+ If you'd rather commit the types instead of generating them (a library package with no dev server/bundler step of its own, for instance), hand-author the same thing in any `.d.ts` your tsconfig includes:
193
195
 
194
196
  ```ts
195
197
  import '@kbach/react'; // or '@kbach/native' — either works, native re-exports react's types
@@ -199,9 +201,12 @@ declare module '@kbach/react' {
199
201
  accent: string; // a flat color, like the built-in white/black — also what a
200
202
  // mode-aware { light, dark } config color resolves to at read time
201
203
  }
204
+ interface KbachCustomSpacing {
205
+ 18: true; // only the key is read — value is just a placeholder
206
+ }
202
207
  }
203
208
  ```
204
- Every `useColors()` call in the project then sees `brand`/`accent` automatically, no type parameter needed — same typo-catching as the built-in names. `useSpacing()` has the equivalent `KbachCustomSpacing` interface (`interface KbachCustomSpacing { 18: true }` — only the key is read) for the spacing scale (`spacing[4]` → `16`, `spacing.full` → `'100%'`).
209
+ A hand-authored file and the generated one both merge into the exact same interfaces, so either — or both at once — works.
205
210
 
206
211
  ---
207
212
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.6.35",
3
+ "version": "0.6.36",
4
4
  "description": "React / React Native components and hooks for the Kbach framework",
5
5
  "repository": {
6
6
  "type": "git",