@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.js CHANGED
@@ -592,14 +592,15 @@ var defaultTheme = {
592
592
  normal: 1.5,
593
593
  relaxed: 1.625,
594
594
  loose: 2,
595
- 3: 12,
596
- 4: 16,
597
- 5: 20,
598
- 6: 24,
599
- 7: 28,
600
- 8: 32,
601
- 9: 36,
602
- 10: 40
595
+ // Pixel values stored as strings; styleValueToCSS passes them through, toNativeValue strips the unit
596
+ 3: "12px",
597
+ 4: "16px",
598
+ 5: "20px",
599
+ 6: "24px",
600
+ 7: "28px",
601
+ 8: "32px",
602
+ 9: "36px",
603
+ 10: "40px"
603
604
  },
604
605
  letterSpacing: {
605
606
  tighter: -0.8,
@@ -620,9 +621,12 @@ var defaultTheme = {
620
621
  },
621
622
  flex: {
622
623
  1: 1,
623
- auto: 1,
624
- initial: 0,
625
- none: 0
624
+ auto: "auto",
625
+ // CSS: flex: auto = 1 1 auto; native: mapped to 1 in resolver
626
+ initial: "initial",
627
+ // CSS: flex: initial = 0 1 auto; native: mapped to 1
628
+ none: "none"
629
+ // CSS: flex: none = 0 0 auto; native: mapped to 0
626
630
  },
627
631
  shadow: {
628
632
  sm: {
@@ -692,6 +696,7 @@ var defaultTheme = {
692
696
  var LRUCache = class {
693
697
  constructor(capacity = 1e4) {
694
698
  this.capacity = capacity;
699
+ if (capacity <= 0) throw new Error("LRUCache: capacity must be a positive integer");
695
700
  this.cache = /* @__PURE__ */ new Map();
696
701
  }
697
702
  get(key) {
@@ -711,11 +716,7 @@ var LRUCache = class {
711
716
  return this;
712
717
  }
713
718
  has(key) {
714
- if (!this.cache.has(key)) return false;
715
- const value = this.cache.get(key);
716
- this.cache.delete(key);
717
- this.cache.set(key, value);
718
- return true;
719
+ return this.cache.has(key);
719
720
  }
720
721
  delete(key) {
721
722
  return this.cache.delete(key);
@@ -730,7 +731,7 @@ var LRUCache = class {
730
731
 
731
732
  // src/core/platform.ts
732
733
  var isWeb = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
733
- var isNative = !isWeb && (typeof globalThis.HermesInternal !== "undefined" || typeof globalThis.__fbBatchedBridge !== "undefined" || typeof navigator !== "undefined" && navigator.product === "ReactNative");
734
+ var isNative = !isWeb && (typeof globalThis.HermesInternal !== "undefined" || typeof globalThis.__fbBatchedBridge !== "undefined" || typeof navigator !== "undefined" && navigator.product === "ReactNative" || typeof globalThis.__REACT_NATIVE__ !== "undefined");
734
735
  var _cssGenMode = false;
735
736
  function getEffectiveIsWeb() {
736
737
  return isWeb || _cssGenMode || !isNative;
@@ -743,7 +744,7 @@ function toNativeValue(raw) {
743
744
  return raw;
744
745
  }
745
746
  function escapeCSSSelector(cls) {
746
- return cls.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
747
+ return cls.replace(/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
747
748
  }
748
749
 
749
750
  // src/core/registry.ts
@@ -820,6 +821,10 @@ function _invalidate() {
820
821
  _responsiveNames = null;
821
822
  }
822
823
  function registerModifier(name, def) {
824
+ if (process.env.NODE_ENV !== "production" && name in BUILTIN_MODIFIERS) {
825
+ console.warn(`[kbach] registerModifier: "${name}" is a built-in modifier and cannot be overridden. Use a different name.`);
826
+ return;
827
+ }
823
828
  _pluginModifiers[name] = def;
824
829
  _invalidate();
825
830
  }
@@ -895,7 +900,7 @@ function resolveColor(value, colors, isArbitrary) {
895
900
  }
896
901
  if (!hex) return null;
897
902
  if (!opacityPart) {
898
- if (!isWeb && hex.startsWith("#") && hex.length === 9) {
903
+ if (!getEffectiveIsWeb() && hex.startsWith("#") && hex.length === 9) {
899
904
  return hexToRgba(hex, Math.round(parseInt(hex.slice(7, 9), 16) / 255 * 1e3) / 1e3);
900
905
  }
901
906
  return hex;
@@ -929,7 +934,10 @@ function resolveSpacing(value, negative, spacing, isArbitrary) {
929
934
  const resolved = getEffectiveIsWeb() ? value : toNativeValue(value);
930
935
  if (negative) {
931
936
  if (typeof resolved === "number") return -resolved;
932
- if (typeof resolved === "string" && /^\d/.test(resolved)) return `-${resolved}`;
937
+ if (typeof resolved === "string") {
938
+ if (/^\d/.test(resolved)) return `-${resolved}`;
939
+ if (/^(calc|var|min|max|clamp|env)\s*\(/.test(resolved)) return `calc(-1 * (${resolved}))`;
940
+ }
933
941
  }
934
942
  return resolved;
935
943
  }
@@ -1435,7 +1443,11 @@ var RESOLVERS = {
1435
1443
  const color = resolveColor(value, colors, false);
1436
1444
  if (color) return { borderColor: color };
1437
1445
  const w = borderWidth[value] ?? spacing[value];
1438
- if (w !== void 0) return { borderWidth: typeof w === "number" ? w : parseFloat(String(w)) };
1446
+ if (w !== void 0) {
1447
+ const numW = typeof w === "number" ? w : parseFloat(String(w));
1448
+ if (isNaN(numW)) return null;
1449
+ return { borderWidth: numW };
1450
+ }
1439
1451
  return null;
1440
1452
  },
1441
1453
  "border-t": ({ value, isArbitrary }, { colors, borderWidth }) => {
@@ -2138,7 +2150,7 @@ var RESOLVERS = {
2138
2150
  },
2139
2151
  // ── Tint color (native-only, for Image and icon components) ──────────────
2140
2152
  tint: ({ value, isArbitrary }, { colors }) => {
2141
- if (isWeb) return null;
2153
+ if (getEffectiveIsWeb()) return null;
2142
2154
  const color = resolveColor(value, colors, isArbitrary);
2143
2155
  return color ? { tintColor: color } : null;
2144
2156
  },
@@ -2210,7 +2222,15 @@ var RESOLVERS = {
2210
2222
  const v = parseFloat(value);
2211
2223
  return isNaN(v) ? null : { flex: v };
2212
2224
  }
2213
- if (value in flex) return { flex: flex[value] };
2225
+ if (value in flex) {
2226
+ const v = flex[value];
2227
+ if (typeof v === "string" && !getEffectiveIsWeb()) {
2228
+ if (v === "auto" || v === "initial") return { flex: 1 };
2229
+ if (v === "none") return { flex: 0 };
2230
+ return null;
2231
+ }
2232
+ return { flex: v };
2233
+ }
2214
2234
  const n = parseFloat(value);
2215
2235
  return isNaN(n) ? null : { flex: n };
2216
2236
  },
@@ -2313,7 +2333,9 @@ var RESOLVERS = {
2313
2333
  leading: ({ value, isArbitrary }, { lineHeight }) => {
2314
2334
  if (isArbitrary) return { lineHeight: getEffectiveIsWeb() ? value : toNativeValue(value) };
2315
2335
  const v = lineHeight[value];
2316
- return v !== void 0 ? { lineHeight: v } : null;
2336
+ if (v === void 0) return null;
2337
+ if (typeof v === "string" && !getEffectiveIsWeb()) return { lineHeight: toNativeValue(v) };
2338
+ return { lineHeight: v };
2317
2339
  },
2318
2340
  // ── Letter spacing ─────────────────────────────────────────────────────────
2319
2341
  tracking: ({ value, isArbitrary }, { letterSpacing }) => {
@@ -2389,7 +2411,7 @@ var RESOLVERS = {
2389
2411
  transition: ({ value }) => {
2390
2412
  if (!getEffectiveIsWeb()) return null;
2391
2413
  const presets = {
2392
- "": "color 150ms, background-color 150ms, border-color 150ms, opacity 150ms, box-shadow 150ms",
2414
+ "": "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",
2393
2415
  all: "all 150ms",
2394
2416
  none: "none",
2395
2417
  colors: "color 150ms, background-color 150ms, border-color 150ms",
@@ -2402,11 +2424,13 @@ var RESOLVERS = {
2402
2424
  },
2403
2425
  duration: ({ value, isArbitrary }) => {
2404
2426
  if (!getEffectiveIsWeb()) return null;
2427
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2405
2428
  const ms = isArbitrary ? value : `${value}ms`;
2406
2429
  return { transitionDuration: ms };
2407
2430
  },
2408
2431
  delay: ({ value, isArbitrary }) => {
2409
2432
  if (!getEffectiveIsWeb()) return null;
2433
+ if (!isArbitrary && isNaN(parseFloat(value))) return null;
2410
2434
  const ms = isArbitrary ? value : `${value}ms`;
2411
2435
  return { transitionDelay: ms };
2412
2436
  },
@@ -2449,14 +2473,20 @@ var RESOLVERS = {
2449
2473
  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}`
2450
2474
  };
2451
2475
  },
2452
- // ── Space between (uses CSS gap or margin fallback) ────────────────────────
2476
+ // ── Space between ─────────────────────────────────────────────────────────
2477
+ // On web: emits __spaceX/__spaceY markers → resolver generates > * + * CSS rules.
2478
+ // On native (RN 0.71+): uses columnGap/rowGap directly.
2453
2479
  "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
2454
2480
  const v = resolveSpacing(value, negative, spacing, isArbitrary);
2455
- return v !== null ? { columnGap: v } : null;
2481
+ if (v === null) return null;
2482
+ if (getEffectiveIsWeb()) return { __spaceX: v };
2483
+ return { columnGap: v };
2456
2484
  },
2457
2485
  "space-y": ({ value, negative, isArbitrary }, { spacing }) => {
2458
2486
  const v = resolveSpacing(value, negative, spacing, isArbitrary);
2459
- return v !== null ? { rowGap: v } : null;
2487
+ if (v === null) return null;
2488
+ if (getEffectiveIsWeb()) return { __spaceY: v };
2489
+ return { rowGap: v };
2460
2490
  }
2461
2491
  };
2462
2492
  function resolveUtility(parsed, theme) {
@@ -2522,12 +2552,28 @@ function parseClass(className) {
2522
2552
  negative = true;
2523
2553
  remaining = remaining.slice(1);
2524
2554
  }
2525
- const bracketStart = remaining.lastIndexOf("-[");
2526
- if (bracketStart > 0 && remaining.endsWith("]")) {
2527
- const utility = remaining.slice(0, bracketStart);
2528
- const value = remaining.slice(bracketStart + 2, -1).replace(/_/g, " ");
2555
+ let bracketDepth = 0;
2556
+ let bracketStart = -1;
2557
+ for (let i = remaining.length - 1; i >= 0; i--) {
2558
+ if (remaining[i] === "]") bracketDepth++;
2559
+ else if (remaining[i] === "[") {
2560
+ bracketDepth--;
2561
+ if (bracketDepth === 0) {
2562
+ bracketStart = i;
2563
+ break;
2564
+ }
2565
+ }
2566
+ }
2567
+ if (bracketStart > 0 && remaining[bracketStart - 1] === "-" && remaining.endsWith("]")) {
2568
+ const utility = remaining.slice(0, bracketStart - 1);
2569
+ const value = remaining.slice(bracketStart + 1, -1).replace(/_/g, " ");
2529
2570
  return { original: trimmed, modifiers, negative, important, utility, value, isArbitrary: true };
2530
2571
  }
2572
+ if (remaining.endsWith("]") && bracketStart === -1) {
2573
+ if (process.env.NODE_ENV !== "production") {
2574
+ console.warn(`[kbach] Malformed class string: "${trimmed}" \u2014 unbalanced brackets`);
2575
+ }
2576
+ }
2531
2577
  if (remaining.startsWith("[") && remaining.endsWith("]")) {
2532
2578
  const value = remaining.slice(1, -1).replace(/_/g, " ");
2533
2579
  return { original: trimmed, modifiers, negative, important, utility: "", value, isArbitrary: true };
@@ -2562,6 +2608,7 @@ function splitClassTokens(classString) {
2562
2608
  const tokens = [];
2563
2609
  let current = "";
2564
2610
  let depth = 0;
2611
+ let parenDepth = 0;
2565
2612
  for (let i = 0; i < classString.length; i++) {
2566
2613
  const ch = classString[i];
2567
2614
  if (ch === "[") {
@@ -2574,8 +2621,18 @@ function splitClassTokens(classString) {
2574
2621
  current += ch;
2575
2622
  continue;
2576
2623
  }
2624
+ if (ch === "(") {
2625
+ parenDepth++;
2626
+ current += ch;
2627
+ continue;
2628
+ }
2629
+ if (ch === ")") {
2630
+ parenDepth--;
2631
+ current += ch;
2632
+ continue;
2633
+ }
2577
2634
  if (/\s/.test(ch)) {
2578
- if (depth === 0) {
2635
+ if (depth === 0 && parenDepth === 0) {
2579
2636
  if (current) {
2580
2637
  tokens.push(current);
2581
2638
  current = "";
@@ -2649,7 +2706,7 @@ function setGlobalWidth(width) {
2649
2706
  const store = getStore();
2650
2707
  if (store.width === width) return;
2651
2708
  store.width = width;
2652
- for (const l of store.listeners) l(width);
2709
+ for (const l of store.listeners) l();
2653
2710
  }
2654
2711
  function getGlobalWidth() {
2655
2712
  return getStore().width;
@@ -2722,7 +2779,8 @@ function injectRule(rule) {
2722
2779
  if (_injectedRules.has(rule)) return;
2723
2780
  _injectedRules.set(rule, true);
2724
2781
  try {
2725
- getStyleEl().sheet?.insertRule(rule, getStyleEl().sheet.cssRules.length);
2782
+ const sheet = getStyleEl().sheet;
2783
+ if (sheet) sheet.insertRule(rule, sheet.cssRules.length);
2726
2784
  } catch {
2727
2785
  }
2728
2786
  }
@@ -2738,24 +2796,41 @@ function injectGlobalStyles(theme) {
2738
2796
  if (typeof document === "undefined" || isRuntimeCSSDisabled()) return;
2739
2797
  const rules = [BASE_RESET];
2740
2798
  const ff = theme.fontFamily;
2799
+ if (!ff) return;
2741
2800
  if (ff.sans && ff.sans !== "System") {
2742
2801
  const family = Array.isArray(ff.sans) ? ff.sans.join(", ") : ff.sans;
2743
2802
  rules.push(`body { font-family: ${family}; }`);
2744
2803
  }
2745
2804
  getGlobalStyleEl().textContent = rules.join("\n");
2746
2805
  }
2747
- function buildDivideDecls(styles) {
2806
+ function buildDivideDecls(styles, forceImportant) {
2807
+ const imp = forceImportant ? " !important" : "";
2748
2808
  const parts = [];
2749
2809
  if ("__divideX" in styles) {
2750
2810
  const w = Number(styles.__divideX);
2751
- parts.push(`border-left-width: ${w}px`, "border-right-width: 0px");
2811
+ parts.push(`border-left-width: ${w}px${imp}`, `border-right-width: 0px${imp}`);
2752
2812
  }
2753
2813
  if ("__divideY" in styles) {
2754
2814
  const w = Number(styles.__divideY);
2755
- parts.push(`border-top-width: ${w}px`, "border-bottom-width: 0px");
2815
+ parts.push(`border-top-width: ${w}px${imp}`, `border-bottom-width: 0px${imp}`);
2816
+ }
2817
+ if ("__divideColor" in styles) parts.push(`border-color: ${String(styles.__divideColor)}${imp}`);
2818
+ if ("__divideStyle" in styles) parts.push(`border-style: ${String(styles.__divideStyle)}${imp}`);
2819
+ return parts.join("; ");
2820
+ }
2821
+ function buildSpaceDecls(styles, forceImportant) {
2822
+ const imp = forceImportant ? " !important" : "";
2823
+ const parts = [];
2824
+ if ("__spaceX" in styles) {
2825
+ const v = styles.__spaceX;
2826
+ const val = typeof v === "number" ? `${v}px` : String(v);
2827
+ parts.push(`margin-left: ${val}${imp}`);
2828
+ }
2829
+ if ("__spaceY" in styles) {
2830
+ const v = styles.__spaceY;
2831
+ const val = typeof v === "number" ? `${v}px` : String(v);
2832
+ parts.push(`margin-top: ${val}${imp}`);
2756
2833
  }
2757
- if ("__divideColor" in styles) parts.push(`border-color: ${String(styles.__divideColor)}`);
2758
- if ("__divideStyle" in styles) parts.push(`border-style: ${String(styles.__divideStyle)}`);
2759
2834
  return parts.join("; ");
2760
2835
  }
2761
2836
  var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
@@ -2775,7 +2850,9 @@ var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
2775
2850
  "__divideY",
2776
2851
  "__divideColor",
2777
2852
  "__divideStyle",
2778
- "__keyframe"
2853
+ "__keyframe",
2854
+ "__spaceX",
2855
+ "__spaceY"
2779
2856
  ]);
2780
2857
  var CSS_UNITLESS = /* @__PURE__ */ new Set([
2781
2858
  "opacity",
@@ -2828,10 +2905,12 @@ function buildClassCSSRules(cls, bucketKey, styles, darkMode, important, screens
2828
2905
  rules.push(styles.__keyframe);
2829
2906
  }
2830
2907
  const isDivide = "__divideX" in styles || "__divideY" in styles || "__divideColor" in styles || "__divideStyle" in styles;
2908
+ const isSpace = "__spaceX" in styles || "__spaceY" in styles;
2909
+ const isChildCombinator = isDivide || isSpace;
2831
2910
  const escaped = escapeCSSSelector(cls);
2832
- const childSuffix = isDivide ? " > * + *" : "";
2911
+ const childSuffix = isChildCombinator ? " > * + *" : "";
2833
2912
  if (bucketKey === "base") {
2834
- const decls2 = isDivide ? buildDivideDecls(styles) : styleValueToCSS(styles, important);
2913
+ const decls2 = isDivide ? buildDivideDecls(styles, important) : isSpace ? buildSpaceDecls(styles, important) : styleValueToCSS(styles, important);
2835
2914
  if (!decls2) return rules;
2836
2915
  rules.push(`.${escaped}${childSuffix} { ${decls2} }`);
2837
2916
  return rules;
@@ -2872,7 +2951,7 @@ function buildClassCSSRules(cls, bucketKey, styles, darkMode, important, screens
2872
2951
  const ancestorPrefix = ancestorParts.join("");
2873
2952
  const dirPrefix = dirParts.join("");
2874
2953
  const selector = `${dirPrefix}${ancestorPrefix}${elementSelector}`;
2875
- const decls = isDivide ? buildDivideDecls(styles) : styleValueToCSS(styles, needsImportant);
2954
+ const decls = isDivide ? buildDivideDecls(styles, needsImportant) : isSpace ? buildSpaceDecls(styles, needsImportant) : styleValueToCSS(styles, needsImportant);
2876
2955
  if (!decls) return rules;
2877
2956
  let rule;
2878
2957
  if (darkScheme === "dark") {
@@ -2901,7 +2980,7 @@ function resolve(classString, theme, darkMode = "attribute") {
2901
2980
  const cached = cache.get(cacheKey);
2902
2981
  if (cached) return cached;
2903
2982
  const result = {};
2904
- const onWeb = typeof document !== "undefined";
2983
+ const onWeb = isWeb;
2905
2984
  for (const parsed of parseClasses(classString)) {
2906
2985
  const styles = resolveUtility(parsed, theme);
2907
2986
  if (!styles) {
@@ -2974,6 +3053,10 @@ function clearCache() {
2974
3053
  _styleEl.remove();
2975
3054
  _styleEl = null;
2976
3055
  }
3056
+ if (_globalStyleEl) {
3057
+ _globalStyleEl.remove();
3058
+ _globalStyleEl = null;
3059
+ }
2977
3060
  }
2978
3061
 
2979
3062
  // src/core/config.ts
@@ -3045,19 +3128,24 @@ function buildConfig(userConfig) {
3045
3128
  theme = deepMerge(theme, userConfig.theme);
3046
3129
  }
3047
3130
  if (userConfig.extend) {
3048
- const { theme: nestedTheme, ...directKeys } = userConfig.extend;
3131
+ const extendConfig = userConfig.extend;
3132
+ const { theme: nestedTheme, ...directKeys } = extendConfig;
3049
3133
  const extSources = [];
3050
3134
  if (nestedTheme) extSources.push(nestedTheme);
3051
3135
  if (Object.keys(directKeys).length) extSources.push(directKeys);
3052
3136
  for (const ext of extSources) {
3053
3137
  for (const [key, value] of Object.entries(ext)) {
3138
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
3139
+ if (process.env.NODE_ENV !== "production") {
3140
+ console.warn(`[kbach] config.extend.${key} must be a plain object, got ${typeof value}. Skipping.`);
3141
+ }
3142
+ continue;
3143
+ }
3054
3144
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
3055
3145
  theme[key] = deepMerge(
3056
3146
  theme[key] ?? {},
3057
3147
  value
3058
3148
  );
3059
- } else if (value !== void 0) {
3060
- theme[key] = value;
3061
3149
  }
3062
3150
  }
3063
3151
  }
@@ -3068,8 +3156,10 @@ function buildConfig(userConfig) {
3068
3156
  theme,
3069
3157
  plugins: userConfig.plugins ?? []
3070
3158
  };
3071
- clearPluginUtilities();
3072
- clearPluginModifiers();
3159
+ if (userConfig.plugins !== void 0) {
3160
+ clearPluginUtilities();
3161
+ clearPluginModifiers();
3162
+ }
3073
3163
  const _store = getConfigStore();
3074
3164
  for (const k of Object.keys(_store.customVariants)) delete _store.customVariants[k];
3075
3165
  const pluginAPI = makePluginAPI(resolved.theme);
@@ -3087,7 +3177,7 @@ function _selectorToModifierDef(selector) {
3087
3177
  if (s.startsWith("@media ")) {
3088
3178
  return { mediaQuery: s.slice(7), jsBehavior: "css-only", forcesImportant: true };
3089
3179
  }
3090
- if (s.startsWith("::") || s.startsWith(":")) {
3180
+ if (s.startsWith("::") || s.startsWith(":") && !s.includes(" ") && !s.includes("&") && !s.includes("(")) {
3091
3181
  return { pseudo: s, jsBehavior: "css-only", forcesImportant: true };
3092
3182
  }
3093
3183
  return { ancestorSelector: s.endsWith(" ") ? s : `${s} `, jsBehavior: "css-only", forcesImportant: true };
@@ -3100,8 +3190,8 @@ function makePluginAPI(theme) {
3100
3190
  },
3101
3191
  addVariant(name, selectorOrDef) {
3102
3192
  const def = typeof selectorOrDef === "string" ? _selectorToModifierDef(selectorOrDef) : selectorOrDef;
3103
- const selector = typeof selectorOrDef === "string" ? selectorOrDef : selectorOrDef.pseudo ?? selectorOrDef.mediaQuery ?? selectorOrDef.ancestorSelector ?? name;
3104
- customVariants[name] = selector;
3193
+ const selector = typeof selectorOrDef === "string" ? selectorOrDef : selectorOrDef.pseudo ?? selectorOrDef.mediaQuery ?? selectorOrDef.ancestorSelector ?? null;
3194
+ if (selector !== null && selector !== name) customVariants[name] = selector;
3105
3195
  registerModifier(name, def);
3106
3196
  },
3107
3197
  theme(path, defaultValue) {
@@ -3162,7 +3252,7 @@ var KEY = "__kbach_dark_store__";
3162
3252
  function getStore2() {
3163
3253
  const g = globalThis;
3164
3254
  if (!g[KEY]) {
3165
- g[KEY] = { isDark: false, lastNotified: false, subscribers: /* @__PURE__ */ new Set() };
3255
+ g[KEY] = { isDark: false, subscribers: /* @__PURE__ */ new Set() };
3166
3256
  }
3167
3257
  return g[KEY];
3168
3258
  }
@@ -3171,9 +3261,8 @@ function syncGlobalDarkMode(isDark) {
3171
3261
  }
3172
3262
  function setGlobalDarkMode(isDark) {
3173
3263
  const store = getStore2();
3264
+ if (store.isDark === isDark) return;
3174
3265
  store.isDark = isDark;
3175
- if (store.lastNotified === isDark) return;
3176
- store.lastNotified = isDark;
3177
3266
  for (const sub of store.subscribers) sub();
3178
3267
  }
3179
3268
  function getGlobalDarkMode() {
@@ -3461,15 +3550,31 @@ function ThemeToggle({
3461
3550
  }
3462
3551
 
3463
3552
  // src/styled.tsx
3464
- var import_react6 = __toESM(require("react"));
3553
+ var import_react7 = __toESM(require("react"));
3465
3554
 
3466
- // src/useGlobalWidth.ts
3555
+ // src/useGlobalDarkMode.ts
3467
3556
  var import_react5 = __toESM(require("react"));
3468
3557
  var useSyncExternalStore = import_react5.default.useSyncExternalStore ?? function useSyncExternalStoreFallback(subscribe, getSnapshot, _getServerSnapshot) {
3469
3558
  const [, forceUpdate] = import_react5.default.useReducer((n) => n + 1, 0);
3470
3559
  import_react5.default.useEffect(() => subscribe(forceUpdate), [subscribe]);
3471
3560
  return getSnapshot();
3472
3561
  };
3562
+ function useGlobalDarkMode() {
3563
+ return useSyncExternalStore(
3564
+ subscribeGlobalDarkMode,
3565
+ getGlobalDarkMode,
3566
+ () => false
3567
+ // SSR: default light
3568
+ );
3569
+ }
3570
+
3571
+ // src/useGlobalWidth.ts
3572
+ var import_react6 = __toESM(require("react"));
3573
+ var useSyncExternalStore2 = import_react6.default.useSyncExternalStore ?? function useSyncExternalStoreFallback2(subscribe, getSnapshot, _getServerSnapshot) {
3574
+ const [, forceUpdate] = import_react6.default.useReducer((n) => n + 1, 0);
3575
+ import_react6.default.useEffect(() => subscribe(forceUpdate), [subscribe]);
3576
+ return getSnapshot();
3577
+ };
3473
3578
  var NOOP_SUB = (_) => () => {
3474
3579
  };
3475
3580
  var ZERO_SNAP = () => 0;
@@ -3503,14 +3608,14 @@ function getWebWidth() {
3503
3608
  return typeof window !== "undefined" ? window.innerWidth : 0;
3504
3609
  }
3505
3610
  function useGlobalWidth() {
3506
- return useSyncExternalStore(
3611
+ return useSyncExternalStore2(
3507
3612
  isWeb ? subscribeWebWidth : subscribeGlobalWidth,
3508
3613
  isWeb ? getWebWidth : getGlobalWidth,
3509
3614
  ZERO_SNAP
3510
3615
  );
3511
3616
  }
3512
3617
  function useConditionalWidth(active) {
3513
- return useSyncExternalStore(
3618
+ return useSyncExternalStore2(
3514
3619
  active ? isWeb ? subscribeWebWidth : subscribeGlobalWidth : NOOP_SUB,
3515
3620
  active ? isWeb ? getWebWidth : getGlobalWidth : ZERO_SNAP,
3516
3621
  ZERO_SNAP
@@ -3575,8 +3680,8 @@ function getWebTag(type, props) {
3575
3680
  _cache.set(obj, null);
3576
3681
  return null;
3577
3682
  }
3578
- if (name === "TextInput" && props) {
3579
- const multiline = props.multiline === true || Number(props.numberOfLines) > 1;
3683
+ if (name === "TextInput") {
3684
+ const multiline = !!props && (props.multiline === true || Number(props.numberOfLines) > 1);
3580
3685
  return multiline ? "textarea" : "input";
3581
3686
  }
3582
3687
  if (_cache.has(obj)) return _cache.get(obj);
@@ -3613,8 +3718,7 @@ var _rnOnlyProps = /* @__PURE__ */ new Set([
3613
3718
  "shouldRasterizeIOS",
3614
3719
  "focusable",
3615
3720
  "hasTVPreferredFocus",
3616
- "pointerEvents",
3617
- // RN and HTML values differ
3721
+ // 'pointerEvents' is handled in transformToWebProps (mapped to CSS style)
3618
3722
  // Text
3619
3723
  "selectable",
3620
3724
  "allowFontScaling",
@@ -3640,7 +3744,7 @@ var _rnOnlyProps = /* @__PURE__ */ new Set([
3640
3744
  "showsVerticalScrollIndicator",
3641
3745
  "showsHorizontalScrollIndicator",
3642
3746
  "contentContainerStyle",
3643
- "horizontal",
3747
+ // 'horizontal' is handled in transformToWebProps (converted to CSS overflow-x).
3644
3748
  "keyboardShouldPersistTaps",
3645
3749
  "keyboardDismissMode",
3646
3750
  "pagingEnabled",
@@ -3724,6 +3828,8 @@ function transformToWebProps(originalName, tag, props) {
3724
3828
  const isPressable = _pressableNames.has(originalName);
3725
3829
  const isTextInput = originalName === "TextInput";
3726
3830
  const isImage = originalName === "Image" || originalName === "ImageBackground";
3831
+ const isScrollable = originalName === "ScrollView" || originalName === "FlatList" || originalName === "SectionList";
3832
+ let pendingStyle = null;
3727
3833
  for (const [k, v] of Object.entries(props)) {
3728
3834
  if (_rnOnlyProps.has(k)) continue;
3729
3835
  if (k === "onPress") {
@@ -3767,9 +3873,19 @@ function transformToWebProps(originalName, tag, props) {
3767
3873
  continue;
3768
3874
  }
3769
3875
  }
3876
+ if (isScrollable && k === "horizontal") {
3877
+ if (v) pendingStyle = { display: "flex", flexDirection: "row", overflowX: "auto" };
3878
+ continue;
3879
+ }
3880
+ if (k === "pointerEvents") {
3881
+ if (v === "none" || v === "auto") pendingStyle = { ...pendingStyle ?? {}, pointerEvents: v };
3882
+ continue;
3883
+ }
3770
3884
  if (isImage) {
3771
3885
  if (k === "source") {
3772
- if (v && typeof v === "object" && "uri" in v) {
3886
+ if (typeof v === "string") {
3887
+ out.src = v;
3888
+ } else if (v && typeof v === "object" && "uri" in v) {
3773
3889
  out.src = v.uri;
3774
3890
  if (v.headers) out["crossOrigin"] = "anonymous";
3775
3891
  }
@@ -3785,7 +3901,7 @@ function transformToWebProps(originalName, tag, props) {
3785
3901
  out.style = { ...existing, objectFit: v };
3786
3902
  continue;
3787
3903
  }
3788
- if (k === "alt" || k === "defaultSource") continue;
3904
+ if (k === "defaultSource") continue;
3789
3905
  }
3790
3906
  if (k === "style") {
3791
3907
  if (Array.isArray(v)) {
@@ -3797,6 +3913,9 @@ function transformToWebProps(originalName, tag, props) {
3797
3913
  }
3798
3914
  out[k] = v;
3799
3915
  }
3916
+ if (pendingStyle) {
3917
+ out.style = out.style ? { ...pendingStyle, ...out.style } : pendingStyle;
3918
+ }
3800
3919
  if (isPressable && !out.role) out.role = "button";
3801
3920
  if (isImage && tag === "img" && out.alt == null) out.alt = "";
3802
3921
  return out;
@@ -3804,7 +3923,7 @@ function transformToWebProps(originalName, tag, props) {
3804
3923
 
3805
3924
  // src/styled.tsx
3806
3925
  function styled(Component, baseClasses = "") {
3807
- const Styled = (0, import_react6.forwardRef)(
3926
+ const Styled = (0, import_react7.forwardRef)(
3808
3927
  (props, ref) => {
3809
3928
  const {
3810
3929
  kb: extraClasses,
@@ -3822,53 +3941,54 @@ function styled(Component, baseClasses = "") {
3822
3941
  } = props;
3823
3942
  const disabled = !!props.disabled;
3824
3943
  const checked = !!props.checked;
3825
- const { isDark, config } = useTheme();
3826
- const [pressed, setPressed] = (0, import_react6.useState)(false);
3827
- const [hovered, setHovered] = (0, import_react6.useState)(false);
3828
- const [focused, setFocused] = (0, import_react6.useState)(false);
3829
- const handlePressIn = (0, import_react6.useCallback)((e) => {
3944
+ const { config } = useTheme();
3945
+ const isDark = useGlobalDarkMode();
3946
+ const [pressed, setPressed] = (0, import_react7.useState)(false);
3947
+ const [hovered, setHovered] = (0, import_react7.useState)(false);
3948
+ const [focused, setFocused] = (0, import_react7.useState)(false);
3949
+ const handlePressIn = (0, import_react7.useCallback)((e) => {
3830
3950
  setPressed(true);
3831
3951
  onPressIn?.(e);
3832
3952
  }, [onPressIn]);
3833
- const handlePressOut = (0, import_react6.useCallback)((e) => {
3953
+ const handlePressOut = (0, import_react7.useCallback)((e) => {
3834
3954
  setPressed(false);
3835
3955
  onPressOut?.(e);
3836
3956
  }, [onPressOut]);
3837
- const handlePointerDown = (0, import_react6.useCallback)((e) => {
3957
+ const handlePointerDown = (0, import_react7.useCallback)((e) => {
3838
3958
  setPressed(true);
3839
3959
  onPointerDown?.(e);
3840
3960
  }, [onPointerDown]);
3841
- const handlePointerUp = (0, import_react6.useCallback)((e) => {
3961
+ const handlePointerUp = (0, import_react7.useCallback)((e) => {
3842
3962
  setPressed(false);
3843
3963
  onPointerUp?.(e);
3844
3964
  }, [onPointerUp]);
3845
- const handlePointerLeave = (0, import_react6.useCallback)((e) => {
3965
+ const handlePointerLeave = (0, import_react7.useCallback)((e) => {
3846
3966
  setPressed(false);
3847
3967
  onPointerLeave?.(e);
3848
3968
  }, [onPointerLeave]);
3849
- const handleMouseEnter = (0, import_react6.useCallback)((e) => {
3969
+ const handleMouseEnter = (0, import_react7.useCallback)((e) => {
3850
3970
  setHovered(true);
3851
3971
  onMouseEnter?.(e);
3852
3972
  }, [onMouseEnter]);
3853
- const handleMouseLeave = (0, import_react6.useCallback)((e) => {
3973
+ const handleMouseLeave = (0, import_react7.useCallback)((e) => {
3854
3974
  setHovered(false);
3855
3975
  onMouseLeave?.(e);
3856
3976
  }, [onMouseLeave]);
3857
- const handleFocus = (0, import_react6.useCallback)((e) => {
3977
+ const handleFocus = (0, import_react7.useCallback)((e) => {
3858
3978
  setFocused(true);
3859
3979
  onFocus?.(e);
3860
3980
  }, [onFocus]);
3861
- const handleBlur = (0, import_react6.useCallback)((e) => {
3981
+ const handleBlur = (0, import_react7.useCallback)((e) => {
3862
3982
  setFocused(false);
3863
3983
  onBlur?.(e);
3864
3984
  }, [onBlur]);
3865
3985
  const combined = extraClasses ? `${baseClasses} ${extraClasses}` : baseClasses;
3866
- const resolved = (0, import_react6.useMemo)(() => resolve(combined, config.theme, config.darkMode), [combined, config.theme, config.darkMode]);
3867
- const hasInteractive = (0, import_react6.useMemo)(() => hasInteractiveBuckets(resolved), [resolved]);
3986
+ const resolved = (0, import_react7.useMemo)(() => resolve(combined, config.theme, config.darkMode), [combined, config.theme, config.darkMode]);
3987
+ const hasInteractive = (0, import_react7.useMemo)(() => hasInteractiveBuckets(resolved), [resolved]);
3868
3988
  const needsWidth = hasResponsiveBuckets(resolved);
3869
3989
  const width = useConditionalWidth(needsWidth);
3870
3990
  const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
3871
- const computedStyle = (0, import_react6.useMemo)(
3991
+ const computedStyle = (0, import_react7.useMemo)(
3872
3992
  () => isWeb ? {} : flatten(resolved, isDark, { pressed, hover: hovered, focus: focused, disabled, checked }, breakpoints),
3873
3993
  [resolved, isDark, pressed, hovered, focused, disabled, checked, width]
3874
3994
  // eslint-disable-line react-hooks/exhaustive-deps
@@ -3879,7 +3999,7 @@ function styled(Component, baseClasses = "") {
3879
3999
  const effectiveComponent = webTag ?? Component;
3880
4000
  const componentName2 = Component.displayName ?? Component.name ?? "";
3881
4001
  const effectiveRest = webTag && componentName2 ? transformToWebProps(componentName2, webTag, rest) : rest;
3882
- return import_react6.default.createElement(effectiveComponent, {
4002
+ return import_react7.default.createElement(effectiveComponent, {
3883
4003
  ref,
3884
4004
  ...effectiveRest,
3885
4005
  style: finalStyle,
@@ -3887,7 +4007,7 @@ function styled(Component, baseClasses = "") {
3887
4007
  ...isWeb && combined ? { className: normalizeClassString(combined) } : {},
3888
4008
  // Only attach state-tracking handlers when interactive modifiers are present.
3889
4009
  // Always forward user-provided handlers to avoid silently swallowing them.
3890
- ...isWeb ? hasInteractive ? { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave } : { onPointerDown, onPointerUp, onPointerLeave } : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
4010
+ ...isWeb ? hasInteractive ? { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave, onPointerCancel: handlePointerLeave } : { onPointerDown, onPointerUp, onPointerLeave } : hasInteractive ? { onPressIn: handlePressIn, onPressOut: handlePressOut } : { onPressIn, onPressOut },
3891
4011
  onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
3892
4012
  onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
3893
4013
  onFocus: hasInteractive ? handleFocus : onFocus,
@@ -3901,42 +4021,26 @@ function styled(Component, baseClasses = "") {
3901
4021
  }
3902
4022
 
3903
4023
  // src/useStyles.ts
3904
- var import_react7 = require("react");
4024
+ var import_react8 = require("react");
3905
4025
  function useStyles(classString, state = {}) {
3906
4026
  const { isDark, config } = useTheme();
3907
4027
  const width = useGlobalWidth();
3908
4028
  const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
3909
- return (0, import_react7.useMemo)(() => {
4029
+ return (0, import_react8.useMemo)(() => {
3910
4030
  const resolved = resolve(normalised, config.theme, config.darkMode);
3911
4031
  const breakpoints = getActiveBreakpoints(width);
3912
4032
  return flatten(resolved, isDark, state, breakpoints);
3913
- }, [normalised, isDark, config, width, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
4033
+ }, [normalised, isDark, config.theme, config.darkMode, width, state.hover, state.focus, state.pressed, state.active, state.disabled, state.checked, state.visited, state.placeholder]);
3914
4034
  }
3915
4035
  function useResolvedStyle(classString) {
3916
4036
  const { config } = useTheme();
3917
4037
  const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
3918
- return (0, import_react7.useMemo)(
4038
+ return (0, import_react8.useMemo)(
3919
4039
  () => resolve(normalised, config.theme, config.darkMode),
3920
4040
  [normalised, config]
3921
4041
  );
3922
4042
  }
3923
4043
 
3924
- // src/useGlobalDarkMode.ts
3925
- var import_react8 = __toESM(require("react"));
3926
- var useSyncExternalStore2 = import_react8.default.useSyncExternalStore ?? function useSyncExternalStoreFallback2(subscribe, getSnapshot, _getServerSnapshot) {
3927
- const [, forceUpdate] = import_react8.default.useReducer((n) => n + 1, 0);
3928
- import_react8.default.useEffect(() => subscribe(forceUpdate), [subscribe]);
3929
- return getSnapshot();
3930
- };
3931
- function useGlobalDarkMode() {
3932
- return useSyncExternalStore2(
3933
- subscribeGlobalDarkMode,
3934
- getGlobalDarkMode,
3935
- () => false
3936
- // SSR: default light
3937
- );
3938
- }
3939
-
3940
4044
  // src/useBreakpoint.ts
3941
4045
  var import_react9 = require("react");
3942
4046
  function useBreakpoint() {
@@ -3944,7 +4048,7 @@ function useBreakpoint() {
3944
4048
  const width = useGlobalWidth();
3945
4049
  const screens = config.theme.screens;
3946
4050
  const sorted = (0, import_react9.useMemo)(
3947
- () => Object.entries(screens).filter(([, v]) => typeof v === "number").sort(([, a], [, b]) => b - a),
4051
+ () => Object.entries(screens).filter((entry) => typeof entry[1] === "number").sort(([, a], [, b]) => b - a),
3948
4052
  [screens]
3949
4053
  );
3950
4054
  for (const [name, minW] of sorted) {
@@ -4006,10 +4110,11 @@ var InteractiveWrapper = (0, import_react10.forwardRef)(
4006
4110
  const handleMouseLeave = (0, import_react10.useCallback)(chain(onMouseLeave, () => setHovered(false)), [onMouseLeave]);
4007
4111
  const handleFocus = (0, import_react10.useCallback)(chain(onFocus, () => setFocused(true)), [onFocus]);
4008
4112
  const handleBlur = (0, import_react10.useCallback)(chain(onBlur, () => setFocused(false)), [onBlur]);
4113
+ const { children, disabled, checked, ...restForComponent } = rest;
4009
4114
  const computedStyle = (0, import_react10.useMemo)(
4010
4115
  () => {
4011
4116
  if (isWeb) return {};
4012
- const s = flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused, disabled: !!rest.disabled, checked: !!rest.checked }, breakpoints);
4117
+ const s = flatten(resolvedStyle, isDark, { pressed, hover: hovered, focus: focused, disabled: !!disabled, checked: !!checked }, breakpoints);
4013
4118
  delete s.__divideX;
4014
4119
  delete s.__divideY;
4015
4120
  delete s.__divideColor;
@@ -4040,20 +4145,20 @@ var InteractiveWrapper = (0, import_react10.forwardRef)(
4040
4145
  }
4041
4146
  return s;
4042
4147
  },
4043
- [resolvedStyle, isDark, pressed, hovered, focused, width]
4044
- // eslint-disable-line react-hooks/exhaustive-deps
4148
+ [resolvedStyle, isDark, pressed, hovered, focused, width, disabled, checked]
4045
4149
  );
4046
4150
  const skipComputedInline = isWeb || !isNative && typeof Component === "string";
4047
4151
  const finalStyle = skipComputedInline ? styleProp ?? void 0 : styleProp ? Array.isArray(styleProp) ? Object.assign({}, computedStyle, ...styleProp) : { ...computedStyle, ...styleProp } : computedStyle;
4048
- const { children, ...restWithoutChildren } = rest;
4049
4152
  const componentProps = {
4050
4153
  ref,
4051
- ...restWithoutChildren,
4154
+ ...restForComponent,
4155
+ ...disabled !== void 0 ? { disabled } : {},
4156
+ ...checked !== void 0 ? { checked } : {},
4052
4157
  style: finalStyle,
4053
4158
  ...!isNative && className ? { className } : {},
4054
4159
  // onPressIn / onPressOut are React Native-only.
4055
4160
  // On web, onPointerDown/Up drive the pressed state instead (covers mouse + touch).
4056
- ...!isNative ? { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave } : { onPressIn: handlePressIn, onPressOut: handlePressOut },
4161
+ ...!isNative ? { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave, onPointerCancel: handlePointerLeave } : { onPressIn: handlePressIn, onPressOut: handlePressOut },
4057
4162
  onMouseEnter: handleMouseEnter,
4058
4163
  onMouseLeave: handleMouseLeave,
4059
4164
  onFocus: handleFocus,