@kbach/react 0.6.51 → 1.0.0-beta.1

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
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
  "use strict";
3
2
  var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
@@ -29,1163 +28,713 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
29
 
31
30
  // src/index.ts
32
- var index_exports = {};
33
- __export(index_exports, {
34
- InteractiveWrapper: () => InteractiveWrapper,
31
+ var src_exports = {};
32
+ __export(src_exports, {
35
33
  KbachReset: () => KbachReset,
36
- ThemeContext: () => ThemeContext,
37
34
  ThemeProvider: () => ThemeProvider,
38
- buildConfig: () => import_core12.buildConfig,
39
- clearCache: () => import_core12.clearCache,
40
- cx: () => cx,
41
- defaultColors: () => import_core12.defaultColors,
42
- defaultTheme: () => import_core12.defaultTheme,
43
- disableRuntimeCSS: () => import_core12.disableRuntimeCSS,
44
- flatten: () => import_core12.flatten,
45
- generateKbachTypesDts: () => import_core12.generateKbachTypesDts,
46
- getConfig: () => import_core12.getConfig,
47
- initConfig: () => import_core12.initConfig,
35
+ defaultTheme: () => defaultTheme,
36
+ disableRuntimeCSS: () => disableRuntimeCSS,
37
+ getGlobalDarkMode: () => getGlobalDarkMode,
38
+ getGlobalThemeMode: () => getGlobalThemeMode,
39
+ getTheme: () => getTheme,
40
+ initKbach: () => initKbach,
41
+ isKbachReady: () => isKbachReady,
42
+ isRuntimeCSSDisabled: () => isRuntimeCSSDisabled,
48
43
  kb: () => kb,
49
- normalizeClassString: () => import_core12.normalizeClassString,
50
- parseClass: () => import_core12.parseClass,
51
- parseClasses: () => import_core12.parseClasses,
52
- registerWebElement: () => registerWebElement,
53
- resolve: () => import_core12.resolve,
54
- setResolveTarget: () => import_core12.setResolveTarget,
55
- splitClassTokens: () => import_core12.splitClassTokens,
56
- styled: () => styled,
57
- updateConfig: () => import_core12.updateConfig,
58
- useBreakpoint: () => useBreakpoint,
59
- useColors: () => useColors,
44
+ setGlobalThemeMode: () => setGlobalThemeMode,
45
+ setTheme: () => setTheme,
46
+ subscribeGlobalDarkMode: () => subscribeGlobalDarkMode,
47
+ toggleGlobalDarkMode: () => toggleGlobalDarkMode,
60
48
  useGlobalDarkMode: () => useGlobalDarkMode,
61
- useIsDark: () => useIsDark,
62
- useResolvedStyle: () => useResolvedStyle,
63
- useResponsive: () => useResponsive,
64
- useSpacing: () => useSpacing,
65
- useStyles: () => useStyles,
66
- useTheme: () => useTheme,
67
- wrapColors: () => wrapColors,
68
- wrapSpacing: () => wrapSpacing
49
+ useTheme: () => useTheme
69
50
  });
70
- module.exports = __toCommonJS(index_exports);
51
+ module.exports = __toCommonJS(src_exports);
71
52
 
72
- // src/context.tsx
73
- var import_react = require("react");
74
- var ThemeContext = globalThis.__kbachThemeContext ?? (globalThis.__kbachThemeContext = (0, import_react.createContext)(null));
75
- function useTheme() {
76
- const ctx = (0, import_react.useContext)(ThemeContext);
77
- if (!ctx) {
78
- throw new Error(
79
- "[Kbach] useTheme() must be called inside a <ThemeProvider>. Wrap your app root with <ThemeProvider>."
80
- );
53
+ // src/wasmLoader.ts
54
+ var import_core_engine = __toESM(require("@kbach/core-engine"));
55
+ var readyPromise = null;
56
+ var ready = false;
57
+ function initKbach() {
58
+ if (!readyPromise) {
59
+ readyPromise = (0, import_core_engine.default)().then(() => {
60
+ ready = true;
61
+ });
81
62
  }
82
- return ctx;
63
+ return readyPromise;
83
64
  }
84
- function useIsDark() {
85
- return useTheme().isDark;
65
+ function isKbachReady() {
66
+ return ready;
86
67
  }
87
-
88
- // src/useColors.ts
89
- var import_react2 = require("react");
90
- var import_core = require("./core");
91
- function applyOpacity(color, opacity) {
92
- const a = Math.max(0, Math.min(1, opacity / 100));
93
- if (color.startsWith("#")) {
94
- const rgb = (0, import_core.parseHexRgb)(color);
95
- if (!rgb) return color;
96
- const [r, g, b] = rgb;
97
- return `rgba(${r},${g},${b},${a})`;
68
+ function generateCss(classString, themeJson) {
69
+ if (!ready) {
70
+ throw new Error(
71
+ "[kbach] generateCss() was called before initKbach() resolved. Call `await initKbach()` once at startup (e.g. before rendering your app) before using kb()."
72
+ );
98
73
  }
99
- if (color.startsWith("rgb(")) return color.replace("rgb(", "rgba(").replace(")", `,${a})`);
100
- if (color.startsWith("rgba(")) return color.replace(/,\s*[\d.]+\)$/, `,${a})`);
101
- return color;
102
- }
103
- function pickSide(value, isDark) {
104
- return (0, import_core.isModeAwareColor)(value) ? isDark ? value.dark : value.light : value;
105
- }
106
- function makeShadeProxy(shades, isDark) {
107
- return new Proxy(shades, {
108
- get(target, prop) {
109
- const key = String(prop);
110
- if (key === "then") return void 0;
111
- if (key.includes("/")) {
112
- const slash = key.indexOf("/");
113
- const shade = key.slice(0, slash);
114
- const op = Number(key.slice(slash + 1));
115
- const color2 = target[shade];
116
- return color2 !== void 0 ? applyOpacity(pickSide(color2, isDark), op) : void 0;
117
- }
118
- const color = target[key];
119
- return color !== void 0 ? pickSide(color, isDark) : void 0;
120
- }
121
- });
122
- }
123
- function wrapColors(rawColors, isDark = false) {
124
- const cache = /* @__PURE__ */ new Map();
125
- const alpha = (color, opacity) => opacity === void 0 ? color : applyOpacity(color, opacity);
126
- return new Proxy({ alpha }, {
127
- get(_, prop) {
128
- const key = String(prop);
129
- if (key === "then") return void 0;
130
- if (key === "alpha") return alpha;
131
- if (key.includes("/")) {
132
- const slash = key.indexOf("/");
133
- const name = key.slice(0, slash);
134
- const op = Number(key.slice(slash + 1));
135
- const entry2 = rawColors[name];
136
- return entry2 !== void 0 && (typeof entry2 === "string" || (0, import_core.isModeAwareColor)(entry2)) ? applyOpacity(pickSide(entry2, isDark), op) : void 0;
137
- }
138
- const entry = rawColors[key];
139
- if (entry === void 0) return void 0;
140
- if (typeof entry === "string" || (0, import_core.isModeAwareColor)(entry)) return pickSide(entry, isDark);
141
- const cacheKey = `${key}:${isDark}`;
142
- if (!cache.has(cacheKey)) cache.set(cacheKey, makeShadeProxy(entry, isDark));
143
- return cache.get(cacheKey);
144
- }
145
- });
146
- }
147
- function useColors() {
148
- const { config, isDark } = useTheme();
149
- return (0, import_react2.useMemo)(() => wrapColors(config.theme.colors, isDark), [config.theme.colors, isDark]);
74
+ return (0, import_core_engine.generate_css)(classString, themeJson);
150
75
  }
151
76
 
152
- // src/useSpacing.ts
153
- var import_react3 = require("react");
154
- function wrapSpacing(rawSpacing) {
155
- return rawSpacing;
77
+ // src/generatedPalette.ts
78
+ var PALETTE = {
79
+ "amber-1": "#fffbeb",
80
+ "amber-10": "#78350f",
81
+ "amber-11": "#451a03",
82
+ "amber-12": "#291002",
83
+ "amber-2": "#fef3c7",
84
+ "amber-3": "#fde68a",
85
+ "amber-4": "#fcd34d",
86
+ "amber-5": "#fbbf24",
87
+ "amber-6": "#f59e0b",
88
+ "amber-7": "#d97706",
89
+ "amber-8": "#b45309",
90
+ "amber-9": "#92400e",
91
+ "black": "#000000",
92
+ "blue-1": "#eff6ff",
93
+ "blue-10": "#1e3a8a",
94
+ "blue-11": "#172554",
95
+ "blue-12": "#0d1633",
96
+ "blue-2": "#dbeafe",
97
+ "blue-3": "#bfdbfe",
98
+ "blue-4": "#93c5fd",
99
+ "blue-5": "#60a5fa",
100
+ "blue-6": "#3b82f6",
101
+ "blue-7": "#2563eb",
102
+ "blue-8": "#1d4ed8",
103
+ "blue-9": "#1e40af",
104
+ "bronze": "#cd7f32",
105
+ "coal": "#1c1c1c",
106
+ "copper": "#b87333",
107
+ "current": "currentColor",
108
+ "cyan-1": "#ecfeff",
109
+ "cyan-10": "#164e63",
110
+ "cyan-11": "#083344",
111
+ "cyan-12": "#041e28",
112
+ "cyan-2": "#cffafe",
113
+ "cyan-3": "#a5f3fc",
114
+ "cyan-4": "#67e8f9",
115
+ "cyan-5": "#22d3ee",
116
+ "cyan-6": "#06b6d4",
117
+ "cyan-7": "#0891b2",
118
+ "cyan-8": "#0e7490",
119
+ "cyan-9": "#155e75",
120
+ "emerald-1": "#ecfdf5",
121
+ "emerald-10": "#064e3b",
122
+ "emerald-11": "#022c22",
123
+ "emerald-12": "#011a14",
124
+ "emerald-2": "#d1fae5",
125
+ "emerald-3": "#a7f3d0",
126
+ "emerald-4": "#6ee7b7",
127
+ "emerald-5": "#34d399",
128
+ "emerald-6": "#10b981",
129
+ "emerald-7": "#059669",
130
+ "emerald-8": "#047857",
131
+ "emerald-9": "#065f46",
132
+ "fuchsia-1": "#fdf4ff",
133
+ "fuchsia-10": "#701a75",
134
+ "fuchsia-11": "#4a044e",
135
+ "fuchsia-12": "#2d022f",
136
+ "fuchsia-2": "#fae8ff",
137
+ "fuchsia-3": "#f5d0fe",
138
+ "fuchsia-4": "#f0abfc",
139
+ "fuchsia-5": "#e879f9",
140
+ "fuchsia-6": "#d946ef",
141
+ "fuchsia-7": "#c026d3",
142
+ "fuchsia-8": "#a21caf",
143
+ "fuchsia-9": "#86198f",
144
+ "gold": "#d4af37",
145
+ "gray-1": "#f9fafb",
146
+ "gray-10": "#111827",
147
+ "gray-11": "#030712",
148
+ "gray-12": "#020409",
149
+ "gray-2": "#f3f4f6",
150
+ "gray-3": "#e5e7eb",
151
+ "gray-4": "#d1d5db",
152
+ "gray-5": "#9ca3af",
153
+ "gray-6": "#6b7280",
154
+ "gray-7": "#4b5563",
155
+ "gray-8": "#374151",
156
+ "gray-9": "#1f2937",
157
+ "green-1": "#f0fdf4",
158
+ "green-10": "#14532d",
159
+ "green-11": "#052e16",
160
+ "green-12": "#031b0d",
161
+ "green-2": "#dcfce7",
162
+ "green-3": "#bbf7d0",
163
+ "green-4": "#86efac",
164
+ "green-5": "#4ade80",
165
+ "green-6": "#22c55e",
166
+ "green-7": "#16a34a",
167
+ "green-8": "#15803d",
168
+ "green-9": "#166534",
169
+ "indigo-1": "#eef2ff",
170
+ "indigo-10": "#312e81",
171
+ "indigo-11": "#1e1b4b",
172
+ "indigo-12": "#12102d",
173
+ "indigo-2": "#e0e7ff",
174
+ "indigo-3": "#c7d2fe",
175
+ "indigo-4": "#a5b4fc",
176
+ "indigo-5": "#818cf8",
177
+ "indigo-6": "#6366f1",
178
+ "indigo-7": "#4f46e5",
179
+ "indigo-8": "#4338ca",
180
+ "indigo-9": "#3730a3",
181
+ "lime-1": "#f7fee7",
182
+ "lime-10": "#365314",
183
+ "lime-11": "#1a2e05",
184
+ "lime-12": "#0f1b03",
185
+ "lime-2": "#ecfccb",
186
+ "lime-3": "#d9f99d",
187
+ "lime-4": "#bef264",
188
+ "lime-5": "#a3e635",
189
+ "lime-6": "#84cc16",
190
+ "lime-7": "#65a30d",
191
+ "lime-8": "#4d7c0f",
192
+ "lime-9": "#3f6212",
193
+ "neutral-1": "#fafafa",
194
+ "neutral-10": "#171717",
195
+ "neutral-11": "#0a0a0a",
196
+ "neutral-12": "#050505",
197
+ "neutral-2": "#f5f5f5",
198
+ "neutral-3": "#e5e5e5",
199
+ "neutral-4": "#d4d4d4",
200
+ "neutral-5": "#a3a3a3",
201
+ "neutral-6": "#737373",
202
+ "neutral-7": "#525252",
203
+ "neutral-8": "#404040",
204
+ "neutral-9": "#262626",
205
+ "orange-1": "#fff7ed",
206
+ "orange-10": "#7c2d12",
207
+ "orange-11": "#431407",
208
+ "orange-12": "#270c04",
209
+ "orange-2": "#ffedd5",
210
+ "orange-3": "#fed7aa",
211
+ "orange-4": "#fdba74",
212
+ "orange-5": "#fb923c",
213
+ "orange-6": "#f97316",
214
+ "orange-7": "#ea580c",
215
+ "orange-8": "#c2410c",
216
+ "orange-9": "#9a3412",
217
+ "pink-1": "#fdf2f8",
218
+ "pink-10": "#831843",
219
+ "pink-11": "#500724",
220
+ "pink-12": "#300415",
221
+ "pink-2": "#fce7f3",
222
+ "pink-3": "#fbcfe8",
223
+ "pink-4": "#f9a8d4",
224
+ "pink-5": "#f472b6",
225
+ "pink-6": "#ec4899",
226
+ "pink-7": "#db2777",
227
+ "pink-8": "#be185d",
228
+ "pink-9": "#9d174d",
229
+ "purple-1": "#faf5ff",
230
+ "purple-10": "#581c87",
231
+ "purple-11": "#3b0764",
232
+ "purple-12": "#23043c",
233
+ "purple-2": "#f3e8ff",
234
+ "purple-3": "#e9d5ff",
235
+ "purple-4": "#d8b4fe",
236
+ "purple-5": "#c084fc",
237
+ "purple-6": "#a855f7",
238
+ "purple-7": "#9333ea",
239
+ "purple-8": "#7e22ce",
240
+ "purple-9": "#6b21a8",
241
+ "red-1": "#fef2f2",
242
+ "red-10": "#7f1d1d",
243
+ "red-11": "#450a0a",
244
+ "red-12": "#280606",
245
+ "red-2": "#fee2e2",
246
+ "red-3": "#fecaca",
247
+ "red-4": "#fca5a5",
248
+ "red-5": "#f87171",
249
+ "red-6": "#ef4444",
250
+ "red-7": "#dc2626",
251
+ "red-8": "#b91c1c",
252
+ "red-9": "#991b1b",
253
+ "rose-1": "#fff1f2",
254
+ "rose-10": "#881337",
255
+ "rose-11": "#4c0519",
256
+ "rose-12": "#2d030e",
257
+ "rose-2": "#ffe4e6",
258
+ "rose-3": "#fecdd3",
259
+ "rose-4": "#fda4af",
260
+ "rose-5": "#fb7185",
261
+ "rose-6": "#f43f5e",
262
+ "rose-7": "#e11d48",
263
+ "rose-8": "#be123c",
264
+ "rose-9": "#9f1239",
265
+ "silver": "#c0c0c0",
266
+ "sky-1": "#f0f9ff",
267
+ "sky-10": "#0c4a6e",
268
+ "sky-11": "#082f49",
269
+ "sky-12": "#041b2b",
270
+ "sky-2": "#e0f2fe",
271
+ "sky-3": "#bae6fd",
272
+ "sky-4": "#7dd3fc",
273
+ "sky-5": "#38bdf8",
274
+ "sky-6": "#0ea5e9",
275
+ "sky-7": "#0284c7",
276
+ "sky-8": "#0369a1",
277
+ "sky-9": "#075985",
278
+ "slate-1": "#f8fafc",
279
+ "slate-10": "#0f172a",
280
+ "slate-11": "#020617",
281
+ "slate-12": "#01020a",
282
+ "slate-2": "#f1f5f9",
283
+ "slate-3": "#e2e8f0",
284
+ "slate-4": "#cbd5e1",
285
+ "slate-5": "#94a3b8",
286
+ "slate-6": "#64748b",
287
+ "slate-7": "#475569",
288
+ "slate-8": "#334155",
289
+ "slate-9": "#1e293b",
290
+ "stone-1": "#fafaf9",
291
+ "stone-10": "#1c1917",
292
+ "stone-11": "#0c0a09",
293
+ "stone-12": "#070605",
294
+ "stone-2": "#f5f5f4",
295
+ "stone-3": "#e7e5e4",
296
+ "stone-4": "#d6d3d1",
297
+ "stone-5": "#a8a29e",
298
+ "stone-6": "#78716c",
299
+ "stone-7": "#57534e",
300
+ "stone-8": "#44403c",
301
+ "stone-9": "#292524",
302
+ "teal-1": "#f0fdfa",
303
+ "teal-10": "#134e4a",
304
+ "teal-11": "#042f2e",
305
+ "teal-12": "#021c1b",
306
+ "teal-2": "#ccfbf1",
307
+ "teal-3": "#99f6e4",
308
+ "teal-4": "#5eead4",
309
+ "teal-5": "#2dd4bf",
310
+ "teal-6": "#14b8a6",
311
+ "teal-7": "#0d9488",
312
+ "teal-8": "#0f766e",
313
+ "teal-9": "#115e59",
314
+ "transparent": "transparent",
315
+ "violet-1": "#f5f3ff",
316
+ "violet-10": "#4c1d95",
317
+ "violet-11": "#2e1065",
318
+ "violet-12": "#1c0a3d",
319
+ "violet-2": "#ede9fe",
320
+ "violet-3": "#ddd6fe",
321
+ "violet-4": "#c4b5fd",
322
+ "violet-5": "#a78bfa",
323
+ "violet-6": "#8b5cf6",
324
+ "violet-7": "#7c3aed",
325
+ "violet-8": "#6d28d9",
326
+ "violet-9": "#5b21b6",
327
+ "white": "#ffffff",
328
+ "yellow-1": "#fefce8",
329
+ "yellow-10": "#713f12",
330
+ "yellow-11": "#422006",
331
+ "yellow-12": "#271304",
332
+ "yellow-2": "#fef9c3",
333
+ "yellow-3": "#fef08a",
334
+ "yellow-4": "#fde047",
335
+ "yellow-5": "#facc15",
336
+ "yellow-6": "#eab308",
337
+ "yellow-7": "#ca8a04",
338
+ "yellow-8": "#a16207",
339
+ "yellow-9": "#854d0e",
340
+ "zinc-1": "#fafafa",
341
+ "zinc-10": "#18181b",
342
+ "zinc-11": "#09090b",
343
+ "zinc-12": "#050506",
344
+ "zinc-2": "#f4f4f5",
345
+ "zinc-3": "#e4e4e7",
346
+ "zinc-4": "#d4d4d8",
347
+ "zinc-5": "#a1a1aa",
348
+ "zinc-6": "#71717a",
349
+ "zinc-7": "#52525b",
350
+ "zinc-8": "#3f3f46",
351
+ "zinc-9": "#27272a"
352
+ };
353
+
354
+ // src/theme.ts
355
+ var defaultTheme = {
356
+ colors: {
357
+ ...PALETTE,
358
+ surface: { light: "#f9fafb", dark: "#111827" }
359
+ },
360
+ // Real Tailwind's default spacing scale in full — every step is exactly
361
+ // `n * 4px` (Tailwind's base spacing unit), including the fractional
362
+ // `.5` steps (`1.5` -> 6px, `2.5` -> 10px, `3.5` -> 14px) and the "px"
363
+ // literal (a bare 1px, for `p-px`-style hairline spacing). Previously
364
+ // this only had {0,1,2,4,6,8} — a handful of spot-checked values, not
365
+ // the real scale — so anything else (`p-3`, `w-5`, `border-2`, `gap-10`,
366
+ // ...) silently failed to resolve (an unknown spacing key returns `None`
367
+ // the same way an unknown class does, with no error). "0" itself is
368
+ // still needed explicitly (not derivable as "any number * 4") for the
369
+ // same reason it always was: `sticky top-0` needs an actual "0" key to
370
+ // resolve at all, not just fall through as unresolvable.
371
+ spacing: {
372
+ "0": 0,
373
+ "px": 1,
374
+ "0.5": 2,
375
+ "1": 4,
376
+ "1.5": 6,
377
+ "2": 8,
378
+ "2.5": 10,
379
+ "3": 12,
380
+ "3.5": 14,
381
+ "4": 16,
382
+ "5": 20,
383
+ "6": 24,
384
+ "7": 28,
385
+ "8": 32,
386
+ "9": 36,
387
+ "10": 40,
388
+ "11": 44,
389
+ "12": 48,
390
+ "14": 56,
391
+ "16": 64,
392
+ "20": 80,
393
+ "24": 96,
394
+ "28": 112,
395
+ "32": 128,
396
+ "36": 144,
397
+ "40": 160,
398
+ "44": 176,
399
+ "48": 192,
400
+ "52": 208,
401
+ "56": 224,
402
+ "60": 240,
403
+ "64": 256,
404
+ "72": 288,
405
+ "80": 320,
406
+ "96": 384
407
+ },
408
+ screens: {
409
+ sm: 640,
410
+ md: 768,
411
+ lg: 1024,
412
+ xl: 1280,
413
+ "2xl": 1536
414
+ },
415
+ darkMode: "attribute"
416
+ };
417
+ var activeTheme = defaultTheme;
418
+ var activeThemeJson = JSON.stringify(defaultTheme);
419
+ function setTheme(theme) {
420
+ activeTheme = theme;
421
+ activeThemeJson = JSON.stringify(theme);
156
422
  }
157
- function useSpacing() {
158
- const { config } = useTheme();
159
- return (0, import_react3.useMemo)(() => wrapSpacing(config.theme.spacing), [config.theme.spacing]);
423
+ function getTheme() {
424
+ return activeTheme;
425
+ }
426
+ function getThemeJson() {
427
+ return activeThemeJson;
160
428
  }
161
429
 
162
- // src/ThemeProvider.tsx
163
- var import_react5 = require("react");
164
-
165
- // src/useSyncExternalStoreShim.ts
166
- var import_react4 = __toESM(require("react"));
167
- var useSyncExternalStore = import_react4.default.useSyncExternalStore ?? function useSyncExternalStoreFallback(subscribe, getSnapshot, getServerSnapshot) {
168
- const isServer = typeof window === "undefined";
169
- const [, forceUpdate] = import_react4.default.useReducer((n) => n + 1, 0);
170
- const value = isServer && getServerSnapshot ? getServerSnapshot() : getSnapshot();
171
- import_react4.default.useEffect(() => {
172
- if (getSnapshot() !== value) forceUpdate();
173
- return subscribe(forceUpdate);
174
- }, [subscribe]);
175
- return value;
176
- };
430
+ // src/reset.ts
431
+ var RESET_STYLE_ID = "kbach-reset";
432
+ var RESET_RULES = [
433
+ // border-style: solid means border-N utilities show a visible border without an extra border-solid class.
434
+ // border-width: 0 keeps all elements borderless by default.
435
+ { css: "*, *::before, *::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }" },
436
+ { css: "body { margin: 0; padding: 0; }" },
437
+ { css: "h1, h2, h3, h4, h5, h6 { margin: 0; font-size: inherit; font-weight: inherit; }", tags: ["h1", "h2", "h3", "h4", "h5", "h6"] },
438
+ { css: "p { margin: 0; }", tags: ["p"] },
439
+ { css: "a { color: inherit; text-decoration: none; }", tags: ["a"] },
440
+ { css: "ul, ol { margin: 0; padding: 0; list-style: none; }", tags: ["ul", "ol"] },
441
+ // svg here only covers a literal <svg> tag in JSX — an icon imported as
442
+ // a component (e.g. `<ArrowIcon />`) never appears as the text "svg" in
443
+ // source, so this rule won't be pulled in by that usage alone. Same
444
+ // caveat applies to every tag-gated rule below: detection is a literal
445
+ // source-text scan (see vite-plugin/scan.ts's scanUsedTags), not a real
446
+ // DOM/type check.
447
+ { css: "img, video, svg { display: block; max-width: 100%; }", tags: ["img", "video", "svg"] },
448
+ // appearance: none is deliberately NOT applied to checkbox/radio/select below —
449
+ // stripping it hides their native checkmark/arrow with nothing rendered in its
450
+ // place, leaving an invisible checkbox or an arrow-less <select> that looks like
451
+ // plain text. Text-like inputs, textarea, and button don't have that problem
452
+ // (their native chrome is just a skin around content utilities can fully
453
+ // restyle), so they keep the blank-canvas treatment.
454
+ // :where() wraps the :not() exclusions so they contribute ZERO specificity
455
+ // (unlike a bare `input:not([type='checkbox']):not([type='radio'])`, whose
456
+ // two :not([attr]) clauses each add a class-level specificity point — (0,2,1)
457
+ // total, MORE than any single utility class (0,1,0)). Without :where(), this
458
+ // reset's `color: inherit` always won the cascade over a text-* color
459
+ // utility applied directly to a <input>/<textarea> regardless of source
460
+ // order, since author rules only override on a tie or higher specificity —
461
+ // confirmed on a real app: a TextInput's own text color utility resolved
462
+ // and injected correctly, class and all, but silently never painted.
463
+ {
464
+ css: "input:where(:not([type='checkbox']):not([type='radio'])), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
465
+ tags: ["input", "textarea"]
466
+ },
467
+ // Native checkbox/radio/range still get typography + spacing normalized, and
468
+ // accent-color re-themes their native indicator to the current text color
469
+ // instead of the browser/OS default blue, so they stay on-brand without
470
+ // needing to be rebuilt from scratch. Wrapped in :where() for the exact
471
+ // same reason as the input:not(...) exclusions above — a bare
472
+ // `input[type='range']` attribute selector has specificity (0,1,1),
473
+ // MORE than any single utility class (0,1,0), so an `accent-*` utility
474
+ // (Phase 23) applied directly to a checkbox/radio/range would resolve and
475
+ // inject correctly but silently never paint, losing the cascade to this
476
+ // reset regardless of source order — confirmed by hand via
477
+ // getComputedStyle on a real `accent-red-6` range input, which reported
478
+ // `currentColor`'s inherited value instead. `:where()` zeroes this
479
+ // selector's specificity contribution entirely, so any `accent-*`
480
+ // utility trivially wins once applied.
481
+ {
482
+ css: ":where(input[type='checkbox'], input[type='radio'], input[type='range']) { margin: 0; font: inherit; accent-color: currentColor; }",
483
+ tags: ["input"]
484
+ },
485
+ // select keeps its native chrome (border/background/arrow are all part of the
486
+ // same OS-drawn widget that appearance: none would blank out) — only typography
487
+ // and spacing are normalized so it still matches surrounding text.
488
+ { css: "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }", tags: ["select"] },
489
+ { css: "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }", tags: ["button"] },
490
+ // Also covers any element opted into the button role (`role="button"` on
491
+ // a <div>/<span>) — but only when a literal <button> tag is ALSO
492
+ // somewhere in the scanned source, since detection here is purely
493
+ // tag-name-based; a project using role="button" exclusively, with no
494
+ // real <button> anywhere, won't pull this rule in. Acceptable tradeoff
495
+ // for this project's scope (rare pattern) — cheap enough that scanning
496
+ // for it separately isn't worth the added complexity.
497
+ { css: "button, [role='button'] { cursor: pointer; }", tags: ["button"] },
498
+ { css: ":disabled { cursor: default; }", tags: ["button", "input", "select", "textarea"] },
499
+ { css: "textarea { resize: vertical; }", tags: ["textarea"] },
500
+ // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
501
+ // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
502
+ { css: "::placeholder { opacity: 1; }", tags: ["input", "textarea"] },
503
+ { css: "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }", tags: ["input"] },
504
+ { css: "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }", tags: ["input"] },
505
+ { css: "fieldset { padding: 0; margin: 0; }", tags: ["fieldset"] },
506
+ { css: "table { border-collapse: collapse; border-spacing: 0; }", tags: ["table"] }
507
+ ];
508
+ var BASE_RESET = RESET_RULES.map((r) => r.css).join("\n");
177
509
 
178
- // src/ThemeProvider.tsx
179
- var import_core2 = require("./core");
180
- var import_jsx_runtime = require("react/jsx-runtime");
181
- var useIsomorphicLayoutEffect = import_core2.isWeb || import_core2.isNative ? import_react5.useLayoutEffect : import_react5.useEffect;
182
- var STORAGE_KEY = "kbach-theme";
183
- var _mountedProviderCount = 0;
184
- var _warnedMultipleProviders = false;
185
- var _mountedConfigOverrideCount = 0;
186
- var _warnedConfigOverride = false;
187
- var _warnedMediaModeToggle = false;
188
- function loadPersistedMode() {
189
- try {
190
- if (import_core2.isWeb) return localStorage.getItem(STORAGE_KEY);
191
- return null;
192
- } catch {
193
- return null;
510
+ // src/kb.ts
511
+ var runtimeCSSDisabled = false;
512
+ function disableRuntimeCSS() {
513
+ runtimeCSSDisabled = true;
514
+ }
515
+ function isRuntimeCSSDisabled() {
516
+ return runtimeCSSDisabled;
517
+ }
518
+ var styleEl = null;
519
+ var sheetKeys = [];
520
+ var ruleOrderByKey = /* @__PURE__ */ new Map();
521
+ function getStyleEl() {
522
+ if (styleEl) return styleEl;
523
+ styleEl = document.createElement("style");
524
+ styleEl.setAttribute("data-kbach", "");
525
+ document.head.appendChild(styleEl);
526
+ return styleEl;
527
+ }
528
+ function findInsertionIndex(order) {
529
+ let lo = 0;
530
+ let hi = sheetKeys.length;
531
+ while (lo < hi) {
532
+ const mid = lo + hi >>> 1;
533
+ if (ruleOrderByKey.get(sheetKeys[mid]) > order) hi = mid;
534
+ else lo = mid + 1;
194
535
  }
536
+ return lo;
195
537
  }
196
- function persistMode(mode) {
538
+ function injectRule(rule, order) {
539
+ if (ruleOrderByKey.has(rule)) return;
540
+ const sheet = getStyleEl().sheet;
541
+ if (!sheet) return;
542
+ const idx = findInsertionIndex(order);
197
543
  try {
198
- if (import_core2.isWeb) localStorage.setItem(STORAGE_KEY, mode);
544
+ sheet.insertRule(rule, idx);
199
545
  } catch {
546
+ return;
200
547
  }
201
- }
202
- function subscribeSystemScheme(callback) {
203
- if (!import_core2.isWeb || typeof window === "undefined") return () => {
204
- };
205
- const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
206
- if (!mq) return () => {
207
- };
208
- mq.addEventListener("change", callback);
209
- return () => mq.removeEventListener("change", callback);
210
- }
211
- function getSystemScheme() {
212
- if (!import_core2.isWeb || typeof window === "undefined") return "light";
213
- return window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ? "dark" : "light";
214
- }
215
- function getSystemSchemeServerSnapshot() {
216
- return "light";
217
- }
218
- function applyWebTheme(resolvedMode, strategy) {
219
- if (!import_core2.isWeb) return;
220
- const root = document.documentElement;
221
- if (strategy === "attribute") {
222
- root.setAttribute("data-theme", resolvedMode);
223
- } else if (strategy === "class") {
224
- root.classList.toggle("dark", resolvedMode === "dark");
225
- root.classList.toggle("light", resolvedMode === "light");
226
- }
227
- }
228
- function ThemeProvider({
229
- children,
230
- defaultMode = "system",
231
- colorScheme,
232
- windowWidth: windowWidthProp,
233
- config: configOverride,
234
- disablePersistence = false
235
- }) {
236
- const [resolvedConfig, setResolvedConfig] = (0, import_react5.useState)(
237
- () => configOverride ? (0, import_core2.buildConfig)(configOverride) : (0, import_core2.getConfig)()
238
- );
239
- const _prevConfigOverrideRef = (0, import_react5.useRef)(void 0);
240
- if (configOverride && _prevConfigOverrideRef.current !== configOverride) {
241
- _prevConfigOverrideRef.current = configOverride;
242
- (0, import_core2.updateConfig)(configOverride);
243
- const fresh = (0, import_core2.getConfig)();
244
- if (fresh !== resolvedConfig) setResolvedConfig(fresh);
245
- }
246
- (0, import_react5.useEffect)(() => {
247
- _mountedProviderCount++;
248
- if (configOverride) _mountedConfigOverrideCount++;
249
- if (process.env.NODE_ENV !== "production") {
250
- if (_mountedProviderCount > 1 && !_warnedMultipleProviders) {
251
- _warnedMultipleProviders = true;
252
- (0, import_core2.kbachWarn)(
253
- "Multiple <ThemeProvider> instances are mounted at once. Dark mode and responsive width are shared through one global store, so whichever provider rendered most recently wins for every consumer \u2014 nested or per-section theming is not isolated between providers."
254
- );
255
- }
256
- if (_mountedConfigOverrideCount > 1 && !_warnedConfigOverride) {
257
- _warnedConfigOverride = true;
258
- (0, import_core2.kbachWarn)(
259
- "Multiple <ThemeProvider config={...}> overrides are mounted at once. The resolved config is shared through the same global store as dark mode/width (see the multiple-providers warning), so per-tree config overrides are not actually isolated between providers \u2014 plain className/kb elements with no hover:/dark:/sm: modifier resolve against whichever override last committed, not necessarily their nearest ancestor <ThemeProvider>."
260
- );
261
- }
548
+ sheetKeys.splice(idx, 0, rule);
549
+ ruleOrderByKey.set(rule, order);
550
+ }
551
+ var resetChecked = false;
552
+ function ensureResetInjected() {
553
+ if (resetChecked) return;
554
+ resetChecked = true;
555
+ if (document.getElementById(RESET_STYLE_ID)) return;
556
+ const el = document.createElement("style");
557
+ el.id = RESET_STYLE_ID;
558
+ el.textContent = BASE_RESET;
559
+ document.head.insertBefore(el, document.head.firstChild);
560
+ }
561
+ function kb(classString) {
562
+ const json = generateCss(classString, getThemeJson());
563
+ const { className, rules } = JSON.parse(json);
564
+ if (!runtimeCSSDisabled) {
565
+ ensureResetInjected();
566
+ for (const { rule, order } of rules) {
567
+ injectRule(rule, order);
262
568
  }
263
- return () => {
264
- _mountedProviderCount--;
265
- if (configOverride) _mountedConfigOverrideCount--;
266
- if (_prevConfigOverrideRef.current !== void 0 && _mountedProviderCount === 0) {
267
- (0, import_core2.resetConfig)();
268
- }
269
- };
270
- }, []);
271
- const [webWidth, setWebWidth] = (0, import_react5.useState)(0);
272
- const effectiveWidth = (0, import_core2.getEffectiveIsWeb)() ? webWidth : windowWidthProp ?? 0;
273
- const numericScreens = (0, import_react5.useMemo)(() => {
274
- const out = {};
275
- for (const [k, v] of Object.entries(resolvedConfig.theme.screens ?? {})) {
276
- out[k] = typeof v === "number" ? v : parseInt(String(v), 10);
277
- }
278
- return out;
279
- }, [resolvedConfig.theme.screens]);
280
- const _prevWidthRef = (0, import_react5.useRef)(-1);
281
- if (_prevWidthRef.current !== effectiveWidth) {
282
- _prevWidthRef.current = effectiveWidth;
283
- (0, import_core2.syncGlobalWidth)(effectiveWidth);
284
- }
285
- const _prevScreensRef = (0, import_react5.useRef)(null);
286
- if (_prevScreensRef.current !== numericScreens) {
287
- _prevScreensRef.current = numericScreens;
288
- (0, import_core2.syncGlobalScreens)(numericScreens);
289
569
  }
290
- (0, import_react5.useEffect)(() => {
291
- (0, import_core2.setGlobalWidth)(effectiveWidth);
292
- }, [effectiveWidth]);
293
- (0, import_react5.useEffect)(() => {
294
- if (!import_core2.isWeb || typeof window === "undefined") return;
295
- setWebWidth(window.innerWidth);
296
- let raf = 0;
297
- const update = () => {
298
- cancelAnimationFrame(raf);
299
- raf = requestAnimationFrame(() => {
300
- const w = window.innerWidth;
301
- setWebWidth(w);
302
- (0, import_core2.setGlobalWidth)(w);
303
- });
304
- };
305
- window.addEventListener("resize", update);
306
- return () => {
307
- window.removeEventListener("resize", update);
308
- cancelAnimationFrame(raf);
309
- };
310
- }, []);
311
- const webScheme = useSyncExternalStore(
312
- subscribeSystemScheme,
313
- getSystemScheme,
314
- getSystemSchemeServerSnapshot
315
- );
316
- const systemScheme = (0, import_core2.getEffectiveIsWeb)() ? webScheme : colorScheme === "dark" ? "dark" : "light";
317
- const [mode, _setMode] = (0, import_react5.useState)(defaultMode);
318
- (0, import_react5.useEffect)(() => {
319
- if (disablePersistence) return;
320
- const persisted = loadPersistedMode();
321
- if (persisted) _setMode(persisted);
322
- }, []);
323
- (0, import_react5.useEffect)(() => {
324
- if (disablePersistence || !import_core2.isWeb || typeof window === "undefined") return;
325
- const onStorage = (e) => {
326
- if (e.key !== STORAGE_KEY || e.newValue == null) return;
327
- if (e.newValue === "light" || e.newValue === "dark" || e.newValue === "system") {
328
- _setMode(e.newValue);
329
- }
330
- };
331
- window.addEventListener("storage", onStorage);
332
- return () => window.removeEventListener("storage", onStorage);
333
- }, [disablePersistence]);
334
- const setMode = (0, import_react5.useCallback)((next) => {
335
- if (process.env.NODE_ENV !== "production" && import_core2.isWeb && resolvedConfig.darkMode === "media" && !_warnedMediaModeToggle) {
336
- _warnedMediaModeToggle = true;
337
- (0, import_core2.kbachWarn)(
338
- 'setMode()/toggle() was called while darkMode is "media" \u2014 the resolved mode/isDark from useTheme()/useIsDark() will update, but the actual CSS stays driven by prefers-color-scheme and will not change to match. Use darkMode: "attribute" or "class" if the app needs manual toggling.'
339
- );
340
- }
341
- _setMode(next);
342
- if (!disablePersistence) persistMode(next);
343
- }, [disablePersistence, resolvedConfig.darkMode]);
344
- const toggle = (0, import_react5.useCallback)(() => {
345
- setMode(mode === "dark" || mode === "system" && systemScheme === "dark" ? "light" : "dark");
346
- }, [mode, systemScheme, setMode]);
347
- const resolvedMode = mode === "system" ? systemScheme : mode;
348
- const isDark = resolvedMode === "dark";
349
- (0, import_core2.syncGlobalDarkMode)(isDark);
350
- useIsomorphicLayoutEffect(() => {
351
- applyWebTheme(resolvedMode, resolvedConfig.darkMode);
352
- (0, import_core2.setGlobalDarkMode)(isDark);
353
- }, [isDark, resolvedMode, resolvedConfig.darkMode]);
354
- (0, import_react5.useEffect)(() => {
355
- if (configOverride) return;
356
- let mounted = true;
357
- const unsub = (0, import_core2.onConfigChange)((config) => {
358
- if (mounted) setResolvedConfig(config);
359
- });
360
- return () => {
361
- mounted = false;
362
- unsub();
363
- };
364
- }, [configOverride]);
365
- const contextValue = (0, import_react5.useMemo)(
366
- () => ({ mode, resolvedMode, isDark, setMode, toggle, config: resolvedConfig }),
367
- [mode, resolvedMode, isDark, setMode, toggle, resolvedConfig]
368
- );
369
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemeContext.Provider, { value: contextValue, children });
570
+ return className;
370
571
  }
371
572
 
372
573
  // src/KbachReset.tsx
373
- var import_core3 = require("./core");
374
- var import_jsx_runtime2 = require("react/jsx-runtime");
574
+ var import_jsx_runtime = require("react/jsx-runtime");
375
575
  function KbachReset() {
376
- if (import_core3.isNative) return null;
377
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { id: import_core3.RESET_STYLE_ID, children: import_core3.BASE_RESET });
576
+ if (isRuntimeCSSDisabled()) return null;
577
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { id: RESET_STYLE_ID, children: BASE_RESET });
378
578
  }
379
579
 
380
- // src/styled.tsx
381
- var import_react6 = __toESM(require("react"));
382
- var import_core7 = require("./core");
383
-
384
- // src/useGlobalDarkMode.ts
385
- var import_core4 = require("./core");
386
- var NOOP_SUB = (_) => () => {
387
- };
388
- var FALSE_SNAP = () => false;
389
- function useGlobalDarkMode() {
390
- return useSyncExternalStore(
391
- import_core4.subscribeGlobalDarkMode,
392
- import_core4.getGlobalDarkMode,
393
- () => false
394
- // SSR: default light
395
- );
580
+ // src/darkModeStore.ts
581
+ var STORAGE_KEY = "kbach-theme";
582
+ var listeners = /* @__PURE__ */ new Set();
583
+ function systemPrefersDark() {
584
+ if (typeof window === "undefined" || !window.matchMedia) return false;
585
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
396
586
  }
397
- function useConditionalGlobalDarkMode(active) {
398
- return useSyncExternalStore(
399
- active ? import_core4.subscribeGlobalDarkMode : NOOP_SUB,
400
- active ? import_core4.getGlobalDarkMode : FALSE_SNAP,
401
- FALSE_SNAP
402
- );
587
+ function storage() {
588
+ return typeof window === "undefined" ? null : window.localStorage;
403
589
  }
404
-
405
- // src/useGlobalWidth.ts
406
- var import_core5 = require("./core");
407
- var NOOP_SUB2 = (_) => () => {
408
- };
409
- var ZERO_SNAP = () => 0;
410
- var EMPTY_BREAKPOINTS = /* @__PURE__ */ new Set();
411
- var _webListeners = null;
412
- var _webRaf = 0;
413
- function _webResizeHandler() {
414
- cancelAnimationFrame(_webRaf);
415
- _webRaf = requestAnimationFrame(() => {
416
- if (_webListeners) for (const cb of _webListeners) cb();
417
- });
418
- }
419
- function subscribeWebWidth(cb) {
420
- if (typeof window === "undefined") return () => {
421
- };
422
- if (!_webListeners) {
423
- _webListeners = /* @__PURE__ */ new Set();
424
- window.addEventListener("resize", _webResizeHandler);
590
+ function readPersistedMode() {
591
+ const ls = storage();
592
+ if (!ls) return null;
593
+ try {
594
+ const raw = ls.getItem(STORAGE_KEY);
595
+ return raw === "light" || raw === "dark" || raw === "system" ? raw : null;
596
+ } catch {
597
+ return null;
425
598
  }
426
- _webListeners.add(cb);
427
- return () => {
428
- _webListeners.delete(cb);
429
- if (_webListeners.size === 0) {
430
- window.removeEventListener("resize", _webResizeHandler);
431
- cancelAnimationFrame(_webRaf);
432
- _webListeners = null;
433
- }
434
- };
435
- }
436
- function getWebWidth() {
437
- return typeof window !== "undefined" ? window.innerWidth : 0;
438
- }
439
- function useGlobalWidth() {
440
- return useSyncExternalStore(
441
- import_core5.isWeb ? subscribeWebWidth : import_core5.subscribeGlobalWidth,
442
- import_core5.isWeb ? getWebWidth : import_core5.getGlobalWidth,
443
- ZERO_SNAP
444
- );
445
599
  }
446
- function useConditionalWidth(active) {
447
- return useSyncExternalStore(
448
- active ? import_core5.isWeb ? subscribeWebWidth : import_core5.subscribeGlobalWidth : NOOP_SUB2,
449
- active ? import_core5.isWeb ? getWebWidth : import_core5.getGlobalWidth : ZERO_SNAP,
450
- ZERO_SNAP
451
- );
600
+ function resolveIsDark(m) {
601
+ return m === "system" ? systemPrefersDark() : m === "dark";
452
602
  }
453
-
454
- // src/shared-utils.ts
455
- var import_core6 = require("./core");
456
- function hasResponsiveBuckets(resolved) {
457
- const responsiveMods = (0, import_core6.getResponsiveModifiers)();
458
- for (const key of Object.keys(resolved)) {
459
- if (key === "base") continue;
460
- for (const mod of key.split(":")) {
461
- if (responsiveMods.has(mod)) return true;
462
- }
463
- }
464
- return false;
465
- }
466
- function hasInteractiveBuckets(resolved) {
467
- const interactiveMods = (0, import_core6.getInteractiveModifiers)();
468
- for (const key of Object.keys(resolved)) {
469
- if (key === "base") continue;
470
- for (const mod of key.split(":")) {
471
- if (interactiveMods.has(mod)) return true;
472
- }
603
+ function applyToDom(dark) {
604
+ if (typeof document === "undefined") return;
605
+ const root = document.documentElement;
606
+ const strategy = getTheme().darkMode;
607
+ if (strategy === "class") {
608
+ root.classList.toggle("dark", dark);
609
+ root.classList.toggle("light", !dark);
610
+ } else if (strategy === "attribute") {
611
+ root.setAttribute("data-theme", dark ? "dark" : "light");
473
612
  }
474
- return false;
475
613
  }
476
- function chain(original, extra) {
477
- return (...args) => {
478
- original?.(...args);
479
- extra();
614
+ var persistedMode = readPersistedMode();
615
+ var hadPersistedMode = persistedMode !== null;
616
+ var mode = persistedMode ?? "system";
617
+ var isDark = resolveIsDark(mode);
618
+ applyToDom(isDark);
619
+ if (typeof window !== "undefined" && window.matchMedia) {
620
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
621
+ const onSystemChange = (event) => {
622
+ if (mode !== "system") return;
623
+ isDark = event.matches;
624
+ applyToDom(isDark);
625
+ notify();
480
626
  };
481
- }
482
- function stripInternalMarkers(s) {
483
- delete s.__divideX;
484
- delete s.__divideY;
485
- delete s.__divideColor;
486
- delete s.__divideStyle;
487
- delete s.__keyframe;
488
- }
489
- function stripWebOnlyProps(s) {
490
- if (s.display === "grid" || s.display === "inline-grid") delete s.display;
491
- delete s.gridTemplateColumns;
492
- delete s.gridTemplateRows;
493
- delete s.gridColumn;
494
- delete s.gridRow;
495
- delete s.gridArea;
496
- delete s.gridColumnStart;
497
- delete s.gridColumnEnd;
498
- delete s.gridRowStart;
499
- delete s.gridRowEnd;
500
- delete s.gridAutoFlow;
501
- delete s.gridAutoColumns;
502
- delete s.gridAutoRows;
503
- delete s.placeItems;
504
- delete s.placeContent;
505
- delete s.justifyItems;
506
- delete s.placeSelf;
507
- delete s.justifySelf;
508
- if (s.position === "sticky" || s.position === "fixed" || s.position === "static") {
509
- delete s.position;
510
- }
511
- }
512
- function composeNativeStyle(computed, userStyle) {
513
- if (!userStyle) return computed;
514
- return Array.isArray(userStyle) ? [computed, ...userStyle] : [computed, userStyle];
515
- }
516
-
517
- // src/web-substitute.ts
518
- var _rnToHtml = {
519
- View: "div",
520
- SafeAreaView: "div",
521
- KeyboardAvoidingView: "div",
522
- ScrollView: "div",
523
- VirtualizedList: "div",
524
- FlatList: "div",
525
- SectionList: "div",
526
- Text: "span",
527
- TextInput: "input",
528
- Image: "img",
529
- ImageBackground: "div",
530
- Pressable: "div",
531
- TouchableOpacity: "div",
532
- TouchableHighlight: "div",
533
- TouchableWithoutFeedback: "div",
534
- TouchableNativeFeedback: "div"
535
- };
536
- var _userMap = /* @__PURE__ */ new Map();
537
- var _cache = /* @__PURE__ */ new WeakMap();
538
- function registerWebElement(rnComponent, htmlTag) {
539
- if (typeof rnComponent !== "object" && typeof rnComponent !== "function") return;
540
- _userMap.set(rnComponent, htmlTag);
541
- _cache.delete(rnComponent);
542
- }
543
- var _forwardRefOrMemoType = /* @__PURE__ */ new Set([
544
- /* @__PURE__ */ Symbol.for("react.forward_ref"),
545
- /* @__PURE__ */ Symbol.for("react.memo")
546
- ]);
547
- function looksLikeRealRNPrimitive(type) {
548
- const t = type.$$typeof;
549
- if (t !== void 0) return _forwardRefOrMemoType.has(t);
550
- return !!type.prototype?.isReactComponent;
551
- }
552
- function getWebTag(type, props) {
553
- if (typeof type === "string") return null;
554
- if (type === null || typeof type !== "function" && typeof type !== "object") return null;
555
- const obj = type;
556
- if (_userMap.has(obj)) return _userMap.get(obj);
557
- if (!looksLikeRealRNPrimitive(obj)) return null;
558
- const name = obj.displayName ?? obj.name;
559
- if (!name) {
560
- _cache.set(obj, null);
561
- return null;
562
- }
563
- if (name === "TextInput") {
564
- const multiline = !!props && (props.multiline === true || Number(props.numberOfLines) > 1);
565
- return multiline ? "textarea" : "input";
627
+ if (mq.addEventListener) mq.addEventListener("change", onSystemChange);
628
+ else mq.addListener(onSystemChange);
629
+ }
630
+ function notify() {
631
+ for (const listener of listeners) listener();
632
+ }
633
+ function getGlobalDarkMode() {
634
+ return isDark;
635
+ }
636
+ function getGlobalThemeMode() {
637
+ return mode;
638
+ }
639
+ function setGlobalThemeMode(nextMode) {
640
+ mode = nextMode;
641
+ isDark = resolveIsDark(nextMode);
642
+ applyToDom(isDark);
643
+ const ls = storage();
644
+ if (ls) {
645
+ try {
646
+ ls.setItem(STORAGE_KEY, nextMode);
647
+ } catch {
648
+ }
566
649
  }
567
- if (_cache.has(obj)) return _cache.get(obj);
568
- const tag = _rnToHtml[name] ?? null;
569
- _cache.set(obj, tag);
570
- return tag;
650
+ notify();
571
651
  }
572
- function getImpliedRNClasses(webTag, resolvedBase) {
573
- if (!webTag || !resolvedBase) return void 0;
574
- const classes = [];
575
- if (resolvedBase.position === void 0) classes.push("relative");
576
- const explicitDisplay = resolvedBase.display;
577
- const hasFlexItemProps = "flexGrow" in resolvedBase || "flexShrink" in resolvedBase || "flex" in resolvedBase || "gap" in resolvedBase || "columnGap" in resolvedBase || "rowGap" in resolvedBase;
578
- const willBeFlex = explicitDisplay === "flex" || explicitDisplay === void 0 && hasFlexItemProps;
579
- if (willBeFlex && (explicitDisplay === void 0 || resolvedBase.flexDirection === void 0)) {
580
- classes.push("flex-col");
581
- }
582
- return classes.length > 0 ? classes.join(" ") : void 0;
652
+ function toggleGlobalDarkMode() {
653
+ setGlobalThemeMode(isDark ? "light" : "dark");
583
654
  }
584
- var _rnOnlyProps = /* @__PURE__ */ new Set([
585
- // Interaction
586
- "onLongPress",
587
- "delayLongPress",
588
- "activeOpacity",
589
- "underlayColor",
590
- "hitSlop",
591
- "pressRetentionOffset",
592
- "android_ripple",
593
- "android_disableSound",
594
- "onHoverIn",
595
- "onHoverOut",
596
- "onHoverStart",
597
- "onHoverEnd",
598
- // Layout event
599
- "onLayout",
600
- // Accessibility
601
- "accessible",
602
- "accessibilityState",
603
- "accessibilityLiveRegion",
604
- "importantForAccessibility",
605
- // Platform
606
- "nativeID",
607
- "collapsable",
608
- "needsOffscreenAlphaCompositing",
609
- "renderToHardwareTextureAndroid",
610
- "shouldRasterizeIOS",
611
- "focusable",
612
- "hasTVPreferredFocus",
613
- // 'pointerEvents' is handled in transformToWebProps (mapped to CSS style)
614
- // Text
615
- "selectable",
616
- "allowFontScaling",
617
- "adjustsFontSizeToFit",
618
- "minimumFontScale",
619
- "ellipsizeMode",
620
- "numberOfLines",
621
- "onTextLayout",
622
- "textBreakStrategy",
623
- "lineBreakStrategyIOS",
624
- // TextInput
625
- "multiline",
626
- // NOTE: 'resizeMode' (Image's prop, not TextInput's) deliberately does NOT
627
- // go here — it's handled below in the isImage branch (mapped to CSS
628
- // object-fit). Blacklisting it here would make that branch unreachable.
629
- "blurOnSubmit",
630
- "clearButtonMode",
631
- "clearTextOnFocus",
632
- "enablesReturnKeyAutomatically",
633
- "returnKeyType",
634
- "spellCheck",
635
- // ScrollView
636
- "scrollEnabled",
637
- "showsVerticalScrollIndicator",
638
- "showsHorizontalScrollIndicator",
639
- "contentContainerStyle",
640
- // 'horizontal' is handled in transformToWebProps (converted to CSS overflow-x).
641
- "keyboardShouldPersistTaps",
642
- "keyboardDismissMode",
643
- "pagingEnabled",
644
- "scrollEventThrottle",
645
- "decelerationRate",
646
- "bounces",
647
- "alwaysBounceHorizontal",
648
- "alwaysBounceVertical",
649
- "snapToAlignment",
650
- "snapToInterval",
651
- "snapToOffsets",
652
- "removeClippedSubviews",
653
- "overScrollMode",
654
- "stickyHeaderIndices",
655
- "invertStickyHeaders",
656
- "onScrollBeginDrag",
657
- "onScrollEndDrag",
658
- "onMomentumScrollBegin",
659
- "onMomentumScrollEnd",
660
- "contentInset",
661
- "contentInsetAdjustmentBehavior",
662
- "automaticallyAdjustContentInsets",
663
- "automaticallyAdjustsScrollIndicatorInsets",
664
- // expo-image
665
- "contentPosition",
666
- "cachePolicy",
667
- "recyclingKey",
668
- "blurRadius",
669
- "fadeDuration",
670
- "responsivePolicy",
671
- "tintColor",
672
- "allowDownscaling",
673
- "placeholderContentFit",
674
- // FlatList / SectionList
675
- "data",
676
- "renderItem",
677
- "keyExtractor",
678
- "getItemLayout",
679
- "initialScrollIndex",
680
- "initialNumToRender",
681
- "maxToRenderPerBatch",
682
- "windowSize",
683
- "updateCellsBatchingPeriod",
684
- "onEndReached",
685
- "onEndReachedThreshold",
686
- "ListHeaderComponent",
687
- "ListFooterComponent",
688
- "ListEmptyComponent",
689
- "ListHeaderComponentStyle",
690
- "ListFooterComponentStyle",
691
- "ItemSeparatorComponent",
692
- "SectionSeparatorComponent",
693
- "inverted",
694
- "getItem",
695
- "getItemCount"
696
- ]);
697
- var _pressableNames = /* @__PURE__ */ new Set([
698
- "Pressable",
699
- "TouchableOpacity",
700
- "TouchableHighlight",
701
- "TouchableWithoutFeedback",
702
- "TouchableNativeFeedback"
703
- ]);
704
- var _keyboardTypeMap = {
705
- "numeric": "number",
706
- "number-pad": "number",
707
- "decimal-pad": "decimal",
708
- "email-address": "email",
709
- "phone-pad": "tel",
710
- "url": "url"
711
- };
712
- var _resizeModeMap = {
713
- "contain": "contain",
714
- "cover": "cover",
715
- "stretch": "fill",
716
- "center": "none",
717
- "repeat": "none"
718
- };
719
- var PLACEHOLDER_RULE_ATTR = "data-kbach-ph";
720
- var _placeholderRuleInjected = false;
721
- function ensurePlaceholderColorRuleInjected() {
722
- if (_placeholderRuleInjected || typeof document === "undefined") return;
723
- _placeholderRuleInjected = true;
724
- const style = document.createElement("style");
725
- style.setAttribute("data-kbach-placeholder", "");
726
- style.textContent = `[${PLACEHOLDER_RULE_ATTR}]::placeholder{color:var(--kbach-ph-color)}`;
727
- document.head.appendChild(style);
655
+ function subscribeGlobalDarkMode(listener) {
656
+ listeners.add(listener);
657
+ return () => listeners.delete(listener);
728
658
  }
729
- function transformToWebProps(originalName, tag, props) {
730
- const out = {};
731
- const isPressable = _pressableNames.has(originalName);
732
- const isTextInput = originalName === "TextInput";
733
- const isImage = originalName === "Image" || originalName === "ImageBackground";
734
- const isScrollable = originalName === "ScrollView" || originalName === "FlatList" || originalName === "SectionList";
735
- let pendingStyle = null;
736
- for (const [k, v] of Object.entries(props)) {
737
- if (_rnOnlyProps.has(k)) continue;
738
- if (k === "onPress") {
739
- if (!("onClick" in props)) out.onClick = v;
740
- continue;
741
- }
742
- if (k === "accessibilityLabel") {
743
- if (out["aria-label"] == null) out["aria-label"] = v;
744
- continue;
745
- }
746
- if (k === "accessibilityRole") {
747
- if (out.role == null) out.role = v;
748
- continue;
749
- }
750
- if (k === "testID") {
751
- if (out["data-testid"] == null) out["data-testid"] = v;
752
- continue;
753
- }
754
- if (isTextInput) {
755
- if (k === "onChangeText") {
756
- if (!("onChange" in props)) out.onChange = (e) => v(e.target.value);
757
- continue;
758
- }
759
- if (k === "onSubmitEditing") {
760
- if (!("onKeyDown" in props)) {
761
- out.onKeyDown = (e) => {
762
- if (e.key === "Enter") v();
763
- };
764
- }
765
- continue;
766
- }
767
- if (k === "placeholderTextColor") {
768
- if (v) {
769
- ensurePlaceholderColorRuleInjected();
770
- out[PLACEHOLDER_RULE_ATTR] = "";
771
- pendingStyle = { ...pendingStyle ?? {}, "--kbach-ph-color": v };
772
- }
773
- continue;
774
- }
775
- if (k === "secureTextEntry") {
776
- if (v && !("type" in props)) out.type = "password";
777
- continue;
778
- }
779
- if (k === "keyboardType") {
780
- if (!("type" in props) && !props.secureTextEntry) {
781
- const mapped = _keyboardTypeMap[v];
782
- if (mapped) out.type = mapped;
783
- }
784
- continue;
785
- }
786
- if (k === "editable") {
787
- if (v === false) out.readOnly = true;
788
- continue;
789
- }
790
- if (k === "maxLength") {
791
- out.maxLength = v;
792
- continue;
793
- }
794
- }
795
- if (isScrollable && k === "horizontal") {
796
- if (v) pendingStyle = { ...pendingStyle ?? {}, display: "flex", flexDirection: "row", overflowX: "auto" };
797
- continue;
798
- }
799
- if (k === "pointerEvents") {
800
- if (v === "none" || v === "auto") pendingStyle = { ...pendingStyle ?? {}, pointerEvents: v };
801
- continue;
802
- }
803
- if (isImage) {
804
- if (k === "source") {
805
- if (typeof v === "string") {
806
- out.src = v;
807
- } else if (v && typeof v === "object" && "uri" in v) {
808
- out.src = v.uri;
809
- if (v.headers) out["crossOrigin"] = "anonymous";
810
- }
811
- continue;
812
- }
813
- if (k === "resizeMode") {
814
- pendingStyle = { ...pendingStyle ?? {}, objectFit: _resizeModeMap[v] ?? "cover" };
815
- continue;
816
- }
817
- if (k === "contentFit") {
818
- pendingStyle = { ...pendingStyle ?? {}, objectFit: v };
819
- continue;
820
- }
821
- if (k === "defaultSource") continue;
822
- }
823
- if (k === "style") {
824
- if (Array.isArray(v)) {
825
- out.style = Object.assign({}, ...v.filter(Boolean));
826
- } else if (v != null) {
827
- out.style = v;
828
- }
829
- continue;
830
- }
831
- out[k] = v;
832
- }
833
- if (pendingStyle) {
834
- out.style = out.style ? { ...pendingStyle, ...out.style } : pendingStyle;
835
- }
836
- if (isPressable && !out.role) out.role = "button";
837
- if (isImage && tag === "img" && out.alt == null) out.alt = "";
838
- return out;
659
+ function seedDefaultMode(fallback) {
660
+ if (hadPersistedMode || mode === fallback) return;
661
+ mode = fallback;
662
+ isDark = resolveIsDark(fallback);
663
+ applyToDom(isDark);
664
+ notify();
839
665
  }
840
666
 
841
- // src/styled.tsx
842
- function styled(Component, baseClasses = "") {
843
- const Styled = (0, import_react6.forwardRef)(
844
- (props, ref) => {
845
- const {
846
- kb: extraClasses,
847
- style: styleProp,
848
- onPressIn,
849
- onPressOut,
850
- onPointerDown,
851
- onPointerUp,
852
- onPointerLeave,
853
- onPointerCancel,
854
- onMouseEnter,
855
- onMouseLeave,
856
- onFocus,
857
- onBlur,
858
- ...rest
859
- } = props;
860
- const disabled = !!props.disabled;
861
- const checked = !!props.checked;
862
- const { config } = useTheme();
863
- const isWebPlatform = (0, import_core7.getEffectiveIsWeb)();
864
- const isDark = useConditionalGlobalDarkMode(!isWebPlatform);
865
- const [pressed, setPressed] = (0, import_react6.useState)(false);
866
- const [hovered, setHovered] = (0, import_react6.useState)(false);
867
- const [focused, setFocused] = (0, import_react6.useState)(false);
868
- const handlePressIn = (0, import_react6.useCallback)(chain(onPressIn, () => {
869
- if (!isWebPlatform) setPressed(true);
870
- }), [onPressIn, isWebPlatform]);
871
- const handlePressOut = (0, import_react6.useCallback)(chain(onPressOut, () => {
872
- if (!isWebPlatform) setPressed(false);
873
- }), [onPressOut, isWebPlatform]);
874
- const handlePointerDown = (0, import_react6.useCallback)(chain(onPointerDown, () => {
875
- if (!isWebPlatform) setPressed(true);
876
- }), [onPointerDown, isWebPlatform]);
877
- const handlePointerUp = (0, import_react6.useCallback)(chain(onPointerUp, () => {
878
- if (!isWebPlatform) setPressed(false);
879
- }), [onPointerUp, isWebPlatform]);
880
- const handlePointerLeave = (0, import_react6.useCallback)(chain(onPointerLeave, () => {
881
- if (!isWebPlatform) setPressed(false);
882
- }), [onPointerLeave, isWebPlatform]);
883
- const handlePointerCancel = (0, import_react6.useCallback)(chain(onPointerCancel, () => {
884
- if (!isWebPlatform) setPressed(false);
885
- }), [onPointerCancel, isWebPlatform]);
886
- const handleMouseEnter = (0, import_react6.useCallback)(chain(onMouseEnter, () => {
887
- if (!isWebPlatform) setHovered(true);
888
- }), [onMouseEnter, isWebPlatform]);
889
- const handleMouseLeave = (0, import_react6.useCallback)(chain(onMouseLeave, () => {
890
- if (!isWebPlatform) setHovered(false);
891
- }), [onMouseLeave, isWebPlatform]);
892
- const handleFocus = (0, import_react6.useCallback)(chain(onFocus, () => {
893
- if (!isWebPlatform) setFocused(true);
894
- }), [onFocus, isWebPlatform]);
895
- const handleBlur = (0, import_react6.useCallback)(chain(onBlur, () => {
896
- if (!isWebPlatform) setFocused(false);
897
- }), [onBlur, isWebPlatform]);
898
- const combined = extraClasses ? `${baseClasses} ${extraClasses}` : baseClasses;
899
- const firstPassResolved = (0, import_react6.useMemo)(() => (0, import_core7.resolve)(combined, config.theme, config.darkMode), [combined, config.theme, config.darkMode]);
900
- const webTag = isWebPlatform ? getWebTag(Component, rest) : null;
901
- const impliedClasses = getImpliedRNClasses(webTag, firstPassResolved.base);
902
- const finalClassStr = impliedClasses ? `${combined} ${impliedClasses}` : combined;
903
- const resolved = (0, import_react6.useMemo)(() => (0, import_core7.resolve)(finalClassStr, config.theme, config.darkMode), [finalClassStr, config.theme, config.darkMode]);
904
- const hasInteractive = (0, import_react6.useMemo)(() => hasInteractiveBuckets(resolved), [resolved]);
905
- const needsWidth = hasResponsiveBuckets(resolved);
906
- const width = useConditionalWidth(needsWidth && !isWebPlatform);
907
- const breakpoints = needsWidth ? (0, import_core7.getActiveBreakpoints)(width) : EMPTY_BREAKPOINTS;
908
- const screens = (0, import_core7.getGlobalScreens)();
909
- const computedStyle = (0, import_react6.useMemo)(
910
- () => isWebPlatform ? {} : (0, import_core7.flatten)(resolved, isDark, { pressed, hover: hovered, focus: focused, disabled, checked }, breakpoints),
911
- [resolved, isDark, pressed, hovered, focused, disabled, checked, width, screens]
912
- // eslint-disable-line react-hooks/exhaustive-deps
913
- );
914
- const finalStyle = isWebPlatform ? (Array.isArray(styleProp) ? Object.assign({}, ...styleProp) : styleProp) ?? void 0 : composeNativeStyle(computedStyle, styleProp);
915
- const effectiveComponent = webTag ?? Component;
916
- const componentName2 = Component.displayName ?? Component.name ?? "";
917
- const effectiveRest = webTag && componentName2 ? transformToWebProps(componentName2, webTag, rest) : rest;
918
- return import_react6.default.createElement(effectiveComponent, {
919
- ref,
920
- ...effectiveRest,
921
- style: finalStyle,
922
- // className lets injected CSS rules (group-hover:, before:, print:, etc.) match the element.
923
- ...isWebPlatform && finalClassStr ? { className: (0, import_core7.normalizeClassString)(finalClassStr) } : {},
924
- // Only attach state-tracking handlers when interactive modifiers are present.
925
- // Always forward user-provided handlers to avoid silently swallowing them.
926
- // Gated on isNative, not isWeb: isWeb is false during SSR too (no `window` there),
927
- // and onPressIn/onPressOut must stay excluded there as well — SSR is exactly where
928
- // getWebTag() above is skipped (it's isWeb-gated), so effectiveComponent may still be
929
- // a non-string RN-style reference server-side, and this used to forward onPressIn/
930
- // onPressOut to it unconditionally in that case (isWeb ? ... : ...'s false branch
931
- // covered both native AND SSR). onPressIn/onPressOut are RN-only prop names — a
932
- // non-string effectiveComponent on the web/SSR side is just as likely to be an
933
- // ordinary web component (React Router's <Link>, Next.js's <Link>, any custom
934
- // wrapper) as an actual react-native-web primitive, and "not a string" alone isn't a
935
- // reliable signal either way. Forwarding them made React DOM warn "Unknown event
936
- // handler property" the moment any such component got an interactive modifier
937
- // (hover:, active:, …), which is the common case, not the exception.
938
- ...!import_core7.isNative ? hasInteractive ? {
939
- onPointerDown: handlePointerDown,
940
- onPointerUp: handlePointerUp,
941
- onPointerLeave: handlePointerLeave,
942
- onPointerCancel: handlePointerCancel
943
- } : {
944
- onPointerDown,
945
- onPointerUp,
946
- onPointerLeave,
947
- onPointerCancel
948
- } : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
949
- onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
950
- onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
951
- onFocus: hasInteractive ? handleFocus : onFocus,
952
- onBlur: hasInteractive ? handleBlur : onBlur
953
- });
954
- }
955
- );
956
- const componentName = Component.displayName ?? Component.name ?? "Component";
957
- Styled.displayName = `Styled(${componentName})`;
958
- return Styled;
667
+ // src/useGlobalDarkMode.ts
668
+ var import_react = require("react");
669
+ var getServerSnapshot = () => false;
670
+ function useGlobalDarkMode() {
671
+ return (0, import_react.useSyncExternalStore)(subscribeGlobalDarkMode, getGlobalDarkMode, getServerSnapshot);
959
672
  }
960
673
 
961
- // src/useStyles.ts
962
- var import_react7 = require("react");
963
- var import_core8 = require("./core");
964
- function useStyles(classString, state = {}) {
965
- const { isDark, config } = useTheme();
966
- const width = useGlobalWidth();
967
- const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
968
- return (0, import_react7.useMemo)(() => {
969
- const resolved = (0, import_core8.resolve)(normalised, config.theme, config.darkMode);
970
- const breakpoints = (0, import_core8.getActiveBreakpoints)(width);
971
- return (0, import_core8.flatten)(resolved, isDark, state, breakpoints);
972
- }, [normalised, isDark, config.theme, config.darkMode, width, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
973
- }
974
- function useResolvedStyle(classString) {
975
- const { config } = useTheme();
976
- const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
977
- return (0, import_react7.useMemo)(
978
- () => (0, import_core8.resolve)(normalised, config.theme, config.darkMode),
979
- [normalised, config]
980
- );
981
- }
674
+ // src/ThemeProvider.tsx
675
+ var import_react3 = require("react");
982
676
 
983
- // src/useBreakpoint.ts
984
- var import_react8 = require("react");
985
- var import_core9 = require("./core");
986
- function toNumericScreens(screens) {
987
- const out = {};
988
- for (const [name, v] of Object.entries(screens)) {
989
- const n = typeof v === "number" ? v : parseInt(String(v), 10);
990
- if (!Number.isNaN(n)) out[name] = n;
677
+ // src/ThemeContext.tsx
678
+ var import_react2 = require("react");
679
+ var ThemeContext = (0, import_react2.createContext)(null);
680
+ function useTheme() {
681
+ const ctx = (0, import_react2.useContext)(ThemeContext);
682
+ if (!ctx) {
683
+ throw new Error(
684
+ 'useTheme() must be called within a <ThemeProvider>. For dark-mode state without a provider, use useGlobalDarkMode() (read) or toggleGlobalDarkMode()/setGlobalThemeMode() (write) from "@kbach/react" instead.'
685
+ );
991
686
  }
992
- return out;
993
- }
994
- function useBreakpoint() {
995
- const { config } = useTheme();
996
- const width = useGlobalWidth();
997
- const screens = config.theme.screens;
998
- const numericScreens = (0, import_react8.useMemo)(() => toNumericScreens(screens), [screens]);
999
- return (0, import_react8.useMemo)(() => {
1000
- const active = (0, import_core9.getActiveBreakpoints)(width, numericScreens);
1001
- let best = null;
1002
- let bestMinW = -Infinity;
1003
- for (const name of active) {
1004
- const minW = numericScreens[name];
1005
- if (minW > bestMinW) {
1006
- bestMinW = minW;
1007
- best = name;
1008
- }
1009
- }
1010
- return best ?? "xs";
1011
- }, [numericScreens, width]);
1012
- }
1013
- function useResponsive() {
1014
- const { config } = useTheme();
1015
- const width = useGlobalWidth();
1016
- const screens = config.theme.screens;
1017
- const numericScreens = (0, import_react8.useMemo)(() => toNumericScreens(screens), [screens]);
1018
- return (0, import_react8.useMemo)(() => {
1019
- const active = (0, import_core9.getActiveBreakpoints)(width, numericScreens);
1020
- const result = {};
1021
- for (const name of Object.keys(numericScreens)) {
1022
- result[name] = active.has(name);
1023
- }
1024
- return result;
1025
- }, [numericScreens, width]);
687
+ return ctx;
1026
688
  }
1027
689
 
1028
- // src/InteractiveWrapper.tsx
1029
- var import_react9 = __toESM(require("react"));
1030
- var import_core10 = require("./core");
1031
- var InteractiveWrapper = (0, import_react9.forwardRef)(
1032
- function InteractiveWrapper2({
1033
- Component,
1034
- resolvedStyle,
1035
- className,
1036
- style: styleProp,
1037
- onPressIn,
1038
- onPressOut,
1039
- onPointerDown,
1040
- onPointerUp,
1041
- onPointerLeave,
1042
- onPointerCancel,
1043
- onMouseEnter,
1044
- onMouseLeave,
1045
- onFocus,
1046
- onBlur,
1047
- ...rest
1048
- }, ref) {
1049
- const isWebPlatform = (0, import_core10.getEffectiveIsWeb)();
1050
- const isDark = useConditionalGlobalDarkMode(!isWebPlatform);
1051
- const needsWidth = hasResponsiveBuckets(resolvedStyle);
1052
- const width = useConditionalWidth(needsWidth && !isWebPlatform);
1053
- const breakpoints = needsWidth ? (0, import_core10.getActiveBreakpoints)(width) : EMPTY_BREAKPOINTS;
1054
- const screens = (0, import_core10.getGlobalScreens)();
1055
- const [pressed, setPressed] = (0, import_react9.useState)(false);
1056
- const [hovered, setHovered] = (0, import_react9.useState)(false);
1057
- const [focused, setFocused] = (0, import_react9.useState)(false);
1058
- const handlePressIn = (0, import_react9.useCallback)(chain(onPressIn, () => {
1059
- if (!isWebPlatform) setPressed(true);
1060
- }), [onPressIn, isWebPlatform]);
1061
- const handlePressOut = (0, import_react9.useCallback)(chain(onPressOut, () => {
1062
- if (!isWebPlatform) setPressed(false);
1063
- }), [onPressOut, isWebPlatform]);
1064
- const handlePointerDown = (0, import_react9.useCallback)(chain(onPointerDown, () => {
1065
- if (!isWebPlatform) setPressed(true);
1066
- }), [onPointerDown, isWebPlatform]);
1067
- const handlePointerUp = (0, import_react9.useCallback)(chain(onPointerUp, () => {
1068
- if (!isWebPlatform) setPressed(false);
1069
- }), [onPointerUp, isWebPlatform]);
1070
- const handlePointerLeave = (0, import_react9.useCallback)(chain(onPointerLeave, () => {
1071
- if (!isWebPlatform) setPressed(false);
1072
- }), [onPointerLeave, isWebPlatform]);
1073
- const handlePointerCancel = (0, import_react9.useCallback)(chain(onPointerCancel, () => {
1074
- if (!isWebPlatform) setPressed(false);
1075
- }), [onPointerCancel, isWebPlatform]);
1076
- const handleMouseEnter = (0, import_react9.useCallback)(chain(onMouseEnter, () => {
1077
- if (!isWebPlatform) setHovered(true);
1078
- }), [onMouseEnter, isWebPlatform]);
1079
- const handleMouseLeave = (0, import_react9.useCallback)(chain(onMouseLeave, () => {
1080
- if (!isWebPlatform) setHovered(false);
1081
- }), [onMouseLeave, isWebPlatform]);
1082
- const handleFocus = (0, import_react9.useCallback)(chain(onFocus, () => {
1083
- if (!isWebPlatform) setFocused(true);
1084
- }), [onFocus, isWebPlatform]);
1085
- const handleBlur = (0, import_react9.useCallback)(chain(onBlur, () => {
1086
- if (!isWebPlatform) setFocused(false);
1087
- }), [onBlur, isWebPlatform]);
1088
- const { children, disabled, checked, ...restForComponent } = rest;
1089
- const isNonStringComponent = typeof Component !== "string";
1090
- const computedStyle = (0, import_react9.useMemo)(
1091
- () => {
1092
- if (isWebPlatform) return {};
1093
- const s = (0, import_core10.flatten)(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused, disabled: !!disabled, checked: !!checked }, breakpoints);
1094
- stripInternalMarkers(s);
1095
- if (isNonStringComponent) stripWebOnlyProps(s);
1096
- return s;
1097
- },
1098
- [resolvedStyle, isDark, pressed, hovered, focused, width, screens, disabled, checked, isNonStringComponent]
1099
- );
1100
- const skipComputedInline = isWebPlatform;
1101
- const finalStyle = skipComputedInline ? styleProp ?? void 0 : composeNativeStyle(computedStyle, styleProp);
1102
- const componentProps = {
1103
- ref,
1104
- ...restForComponent,
1105
- ...disabled !== void 0 ? { disabled } : {},
1106
- ...checked !== void 0 ? { checked } : {},
1107
- style: finalStyle,
1108
- ...!import_core10.isNative && className ? { className } : {},
1109
- // On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch) —
1110
- // onPressIn/onPressOut are RN-only prop names and are never forwarded here, even when
1111
- // Component isn't a literal HTML tag string. A non-string Component on web is just as
1112
- // likely to be an ordinary web component (React Router's <Link>, Next.js's <Link>, any
1113
- // custom wrapper) as an actual react-native-web primitive — "not a string" alone was never
1114
- // a reliable signal that onPressIn/onPressOut are wanted, and forwarding them
1115
- // unconditionally made React DOM warn "Unknown event handler property" the moment any web
1116
- // component got an interactive modifier (hover:, active:, …), which is the common case,
1117
- // not the exception. (A real react-native-web primitive would additionally have this pair
1118
- // of props silently vanish after hydration anyway, once the browser-only web-substitution
1119
- // in jsx-runtime.tsx swaps it for a plain host tag — so keeping them pre-hydration would
1120
- // only have traded one prop-mismatch warning for another.) Genuinely native code paths
1121
- // still get them via the isNative branch below.
1122
- ...!import_core10.isNative ? {
1123
- onPointerDown: handlePointerDown,
1124
- onPointerUp: handlePointerUp,
1125
- onPointerLeave: handlePointerLeave,
1126
- onPointerCancel: handlePointerCancel
1127
- } : { onPressIn: handlePressIn, onPressOut: handlePressOut },
1128
- onMouseEnter: handleMouseEnter,
1129
- onMouseLeave: handleMouseLeave,
1130
- onFocus: handleFocus,
1131
- onBlur: handleBlur
690
+ // src/ThemeProvider.tsx
691
+ var import_jsx_runtime2 = require("react/jsx-runtime");
692
+ var mountedProviderCount = 0;
693
+ var warnedMultipleProviders = false;
694
+ function ThemeProvider({ children, defaultMode = "system" }) {
695
+ (0, import_react3.useEffect)(() => {
696
+ mountedProviderCount++;
697
+ if (mountedProviderCount > 1 && !warnedMultipleProviders) {
698
+ warnedMultipleProviders = true;
699
+ console.warn(
700
+ "[kbach] Multiple <ThemeProvider> instances are mounted at once. Dark-mode state is one global store (the same one useGlobalDarkMode()/toggleGlobalDarkMode() read and write), not scoped per-provider, so they will fight over the same state instead of each getting their own."
701
+ );
702
+ }
703
+ return () => {
704
+ mountedProviderCount--;
1132
705
  };
1133
- return Array.isArray(children) ? import_react9.default.createElement(Component, componentProps, ...children) : import_react9.default.createElement(Component, componentProps, children);
1134
- }
1135
- );
1136
- InteractiveWrapper.displayName = "Kbach.InteractiveWrapper";
1137
-
1138
- // src/kb.ts
1139
- var import_core11 = require("./core");
1140
- function kb(classString, isDark = false) {
1141
- const config = (0, import_core11.getConfig)();
1142
- const resolved = (0, import_core11.resolve)(classString, config.theme, config.darkMode);
1143
- if ((0, import_core11.getEffectiveIsWeb)()) {
1144
- return classString;
1145
- }
1146
- return (0, import_core11.flatten)(resolved, isDark);
1147
- }
1148
- function cx(...classes) {
1149
- return classes.filter(Boolean).join(" ");
706
+ }, []);
707
+ const seeded = (0, import_react3.useRef)(false);
708
+ (0, import_react3.useEffect)(() => {
709
+ if (seeded.current) return;
710
+ seeded.current = true;
711
+ seedDefaultMode(defaultMode);
712
+ }, []);
713
+ const isDark2 = (0, import_react3.useSyncExternalStore)(subscribeGlobalDarkMode, getGlobalDarkMode, () => false);
714
+ const mode2 = (0, import_react3.useSyncExternalStore)(subscribeGlobalDarkMode, getGlobalThemeMode, () => defaultMode);
715
+ const value = (0, import_react3.useMemo)(
716
+ () => ({ mode: mode2, isDark: isDark2, setMode: setGlobalThemeMode, toggle: toggleGlobalDarkMode }),
717
+ [mode2, isDark2]
718
+ );
719
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ThemeContext.Provider, { value, children });
1150
720
  }
1151
-
1152
- // src/index.ts
1153
- var import_core12 = require("./core");
1154
721
  // Annotate the CommonJS export names for ESM import in node:
1155
722
  0 && (module.exports = {
1156
- InteractiveWrapper,
1157
723
  KbachReset,
1158
- ThemeContext,
1159
724
  ThemeProvider,
1160
- buildConfig,
1161
- clearCache,
1162
- cx,
1163
- defaultColors,
1164
725
  defaultTheme,
1165
726
  disableRuntimeCSS,
1166
- flatten,
1167
- generateKbachTypesDts,
1168
- getConfig,
1169
- initConfig,
727
+ getGlobalDarkMode,
728
+ getGlobalThemeMode,
729
+ getTheme,
730
+ initKbach,
731
+ isKbachReady,
732
+ isRuntimeCSSDisabled,
1170
733
  kb,
1171
- normalizeClassString,
1172
- parseClass,
1173
- parseClasses,
1174
- registerWebElement,
1175
- resolve,
1176
- setResolveTarget,
1177
- splitClassTokens,
1178
- styled,
1179
- updateConfig,
1180
- useBreakpoint,
1181
- useColors,
734
+ setGlobalThemeMode,
735
+ setTheme,
736
+ subscribeGlobalDarkMode,
737
+ toggleGlobalDarkMode,
1182
738
  useGlobalDarkMode,
1183
- useIsDark,
1184
- useResolvedStyle,
1185
- useResponsive,
1186
- useSpacing,
1187
- useStyles,
1188
- useTheme,
1189
- wrapColors,
1190
- wrapSpacing
739
+ useTheme
1191
740
  });