@kbach/react 0.5.56 → 0.5.58

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
@@ -11,7 +11,7 @@ import {
11
11
  useConditionalWidth,
12
12
  useGlobalDarkMode,
13
13
  useGlobalWidth
14
- } from "./chunk-PESA3FMP.mjs";
14
+ } from "./chunk-F5NQX5YO.mjs";
15
15
  import {
16
16
  __require,
17
17
  buildConfig,
@@ -33,7 +33,7 @@ import {
33
33
  syncGlobalScreens,
34
34
  syncGlobalWidth,
35
35
  updateConfig
36
- } from "./chunk-JBV2R655.mjs";
36
+ } from "./chunk-2HWRNTJG.mjs";
37
37
 
38
38
  // src/context.tsx
39
39
  import { createContext, useContext } from "react";
@@ -429,7 +429,8 @@ function styled(Component, baseClasses = "") {
429
429
  } = props;
430
430
  const disabled = !!props.disabled;
431
431
  const checked = !!props.checked;
432
- const { isDark, config } = useTheme();
432
+ const { config } = useTheme();
433
+ const isDark = useGlobalDarkMode();
433
434
  const [pressed, setPressed] = useState2(false);
434
435
  const [hovered, setHovered] = useState2(false);
435
436
  const [focused, setFocused] = useState2(false);
