@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 +214 -162
- package/dist/{chunk-UP7MWEU5.mjs → chunk-6KXERZP3.mjs} +1 -1
- package/dist/{chunk-OVKVZA7B.mjs → chunk-FBYS4BI3.mjs} +76 -0
- package/dist/{chunk-DJKDEDPE.mjs → chunk-K6AUD64N.mjs} +70 -6
- package/dist/{chunk-KTRJZDVE.mjs → chunk-NHWKWY2T.mjs} +71 -17
- package/dist/{chunk-43FOLRQA.mjs → chunk-UK5S55DF.mjs} +9 -54
- package/dist/index.d.mts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +144 -55
- package/dist/index.mjs +5 -5
- package/dist/jsx-dev-runtime.js +141 -4
- package/dist/jsx-dev-runtime.mjs +3 -3
- package/dist/jsx-runtime.js +141 -4
- package/dist/jsx-runtime.mjs +3 -3
- package/dist/postcss-plugin.js +99 -21
- package/dist/postcss-plugin.mjs +8 -4
- package/dist/vite-plugin.d.mts +3 -1
- package/dist/vite-plugin.d.ts +3 -1
- package/dist/vite-plugin.js +125 -23
- package/dist/vite-plugin.mjs +5 -3
- package/package.json +2 -2
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
`
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
`
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
##
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
@@ -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
|
-
|
|
5
|
-
|
|
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(
|
|
157
|
+
let result = kbCache.get(toResolve);
|
|
95
158
|
if (result === void 0) {
|
|
96
|
-
const json = generateCss(
|
|
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(
|
|
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 {
|