@kbach/react 0.5.62 → 0.6.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.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  EMPTY_BREAKPOINTS,
3
3
  InteractiveWrapper,
4
+ chain,
4
5
  getWebTag,
5
6
  hasInteractiveBuckets,
6
7
  hasResponsiveBuckets,
@@ -10,11 +11,12 @@ import {
10
11
  transformToWebProps,
11
12
  useConditionalWidth,
12
13
  useGlobalDarkMode,
13
- useGlobalWidth
14
- } from "./chunk-5MX3E4OL.mjs";
14
+ useGlobalWidth,
15
+ useSyncExternalStore
16
+ } from "./chunk-36QRQNPL.mjs";
15
17
  import {
16
- __require,
17
18
  buildConfig,
19
+ clearCache,
18
20
  defaultColors,
19
21
  defaultTheme,
20
22
  disableRuntimeCSS,
@@ -27,13 +29,15 @@ import {
27
29
  onConfigChange,
28
30
  parseClass,
29
31
  parseClasses,
32
+ parseHexRgb,
30
33
  resolve,
31
34
  setGlobalWidth,
35
+ setResolveTarget,
32
36
  splitClassTokens,
33
37
  syncGlobalScreens,
34
38
  syncGlobalWidth,
35
39
  updateConfig
36
- } from "./chunk-BRCQK5DA.mjs";
40
+ } from "./chunk-MPHPB357.mjs";
37
41
 
38
42
  // src/context.tsx
39
43
  import { createContext, useContext } from "react";
@@ -56,22 +60,10 @@ import { useMemo } from "react";
56
60
  function applyOpacity(color, opacity) {
57
61
  const a = Math.max(0, Math.min(1, opacity / 100));
58
62
  if (color.startsWith("#")) {
59
- const h = color.slice(1);
60
- let rs, gs, bs;
61
- if (h.length === 3 || h.length === 4) {
62
- rs = h[0] + h[0];
63
- gs = h[1] + h[1];
64
- bs = h[2] + h[2];
65
- } else if (h.length === 8) {
66
- rs = h.slice(0, 2);
67
- gs = h.slice(2, 4);
68
- bs = h.slice(4, 6);
69
- } else {
70
- rs = h.slice(0, 2);
71
- gs = h.slice(2, 4);
72
- bs = h.slice(4, 6);
73
- }
74
- return `rgba(${parseInt(rs, 16)},${parseInt(gs, 16)},${parseInt(bs, 16)},${a})`;
63
+ const rgb = parseHexRgb(color);
64
+ if (!rgb) return color;
65
+ const [r, g, b] = rgb;
66
+ return `rgba(${r},${g},${b},${a})`;
75
67
  }
76
68
  if (color.startsWith("rgb(")) return color.replace("rgb(", "rgba(").replace(")", `,${a})`);
77
69
  if (color.startsWith("rgba(")) return color.replace(/,\s*[\d.]+\)$/, `,${a})`);
@@ -145,6 +137,22 @@ function persistMode(mode) {
145
137
  } catch {
146
138
  }
147
139
  }
