@kbach/ui 0.1.0-beta.0 → 0.1.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
@@ -35,26 +35,26 @@ __export(index_exports, {
35
35
  KbachReset: () => KbachReset,
36
36
  ThemeContext: () => ThemeContext,
37
37
  ThemeProvider: () => ThemeProvider,
38
- buildConfig: () => import_core12.buildConfig,
39
- clearCache: () => import_core12.clearCache,
38
+ buildConfig: () => import_core13.buildConfig,
39
+ clearCache: () => import_core13.clearCache,
40
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,
41
+ defaultColors: () => import_core13.defaultColors,
42
+ defaultTheme: () => import_core13.defaultTheme,
43
+ disableRuntimeCSS: () => import_core13.disableRuntimeCSS,
44
+ flatten: () => import_core13.flatten,
45
+ generateKbachTypesDts: () => import_core13.generateKbachTypesDts,
46
+ getConfig: () => import_core13.getConfig,
47
+ initConfig: () => import_core13.initConfig,
48
48
  kb: () => kb,
49
- normalizeClassString: () => import_core12.normalizeClassString,
50
- parseClass: () => import_core12.parseClass,
51
- parseClasses: () => import_core12.parseClasses,
49
+ normalizeClassString: () => import_core13.normalizeClassString,
50
+ parseClass: () => import_core13.parseClass,
51
+ parseClasses: () => import_core13.parseClasses,
52
52
  registerWebElement: () => registerWebElement,
53
- resolve: () => import_core12.resolve,
54
- setResolveTarget: () => import_core12.setResolveTarget,
55
- splitClassTokens: () => import_core12.splitClassTokens,
53
+ resolve: () => import_core13.resolve,
54
+ setResolveTarget: () => import_core13.setResolveTarget,
55
+ splitClassTokens: () => import_core13.splitClassTokens,
56
56
  styled: () => styled,
57
- updateConfig: () => import_core12.updateConfig,
57
+ updateConfig: () => import_core13.updateConfig,
58
58
  useBreakpoint: () => useBreakpoint,
59
59
  useColors: () => useColors,
60
60
  useGlobalDarkMode: () => useGlobalDarkMode,
@@ -71,7 +71,8 @@ module.exports = __toCommonJS(index_exports);
71
71
 
72
72
  // src/context.tsx
73
73
  var import_react = require("react");
74
- var ThemeContext = globalThis.__kbachThemeContext ?? (globalThis.__kbachThemeContext = (0, import_react.createContext)(null));
74
+ var import_core = require("./core");
75
+ var ThemeContext = (0, import_core.getGlobalSingleton)("themeContext", () => (0, import_react.createContext)(null));
75
76
  function useTheme() {
76
77
  const ctx = (0, import_react.useContext)(ThemeContext);
77
78
  if (!ctx) {
@@ -87,11 +88,11 @@ function useIsDark() {
87
88
 
88
89
  // src/useColors.ts
89
90
  var import_react2 = require("react");
90
- var import_core = require("./core");
91
+ var import_core2 = require("./core");
91
92
  function applyOpacity(color, opacity) {
92
93
  const a = Math.max(0, Math.min(1, opacity / 100));
93
94
  if (color.startsWith("#")) {
94
- const rgb = (0, import_core.parseHexRgb)(color);
95
+ const rgb = (0, import_core2.parseHexRgb)(color);
95
96
  if (!rgb) return color;
96
97
  const [r, g, b] = rgb;
97
98
  return `rgba(${r},${g},${b},${a})`;
@@ -101,7 +102,7 @@ function applyOpacity(color, opacity) {
101
102
  return color;
102
103
  }
103
104
  function pickSide(value, isDark) {
104
- return (0, import_core.isModeAwareColor)(value) ? isDark ? value.dark : value.light : value;
105
+ return (0, import_core2.isModeAwareColor)(value) ? isDark ? value.dark : value.light : value;
105
106
  }
106
107
  function makeShadeProxy(shades, isDark) {
107
108
  return new Proxy(shades, {
@@ -133,11 +134,11 @@ function wrapColors(rawColors, isDark = false) {
133
134
  const name = key.slice(0, slash);
134
135
  const op = Number(key.slice(slash + 1));
135
136
  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
+ return entry2 !== void 0 && (typeof entry2 === "string" || (0, import_core2.isModeAwareColor)(entry2)) ? applyOpacity(pickSide(entry2, isDark), op) : void 0;
137
138
  }
138
139
  const entry = rawColors[key];
139
140
  if (entry === void 0) return void 0;
140
- if (typeof entry === "string" || (0, import_core.isModeAwareColor)(entry)) return pickSide(entry, isDark);
141
+ if (typeof entry === "string" || (0, import_core2.isModeAwareColor)(entry)) return pickSide(entry, isDark);
141
142
  const cacheKey = `${key}:${isDark}`;
142
143
  if (!cache.has(cacheKey)) cache.set(cacheKey, makeShadeProxy(entry, isDark));
143
144
  return cache.get(cacheKey);
@@ -176,9 +177,9 @@ var useSyncExternalStore = import_react4.default.useSyncExternalStore ?? functio
176
177
  };
177
178
 
178
179
  // src/ThemeProvider.tsx
179
- var import_core2 = require("./core");
180
+ var import_core3 = require("./core");
180
181
  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 useIsomorphicLayoutEffect = import_core3.isWeb || import_core3.isNative ? import_react5.useLayoutEffect : import_react5.useEffect;
182
183
  var STORAGE_KEY = "kbach-theme";
183
184
  var _mountedProviderCount = 0;
184
185
  var _warnedMultipleProviders = false;
@@ -187,7 +188,7 @@ var _warnedConfigOverride = false;
187
188
  var _warnedMediaModeToggle = false;
188
189
  function loadPersistedMode() {
189
190
  try {
190
- if (import_core2.isWeb) return localStorage.getItem(STORAGE_KEY);
191
+ if (import_core3.isWeb) return localStorage.getItem(STORAGE_KEY);
191
192
  return null;
192
193
  } catch {
193
194
  return null;
@@ -195,12 +196,12 @@ function loadPersistedMode() {
195
196
  }
196
197
  function persistMode(mode) {
197
198
  try {
198
- if (import_core2.isWeb) localStorage.setItem(STORAGE_KEY, mode);
199
+ if (import_core3.isWeb) localStorage.setItem(STORAGE_KEY, mode);
199
200
  } catch {
200
201
  }
201
202
  }
202
203
  function subscribeSystemScheme(callback) {
203
- if (!import_core2.isWeb || typeof window === "undefined") return () => {
204
+ if (!import_core3.isWeb || typeof window === "undefined") return () => {
204
205
  };
205
206
  const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
206
207
  if (!mq) return () => {
@@ -209,14 +210,14 @@ function subscribeSystemScheme(callback) {
209
210
  return () => mq.removeEventListener("change", callback);
210
211
  }
211
212
  function getSystemScheme() {
212
- if (!import_core2.isWeb || typeof window === "undefined") return "light";
213
+ if (!import_core3.isWeb || typeof window === "undefined") return "light";
213
214
  return window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ? "dark" : "light";
214
215
  }
215
216
  function getSystemSchemeServerSnapshot() {
216
217
  return "light";
217
218
  }
218
219
  function applyWebTheme(resolvedMode, strategy) {
219
- if (!import_core2.isWeb) return;
220
+ if (!import_core3.isWeb) return;
220
221
  const root = document.documentElement;
221
222
  if (strategy === "attribute") {
222
223
  root.setAttribute("data-theme", resolvedMode);
@@ -233,14 +234,22 @@ function ThemeProvider({
233
234
  config: configOverride,
234
235
  disablePersistence = false
235
236
  }) {
237
+ let nativeColorScheme = null;
238
+ let nativeWindowWidth;
239
+ if (import_core3.isNative) {
240
+ const { useColorScheme, useWindowDimensions } = require("react-native");
241
+ const raw = useColorScheme();
242
+ nativeColorScheme = raw === "dark" ? "dark" : raw === "light" ? "light" : null;
243
+ nativeWindowWidth = useWindowDimensions().width;
244
+ }
236
245
  const [resolvedConfig, setResolvedConfig] = (0, import_react5.useState)(
237
- () => configOverride ? (0, import_core2.buildConfig)(configOverride) : (0, import_core2.getConfig)()
246
+ () => configOverride ? (0, import_core3.buildConfig)(configOverride) : (0, import_core3.getConfig)()
238
247
  );
239
248
  const _prevConfigOverrideRef = (0, import_react5.useRef)(void 0);
240
249
  if (configOverride && _prevConfigOverrideRef.current !== configOverride) {
241
250
  _prevConfigOverrideRef.current = configOverride;
242
- (0, import_core2.updateConfig)(configOverride);
243
- const fresh = (0, import_core2.getConfig)();
251
+ (0, import_core3.updateConfig)(configOverride);
252
+ const fresh = (0, import_core3.getConfig)();
244
253
  if (fresh !== resolvedConfig) setResolvedConfig(fresh);
245
254
  }
246
255
  (0, import_react5.useEffect)(() => {
@@ -249,13 +258,13 @@ function ThemeProvider({
249
258
  if (process.env.NODE_ENV !== "production") {
250
259
  if (_mountedProviderCount > 1 && !_warnedMultipleProviders) {
251
260
  _warnedMultipleProviders = true;
252
- (0, import_core2.kbachWarn)(
261
+ (0, import_core3.kbachWarn)(
253
262
  "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
263
  );
255
264
  }
256
265
  if (_mountedConfigOverrideCount > 1 && !_warnedConfigOverride) {
257
266
  _warnedConfigOverride = true;
258
- (0, import_core2.kbachWarn)(
267
+ (0, import_core3.kbachWarn)(
259
268
  "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
269
  );
261
270
  }
@@ -264,12 +273,12 @@ function ThemeProvider({
264
273
  _mountedProviderCount--;
265
274
  if (configOverride) _mountedConfigOverrideCount--;
266
275
  if (_prevConfigOverrideRef.current !== void 0 && _mountedProviderCount === 0) {
267
- (0, import_core2.resetConfig)();
276
+ (0, import_core3.resetConfig)();
268
277
  }
269
278
  };
270
279
  }, []);
271
280
  const [webWidth, setWebWidth] = (0, import_react5.useState)(0);
272
- const effectiveWidth = (0, import_core2.getEffectiveIsWeb)() ? webWidth : windowWidthProp ?? 0;
281
+ const effectiveWidth = (0, import_core3.getEffectiveIsWeb)() ? webWidth : windowWidthProp ?? nativeWindowWidth ?? 0;
273
282
  const numericScreens = (0, import_react5.useMemo)(() => {
274
283
  const out = {};
275
284
  for (const [k, v] of Object.entries(resolvedConfig.theme.screens ?? {})) {
@@ -280,18 +289,18 @@ function ThemeProvider({
280
289
  const _prevWidthRef = (0, import_react5.useRef)(-1);
281
290
  if (_prevWidthRef.current !== effectiveWidth) {
282
291
  _prevWidthRef.current = effectiveWidth;
283
- (0, import_core2.syncGlobalWidth)(effectiveWidth);
292
+ (0, import_core3.syncGlobalWidth)(effectiveWidth);
284
293
  }
285
294
  const _prevScreensRef = (0, import_react5.useRef)(null);
286
295
  if (_prevScreensRef.current !== numericScreens) {
287
296
  _prevScreensRef.current = numericScreens;
288
- (0, import_core2.syncGlobalScreens)(numericScreens);
297
+ (0, import_core3.syncGlobalScreens)(numericScreens);
289
298
  }
290
299
  (0, import_react5.useEffect)(() => {
291
- (0, import_core2.setGlobalWidth)(effectiveWidth);
300
+ (0, import_core3.setGlobalWidth)(effectiveWidth);
292
301
  }, [effectiveWidth]);
293
302
  (0, import_react5.useEffect)(() => {
294
- if (!import_core2.isWeb || typeof window === "undefined") return;
303
+ if (!import_core3.isWeb || typeof window === "undefined") return;
295
304
  setWebWidth(window.innerWidth);
296
305
  let raf = 0;
297
306
  const update = () => {
@@ -299,7 +308,7 @@ function ThemeProvider({
299
308
  raf = requestAnimationFrame(() => {
300
309
  const w = window.innerWidth;
301
310
  setWebWidth(w);
302
- (0, import_core2.setGlobalWidth)(w);
311
+ (0, import_core3.setGlobalWidth)(w);
303
312
  });
304
313
  };
305
314
  window.addEventListener("resize", update);
@@ -313,7 +322,7 @@ function ThemeProvider({
313
322
  getSystemScheme,
314
323
  getSystemSchemeServerSnapshot
315
324
  );
316
- const systemScheme = (0, import_core2.getEffectiveIsWeb)() ? webScheme : colorScheme === "dark" ? "dark" : "light";
325
+ const systemScheme = (0, import_core3.getEffectiveIsWeb)() ? webScheme : (colorScheme ?? nativeColorScheme) === "dark" ? "dark" : "light";
317
326
  const [mode, _setMode] = (0, import_react5.useState)(defaultMode);
318
327
  (0, import_react5.useEffect)(() => {
319
328
  if (disablePersistence) return;
@@ -321,7 +330,7 @@ function ThemeProvider({
321
330
  if (persisted) _setMode(persisted);
322
331
  }, []);
323
332
  (0, import_react5.useEffect)(() => {
324
- if (disablePersistence || !import_core2.isWeb || typeof window === "undefined") return;
333
+ if (disablePersistence || !import_core3.isWeb || typeof window === "undefined") return;
325
334
  const onStorage = (e) => {
326
335
  if (e.key !== STORAGE_KEY || e.newValue == null) return;
327
336
  if (e.newValue === "light" || e.newValue === "dark" || e.newValue === "system") {
@@ -332,9 +341,9 @@ function ThemeProvider({
332
341
  return () => window.removeEventListener("storage", onStorage);
333
342
  }, [disablePersistence]);
334
343
  const setMode = (0, import_react5.useCallback)((next) => {
335
- if (process.env.NODE_ENV !== "production" && import_core2.isWeb && resolvedConfig.darkMode === "media" && !_warnedMediaModeToggle) {
344
+ if (process.env.NODE_ENV !== "production" && import_core3.isWeb && resolvedConfig.darkMode === "media" && !_warnedMediaModeToggle) {
336
345
  _warnedMediaModeToggle = true;
337
- (0, import_core2.kbachWarn)(
346
+ (0, import_core3.kbachWarn)(
338
347
  '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
348
  );
340
349
  }
@@ -346,15 +355,15 @@ function ThemeProvider({
346
355
  }, [mode, systemScheme, setMode]);
347
356
  const resolvedMode = mode === "system" ? systemScheme : mode;
348
357
  const isDark = resolvedMode === "dark";
349
- (0, import_core2.syncGlobalDarkMode)(isDark);
358
+ (0, import_core3.syncGlobalDarkMode)(isDark);
350
359
  useIsomorphicLayoutEffect(() => {
351
360
  applyWebTheme(resolvedMode, resolvedConfig.darkMode);
352
- (0, import_core2.setGlobalDarkMode)(isDark);
361
+ (0, import_core3.setGlobalDarkMode)(isDark);
353
362
  }, [isDark, resolvedMode, resolvedConfig.darkMode]);
354
363
  (0, import_react5.useEffect)(() => {
355
364
  if (configOverride) return;
356
365
  let mounted = true;
357
- const unsub = (0, import_core2.onConfigChange)((config) => {
366
+ const unsub = (0, import_core3.onConfigChange)((config) => {
358
367
  if (mounted) setResolvedConfig(config);
359
368
  });
360
369
  return () => {
@@ -370,40 +379,40 @@ function ThemeProvider({
370
379
  }
371
380
 
372
381
  // src/KbachReset.tsx
373
- var import_core3 = require("./core");
382
+ var import_core4 = require("./core");
374
383
  var import_jsx_runtime2 = require("react/jsx-runtime");
375
384
  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 });
385
+ if (import_core4.isNative) return null;
386
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { id: import_core4.RESET_STYLE_ID, children: import_core4.BASE_RESET });
378
387
  }
379
388
 
380
389
  // src/styled.tsx
381
390
  var import_react6 = __toESM(require("react"));
382
- var import_core7 = require("./core");
391
+ var import_core8 = require("./core");
383
392
 
384
393
  // src/useGlobalDarkMode.ts
385
- var import_core4 = require("./core");
394
+ var import_core5 = require("./core");
386
395
  var NOOP_SUB = (_) => () => {
387
396
  };
388
397
  var FALSE_SNAP = () => false;
389
398
  function useGlobalDarkMode() {
390
399
  return useSyncExternalStore(
391
- import_core4.subscribeGlobalDarkMode,
392
- import_core4.getGlobalDarkMode,
400
+ import_core5.subscribeGlobalDarkMode,
401
+ import_core5.getGlobalDarkMode,
393
402
  () => false
394
403
  // SSR: default light
395
404
  );
396
405
  }
397
406
  function useConditionalGlobalDarkMode(active) {
398
407
  return useSyncExternalStore(
399
- active ? import_core4.subscribeGlobalDarkMode : NOOP_SUB,
400
- active ? import_core4.getGlobalDarkMode : FALSE_SNAP,
408
+ active ? import_core5.subscribeGlobalDarkMode : NOOP_SUB,
409
+ active ? import_core5.getGlobalDarkMode : FALSE_SNAP,
401
410
  FALSE_SNAP
402
411
  );
403
412
  }
404
413
 
405
414
  // src/useGlobalWidth.ts
406
- var import_core5 = require("./core");
415
+ var import_core6 = require("./core");
407
416
  var NOOP_SUB2 = (_) => () => {
408
417
  };
409
418
  var ZERO_SNAP = () => 0;
@@ -438,23 +447,23 @@ function getWebWidth() {
438
447
  }
439
448
  function useGlobalWidth() {
440
449
  return useSyncExternalStore(
441
- import_core5.isWeb ? subscribeWebWidth : import_core5.subscribeGlobalWidth,
442
- import_core5.isWeb ? getWebWidth : import_core5.getGlobalWidth,
450
+ import_core6.isWeb ? subscribeWebWidth : import_core6.subscribeGlobalWidth,
451
+ import_core6.isWeb ? getWebWidth : import_core6.getGlobalWidth,
443
452
  ZERO_SNAP
444
453
  );
445
454
  }
446
455
  function useConditionalWidth(active) {
447
456
  return useSyncExternalStore(
448
- active ? import_core5.isWeb ? subscribeWebWidth : import_core5.subscribeGlobalWidth : NOOP_SUB2,
449
- active ? import_core5.isWeb ? getWebWidth : import_core5.getGlobalWidth : ZERO_SNAP,
457
+ active ? import_core6.isWeb ? subscribeWebWidth : import_core6.subscribeGlobalWidth : NOOP_SUB2,
458
+ active ? import_core6.isWeb ? getWebWidth : import_core6.getGlobalWidth : ZERO_SNAP,
450
459
  ZERO_SNAP
451
460
  );
452
461
  }
453
462
 
454
463
  // src/shared-utils.ts
455
- var import_core6 = require("./core");
464
+ var import_core7 = require("./core");
456
465
  function hasResponsiveBuckets(resolved) {
457
- const responsiveMods = (0, import_core6.getResponsiveModifiers)();
466
+ const responsiveMods = (0, import_core7.getResponsiveModifiers)();
458
467
  for (const key of Object.keys(resolved)) {
459
468
  if (key === "base") continue;
460
469
  for (const mod of key.split(":")) {
@@ -464,7 +473,7 @@ function hasResponsiveBuckets(resolved) {
464
473
  return false;
465
474
  }
466
475
  function hasInteractiveBuckets(resolved) {
467
- const interactiveMods = (0, import_core6.getInteractiveModifiers)();
476
+ const interactiveMods = (0, import_core7.getInteractiveModifiers)();
468
477
  for (const key of Object.keys(resolved)) {
469
478
  if (key === "base") continue;
470
479
  for (const mod of key.split(":")) {
@@ -860,7 +869,7 @@ function styled(Component, baseClasses = "") {
860
869
  const disabled = !!props.disabled;
861
870
  const checked = !!props.checked;
862
871
  const { config } = useTheme();
863
- const isWebPlatform = (0, import_core7.getEffectiveIsWeb)();
872
+ const isWebPlatform = (0, import_core8.getEffectiveIsWeb)();
864
873
  const isDark = useConditionalGlobalDarkMode(!isWebPlatform);
865
874
  const [pressed, setPressed] = (0, import_react6.useState)(false);
866
875
  const [hovered, setHovered] = (0, import_react6.useState)(false);
@@ -896,18 +905,18 @@ function styled(Component, baseClasses = "") {
896
905
  if (!isWebPlatform) setFocused(false);
897
906
  }), [onBlur, isWebPlatform]);
898
907
  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]);
908
+ const firstPassResolved = (0, import_react6.useMemo)(() => (0, import_core8.resolve)(combined, config.theme, config.darkMode), [combined, config.theme, config.darkMode]);
900
909
  const webTag = isWebPlatform ? getWebTag(Component, rest) : null;
901
910
  const impliedClasses = getImpliedRNClasses(webTag, firstPassResolved.base);
902
911
  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]);
912
+ const resolved = (0, import_react6.useMemo)(() => (0, import_core8.resolve)(finalClassStr, config.theme, config.darkMode), [finalClassStr, config.theme, config.darkMode]);
904
913
  const hasInteractive = (0, import_react6.useMemo)(() => hasInteractiveBuckets(resolved), [resolved]);
905
914
  const needsWidth = hasResponsiveBuckets(resolved);
906
915
  const width = useConditionalWidth(needsWidth && !isWebPlatform);
907
- const breakpoints = needsWidth ? (0, import_core7.getActiveBreakpoints)(width) : EMPTY_BREAKPOINTS;
908
- const screens = (0, import_core7.getGlobalScreens)();
916
+ const breakpoints = needsWidth ? (0, import_core8.getActiveBreakpoints)(width) : EMPTY_BREAKPOINTS;
917
+ const screens = (0, import_core8.getGlobalScreens)();
909
918
  const computedStyle = (0, import_react6.useMemo)(
910
- () => isWebPlatform ? {} : (0, import_core7.flatten)(resolved, isDark, { pressed, hover: hovered, focus: focused, disabled, checked }, breakpoints),
919
+ () => isWebPlatform ? {} : (0, import_core8.flatten)(resolved, isDark, { pressed, hover: hovered, focus: focused, disabled, checked }, breakpoints),
911
920
  [resolved, isDark, pressed, hovered, focused, disabled, checked, width, screens]
912
921
  // eslint-disable-line react-hooks/exhaustive-deps
913
922
  );
@@ -920,7 +929,7 @@ function styled(Component, baseClasses = "") {
920
929
  ...effectiveRest,
921
930
  style: finalStyle,
922
931
  // className lets injected CSS rules (group-hover:, before:, print:, etc.) match the element.
923
- ...isWebPlatform && finalClassStr ? { className: (0, import_core7.normalizeClassString)(finalClassStr) } : {},
932
+ ...isWebPlatform && finalClassStr ? { className: (0, import_core8.normalizeClassString)(finalClassStr) } : {},
924
933
  // Only attach state-tracking handlers when interactive modifiers are present.
925
934
  // Always forward user-provided handlers to avoid silently swallowing them.
926
935
  // Gated on isNative, not isWeb: isWeb is false during SSR too (no `window` there),
@@ -935,7 +944,7 @@ function styled(Component, baseClasses = "") {
935
944
  // reliable signal either way. Forwarding them made React DOM warn "Unknown event
936
945
  // handler property" the moment any such component got an interactive modifier
937
946
  // (hover:, active:, …), which is the common case, not the exception.
938
- ...!import_core7.isNative ? hasInteractive ? {
947
+ ...!import_core8.isNative ? hasInteractive ? {
939
948
  onPointerDown: handlePointerDown,
940
949
  onPointerUp: handlePointerUp,
941
950
  onPointerLeave: handlePointerLeave,
@@ -960,29 +969,29 @@ function styled(Component, baseClasses = "") {
960
969
 
961
970
  // src/useStyles.ts
962
971
  var import_react7 = require("react");
963
- var import_core8 = require("./core");
972
+ var import_core9 = require("./core");
964
973
  function useStyles(classString, state = {}) {
965
974
  const { isDark, config } = useTheme();
966
975
  const width = useGlobalWidth();
967
976
  const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
968
977
  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);
978
+ const resolved = (0, import_core9.resolve)(normalised, config.theme, config.darkMode);
979
+ const breakpoints = (0, import_core9.getActiveBreakpoints)(width);
980
+ return (0, import_core9.flatten)(resolved, isDark, state, breakpoints);
972
981
  }, [normalised, isDark, config.theme, config.darkMode, width, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
973
982
  }
974
983
  function useResolvedStyle(classString) {
975
984
  const { config } = useTheme();
976
985
  const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
977
986
  return (0, import_react7.useMemo)(
978
- () => (0, import_core8.resolve)(normalised, config.theme, config.darkMode),
987
+ () => (0, import_core9.resolve)(normalised, config.theme, config.darkMode),
979
988
  [normalised, config]
980
989
  );
981
990
  }
982
991
 
983
992
  // src/useBreakpoint.ts
984
993
  var import_react8 = require("react");
985
- var import_core9 = require("./core");
994
+ var import_core10 = require("./core");
986
995
  function toNumericScreens(screens) {
987
996
  const out = {};
988
997
  for (const [name, v] of Object.entries(screens)) {
@@ -997,7 +1006,7 @@ function useBreakpoint() {
997
1006
  const screens = config.theme.screens;
998
1007
  const numericScreens = (0, import_react8.useMemo)(() => toNumericScreens(screens), [screens]);
999
1008
  return (0, import_react8.useMemo)(() => {
1000
- const active = (0, import_core9.getActiveBreakpoints)(width, numericScreens);
1009
+ const active = (0, import_core10.getActiveBreakpoints)(width, numericScreens);
1001
1010
  let best = null;
1002
1011
  let bestMinW = -Infinity;
1003
1012
  for (const name of active) {
@@ -1016,7 +1025,7 @@ function useResponsive() {
1016
1025
  const screens = config.theme.screens;
1017
1026
  const numericScreens = (0, import_react8.useMemo)(() => toNumericScreens(screens), [screens]);
1018
1027
  return (0, import_react8.useMemo)(() => {
1019
- const active = (0, import_core9.getActiveBreakpoints)(width, numericScreens);
1028
+ const active = (0, import_core10.getActiveBreakpoints)(width, numericScreens);
1020
1029
  const result = {};
1021
1030
  for (const name of Object.keys(numericScreens)) {
1022
1031
  result[name] = active.has(name);
@@ -1027,7 +1036,7 @@ function useResponsive() {
1027
1036
 
1028
1037
  // src/InteractiveWrapper.tsx
1029
1038
  var import_react9 = __toESM(require("react"));
1030
- var import_core10 = require("./core");
1039
+ var import_core11 = require("./core");
1031
1040
  var InteractiveWrapper = (0, import_react9.forwardRef)(
1032
1041
  function InteractiveWrapper2({
1033
1042
  Component,
@@ -1046,12 +1055,12 @@ var InteractiveWrapper = (0, import_react9.forwardRef)(
1046
1055
  onBlur,
1047
1056
  ...rest
1048
1057
  }, ref) {
1049
- const isWebPlatform = (0, import_core10.getEffectiveIsWeb)();
1058
+ const isWebPlatform = (0, import_core11.getEffectiveIsWeb)();
1050
1059
  const isDark = useConditionalGlobalDarkMode(!isWebPlatform);
1051
1060
  const needsWidth = hasResponsiveBuckets(resolvedStyle);
1052
1061
  const width = useConditionalWidth(needsWidth && !isWebPlatform);
1053
- const breakpoints = needsWidth ? (0, import_core10.getActiveBreakpoints)(width) : EMPTY_BREAKPOINTS;
1054
- const screens = (0, import_core10.getGlobalScreens)();
1062
+ const breakpoints = needsWidth ? (0, import_core11.getActiveBreakpoints)(width) : EMPTY_BREAKPOINTS;
1063
+ const screens = (0, import_core11.getGlobalScreens)();
1055
1064
  const [pressed, setPressed] = (0, import_react9.useState)(false);
1056
1065
  const [hovered, setHovered] = (0, import_react9.useState)(false);
1057
1066
  const [focused, setFocused] = (0, import_react9.useState)(false);
@@ -1090,7 +1099,7 @@ var InteractiveWrapper = (0, import_react9.forwardRef)(
1090
1099
  const computedStyle = (0, import_react9.useMemo)(
1091
1100
  () => {
1092
1101
  if (isWebPlatform) return {};
1093
- const s = (0, import_core10.flatten)(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused, disabled: !!disabled, checked: !!checked }, breakpoints);
1102
+ const s = (0, import_core11.flatten)(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused, disabled: !!disabled, checked: !!checked }, breakpoints);
1094
1103
  stripInternalMarkers(s);
1095
1104
  if (isNonStringComponent) stripWebOnlyProps(s);
1096
1105
  return s;
@@ -1105,7 +1114,7 @@ var InteractiveWrapper = (0, import_react9.forwardRef)(
1105
1114
  ...disabled !== void 0 ? { disabled } : {},
1106
1115
  ...checked !== void 0 ? { checked } : {},
1107
1116
  style: finalStyle,
1108
- ...!import_core10.isNative && className ? { className } : {},
1117
+ ...!import_core11.isNative && className ? { className } : {},
1109
1118
  // On web, onPointerDown/Up drive the wrapper's own pressed state (covers mouse + touch) —
1110
1119
  // onPressIn/onPressOut are RN-only prop names and are never forwarded here, even when
1111
1120
  // Component isn't a literal HTML tag string. A non-string Component on web is just as
@@ -1119,7 +1128,7 @@ var InteractiveWrapper = (0, import_react9.forwardRef)(
1119
1128
  // in jsx-runtime.tsx swaps it for a plain host tag — so keeping them pre-hydration would
1120
1129
  // only have traded one prop-mismatch warning for another.) Genuinely native code paths
1121
1130
  // still get them via the isNative branch below.
1122
- ...!import_core10.isNative ? {
1131
+ ...!import_core11.isNative ? {
1123
1132
  onPointerDown: handlePointerDown,
1124
1133
  onPointerUp: handlePointerUp,
1125
1134
  onPointerLeave: handlePointerLeave,
@@ -1136,21 +1145,21 @@ var InteractiveWrapper = (0, import_react9.forwardRef)(
1136
1145
  InteractiveWrapper.displayName = "Kbach.InteractiveWrapper";
1137
1146
 
1138
1147
  // src/kb.ts
1139
- var import_core11 = require("./core");
1148
+ var import_core12 = require("./core");
1140
1149
  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)()) {
1150
+ const config = (0, import_core12.getConfig)();
1151
+ const resolved = (0, import_core12.resolve)(classString, config.theme, config.darkMode);
1152
+ if ((0, import_core12.getEffectiveIsWeb)()) {
1144
1153
  return classString;
1145
1154
  }
1146
- return (0, import_core11.flatten)(resolved, isDark);
1155
+ return (0, import_core12.flatten)(resolved, isDark);
1147
1156
  }
1148
1157
  function cx(...classes) {
1149
1158
  return classes.filter(Boolean).join(" ");
1150
1159
  }
1151
1160
 
1152
1161
  // src/index.ts
1153
- var import_core12 = require("./core");
1162
+ var import_core13 = require("./core");
1154
1163
  // Annotate the CommonJS export names for ESM import in node:
1155
1164
  0 && (module.exports = {
1156
1165
  InteractiveWrapper,
package/dist/index.mjs CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  EMPTY_BREAKPOINTS,
5
5
  InteractiveWrapper,
6
6
  RESET_STYLE_ID,
7
+ __require,
7
8
  buildConfig,
8
9
  chain,
9
10
  clearCache,
@@ -17,6 +18,7 @@ import {
17
18
  getConfig,
18
19
  getEffectiveIsWeb,
19
20
  getGlobalScreens,
21
+ getGlobalSingleton,
20
22
  getImpliedRNClasses,
21
23
  getWebTag,
22
24
  hasInteractiveBuckets,
@@ -48,11 +50,11 @@ import {
48
50
  useGlobalDarkMode,
49
51
  useGlobalWidth,
50
52
  useSyncExternalStore
51
- } from "./chunk-BPCFICND.mjs";
53
+ } from "./chunk-VBK6ORYJ.mjs";
52
54
 
53
55
  // src/context.tsx
54
56
  import { createContext, useContext } from "react";
55
- var ThemeContext = globalThis.__kbachThemeContext ?? (globalThis.__kbachThemeContext = createContext(null));
57
+ var ThemeContext = getGlobalSingleton("themeContext", () => createContext(null));
56
58
  function useTheme() {
57
59
  const ctx = useContext(ThemeContext);
58
60
  if (!ctx) {
@@ -204,6 +206,14 @@ function ThemeProvider({
204
206
  config: configOverride,
205
207
  disablePersistence = false
206
208
  }) {
209
+ let nativeColorScheme = null;
210
+ let nativeWindowWidth;
211
+ if (isNative) {
212
+ const { useColorScheme, useWindowDimensions } = __require("react-native");
213
+ const raw = useColorScheme();
214
+ nativeColorScheme = raw === "dark" ? "dark" : raw === "light" ? "light" : null;
215
+ nativeWindowWidth = useWindowDimensions().width;
216
+ }
207
217
  const [resolvedConfig, setResolvedConfig] = useState(
208
218
  () => configOverride ? buildConfig(configOverride) : getConfig()
209
219
  );
@@ -240,7 +250,7 @@ function ThemeProvider({
240
250
  };
241
251
  }, []);
242
252
  const [webWidth, setWebWidth] = useState(0);
243
- const effectiveWidth = getEffectiveIsWeb() ? webWidth : windowWidthProp ?? 0;
253
+ const effectiveWidth = getEffectiveIsWeb() ? webWidth : windowWidthProp ?? nativeWindowWidth ?? 0;
244
254
  const numericScreens = useMemo3(() => {
245
255
  const out = {};
246
256
  for (const [k, v] of Object.entries(resolvedConfig.theme.screens ?? {})) {
@@ -284,7 +294,7 @@ function ThemeProvider({
284
294
  getSystemScheme,
285
295
  getSystemSchemeServerSnapshot
286
296
  );
287
- const systemScheme = getEffectiveIsWeb() ? webScheme : colorScheme === "dark" ? "dark" : "light";
297
+ const systemScheme = getEffectiveIsWeb() ? webScheme : (colorScheme ?? nativeColorScheme) === "dark" ? "dark" : "light";
288
298
  const [mode, _setMode] = useState(defaultMode);
289
299
  useEffect(() => {
290
300
  if (disablePersistence) return;
@@ -3,8 +3,8 @@ import {
3
3
  Fragment,
4
4
  jsx,
5
5
  jsxs
6
- } from "./chunk-UE54W6ZG.mjs";
7
- import "./chunk-BPCFICND.mjs";
6
+ } from "./chunk-KHTZ244V.mjs";
7
+ import "./chunk-VBK6ORYJ.mjs";
8
8
 
9
9
  // src/jsx-dev-runtime.tsx
10
10
  function jsxDEV(type, props, key, isStaticChildren, source, self) {
@@ -4,10 +4,10 @@ import {
4
4
  _invalidateDefaultFontCache,
5
5
  jsx,
6
6
  jsxs
7
- } from "./chunk-UE54W6ZG.mjs";
7
+ } from "./chunk-KHTZ244V.mjs";
8
8
  import {
9
9
  registerWebElement
10
- } from "./chunk-BPCFICND.mjs";
10
+ } from "./chunk-VBK6ORYJ.mjs";
11
11
  export {
12
12
  Fragment,
13
13
  _invalidateDefaultFontCache,