@kbach/react 0.2.7 → 0.2.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.
package/README.md CHANGED
@@ -119,14 +119,14 @@ function Badge() {
119
119
  }
120
120
  ```
121
121
 
122
- ### tw(classes)
122
+ ### kb(classes)
123
123
 
124
- Resolve classes outside a component (static contexts).
124
+ Resolve classes outside a component (static contexts, StyleSheet.create, etc.).
125
125
 
126
126
  ```js
127
- import { tw } from '@kbach/react';
127
+ import { kb } from '@kbach/react';
128
128
 
129
- const cardStyle = tw('bg-white p-4 rounded-xl') as React.CSSProperties;
129
+ const cardStyle = kb('bg-white p-4 rounded-xl') as React.CSSProperties;
130
130
  ```
131
131
 
132
132
  ### cx(...classes)
@@ -148,16 +148,59 @@ import { cx } from '@kbach/react';
148
148
  ```js
149
149
  import { useTheme } from '@kbach/react';
150
150
 
151
- const { mode, resolvedMode, isDark, setMode, toggle } = useTheme();
151
+ const { mode, resolvedMode, isDark, setMode, toggle, config } = useTheme();
152
152
  ```
153
153
 
154
154
  | Value | Type | Description |
155
155
  |---|---|---|
156
- | mode | `'light' \| 'dark' \| 'system'` | User-selected mode |
157
- | resolvedMode | `'light' \| 'dark'` | Resolved after system lookup |
158
- | isDark | boolean | Shorthand for `resolvedMode === 'dark'` |
159
- | setMode | function | Set mode explicitly |
160
- | toggle | function | Toggle between light and dark |
156
+ | `mode` | `'light' \| 'dark' \| 'system'` | User-selected mode |
157
+ | `resolvedMode` | `'light' \| 'dark'` | Resolved after system lookup |
158
+ | `isDark` | `boolean` | Shorthand for `resolvedMode === 'dark'` |
159
+ | `setMode` | `fn` | Set mode explicitly |
160
+ | `toggle` | `fn` | Toggle between light and dark |
161
+ | `config` | `ResolvedConfig` | Full resolved config (theme, darkMode, plugins) |
162
+
163
+ ### useIsDark()
164
+
165
+ Shorthand hook when you only need the dark-mode boolean.
166
+
167
+ ```js
168
+ import { useIsDark } from '@kbach/react';
169
+
170
+ const isDark = useIsDark();
171
+ ```
172
+
173
+ ### useColors()
174
+
175
+ Returns the active theme's color palette as a smart proxy. Supports shade access, the `/opacity` modifier, flat colors with opacity, and arbitrary CSS color manipulation.
176
+
177
+ ```js
178
+ import { useColors } from '@kbach/react';
179
+
180
+ const colors = useColors();
181
+
182
+ // Shade access
183
+ colors.blue[6] // '#6c87b6'
184
+ colors.red[11] // '#3e0606'
185
+
186
+ // Shade + opacity (0–100)
187
+ colors.blue['6/50'] // 'rgba(108,135,182,0.5)'
188
+ colors.slate['3/10'] // 'rgba(221,227,242,0.1)'
189
+
190
+ // Flat colors
191
+ colors.white // '#ffffff'
192
+ colors.transparent // 'transparent'
193
+
194
+ // Flat color + opacity
195
+ colors['white/20'] // 'rgba(255,255,255,0.2)'
196
+ colors['black/80'] // 'rgba(0,0,0,0.8)'
197
+
198
+ // Arbitrary CSS color with opacity
199
+ colors.alpha('#ff6b35', 60) // 'rgba(255,107,53,0.6)'
200
+ colors.alpha('rgb(100,200,100)', 30) // 'rgba(100,200,100,0.3)'
201
+ ```
202
+
203
+ Common uses: chart libraries that need raw hex values, dynamic inline styles, color pickers that mirror the theme palette.
161
204
 
162
205
  ### ThemeToggle
163
206
 
@@ -5,7 +5,7 @@ import {
5
5
  isWeb,
6
6
  resolve,
7
7
  useGlobalDarkMode
8
- } from "./chunk-LYGD2GSW.mjs";
8
+ } from "./chunk-V5KHX4CZ.mjs";
9
9
 
10
10
  // src/jsx-runtime.tsx
11
11
  import { jsx as _jsx, jsxs as _jsxs, Fragment } from "react/jsx-runtime";
@@ -529,6 +529,13 @@ function escapeCSSSelector(cls) {
529
529
  }
530
530
 
531
531
  // src/core/parser.ts
532
+ var PLUGIN_MODIFIERS = /* @__PURE__ */ new Set();
533
+ function registerModifier(name) {
534
+ PLUGIN_MODIFIERS.add(name);
535
+ }
536
+ function clearPluginModifiers() {
537
+ PLUGIN_MODIFIERS.clear();
538
+ }
532
539
  var MODIFIERS = /* @__PURE__ */ new Set([
533
540
  // Theme
534
541
  "dark",
@@ -958,7 +965,7 @@ function parseClass(className) {
958
965
  const colonIdx = findOuterColon(remaining);
959
966
  if (colonIdx === -1) break;
960
967
  const candidate = remaining.slice(0, colonIdx);
961
- if (MODIFIERS.has(candidate)) {
968
+ if (MODIFIERS.has(candidate) || PLUGIN_MODIFIERS.has(candidate)) {
962
969
  modifiers.push(candidate);
963
970
  remaining = remaining.slice(colonIdx + 1);
964
971
  } else {
@@ -1424,43 +1431,64 @@ var RESOLVERS = {
1424
1431
  // ── Border width ───────────────────────────────────────────────────────────
1425
1432
  border: ({ value, isArbitrary }, { colors, borderWidth, spacing }) => {
1426
1433
  if (!value) return { borderWidth: borderWidth["DEFAULT"] ?? 1 };
1427
- const color = resolveColor(value, colors, isArbitrary);
1428
- if (color) return { borderColor: color };
1429
1434
  if (isArbitrary) {
1430
1435
  const w2 = toNativeValue(value);
1431
- return { borderWidth: typeof w2 === "number" ? w2 : 1 };
1436
+ if (typeof w2 === "number") return { borderWidth: w2 };
1437
+ return { borderColor: value };
1432
1438
  }
1439
+ const color = resolveColor(value, colors, false);
1440
+ if (color) return { borderColor: color };
1433
1441
  const w = borderWidth[value] ?? spacing[value];
1434
1442
  if (w !== void 0) return { borderWidth: typeof w === "number" ? w : parseFloat(String(w)) };
1435
1443
  return null;
1436
1444
  },
1437
1445
  "border-t": ({ value, isArbitrary }, { colors, borderWidth }) => {
1438
1446
  if (!value) return { borderTopWidth: 1 };
1439
- const color = resolveColor(value, colors, isArbitrary);
1447
+ if (isArbitrary) {
1448
+ const w2 = toNativeValue(value);
1449
+ if (typeof w2 === "number") return { borderTopWidth: w2 };
1450
+ return { borderTopColor: value };
1451
+ }
1452
+ const color = resolveColor(value, colors, false);
1440
1453
  if (color) return { borderTopColor: color };
1441
- const w = borderWidth[value] ?? (isArbitrary ? toNativeValue(value) : null);
1442
- return w !== null ? { borderTopWidth: typeof w === "number" ? w : parseFloat(String(w)) } : null;
1454
+ const w = borderWidth[value];
1455
+ return w !== void 0 ? { borderTopWidth: w } : null;
1443
1456
  },
1444
1457
  "border-r": ({ value, isArbitrary }, { colors, borderWidth }) => {
1445
1458
  if (!value) return { borderRightWidth: 1 };
1446
- const color = resolveColor(value, colors, isArbitrary);
1459
+ if (isArbitrary) {
1460
+ const w2 = toNativeValue(value);
1461
+ if (typeof w2 === "number") return { borderRightWidth: w2 };
1462
+ return { borderRightColor: value };
1463
+ }
1464
+ const color = resolveColor(value, colors, false);
1447
1465
  if (color) return { borderRightColor: color };
1448
- const w = borderWidth[value] ?? (isArbitrary ? toNativeValue(value) : null);
1449
- return w !== null ? { borderRightWidth: typeof w === "number" ? w : parseFloat(String(w)) } : null;
1466
+ const w = borderWidth[value];
1467
+ return w !== void 0 ? { borderRightWidth: w } : null;
1450
1468
  },
1451
1469
  "border-b": ({ value, isArbitrary }, { colors, borderWidth }) => {
1452
1470
  if (!value) return { borderBottomWidth: 1 };
1453
- const color = resolveColor(value, colors, isArbitrary);
1471
+ if (isArbitrary) {
1472
+ const w2 = toNativeValue(value);
1473
+ if (typeof w2 === "number") return { borderBottomWidth: w2 };
1474
+ return { borderBottomColor: value };
1475
+ }
1476
+ const color = resolveColor(value, colors, false);
1454
1477
  if (color) return { borderBottomColor: color };
1455
- const w = borderWidth[value] ?? (isArbitrary ? toNativeValue(value) : null);
1456
- return w !== null ? { borderBottomWidth: typeof w === "number" ? w : parseFloat(String(w)) } : null;
1478
+ const w = borderWidth[value];
1479
+ return w !== void 0 ? { borderBottomWidth: w } : null;
1457
1480
  },
1458
1481
  "border-l": ({ value, isArbitrary }, { colors, borderWidth }) => {
1459
1482
  if (!value) return { borderLeftWidth: 1 };
1460
- const color = resolveColor(value, colors, isArbitrary);
1483
+ if (isArbitrary) {
1484
+ const w2 = toNativeValue(value);
1485
+ if (typeof w2 === "number") return { borderLeftWidth: w2 };
1486
+ return { borderLeftColor: value };
1487
+ }
1488
+ const color = resolveColor(value, colors, false);
1461
1489
  if (color) return { borderLeftColor: color };
1462
- const w = borderWidth[value] ?? (isArbitrary ? toNativeValue(value) : null);
1463
- return w !== null ? { borderLeftWidth: typeof w === "number" ? w : parseFloat(String(w)) } : null;
1490
+ const w = borderWidth[value];
1491
+ return w !== void 0 ? { borderLeftWidth: w } : null;
1464
1492
  },
1465
1493
  // ── Border radius ──────────────────────────────────────────────────────────
1466
1494
  rounded: ({ value, isArbitrary }, { borderRadius }) => {
@@ -1525,7 +1553,6 @@ var RESOLVERS = {
1525
1553
  return { flexShrink: isNaN(n) ? 1 : n };
1526
1554
  },
1527
1555
  order: ({ value, isArbitrary }, _) => {
1528
- if (isArbitrary) return { order: parseInt(value) };
1529
1556
  const n = parseInt(value);
1530
1557
  return isNaN(n) ? null : { order: n };
1531
1558
  },
@@ -1573,9 +1600,15 @@ var RESOLVERS = {
1573
1600
  },
1574
1601
  // ── Z-index ────────────────────────────────────────────────────────────────
1575
1602
  z: ({ value, isArbitrary }, { zIndex }) => {
1576
- if (isArbitrary) return { zIndex: parseInt(value) };
1603
+ if (isArbitrary) {
1604
+ const n2 = parseInt(value);
1605
+ return isNaN(n2) ? null : { zIndex: n2 };
1606
+ }
1577
1607
  const v = zIndex[value];
1578
- if (v !== void 0) return { zIndex: v };
1608
+ if (v !== void 0) {
1609
+ if (v === "auto") return isWeb ? { zIndex: "auto" } : null;
1610
+ return { zIndex: v };
1611
+ }
1579
1612
  const n = parseInt(value);
1580
1613
  return isNaN(n) ? null : { zIndex: n };
1581
1614
  },
@@ -1618,25 +1651,30 @@ var RESOLVERS = {
1618
1651
  return shadow[key] ?? null;
1619
1652
  },
1620
1653
  // ── Scale ──────────────────────────────────────────────────────────────────
1654
+ // Non-arbitrary: scale-150 → value='150' → 150/100 = 1.5
1655
+ // Arbitrary: scale-[1.5] → value='1.5' → used as-is (already the factor)
1621
1656
  scale: ({ value, isArbitrary }) => {
1622
- const n = isArbitrary ? parseFloat(value) / 100 : parseFloat(value) / 100;
1657
+ const n = isArbitrary ? parseFloat(value) : parseFloat(value) / 100;
1623
1658
  if (isNaN(n)) return null;
1624
1659
  return isWeb ? { transform: `scale(${n})` } : { transform: [{ scale: n }] };
1625
1660
  },
1626
1661
  "scale-x": ({ value, isArbitrary }) => {
1627
- const n = isArbitrary ? parseFloat(value) / 100 : parseFloat(value) / 100;
1662
+ const n = isArbitrary ? parseFloat(value) : parseFloat(value) / 100;
1628
1663
  if (isNaN(n)) return null;
1629
1664
  return isWeb ? { transform: `scaleX(${n})` } : { transform: [{ scaleX: n }] };
1630
1665
  },
1631
1666
  "scale-y": ({ value, isArbitrary }) => {
1632
- const n = isArbitrary ? parseFloat(value) / 100 : parseFloat(value) / 100;
1667
+ const n = isArbitrary ? parseFloat(value) : parseFloat(value) / 100;
1633
1668
  if (isNaN(n)) return null;
1634
1669
  return isWeb ? { transform: `scaleY(${n})` } : { transform: [{ scaleY: n }] };
1635
1670
  },
1636
1671
  // ── Rotate ─────────────────────────────────────────────────────────────────
1637
1672
  rotate: ({ value, negative, isArbitrary }) => {
1638
- const raw = isArbitrary ? value : `${value}deg`;
1639
- const deg = parseFloat(raw);
1673
+ if (isArbitrary) {
1674
+ const finalValue = negative ? `-${value}` : value;
1675
+ return isWeb ? { transform: `rotate(${finalValue})` } : { transform: [{ rotate: finalValue }] };
1676
+ }
1677
+ const deg = parseFloat(value);
1640
1678
  if (isNaN(deg)) return null;
1641
1679
  const finalDeg = negative ? -deg : deg;
1642
1680
  return isWeb ? { transform: `rotate(${finalDeg}deg)` } : { transform: [{ rotate: `${finalDeg}deg` }] };
@@ -1645,18 +1683,31 @@ var RESOLVERS = {
1645
1683
  "translate-x": ({ value, negative, isArbitrary }, { spacing }) => {
1646
1684
  const v = resolveSpacing(value, negative, spacing, isArbitrary);
1647
1685
  if (v === null) return null;
1648
- return isWeb ? { transform: `translateX(${v})` } : { transform: [{ translateX: typeof v === "string" ? parseFloat(v) : v }] };
1686
+ if (isWeb) return { transform: `translateX(${v})` };
1687
+ if (typeof v === "string") {
1688
+ const n = parseFloat(v);
1689
+ return isNaN(n) ? null : { transform: [{ translateX: n }] };
1690
+ }
1691
+ return { transform: [{ translateX: v }] };
1649
1692
  },
1650
1693
  "translate-y": ({ value, negative, isArbitrary }, { spacing }) => {
1651
1694
  const v = resolveSpacing(value, negative, spacing, isArbitrary);
1652
1695
  if (v === null) return null;
1653
- return isWeb ? { transform: `translateY(${v})` } : { transform: [{ translateY: typeof v === "string" ? parseFloat(v) : v }] };
1696
+ if (isWeb) return { transform: `translateY(${v})` };
1697
+ if (typeof v === "string") {
1698
+ const n = parseFloat(v);
1699
+ return isNaN(n) ? null : { transform: [{ translateY: n }] };
1700
+ }
1701
+ return { transform: [{ translateY: v }] };
1654
1702
  },
1655
1703
  // ── Aspect ratio ───────────────────────────────────────────────────────────
1656
1704
  aspect: ({ value, isArbitrary }) => {
1657
1705
  if (isArbitrary) return { aspectRatio: value };
1658
1706
  const presets = { auto: "auto", square: 1, video: 16 / 9 };
1659
- return value in presets ? { aspectRatio: presets[value] } : null;
1707
+ if (!(value in presets)) return null;
1708
+ const v = presets[value];
1709
+ if (v === "auto") return isWeb ? { aspectRatio: "auto" } : null;
1710
+ return { aspectRatio: v };
1660
1711
  },
1661
1712
  // ── Transition (web-only; use Animated API on native) ─────────────────────
1662
1713
  transition: ({ value }) => {
@@ -1733,18 +1784,25 @@ var RESOLVERS = {
1733
1784
  }
1734
1785
  };
1735
1786
  function resolveUtility(parsed, theme) {
1736
- if (!parsed.value && parsed.utility in STANDALONE) {
1737
- return STANDALONE[parsed.utility] ?? null;
1787
+ if (!parsed.value) {
1788
+ if (parsed.utility in PLUGIN_STANDALONE) return PLUGIN_STANDALONE[parsed.utility] ?? null;
1789
+ if (parsed.utility in STANDALONE) return STANDALONE[parsed.utility] ?? null;
1738
1790
  }
1739
- const resolver = RESOLVERS[parsed.utility];
1791
+ const resolver = PLUGIN_RESOLVERS[parsed.utility] ?? RESOLVERS[parsed.utility];
1740
1792
  if (resolver) return resolver(parsed, theme);
1741
1793
  return null;
1742
1794
  }
1743
- function getStandaloneMap() {
1744
- return STANDALONE;
1795
+ var PLUGIN_STANDALONE = {};
1796
+ var PLUGIN_RESOLVERS = {};
1797
+ function clearPluginUtilities() {
1798
+ for (const key of Object.keys(PLUGIN_STANDALONE)) delete PLUGIN_STANDALONE[key];
1799
+ for (const key of Object.keys(PLUGIN_RESOLVERS)) delete PLUGIN_RESOLVERS[key];
1800
+ }
1801
+ function getPluginStandaloneMap() {
1802
+ return PLUGIN_STANDALONE;
1745
1803
  }
1746
- function getResolverMap() {
1747
- return RESOLVERS;
1804
+ function getPluginResolverMap() {
1805
+ return PLUGIN_RESOLVERS;
1748
1806
  }
1749
1807
 
1750
1808
  // src/core/cache.ts
@@ -1797,8 +1855,42 @@ function getStyleEl() {
1797
1855
  function injectRule(rule) {
1798
1856
  if (_injectedRules.has(rule)) return;
1799
1857
  _injectedRules.add(rule);
1800
- getStyleEl().sheet?.insertRule(rule, getStyleEl().sheet.cssRules.length);
1858
+ try {
1859
+ getStyleEl().sheet?.insertRule(rule, getStyleEl().sheet.cssRules.length);
1860
+ } catch {
1861
+ }
1801
1862
  }
1863
+ var MODIFIER_TO_PSEUDO = {
1864
+ hover: ":hover",
1865
+ focus: ":focus",
1866
+ "focus-within": ":focus-within",
1867
+ "focus-visible": ":focus-visible",
1868
+ active: ":active",
1869
+ pressed: ":active",
1870
+ disabled: ":disabled",
1871
+ checked: ":checked",
1872
+ visited: ":visited",
1873
+ placeholder: "::placeholder",
1874
+ first: ":first-child",
1875
+ last: ":last-child",
1876
+ odd: ":nth-child(odd)",
1877
+ even: ":nth-child(even)",
1878
+ only: ":only-child",
1879
+ "not-hover": ":not(:hover)",
1880
+ "not-focus": ":not(:focus)",
1881
+ "not-active": ":not(:active)",
1882
+ "not-pressed": ":not(:active)",
1883
+ "not-disabled": ":not(:disabled)",
1884
+ "not-checked": ":not(:checked)",
1885
+ "not-visited": ":not(:visited)"
1886
+ };
1887
+ var GROUP_PEER_PREFIX = {
1888
+ "group-hover": ".group:hover",
1889
+ "group-focus": ".group:focus",
1890
+ "peer-hover": ".peer:hover ~",
1891
+ "peer-focus": ".peer:focus ~"
1892
+ };
1893
+ var MODE_MODS = /* @__PURE__ */ new Set(["dark", "light", "not-dark", "not-light"]);
1802
1894
  function injectClassRule(cls, bucketKey, styles, darkMode) {
1803
1895
  const cssDecls = styleValueToCSS(styles);
1804
1896
  if (!cssDecls) return;
@@ -1808,10 +1900,14 @@ function injectClassRule(cls, bucketKey, styles, darkMode) {
1808
1900
  return;
1809
1901
  }
1810
1902
  const mods = bucketKey.split(":");
1811
- const pseudos = mods.filter((m) => !["dark", "light"].includes(m));
1812
- const hasDark = mods.includes("dark");
1813
- const pseudoSuffix = pseudos.map((p) => `:${p}`).join("");
1814
- const selector = `.${escaped}${pseudoSuffix}`;
1903
+ const hasDark = mods.includes("dark") || mods.includes("not-light");
1904
+ const hasLight = mods.includes("light") || mods.includes("not-dark");
1905
+ const ancestorMods = mods.filter((m) => m in GROUP_PEER_PREFIX);
1906
+ const pseudoMods = mods.filter((m) => !MODE_MODS.has(m) && !(m in GROUP_PEER_PREFIX));
1907
+ const pseudoSuffix = pseudoMods.map((p) => MODIFIER_TO_PSEUDO[p] ?? `:${p}`).join("");
1908
+ const elementSelector = `.${escaped}${pseudoSuffix}`;
1909
+ const ancestorPrefix = ancestorMods.length ? ancestorMods.map((m) => GROUP_PEER_PREFIX[m]).join(" ") + " " : "";
1910
+ const selector = `${ancestorPrefix}${elementSelector}`;
1815
1911
  let rule;
1816
1912
  if (hasDark) {
1817
1913
  if (darkMode === "media") {
@@ -1821,13 +1917,54 @@ function injectClassRule(cls, bucketKey, styles, darkMode) {
1821
1917
  } else {
1822
1918
  rule = `[data-theme="dark"] ${selector} { ${cssDecls} }`;
1823
1919
  }
1920
+ } else if (hasLight) {
1921
+ if (darkMode === "media") {
1922
+ rule = `@media (prefers-color-scheme: light) { ${selector} { ${cssDecls} } }`;
1923
+ } else if (darkMode === "class") {
1924
+ rule = `:not(.dark) ${selector} { ${cssDecls} }`;
1925
+ } else {
1926
+ rule = `[data-theme="light"] ${selector} { ${cssDecls} }`;
1927
+ }
1824
1928
  } else {
1825
1929
  rule = `${selector} { ${cssDecls} }`;
1826
1930
  }
1827
1931
  injectRule(rule);
1828
1932
  }
1933
+ var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
1934
+ "shadowColor",
1935
+ "shadowOffset",
1936
+ "shadowOpacity",
1937
+ "shadowRadius",
1938
+ "elevation",
1939
+ "includeFontPadding",
1940
+ "textAlignVertical",
1941
+ "writingDirection"
1942
+ ]);
1943
+ var CSS_UNITLESS = /* @__PURE__ */ new Set([
1944
+ "opacity",
1945
+ "fontWeight",
1946
+ "flex",
1947
+ "flexGrow",
1948
+ "flexShrink",
1949
+ "order",
1950
+ "zIndex",
1951
+ "aspectRatio",
1952
+ "columnCount",
1953
+ "lineHeight"
1954
+ ]);
1829
1955
  function styleValueToCSS(styles) {
1830
- return Object.entries(styles).filter(([, v]) => v !== void 0 && v !== null && typeof v !== "object").map(([prop, val]) => `${camelToKebab(prop)}: ${val}`).join("; ");
1956
+ return Object.entries(styles).filter(
1957
+ ([prop, v]) => v !== void 0 && v !== null && typeof v !== "object" && !RN_ONLY_PROPS.has(prop)
1958
+ ).map(([prop, val]) => {
1959
+ const cssProp = camelToKebab(prop);
1960
+ let cssVal;
1961
+ if (typeof val === "number") {
1962
+ cssVal = val === 0 || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
1963
+ } else {
1964
+ cssVal = String(val);
1965
+ }
1966
+ return `${cssProp}: ${cssVal}`;
1967
+ }).join("; ");
1831
1968
  }
1832
1969
  function camelToKebab(str) {
1833
1970
  return str.replace(/([A-Z])/g, "-$1").toLowerCase();
@@ -1913,6 +2050,9 @@ function matchesMods(mods, isDark, state) {
1913
2050
  }
1914
2051
  });
1915
2052
  }
2053
+ function clearCache() {
2054
+ styleCache.clear();
2055
+ }
1916
2056
 
1917
2057
  // src/core/config.ts
1918
2058
  function deepMerge(base, override) {
@@ -1968,20 +2108,24 @@ function buildConfig(userConfig) {
1968
2108
  theme,
1969
2109
  plugins: userConfig.plugins ?? []
1970
2110
  };
2111
+ clearPluginUtilities();
2112
+ clearPluginModifiers();
2113
+ const pluginAPI = makePluginAPI(resolved.theme);
1971
2114
  for (const plugin of resolved.plugins) {
1972
- plugin(makePluginAPI(resolved.theme));
2115
+ plugin(pluginAPI);
1973
2116
  }
1974
2117
  return resolved;
1975
2118
  }
1976
2119
  function makePluginAPI(theme) {
1977
- const standalone = getStandaloneMap();
1978
- const resolvers = getResolverMap();
2120
+ const standalone = getPluginStandaloneMap();
2121
+ const resolvers = getPluginResolverMap();
1979
2122
  return {
1980
2123
  addUtility(name, styles) {
1981
2124
  standalone[name] = styles;
1982
2125
  },
1983
- addVariant(name, _selector) {
1984
- customVariants[name] = _selector;
2126
+ addVariant(name, selector) {
2127
+ customVariants[name] = selector;
2128
+ registerModifier(name);
1985
2129
  },
1986
2130
  theme(path, defaultValue) {
1987
2131
  const parts = path.replace(/\[([^\]]+)\]/g, ".$1").split(".");
@@ -2005,6 +2149,7 @@ function onConfigChange(listener) {
2005
2149
  function updateConfig(userConfig) {
2006
2150
  const store = getConfigStore();
2007
2151
  store.resolved = buildConfig(userConfig);
2152
+ clearCache();
2008
2153
  for (const listener of store.listeners) {
2009
2154
  listener(store.resolved);
2010
2155
  }
@@ -2117,8 +2262,8 @@ var InteractiveWrapper = forwardRef(
2117
2262
  ...restWithoutChildren,
2118
2263
  style: finalStyle,
2119
2264
  ...isWeb && className ? { className } : {},
2120
- onPressIn: handlePressIn,
2121
- onPressOut: handlePressOut,
2265
+ // onPressIn / onPressOut are React Native-only; skip on web to avoid DOM warnings.
2266
+ ...isWeb ? {} : { onPressIn: handlePressIn, onPressOut: handlePressOut },
2122
2267
  onMouseEnter: handleMouseEnter,
2123
2268
  onMouseLeave: handleMouseLeave,
2124
2269
  onFocus: handleFocus,
package/dist/index.d.mts CHANGED
@@ -139,6 +139,25 @@ interface ThemeContextValue {
139
139
  }
140
140
  declare const ThemeContext: React.Context<ThemeContextValue | null>;
141
141
  declare function useTheme(): ThemeContextValue;
142
+ declare function useIsDark(): boolean;
143
+
144
+ interface ColorScale {
145
+ /** `colors.blue[6]` → raw hex string */
146
+ readonly [shade: number]: string;
147
+ /** `colors.blue['6/50']` → shade 6 at 50% opacity */
148
+ readonly [key: string]: string;
149
+ }
150
+ interface ColorsAPI {
151
+ /** Named color scale (e.g. `colors.blue`) or flat value (e.g. `colors.white`) */
152
+ readonly [color: string]: string | ColorScale | ColorsAPI['alpha'];
153
+ /** Flat color with opacity shorthand: `colors['white/50']` */
154
+ /** Apply opacity to any CSS color string.
155
+ * `colors.alpha('#3b82f6', 50)` → `'rgba(59,130,246,0.5)'`
156
+ * `colors.alpha('rgb(0,0,0)', 10)` → `'rgba(0,0,0,0.1)'` */
157
+ alpha(color: string, opacity: number): string;
158
+ }
159
+ declare function wrapColors(rawColors: ThemeColors): ColorsAPI;
160
+ declare function useColors(): ColorsAPI;
142
161
 
143
162
  interface ThemeProviderProps {
144
163
  children: ReactNode;
@@ -249,7 +268,7 @@ declare function useResolvedStyle(classString: string | string[]): ResolvedStyle
249
268
 
250
269
  /**
251
270
  * Subscribe to the global dark-mode store with React's concurrent-safe
252
- * useSyncExternalStore so that any component using className/tw re-renders
271
+ * useSyncExternalStore so that any component using className/kb re-renders
253
272
  * immediately when the theme changes — without needing ThemeContext.
254
273
  */
255
274
  declare function useGlobalDarkMode(): boolean;
@@ -272,7 +291,7 @@ interface InteractiveWrapperProps {
272
291
  onBlur?: (...args: any[]) => void;
273
292
  }
274
293
  /**
275
- * Thin wrapper rendered automatically by the JSX runtime whenever a className/tw
294
+ * Thin wrapper rendered automatically by the JSX runtime whenever a className/kb
276
295
  * string contains interactive modifiers (hover:, pressed:, focus:, active:, …).
277
296
  *
278
297
  * Manages interaction state locally and flattens the correct style bucket on
@@ -291,20 +310,20 @@ declare const InteractiveWrapper: React__default.ForwardRefExoticComponent<Inter
291
310
  *
292
311
  * ```ts
