@kbach/react 1.0.0-beta.28 → 1.0.0-beta.29

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/AGENTS.md CHANGED
@@ -1,162 +1,214 @@
1
- # `@kbach/react` — reference for AI assistants
2
-
3
- The web binding of [Kbach](https://github.com/semsakadanupol/kbach) — the
4
- same Tailwind-v4-shaped class-name vocabulary as `@kbach/react-native`,
5
- resolved at **build time** into a real CSS file via a shared Rust/WASM
6
- core. This file covers `@kbach/react` only; for the cross-package
7
- overview, `kbach.config.js` reference, the color palette, and how the
8
- underlying engine is built, see the repo root's
9
- [AGENTS.md](https://github.com/semsakadanupol/kbach/blob/main/AGENTS.md).
10
-
11
- > **Before writing a single class, know this — Tailwind familiarity
12
- > actively misleads here:** color shades are `1`–`12` (lightest→darkest),
13
- > not Tailwind's `50`–`950`. `bg-blue-500` is not a Kbach class — it
14
- > silently resolves to nothing (an "Unknown class ... Typo?" dev warning,
15
- > not a visible color, and ONLY if the string was actually reachable by
16
- > the static scanner — see §2). The real class is `bg-blue-6`. Full
17
- > palette: root AGENTS.md §7. Everything else in real Tailwind's utility
18
- > vocabulary works here essentially as-is — unlike `@kbach/react-native`,
19
- > web has the full modifier set with no native-platform gaps.
20
-
21
- ---
22
-
23
- ## 1. Install + wire up the build
24
-
25
- ```sh
26
- npx @kbach/cli init
27
- ```
28
-
29
- Automates everything below — installs `@kbach/react`, patches
30
- `vite.config.ts` or `postcss.config.js` via a format-preserving codemod
31
- (never string templating), and for the PostCSS path ensures the marker
32
- pair exists in `globals.css`. Safe to re-run at any time (idempotent —
33
- reports "already wired up" rather than duplicating an entry). See
34
- [`packages/cli/AGENTS.md`](../cli/AGENTS.md) for exactly what it detects
35
- and changes, and what it does when a codemod can't safely apply (prints
36
- the same manual snippet shown below, rather than guessing).
37
-
38
- The manual steps below are both the CLI's own fallback and the accurate
39
- reference for what it actually does — start here if you're wiring this
40
- up by hand, or the CLI reported it couldn't:
41
-
42
- ```sh
43
- npm install @kbach/react@beta
44
- ```
45
-
46
- **Vite:**
47
-
48
- ```ts
49
- // vite.config.ts
50
- import { defineConfig } from 'vite';
51
- import react from '@vitejs/plugin-react';
52
- import { kbach } from '@kbach/react/vite';
53
-
54
- export default defineConfig({ plugins: [kbach(), react()] });
55
- ```
56
-
57
- **Next.js / any PostCSS bundler:**
58
-
59
- ```js
60
- // postcss.config.js
61
- module.exports = { plugins: { '@kbach/react/postcss': {} } };
62
- ```
63
-
64
- ```css
65
- /* globals.css — the plugin fills between these markers */
66
- /* kbach:start */
67
- /* kbach:end */
68
- ```
69
-
70
- Then import the generated CSS once from your entry point
71
- (`import './kbach.css'` for Vite; `import './globals.css'` for PostCSS) and
72
- write `className="..."` normally.
73
-
74
- ## 2. How resolution works
75
-
76
- 1. At build time the plugin walks your `include` dirs (`src`, `app`,
77
- `pages`, `components` by default), extracts every string that looks like
78
- a class list, and hands each to the WASM engine.
79
- 2. The engine parses each class → emits CSS rules (with the correct
80
- `@media` / selector wrappers for modifiers).
81
- 3. Rules are written between the `/* kbach:start */` / `/* kbach:end */`
82
- markers of a `kbach.css` file. The plugin finds an existing marked file
83
- under your source dirs or the project root, else creates one; pass
84
- `cssFile` to pin the path.
85
- 4. At runtime `className` is a plain string prop — **nothing calls the
86
- engine**, the CSS already exists. No async init.
87
-
88
- ## 3. Runtime pieces (opt-in, these DO call the engine)
89
-
90
- - **`kb(dynamicClassString)`** — resolve a class list assembled from
91
- runtime data (`kb(\`bg-${color}-6\`)`). Requires `await initKbach()` once
92
- before first use. Prefer a `safelist: [...]` plugin entry when the
93
- possible values are known ahead of time (stays fully static).
94
- - **`@kbach/react/jsx-runtime`** — a `jsxImportSource` override that
95
- intercepts *every* element's `className` at runtime instead of relying on
96
- the static scan. Also needs `await initKbach()`.
97
- - **No-build CDN usage is a SEPARATE package, `@kbach/browser`**
98
- (packages/browser), which bundles this package's runtime modules (`kb`,
99
- `theme`, `config`, `colorVariables`, `darkModeStore`, `wasmLoader`)
100
- straight from `src/`. Renaming or changing those modules' exports (or
101
- `wasmLoader.ts`'s internal `initKbachFrom`) breaks that package, and its
102
- own tests catch it. See [packages/browser/AGENTS.md](../browser/AGENTS.md).
103
-
104
- ## 4. Dark mode
105
-
106
- `dark:` classes work through plain CSS with zero setup, following the OS
107
- (`@media (prefers-color-scheme)`).
108
-
109
- For a **manual toggle**, add one line to `kbach.config.js`:
110
-
111
- ```js
112
- export default { darkMode: 'attribute' }; // or 'class'
113
- ```
114
-
115
- Nothing else — the plugin auto-discovers the file, and the runtime reads
116
- the strategy back out of the generated CSS. Then:
117
-
118
- ```tsx
119
- import { useTheme } from '@kbach/react';
120
- const { mode, isDark, toggle, setMode } = useTheme();
121
- ```
122
-
123
- No provider needed (`useTheme` reads a global store). `<ThemeProvider
124
- defaultMode="dark">` only seeds a startup default. Outside React:
125
- `getGlobalDarkMode()` / `toggleGlobalDarkMode()` / `setGlobalThemeMode()`.
126
-
127
- Pure-runtime (`kb()`-only, no build step): call `applyKbachConfig({
128
- darkMode: 'attribute' })` yourself once before first render.
129
-
130
- ## 5. Plugin options (`KbachPluginOptions`)
131
-
132
- `root`, `theme` (a resolved `ThemeConfig`), `config` (a `KbachConfig`, same
133
- shape as `kbach.config.js` — see the root AGENTS.md's config reference),
134
- `include` (dirs to scan), `safelist` (classes to always emit), `cssFile`
135
- (explicit output path). PostCSS: `root` defaults to `process.cwd()`; pass
136
- it if `postcss.config.js` isn't at the project root.
137
-
138
- ## 6. Exports
139
-
140
- `initKbach`, `isKbachReady`, `kb`, `disableRuntimeCSS`,
141
- `isRuntimeCSSDisabled`, `setTheme`, `getTheme`, `defaultTheme`,
142
- `KbachReset`, `resolveKbachConfig`, `applyKbachConfig`, `useTheme`,
143
- `ThemeProvider`, `useColors`, `clsx`, `getGlobalDarkMode`,
144
- `getGlobalThemeMode`, `setGlobalThemeMode`, `toggleGlobalDarkMode`,
145
- `subscribeGlobalDarkMode`. Types: `ThemeConfig`, `ColorEntry`,
146
- `ContainerConfig`, `DarkModeStrategy`, `KbachConfig`, `ThemeState`,
147
- `ThemeProviderProps`, `ThemeMode`, `ColorsAPI`, `ColorScale`, `ClassValue`.
148
-
149
- ## 7. Common failure modes
150
-
151
- - **Build fails after adding/editing `kbach.config.js`** — an unquoted
152
- hyphenated object key (`surface-dim:` instead of `'surface-dim':`) is a
153
- syntax error; `loadConfigFile` (vite-plugin/index.ts) re-throws it
154
- rather than silently falling back to defaults, so the whole build fails
155
- loudly. Quote the key.
156
- - **A dynamic `kb(...)` call returns nothing / throws** — `initKbach()`
157
- hasn't resolved yet. It's async; await it once before the first `kb()`
158
- call, or prefer a static `safelist` entry when the value set is known
159
- ahead of time.
160
- - **A class works in one file but not another** — it's outside every
161
- `include` dir the plugin scans (default: `src`, `app`, `pages`,
162
- `components`). Add the directory via the plugin's `include` option.
1
+ # `@kbach/react` — reference for AI assistants
2
+
3
+ The web binding of [Kbach](https://github.com/semsakadanupol/kbach) — the
4
+ same Tailwind-v4-shaped class-name vocabulary as `@kbach/react-native`,
5
+ resolved at **build time** into a real CSS file via a shared Rust/WASM
6
+ core. This file covers `@kbach/react` only; for the cross-package
7
+ overview, `kbach.config.js` reference, the color palette, and how the
8
+ underlying engine is built, see the repo root's
9
+ [AGENTS.md](https://github.com/semsakadanupol/kbach/blob/main/AGENTS.md).
10
+
11
+ > **Before writing a single class, know this — Tailwind familiarity
12
+ > actively misleads here:** color shades are `1`–`12` (lightest→darkest),
13
+ > not Tailwind's `50`–`950`. `bg-blue-500` is not a Kbach class — it
14
+ > silently resolves to nothing (an "Unknown class ... Typo?" dev warning,
15
+ > not a visible color, and ONLY if the string was actually reachable by
16
+ > the static scanner — see §2). The real class is `bg-blue-6`. Full
17
+ > palette: root AGENTS.md §7. Everything else in real Tailwind's utility
18
+ > vocabulary works here essentially as-is — unlike `@kbach/react-native`,
19
+ > web has the full modifier set with no native-platform gaps.
20
+
21
+ ---
22
+
23
+ ## 1. Install + wire up the build
24
+
25
+ ```sh
26
+ npx @kbach/cli init
27
+ ```
28
+
29
+ Automates everything below — installs `@kbach/react`, patches
30
+ `vite.config.ts` or `postcss.config.js` via a format-preserving codemod
31
+ (never string templating), and for the PostCSS path ensures the marker
32
+ pair exists in `globals.css`. Safe to re-run at any time (idempotent —
33
+ reports "already wired up" rather than duplicating an entry). See
34
+ [`packages/cli/AGENTS.md`](../cli/AGENTS.md) for exactly what it detects
35
+ and changes, and what it does when a codemod can't safely apply (prints
36
+ the same manual snippet shown below, rather than guessing).
37
+
38
+ The manual steps below are both the CLI's own fallback and the accurate
39
+ reference for what it actually does — start here if you're wiring this
40
+ up by hand, or the CLI reported it couldn't:
41
+
42
+ ```sh
43
+ npm install @kbach/react@beta
44
+ ```
45
+
46
+ **Vite:**
47
+
48
+ ```ts
49
+ // vite.config.ts
50
+ import { defineConfig } from 'vite';
51
+ import react from '@vitejs/plugin-react';
52
+ import { kbach } from '@kbach/react/vite';
53
+
54
+ export default defineConfig({ plugins: [kbach(), react()] });
55
+ ```
56
+
57
+ **Next.js / any PostCSS bundler:**
58
+
59
+ ```js
60
+ // postcss.config.js
61
+ module.exports = { plugins: { '@kbach/react/postcss': {} } };
62
+ ```
63
+
64
+ ```css
65
+ /* globals.css — the plugin fills between these markers */
66
+ /* kbach:start */
67
+ /* kbach:end */
68
+ ```
69
+
70
+ Then import the generated CSS once from your entry point
71
+ (`import './kbach.css'` for Vite; `import './globals.css'` for PostCSS) and
72
+ write `className="..."` normally.
73
+
74
+ ## 2. How resolution works
75
+
76
+ 1. At build time the plugin walks your `include` dirs (`src`, `app`,
77
+ `pages`, `components` by default), extracts every string that looks like
78
+ a class list, and hands each to the WASM engine.
79
+ 2. The engine parses each class → emits CSS rules (with the correct
80
+ `@media` / selector wrappers for modifiers).
81
+ 3. Rules are written between the `/* kbach:start */` / `/* kbach:end */`
82
+ markers of a `kbach.css` file. The plugin finds an existing marked file
83
+ under your source dirs or the project root, else creates one; pass
84
+ `cssFile` to pin the path.
85
+ 4. At runtime `className` is a plain string prop — **nothing calls the
86
+ engine**, the CSS already exists. No async init.
87
+
88
+ Mode-aware colors (`surface: { light, dark }`, built in or from
89
+ `kbach.config.js`) compile to `rgb(var(--kb-color-<name>))` against the
90
+ class as written (`.bg-surface`), and kbach.css defines each variable on
91
+ `:root` plus under the dark-mode selector. `staticCss/engine.ts` gets this
92
+ by handing the engine `withColorVariables(theme)` (colorVariables.ts);
93
+ `format.ts` writes the variables with `colorVariablesCss(theme)`, the same
94
+ text `ensureColorVariablesInjected` injects at runtime. `kb()` uses the
95
+ same variable form (kb.ts `refreshEngineTheme`, 1.0.0-beta.29+) and
96
+ injects the variables, so `kb('bg-surface')` returns `bg-surface`
97
+ unchanged; only a mode-aware color whose values aren't hex still comes
98
+ back expanded (`bg-[…] dark:bg-[…]`), which is why callers must use the
99
+ string `kb()` returns.
100
+
101
+ ## 3. Runtime pieces (opt-in, these DO call the engine)
102
+
103
+ - **`kb(dynamicClassString)`** — resolve a class list assembled from
104
+ runtime data (`kb(\`bg-${color}-6\`)`). Requires `await initKbach()` once
105
+ before first use. Prefer a `safelist: [...]` plugin entry when the
106
+ possible values are known ahead of time (stays fully static).
107
+ It leaves classes the static kbach.css already defines to that file
108
+ (kb.ts `staticClasses`, found by the `--kb-dark-mode` declaration the
109
+ plugin writes on `:root`). Before 1.0.0-beta.29 it injected them again,
110
+ into a `<style data-kbach>` that comes later in the document, so a
111
+ runtime `.grid` beat a static `@media (min-width: 1024px) .lg:hidden`
112
+ on another element: responsive classes broke once `kb()` ran.
113
+ - **`@kbach/react/jsx-runtime`** — a `jsxImportSource` override that
114
+ intercepts *every* element's `className` at runtime instead of relying on
115
+ the static scan. Also needs `await initKbach()`.
116
+ - **No-build CDN usage is a SEPARATE package, `@kbach/browser`**
117
+ (packages/browser), which bundles this package's runtime modules (`kb`,
118
+ `theme`, `config`, `colorVariables`, `darkModeStore`, `wasmLoader`)
119
+ straight from `src/`. Renaming or changing those modules' exports (or
120
+ `wasmLoader.ts`'s internal `initKbachFrom`) breaks that package, and its
121
+ own tests catch it. See [packages/browser/AGENTS.md](../browser/AGENTS.md).
122
+
123
+ ## 4. Dark mode
124
+
125
+ `dark:` classes work through plain CSS with zero setup, following the OS
126
+ (`@media (prefers-color-scheme)`).
127
+
128
+ For a **manual toggle**, add one line to `kbach.config.js`:
129
+
130
+ ```js
131
+ export default { darkMode: 'attribute' }; // or 'class'
132
+ ```
133
+
134
+ Nothing else — the plugin auto-discovers the file, and the runtime reads
135
+ the strategy back out of the generated CSS. Then:
136
+
137
+ ```tsx
138
+ import { useTheme } from '@kbach/react';
139
+ const { mode, isDark, toggle, setMode } = useTheme();
140
+ ```
141
+
142
+ No provider needed (`useTheme` reads a global store). `<ThemeProvider
143
+ defaultMode="dark">` only seeds a startup default. Outside React:
144
+ `getGlobalDarkMode()` / `toggleGlobalDarkMode()` / `setGlobalThemeMode()`.
145
+
146
+ Pure-runtime (`kb()`-only, no build step): call `applyKbachConfig({
147
+ darkMode: 'attribute' })` yourself once before first render.
148
+
149
+ ## 5. Plugin options (`KbachPluginOptions`)
150
+
151
+ `root`, `theme` (a resolved `ThemeConfig`), `config` (a `KbachConfig`, same
152
+ shape as `kbach.config.js` — see the root AGENTS.md's config reference),
153
+ `include` (dirs to scan), `safelist` (classes to always emit), `cssFile`
154
+ (explicit output path). PostCSS: `root` defaults to `process.cwd()`; pass
155
+ it if `postcss.config.js` isn't at the project root.
156
+
157
+ ## 6. Exports
158
+
159
+ `initKbach`, `isKbachReady`, `kb`, `disableRuntimeCSS`,
160
+ `isRuntimeCSSDisabled`, `setTheme`, `getTheme`, `defaultTheme`,
161
+ `KbachReset`, `resolveKbachConfig`, `applyKbachConfig`, `useTheme`,
162
+ `ThemeProvider`, `useColors`, `clsx`, `getGlobalDarkMode`,
163
+ `getGlobalThemeMode`, `setGlobalThemeMode`, `toggleGlobalDarkMode`,
164
+ `subscribeGlobalDarkMode`. Types: `ThemeConfig`, `ColorEntry`,
165
+ `ContainerConfig`, `DarkModeStrategy`, `KbachConfig`, `ThemeState`,
166
+ `ThemeProviderProps`, `ThemeMode`, `ColorsAPI`, `ColorScale`, `ClassValue`.
167
+
168
+ ## 7. Editing kbach.config.js
169
+
170
+ The Vite plugin watches `kbach.config.{js,mjs,cjs}` at the root: on a
171
+ change it re-reads the file, rebuilds the theme and a fresh engine,
172
+ rescans and rewrites kbach.css, with no restart (`reloadConfig` in
173
+ vite-plugin/index.ts, 1.0.0-beta.29+). A config that fails to load
174
+ mid-edit is reported and the last good theme kept. Both loaders bypass
175
+ Node's module cache (`delete require.cache[…]`; `import()` with a `?t=`
176
+ query through a runtime-built `new Function` import, which a bundler or
177
+ vitest can't rewrite). An ESM-type project's `.js` config goes straight
178
+ to `import()`: Node 22+ can `require()` an ES module, but into the ESM
179
+ cache, where clearing `require.cache` never reached it. The PostCSS
180
+ plugin re-reads the config on every run the same way, and registers it
181
+ as a postcss `dependency` message so Next.js rebuilds on save. An explicit
182
+ `config`/`theme` plugin option disables the watcher.
183
+
184
+ ## 8. Unknown-class warnings
185
+
186
+ In development, a scanned class that resolves to nothing (and isn't
187
+ defined in the project's own CSS) prints a terminal warning with its
188
+ `file:line:column`. It names the fix when there is one, from the
189
+ engine's `suggest_class` (vite-plugin/wasmNode.ts
190
+ `suggestClassForToken`): `bg-blue-500` → "did you mean bg-blue-6? Kbach
191
+ color shades run 1–12, not 50–950.", `flexx` → "did you mean flex?".
192
+ The suggestion is computed only for a class that is really unknown.
193
+
194
+ ## 9. Common failure modes
195
+
196
+ - **Build fails after adding/editing `kbach.config.js`** — an unquoted
197
+ hyphenated object key (`surface-dim:` instead of `'surface-dim':`) is a
198
+ syntax error; `loadConfigFile` (vite-plugin/index.ts) re-throws it
199
+ rather than silently falling back to defaults, so the whole build fails
200
+ loudly. Quote the key.
201
+ - **A dynamic `kb(...)` call returns nothing / throws** — `initKbach()`
202
+ hasn't resolved yet. It's async; await it once before the first `kb()`
203
+ call, or prefer a static `safelist` entry when the value set is known
204
+ ahead of time.
205
+ - **A mode-aware color class (`bg-surface`, or a `{ light, dark }` color
206
+ from `kbach.config.js`) has no style at all with plain `className`** —
207
+ `@kbach/react` 1.0.0-beta.28 and earlier wrote rules only for the
208
+ engine's expanded `bg-[#…] dark:bg-[#…]` classes, which only `kb()` and
209
+ the jsx-runtime path ever put on an element. Fixed in the release after
210
+ beta.28 (see §2). On an affected version, use the jsx-runtime path or
211
+ write the two classes yourself.
212
+ - **A class works in one file but not another** — it's outside every
213
+ `include` dir the plugin scans (default: `src`, `app`, `pages`,
214
+ `components`). Add the directory via the plugin's `include` option.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  kb
3
- } from "./chunk-DJKDEDPE.mjs";
3
+ } from "./chunk-K6AUD64N.mjs";
4
4
 