@@ -494,7 +495,7 @@ function styled(Component, baseClasses = "") {
494
495
  ...isWeb && combined ? { className: normalizeClassString(combined) } : {},
495
496
  // Only attach state-tracking handlers when interactive modifiers are present.
496
497
  // Always forward user-provided handlers to avoid silently swallowing them.
497
- ...isWeb ? hasInteractive ? { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave } : { onPointerDown, onPointerUp, onPointerLeave } : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
498
+ ...isWeb ? hasInteractive ? { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave, onPointerCancel: handlePointerLeave } : { onPointerDown, onPointerUp, onPointerLeave } : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
498
499
  onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
499
500
  onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
500
501
  onFocus: hasInteractive ? handleFocus : onFocus,
@@ -517,7 +518,7 @@ function useStyles(classString, state = {}) {
517
518
  const resolved = resolve(normalised, config.theme, config.darkMode);
518
519
  const breakpoints = getActiveBreakpoints(width);
519
520
  return flatten(resolved, isDark, state, breakpoints);
520
- }, [normalised, isDark, config, width, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
521
+ }, [normalised, isDark, config.theme, config.darkMode, width, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
521
522
  }
522
523
  function useResolvedStyle(classString) {
523
524
  const { config } = useTheme();
@@ -535,7 +536,7 @@ function useBreakpoint() {
535
536
  const width = useGlobalWidth();
536
537
  const screens = config.theme.screens;
537
538
  const sorted = useMemo5(
538
- () => Object.entries(screens).filter(([, v]) => typeof v === "number").sort(([, a], [, b]) => b - a),
539
+ () => Object.entries(screens).filter((entry) => typeof entry[1] === "number").sort(([, a], [, b]) => b - a),
539
540
  [screens]
540
541
  );
541
542
  for (const [name, minW] of sorted) {
@@ -479,14 +479,15 @@ var defaultTheme = {
479
479
  normal: 1.5,
480
480
  relaxed: 1.625,
481
481
  loose: 2,
482
- 3: 12,
483
- 4: 16,
484
- 5: 20,
485
- 6: 24,
486
- 7: 28,
487
- 8: 32,
488
- 9: 36,
489
- 10: 40
482
+ // Pixel values stored as strings; styleValueToCSS passes them through, toNativeValue strips the unit
483
+ 3: "12px",
484
+ 4: "16px",
485
+ 5: "20px",
486
+ 6: "24px",
487
+ 7: "28px",
488
+ 8: "32px",
489
+ 9: "36px",
490
+ 10: "40px"
490
491
  },
491
492
  letterSpacing: {
492
493
  tighter: -0.8,
@@ -507,9 +508,12 @@ var defaultTheme = {
507
508
  },
508
509
  flex: {
509
510
  1: 1,
510
- auto: 1,
511
- initial: 0,
512
- none: 0
511
+ auto: "auto",
512
+ // CSS: flex: auto = 1 1 auto; native: mapped to 1 in resolver
513
+ initial: "initial",
514
+ // CSS: flex: initial = 0 1 auto; native: mapped to 1
515
+ none: "none"
516
+ // CSS: flex: none = 0 0 auto; native: mapped to 0
513
517
  },
514
518
  shadow: {
515
519
  sm: {
@@ -579,6 +583,7 @@ var defaultTheme = {
579
583
  var LRUCache = class {
580
584
  constructor(capacity = 1e4) {
581
585
  this.capacity = capacity;
586
+ if (capacity <= 0) throw new Error("LRUCache: capacity must be a positive integer");
582
587
  this.cache = /* @__PURE__ */ new Map();
583
588
  }
584
589
  get(key) {
@@ -598,11 +603,7 @@ var LRUCache = class {
598
603
  return this;
599
604
  }
600
605
  has(key) {
601
- if (!this.cache.has(key)) return false;
602
- const value = this.cache.get(key);
603
- this.cache.delete(key);
604
- this.cache.set(key, value);
605
- return true;
606
+ return this.cache.has(key);
606
607
  }
607
608
  delete(key) {
608
609
  return this.cache.delete(key);
@@ -617,7 +618,7 @@ var LRUCache = class {
617
618
 
618
619
  // src/core/platform.ts
619
620
  var isWeb = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
620
- var isNative = !isWeb && (typeof globalThis.HermesInternal !== "undefined" || typeof globalThis.__fbBatchedBridge !== "undefined" || typeof navigator !== "undefined" && navigator.product === "ReactNative");
621
+ var isNative = !isWeb && (typeof globalThis.HermesInternal !== "undefined" || typeof globalThis.__fbBatchedBridge !== "undefined" || typeof navigator !== "undefined" && navigator.product === "ReactNative" || typeof globalThis.__REACT_NATIVE__ !== "undefined");
621
622
  var _cssGenMode = false;
622
623
  function getEffectiveIsWeb() {
623
624
  return isWeb || _cssGenMode || !isNative;
@@ -630,7 +631,7 @@ function toNativeValue(raw) {
630
631
  return raw;
631
632
  }
632
633
  function escapeCSSSelector(cls) {
633
- return cls.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
634
+ return cls.replace(/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
634
635
  }
635
636
 
636
637
  // src/core/registry.ts
@@ -707,6 +708,10 @@ function _invalidate() {
707
708
  _responsiveNames = null;
708
709
  }
709
710
  function registerModifier(name, def) {
711
+ if (process.env.NODE_ENV !== "production" && name in BUILTIN_MODIFIERS) {
712
+ console.warn(`[kbach] registerModifier: "${name}" is a built-in modifier and cannot be overridden. Use a different name.`);
713
+ return;
714
+ }
710
715
  _pluginModifiers[name] = def;
711
716
  _invalidate();
712
717
  }
@@ -788,7 +793,7 @@ function resolveColor(value, colors, isArbitrary) {
788
793
  }
789
794
  if (!hex) return null;
790
795
  if (!opacityPart) {
791
- if (!isWeb && hex.startsWith("#") && hex.length === 9) {
796
+ if (!getEffectiveIsWeb() && hex.startsWith("#") && hex.length === 9) {
792
797
  return hexToRgba(hex, Math.round(parseInt(hex.slice(7, 9), 16) / 255 * 1e3) / 1e3);
793
798
  }
794
799
  return hex;
@@ -822,7 +827,10 @@ function resolveSpacing(value, negative, spacing, isArbitrary) {
822
827
  const resolved = getEffectiveIsWeb() ? value : toNativeValue(value);
823
828
  if (negative) {
824
829
  if (typeof resolved === "number") return -resolved;
825
- if (typeof resolved === "string" && /^\d/.test(resolved)) return `-${resolved}`;
830
+ if (typeof resolved === "string") {
831
+ if (/^\d/.test(resolved)) return `-${resolved}`;
832
+ if (/^(calc|var|min|max|clamp|env)\s*\(/.test(resolved)) return `calc(-1 * (${resolved}))`;
833
+ }
826
834
  }
827
835
  return resolved;
828
836
  }
@@ -1328,7 +1336,11 @@ var RESOLVERS = {
1328
1336
  const color = resolveColor(value, colors, false);
1329
1337
  if (color) return { borderColor: color };
1330
1338
  const w = borderWidth[value] ?? spacing[value];
1331
- if (w !== void 0) return { borderWidth: typeof w === "number" ? w : parseFloat(String(w)) };
1339
+ if (w !== void 0) {
1340
+ const numW = typeof w === "number" ? w : parseFloat(String(w));
1341
+ if (isNaN(numW)) return null;
1342
+ return { borderWidth: numW };
1343
+ }
1332
1344
  return null;
1333
1345
  },
1334
1346
  "border-t": ({ value, isArbitrary }, { colors, borderWidth }) => {
@@ -2031,7 +2043,7 @@ var RESOLVERS = {
2031
2043
  },
2032
2044
  // ── Tint color (native-only, for Image and icon components) ──────────────
2033
2045
  tint: ({ value, isArbitrary }, { colors }) => {
2034
- if (isWeb) return null;
2046
+ if (getEffectiveIsWeb()) return null;
2035
2047
  const color = resolveColor(value, colors, isArbitrary);
2036
2048
  return color ? { tintColor: color } : null;
2037
2049
  },
@@ -2103,7 +2115,15 @@ var RESOLVERS = {
2103
2115
  const v = parseFloat(value);
2104
2116
  return isNaN(v) ? null : { flex: v };
2105
2117
  }
2106
- if (value in flex) return { flex: flex[value] };
2118
+ if (value in flex) {
2119
+ const v = flex[value];
2120
+ if (typeof v === "string" && !getEffectiveIsWeb()) {
2121
+ if (v === "auto" || v === "initial") return { flex: 1 };
2122
+ if (v === "none") return { flex: 0 };
2123
+ return null;
2124
+ }
2125
+ return { flex: v };
2126
+ }
2107
2127
  const n = parseFloat(value);
2108
2128
  return isNaN(n) ? null : { flex: n };
2109
2129
  },
@@ -2206,7 +2226,9 @@ var RESOLVERS = {
2206
2226
  leading: ({ value, isArbitrary }, { lineHeight }) => {
2207
2227
  if (isArbitrary) return { lineHeight: getEffectiveIsWeb() ? value : toNativeValue(value) };
2208
2228
  const v = lineHeight[value];
2209
- return v !== void 0 ? { lineHeight: v } : null;
2229
+ if (v === void 0) return null;
2230
+ if (typeof v === "string" && !getEffectiveIsWeb()) return { lineHeight: toNativeValue(v) };
2231
+ return { lineHeight: v };
2210
2232
  },
2211
2233
  // ── Letter spacing ─────────────────────────────────────────────────────────
2212
2234
  tracking: ({ value, isArbitrary }, { letterSpacing }) => {
@@ -2282,7 +2304,7 @@ var RESOLVERS = {
2282
2304
  transition: ({ value }) => {
2283
2305
  if (!getEffectiveIsWeb()) return null;
2284
2306
  const presets = {
2285
- "": "color 150ms, background-color 150ms, border-color 150ms, opacity 150ms, box-shadow 150ms",
2307
+ "": "color 150ms, background-color 150ms, border-color 150ms, text-decoration-color 150ms, fill 150ms, stroke 150ms, opacity 150ms, box-shadow 150ms, transform 150ms, filter 150ms, backdrop-filter 150ms",
2286
2308
  all: "all 150ms",
2287
2309
  none: "none",
2288
2310
  colors: "color 150ms, background-color 150ms, border-color 150ms",
@@ -2295,11 +2317,13 @@ var RESOLVERS = {
2295
2317
  },
2296
2318
  duration: ({ value, isArbitrary }) => {
2297
2319
  if (!getEffectiveIsWeb()) return null;
2320
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2298
2321
  const ms = isArbitrary ? value : `${value}ms`;
2299
2322
  return { transitionDuration: ms };
2300
2323
  },
2301
2324
  delay: ({ value, isArbitrary }) => {
2302
2325
  if (!getEffectiveIsWeb()) return null;
2326
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2303
2327
  const ms = isArbitrary ? value : `${value}ms`;
2304
2328
  return { transitionDelay: ms };
2305
2329
  },
@@ -2342,14 +2366,20 @@ var RESOLVERS = {
2342
2366
  boxShadow: offsetWidth === 0 ? `0 0 0 ${DEFAULT_RING_WIDTH}px ${DEFAULT_RING_COLOR}` : `0 0 0 ${offsetWidth}px #fff, 0 0 0 ${offsetWidth + DEFAULT_RING_WIDTH}px ${DEFAULT_RING_COLOR}`
2343
2367
  };
2344
2368
  },
2345
- // ── Space between (uses CSS gap or margin fallback) ────────────────────────
2369
+ // ── Space between ─────────────────────────────────────────────────────────
2370
+ // On web: emits __spaceX/__spaceY markers → resolver generates > * + * CSS rules.
2371
+ // On native (RN 0.71+): uses columnGap/rowGap directly.
2346
2372
  "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
2347
2373
  const v = resolveSpacing(value, negative, spacing, isArbitrary);
2348
- return v !== null ? { columnGap: v } : null;
2374
+ if (v === null) return null;
2375
+ if (getEffectiveIsWeb()) return { __spaceX: v };
2376
+ return { columnGap: v };
2349
2377
  },
2350
2378
  "space-y": ({ value, negative, isArbitrary }, { spacing }) => {
2351
2379
  const v = resolveSpacing(value, negative, spacing, isArbitrary);
2352
- return v !== null ? { rowGap: v } : null;
2380
+ if (v === null) return null;
2381
+ if (getEffectiveIsWeb()) return { __spaceY: v };
2382
+ return { rowGap: v };
2353
2383
  }
2354
2384
  };
2355
2385
  function resolveUtility(parsed, theme) {
@@ -2415,12 +2445,28 @@ function parseClass(className) {
2415
2445
  negative = true;
2416
2446
  remaining = remaining.slice(1);
2417
2447
  }
2418
- const bracketStart = remaining.lastIndexOf("-[");
2419
- if (bracketStart > 0 && remaining.endsWith("]")) {
2420
- const utility = remaining.slice(0, bracketStart);
2421
- const value = remaining.slice(bracketStart + 2, -1).replace(/_/g, " ");
2448
+ let bracketDepth = 0;
2449
+ let bracketStart = -1;
2450
+ for (let i = remaining.length - 1; i >= 0; i--) {
2451
+ if (remaining[i] === "]") bracketDepth++;
2452
+ else if (remaining[i] === "[") {
2453
+ bracketDepth--;
2454
+ if (bracketDepth === 0) {
2455
+ bracketStart = i;
2456
+ break;
2457
+ }
2458
+ }
2459
+ }
2460
+ if (bracketStart > 0 && remaining[bracketStart - 1] === "-" && remaining.endsWith("]")) {
2461
+ const utility = remaining.slice(0, bracketStart - 1);
2462
+ const value = remaining.slice(bracketStart + 1, -1).replace(/_/g, " ");
2422
2463
  return { original: trimmed, modifiers, negative, important, utility, value, isArbitrary: true };
2423
2464
  }
2465
+ if (remaining.endsWith("]") && bracketStart === -1) {
2466
+ if (process.env.NODE_ENV !== "production") {
2467
+ console.warn(`[kbach] Malformed class string: "${trimmed}" \u2014 unbalanced brackets`);
2468
+ }
2469
+ }
2424
2470
  if (remaining.startsWith("[") && remaining.endsWith("]")) {
2425
2471
  const value = remaining.slice(1, -1).replace(/_/g, " ");
2426
2472
  return { original: trimmed, modifiers, negative, important, utility: "", value, isArbitrary: true };
@@ -2455,6 +2501,7 @@ function splitClassTokens(classString) {
2455
2501
  const tokens = [];
2456
2502
  let current = "";
2457
2503
  let depth = 0;
2504
+ let parenDepth = 0;
2458
2505
  for (let i = 0; i < classString.length; i++) {
2459
2506
  const ch = classString[i];
2460
2507
  if (ch === "[") {
@@ -2467,8 +2514,18 @@ function splitClassTokens(classString) {
2467
2514
  current += ch;
2468
2515
  continue;
2469
2516
  }
2517
+ if (ch === "(") {
2518
+ parenDepth++;
2519
+ current += ch;
2520
+ continue;
2521
+ }
2522
+ if (ch === ")") {
2523
+ parenDepth--;
2524
+ current += ch;
2525
+ continue;
2526
+ }
2470
2527
  if (/\s/.test(ch)) {
2471
- if (depth === 0) {
2528
+ if (depth === 0 && parenDepth === 0) {
2472
2529
  if (current) {
2473
2530
  tokens.push(current);
2474
2531
  current = "";
@@ -2600,7 +2657,8 @@ function injectRule(rule) {
2600
2657
  if (_injectedRules.has(rule)) return;
2601
2658
  _injectedRules.set(rule, true);
2602
2659
  try {
2603
- getStyleEl().sheet?.insertRule(rule, getStyleEl().sheet.cssRules.length);
2660
+ const sheet = getStyleEl().sheet;
2661
+ if (sheet) sheet.insertRule(rule, sheet.cssRules.length);
2604
2662
  } catch {
2605
2663
  }
2606
2664
  }
@@ -2616,24 +2674,41 @@ function injectGlobalStyles(theme) {
2616
2674
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2617
2675
  const rules = [BASE_RESET];
2618
2676
  const ff = theme.fontFamily;
2677
+ if (!ff) return;
2619
2678
  if (ff.sans && ff.sans !== "System") {
2620
2679
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
2621
2680
  rules.push(`body { font-family: ${family}; }`);
2622
2681
  }
2623
2682
  getGlobalStyleEl().textContent = rules.join("\n");
2624
2683
  }
2625
- function buildDivideDecls(styles) {
2684
+ function buildDivideDecls(styles, forceImportant) {
2685
+ const imp = forceImportant ? " !important" : "";
2626
2686
  const parts = [];
2627
2687
  if ("__divideX" in styles) {
2628
2688
  const w = Number(styles.__divideX);
2629
- parts.push(`border-left-width: ${w}px`, "border-right-width: 0px");
2689
+ parts.push(`border-left-width: ${w}px${imp}`, `border-right-width: 0px${imp}`);
2630
2690
  }
2631
2691
  if ("__divideY" in styles) {
2632
2692
  const w = Number(styles.__divideY);
2633
- parts.push(`border-top-width: ${w}px`, "border-bottom-width: 0px");
2693
+ parts.push(`border-top-width: ${w}px${imp}`, `border-bottom-width: 0px${imp}`);
2694
+ }
2695
+ if ("__divideColor" in styles) parts.push(`border-color: ${String(styles.__divideColor)}${imp}`);
2696
+ if ("__divideStyle" in styles) parts.push(`border-style: ${String(styles.__divideStyle)}${imp}`);
2697
+ return parts.join("; ");
2698
+ }
2699
+ function buildSpaceDecls(styles, forceImportant) {
2700
+ const imp = forceImportant ? " !important" : "";
2701
+ const parts = [];
2702
+ if ("__spaceX" in styles) {
2703
+ const v = styles.__spaceX;
2704
+ const val = typeof v === "number" ? `${v}px` : String(v);
2705
+ parts.push(`margin-left: ${val}${imp}`);
2706
+ }
2707
+ if ("__spaceY" in styles) {
2708
+ const v = styles.__spaceY;
2709
+ const val = typeof v === "number" ? `${v}px` : String(v);
2710
+ parts.push(`margin-top: ${val}${imp}`);
2634
2711
  }
2635
- if ("__divideColor" in styles) parts.push(`border-color: ${String(styles.__divideColor)}`);
2636
- if ("__divideStyle" in styles) parts.push(`border-style: ${String(styles.__divideStyle)}`);
2637
2712
  return parts.join("; ");
2638
2713
  }
2639
2714
  var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
@@ -2653,7 +2728,9 @@ var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
2653
2728
  "__divideY",
2654
2729
  "__divideColor",
2655
2730
  "__divideStyle",
2656
- "__keyframe"
2731
+ "__keyframe",
2732
+ "__spaceX",
2733
+ "__spaceY"
2657
2734
  ]);
2658
2735
  var CSS_UNITLESS = /* @__PURE__ */ new Set([
2659
2736
  "opacity",
@@ -2706,10 +2783,12 @@ function buildClassCSSRules(cls, bucketKey, styles, darkMode, important, screens
2706
2783
  rules.push(styles.__keyframe);
2707
2784
  }
2708
2785
  const isDivide = "__divideX" in styles || "__divideY" in styles || "__divideColor" in styles || "__divideStyle" in styles;
2786
+ const isSpace = "__spaceX" in styles || "__spaceY" in styles;
2787
+ const isChildCombinator = isDivide || isSpace;
2709
2788
  const escaped = escapeCSSSelector(cls);
2710
- const childSuffix = isDivide ? " > * + *" : "";
2789
+ const childSuffix = isChildCombinator ? " > * + *" : "";
2711
2790
  if (bucketKey === "base") {
2712
- const decls2 = isDivide ? buildDivideDecls(styles) : styleValueToCSS(styles, important);
2791
+ const decls2 = isDivide ? buildDivideDecls(styles, important) : isSpace ? buildSpaceDecls(styles, important) : styleValueToCSS(styles, important);
2713
2792
  if (!decls2) return rules;
2714
2793
  rules.push(`.${escaped}${childSuffix} { ${decls2} }`);
2715
2794
  return rules;
@@ -2750,7 +2829,7 @@ function buildClassCSSRules(cls, bucketKey, styles, darkMode, important, screens
2750
2829
  const ancestorPrefix = ancestorParts.join("");
2751
2830
  const dirPrefix = dirParts.join("");
2752
2831
  const selector = `${dirPrefix}${ancestorPrefix}${elementSelector}`;
2753
- const decls = isDivide ? buildDivideDecls(styles) : styleValueToCSS(styles, needsImportant);
2832
+ const decls = isDivide ? buildDivideDecls(styles, needsImportant) : isSpace ? buildSpaceDecls(styles, needsImportant) : styleValueToCSS(styles, needsImportant);
2754
2833
  if (!decls) return rules;
2755
2834
  let rule;
2756
2835
  if (darkScheme === "dark") {
@@ -2779,7 +2858,7 @@ function resolve(classString, theme, darkMode = "attribute") {
2779
2858
  const cached = cache.get(cacheKey);
2780
2859
  if (cached) return cached;
2781
2860
  const result = {};
2782
- const onWeb = typeof document !== "undefined";
2861
+ const onWeb = isWeb;
2783
2862
  for (const parsed of parseClasses(classString)) {
2784
2863
  const styles = resolveUtility(parsed, theme);
2785
2864
  if (!styles) {
@@ -2916,19 +2995,24 @@ function buildConfig(userConfig) {
2916
2995
  theme = deepMerge(theme, userConfig.theme);
2917
2996
  }
2918
2997
  if (userConfig.extend) {
2919
- const { theme: nestedTheme, ...directKeys } = userConfig.extend;
2998
+ const extendConfig = userConfig.extend;
2999
+ const { theme: nestedTheme, ...directKeys } = extendConfig;
2920
3000
  const extSources = [];
2921
3001
  if (nestedTheme) extSources.push(nestedTheme);
2922
3002
  if (Object.keys(directKeys).length) extSources.push(directKeys);
2923
3003
  for (const ext of extSources) {
2924
3004
  for (const [key, value] of Object.entries(ext)) {
3005
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
3006
+ if (process.env.NODE_ENV !== "production") {
3007
+ console.warn(`[kbach] config.extend.${key} must be a plain object, got ${typeof value}. Skipping.`);
3008
+ }
3009
+ continue;
3010
+ }
2925
3011
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
2926
3012
  theme[key] = deepMerge(
2927
3013
  theme[key] ?? {},
2928
3014
  value
2929
3015
  );
2930
- } else if (value !== void 0) {
2931
- theme[key] = value;
2932
3016
  }
2933
3017
  }
2934
3018
  }
@@ -2939,8 +3023,10 @@ function buildConfig(userConfig) {
2939
3023
  theme,
2940
3024
  plugins: userConfig.plugins ?? []
2941
3025
  };
2942
- clearPluginUtilities();
2943
- clearPluginModifiers();
3026
+ if (userConfig.plugins !== void 0) {
3027
+ clearPluginUtilities();
3028
+ clearPluginModifiers();
3029
+ }
2944
3030
  const _store = getConfigStore();
2945
3031
  for (const k of Object.keys(_store.customVariants)) delete _store.customVariants[k];
2946
3032
  const pluginAPI = makePluginAPI(resolved.theme);
@@ -2958,7 +3044,7 @@ function _selectorToModifierDef(selector) {
2958
3044
  if (s.startsWith("@media ")) {
2959
3045
  return { mediaQuery: s.slice(7), jsBehavior: "css-only", forcesImportant: true };
2960
3046
  }
2961
- if (s.startsWith("::") || s.startsWith(":")) {
3047
+ if (s.startsWith("::") || s.startsWith(":") && !s.includes(" ") && !s.includes("&") && !s.includes("(")) {
2962
3048
  return { pseudo: s, jsBehavior: "css-only", forcesImportant: true };
2963
3049
  }
2964
3050
  return { ancestorSelector: s.endsWith(" ") ? s : `${s} `, jsBehavior: "css-only", forcesImportant: true };
@@ -2971,8 +3057,8 @@ function makePluginAPI(theme) {
2971
3057
  },
2972
3058
  addVariant(name, selectorOrDef) {
2973
3059
  const def = typeof selectorOrDef === "string" ? _selectorToModifierDef(selectorOrDef) : selectorOrDef;
2974
- const selector = typeof selectorOrDef === "string" ? selectorOrDef : selectorOrDef.pseudo ?? selectorOrDef.mediaQuery ?? selectorOrDef.ancestorSelector ?? name;
2975
- customVariants[name] = selector;
3060
+ const selector = typeof selectorOrDef === "string" ? selectorOrDef : selectorOrDef.pseudo ?? selectorOrDef.mediaQuery ?? selectorOrDef.ancestorSelector ?? null;
3061
+ if (selector !== null && selector !== name) customVariants[name] = selector;
2976
3062
  registerModifier(name, def);
2977
3063
  },
2978
3064
  theme(path, defaultValue) {
@@ -2989,6 +3075,11 @@ function makePluginAPI(theme) {
2989
3075
  }
2990
3076
  };
2991
3077
  }
3078
+ function onConfigChange(listener) {
3079
+ const store = getConfigStore();
3080
+ store.listeners.add(listener);
3081
+ return () => store.listeners.delete(listener);
3082
+ }
2992
3083
  var customVariants = new Proxy(
2993
3084
  {},
2994
3085
  {
@@ -3015,7 +3106,7 @@ var KEY = "__kbach_dark_store__";
3015
3106
  function getStore2() {
3016
3107
  const g = globalThis;
3017
3108
  if (!g[KEY]) {
3018
- g[KEY] = { isDark: false, lastNotified: false, subscribers: /* @__PURE__ */ new Set() };
3109
+ g[KEY] = { isDark: false, subscribers: /* @__PURE__ */ new Set() };
3019
3110
  }
3020
3111
  return g[KEY];
3021
3112
  }
@@ -3147,10 +3238,11 @@ var InteractiveWrapper = (0, import_react3.forwardRef)(
3147
3238
  const handleMouseLeave = (0, import_react3.useCallback)(chain(onMouseLeave, () => setHovered(false)), [onMouseLeave]);
3148
3239
  const handleFocus = (0, import_react3.useCallback)(chain(onFocus, () => setFocused(true)), [onFocus]);
3149
3240
  const handleBlur = (0, import_react3.useCallback)(chain(onBlur, () => setFocused(false)), [onBlur]);
3241
+ const { children, disabled, checked, ...restForComponent } = rest;
3150
3242
  const computedStyle = (0, import_react3.useMemo)(
3151
3243
  () => {
3152
3244
  if (isWeb) return {};
3153
- const s = flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused, disabled: !!rest.disabled, checked: !!rest.checked }, breakpoints);
3245
+ const s = flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused, disabled: !!disabled, checked: !!checked }, breakpoints);
3154
3246
  delete s.__divideX;
3155
3247
  delete s.__divideY;
3156
3248
  delete s.__divideColor;
@@ -3181,20 +3273,20 @@ var InteractiveWrapper = (0, import_react3.forwardRef)(
3181
3273
  }
3182
3274
  return s;
3183
3275
  },
3184
- [resolvedStyle, isDark, pressed, hovered, focused, width]
3185
- // eslint-disable-line react-hooks/exhaustive-deps
3276
+ [resolvedStyle, isDark, pressed, hovered, focused, width, disabled, checked]
3186
3277
  );
3187
3278
  const skipComputedInline = isWeb || !isNative && typeof Component === "string";
3188
3279
  const finalStyle = skipComputedInline ? styleProp ?? void 0 : styleProp ? Array.isArray(styleProp) ? Object.assign({}, computedStyle, ...styleProp) : { ...computedStyle, ...styleProp } : computedStyle;
3189
- const { children, ...restWithoutChildren } = rest;
3190
3280
  const componentProps = {
3191
3281
  ref,
3192
- ...restWithoutChildren,
3282
+ ...restForComponent,
3283
+ ...disabled !== void 0 ? { disabled } : {},
3284
+ ...checked !== void 0 ? { checked } : {},
3193
3285
  style: finalStyle,
3194
3286
  ...!isNative && className ? { className } : {},
3195
3287
  // onPressIn / onPressOut are React Native-only.
3196
3288
  // On web, onPointerDown/Up drive the pressed state instead (covers mouse + touch).
3197
- ...!isNative ? { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave } : { onPressIn: handlePressIn, onPressOut: handlePressOut },
3289
+ ...!isNative ? { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave, onPointerCancel: handlePointerLeave } : { onPressIn: handlePressIn, onPressOut: handlePressOut },
3198
3290
  onMouseEnter: handleMouseEnter,
3199
3291
  onMouseLeave: handleMouseLeave,
3200
3292
  onFocus: handleFocus,
@@ -3213,6 +3305,7 @@ var DarkWrapper = import_react4.default.forwardRef(
3213
3305
  const needsWidth = hasResponsiveBuckets(resolvedStyle);
3214
3306
  const width = useConditionalWidth(needsWidth);
3215
3307
  const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS2;
3308
+ const screens = getGlobalScreens();
3216
3309
  const isNonStringComponent = typeof Component !== "string";
3217
3310
  const computedStyle = (0, import_react4.useMemo)(() => {
3218
3311
  if (isWeb) return {};
@@ -3220,7 +3313,7 @@ var DarkWrapper = import_react4.default.forwardRef(
3220
3313
  stripInternalMarkers(s);
3221
3314
  if (isNonStringComponent) stripWebOnlyProps(s);
3222
3315
  return s;
3223
- }, [resolvedStyle, isDark, width, isNonStringComponent]);
3316
+ }, [resolvedStyle, isDark, width, screens, isNonStringComponent]);
3224
3317
  const skipComputedInline = isWeb || !isNative && typeof Component === "string";
3225
3318
  const finalStyle = skipComputedInline ? styleProp ?? void 0 : styleProp ? Array.isArray(styleProp) ? Object.assign({}, computedStyle, ...styleProp) : { ...computedStyle, ...styleProp } : computedStyle;
3226
3319
  const props = { ref, ...rest, style: finalStyle };
@@ -3290,8 +3383,8 @@ function getWebTag(type, props) {
3290
3383
  _cache.set(obj, null);
3291
3384
  return null;
3292
3385
  }
3293
- if (name === "TextInput" && props) {
3294
- const multiline = props.multiline === true || Number(props.numberOfLines) > 1;
3386
+ if (name === "TextInput") {
3387
+ const multiline = !!props && (props.multiline === true || Number(props.numberOfLines) > 1);
3295
3388
  return multiline ? "textarea" : "input";
3296
3389
  }
3297
3390
  if (_cache.has(obj)) return _cache.get(obj);
@@ -3328,8 +3421,7 @@ var _rnOnlyProps = /* @__PURE__ */ new Set([
3328
3421
  "shouldRasterizeIOS",
3329
3422
  "focusable",
3330
3423
  "hasTVPreferredFocus",
3331
- "pointerEvents",
3332
- // RN and HTML values differ
3424
+ // 'pointerEvents' is handled in transformToWebProps (mapped to CSS style)
3333
3425
  // Text
3334
3426
  "selectable",
3335
3427
  "allowFontScaling",
@@ -3355,7 +3447,7 @@ var _rnOnlyProps = /* @__PURE__ */ new Set([
3355
3447
  "showsVerticalScrollIndicator",
3356
3448
  "showsHorizontalScrollIndicator",
3357
3449
  "contentContainerStyle",
3358
- "horizontal",
3450
+ // 'horizontal' is handled in transformToWebProps (converted to CSS overflow-x).
3359
3451
  "keyboardShouldPersistTaps",
3360
3452
  "keyboardDismissMode",
3361
3453
  "pagingEnabled",
@@ -3439,6 +3531,8 @@ function transformToWebProps(originalName, tag, props) {
3439
3531
  const isPressable = _pressableNames.has(originalName);
3440
3532
  const isTextInput = originalName === "TextInput";
3441
3533
  const isImage = originalName === "Image" || originalName === "ImageBackground";
3534
+ const isScrollable = originalName === "ScrollView" || originalName === "FlatList" || originalName === "SectionList";
3535
+ let pendingStyle = null;
3442
3536
  for (const [k, v] of Object.entries(props)) {
3443
3537
  if (_rnOnlyProps.has(k)) continue;
3444
3538
  if (k === "onPress") {
@@ -3482,9 +3576,19 @@ function transformToWebProps(originalName, tag, props) {
3482
3576
  continue;
3483
3577
  }
3484
3578
  }
3579
+ if (isScrollable && k === "horizontal") {
3580
+ if (v) pendingStyle = { display: "flex", flexDirection: "row", overflowX: "auto" };
3581
+ continue;
3582
+ }
3583
+ if (k === "pointerEvents") {
3584
+ if (v === "none" || v === "auto") pendingStyle = { ...pendingStyle ?? {}, pointerEvents: v };
3585
+ continue;
3586
+ }
3485
3587
  if (isImage) {
3486
3588
  if (k === "source") {
3487
- if (v && typeof v === "object" && "uri" in v) {
3589
+ if (typeof v === "string") {
3590
+ out.src = v;
3591
+ } else if (v && typeof v === "object" && "uri" in v) {
3488
3592
  out.src = v.uri;
3489
3593
  if (v.headers) out["crossOrigin"] = "anonymous";
3490
3594
  }
@@ -3500,7 +3604,7 @@ function transformToWebProps(originalName, tag, props) {
3500
3604
  out.style = { ...existing, objectFit: v };
3501
3605
  continue;
3502
3606
  }
3503
- if (k === "alt" || k === "defaultSource") continue;
3607
+ if (k === "defaultSource") continue;
3504
3608
  }
3505
3609
  if (k === "style") {
3506
3610
  if (Array.isArray(v)) {
@@ -3512,6 +3616,9 @@ function transformToWebProps(originalName, tag, props) {
3512
3616
  }
3513
3617
  out[k] = v;
3514
3618
  }
3619
+ if (pendingStyle) {
3620
+ out.style = out.style ? { ...pendingStyle, ...out.style } : pendingStyle;
3621
+ }
3515
3622
  if (isPressable && !out.role) out.role = "button";
3516
3623
  if (isImage && tag === "img" && out.alt == null) out.alt = "";
3517
3624
  return out;
@@ -3521,6 +3628,10 @@ function transformToWebProps(originalName, tag, props) {
3521
3628
  var _cachedDefaultFont = void 0;
3522
3629
  var _defaultFontVersion = 0;
3523
3630
  var _lastFontVersion = -1;
3631
+ function _invalidateDefaultFontCache() {
3632
+ _defaultFontVersion++;
3633
+ }
3634
+ onConfigChange(_invalidateDefaultFontCache);
3524
3635
  function getCachedDefaultFont() {
3525
3636
  if (_lastFontVersion !== _defaultFontVersion) {
3526
3637
  _cachedDefaultFont = getDefaultFontFamily();
@@ -3596,7 +3707,7 @@ function stripWebOnlyInlineProps(s) {
3596
3707
  }
3597
3708
  }
3598
3709
  function processElement(type, rawProps, key, isStaticChildren) {
3599
- if (typeof type === "symbol") return makeElement(isStaticChildren, type, rawProps ?? {}, key);
3710
+ if (type === null || typeof type === "symbol") return makeElement(isStaticChildren, type, rawProps ?? {}, key);
3600
3711
  const webTag = isWeb ? getWebTag(type, rawProps ?? void 0) : null;
3601
3712
  const effectiveType = webTag ?? type;
3602
3713
  const originalName = webTag ? type?.displayName ?? type?.name : void 0;
@@ -3654,7 +3765,7 @@ function processElement(type, rawProps, key, isStaticChildren) {
3654
3765
  }
3655
3766
  return makeElement(isStaticChildren, effectiveType, {
3656
3767
  ...passProps,
3657
- style: finalStyle,
3768
+ ...finalStyle !== void 0 ? { style: finalStyle } : {},
3658
3769
  ...!isNative && classStr ? { className: normalizeClassString(classStr) } : {}
3659
3770
  }, key);
3660
3771
  }
@@ -3668,7 +3779,7 @@ function jsxs(type, props, key) {
3668
3779
  // src/jsx-dev-runtime.tsx
3669
3780
  function jsxDEV(type, props, key, isStaticChildren, source, self) {
3670
3781
  const element = (isStaticChildren ? jsxs : jsx)(type, props, key);
3671
- if (source && element && typeof element === "object" && "_source" in Object.getPrototypeOf(element ?? {})) {
3782
+ if (source && element && typeof element === "object") {
3672
3783
  element._source = source;
3673
3784
  element._self = self;
3674
3785
  }