293
312
  * // Inside a component use useStyles() instead.
294
- * // tw() is useful for StyleSheet.create() calls and static values.
313
+ * // kb() is useful for StyleSheet.create() calls and static values.
295
314
  *
296
315
  * const styles = StyleSheet.create({
297
- * container: tw('flex-1 bg-white p-4') as any,
316
+ * container: kb('flex-1 bg-white p-4') as any,
298
317
  * });
299
318
  *
300
319
  * // Web: use as className
301
- * <div className={tw('bg-white dark:bg-gray-900 p-4') as string} />
320
+ * <div className={kb('bg-white dark:bg-gray-900 p-4') as string} />
302
321
  * ```
303
322
  *
304
323
  * @param classString Space-separated utility classes
305
324
  * @param isDark Whether dark mode is active (default: false)
306
325
  */
307
- declare function tw(classString: string, isDark?: boolean): StyleValue | string;
326
+ declare function kb(classString: string, isDark?: boolean): StyleValue | string;
308
327
  /**
309
328
  * Conditionally join class names. Falsy values are ignored.
310
329
  *
@@ -315,4 +334,4 @@ declare function tw(classString: string, isDark?: boolean): StyleValue | string;
315
334
  */
316
335
  declare function cx(...classes: Array<string | false | null | undefined>): string;
317
336
 
318
- export { type FrameworkConfig, type InteractionState, InteractiveWrapper, type InteractiveWrapperProps, type ParsedClass, type PluginAPI, type ResolvedConfig, type ResolvedStyle, type StyleValue, type StyledProps, type ThemeConfig, ThemeContext, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, ThemeToggle, type ThemeToggleProps, type ToggleVariant, buildConfig, cx, defaultColors, defaultTheme, flatten, getConfig, parseClass, parseClasses, resolve, styled, tw, updateConfig, useGlobalDarkMode, useResolvedStyle, useStyles, useTheme };
337
+ export { type ColorScale, type ColorsAPI, type FrameworkConfig, type InteractionState, InteractiveWrapper, type InteractiveWrapperProps, type ParsedClass, type PluginAPI, type ResolvedConfig, type ResolvedStyle, type StyleValue, type StyledProps, type ThemeConfig, ThemeContext, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, ThemeToggle, type ThemeToggleProps, type ToggleVariant, buildConfig, cx, defaultColors, defaultTheme, flatten, getConfig, kb, parseClass, parseClasses, resolve, styled, updateConfig, useColors, useGlobalDarkMode, useIsDark, useResolvedStyle, useStyles, useTheme, wrapColors };
package/dist/index.d.ts CHANGED
@@ -139,6 +139,25 @@ interface ThemeContextValue {
139
139
  }
140
140
  declare const ThemeContext: React.Context<ThemeContextValue | null>;
141
141
  declare function useTheme(): ThemeContextValue;
142
+ declare function useIsDark(): boolean;
143
+
144
+ interface ColorScale {
145
+ /** `colors.blue[6]` → raw hex string */
146
+ readonly [shade: number]: string;
147
+ /** `colors.blue['6/50']` → shade 6 at 50% opacity */
148
+ readonly [key: string]: string;
149
+ }
150
+ interface ColorsAPI {
151
+ /** Named color scale (e.g. `colors.blue`) or flat value (e.g. `colors.white`) */
152
+ readonly [color: string]: string | ColorScale | ColorsAPI['alpha'];
153
+ /** Flat color with opacity shorthand: `colors['white/50']` */
154
+ /** Apply opacity to any CSS color string.
155
+ * `colors.alpha('#3b82f6', 50)` → `'rgba(59,130,246,0.5)'`
156
+ * `colors.alpha('rgb(0,0,0)', 10)` → `'rgba(0,0,0,0.1)'` */
157
+ alpha(color: string, opacity: number): string;
158
+ }
159
+ declare function wrapColors(rawColors: ThemeColors): ColorsAPI;
160
+ declare function useColors(): ColorsAPI;
142
161
 
143
162
  interface ThemeProviderProps {
144
163
  children: ReactNode;
@@ -249,7 +268,7 @@ declare function useResolvedStyle(classString: string | string[]): ResolvedStyle
249
268
 
250
269
  /**
251
270
  * Subscribe to the global dark-mode store with React's concurrent-safe
252
- * useSyncExternalStore so that any component using className/tw re-renders
271
+ * useSyncExternalStore so that any component using className/kb re-renders
253
272
  * immediately when the theme changes — without needing ThemeContext.
254
273
  */
255
274
  declare function useGlobalDarkMode(): boolean;
@@ -272,7 +291,7 @@ interface InteractiveWrapperProps {
272
291
  onBlur?: (...args: any[]) => void;
273
292
  }
274
293
  /**
275
- * Thin wrapper rendered automatically by the JSX runtime whenever a className/tw
294
+ * Thin wrapper rendered automatically by the JSX runtime whenever a className/kb
276
295
  * string contains interactive modifiers (hover:, pressed:, focus:, active:, …).
277
296
  *
278
297
  * Manages interaction state locally and flattens the correct style bucket on
@@ -291,20 +310,20 @@ declare const InteractiveWrapper: React__default.ForwardRefExoticComponent<Inter
291
310
  *
292
311
  * ```ts
