@kbach/react 0.6.50 → 1.0.0-beta.0

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.mjs CHANGED
@@ -1,589 +1,182 @@
1
- 'use client';
1
+ import {
2
+ disableRuntimeCSS,
3
+ initKbach,
4
+ isKbachReady,
5
+ isRuntimeCSSDisabled,
6
+ kb
7
+ } from "./chunk-G3RJJNSY.mjs";
2
8
  import {
3
9
  BASE_RESET,
4
- EMPTY_BREAKPOINTS,
5
- InteractiveWrapper,
6
10
  RESET_STYLE_ID,
7
- buildConfig,
8
- chain,
9
- clearCache,
10
- composeNativeStyle,
11
- defaultColors,
12
11
  defaultTheme,
13
- disableRuntimeCSS,
14
- flatten,
15
- generateKbachTypesDts,
16
- getActiveBreakpoints,
17
- getConfig,
18
- getEffectiveIsWeb,
19
- getGlobalScreens,
20
- getImpliedRNClasses,
21
- getWebTag,
22
- hasInteractiveBuckets,
23
- hasResponsiveBuckets,
24
- initConfig,
25
- isModeAwareColor,
26
- isNative,
27
- isWeb,
28
- kbachWarn,
29
- normalizeClassString,
30
- onConfigChange,
31
- parseClass,
32
- parseClasses,
33
- parseHexRgb,
34
- registerWebElement,
35
- resetConfig,
36
- resolve,
37
- setGlobalDarkMode,
38
- setGlobalWidth,
39
- setResolveTarget,
40
- splitClassTokens,
41
- syncGlobalDarkMode,
42
- syncGlobalScreens,
43
- syncGlobalWidth,
44
- transformToWebProps,
45
- updateConfig,
46
- useConditionalGlobalDarkMode,
47
- useConditionalWidth,
48
- useGlobalDarkMode,
49
- useGlobalWidth,
50
- useSyncExternalStore
51
- } from "./chunk-KRRGLJLA.mjs";
52
-
53
- // src/context.tsx
54
- import { createContext, useContext } from "react";
55
- var ThemeContext = globalThis.__kbachThemeContext ?? (globalThis.__kbachThemeContext = createContext(null));
56
- function useTheme() {
57
- const ctx = useContext(ThemeContext);
58
- if (!ctx) {
59
- throw new Error(
60
- "[Kbach] useTheme() must be called inside a <ThemeProvider>. Wrap your app root with <ThemeProvider>."
61
- );
62
- }
63
- return ctx;
64
- }
65
- function useIsDark() {
66
- return useTheme().isDark;
67
- }
12
+ getTheme,
13
+ setTheme
14
+ } from "./chunk-AFY36PRX.mjs";
68
15
 
