@kbach/react 1.0.0-beta.3 → 1.0.0-beta.5

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.
package/dist/index.js CHANGED
@@ -806,7 +806,7 @@ var warnedMultipleProviders = false;
806
806
  function ThemeProvider({ children, defaultMode = "system" }) {
807
807
  (0, import_react2.useEffect)(() => {
808
808
  mountedProviderCount++;
809
- if (mountedProviderCount > 1 && !warnedMultipleProviders) {
809
+ if (mountedProviderCount > 1 && !warnedMultipleProviders && process.env.NODE_ENV !== "production") {
810
810
  warnedMultipleProviders = true;
811
811
  console.warn(
812
812
  "[kbach] Multiple <ThemeProvider> instances are mounted at once. Dark-mode state is one global store useTheme() reads everywhere, not scoped per-provider, so their defaultMode seeds will fight over the same state."
package/dist/index.mjs CHANGED
@@ -134,7 +134,7 @@ var warnedMultipleProviders = false;
134
134
  function ThemeProvider({ children, defaultMode = "system" }) {
135
135
  useEffect(() => {
136
136
  mountedProviderCount++;
137
- if (mountedProviderCount > 1 && !warnedMultipleProviders) {
137
+ if (mountedProviderCount > 1 && !warnedMultipleProviders && process.env.NODE_ENV !== "production") {
138
138
  warnedMultipleProviders = true;
139
139
  console.warn(
140
140
  "[kbach] Multiple <ThemeProvider> instances are mounted at once. Dark-mode state is one global store useTheme() reads everywhere, not scoped per-provider, so their defaultMode seeds will fight over the same state."
@@ -28,6 +28,7 @@ interface KbachPluginOptions {
28
28
  */
29
29
  safelist?: string[];
30
30
  }
31
+ declare const CSS_SYNC_DEBOUNCE_MS = 50;
31
32
  /**
32
33
  * Build-time static CSS generation for Vite — scans source files for
33
34
  * Kbach class strings, resolves them through the same Rust engine the
@@ -39,4 +40,4 @@ interface KbachPluginOptions {
39
40
  */
40
41
  declare function kbach(options?: KbachPluginOptions): Plugin;
41
42
 
42
- export { type KbachPluginOptions, kbach };
43
+ export { CSS_SYNC_DEBOUNCE_MS, type KbachPluginOptions, kbach };
@@ -28,6 +28,7 @@ interface KbachPluginOptions {
28
28
  */
29
29
  safelist?: string[];
30
30
  }
31
+ declare const CSS_SYNC_DEBOUNCE_MS = 50;
31
32
  /**
32
33
  * Build-time static CSS generation for Vite — scans source files for
33
34
  * Kbach class strings, resolves them through the same Rust engine the
@@ -39,4 +40,4 @@ interface KbachPluginOptions {
39
40
  */
40
41
  declare function kbach(options?: KbachPluginOptions): Plugin;
41
42
 
42
- export { type KbachPluginOptions, kbach };
43
+ export { CSS_SYNC_DEBOUNCE_MS, type KbachPluginOptions, kbach };
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/vite-plugin/index.ts
21
21
  var vite_plugin_exports = {};
22
22
  __export(vite_plugin_exports, {
23
+ CSS_SYNC_DEBOUNCE_MS: () => CSS_SYNC_DEBOUNCE_MS,
23
24
  kbach: () => kbach
24
25
  });
25
26
  module.exports = __toCommonJS(vite_plugin_exports);
@@ -36,6 +37,82 @@ function generateCssForToken(token, themeJson) {
36
37
  // src/vite-plugin/scan.ts
37
38
  var import_fs = require("fs");
38
39
  var import_path = require("path");
40
+ function stripComments(code) {
41
+ let out = "";
42
+ let i = 0;
43
+ const n = code.length;
44
+ while (i < n) {
45
+ const two = code[i] + (code[i + 1] ?? "");
46
+ if (two === "//") {
47
+ i += 2;
48
+ while (i < n && code[i] !== "\n") i++;
49
+ continue;
50
+ }
51
+ if (two === "/*") {
52
+ i += 2;
53
+ while (i < n && code[i] + (code[i + 1] ?? "") !== "*/") i++;
54
+ i += 2;
55
+ continue;
56
+ }
57
+ const ch = code[i];
58
+ if (ch === '"' || ch === "'") {
59
+ out += ch;
60
+ i++;
61
+ while (i < n && code[i] !== ch) {
62
+ if (code[i] === "\\") {
63
+ out += code[i] + (code[i + 1] ?? "");
64
+ i += 2;
65
+ continue;
66
+ }
67
+ out += code[i];
68
+ i++;
69
+ }
70
+ out += code[i] ?? "";
71
+ i++;
72
+ continue;
73
+ }
74
+ if (ch === "`") {
75
+ out += ch;
76
+ i++;
77
+ let interpolationDepth = 0;
78
+ while (i < n) {
79
+ if (interpolationDepth === 0 && code[i] === "`") {
80
+ out += code[i];
81
+ i++;
82
+ break;
83
+ }
84
+ if (interpolationDepth === 0 && code[i] === "\\") {
85
+ out += code[i] + (code[i + 1] ?? "");
86
+ i += 2;
87
+ continue;
88
+ }
89
+ if (interpolationDepth === 0 && code[i] + (code[i + 1] ?? "") === "${") {
90
+ out += "${";
91
+ i += 2;
92
+ interpolationDepth = 1;
93
+ continue;
94
+ }
95
+ if (interpolationDepth > 0) {
96
+ if (code[i] === "{") interpolationDepth++;
97
+ else if (code[i] === "}") {
98
+ interpolationDepth--;
99
+ if (interpolationDepth === 0) {
100
+ out += "}";
101
+ i++;
102
+ continue;
103
+ }
104
+ }
105
+ }
106
+ out += code[i];
107
+ i++;
108
+ }
109
+ continue;
110
+ }
111
+ out += ch;
112
+ i++;
113
+ }
114
+ return out;
115
+ }
39
116
  function splitClassTokens(str) {
40
117
  return str.split(/\s+/).filter(Boolean);
41
118
  }
@@ -64,13 +141,15 @@ function pushClassLikeStrings(text, into) {
64
141
  }
65
142
  var JSX_TAG_RE = /<([a-z][a-zA-Z0-9]*)[\s/>]/g;
66
143
  function scanUsedTags(code) {
144
+ const cleaned = stripComments(code);
67
145
  const tags = /* @__PURE__ */ new Set();
68
146
  let m;
69
147
  JSX_TAG_RE.lastIndex = 0;
70
- while ((m = JSX_TAG_RE.exec(code)) !== null) tags.add(m[1]);
148
+ while ((m = JSX_TAG_RE.exec(cleaned)) !== null) tags.add(m[1]);
71
149
  return tags;
72
150
  }
73
- function extractClassStrings(code) {
151
+ function extractClassStrings(rawCode) {
152
+ const code = stripComments(rawCode);
74
153
  const found = /* @__PURE__ */ new Set();
75
154
  const simpleRe = /(?:className|kb)=(["'])((?:(?!\1).)*)\1/g;
76
155
  let m;
@@ -1018,6 +1097,41 @@ var STRUCTURAL_TOKENS = [
1018
1097
  "aria-required:",
1019
1098
  "aria-selected:",
1020
1099
  "aria-busy:",
1100
+ // registry.rs modifiers, added later — same "fixed/static, finite" bar
1101
+ // as the Phase 24 block above. Structural -of-type/empty, form-state and
1102
+ // validation pseudo-classes, other native pseudo-classes with no aria-*
1103
+ // counterpart, a pseudo-element, pointer media variants, and the
1104
+ // direct-children/all-descendants variants. "supports-[...]:"/"in-*:" are
1105
+ // deliberately excluded here for the same "no finite enumerable set"
1106
+ // reason as has-[...]:/group-<pseudo>: above.
1107
+ "first-of-type:",
1108
+ "last-of-type:",
1109
+ "only-of-type:",
1110
+ "empty:",
1111
+ "enabled:",
1112
+ "read-only:",
1113
+ "required:",
1114
+ "optional:",
1115
+ "valid:",
1116
+ "invalid:",
1117
+ "in-range:",
1118
+ "out-of-range:",
1119
+ "default:",
1120
+ "indeterminate:",
1121
+ "placeholder-shown:",
1122
+ "autofill:",
1123
+ "user-valid:",
1124
+ "user-invalid:",
1125
+ "target:",
1126
+ "fullscreen:",
1127
+ "popover-open:",
1128
+ "details-content:",
1129
+ "pointer-coarse:",
1130
+ "pointer-fine:",
1131
+ "any-pointer-coarse:",
1132
+ "any-pointer-fine:",
1133
+ "*:",
1134
+ "**:",
1021
1135
  // Phase 25 (container queries & arbitrary properties). "@container" is a
1022
1136
  // real UTILITY (container-type: inline-size), not a modifier — included
1023
1137
  // as a bare token like "flex"/"hidden" above, not with the trailing ":"
@@ -1557,6 +1671,7 @@ function resolveKbachConfig(config) {
1557
1671
 
1558
1672
  // src/vite-plugin/index.ts
1559
1673
  var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components"];
1674
+ var CSS_SYNC_DEBOUNCE_MS = 50;
1560
1675
  function normPath(p) {
1561
1676
  const fwd = p.replace(/\\/g, "/");
1562
1677
  return process.platform === "win32" ? fwd.toLowerCase() : fwd;
@@ -1650,6 +1765,14 @@ function kbach(options = {}) {
1650
1765
  const changed = writeKbachToFile(mainCSSFile(), generateCSS(), readFile, writeFile);
1651
1766
  if (changed && server) server.watcher.emit("change", mainCSSFile());
1652
1767
  }
1768
+ let syncTimer = null;
1769
+ function scheduleSync(server) {
1770
+ if (syncTimer) clearTimeout(syncTimer);
1771
+ syncTimer = setTimeout(() => {
1772
+ syncTimer = null;
1773
+ syncMainCSSFile(server);
1774
+ }, CSS_SYNC_DEBOUNCE_MS);
1775
+ }
1653
1776
  function initialScan() {
1654
1777
  for (const dir of includeDirs) {
1655
1778
  scanDir((0, import_path3.join)(root, dir), processFile);
@@ -1693,7 +1816,7 @@ function kbach(options = {}) {
1693
1816
  fileTags.delete(normPath(file));
1694
1817
  }
1695
1818
  }
1696
- syncMainCSSFile(server);
1819
+ scheduleSync(server);
1697
1820
  };
1698
1821
  server.watcher.on("add", (file) => onAddOrUnlink(file, false));
1699
1822
  server.watcher.on("unlink", (file) => onAddOrUnlink(file, true));
@@ -1707,11 +1830,12 @@ function kbach(options = {}) {
1707
1830
  fileTokens.delete(normPath(file));
1708
1831
  fileTags.delete(normPath(file));
1709
1832
  }
1710
- syncMainCSSFile(server);
1833
+ scheduleSync(server);
1711
1834
  }
1712
1835
  };
1713
1836
  }
1714
1837
  // Annotate the CommonJS export names for ESM import in node:
1715
1838
  0 && (module.exports = {
1839
+ CSS_SYNC_DEBOUNCE_MS,
1716
1840
  kbach
1717
1841
  });
@@ -20,6 +20,82 @@ function generateCssForToken(token, themeJson) {
20
20
  // src/vite-plugin/scan.ts
21
21
  import { readdirSync, readFileSync, statSync } from "fs";
22
22
  import { join } from "path";
23
+ function stripComments(code) {
24
+ let out = "";
25
+ let i = 0;
26
+ const n = code.length;
27
+ while (i < n) {
28
+ const two = code[i] + (code[i + 1] ?? "");
29
+ if (two === "//") {
30
+ i += 2;
31
+ while (i < n && code[i] !== "\n") i++;
32
+ continue;
33
+ }
34
+ if (two === "/*") {
35
+ i += 2;
36
+ while (i < n && code[i] + (code[i + 1] ?? "") !== "*/") i++;
37
+ i += 2;
38
+ continue;
39
+ }
40
+ const ch = code[i];
41
+ if (ch === '"' || ch === "'") {
42
+ out += ch;
43
+ i++;
44
+ while (i < n && code[i] !== ch) {
45
+ if (code[i] === "\\") {
46
+ out += code[i] + (code[i + 1] ?? "");
47
+ i += 2;
48
+ continue;
49
+ }
50
+ out += code[i];
51
+ i++;
52
+ }
53
+ out += code[i] ?? "";
54
+ i++;
55
+ continue;
56
+ }
57
+ if (ch === "`") {
58
+ out += ch;
59
+ i++;
60
+ let interpolationDepth = 0;
61
+ while (i < n) {
62
+ if (interpolationDepth === 0 && code[i] === "`") {
63
+ out += code[i];
64
+ i++;
65
+ break;
66
+ }
67
+ if (interpolationDepth === 0 && code[i] === "\\") {
68
+ out += code[i] + (code[i + 1] ?? "");
69
+ i += 2;
70
+ continue;
71
+ }
72
+ if (interpolationDepth === 0 && code[i] + (code[i + 1] ?? "") === "${") {
73
+ out += "${";
74
+ i += 2;
75
+ interpolationDepth = 1;
76
+ continue;
77
+ }
78
+ if (interpolationDepth > 0) {
79
+ if (code[i] === "{") interpolationDepth++;
80
+ else if (code[i] === "}") {
81
+ interpolationDepth--;
82
+ if (interpolationDepth === 0) {
83
+ out += "}";
84
+ i++;
85
+ continue;
86
+ }
87
+ }
88
+ }
89
+ out += code[i];
90
+ i++;
91
+ }
92
+ continue;
93
+ }
94
+ out += ch;
95
+ i++;
96
+ }
97
+ return out;
98
+ }
23
99
  function splitClassTokens(str) {
24
100
  return str.split(/\s+/).filter(Boolean);
25
101
  }
@@ -48,13 +124,15 @@ function pushClassLikeStrings(text, into) {
48
124
  }
49
125
  var JSX_TAG_RE = /<([a-z][a-zA-Z0-9]*)[\s/>]/g;
50
126
  function scanUsedTags(code) {
127
+ const cleaned = stripComments(code);
51
128
  const tags = /* @__PURE__ */ new Set();
52
129
  let m;
53
130
  JSX_TAG_RE.lastIndex = 0;
54
- while ((m = JSX_TAG_RE.exec(code)) !== null) tags.add(m[1]);
131
+ while ((m = JSX_TAG_RE.exec(cleaned)) !== null) tags.add(m[1]);
55
132
  return tags;
56
133
  }
57
- function extractClassStrings(code) {
134
+ function extractClassStrings(rawCode) {
135
+ const code = stripComments(rawCode);
58
136
  const found = /* @__PURE__ */ new Set();
59
137
  const simpleRe = /(?:className|kb)=(["'])((?:(?!\1).)*)\1/g;
60
138
  let m;
@@ -919,6 +997,41 @@ var STRUCTURAL_TOKENS = [
919
997
  "aria-required:",
920
998
  "aria-selected:",
921
999
  "aria-busy:",
1000
+ // registry.rs modifiers, added later — same "fixed/static, finite" bar
1001
+ // as the Phase 24 block above. Structural -of-type/empty, form-state and
1002
+ // validation pseudo-classes, other native pseudo-classes with no aria-*
1003
+ // counterpart, a pseudo-element, pointer media variants, and the
1004
+ // direct-children/all-descendants variants. "supports-[...]:"/"in-*:" are
1005
+ // deliberately excluded here for the same "no finite enumerable set"
1006
+ // reason as has-[...]:/group-<pseudo>: above.
1007
+ "first-of-type:",
1008
+ "last-of-type:",
1009
+ "only-of-type:",
1010
+ "empty:",
1011
+ "enabled:",
1012
+ "read-only:",
1013
+ "required:",
1014
+ "optional:",
1015
+ "valid:",
1016
+ "invalid:",
1017
+ "in-range:",
1018
+ "out-of-range:",
1019
+ "default:",
1020
+ "indeterminate:",
1021
+ "placeholder-shown:",
1022
+ "autofill:",
1023
+ "user-valid:",
1024
+ "user-invalid:",
1025
+ "target:",
1026
+ "fullscreen:",
1027
+ "popover-open:",
1028
+ "details-content:",
1029
+ "pointer-coarse:",
1030
+ "pointer-fine:",
1031
+ "any-pointer-coarse:",
1032
+ "any-pointer-fine:",
1033
+ "*:",
1034
+ "**:",
922
1035
  // Phase 25 (container queries & arbitrary properties). "@container" is a
923
1036
  // real UTILITY (container-type: inline-size), not a modifier — included
924
1037
  // as a bare token like "flex"/"hidden" above, not with the trailing ":"
@@ -1042,6 +1155,7 @@ function buildClassNameHintsDts(tokens) {
1042
1155
 
1043
1156
  // src/vite-plugin/index.ts
1044
1157
  var DEFAULT_SCAN_DIRS = ["src", "app", "pages", "components"];
1158
+ var CSS_SYNC_DEBOUNCE_MS = 50;
1045
1159
  function normPath(p) {
1046
1160
  const fwd = p.replace(/\\/g, "/");
1047
1161
  return process.platform === "win32" ? fwd.toLowerCase() : fwd;
@@ -1135,6 +1249,14 @@ function kbach(options = {}) {
1135
1249
  const changed = writeKbachToFile(mainCSSFile(), generateCSS(), readFile, writeFile);
1136
1250
  if (changed && server) server.watcher.emit("change", mainCSSFile());
1137
1251
  }
1252
+ let syncTimer = null;
1253
+ function scheduleSync(server) {
1254
+ if (syncTimer) clearTimeout(syncTimer);
1255
+ syncTimer = setTimeout(() => {
1256
+ syncTimer = null;
1257
+ syncMainCSSFile(server);
1258
+ }, CSS_SYNC_DEBOUNCE_MS);
1259
+ }
1138
1260
  function initialScan() {
1139
1261
  for (const dir of includeDirs) {
1140
1262
  scanDir(join3(root, dir), processFile);
@@ -1178,7 +1300,7 @@ function kbach(options = {}) {
1178
1300
  fileTags.delete(normPath(file));
1179
1301
  }
1180
1302
  }
1181
- syncMainCSSFile(server);
1303
+ scheduleSync(server);
1182
1304
  };
1183
1305
  server.watcher.on("add", (file) => onAddOrUnlink(file, false));
1184
1306
  server.watcher.on("unlink", (file) => onAddOrUnlink(file, true));
@@ -1192,10 +1314,11 @@ function kbach(options = {}) {
1192
1314
  fileTokens.delete(normPath(file));
1193
1315
  fileTags.delete(normPath(file));
1194
1316
  }
1195
- syncMainCSSFile(server);
1317
+ scheduleSync(server);
1196
1318
  }
1197
1319
  };
1198
1320
  }
1199
1321
  export {
1322
+ CSS_SYNC_DEBOUNCE_MS,
1200
1323
  kbach
1201
1324
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.5",
4
4
  "description": "A utility-first styling engine for React, powered by a real Rust/WASM core — Tailwind CSS v4 feature parity with Kbach's own color palette.",
5
5
  "keywords": ["css", "react", "tailwind", "utility-first", "wasm", "styling"],
6
6
  "license": "MIT",
@@ -56,7 +56,7 @@
56
56
  "generate:colors": "node ../core-engine/scripts/generate-palette.mjs src/generatedPalette.ts"
57
57
  },
58
58
  "dependencies": {
59
- "@kbach/core-engine": "1.0.0-beta.4"
59
+ "@kbach/core-engine": "1.0.0-beta.6"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/react": "^19.2.0",