293
312
  * // Inside a component use useStyles() instead.
294
- * // tw() is useful for StyleSheet.create() calls and static values.
313
+ * // kb() is useful for StyleSheet.create() calls and static values.
295
314
  *
296
315
  * const styles = StyleSheet.create({
297
- * container: tw('flex-1 bg-white p-4') as any,
316
+ * container: kb('flex-1 bg-white p-4') as any,
298
317
  * });
299
318
  *
300
319
  * // Web: use as className
301
- * <div className={tw('bg-white dark:bg-gray-900 p-4') as string} />
320
+ * <div className={kb('bg-white dark:bg-gray-900 p-4') as string} />
302
321
  * ```
303
322
  *
304
323
  * @param classString Space-separated utility classes
305
324
  * @param isDark Whether dark mode is active (default: false)
306
325
  */
307
- declare function tw(classString: string, isDark?: boolean): StyleValue | string;
326
+ declare function kb(classString: string, isDark?: boolean): StyleValue | string;
308
327
  /**
309
328
  * Conditionally join class names. Falsy values are ignored.
310
329
  *
@@ -315,4 +334,4 @@ declare function tw(classString: string, isDark?: boolean): StyleValue | string;
315
334
  */
316
335
  declare function cx(...classes: Array<string | false | null | undefined>): string;
317
336
 
318
- export { type FrameworkConfig, type InteractionState, InteractiveWrapper, type InteractiveWrapperProps, type ParsedClass, type PluginAPI, type ResolvedConfig, type ResolvedStyle, type StyleValue, type StyledProps, type ThemeConfig, ThemeContext, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, ThemeToggle, type ThemeToggleProps, type ToggleVariant, buildConfig, cx, defaultColors, defaultTheme, flatten, getConfig, parseClass, parseClasses, resolve, styled, tw, updateConfig, useGlobalDarkMode, useResolvedStyle, useStyles, useTheme };
337
+ export { type ColorScale, type ColorsAPI, type FrameworkConfig, type InteractionState, InteractiveWrapper, type InteractiveWrapperProps, type ParsedClass, type PluginAPI, type ResolvedConfig, type ResolvedStyle, type StyleValue, type StyledProps, type ThemeConfig, ThemeContext, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, ThemeToggle, type ThemeToggleProps, type ToggleVariant, buildConfig, cx, defaultColors, defaultTheme, flatten, getConfig, kb, parseClass, parseClasses, resolve, styled, updateConfig, useColors, useGlobalDarkMode, useIsDark, useResolvedStyle, useStyles, useTheme, wrapColors };