5
5
  // src/jsx-runtime.tsx
6
6
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -385,6 +385,78 @@ function detectDarkModeStrategyFromDom() {
385
385
  setTheme({ ...activeTheme, darkMode: raw });
386
386
  }
387
387
 
388
+ // src/colorVariables.ts
389
+ function parseHexRgb(hex) {
390
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
391
+ if (!m) return null;
392
+ let h = m[1];
393
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
394
+ const n = parseInt(h, 16);
395
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
396
+ }
397
+ function wrapDarkDecls(strategy, decls) {
398
+ switch (strategy) {
399
+ case "class":
400
+ return `.dark { ${decls} }`;
401
+ case "media":
402
+ return `@media (prefers-color-scheme: dark) { :root { ${decls} } }`;
403
+ case "attribute":
404
+ default:
405
+ return `[data-theme="dark"] { ${decls} }`;
406
+ }
407
+ }
408
+ function modeAwareDecls(theme) {
409
+ const light = [];
410
+ const dark = [];
411
+ for (const [name, value] of Object.entries(theme.colors)) {
412
+ if (typeof value === "string") continue;
413
+ const l = parseHexRgb(value.light);
414
+ const d = parseHexRgb(value.dark);
415
+ if (!l || !d) continue;
416
+ light.push(`--kb-color-${name}: ${l.join(",")};`);
417
+ dark.push(`--kb-color-${name}: ${d.join(",")};`);
418
+ }
419
+ return { light, dark };
420
+ }
421
+ function colorVariablesCss(theme) {
422
+ const { light, dark } = modeAwareDecls(theme);
423
+ if (light.length === 0) return "";
424
+ return `:root { ${light.join(" ")} }
425
+ ${wrapDarkDecls(theme.darkMode, dark.join(" "))}`;
426
+ }
427
+ function withColorVariables(theme) {
428
+ let changed = false;
429
+ const colors = {};
430
+ for (const [name, value] of Object.entries(theme.colors)) {
431
+ if (typeof value !== "string" && parseHexRgb(value.light) && parseHexRgb(value.dark)) {
432
+ colors[name] = `rgb(var(--kb-color-${name}))`;
433
+ changed = true;
434
+ } else {
435
+ colors[name] = value;
436
+ }
437
+ }
438
+ return changed ? { ...theme, colors } : theme;
439
+ }
440
+ var STYLE_ID = "kbach-colors";
441
+ var injectedForTheme = null;
442
+ function ensureColorVariablesInjected(theme) {
443
+ if (typeof document === "undefined") return;
444
+ if (injectedForTheme === theme) return;
445
+ injectedForTheme = theme;
446
+ const css = colorVariablesCss(theme);
447
+ let el = document.getElementById(STYLE_ID);
448
+ if (!css) {
449
+ el?.remove();
450
+ return;
451
+ }
452
+ if (!el) {
453
+ el = document.createElement("style");
454
+ el.id = STYLE_ID;
455
+ document.head.insertBefore(el, document.head.firstChild);
456
+ }
457
+ el.textContent = css;
458
+ }
459
+
388
460
  // src/reset.ts