69
- // src/useColors.ts
70
- import { useMemo } from "react";
71
- function applyOpacity(color, opacity) {
72
- const a = Math.max(0, Math.min(1, opacity / 100));
73
- if (color.startsWith("#")) {
74
- const rgb = parseHexRgb(color);
75
- if (!rgb) return color;
76
- const [r, g, b] = rgb;
77
- return `rgba(${r},${g},${b},${a})`;
78
- }
79
- if (color.startsWith("rgb(")) return color.replace("rgb(", "rgba(").replace(")", `,${a})`);
80
- if (color.startsWith("rgba(")) return color.replace(/,\s*[\d.]+\)$/, `,${a})`);
81
- return color;
82
- }
83
- function pickSide(value, isDark) {
84
- return isModeAwareColor(value) ? isDark ? value.dark : value.light : value;
85
- }
86
- function makeShadeProxy(shades, isDark) {
87
- return new Proxy(shades, {
88
- get(target, prop) {
89
- const key = String(prop);
90
- if (key === "then") return void 0;
91
- if (key.includes("/")) {
92
- const slash = key.indexOf("/");
93
- const shade = key.slice(0, slash);
94
- const op = Number(key.slice(slash + 1));
95
- const color2 = target[shade];
96
- return color2 !== void 0 ? applyOpacity(pickSide(color2, isDark), op) : void 0;
97
- }
98
- const color = target[key];
99
- return color !== void 0 ? pickSide(color, isDark) : void 0;
100
- }
101
- });
102
- }
103
- function wrapColors(rawColors, isDark = false) {
104
- const cache = /* @__PURE__ */ new Map();
105
- const alpha = (color, opacity) => opacity === void 0 ? color : applyOpacity(color, opacity);
106
- return new Proxy({ alpha }, {
107
- get(_, prop) {
108
- const key = String(prop);
109
- if (key === "then") return void 0;
110
- if (key === "alpha") return alpha;
111
- if (key.includes("/")) {
112
- const slash = key.indexOf("/");
113
- const name = key.slice(0, slash);
114
- const op = Number(key.slice(slash + 1));
115
- const entry2 = rawColors[name];
116
- return entry2 !== void 0 && (typeof entry2 === "string" || isModeAwareColor(entry2)) ? applyOpacity(pickSide(entry2, isDark), op) : void 0;
117
- }
118
- const entry = rawColors[key];
119
- if (entry === void 0) return void 0;
120
- if (typeof entry === "string" || isModeAwareColor(entry)) return pickSide(entry, isDark);
121
- const cacheKey = `${key}:${isDark}`;
122
- if (!cache.has(cacheKey)) cache.set(cacheKey, makeShadeProxy(entry, isDark));
123
- return cache.get(cacheKey);
124
- }
125
- });
126
- }
127
- function useColors() {
128
- const { config, isDark } = useTheme();
129
- return useMemo(() => wrapColors(config.theme.colors, isDark), [config.theme.colors, isDark]);
16
+ // src/KbachReset.tsx
17
+ import { jsx } from "react/jsx-runtime";
18
+ function KbachReset() {
19
+ if (isRuntimeCSSDisabled()) return null;
20
+ return /* @__PURE__ */ jsx("style", { id: RESET_STYLE_ID, children: BASE_RESET });
130
21
  }
131
22
 
132
- // src/useSpacing.ts
133
- import { useMemo as useMemo2 } from "react";
134
- function wrapSpacing(rawSpacing) {
135
- return rawSpacing;
23
+ // src/darkModeStore.ts
24
+ var STORAGE_KEY = "kbach-theme";
25
+ var listeners = /* @__PURE__ */ new Set();
26
+ function systemPrefersDark() {
27
+ if (typeof window === "undefined" || !window.matchMedia) return false;
28
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
136
29
  }
137
- function useSpacing() {
138
- const { config } = useTheme();
139
- return useMemo2(() => wrapSpacing(config.theme.spacing), [config.theme.spacing]);
30
+ function storage() {
31
+ return typeof window === "undefined" ? null : window.localStorage;
140
32
  }
141
-
142
- // src/ThemeProvider.tsx
143
- import {
144
- useCallback,
145
- useEffect,
146
- useLayoutEffect,
147
- useMemo as useMemo3,
148
- useRef,
149
- useState
150
- } from "react";
151
- import { jsx } from "react/jsx-runtime";
152
- var useIsomorphicLayoutEffect = isWeb || isNative ? useLayoutEffect : useEffect;
153
- var STORAGE_KEY = "kbach-theme";
154
- var _mountedProviderCount = 0;
155
- var _warnedMultipleProviders = false;
156
- var _mountedConfigOverrideCount = 0;
157
- var _warnedConfigOverride = false;
158
- var _warnedMediaModeToggle = false;
159
- function loadPersistedMode() {
33
+ function readPersistedMode() {
34
+ const ls = storage();
35
+ if (!ls) return null;
160
36
  try {
161
- if (isWeb) return localStorage.getItem(STORAGE_KEY);
162
- return null;
37
+ const raw = ls.getItem(STORAGE_KEY);
38
+ return raw === "light" || raw === "dark" || raw === "system" ? raw : null;
163
39
  } catch {
164
40
  return null;
165
41
  }
166
42
  }
167
- function persistMode(mode) {
168
- try {
169
- if (isWeb) localStorage.setItem(STORAGE_KEY, mode);
170
- } catch {
43
+ function resolveIsDark(m) {
44
+ return m === "system" ? systemPrefersDark() : m === "dark";
45
+ }
46
+ function applyToDom(dark) {
47
+ if (typeof document === "undefined") return;
48
+ const root = document.documentElement;
49
+ const strategy = getTheme().darkMode;
50
+ if (strategy === "class") {
51
+ root.classList.toggle("dark", dark);
52
+ root.classList.toggle("light", !dark);
53
+ } else if (strategy === "attribute") {
54
+ root.setAttribute("data-theme", dark ? "dark" : "light");
171
55
  }
172
56
  }
173
- function subscribeSystemScheme(callback) {
174
- if (!isWeb || typeof window === "undefined") return () => {
57
+ var persistedMode = readPersistedMode();
58
+ var hadPersistedMode = persistedMode !== null;
59
+ var mode = persistedMode ?? "system";
60
+ var isDark = resolveIsDark(mode);
61
+ applyToDom(isDark);
62
+ if (typeof window !== "undefined" && window.matchMedia) {
63
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
64
+ const onSystemChange = (event) => {
65
+ if (mode !== "system") return;
66
+ isDark = event.matches;
67
+ applyToDom(isDark);
68
+ notify();
175
69
  };
176
- const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
177
- if (!mq) return () => {
178
- };
179
- mq.addEventListener("change", callback);
180
- return () => mq.removeEventListener("change", callback);
70
+ if (mq.addEventListener) mq.addEventListener("change", onSystemChange);
71
+ else mq.addListener(onSystemChange);
72
+ }
73
+ function notify() {
74
+ for (const listener of listeners) listener();
75
+ }
76
+ function getGlobalDarkMode() {
77
+ return isDark;
78
+ }
79
+ function getGlobalThemeMode() {
80
+ return mode;
81
+ }
82
+ function setGlobalThemeMode(nextMode) {
83
+ mode = nextMode;
84
+ isDark = resolveIsDark(nextMode);
85
+ applyToDom(isDark);
86
+ const ls = storage();
87
+ if (ls) {
88
+ try {
89
+ ls.setItem(STORAGE_KEY, nextMode);
90
+ } catch {
91
+ }
92
+ }
93
+ notify();
181
94
  }
182
- function getSystemScheme() {
183
- if (!isWeb || typeof window === "undefined") return "light";
184
- return window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ? "dark" : "light";
95
+ function toggleGlobalDarkMode() {
96
+ setGlobalThemeMode(isDark ? "light" : "dark");
185
97
  }
186
- function getSystemSchemeServerSnapshot() {
187
- return "light";
98
+ function subscribeGlobalDarkMode(listener) {
99
+ listeners.add(listener);
100
+ return () => listeners.delete(listener);
188
101
  }
189
- function applyWebTheme(resolvedMode, strategy) {
190
- if (!isWeb) return;
191
- const root = document.documentElement;
192
- if (strategy === "attribute") {
193
- root.setAttribute("data-theme", resolvedMode);
194
- } else if (strategy === "class") {
195
- root.classList.toggle("dark", resolvedMode === "dark");
196
- root.classList.toggle("light", resolvedMode === "light");
197
- }
102
+ function seedDefaultMode(fallback) {
103
+ if (hadPersistedMode || mode === fallback) return;
104
+ mode = fallback;
105
+ isDark = resolveIsDark(fallback);
106
+ applyToDom(isDark);
107
+ notify();
198
108
  }
199
- function ThemeProvider({
200
- children,
201
- defaultMode = "system",
202
- colorScheme,
203
- windowWidth: windowWidthProp,
204
- config: configOverride,
205
- disablePersistence = false
206
- }) {
207
- const [resolvedConfig, setResolvedConfig] = useState(
208
- () => configOverride ? buildConfig(configOverride) : getConfig()
209
- );
210
- const _prevConfigOverrideRef = useRef(void 0);
211
- if (configOverride && _prevConfigOverrideRef.current !== configOverride) {
212
- _prevConfigOverrideRef.current = configOverride;
213
- updateConfig(configOverride);
214
- const fresh = getConfig();
215
- if (fresh !== resolvedConfig) setResolvedConfig(fresh);
109
+
110
+ // src/useGlobalDarkMode.ts
111
+ import { useSyncExternalStore } from "react";
112
+ var getServerSnapshot = () => false;
113
+ function useGlobalDarkMode() {
114
+ return useSyncExternalStore(subscribeGlobalDarkMode, getGlobalDarkMode, getServerSnapshot);
115
+ }
116
+
117
+ // src/ThemeProvider.tsx
118
+ import { useEffect, useMemo, useRef, useSyncExternalStore as useSyncExternalStore2 } from "react";
119
+
120
+ // src/ThemeContext.tsx
121
+ import { createContext, useContext } from "react";
122
+ var ThemeContext = createContext(null);
123
+ function useTheme() {
124
+ const ctx = useContext(ThemeContext);
125
+ if (!ctx) {
126
+ throw new Error(
127
+ '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.'
128
+ );
216
129
  }
130
+ return ctx;
131
+ }
132
+
133
+ // src/ThemeProvider.tsx
134
+ import { jsx as jsx2 } from "react/jsx-runtime";
135
+ var mountedProviderCount = 0;
136
+ var warnedMultipleProviders = false;
137
+ function ThemeProvider({ children, defaultMode = "system" }) {
217
138
  useEffect(() => {
218
- _mountedProviderCount++;
219
- if (configOverride) _mountedConfigOverrideCount++;
220
- if (process.env.NODE_ENV !== "production") {
221
- if (_mountedProviderCount > 1 && !_warnedMultipleProviders) {
222
- _warnedMultipleProviders = true;
223
- kbachWarn(
224
- "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."
225
- );
226
- }
227
- if (_mountedConfigOverrideCount > 1 && !_warnedConfigOverride) {
228
- _warnedConfigOverride = true;
229
- kbachWarn(
230
- "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>."
231
- );
232
- }
139
+ mountedProviderCount++;
140
+ if (mountedProviderCount > 1 && !warnedMultipleProviders) {
141
+ warnedMultipleProviders = true;
142
+ console.warn(
143
+ "[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."
144
+ );
233
145
  }
234
146
  return () => {
235
- _mountedProviderCount--;
236
- if (configOverride) _mountedConfigOverrideCount--;
237
- if (_prevConfigOverrideRef.current !== void 0 && _mountedProviderCount === 0) {
238
- resetConfig();
239
- }
147
+ mountedProviderCount--;
240
148
  };
241
149
  }, []);
242
- const [webWidth, setWebWidth] = useState(0);
243
- const effectiveWidth = getEffectiveIsWeb() ? webWidth : windowWidthProp ?? 0;
244
- const numericScreens = useMemo3(() => {
245
- const out = {};
246
- for (const [k, v] of Object.entries(resolvedConfig.theme.screens ?? {})) {
247
- out[k] = typeof v === "number" ? v : parseInt(String(v), 10);
248
- }
249
- return out;
250
- }, [resolvedConfig.theme.screens]);
251
- const _prevWidthRef = useRef(-1);
252
- if (_prevWidthRef.current !== effectiveWidth) {
253
- _prevWidthRef.current = effectiveWidth;
254
- syncGlobalWidth(effectiveWidth);
255
- }
256
- const _prevScreensRef = useRef(null);
257
- if (_prevScreensRef.current !== numericScreens) {
258
- _prevScreensRef.current = numericScreens;
259
- syncGlobalScreens(numericScreens);
260
- }
261
- useEffect(() => {
262
- setGlobalWidth(effectiveWidth);
263
- }, [effectiveWidth]);
150
+ const seeded = useRef(false);
264
151
  useEffect(() => {
265
- if (!isWeb || typeof window === "undefined") return;
266
- setWebWidth(window.innerWidth);
267
- let raf = 0;
268
- const update = () => {
269
- cancelAnimationFrame(raf);
270
- raf = requestAnimationFrame(() => {
271
- const w = window.innerWidth;
272
- setWebWidth(w);
273
- setGlobalWidth(w);
274
- });
275
- };
276
- window.addEventListener("resize", update);
277
- return () => {
278
- window.removeEventListener("resize", update);
279
- cancelAnimationFrame(raf);
280
- };
281
- }, []);
282
- const webScheme = useSyncExternalStore(
283
- subscribeSystemScheme,
284
- getSystemScheme,
285
- getSystemSchemeServerSnapshot
286
- );
287
- const systemScheme = getEffectiveIsWeb() ? webScheme : colorScheme === "dark" ? "dark" : "light";
288
- const [mode, _setMode] = useState(defaultMode);
289
- useEffect(() => {
290
- if (disablePersistence) return;
291
- const persisted = loadPersistedMode();
292
- if (persisted) _setMode(persisted);
152
+ if (seeded.current) return;
153
+ seeded.current = true;
154
+ seedDefaultMode(defaultMode);
293
155
  }, []);
294
- useEffect(() => {
295
- if (disablePersistence || !isWeb || typeof window === "undefined") return;
296
- const onStorage = (e) => {
297
- if (e.key !== STORAGE_KEY || e.newValue == null) return;
298
- if (e.newValue === "light" || e.newValue === "dark" || e.newValue === "system") {
299
- _setMode(e.newValue);
300
- }
301
- };
302
- window.addEventListener("storage", onStorage);
303
- return () => window.removeEventListener("storage", onStorage);
304
- }, [disablePersistence]);
305
- const setMode = useCallback((next) => {
306
- if (process.env.NODE_ENV !== "production" && isWeb && resolvedConfig.darkMode === "media" && !_warnedMediaModeToggle) {
307
- _warnedMediaModeToggle = true;
308
- kbachWarn(
309
- '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.'
310
- );
311
- }
312
- _setMode(next);
313
- if (!disablePersistence) persistMode(next);
314
- }, [disablePersistence, resolvedConfig.darkMode]);
315
- const toggle = useCallback(() => {
316
- setMode(mode === "dark" || mode === "system" && systemScheme === "dark" ? "light" : "dark");
317
- }, [mode, systemScheme, setMode]);
318
- const resolvedMode = mode === "system" ? systemScheme : mode;
319
- const isDark = resolvedMode === "dark";
320
- syncGlobalDarkMode(isDark);
321
- useIsomorphicLayoutEffect(() => {
322
- applyWebTheme(resolvedMode, resolvedConfig.darkMode);
323
- setGlobalDarkMode(isDark);
324
- }, [isDark, resolvedMode, resolvedConfig.darkMode]);
325
- useEffect(() => {
326
- if (configOverride) return;
327
- let mounted = true;
328
- const unsub = onConfigChange((config) => {
329
- if (mounted) setResolvedConfig(config);
330
- });
331
- return () => {
332
- mounted = false;
333
- unsub();
334
- };
335
- }, [configOverride]);
336
- const contextValue = useMemo3(
337
- () => ({ mode, resolvedMode, isDark, setMode, toggle, config: resolvedConfig }),
338
- [mode, resolvedMode, isDark, setMode, toggle, resolvedConfig]
156
+ const isDark2 = useSyncExternalStore2(subscribeGlobalDarkMode, getGlobalDarkMode, () => false);
157
+ const mode2 = useSyncExternalStore2(subscribeGlobalDarkMode, getGlobalThemeMode, () => defaultMode);
158
+ const value = useMemo(
159
+ () => ({ mode: mode2, isDark: isDark2, setMode: setGlobalThemeMode, toggle: toggleGlobalDarkMode }),
160
+ [mode2, isDark2]
339
161
  );
340
- return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children });
341
- }
342
-
343
- // src/KbachReset.tsx
344
- import { jsx as jsx2 } from "react/jsx-runtime";
345
- function KbachReset() {
346
- if (isNative) return null;
347
- return /* @__PURE__ */ jsx2("style", { id: RESET_STYLE_ID, children: BASE_RESET });
348
- }
349
-
350
- // src/styled.tsx
351
- import React2, {
352
- forwardRef,
353
- useState as useState2,
354
- useCallback as useCallback2,
355
- useMemo as useMemo4
356
- } from "react";
357
- function styled(Component, baseClasses = "") {
358
- const Styled = forwardRef(
359
- (props, ref) => {
360
- const {
361
- kb: extraClasses,
362
- style: styleProp,
363
- onPressIn,
364
- onPressOut,
365
- onPointerDown,
366
- onPointerUp,
367
- onPointerLeave,
368
- onPointerCancel,
369
- onMouseEnter,
370
- onMouseLeave,
371
- onFocus,
372
- onBlur,
373
- ...rest
374
- } = props;
375
- const disabled = !!props.disabled;
376
- const checked = !!props.checked;
377
- const { config } = useTheme();
378
- const isWebPlatform = getEffectiveIsWeb();
379
- const isDark = useConditionalGlobalDarkMode(!isWebPlatform);
380
- const [pressed, setPressed] = useState2(false);
381
- const [hovered, setHovered] = useState2(false);
382
- const [focused, setFocused] = useState2(false);
383
- const handlePressIn = useCallback2(chain(onPressIn, () => {
384
- if (!isWebPlatform) setPressed(true);
385
- }), [onPressIn, isWebPlatform]);
386
- const handlePressOut = useCallback2(chain(onPressOut, () => {
387
- if (!isWebPlatform) setPressed(false);
388
- }), [onPressOut, isWebPlatform]);
389
- const handlePointerDown = useCallback2(chain(onPointerDown, () => {
390
- if (!isWebPlatform) setPressed(true);
391
- }), [onPointerDown, isWebPlatform]);
392
- const handlePointerUp = useCallback2(chain(onPointerUp, () => {
393
- if (!isWebPlatform) setPressed(false);
394
- }), [onPointerUp, isWebPlatform]);
395
- const handlePointerLeave = useCallback2(chain(onPointerLeave, () => {
396
- if (!isWebPlatform) setPressed(false);
397
- }), [onPointerLeave, isWebPlatform]);
398
- const handlePointerCancel = useCallback2(chain(onPointerCancel, () => {
399
- if (!isWebPlatform) setPressed(false);
400
- }), [onPointerCancel, isWebPlatform]);
401
- const handleMouseEnter = useCallback2(chain(onMouseEnter, () => {
402
- if (!isWebPlatform) setHovered(true);
403
- }), [onMouseEnter, isWebPlatform]);
404
- const handleMouseLeave = useCallback2(chain(onMouseLeave, () => {
405
- if (!isWebPlatform) setHovered(false);
406
- }), [onMouseLeave, isWebPlatform]);
407
- const handleFocus = useCallback2(chain(onFocus, () => {
408
- if (!isWebPlatform) setFocused(true);
409
- }), [onFocus, isWebPlatform]);
410
- const handleBlur = useCallback2(chain(onBlur, () => {
411
- if (!isWebPlatform) setFocused(false);
412
- }), [onBlur, isWebPlatform]);
413
- const combined = extraClasses ? `${baseClasses} ${extraClasses}` : baseClasses;
414
- const firstPassResolved = useMemo4(() => resolve(combined, config.theme, config.darkMode), [combined, config.theme, config.darkMode]);
415
- const webTag = isWebPlatform ? getWebTag(Component, rest) : null;
416
- const impliedClasses = getImpliedRNClasses(webTag, firstPassResolved.base);
417
- const finalClassStr = impliedClasses ? `${combined} ${impliedClasses}` : combined;
418
- const resolved = useMemo4(() => resolve(finalClassStr, config.theme, config.darkMode), [finalClassStr, config.theme, config.darkMode]);
419
- const hasInteractive = useMemo4(() => hasInteractiveBuckets(resolved), [resolved]);
420
- const needsWidth = hasResponsiveBuckets(resolved);
421
- const width = useConditionalWidth(needsWidth && !isWebPlatform);
422
- const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
423
- const screens = getGlobalScreens();
424
- const computedStyle = useMemo4(
425
- () => isWebPlatform ? {} : flatten(resolved, isDark, { pressed, hover: hovered, focus: focused, disabled, checked }, breakpoints),
426
- [resolved, isDark, pressed, hovered, focused, disabled, checked, width, screens]
427
- // eslint-disable-line react-hooks/exhaustive-deps
428
- );
429
- const finalStyle = isWebPlatform ? (Array.isArray(styleProp) ? Object.assign({}, ...styleProp) : styleProp) ?? void 0 : composeNativeStyle(computedStyle, styleProp);
430
- const effectiveComponent = webTag ?? Component;
431
- const componentName2 = Component.displayName ?? Component.name ?? "";
432
- const effectiveRest = webTag && componentName2 ? transformToWebProps(componentName2, webTag, rest) : rest;
433
- return React2.createElement(effectiveComponent, {
434
- ref,
435
- ...effectiveRest,
436
- style: finalStyle,
437
- // className lets injected CSS rules (group-hover:, before:, print:, etc.) match the element.
438
- ...isWebPlatform && finalClassStr ? { className: normalizeClassString(finalClassStr) } : {},
439
- // Only attach state-tracking handlers when interactive modifiers are present.
440
- // Always forward user-provided handlers to avoid silently swallowing them.
441
- // Gated on isNative, not isWeb: isWeb is false during SSR too (no `window` there),
442
- // and onPressIn/onPressOut must stay excluded there as well — SSR is exactly where
443
- // getWebTag() above is skipped (it's isWeb-gated), so effectiveComponent may still be
444
- // a non-string RN-style reference server-side, and this used to forward onPressIn/
445
- // onPressOut to it unconditionally in that case (isWeb ? ... : ...'s false branch
446
- // covered both native AND SSR). onPressIn/onPressOut are RN-only prop names — a
447
- // non-string effectiveComponent on the web/SSR side is just as likely to be an
448
- // ordinary web component (React Router's <Link>, Next.js's <Link>, any custom
449
- // wrapper) as an actual react-native-web primitive, and "not a string" alone isn't a
450
- // reliable signal either way. Forwarding them made React DOM warn "Unknown event
451
- // handler property" the moment any such component got an interactive modifier
452
- // (hover:, active:, …), which is the common case, not the exception.
453
- ...!isNative ? hasInteractive ? {
454
- onPointerDown: handlePointerDown,
455
- onPointerUp: handlePointerUp,
456
- onPointerLeave: handlePointerLeave,
457
- onPointerCancel: handlePointerCancel
458
- } : {
459
- onPointerDown,
460
- onPointerUp,
461
- onPointerLeave,
462
- onPointerCancel
463
- } : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
464
- onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
465
- onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
466
- onFocus: hasInteractive ? handleFocus : onFocus,
467
- onBlur: hasInteractive ? handleBlur : onBlur
468
- });
469
- }
470
- );
471
- const componentName = Component.displayName ?? Component.name ?? "Component";
472
- Styled.displayName = `Styled(${componentName})`;
473
- return Styled;
474
- }
475
-
476
- // src/useStyles.ts
477
- import { useMemo as useMemo5 } from "react";
478
- function useStyles(classString, state = {}) {
479
- const { isDark, config } = useTheme();
480
- const width = useGlobalWidth();
481
- const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
482
- return useMemo5(() => {
483
- const resolved = resolve(normalised, config.theme, config.darkMode);
484
- const breakpoints = getActiveBreakpoints(width);
485
- return flatten(resolved, isDark, state, breakpoints);
486
- }, [normalised, isDark, config.theme, config.darkMode, width, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
487
- }
488
- function useResolvedStyle(classString) {
489
- const { config } = useTheme();
490
- const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
491
- return useMemo5(
492
- () => resolve(normalised, config.theme, config.darkMode),
493
- [normalised, config]
494
- );
495
- }
496
-
497
- // src/useBreakpoint.ts
498
- import { useMemo as useMemo6 } from "react";
499
- function toNumericScreens(screens) {
500
- const out = {};
501
- for (const [name, v] of Object.entries(screens)) {
502
- const n = typeof v === "number" ? v : parseInt(String(v), 10);
503
- if (!Number.isNaN(n)) out[name] = n;
504
- }
505
- return out;
506
- }
507
- function useBreakpoint() {
508
- const { config } = useTheme();
509
- const width = useGlobalWidth();
510
- const screens = config.theme.screens;
511
- const numericScreens = useMemo6(() => toNumericScreens(screens), [screens]);
512
- return useMemo6(() => {
513
- const active = getActiveBreakpoints(width, numericScreens);
514
- let best = null;
515
- let bestMinW = -Infinity;
516
- for (const name of active) {
517
- const minW = numericScreens[name];
518
- if (minW > bestMinW) {
519
- bestMinW = minW;
520
- best = name;
521
- }
522
- }
523
- return best ?? "xs";
524
- }, [numericScreens, width]);
525
- }
526
- function useResponsive() {
527
- const { config } = useTheme();
528
- const width = useGlobalWidth();
529
- const screens = config.theme.screens;
530
- const numericScreens = useMemo6(() => toNumericScreens(screens), [screens]);
531
- return useMemo6(() => {
532
- const active = getActiveBreakpoints(width, numericScreens);
533
- const result = {};
534
- for (const name of Object.keys(numericScreens)) {
535
- result[name] = active.has(name);
536
- }
537
- return result;
538
- }, [numericScreens, width]);
539
- }
540
-
541
- // src/kb.ts
542
- function kb(classString, isDark = false) {
543
- const config = getConfig();
544
- const resolved = resolve(classString, config.theme, config.darkMode);
545
- if (getEffectiveIsWeb()) {
546
- return classString;
547
- }
548
- return flatten(resolved, isDark);
549
- }
550
- function cx(...classes) {
551
- return classes.filter(Boolean).join(" ");
162
+ return /* @__PURE__ */ jsx2(ThemeContext.Provider, { value, children });
552
163
  }
553
164
  export {
554
- InteractiveWrapper,
555
165
  KbachReset,
556
- ThemeContext,
557
166
  ThemeProvider,
558
- buildConfig,
559
- clearCache,
560
- cx,
561
- defaultColors,
562
167
  defaultTheme,
563
168
  disableRuntimeCSS,
564
- flatten,
565
- generateKbachTypesDts,
566
- getConfig,
567
- initConfig,
169
+ getGlobalDarkMode,
170
+ getGlobalThemeMode,
171
+ getTheme,
172
+ initKbach,
173
+ isKbachReady,
174
+ isRuntimeCSSDisabled,
568
175
  kb,
569
- normalizeClassString,
570
- parseClass,
571
- parseClasses,
572
- registerWebElement,
573
- resolve,
574
- setResolveTarget,
575
- splitClassTokens,
576
- styled,
577
- updateConfig,
578
- useBreakpoint,
579
- useColors,
176
+ setGlobalThemeMode,
177
+ setTheme,
178
+ subscribeGlobalDarkMode,
179
+ toggleGlobalDarkMode,
580
180
  useGlobalDarkMode,
581
- useIsDark,
582
- useResolvedStyle,
583
- useResponsive,
584
- useSpacing,
585
- useStyles,
586
- useTheme,
587
- wrapColors,
588
- wrapSpacing
181
+ useTheme
589
182
  };