@kbach/react 0.5.57 → 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",
@@ -3725,6 +3829,7 @@ function transformToWebProps(originalName, tag, props) {
3725
3829
  const isTextInput = originalName === "TextInput";
3726
3830
  const isImage = originalName === "Image" || originalName === "ImageBackground";
3727
3831
  const isScrollable = originalName === "ScrollView" || originalName === "FlatList" || originalName === "SectionList";
3832
+ let pendingStyle = null;
3728
3833
  for (const [k, v] of Object.entries(props)) {
3729
3834
  if (_rnOnlyProps.has(k)) continue;
3730
3835
  if (k === "onPress") {
@@ -3769,10 +3874,11 @@ function transformToWebProps(originalName, tag, props) {
3769
3874
  }
3770
3875
  }
3771
3876
  if (isScrollable && k === "horizontal") {
3772
- if (v) {
3773
- const existing = out.style ?? {};
3774
- out.style = { display: "flex", flexDirection: "row", overflowX: "auto", ...existing };
3775
- }
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 };
3776
3882
  continue;
3777
3883
  }
3778
3884
  if (isImage) {
@@ -3795,7 +3901,7 @@ function transformToWebProps(originalName, tag, props) {
3795
3901
  out.style = { ...existing, objectFit: v };
3796
3902
  continue;
3797
3903
  }
3798
- if (k === "alt" || k === "defaultSource") continue;
3904
+ if (k === "defaultSource") continue;
3799
3905
  }
3800
3906
  if (k === "style") {
3801
3907
  if (Array.isArray(v)) {
@@ -3807,6 +3913,9 @@ function transformToWebProps(originalName, tag, props) {
3807
3913
  }
3808
3914
  out[k] = v;
3809
3915
  }
3916
+ if (pendingStyle) {
3917
+ out.style = out.style ? { ...pendingStyle, ...out.style } : pendingStyle;
3918
+ }
3810
3919
  if (isPressable && !out.role) out.role = "button";
3811
3920
  if (isImage && tag === "img" && out.alt == null) out.alt = "";
3812
3921
  return out;
@@ -3814,7 +3923,7 @@ function transformToWebProps(originalName, tag, props) {
3814
3923
 
3815
3924
  // src/styled.tsx
3816
3925
  function styled(Component, baseClasses = "") {
3817
- const Styled = (0, import_react6.forwardRef)(
3926
+ const Styled = (0, import_react7.forwardRef)(
3818
3927
  (props, ref) => {
3819
3928
  const {
3820
3929
  kb: extraClasses,
@@ -3832,53 +3941,54 @@ function styled(Component, baseClasses = "") {
3832
3941
  } = props;
3833
3942
  const disabled = !!props.disabled;
3834
3943
  const checked = !!props.checked;
3835
- const { isDark, config } = useTheme();
3836
- const [pressed, setPressed] = (0, import_react6.useState)(false);
3837
- const [hovered, setHovered] = (0, import_react6.useState)(false);
3838
- const [focused, setFocused] = (0, import_react6.useState)(false);
3839
- 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) => {
3840
3950
  setPressed(true);
3841
3951
  onPressIn?.(e);
3842
3952
  }, [onPressIn]);
3843
- const handlePressOut = (0, import_react6.useCallback)((e) => {
3953
+ const handlePressOut = (0, import_react7.useCallback)((e) => {
3844
3954
  setPressed(false);
3845
3955
  onPressOut?.(e);
3846
3956
  }, [onPressOut]);
3847
- const handlePointerDown = (0, import_react6.useCallback)((e) => {
3957
+ const handlePointerDown = (0, import_react7.useCallback)((e) => {
3848
3958
  setPressed(true);
3849
3959
  onPointerDown?.(e);
3850
3960
  }, [onPointerDown]);
3851
- const handlePointerUp = (0, import_react6.useCallback)((e) => {
3961
+ const handlePointerUp = (0, import_react7.useCallback)((e) => {
3852
3962
  setPressed(false);
3853
3963
  onPointerUp?.(e);
3854
3964
  }, [onPointerUp]);
3855
- const handlePointerLeave = (0, import_react6.useCallback)((e) => {
3965
+ const handlePointerLeave = (0, import_react7.useCallback)((e) => {
3856
3966
  setPressed(false);
3857
3967
  onPointerLeave?.(e);
3858
3968
  }, [onPointerLeave]);
3859
- const handleMouseEnter = (0, import_react6.useCallback)((e) => {
3969
+ const handleMouseEnter = (0, import_react7.useCallback)((e) => {
3860
3970
  setHovered(true);
3861
3971
  onMouseEnter?.(e);
3862
3972
  }, [onMouseEnter]);
3863
- const handleMouseLeave = (0, import_react6.useCallback)((e) => {
3973
+ const handleMouseLeave = (0, import_react7.useCallback)((e) => {
3864
3974
  setHovered(false);
3865
3975
  onMouseLeave?.(e);
3866
3976
  }, [onMouseLeave]);
3867
- const handleFocus = (0, import_react6.useCallback)((e) => {
3977
+ const handleFocus = (0, import_react7.useCallback)((e) => {
3868
3978
  setFocused(true);
3869
3979
  onFocus?.(e);
3870
3980
  }, [onFocus]);
3871
- const handleBlur = (0, import_react6.useCallback)((e) => {
3981
+ const handleBlur = (0, import_react7.useCallback)((e) => {
3872
3982
  setFocused(false);
3873
3983
  onBlur?.(e);
3874
3984
  }, [onBlur]);
3875
3985
  const combined = extraClasses ? `${baseClasses} ${extraClasses}` : baseClasses;
3876
- const resolved = (0, import_react6.useMemo)(() => resolve(combined, config.theme, config.darkMode), [combined, config.theme, config.darkMode]);
3877
- 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]);
3878
3988
  const needsWidth = hasResponsiveBuckets(resolved);
3879
3989
  const width = useConditionalWidth(needsWidth);
3880
3990
  const breakpoints = needsWidth ? getActiveBreakpoints(width) : EMPTY_BREAKPOINTS;
3881
- const computedStyle = (0, import_react6.useMemo)(
3991
+ const computedStyle = (0, import_react7.useMemo)(
3882
3992
  () => isWeb ? {} : flatten(resolved, isDark, { pressed, hover: hovered, focus: focused, disabled, checked }, breakpoints),
3883
3993
  [resolved, isDark, pressed, hovered, focused, disabled, checked, width]
3884
3994
  // eslint-disable-line react-hooks/exhaustive-deps
@@ -3889,7 +3999,7 @@ function styled(Component, baseClasses = "") {
3889
3999
  const effectiveComponent = webTag ?? Component;
3890
4000
  const componentName2 = Component.displayName ?? Component.name ?? "";
3891
4001
  const effectiveRest = webTag && componentName2 ? transformToWebProps(componentName2, webTag, rest) : rest;
3892
- return import_react6.default.createElement(effectiveComponent, {
4002
+ return import_react7.default.createElement(effectiveComponent, {
3893
4003
  ref,
3894
4004
  ...effectiveRest,
3895
4005
  style: finalStyle,
@@ -3897,7 +4007,7 @@ function styled(Component, baseClasses = "") {
3897
4007
  ...isWeb && combined ? { className: normalizeClassString(combined) } : {},
3898
4008
  // Only attach state-tracking handlers when interactive modifiers are present.
3899
4009
  // Always forward user-provided handlers to avoid silently swallowing them.
3900
- ...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 },
3901
4011
  onMouseEnter: hasInteractive ? handleMouseEnter : onMouseEnter,
3902
4012
  onMouseLeave: hasInteractive ? handleMouseLeave : onMouseLeave,
3903
4013
  onFocus: hasInteractive ? handleFocus : onFocus,
@@ -3911,42 +4021,26 @@ function styled(Component, baseClasses = "") {
3911
4021
  }
3912
4022
 
3913
4023
  // src/useStyles.ts
3914
- var import_react7 = require("react");
4024
+ var import_react8 = require("react");
3915
4025
  function useStyles(classString, state = {}) {
3916
4026
  const { isDark, config } = useTheme();
3917
4027
  const width = useGlobalWidth();
3918
4028
  const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
3919
- return (0, import_react7.useMemo)(() => {
4029
+ return (0, import_react8.useMemo)(() => {
3920
4030
  const resolved = resolve(normalised, config.theme, config.darkMode);
3921
4031
  const breakpoints = getActiveBreakpoints(width);
3922
4032
  return flatten(resolved, isDark, state, breakpoints);
3923
- }, [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]);
3924
4034
  }
3925
4035
  function useResolvedStyle(classString) {
3926
4036
  const { config } = useTheme();
3927
4037
  const normalised = Array.isArray(classString) ? classString.join(" ") : classString;
3928
- return (0, import_react7.useMemo)(
4038
+ return (0, import_react8.useMemo)(
3929
4039
  () => resolve(normalised, config.theme, config.darkMode),
3930
4040
  [normalised, config]
3931
4041
  );
3932
4042
  }
3933
4043
 
3934
- // src/useGlobalDarkMode.ts
3935
- var import_react8 = __toESM(require("react"));
3936
- var useSyncExternalStore2 = import_react8.default.useSyncExternalStore ?? function useSyncExternalStoreFallback2(subscribe, getSnapshot, _getServerSnapshot) {
3937
- const [, forceUpdate] = import_react8.default.useReducer((n) => n + 1, 0);
3938
- import_react8.default.useEffect(() => subscribe(forceUpdate), [subscribe]);
3939
- return getSnapshot();
3940
- };
3941
- function useGlobalDarkMode() {
3942
- return useSyncExternalStore2(
3943
- subscribeGlobalDarkMode,
3944
- getGlobalDarkMode,
3945
- () => false
3946
- // SSR: default light
3947
- );
3948
- }
3949
-
3950
4044
  // src/useBreakpoint.ts
3951
4045
  var import_react9 = require("react");
3952
4046
  function useBreakpoint() {
@@ -3954,7 +4048,7 @@ function useBreakpoint() {
3954
4048
  const width = useGlobalWidth();
3955
4049
  const screens = config.theme.screens;
3956
4050
  const sorted = (0, import_react9.useMemo)(
3957
- () => 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),
3958
4052
  [screens]
3959
4053
  );
3960
4054
  for (const [name, minW] of sorted) {
@@ -4016,10 +4110,11 @@ var InteractiveWrapper = (0, import_react10.forwardRef)(
4016
4110
  const handleMouseLeave = (0, import_react10.useCallback)(chain(onMouseLeave, () => setHovered(false)), [onMouseLeave]);
4017
4111
  const handleFocus = (0, import_react10.useCallback)(chain(onFocus, () => setFocused(true)), [onFocus]);
4018
4112
  const handleBlur = (0, import_react10.useCallback)(chain(onBlur, () => setFocused(false)), [onBlur]);
4113
+ const { children, disabled, checked, ...restForComponent } = rest;
4019
4114
  const computedStyle = (0, import_react10.useMemo)(
4020
4115
  () => {
4021
4116
  if (isWeb) return {};
4022
- 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);
4023
4118
  delete s.__divideX;
4024
4119
  delete s.__divideY;
4025
4120
  delete s.__divideColor;
@@ -4050,20 +4145,20 @@ var InteractiveWrapper = (0, import_react10.forwardRef)(
4050
4145
  }
4051
4146
  return s;
4052
4147
  },
4053
- [resolvedStyle, isDark, pressed, hovered, focused, width]
4054
- // eslint-disable-line react-hooks/exhaustive-deps
4148
+ [resolvedStyle, isDark, pressed, hovered, focused, width, disabled, checked]
4055
4149
  );
4056
4150
  const skipComputedInline = isWeb || !isNative && typeof Component === "string";
4057
4151
  const finalStyle = skipComputedInline ? styleProp ?? void 0 : styleProp ? Array.isArray(styleProp) ? Object.assign({}, computedStyle, ...styleProp) : { ...computedStyle, ...styleProp } : computedStyle;
4058
- const { children, ...restWithoutChildren } = rest;
4059
4152
  const componentProps = {
4060
4153
  ref,
4061
- ...restWithoutChildren,
4154
+ ...restForComponent,
4155
+ ...disabled !== void 0 ? { disabled } : {},
4156
+ ...checked !== void 0 ? { checked } : {},
4062
4157
  style: finalStyle,
4063
4158
  ...!isNative && className ? { className } : {},
4064
4159
  // onPressIn / onPressOut are React Native-only.
4065
4160
  // On web, onPointerDown/Up drive the pressed state instead (covers mouse + touch).
4066
- ...!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 },
4067
4162
  onMouseEnter: handleMouseEnter,
4068
4163
  onMouseLeave: handleMouseLeave,
4069
4164
  onFocus: handleFocus,