@kbach/react 0.6.7 → 0.6.9

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.
@@ -23,7 +23,7 @@ import {
23
23
  transformToWebProps,
24
24
  useConditionalWidth,
25
25
  useGlobalDarkMode
26
- } from "./chunk-LXOMVLQI.mjs";
26
+ } from "./chunk-XJFOY7HN.mjs";
27
27
 
28
28
  // src/jsx-runtime.tsx
29
29
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -791,6 +791,13 @@ function hexToRgba(hex, alpha) {
791
791
  const [r, g, b] = rgb;
792
792
  return `rgba(${r},${g},${b},${alpha})`;
793
793
  }
794
+ function withOpacityVar(color, varName) {
795
+ if (!color.startsWith("#")) return color;
796
+ const rgb = parseHexRgb(color);
797
+ if (!rgb) return color;
798
+ const [r, g, b] = rgb;
799
+ return `rgba(${r},${g},${b},var(${varName},1))`;
800
+ }
794
801
  function resolveSpacing(value, negative, spacing, isArbitrary) {
795
802
  if (isArbitrary) {
796
803
  const resolved = getEffectiveIsWeb() ? value : toNativeValue(value);
@@ -1237,7 +1244,8 @@ var RESOLVERS = {
1237
1244
  // ── Background ─────────────────────────────────────────────────────────────
1238
1245
  bg: ({ value, isArbitrary }, { colors }) => {
1239
1246
  const color = resolveColor(value, colors, isArbitrary);
1240
- return color ? { backgroundColor: color } : null;
1247
+ if (!color) return null;
1248
+ return { backgroundColor: getEffectiveIsWeb() ? withOpacityVar(color, "--bg-opacity") : color };
1241
1249
  },
1242
1250
  "bg-opacity": ({ value, isArbitrary }, _) => {
1243
1251
  if (!getEffectiveIsWeb()) return null;
@@ -1254,10 +1262,11 @@ var RESOLVERS = {
1254
1262
  if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
1255
1263
  return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
1256
1264
  }
1257
- return { color: value };
1265
+ return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
1258
1266
  }
1259
1267
  const color = resolveColor(value, colors, false);
1260
- return color ? { color } : null;
1268
+ if (!color) return null;
1269
+ return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
1261
1270
  },
1262
1271
  "text-opacity": ({ value, isArbitrary }, _) => {
1263
1272
  if (!getEffectiveIsWeb()) return null;
@@ -2550,8 +2559,31 @@ var BASE_RESET = [
2550
2559
  "a { color: inherit; text-decoration: none; }",
2551
2560
  "ul, ol { margin: 0; padding: 0; list-style: none; }",
2552
2561
  "img, video, svg { display: block; max-width: 100%; }",
2553
- "input, textarea, select { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2562
+ // appearance: none is deliberately NOT applied to checkbox/radio/select below —
2563
+ // stripping it hides their native checkmark/arrow with nothing rendered in its
2564
+ // place, leaving an invisible checkbox or an arrow-less <select> that looks like
2565
+ // plain text. Text-like inputs, textarea, and button don't have that problem
2566
+ // (their native chrome is just a skin around content utilities can fully
2567
+ // restyle), so they keep the blank-canvas treatment.
2568
+ "input:not([type='checkbox']):not([type='radio']), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2569
+ // Native checkbox/radio/range still get typography + spacing normalized, and
2570
+ // accent-color re-themes their native indicator to the current text color
2571
+ // instead of the browser/OS default blue, so they stay on-brand without
2572
+ // needing to be rebuilt from scratch.
2573
+ "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
2574
+ // select keeps its native chrome (border/background/arrow are all part of the
2575
+ // same OS-drawn widget that appearance: none would blank out) — only typography
2576
+ // and spacing are normalized so it still matches surrounding text.
2577
+ "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2554
2578
  "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
2579
+ "button, [role='button'] { cursor: pointer; }",
2580
+ ":disabled { cursor: default; }",
2581
+ "textarea { resize: vertical; }",
2582
+ // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
2583
+ // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
2584
+ "::placeholder { opacity: 1; }",
2585
+ "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
2586
+ "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
2555
2587
  "fieldset { padding: 0; margin: 0; }",
2556
2588
  "table { border-collapse: collapse; border-spacing: 0; }"
2557
2589
  ].join("\n");
@@ -2807,7 +2839,8 @@ function styleValueToCSS(styles, forceImportant = false) {
2807
2839
  if (val === void 0 || val === null || typeof val === "object" || RN_ONLY_PROPS.has(prop)) continue;
2808
2840
  let cssVal;
2809
2841
  if (typeof val === "number") {
2810
- cssVal = val === 0 || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
2842
+ const isCustomProp = prop.startsWith("--");
2843
+ cssVal = val === 0 || isCustomProp || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
2811
2844
  } else {
2812
2845
  cssVal = String(val);
2813
2846
  }
package/dist/index.js CHANGED
@@ -910,6 +910,13 @@ function hexToRgba(hex, alpha) {
910
910
  const [r, g, b] = rgb;
911
911
  return `rgba(${r},${g},${b},${alpha})`;
912
912
  }
913
+ function withOpacityVar(color, varName) {
914
+ if (!color.startsWith("#")) return color;
915
+ const rgb = parseHexRgb(color);
916
+ if (!rgb) return color;
917
+ const [r, g, b] = rgb;
918
+ return `rgba(${r},${g},${b},var(${varName},1))`;
919
+ }
913
920
  function resolveSpacing(value, negative, spacing, isArbitrary) {
914
921
  if (isArbitrary) {
915
922
  const resolved = getEffectiveIsWeb() ? value : toNativeValue(value);
@@ -1356,7 +1363,8 @@ var RESOLVERS = {
1356
1363
  // ── Background ─────────────────────────────────────────────────────────────
1357
1364
  bg: ({ value, isArbitrary }, { colors }) => {
1358
1365
  const color = resolveColor(value, colors, isArbitrary);
1359
- return color ? { backgroundColor: color } : null;
1366
+ if (!color) return null;
1367
+ return { backgroundColor: getEffectiveIsWeb() ? withOpacityVar(color, "--bg-opacity") : color };
1360
1368
  },
1361
1369
  "bg-opacity": ({ value, isArbitrary }, _) => {
1362
1370
  if (!getEffectiveIsWeb()) return null;
@@ -1373,10 +1381,11 @@ var RESOLVERS = {
1373
1381
  if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
1374
1382
  return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
1375
1383
  }
1376
- return { color: value };
1384
+ return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
1377
1385
  }
1378
1386
  const color = resolveColor(value, colors, false);
1379
- return color ? { color } : null;
1387
+ if (!color) return null;
1388
+ return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
1380
1389
  },
1381
1390
  "text-opacity": ({ value, isArbitrary }, _) => {
1382
1391
  if (!getEffectiveIsWeb()) return null;
@@ -2669,8 +2678,31 @@ var BASE_RESET = [
2669
2678
  "a { color: inherit; text-decoration: none; }",
2670
2679
  "ul, ol { margin: 0; padding: 0; list-style: none; }",
2671
2680
  "img, video, svg { display: block; max-width: 100%; }",
2672
- "input, textarea, select { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2681
+ // appearance: none is deliberately NOT applied to checkbox/radio/select below —
2682
+ // stripping it hides their native checkmark/arrow with nothing rendered in its
2683
+ // place, leaving an invisible checkbox or an arrow-less <select> that looks like
2684
+ // plain text. Text-like inputs, textarea, and button don't have that problem
2685
+ // (their native chrome is just a skin around content utilities can fully
2686
+ // restyle), so they keep the blank-canvas treatment.
2687
+ "input:not([type='checkbox']):not([type='radio']), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2688
+ // Native checkbox/radio/range still get typography + spacing normalized, and
2689
+ // accent-color re-themes their native indicator to the current text color
2690
+ // instead of the browser/OS default blue, so they stay on-brand without
2691
+ // needing to be rebuilt from scratch.
2692
+ "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
2693
+ // select keeps its native chrome (border/background/arrow are all part of the
2694
+ // same OS-drawn widget that appearance: none would blank out) — only typography
2695
+ // and spacing are normalized so it still matches surrounding text.
2696
+ "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2673
2697
  "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
2698
+ "button, [role='button'] { cursor: pointer; }",
2699
+ ":disabled { cursor: default; }",
2700
+ "textarea { resize: vertical; }",
2701
+ // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
2702
+ // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
2703
+ "::placeholder { opacity: 1; }",
2704
+ "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
2705
+ "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
2674
2706
  "fieldset { padding: 0; margin: 0; }",
2675
2707
  "table { border-collapse: collapse; border-spacing: 0; }"
2676
2708
  ].join("\n");
@@ -2885,7 +2917,8 @@ function styleValueToCSS(styles, forceImportant = false) {
2885
2917
  if (val === void 0 || val === null || typeof val === "object" || RN_ONLY_PROPS.has(prop)) continue;
2886
2918
  let cssVal;
2887
2919
  if (typeof val === "number") {
2888
- cssVal = val === 0 || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
2920
+ const isCustomProp = prop.startsWith("--");
2921
+ cssVal = val === 0 || isCustomProp || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
2889
2922
  } else {
2890
2923
  cssVal = String(val);
2891
2924
  }
package/dist/index.mjs CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  useGlobalDarkMode,
37
37
  useGlobalWidth,
38
38
  useSyncExternalStore
39
- } from "./chunk-LXOMVLQI.mjs";
39
+ } from "./chunk-XJFOY7HN.mjs";
40
40
 
41
41
  // src/context.tsx
42
42
  import { createContext, useContext } from "react";
@@ -869,6 +869,13 @@ function hexToRgba(hex, alpha) {
869
869
  const [r, g, b] = rgb;
870
870
  return `rgba(${r},${g},${b},${alpha})`;
871
871
  }
872
+ function withOpacityVar(color, varName) {
873
+ if (!color.startsWith("#")) return color;
874
+ const rgb = parseHexRgb(color);
875
+ if (!rgb) return color;
876
+ const [r, g, b] = rgb;
877
+ return `rgba(${r},${g},${b},var(${varName},1))`;
878
+ }
872
879
  function resolveSpacing(value, negative, spacing, isArbitrary) {
873
880
  if (isArbitrary) {
874
881
  const resolved = getEffectiveIsWeb() ? value : toNativeValue(value);
@@ -1315,7 +1322,8 @@ var RESOLVERS = {
1315
1322
  // ── Background ─────────────────────────────────────────────────────────────
1316
1323
  bg: ({ value, isArbitrary }, { colors }) => {
1317
1324
  const color = resolveColor(value, colors, isArbitrary);
1318
- return color ? { backgroundColor: color } : null;
1325
+ if (!color) return null;
1326
+ return { backgroundColor: getEffectiveIsWeb() ? withOpacityVar(color, "--bg-opacity") : color };
1319
1327
  },
1320
1328
  "bg-opacity": ({ value, isArbitrary }, _) => {
1321
1329
  if (!getEffectiveIsWeb()) return null;
@@ -1332,10 +1340,11 @@ var RESOLVERS = {
1332
1340
  if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
1333
1341
  return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
1334
1342
  }
1335
- return { color: value };
1343
+ return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
1336
1344
  }
1337
1345
  const color = resolveColor(value, colors, false);
1338
- return color ? { color } : null;
1346
+ if (!color) return null;
1347
+ return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
1339
1348
  },
1340
1349
  "text-opacity": ({ value, isArbitrary }, _) => {
1341
1350
  if (!getEffectiveIsWeb()) return null;
@@ -2628,8 +2637,31 @@ var BASE_RESET = [
2628
2637
  "a { color: inherit; text-decoration: none; }",
2629
2638
  "ul, ol { margin: 0; padding: 0; list-style: none; }",
2630
2639
  "img, video, svg { display: block; max-width: 100%; }",
2631
- "input, textarea, select { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2640
+ // appearance: none is deliberately NOT applied to checkbox/radio/select below —
2641
+ // stripping it hides their native checkmark/arrow with nothing rendered in its
2642
+ // place, leaving an invisible checkbox or an arrow-less <select> that looks like
2643
+ // plain text. Text-like inputs, textarea, and button don't have that problem
2644
+ // (their native chrome is just a skin around content utilities can fully
2645
+ // restyle), so they keep the blank-canvas treatment.
2646
+ "input:not([type='checkbox']):not([type='radio']), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2647
+ // Native checkbox/radio/range still get typography + spacing normalized, and
2648
+ // accent-color re-themes their native indicator to the current text color
2649
+ // instead of the browser/OS default blue, so they stay on-brand without
2650
+ // needing to be rebuilt from scratch.
2651
+ "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
2652
+ // select keeps its native chrome (border/background/arrow are all part of the
2653
+ // same OS-drawn widget that appearance: none would blank out) — only typography
2654
+ // and spacing are normalized so it still matches surrounding text.
2655
+ "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2632
2656
  "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
2657
+ "button, [role='button'] { cursor: pointer; }",
2658
+ ":disabled { cursor: default; }",
2659
+ "textarea { resize: vertical; }",
2660
+ // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
2661
+ // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
2662
+ "::placeholder { opacity: 1; }",
2663
+ "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
2664
+ "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
2633
2665
  "fieldset { padding: 0; margin: 0; }",
2634
2666
  "table { border-collapse: collapse; border-spacing: 0; }"
2635
2667
  ].join("\n");
@@ -2829,7 +2861,8 @@ function styleValueToCSS(styles, forceImportant = false) {
2829
2861
  if (val === void 0 || val === null || typeof val === "object" || RN_ONLY_PROPS.has(prop)) continue;
2830
2862
  let cssVal;
2831
2863
  if (typeof val === "number") {
2832
- cssVal = val === 0 || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
2864
+ const isCustomProp = prop.startsWith("--");
2865
+ cssVal = val === 0 || isCustomProp || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
2833
2866
  } else {
2834
2867
  cssVal = String(val);
2835
2868
  }
@@ -3,8 +3,8 @@ import {
3
3
  Fragment,
4
4
  jsx,
5
5
  jsxs
6
- } from "./chunk-5YN6PIXQ.mjs";
7
- import "./chunk-LXOMVLQI.mjs";
6
+ } from "./chunk-35CBHSAG.mjs";
7
+ import "./chunk-XJFOY7HN.mjs";
8
8
 
9
9
  // src/jsx-dev-runtime.tsx
10
10
  function jsxDEV(type, props, key, isStaticChildren, source, self) {
@@ -870,6 +870,13 @@ function hexToRgba(hex, alpha) {
870
870
  const [r, g, b] = rgb;
871
871
  return `rgba(${r},${g},${b},${alpha})`;
872
872
  }
873
+ function withOpacityVar(color, varName) {
874
+ if (!color.startsWith("#")) return color;
875
+ const rgb = parseHexRgb(color);
876
+ if (!rgb) return color;
877
+ const [r, g, b] = rgb;
878
+ return `rgba(${r},${g},${b},var(${varName},1))`;
879
+ }
873
880
  function resolveSpacing(value, negative, spacing, isArbitrary) {
874
881
  if (isArbitrary) {
875
882
  const resolved = getEffectiveIsWeb() ? value : toNativeValue(value);
@@ -1316,7 +1323,8 @@ var RESOLVERS = {
1316
1323
  // ── Background ─────────────────────────────────────────────────────────────
1317
1324
  bg: ({ value, isArbitrary }, { colors }) => {
1318
1325
  const color = resolveColor(value, colors, isArbitrary);
1319
- return color ? { backgroundColor: color } : null;
1326
+ if (!color) return null;
1327
+ return { backgroundColor: getEffectiveIsWeb() ? withOpacityVar(color, "--bg-opacity") : color };
1320
1328
  },
1321
1329
  "bg-opacity": ({ value, isArbitrary }, _) => {
1322
1330
  if (!getEffectiveIsWeb()) return null;
@@ -1333,10 +1341,11 @@ var RESOLVERS = {
1333
1341
  if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
1334
1342
  return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
1335
1343
  }
1336
- return { color: value };
1344
+ return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
1337
1345
  }
1338
1346
  const color = resolveColor(value, colors, false);
1339
- return color ? { color } : null;
1347
+ if (!color) return null;
1348
+ return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
1340
1349
  },
1341
1350
  "text-opacity": ({ value, isArbitrary }, _) => {
1342
1351
  if (!getEffectiveIsWeb()) return null;
@@ -2629,8 +2638,31 @@ var BASE_RESET = [
2629
2638
  "a { color: inherit; text-decoration: none; }",
2630
2639
  "ul, ol { margin: 0; padding: 0; list-style: none; }",
2631
2640
  "img, video, svg { display: block; max-width: 100%; }",
2632
- "input, textarea, select { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2641
+ // appearance: none is deliberately NOT applied to checkbox/radio/select below —
2642
+ // stripping it hides their native checkmark/arrow with nothing rendered in its
2643
+ // place, leaving an invisible checkbox or an arrow-less <select> that looks like
2644
+ // plain text. Text-like inputs, textarea, and button don't have that problem
2645
+ // (their native chrome is just a skin around content utilities can fully
2646
+ // restyle), so they keep the blank-canvas treatment.
2647
+ "input:not([type='checkbox']):not([type='radio']), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2648
+ // Native checkbox/radio/range still get typography + spacing normalized, and
2649
+ // accent-color re-themes their native indicator to the current text color
2650
+ // instead of the browser/OS default blue, so they stay on-brand without
2651
+ // needing to be rebuilt from scratch.
2652
+ "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
2653
+ // select keeps its native chrome (border/background/arrow are all part of the
2654
+ // same OS-drawn widget that appearance: none would blank out) — only typography
2655
+ // and spacing are normalized so it still matches surrounding text.
2656
+ "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
2633
2657
  "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
2658
+ "button, [role='button'] { cursor: pointer; }",
2659
+ ":disabled { cursor: default; }",
2660
+ "textarea { resize: vertical; }",
2661
+ // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
2662
+ // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
2663
+ "::placeholder { opacity: 1; }",
2664
+ "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
2665
+ "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
2634
2666
  "fieldset { padding: 0; margin: 0; }",
2635
2667
  "table { border-collapse: collapse; border-spacing: 0; }"
2636
2668
  ].join("\n");
@@ -2830,7 +2862,8 @@ function styleValueToCSS(styles, forceImportant = false) {
2830
2862
  if (val === void 0 || val === null || typeof val === "object" || RN_ONLY_PROPS.has(prop)) continue;
2831
2863
  let cssVal;
2832
2864
  if (typeof val === "number") {
2833
- cssVal = val === 0 || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
2865
+ const isCustomProp = prop.startsWith("--");
2866
+ cssVal = val === 0 || isCustomProp || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
2834
2867
  } else {
2835
2868
  cssVal = String(val);
2836
2869
  }
@@ -4,10 +4,10 @@ import {
4
4
  _invalidateDefaultFontCache,
5
5
  jsx,
6
6
  jsxs
7
- } from "./chunk-5YN6PIXQ.mjs";
7
+ } from "./chunk-35CBHSAG.mjs";
8
8
  import {
9
9
  registerWebElement
10
- } from "./chunk-LXOMVLQI.mjs";
10
+ } from "./chunk-XJFOY7HN.mjs";
11
11
  export {
12
12
  Fragment,
13
13
  _invalidateDefaultFontCache,
@@ -70,8 +70,31 @@ var BASE_RESET = [
70
70
  "a { color: inherit; text-decoration: none; }",
71
71
  "ul, ol { margin: 0; padding: 0; list-style: none; }",
72
72
  "img, video, svg { display: block; max-width: 100%; }",
73
- "input, textarea, select { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
73
+ // appearance: none is deliberately NOT applied to checkbox/radio/select below —
74
+ // stripping it hides their native checkmark/arrow with nothing rendered in its
75
+ // place, leaving an invisible checkbox or an arrow-less <select> that looks like
76
+ // plain text. Text-like inputs, textarea, and button don't have that problem
77
+ // (their native chrome is just a skin around content utilities can fully
78
+ // restyle), so they keep the blank-canvas treatment.
79
+ "input:not([type='checkbox']):not([type='radio']), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
80
+ // Native checkbox/radio/range still get typography + spacing normalized, and
81
+ // accent-color re-themes their native indicator to the current text color
82
+ // instead of the browser/OS default blue, so they stay on-brand without
83
+ // needing to be rebuilt from scratch.
84
+ "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
85
+ // select keeps its native chrome (border/background/arrow are all part of the
86
+ // same OS-drawn widget that appearance: none would blank out) — only typography
87
+ // and spacing are normalized so it still matches surrounding text.
88
+ "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
74
89
  "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
90
+ "button, [role='button'] { cursor: pointer; }",
91
+ ":disabled { cursor: default; }",
92
+ "textarea { resize: vertical; }",
93
+ // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
94
+ // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
95
+ "::placeholder { opacity: 1; }",
96
+ "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
97
+ "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
75
98
  "fieldset { padding: 0; margin: 0; }",
76
99
  "table { border-collapse: collapse; border-spacing: 0; }"
77
100
  ].join("\n");
@@ -701,6 +724,13 @@ function hexToRgba(hex, alpha) {
701
724
  const [r, g, b] = rgb;
702
725
  return `rgba(${r},${g},${b},${alpha})`;
703
726
  }
727
+ function withOpacityVar(color, varName) {
728
+ if (!color.startsWith("#")) return color;
729
+ const rgb = parseHexRgb(color);
730
+ if (!rgb) return color;
731
+ const [r, g, b] = rgb;
732
+ return `rgba(${r},${g},${b},var(${varName},1))`;
733
+ }
704
734
  function resolveSpacing(value, negative, spacing, isArbitrary) {
705
735
  if (isArbitrary) {
706
736
  const resolved = getEffectiveIsWeb() ? value : toNativeValue(value);
@@ -1147,7 +1177,8 @@ var RESOLVERS = {
1147
1177
  // ── Background ─────────────────────────────────────────────────────────────
1148
1178
  bg: ({ value, isArbitrary }, { colors }) => {
1149
1179
  const color = resolveColor(value, colors, isArbitrary);
1150
- return color ? { backgroundColor: color } : null;
1180
+ if (!color) return null;
1181
+ return { backgroundColor: getEffectiveIsWeb() ? withOpacityVar(color, "--bg-opacity") : color };
1151
1182
  },
1152
1183
  "bg-opacity": ({ value, isArbitrary }, _) => {
1153
1184
  if (!getEffectiveIsWeb()) return null;
@@ -1164,10 +1195,11 @@ var RESOLVERS = {
1164
1195
  if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
1165
1196
  return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
1166
1197
  }
1167
- return { color: value };
1198
+ return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
1168
1199
  }
1169
1200
  const color = resolveColor(value, colors, false);
1170
- return color ? { color } : null;
1201
+ if (!color) return null;
1202
+ return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
1171
1203
  },
1172
1204
  "text-opacity": ({ value, isArbitrary }, _) => {
1173
1205
  if (!getEffectiveIsWeb()) return null;
@@ -2732,7 +2764,8 @@ function styleValueToCSS(styles, forceImportant = false) {
2732
2764
  if (val === void 0 || val === null || typeof val === "object" || RN_ONLY_PROPS.has(prop)) continue;
2733
2765
  let cssVal;
2734
2766
  if (typeof val === "number") {
2735
- cssVal = val === 0 || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
2767
+ const isCustomProp = prop.startsWith("--");
2768
+ cssVal = val === 0 || isCustomProp || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
2736
2769
  } else {
2737
2770
  cssVal = String(val);
2738
2771
  }
@@ -3192,6 +3225,23 @@ function toNumericScreens(screens) {
3192
3225
  }
3193
3226
  return out;
3194
3227
  }
3228
+ function pushTemplateLiteralBody(body, into) {
3229
+ const interpolationRe = /\$\{(?:[^{}]|\{[^{}]*\})*\}/g;
3230
+ let im;
3231
+ while ((im = interpolationRe.exec(body)) !== null) {
3232
+ pushClassLikeStrings(im[0], into);
3233
+ }
3234
+ pushTokens(body.replace(/\$\{(?:[^{}]|\{[^{}]*\})*\}/g, " "), into);
3235
+ }
3236
+ function pushClassLikeStrings(text, into) {
3237
+ const quotedStringRe = /(["'`])((?:(?!\1).)*)\1/g;
3238
+ let qm;
3239
+ while ((qm = quotedStringRe.exec(text)) !== null) {
3240
+ const [, quote, content] = qm;
3241
+ if (quote === "`") pushTemplateLiteralBody(content, into);
3242
+ else pushTokens(content, into);
3243
+ }
3244
+ }
3195
3245
  function extractClassStrings(code) {
3196
3246
  const found = /* @__PURE__ */ new Set();
3197
3247
  const simpleRe = /(?:className|kb)=["']([^"']+)["']/g;
@@ -3212,8 +3262,7 @@ function extractClassStrings(code) {
3212
3262
  i++;
3213
3263
  }
3214
3264
  jsxExprRe.lastIndex = i + 1;
3215
- const innerStrings = block.match(/["'`]([^"'`]{1,2000})["'`]/g) ?? [];
3216
- for (const s of innerStrings) pushTokens(s.slice(1, -1), found);
3265
+ pushClassLikeStrings(block, found);
3217
3266
  }
3218
3267
  const clsxCallRe = /(?:clsx|cn|classnames|cx)\(/g;
3219
3268
  while ((m = clsxCallRe.exec(code)) !== null) {
@@ -3230,19 +3279,16 @@ function extractClassStrings(code) {
3230
3279
  i++;
3231
3280
  }
3232
3281
  clsxCallRe.lastIndex = i + 1;
3233
- const innerStrings = block.match(/["'`]([^"'`]{1,2000})["'`]/g) ?? [];
3234
- for (const s of innerStrings) pushTokens(s.slice(1, -1), found);
3282
+ pushClassLikeStrings(block, found);
3235
3283
  }
3236
3284
  const templateRe = /`([^`]{1,2000})`/g;
3237
- while ((m = templateRe.exec(code)) !== null) {
3238
- const staticParts = m[1].replace(/\$\{(?:[^{}]|\{[^{}]*\})*\}/g, " ");
3239
- pushTokens(staticParts, found);
3240
- }
3285
+ while ((m = templateRe.exec(code)) !== null) pushTemplateLiteralBody(m[1], found);
3241
3286
  return [...found];
3242
3287
  }
3288
+ var NOT_A_CLASS_NAME_RE = /[${}]|-$|^[?:&|!"'=<>]+$/;
3243
3289
  function pushTokens(str, into) {
3244
3290
  for (const tok of splitClassTokens(str)) {
3245
- if (tok) into.add(tok);
3291
+ if (tok && !NOT_A_CLASS_NAME_RE.test(tok)) into.add(tok);
3246
3292
  }
3247
3293
  }
3248
3294
  function scanDir(dir, onFile) {
@@ -36,8 +36,31 @@ var BASE_RESET = [
36
36
  "a { color: inherit; text-decoration: none; }",
37
37
  "ul, ol { margin: 0; padding: 0; list-style: none; }",
38
38
  "img, video, svg { display: block; max-width: 100%; }",
39
- "input, textarea, select { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
39
+ // appearance: none is deliberately NOT applied to checkbox/radio/select below —
40
+ // stripping it hides their native checkmark/arrow with nothing rendered in its
41
+ // place, leaving an invisible checkbox or an arrow-less <select> that looks like
42
+ // plain text. Text-like inputs, textarea, and button don't have that problem
43
+ // (their native chrome is just a skin around content utilities can fully
44
+ // restyle), so they keep the blank-canvas treatment.
45
+ "input:not([type='checkbox']):not([type='radio']), textarea { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; line-height: inherit; }",
46
+ // Native checkbox/radio/range still get typography + spacing normalized, and
47
+ // accent-color re-themes their native indicator to the current text color
48
+ // instead of the browser/OS default blue, so they stay on-brand without
49
+ // needing to be rebuilt from scratch.
50
+ "input[type='checkbox'], input[type='radio'], input[type='range'] { margin: 0; font: inherit; accent-color: currentColor; }",
51
+ // select keeps its native chrome (border/background/arrow are all part of the
52
+ // same OS-drawn widget that appearance: none would blank out) — only typography
53
+ // and spacing are normalized so it still matches surrounding text.
54
+ "select { margin: 0; font: inherit; color: inherit; line-height: inherit; }",
40
55
  "button { appearance: none; -webkit-appearance: none; background: transparent; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; line-height: inherit; text-align: inherit; }",
56
+ "button, [role='button'] { cursor: pointer; }",
57
+ ":disabled { cursor: default; }",
58
+ "textarea { resize: vertical; }",
59
+ // Firefox renders placeholders at ~54% opacity by default; every other browser uses 1 —
60
+ // normalize to 1 so placeholder color is consistent and fully controlled by the placeholder: modifier.
61
+ "::placeholder { opacity: 1; }",
62
+ "input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-outer-spin-button { margin: 0; }",
63
+ "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button { -webkit-appearance: none; }",
41
64
  "fieldset { padding: 0; margin: 0; }",
42
65
  "table { border-collapse: collapse; border-spacing: 0; }"
43
66
  ].join("\n");
@@ -667,6 +690,13 @@ function hexToRgba(hex, alpha) {
667
690
  const [r, g, b] = rgb;
668
691
  return `rgba(${r},${g},${b},${alpha})`;
669
692
  }
693
+ function withOpacityVar(color, varName) {
694
+ if (!color.startsWith("#")) return color;
695
+ const rgb = parseHexRgb(color);
696
+ if (!rgb) return color;
697
+ const [r, g, b] = rgb;
698
+ return `rgba(${r},${g},${b},var(${varName},1))`;
699
+ }
670
700
  function resolveSpacing(value, negative, spacing, isArbitrary) {
671
701
  if (isArbitrary) {
672
702
  const resolved = getEffectiveIsWeb() ? value : toNativeValue(value);
@@ -1113,7 +1143,8 @@ var RESOLVERS = {
1113
1143
  // ── Background ─────────────────────────────────────────────────────────────
1114
1144
  bg: ({ value, isArbitrary }, { colors }) => {
1115
1145
  const color = resolveColor(value, colors, isArbitrary);
1116
- return color ? { backgroundColor: color } : null;
1146
+ if (!color) return null;
1147
+ return { backgroundColor: getEffectiveIsWeb() ? withOpacityVar(color, "--bg-opacity") : color };
1117
1148
  },
1118
1149
  "bg-opacity": ({ value, isArbitrary }, _) => {
1119
1150
  if (!getEffectiveIsWeb()) return null;
@@ -1130,10 +1161,11 @@ var RESOLVERS = {
1130
1161
  if (/^\d/.test(value) || /^(calc|min|max|clamp)/.test(value)) {
1131
1162
  return { fontSize: getEffectiveIsWeb() ? value : toNativeValue(value) };
1132
1163
  }
1133
- return { color: value };
1164
+ return { color: getEffectiveIsWeb() ? withOpacityVar(value, "--text-opacity") : value };
1134
1165
  }
1135
1166
  const color = resolveColor(value, colors, false);
1136
- return color ? { color } : null;
1167
+ if (!color) return null;
1168
+ return { color: getEffectiveIsWeb() ? withOpacityVar(color, "--text-opacity") : color };
1137
1169
  },
1138
1170
  "text-opacity": ({ value, isArbitrary }, _) => {
1139
1171
  if (!getEffectiveIsWeb()) return null;
@@ -2698,7 +2730,8 @@ function styleValueToCSS(styles, forceImportant = false) {
2698
2730
  if (val === void 0 || val === null || typeof val === "object" || RN_ONLY_PROPS.has(prop)) continue;
2699
2731
  let cssVal;
2700
2732
  if (typeof val === "number") {
2701
- cssVal = val === 0 || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
2733
+ const isCustomProp = prop.startsWith("--");
2734
+ cssVal = val === 0 || isCustomProp || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
2702
2735
  } else {
2703
2736
  cssVal = String(val);
2704
2737
  }
@@ -3158,6 +3191,23 @@ function toNumericScreens(screens) {
3158
3191
  }
3159
3192
  return out;
3160
3193
  }
3194
+ function pushTemplateLiteralBody(body, into) {
3195
+ const interpolationRe = /\$\{(?:[^{}]|\{[^{}]*\})*\}/g;
3196
+ let im;
3197
+ while ((im = interpolationRe.exec(body)) !== null) {
3198
+ pushClassLikeStrings(im[0], into);
3199
+ }
3200
+ pushTokens(body.replace(/\$\{(?:[^{}]|\{[^{}]*\})*\}/g, " "), into);
3201
+ }
3202
+ function pushClassLikeStrings(text, into) {
3203
+ const quotedStringRe = /(["'`])((?:(?!\1).)*)\1/g;
3204
+ let qm;
3205
+ while ((qm = quotedStringRe.exec(text)) !== null) {
3206
+ const [, quote, content] = qm;
3207
+ if (quote === "`") pushTemplateLiteralBody(content, into);
3208
+ else pushTokens(content, into);
3209
+ }
3210
+ }
3161
3211
  function extractClassStrings(code) {
3162
3212
  const found = /* @__PURE__ */ new Set();
3163
3213
  const simpleRe = /(?:className|kb)=["']([^"']+)["']/g;
@@ -3178,8 +3228,7 @@ function extractClassStrings(code) {
3178
3228
  i++;
3179
3229
  }
3180
3230
  jsxExprRe.lastIndex = i + 1;
3181
- const innerStrings = block.match(/["'`]([^"'`]{1,2000})["'`]/g) ?? [];
3182
- for (const s of innerStrings) pushTokens(s.slice(1, -1), found);
3231
+ pushClassLikeStrings(block, found);
3183
3232
  }
3184
3233
  const clsxCallRe = /(?:clsx|cn|classnames|cx)\(/g;
3185
3234
  while ((m = clsxCallRe.exec(code)) !== null) {
@@ -3196,19 +3245,16 @@ function extractClassStrings(code) {
3196
3245
  i++;
3197
3246
  }
3198
3247
  clsxCallRe.lastIndex = i + 1;
3199
- const innerStrings = block.match(/["'`]([^"'`]{1,2000})["'`]/g) ?? [];
3200
- for (const s of innerStrings) pushTokens(s.slice(1, -1), found);
3248
+ pushClassLikeStrings(block, found);
3201
3249
  }
3202
3250
  const templateRe = /`([^`]{1,2000})`/g;
3203
- while ((m = templateRe.exec(code)) !== null) {
3204
- const staticParts = m[1].replace(/\$\{(?:[^{}]|\{[^{}]*\})*\}/g, " ");
3205
- pushTokens(staticParts, found);
3206
- }
3251
+ while ((m = templateRe.exec(code)) !== null) pushTemplateLiteralBody(m[1], found);
3207
3252
  return [...found];
3208
3253
  }
3254
+ var NOT_A_CLASS_NAME_RE = /[${}]|-$|^[?:&|!"'=<>]+$/;
3209
3255
  function pushTokens(str, into) {
3210
3256
  for (const tok of splitClassTokens(str)) {
3211
- if (tok) into.add(tok);
3257
+ if (tok && !NOT_A_CLASS_NAME_RE.test(tok)) into.add(tok);
3212
3258
  }
3213
3259
  }
3214
3260
  function scanDir(dir, onFile) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "description": "React / React Native components and hooks for the Kbach framework",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./dist/index.js",