389
461
  var RESET_STYLE_ID = "kbach-reset";
390
462
  var RESET_RULES = [
@@ -475,6 +547,10 @@ export {
475
547
  getTheme,
476
548
  getThemeJson,
477
549
  detectDarkModeStrategyFromDom,
550
+ parseHexRgb,
551
+ colorVariablesCss,
552
+ withColorVariables,
553
+ ensureColorVariablesInjected,
478
554
  RESET_STYLE_ID,
479
555
  BASE_RESET,
480
556
  buildResetCSS
@@ -1,8 +1,11 @@
1
1
  import {
2
2
  BASE_RESET,
3
3
  RESET_STYLE_ID,
4
- getThemeJson
5
- } from "./chunk-OVKVZA7B.mjs";
4
+ ensureColorVariablesInjected,
5
+ getTheme,
6
+ getThemeJson,
7
+ withColorVariables
8
+ } from "./chunk-FBYS4BI3.mjs";
6
9
 
7
10
  // src/wasmLoader.ts
8
11
  import init, { generate_css as wasmGenerateCss } from "@kbach/core-engine";
@@ -85,27 +88,88 @@ function ensureResetInjected() {
85
88
  var KB_CACHE_MAX = 500;
86
89
  var kbCache = /* @__PURE__ */ new Map();
87
90
  var cachedForThemeJson = null;
91
+ var engineThemeJson = "";
92
+ var hasModeAware = false;
93
+ function refreshEngineTheme() {
94
+ const theme = getTheme();
95
+ const withVars = withColorVariables(theme);
96
+ hasModeAware = withVars !== theme;
97
+ engineThemeJson = hasModeAware ? JSON.stringify(withVars) : getThemeJson();
98
+ }
99
+ var staticOwner = null;
100
+ var staticSheet = null;
101
+ var staticRuleCount = -1;
102
+ var staticTokens = /* @__PURE__ */ new Set();
103
+ function unescapeCssIdent(s) {
104
+ return s.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_, hex) => String.fromCodePoint(parseInt(hex, 16))).replace(/\\(.)/g, "$1");
105
+ }
106
+ function isStaticKbachSheet(sheet) {
107
+ try {
108
+ return Array.from(sheet.cssRules).some((r) => r instanceof CSSStyleRule && r.selectorText === ":root" && r.cssText.includes("--kb-dark-mode"));
109
+ } catch {
110
+ return false;
111
+ }
112
+ }
113
+ function collectClasses(rules, into) {
114
+ for (const r of Array.from(rules)) {
115
+ if (r instanceof CSSStyleRule) {
116
+ for (const m of r.selectorText.matchAll(/\.((?:\\[0-9a-fA-F]{1,6}\s?|\\.|[\w-])+)/g)) into.add(unescapeCssIdent(m[1]));
117
+ } else if ("cssRules" in r) {
118
+ collectClasses(r.cssRules, into);
119
+ }
120
+ }
121
+ }
122
+ function staticClasses() {
123
+ if (typeof document === "undefined") return staticTokens;
124
+ let sheet = staticOwner && staticOwner.sheet;
125
+ if (!sheet || !isStaticKbachSheet(sheet)) {
126
+ sheet = Array.from(document.styleSheets).find((s) => s !== styleEl?.sheet && isStaticKbachSheet(s)) ?? null;
127
+ staticOwner = sheet?.ownerNode ?? null;
128
+ }
129
+ if (!sheet) {
130
+ staticSheet = null;
131
+ staticTokens = /* @__PURE__ */ new Set();
132
+ return staticTokens;
133
+ }
134
+ if (sheet !== staticSheet || sheet.cssRules.length !== staticRuleCount) {
135
+ staticSheet = sheet;
136
+ staticRuleCount = sheet.cssRules.length;
137
+ staticTokens = /* @__PURE__ */ new Set();
138
+ collectClasses(sheet.cssRules, staticTokens);
139
+ }
140
+ return staticTokens;
141
+ }
88
142
  function kb(classString) {
89
143
  const themeJson = getThemeJson();
90
144
  if (themeJson !== cachedForThemeJson) {
91
145
  kbCache.clear();
92
146
  cachedForThemeJson = themeJson;
147
+ refreshEngineTheme();
148
+ }
149
+ const statics = runtimeCSSDisabled ? null : staticClasses();
150
+ let toResolve = classString;
151
+ let alreadyStatic = "";
152
+ if (statics && statics.size > 0) {
153
+ const tokens = classString.split(/\s+/).filter(Boolean);
154
+ toResolve = tokens.filter((t) => !statics.has(t)).join(" ");
155
+ alreadyStatic = tokens.filter((t) => statics.has(t)).join(" ");
93
156
  }
94
- let result = kbCache.get(classString);
157
+ let result = kbCache.get(toResolve);
95
158
  if (result === void 0) {
96
- const json = generateCss(classString, themeJson);
159
+ const json = generateCss(toResolve, engineThemeJson);
97
160
  result = JSON.parse(json);
98
161
  if (kbCache.size >= KB_CACHE_MAX) kbCache.clear();
99
- kbCache.set(classString, result);
162
+ kbCache.set(toResolve, result);
100
163
  }
101
164
  const { className, rules } = result;
102
165
  if (!runtimeCSSDisabled) {
103
166
  ensureResetInjected();
167
+ if (hasModeAware) ensureColorVariablesInjected(getTheme());
104
168
  for (const { rule, order } of rules) {
105
169
  injectRule(rule, order);
106
170
  }
107
171
  }
108
- return className;
172
+ return alreadyStatic ? (alreadyStatic + " " + className).trim() : className;
109
173
  }
110
174
 
111
175
  export {