@kbach/react 0.5.38 → 0.5.40

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.
@@ -3,7 +3,7 @@ import {
3
3
  generateClassCSS,
4
4
  setCSSGenMode,
5
5
  splitClassTokens
6
- } from "./chunk-NWVL4BES.mjs";
6
+ } from "./chunk-IZ3W6G3V.mjs";
7
7
 
8
8
  // src/vite-plugin.ts
9
9
  import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "fs";
@@ -31,12 +31,18 @@ function classifyToken(token) {
31
31
  return "Utilities";
32
32
  }
33
33
  function buildColorVarMap(theme, usedCSS) {
34
+ const usedHexValues = /* @__PURE__ */ new Set();
35
+ const hexRe = /#[0-9a-fA-F]{3,8}\b/g;
36
+ let hm;
37
+ while ((hm = hexRe.exec(usedCSS)) !== null) usedHexValues.add(hm[0].toLowerCase());
34
38
  const map = /* @__PURE__ */ new Map();
35
39
  for (const [name, shades] of Object.entries(theme.colors ?? {})) {
36
40
  const entries = typeof shades === "string" ? [[name, shades]] : Object.entries(shades).map(([s, v]) => [`${name}-${s}`, v]);
37
41
  for (const [fullName, val] of entries) {
38
- if (typeof val === "string" && /^#[0-9a-fA-F]{3,8}$/.test(val) && usedCSS.includes(val)) {
39
- map.set(val, `--color-${fullName}`);
42
+ if (typeof val === "string" && /^#[0-9a-fA-F]{3,8}$/.test(val)) {
43
+ if (usedHexValues.has(val.toLowerCase())) {
44
+ map.set(val, `--color-${fullName}`);
45
+ }
40
46
  }
41
47
  }
42
48
  }
@@ -116,6 +122,37 @@ function toNumericScreens(screens) {
116
122
  }
117
123
  return out;
118
124
  }
125
+ function extractClassStrings(code) {
126
+ const found = /* @__PURE__ */ new Set();
127
+ const simpleRe = /(?:className|kb)=["']([^"']+)["']/g;
128
+ let m;
129
+ 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];
135
+ const innerStrings = block.match(/["'`]([^"'`]{1,300})["'`]/g) ?? [];
136
+ for (const s of innerStrings) pushTokens(s.slice(1, -1), found);
137
+ }
138
+ const clsxRe = /(?:clsx|cn|classnames)\(([^)]{1,1000})\)/g;
139
+ while ((m = clsxRe.exec(code)) !== null) {
140
+ const block = m[1];
141
+ const innerStrings = block.match(/["'`]([^"'`]{1,300})["'`]/g) ?? [];
142
+ for (const s of innerStrings) pushTokens(s.slice(1, -1), found);
143
+ }
144
+ const templateRe = /`([^`]{1,2000})`/g;
145
+ while ((m = templateRe.exec(code)) !== null) {
146
+ const staticParts = m[1].replace(/\$\{[^}]*\}/g, " ");
147
+ pushTokens(staticParts, found);
148
+ }
149
+ return [...found];
150
+ }
151
+ function pushTokens(str, into) {
152
+ for (const tok of splitClassTokens(str)) {
153
+ if (tok) into.add(tok);
154
+ }
155
+ }
119
156
  function scanDir(dir, onFile) {
120
157
  try {
121
158
  for (const entry of readdirSync(dir)) {
@@ -124,41 +161,49 @@ function scanDir(dir, onFile) {
124
161
  try {
125
162
  const st = statSync(full);
126
163
  if (st.isDirectory()) scanDir(full, onFile);
127
- else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(readFileSync(full, "utf-8"));
164
+ else if (/\.(tsx?|jsx?)$/.test(entry)) onFile(full, readFileSync(full, "utf-8"));
128
165
  } catch {
129
166
  }
130
167
  }
131
168
  } catch {
132
169
  }
133
170
  }
134
- function extractClassStrings(code) {
135
- const found = [];
136
- const re = /(?:className|kb)=(?:["']([^"']+)["']|\{["']([^"']+)["']\})/g;
137
- let m;
138
- while ((m = re.exec(code)) !== null) {
139
- const val = m[1] ?? m[2];
140
- if (val) found.push(val);
141
- }
142
- return found;
143
- }
144
171
  function kbach(userConfig) {
145
172
  const cfg = buildConfig(userConfig ?? {});
146
173
  const screens = toNumericScreens(cfg.theme.screens ?? {});
147
174
  let root = process.cwd();
148
175
  let mainCSSFile = null;
149
- function generateCSS() {
150
- const tokenCSS = /* @__PURE__ */ new Map();
176
+ const fileTokens = /* @__PURE__ */ new Map();
177
+ const tokenCSS = /* @__PURE__ */ new Map();
178
+ function processFile(filePath, code) {
179
+ const tokens = /* @__PURE__ */ new Set();
180
+ for (const cls of extractClassStrings(code)) {
181
+ for (const tok of splitClassTokens(cls)) {
182
+ tokens.add(tok);
183
+ if (!tokenCSS.has(tok)) {
184
+ tokenCSS.set(tok, generateClassCSS(tok, cfg.theme, cfg.darkMode, screens));
185
+ }
186
+ }
187
+ }
188
+ fileTokens.set(filePath, tokens);
189
+ }
190
+ function buildTokenCSSView() {
191
+ const active = /* @__PURE__ */ new Set();
192
+ for (const tokens of fileTokens.values()) for (const t of tokens) active.add(t);
193
+ const view = /* @__PURE__ */ new Map();
194
+ for (const tok of active) {
195
+ const css = tokenCSS.get(tok);
196
+ if (css !== void 0) view.set(tok, css);
197
+ }
198
+ return view;
199
+ }
200
+ function initialScan() {
151
201
  for (const dir of ["src", "app", "pages", "components", "views", "layouts"]) {
152
- scanDir(join(root, dir), (code) => {
153
- for (const cls of extractClassStrings(code))
154
- for (const token of splitClassTokens(cls))
155
- if (!tokenCSS.has(token)) {
156
- const css = generateClassCSS(token, cfg.theme, cfg.darkMode, screens);
157
- tokenCSS.set(token, css);
158
- }
159
- });
202
+ scanDir(join(root, dir), processFile);
160
203
  }
161
- return formatKbachCSS(tokenCSS, cfg.theme);
204
+ }
205
+ function generateCSS() {
206
+ return formatKbachCSS(buildTokenCSSView(), cfg.theme);
162
207
  }
163
208
  return {
164
209
  name: "kbach",
@@ -168,10 +213,16 @@ function kbach(userConfig) {
168
213
  },
169
214
  buildStart() {
170
215
  mainCSSFile = findMainCSSFile(root);
216
+ initialScan();
171
217
  if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
172
218
  },
173
219
  handleHotUpdate({ file }) {
174
220
  if (file.includes("node_modules") || !/\.(tsx?|jsx?)$/.test(file)) return;
221
+ try {
222
+ processFile(file, readFileSync(file, "utf-8"));
223
+ } catch {
224
+ fileTokens.delete(file);
225
+ }
175
226
  if (mainCSSFile) writeKbachToFile(mainCSSFile, generateCSS());
176
227
  }
177
228
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.5.38",
3
+ "version": "0.5.40",
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",
@@ -72,6 +72,8 @@
72
72
  "files": [
73
73
  "dist",
74
74
  "scripts",
75
- "kbach-react.md"
75
+ "types.d.ts",
76
+ "kbach-react.md",
77
+ "README.md"
76
78
  ]
77
79
  }
package/types.d.ts ADDED
@@ -0,0 +1 @@
1
+ /// <reference path="./src/kbach-env.d.ts" />
@@ -1,70 +0,0 @@
1
- type ThemeMode = 'light' | 'dark' | 'system';
2
- interface StyleValue {
3
- [key: string]: string | number | undefined | null | StyleValue | unknown[];
4
- }
5
- interface ParsedClass {
6
- /** The original class string, e.g. "dark:hover:bg-[#fff]" */
7
- original: string;
8
- /** Up to 3 modifier prefixes, e.g. ['dark', 'hover'] */
9
- modifiers: string[];
10
- /** Whether a leading `-` was present for negative values */
11
- negative: boolean;
12
- /** Whether a leading `!` was present — applies !important to all CSS declarations */
13
- important: boolean;
14
- /** The utility name, e.g. 'bg', 'p', 'text' */
15
- utility: string;
16
- /** The resolved value, e.g. 'white', '#fff', '4' */
17
- value: string;
18
- /** Whether the value was specified with bracket notation [value] */
19
- isArbitrary: boolean;
20
- }
21
- interface ResolvedStyle {
22
- /** Base styles applied unconditionally */
23
- base?: StyleValue;
24
- /** Styles keyed by modifier or modifier combo, e.g. 'dark', 'hover', 'dark:hover' */
25
- [modifierKey: string]: StyleValue | undefined;
26
- }
27
- type ColorShades = Record<string, string>;
28
- type ThemeColors = Record<string, string | ColorShades>;
29
- type ThemeSpacing = Record<string, number | string>;
30
- interface ThemeConfig {
31
- colors: ThemeColors;
32
- spacing: ThemeSpacing;
33
- fontSize: Record<string, number | string>;
34
- fontFamily: Record<string, string | string[]>;
35
- fontWeight: Record<string, string | number>;
36
- borderRadius: Record<string, number | string>;
37
- borderWidth: Record<string, number>;
38
- opacity: Record<string, number>;
39
- lineHeight: Record<string, number | string>;
40
- letterSpacing: Record<string, number | string>;
41
- zIndex: Record<string, number | string>;
42
- flex: Record<string, number>;
43
- shadow: Record<string, StyleValue>;
44
- screens: Record<string, string | number>;
45
- [key: string]: unknown;
46
- }
47
- type DarkMode = 'attribute' | 'class' | 'media';
48
- interface PluginAPI {
49
- addUtility(name: string, styles: StyleValue): void;
50
- addVariant(name: string, selector: string): void;
51
- theme(path: string, defaultValue?: unknown): unknown;
52
- e(className: string): string;
53
- }
54
- interface FrameworkConfig {
55
- darkMode?: DarkMode;
56
- theme?: Partial<ThemeConfig>;
57
- /** Additive theme extension — accepts either `extend.theme.X` or `extend.X` directly. */
58
- extend?: {
59
- theme?: Partial<ThemeConfig>;
60
- } & Partial<ThemeConfig>;
61
- plugins?: Array<(api: PluginAPI) => void>;
62
- content?: string[];
63
- }
64
- interface ResolvedConfig {
65
- darkMode: DarkMode;
66
- theme: ThemeConfig;
67
- plugins: Array<(api: PluginAPI) => void>;
68
- }
69
-
70
- export type { FrameworkConfig as F, ParsedClass as P, ResolvedStyle as R, StyleValue as S, ThemeColors as T, ThemeConfig as a, ResolvedConfig as b, ThemeMode as c, PluginAPI as d };
@@ -1,70 +0,0 @@
1
- type ThemeMode = 'light' | 'dark' | 'system';
2
- interface StyleValue {
3
- [key: string]: string | number | undefined | null | StyleValue | unknown[];
4
- }
5
- interface ParsedClass {
6
- /** The original class string, e.g. "dark:hover:bg-[#fff]" */
7
- original: string;
8
- /** Up to 3 modifier prefixes, e.g. ['dark', 'hover'] */
9
- modifiers: string[];
10
- /** Whether a leading `-` was present for negative values */
11
- negative: boolean;
12
- /** Whether a leading `!` was present — applies !important to all CSS declarations */
13
- important: boolean;
14
- /** The utility name, e.g. 'bg', 'p', 'text' */
15
- utility: string;
16
- /** The resolved value, e.g. 'white', '#fff', '4' */
17
- value: string;
18
- /** Whether the value was specified with bracket notation [value] */
19
- isArbitrary: boolean;
20
- }
21
- interface ResolvedStyle {
22
- /** Base styles applied unconditionally */
23
- base?: StyleValue;
24
- /** Styles keyed by modifier or modifier combo, e.g. 'dark', 'hover', 'dark:hover' */
25
- [modifierKey: string]: StyleValue | undefined;
26
- }
27
- type ColorShades = Record<string, string>;
28
- type ThemeColors = Record<string, string | ColorShades>;
29
- type ThemeSpacing = Record<string, number | string>;
30
- interface ThemeConfig {
31
- colors: ThemeColors;
32
- spacing: ThemeSpacing;
33
- fontSize: Record<string, number | string>;
34
- fontFamily: Record<string, string | string[]>;
35
- fontWeight: Record<string, string | number>;
36
- borderRadius: Record<string, number | string>;
37
- borderWidth: Record<string, number>;
38
- opacity: Record<string, number>;
39
- lineHeight: Record<string, number | string>;
40
- letterSpacing: Record<string, number | string>;
41
- zIndex: Record<string, number | string>;
42
- flex: Record<string, number>;
43
- shadow: Record<string, StyleValue>;
44
- screens: Record<string, string | number>;
45
- [key: string]: unknown;
46
- }
47
- type DarkMode = 'attribute' | 'class' | 'media';
48
- interface PluginAPI {
49
- addUtility(name: string, styles: StyleValue): void;
50
- addVariant(name: string, selector: string): void;
51
- theme(path: string, defaultValue?: unknown): unknown;
52
- e(className: string): string;
53
- }
54
- interface FrameworkConfig {
55
- darkMode?: DarkMode;
56
- theme?: Partial<ThemeConfig>;
57
- /** Additive theme extension — accepts either `extend.theme.X` or `extend.X` directly. */
58
- extend?: {
59
- theme?: Partial<ThemeConfig>;
60
- } & Partial<ThemeConfig>;
61
- plugins?: Array<(api: PluginAPI) => void>;
62
- content?: string[];
63
- }
64
- interface ResolvedConfig {
65
- darkMode: DarkMode;
66
- theme: ThemeConfig;
67
- plugins: Array<(api: PluginAPI) => void>;
68
- }
69
-
70
- export type { FrameworkConfig as F, ParsedClass as P, ResolvedStyle as R, StyleValue as S, ThemeColors as T, ThemeConfig as a, ResolvedConfig as b, ThemeMode as c, PluginAPI as d };