140
+ function subscribeSystemScheme(callback) {
141
+ if (!isWeb || typeof window === "undefined") return () => {
142
+ };
143
+ const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
144
+ if (!mq) return () => {
145
+ };
146
+ mq.addEventListener("change", callback);
147
+ return () => mq.removeEventListener("change", callback);
148
+ }
149
+ function getSystemScheme() {
150
+ if (!isWeb || typeof window === "undefined") return "light";
151
+ return window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ? "dark" : "light";
152
+ }
153
+ function getSystemSchemeServerSnapshot() {
154
+ return "light";
155
+ }
148
156
  function applyWebTheme(resolvedMode, strategy) {
149
157
  if (!isWeb) return;
150
158
  const root = document.documentElement;
@@ -166,9 +174,7 @@ function ThemeProvider({
166
174
  const [resolvedConfig, setResolvedConfig] = useState(
167
175
  () => configOverride ? buildConfig(configOverride) : getConfig()
168
176
  );
169
- const [webWidth, setWebWidth] = useState(
170
- () => isWeb && typeof window !== "undefined" ? window.innerWidth : 0
171
- );
177
+ const [webWidth, setWebWidth] = useState(0);
172
178
  const effectiveWidth = windowWidthProp ?? (isWeb ? webWidth : 0);
173
179
  const numericScreens = useMemo2(() => {
174
180
  const out = {};
@@ -192,6 +198,7 @@ function ThemeProvider({
192
198
  }, [effectiveWidth]);
193
199
  useEffect(() => {
194
200
  if (!isWeb || typeof window === "undefined") return;
201
+ setWebWidth(window.innerWidth);
195
202
  let raf = 0;
196
203
  const update = () => {
197
204
  cancelAnimationFrame(raf);
@@ -207,20 +214,18 @@ function ThemeProvider({
207
214
  cancelAnimationFrame(raf);
208
215
  };
209
216
  }, []);
210
- const [webScheme, setWebScheme] = useState(() => {
211
- if (isWeb && typeof window !== "undefined") {
212
- return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
213
- }
214
- return "light";
215
- });
217
+ const webScheme = useSyncExternalStore(
218
+ subscribeSystemScheme,
219
+ getSystemScheme,
220
+ getSystemSchemeServerSnapshot
221
+ );
216
222
  const systemScheme = isWeb ? webScheme : colorScheme === "dark" ? "dark" : "light";
217
- const [mode, _setMode] = useState(() => {
218
- if (!disablePersistence) {
219
- const persisted = loadPersistedMode();
220
- if (persisted) return persisted;
221
- }
222
- return defaultMode;
223
- });
223
+ const [mode, _setMode] = useState(defaultMode);
224
+ useEffect(() => {
225
+ if (disablePersistence) return;
226
+ const persisted = loadPersistedMode();
227
+ if (persisted) _setMode(persisted);
228
+ }, []);
224
229
  const setMode = useCallback((next) => {
225
230
  _setMode(next);
226
231
  if (!disablePersistence) persistMode(next);
@@ -235,18 +240,6 @@ function ThemeProvider({
235
240
  applyWebTheme(resolvedMode, resolvedConfig.darkMode);
236
241
  setGlobalDarkMode(isDark);
237
242
  }, [isDark, resolvedMode, resolvedConfig.darkMode]);
238
- const setWebSchemeRef = useRef(setWebScheme);
239
- setWebSchemeRef.current = setWebScheme;
240
- useEffect(() => {
241
- if (!isWeb || typeof window === "undefined") return;
242
- const mq = window.matchMedia?.("(prefers-color-scheme: dark)");
243
- if (!mq) return;
244
- const handler = (e) => {
245
- setWebSchemeRef.current(e.matches ? "dark" : "light");
246
- };
247
- mq.addEventListener("change", handler);
248
- return () => mq.removeEventListener("change", handler);
249
- }, []);
250
243
  useEffect(() => {
251
244
  if (!configOverride) return;
252
245
  updateConfig(configOverride);
@@ -270,150 +263,11 @@ function ThemeProvider({
270
263
  return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: contextValue, children });
271
264
  }
272
265
 
273
- // src/ThemeToggle.tsx
274
- import React2 from "react";
275
- function getPrimitives() {
276
- try {
277
- const RN = __require("react-native");
278
- return {
279
- View: RN.View,
280
- Text: RN.Text,
281
- Touchable: RN.TouchableOpacity,
282
- Switch: RN.Switch
283
- };
284
- } catch {
285
- const View = ({ style, ...rest }) => React2.createElement("div", { style, ...rest });
286
- const Text = ({ style, ...rest }) => React2.createElement("span", { style, ...rest });
287
- const Touchable = ({ onPress, style, ...rest }) => React2.createElement("button", { onClick: onPress, style, ...rest });
288
- const Switch = ({
289
- value,
290
- onValueChange,
291
- accessibilityLabel
292
- }) => React2.createElement("input", {
293
- type: "checkbox",
294
- checked: value,
295
- onChange: (e) => onValueChange?.(e.target.checked),
296
- "aria-label": accessibilityLabel
297
- });
298
- return { View, Text, Touchable, Switch };
299
- }
300
- }
301
- var { View: _View, Text: _Text, Touchable: _Touchable, Switch: _Switch } = getPrimitives();
302
- function ThemeToggle({
303
- variant = "button",
304
- label,
305
- lightLabel = "Light",
306
- darkLabel = "Dark",
307
- includeSystem = false,
308
- style,
309
- labelStyle
310
- }) {
311
- const { mode, isDark, setMode, toggle } = useTheme();
312
- const View = _View, Text = _Text, Touchable = _Touchable, Switch = _Switch;
313
- if (variant === "switch") {
314
- return React2.createElement(
315
- View,
316
- { style: { flexDirection: "row", alignItems: "center", gap: 8, ...style } },
317
- React2.createElement(Text, { style: { fontSize: 14, ...labelStyle } }, lightLabel),
318
- React2.createElement(Switch, {
319
- value: isDark,
320
- onValueChange: (v) => setMode(v ? "dark" : "light"),
321
- trackColor: { false: "#d1d5db", true: "#6366f1" },
322
- thumbColor: isDark ? "#ffffff" : "#f3f4f6",
323
- ios_backgroundColor: "#d1d5db",
324
- accessibilityLabel: "Toggle dark mode"
325
- }),
326
- React2.createElement(Text, { style: { fontSize: 14, ...labelStyle } }, darkLabel)
327
- );
328
- }
329
- if (variant === "icon-button") {
330
- const icon = isDark ? darkLabel : lightLabel;
331
- return React2.createElement(
332
- Touchable,
333
- {
334
- onPress: toggle,
335
- style: {
336
- padding: 8,
337
- borderRadius: 8,
338
- backgroundColor: isDark ? "#374151" : "#f3f4f6",
339
- ...style
340
- },
341
- accessibilityRole: "button",
342
- accessibilityLabel: `Switch to ${isDark ? "light" : "dark"} mode`,
343
- accessibilityState: {}
344
- },
345
- React2.createElement(Text, { style: { fontSize: 18, ...labelStyle } }, icon)
346
- );
347
- }
348
- if (includeSystem) {
349
- const modes = [
350
- { key: "light", label: lightLabel },
351
- { key: "dark", label: darkLabel },
352
- { key: "system", label: "System" }
353
- ];
354
- return React2.createElement(
355
- View,
356
- { style: { flexDirection: "row", gap: 4, ...style } },
357
- ...modes.map(
358
- ({ key, label: mLabel }) => React2.createElement(
359
- Touchable,
360
- {
361
- key,
362
- onPress: () => setMode(key),
363
- style: {
364
- paddingHorizontal: 12,
365
- paddingVertical: 6,
366
- borderRadius: 6,
367
- backgroundColor: mode === key ? isDark ? "#6366f1" : "#3b82f6" : isDark ? "#374151" : "#e5e7eb"
368
- },
369
- accessibilityRole: "button",
370
- accessibilityLabel: `Set ${key} theme`,
371
- accessibilityState: { selected: mode === key }
372
- },
373
- React2.createElement(Text, {
374
- style: {
375
- fontSize: 13,
376
- fontWeight: "500",
377
- color: mode === key ? "#ffffff" : isDark ? "#d1d5db" : "#374151",
378
- ...labelStyle
379
- }
380
- }, mLabel)
381
- )
382
- )
383
- );
384
- }
385
- const currentLabel = label ?? (isDark ? darkLabel : lightLabel);
386
- return React2.createElement(
387
- Touchable,
388
- {
389
- onPress: toggle,
390
- style: {
391
- paddingHorizontal: 16,
392
- paddingVertical: 8,
393
- borderRadius: 8,
394
- backgroundColor: isDark ? "#374151" : "#e5e7eb",
395
- ...style
396
- },
397
- accessibilityRole: "button",
398
- accessibilityLabel: `Switch to ${isDark ? "light" : "dark"} mode`,
399
- accessibilityState: {}
400
- },
401
- React2.createElement(Text, {
402
- style: {
403
- fontSize: 14,
404
- fontWeight: "500",
405
- color: isDark ? "#f9fafb" : "#111827",
406
- ...labelStyle
407
- }
408
- }, currentLabel)
409
- );
410
- }
411
-
412
266
  // src/styled.tsx
413
- import React3, {
267
+ import React2, {
414
268
  forwardRef,
415
269
  useState as useState2,
416
- useCallback as useCallback3,
270
+ useCallback as useCallback2,
417
271
  useMemo as useMemo3
418
272
  } from "react";
419
273
  function styled(Component, baseClasses = "") {
@@ -427,6 +281,7 @@ function styled(Component, baseClasses = "") {
427
281
  onPointerDown,
428
282
  onPointerUp,
429
283
  onPointerLeave,
284
+ onPointerCancel,
430
285
  onMouseEnter,
431
286
  onMouseLeave,
432
287
  onFocus,
@@ -440,42 +295,16 @@ function styled(Component, baseClasses = "") {
440
295
  const [pressed, setPressed] = useState2(false);
441
296
  const [hovered, setHovered] = useState2(false);
442
297
  const [focused, setFocused] = useState2(false);
443
- const handlePressIn = useCallback3((e) => {
444
- setPressed(true);
445
- onPressIn?.(e);
446
- }, [onPressIn]);
447
- const handlePressOut = useCallback3((e) => {
448
- setPressed(false);
449
- onPressOut?.(e);
450
- }, [onPressOut]);
451
- const handlePointerDown = useCallback3((e) => {
452
- setPressed(true);
453
- onPointerDown?.(e);
454
- }, [onPointerDown]);
455
- const handlePointerUp = useCallback3((e) => {
456
- setPressed(false);
457
- onPointerUp?.(e);
458
- }, [onPointerUp]);
459
- const handlePointerLeave = useCallback3((e) => {
460
- setPressed(false);
461
- onPointerLeave?.(e);
462
- }, [onPointerLeave]);
463
- const handleMouseEnter = useCallback3((e) => {
464
- setHovered(true);
465
- onMouseEnter?.(e);
466
- }, [onMouseEnter]);
467
- const handleMouseLeave = useCallback3((e) => {
468
- setHovered(false);
469
- onMouseLeave?.(e);
470
- }, [onMouseLeave]);
471
- const handleFocus = useCallback3((e) => {
472
- setFocused(true);
473
- onFocus?.(e);
474
- }, [onFocus]);
475
- const handleBlur = useCallback3((e) => {
476
- setFocused(false);
477
- onBlur?.(e);
478
- }, [onBlur]);
298
+ const handlePressIn = useCallback2(chain(onPressIn, () => setPressed(true)), [onPressIn]);
299
+ const handlePressOut = useCallback2(chain(onPressOut, () => setPressed(false)), [onPressOut]);
300
+ const handlePointerDown = useCallback2(chain(onPointerDown, () => setPressed(true)), [onPointerDown]);
301
+ const handlePointerUp = useCallback2(chain(onPointerUp, () => setPressed(false)), [onPointerUp]);
302
+ const handlePointerLeave = useCallback2(chain(onPointerLeave, () => setPressed(false)), [onPointerLeave]);
303
+ const handlePointerCancel = useCallback2(chain(onPointerCancel, () => setPressed(false)), [onPointerCancel]);
304
+ const handleMouseEnter = useCallback2(chain(onMouseEnter, () => setHovered(true)), [onMouseEnter]);
305
+ const handleMouseLeave = useCallback2(chain(onMouseLeave, () => setHovered(false)), [onMouseLeave]);
306
+ const handleFocus = useCallback2(chain(onFocus, () => setFocused(true)), [onFocus]);
307
+ const handleBlur = useCallback2(chain(onBlur, () => setFocused(false)), [onBlur]);
479
308
  const combined = extraClasses ? `${baseClasses} ${extraClasses}` : baseClasses;
480
309
  const resolved = useMemo3(() => resolve(combined, config.theme, config.darkMode), [combined, config.theme, config.darkMode]);
481
310
  const hasInteractive = useMemo3(() => hasInteractiveBuckets(resolved), [resolved]);
@@ -493,7 +322,7 @@ function styled(Component, baseClasses = "") {
493
322
  const effectiveComponent = webTag ?? Component;
494
323
  const componentName2 = Component.displayName ?? Component.name ?? "";
495
324
  const effectiveRest = webTag && componentName2 ? transformToWebProps(componentName2, webTag, rest) : rest;
496
- return React3.createElement(effectiveComponent, {
325
+ return React2.createElement(effectiveComponent, {
497
326
  ref,
498
327
  ...effectiveRest,
499
328
  style: finalStyle,
@@ -503,7 +332,7 @@ function styled(Component, baseClasses = "") {
503
332
  // Always forward user-provided handlers to avoid silently swallowing them.
504
333
  // onPressIn/onPressOut are forwarded on web too (not just native) since
505
334
  // react-native-web components rely on receiving them directly.
506
- ...isWeb ? hasInteractive ? { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave, onPointerCancel: handlePointerLeave, onPressIn, onPressOut } : { onPointerDown, onPointerUp, onPointerLeave, onPressIn, onPressOut } : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
335
+ ...isWeb ? hasInteractive ? { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave, onPointerCancel: handlePointerCancel, onPressIn, onPressOut } : { onPointerDown, onPointerUp, onPointerLeave, onPointerCancel, onPressIn, onPressOut } : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
507
336
  onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
508
337
  onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
509
338
  onFocus: hasInteractive ? handleFocus : onFocus,
@@ -544,7 +373,7 @@ function useBreakpoint() {
544
373
  const width = useGlobalWidth();
545
374
  const screens = config.theme.screens;
546
375
  const sorted = useMemo5(
547
- () => Object.entries(screens).filter((entry) => typeof entry[1] === "number").sort(([, a], [, b]) => b - a),
376
+ () => Object.entries(screens).map(([name, v]) => [name, typeof v === "number" ? v : parseInt(String(v), 10)]).filter(([, minW]) => !Number.isNaN(minW)).sort(([, a], [, b]) => b - a),
548
377
  [screens]
549
378
  );
550
379
  for (const [name, minW] of sorted) {
@@ -558,8 +387,9 @@ function useResponsive() {
558
387
  const screens = config.theme.screens;
559
388
  return useMemo5(() => {
560
389
  const result = {};
561
- for (const [name, minW] of Object.entries(screens)) {
562
- if (typeof minW === "number") result[name] = width >= minW;
390
+ for (const [name, v] of Object.entries(screens)) {
391
+ const minW = typeof v === "number" ? v : parseInt(String(v), 10);
392
+ if (!Number.isNaN(minW)) result[name] = width >= minW;
563
393
  }
564
394
  return result;
565
395
  }, [screens, width]);
@@ -581,8 +411,8 @@ export {
581
411
  InteractiveWrapper,
582
412
  ThemeContext,
583
413
  ThemeProvider,
584
- ThemeToggle,
585
414
  buildConfig,
415
+ clearCache,
586
416
  cx,
587
417
  defaultColors,
588
418
  defaultTheme,
@@ -596,6 +426,7 @@ export {
596
426
  parseClasses,
597
427
  registerWebElement,
598
428
  resolve,
429
+ setResolveTarget,
599
430
  splitClassTokens,
600
431
  styled,
601
432
  updateConfig,