@kbach/react 0.2.6 → 0.2.8

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.
@@ -1683,6 +1683,45 @@ var RESOLVERS = {
1683
1683
  const ms = isArbitrary ? value : `${value}ms`;
1684
1684
  return { transitionDelay: ms };
1685
1685
  },
1686
+ // ── Ring (web-only: box-shadow outline ring) ──────────────────────────────
1687
+ ring: ({ value, isArbitrary }, { colors }) => {
1688
+ if (!isWeb) return null;
1689
+ const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
1690
+ const DEFAULT_WIDTH = 3;
1691
+ if (!value) {
1692
+ return { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
1693
+ }
1694
+ if (value === "inset") {
1695
+ return { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
1696
+ }
1697
+ const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1698
+ if (!isArbitrary && value in widthTokens) {
1699
+ const w = widthTokens[value];
1700
+ return { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` };
1701
+ }
1702
+ if (isArbitrary) {
1703
+ const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
1704
+ if (numMatch) {
1705
+ const unit = numMatch[2] ?? "px";
1706
+ return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
1707
+ }
1708
+ return { boxShadow: value.replace(/_/g, " ") };
1709
+ }
1710
+ const color = resolveColor(value, colors, false);
1711
+ if (color) return { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` };
1712
+ return null;
1713
+ },
1714
+ "ring-offset": ({ value, isArbitrary }, _theme) => {
1715
+ if (!isWeb) return null;
1716
+ const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
1717
+ const DEFAULT_RING_WIDTH = 3;
1718
+ const offsetTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1719
+ const offsetWidth = !isArbitrary ? value in offsetTokens ? offsetTokens[value] : null : parseFloat(value);
1720
+ if (offsetWidth === null || isNaN(offsetWidth)) return null;
1721
+ return {
1722
+ 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}`
1723
+ };
1724
+ },
1686
1725
  // ── Space between (uses CSS gap or margin fallback) ────────────────────────
1687
1726
  "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
1688
1727
  const v = resolveSpacing(value, negative, spacing, isArbitrary);
@@ -1758,44 +1797,73 @@ function getStyleEl() {
1758
1797
  function injectRule(rule) {
1759
1798
  if (_injectedRules.has(rule)) return;
1760
1799
  _injectedRules.add(rule);
1761
- getStyleEl().sheet?.insertRule(rule, getStyleEl().sheet.cssRules.length);
1800
+ try {
1801
+ getStyleEl().sheet?.insertRule(rule, getStyleEl().sheet.cssRules.length);
1802
+ } catch {
1803
+ }
1762
1804
  }
1763
- function injectCSS(classString, resolved, darkMode) {
1764
- if (!isWeb) return;
1765
- for (const [modifierKey, styles] of Object.entries(resolved)) {
1766
- if (!styles || Object.keys(styles).length === 0) continue;
1767
- const classes = classString.trim().split(/\s+/);
1768
- for (const cls of classes) {
1769
- const cssDecls = styleValueToCSS(styles);
1770
- if (!cssDecls) continue;
1771
- const escaped = escapeCSSSelector(cls);
1772
- if (modifierKey === "base") {
1773
- injectRule(`.${escaped} { ${cssDecls} }`);
1774
- continue;
1775
- }
1776
- const mods = modifierKey.split(":");
1777
- const pseudos = mods.filter((m) => !["dark", "light"].includes(m));
1778
- const hasDark = mods.includes("dark");
1779
- const pseudoSuffix = pseudos.map((p) => `:${p}`).join("");
1780
- let selector = `.${escaped}${pseudoSuffix}`;
1781
- let rule;
1782
- if (hasDark) {
1783
- if (darkMode === "media") {
1784
- rule = `@media (prefers-color-scheme: dark) { ${selector} { ${cssDecls} } }`;
1785
- } else if (darkMode === "class") {
1786
- rule = `.dark ${selector} { ${cssDecls} }`;
1787
- } else {
1788
- rule = `[data-theme="dark"] ${selector} { ${cssDecls} }`;
1789
- }
1790
- } else {
1791
- rule = `${selector} { ${cssDecls} }`;
1792
- }
1793
- injectRule(rule);
1805
+ function injectClassRule(cls, bucketKey, styles, darkMode) {
1806
+ const cssDecls = styleValueToCSS(styles);
1807
+ if (!cssDecls) return;
1808
+ const escaped = escapeCSSSelector(cls);
1809
+ if (bucketKey === "base") {
1810
+ injectRule(`.${escaped} { ${cssDecls} }`);
1811
+ return;
1812
+ }
1813
+ const mods = bucketKey.split(":");
1814
+ const pseudos = mods.filter((m) => !["dark", "light"].includes(m));
1815
+ const hasDark = mods.includes("dark");
1816
+ const pseudoSuffix = pseudos.map((p) => `:${p}`).join("");
1817
+ const selector = `.${escaped}${pseudoSuffix}`;
1818
+ let rule;
1819
+ if (hasDark) {
1820
+ if (darkMode === "media") {
1821
+ rule = `@media (prefers-color-scheme: dark) { ${selector} { ${cssDecls} } }`;
1822
+ } else if (darkMode === "class") {
1823
+ rule = `.dark ${selector} { ${cssDecls} }`;
1824
+ } else {
1825
+ rule = `[data-theme="dark"] ${selector} { ${cssDecls} }`;
1794
1826
  }
1827
+ } else {
1828
+ rule = `${selector} { ${cssDecls} }`;
1795
1829
  }
1830
+ injectRule(rule);
1796
1831
  }
1832
+ var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
1833
+ "shadowColor",
1834
+ "shadowOffset",
1835
+ "shadowOpacity",
1836
+ "shadowRadius",
1837
+ "elevation",
1838
+ "includeFontPadding",
1839
+ "textAlignVertical",
1840
+ "writingDirection"
1841
+ ]);
1842
+ var CSS_UNITLESS = /* @__PURE__ */ new Set([
1843
+ "opacity",
1844
+ "fontWeight",
1845
+ "flex",
1846
+ "flexGrow",
1847
+ "flexShrink",
1848
+ "order",
1849
+ "zIndex",
1850
+ "aspectRatio",
1851
+ "columnCount",
1852
+ "lineHeight"
1853
+ ]);
1797
1854
  function styleValueToCSS(styles) {
1798
- return Object.entries(styles).filter(([, v]) => v !== void 0 && v !== null).map(([prop, val]) => `${camelToKebab(prop)}: ${val}`).join("; ");
1855
+ return Object.entries(styles).filter(
1856
+ ([prop, v]) => v !== void 0 && v !== null && typeof v !== "object" && !RN_ONLY_PROPS.has(prop)
1857
+ ).map(([prop, val]) => {
1858
+ const cssProp = camelToKebab(prop);
1859
+ let cssVal;
1860
+ if (typeof val === "number") {
1861
+ cssVal = val === 0 || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
1862
+ } else {
1863
+ cssVal = String(val);
1864
+ }
1865
+ return `${cssProp}: ${cssVal}`;
1866
+ }).join("; ");
1799
1867
  }
1800
1868
  function camelToKebab(str) {
1801
1869
  return str.replace(/([A-Z])/g, "-$1").toLowerCase();
@@ -1806,16 +1874,24 @@ function resolve(classString, theme, darkMode = "attribute") {
1806
1874
  if (cached) return cached;
1807
1875
  const result = {};
1808
1876
  const parsedClasses = parseClasses(classString);
1877
+ const injectQueue = [];
1809
1878
  for (const parsed of parsedClasses) {
1810
1879
  const styles = resolveUtility(parsed, theme);
1811
1880
  if (!styles) continue;
1812
1881
  const bucketKey = parsed.modifiers.length === 0 ? "base" : parsed.modifiers.join(":");
1813
1882
  if (!result[bucketKey]) result[bucketKey] = {};
1814
1883
  Object.assign(result[bucketKey], styles);
1884
+ if (isWeb) {
1885
+ injectQueue.push({ cls: parsed.original, bucketKey, styles });
1886
+ }
1815
1887
  }
1816
1888
  styleCache.set(cacheKey, result);
1817
- if (isWeb) {
1818
- Promise.resolve().then(() => injectCSS(classString, result, darkMode));
1889
+ if (isWeb && injectQueue.length > 0) {
1890
+ Promise.resolve().then(() => {
1891
+ for (const { cls, bucketKey, styles } of injectQueue) {
1892
+ injectClassRule(cls, bucketKey, styles, darkMode);
1893
+ }
1894
+ });
1819
1895
  }
1820
1896
  return result;
1821
1897
  }
@@ -5,7 +5,7 @@ import {
5
5
  isWeb,
6
6
  resolve,
7
7
  useGlobalDarkMode
8
- } from "./chunk-7YSVROUJ.mjs";
8
+ } from "./chunk-GN4JPJHC.mjs";
9
9
 
10
10
  // src/jsx-runtime.tsx
11
11
  import { jsx as _jsx, jsxs as _jsxs, Fragment } from "react/jsx-runtime";
package/dist/index.js CHANGED
@@ -1783,6 +1783,45 @@ var RESOLVERS = {
1783
1783
  const ms = isArbitrary ? value : `${value}ms`;
1784
1784
  return { transitionDelay: ms };
1785
1785
  },
1786
+ // ── Ring (web-only: box-shadow outline ring) ──────────────────────────────
1787
+ ring: ({ value, isArbitrary }, { colors }) => {
1788
+ if (!isWeb) return null;
1789
+ const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
1790
+ const DEFAULT_WIDTH = 3;
1791
+ if (!value) {
1792
+ return { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
1793
+ }
1794
+ if (value === "inset") {
1795
+ return { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
1796
+ }
1797
+ const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1798
+ if (!isArbitrary && value in widthTokens) {
1799
+ const w = widthTokens[value];
1800
+ return { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` };
1801
+ }
1802
+ if (isArbitrary) {
1803
+ const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
1804
+ if (numMatch) {
1805
+ const unit = numMatch[2] ?? "px";
1806
+ return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
1807
+ }
1808
+ return { boxShadow: value.replace(/_/g, " ") };
1809
+ }
1810
+ const color = resolveColor(value, colors, false);
1811
+ if (color) return { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` };
1812
+ return null;
1813
+ },
1814
+ "ring-offset": ({ value, isArbitrary }, _theme) => {
1815
+ if (!isWeb) return null;
1816
+ const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
1817
+ const DEFAULT_RING_WIDTH = 3;
1818
+ const offsetTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1819
+ const offsetWidth = !isArbitrary ? value in offsetTokens ? offsetTokens[value] : null : parseFloat(value);
1820
+ if (offsetWidth === null || isNaN(offsetWidth)) return null;
1821
+ return {
1822
+ 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}`
1823
+ };
1824
+ },
1786
1825
  // ── Space between (uses CSS gap or margin fallback) ────────────────────────
1787
1826
  "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
1788
1827
  const v = resolveSpacing(value, negative, spacing, isArbitrary);
@@ -1822,44 +1861,73 @@ function getStyleEl() {
1822
1861
  function injectRule(rule) {
1823
1862
  if (_injectedRules.has(rule)) return;
1824
1863
  _injectedRules.add(rule);
1825
- getStyleEl().sheet?.insertRule(rule, getStyleEl().sheet.cssRules.length);
1864
+ try {
1865
+ getStyleEl().sheet?.insertRule(rule, getStyleEl().sheet.cssRules.length);
1866
+ } catch {
1867
+ }
1826
1868
  }
1827
- function injectCSS(classString, resolved, darkMode) {
1828
- if (!isWeb) return;
1829
- for (const [modifierKey, styles] of Object.entries(resolved)) {
1830
- if (!styles || Object.keys(styles).length === 0) continue;
1831
- const classes = classString.trim().split(/\s+/);
1832
- for (const cls of classes) {
1833
- const cssDecls = styleValueToCSS(styles);
1834
- if (!cssDecls) continue;
1835
- const escaped = escapeCSSSelector(cls);
1836
- if (modifierKey === "base") {
1837
- injectRule(`.${escaped} { ${cssDecls} }`);
1838
- continue;
1839
- }
1840
- const mods = modifierKey.split(":");
1841
- const pseudos = mods.filter((m) => !["dark", "light"].includes(m));
1842
- const hasDark = mods.includes("dark");
1843
- const pseudoSuffix = pseudos.map((p) => `:${p}`).join("");
1844
- let selector = `.${escaped}${pseudoSuffix}`;
1845
- let rule;
1846
- if (hasDark) {
1847
- if (darkMode === "media") {
1848
- rule = `@media (prefers-color-scheme: dark) { ${selector} { ${cssDecls} } }`;
1849
- } else if (darkMode === "class") {
1850
- rule = `.dark ${selector} { ${cssDecls} }`;
1851
- } else {
1852
- rule = `[data-theme="dark"] ${selector} { ${cssDecls} }`;
1853
- }
1854
- } else {
1855
- rule = `${selector} { ${cssDecls} }`;
1856
- }
1857
- injectRule(rule);
1869
+ function injectClassRule(cls, bucketKey, styles, darkMode) {
1870
+ const cssDecls = styleValueToCSS(styles);
1871
+ if (!cssDecls) return;
1872
+ const escaped = escapeCSSSelector(cls);
1873
+ if (bucketKey === "base") {
1874
+ injectRule(`.${escaped} { ${cssDecls} }`);
1875
+ return;
1876
+ }
1877
+ const mods = bucketKey.split(":");
1878
+ const pseudos = mods.filter((m) => !["dark", "light"].includes(m));
1879
+ const hasDark = mods.includes("dark");
1880
+ const pseudoSuffix = pseudos.map((p) => `:${p}`).join("");
1881
+ const selector = `.${escaped}${pseudoSuffix}`;
1882
+ let rule;
1883
+ if (hasDark) {
1884
+ if (darkMode === "media") {
1885
+ rule = `@media (prefers-color-scheme: dark) { ${selector} { ${cssDecls} } }`;
1886
+ } else if (darkMode === "class") {
1887
+ rule = `.dark ${selector} { ${cssDecls} }`;
1888
+ } else {
1889
+ rule = `[data-theme="dark"] ${selector} { ${cssDecls} }`;
1858
1890
  }
1891
+ } else {
1892
+ rule = `${selector} { ${cssDecls} }`;
1859
1893
  }
1894
+ injectRule(rule);
1860
1895
  }
1896
+ var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
1897
+ "shadowColor",
1898
+ "shadowOffset",
1899
+ "shadowOpacity",
1900
+ "shadowRadius",
1901
+ "elevation",
1902
+ "includeFontPadding",
1903
+ "textAlignVertical",
1904
+ "writingDirection"
1905
+ ]);
1906
+ var CSS_UNITLESS = /* @__PURE__ */ new Set([
1907
+ "opacity",
1908
+ "fontWeight",
1909
+ "flex",
1910
+ "flexGrow",
1911
+ "flexShrink",
1912
+ "order",
1913
+ "zIndex",
1914
+ "aspectRatio",
1915
+ "columnCount",
1916
+ "lineHeight"
1917
+ ]);
1861
1918
  function styleValueToCSS(styles) {
1862
- return Object.entries(styles).filter(([, v]) => v !== void 0 && v !== null).map(([prop, val]) => `${camelToKebab(prop)}: ${val}`).join("; ");
1919
+ return Object.entries(styles).filter(
1920
+ ([prop, v]) => v !== void 0 && v !== null && typeof v !== "object" && !RN_ONLY_PROPS.has(prop)
1921
+ ).map(([prop, val]) => {
1922
+ const cssProp = camelToKebab(prop);
1923
+ let cssVal;
1924
+ if (typeof val === "number") {
1925
+ cssVal = val === 0 || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
1926
+ } else {
1927
+ cssVal = String(val);
1928
+ }
1929
+ return `${cssProp}: ${cssVal}`;
1930
+ }).join("; ");
1863
1931
  }
1864
1932
  function camelToKebab(str) {
1865
1933
  return str.replace(/([A-Z])/g, "-$1").toLowerCase();
@@ -1870,16 +1938,24 @@ function resolve(classString, theme, darkMode = "attribute") {
1870
1938
  if (cached) return cached;
1871
1939
  const result = {};
1872
1940
  const parsedClasses = parseClasses(classString);
1941
+ const injectQueue = [];
1873
1942
  for (const parsed of parsedClasses) {
1874
1943
  const styles = resolveUtility(parsed, theme);
1875
1944
  if (!styles) continue;
1876
1945
  const bucketKey = parsed.modifiers.length === 0 ? "base" : parsed.modifiers.join(":");
1877
1946
  if (!result[bucketKey]) result[bucketKey] = {};
1878
1947
  Object.assign(result[bucketKey], styles);
1948
+ if (isWeb) {
1949
+ injectQueue.push({ cls: parsed.original, bucketKey, styles });
1950
+ }
1879
1951
  }
1880
1952
  styleCache.set(cacheKey, result);
1881
- if (isWeb) {
1882
- Promise.resolve().then(() => injectCSS(classString, result, darkMode));
1953
+ if (isWeb && injectQueue.length > 0) {
1954
+ Promise.resolve().then(() => {
1955
+ for (const { cls, bucketKey, styles } of injectQueue) {
1956
+ injectClassRule(cls, bucketKey, styles, darkMode);
1957
+ }
1958
+ });
1883
1959
  }
1884
1960
  return result;
1885
1961
  }
package/dist/index.mjs CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  syncGlobalDarkMode,
16
16
  updateConfig,
17
17
  useGlobalDarkMode
18
- } from "./chunk-7YSVROUJ.mjs";
18
+ } from "./chunk-GN4JPJHC.mjs";
19
19
 
20
20
  // src/context.tsx
21
21
  import { createContext, useContext } from "react";
@@ -1752,6 +1752,45 @@ var RESOLVERS = {
1752
1752
  const ms = isArbitrary ? value : `${value}ms`;
1753
1753
  return { transitionDelay: ms };
1754
1754
  },
1755
+ // ── Ring (web-only: box-shadow outline ring) ──────────────────────────────
1756
+ ring: ({ value, isArbitrary }, { colors }) => {
1757
+ if (!isWeb) return null;
1758
+ const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
1759
+ const DEFAULT_WIDTH = 3;
1760
+ if (!value) {
1761
+ return { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
1762
+ }
1763
+ if (value === "inset") {
1764
+ return { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
1765
+ }
1766
+ const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1767
+ if (!isArbitrary && value in widthTokens) {
1768
+ const w = widthTokens[value];
1769
+ return { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` };
1770
+ }
1771
+ if (isArbitrary) {
1772
+ const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
1773
+ if (numMatch) {
1774
+ const unit = numMatch[2] ?? "px";
1775
+ return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
1776
+ }
1777
+ return { boxShadow: value.replace(/_/g, " ") };
1778
+ }
1779
+ const color = resolveColor(value, colors, false);
1780
+ if (color) return { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` };
1781
+ return null;
1782
+ },
1783
+ "ring-offset": ({ value, isArbitrary }, _theme) => {
1784
+ if (!isWeb) return null;
1785
+ const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
1786
+ const DEFAULT_RING_WIDTH = 3;
1787
+ const offsetTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1788
+ const offsetWidth = !isArbitrary ? value in offsetTokens ? offsetTokens[value] : null : parseFloat(value);
1789
+ if (offsetWidth === null || isNaN(offsetWidth)) return null;
1790
+ return {
1791
+ 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}`
1792
+ };
1793
+ },
1755
1794
  // ── Space between (uses CSS gap or margin fallback) ────────────────────────
1756
1795
  "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
1757
1796
  const v = resolveSpacing(value, negative, spacing, isArbitrary);
@@ -1791,44 +1830,73 @@ function getStyleEl() {
1791
1830
  function injectRule(rule) {
1792
1831
  if (_injectedRules.has(rule)) return;
1793
1832
  _injectedRules.add(rule);
1794
- getStyleEl().sheet?.insertRule(rule, getStyleEl().sheet.cssRules.length);
1833
+ try {
1834
+ getStyleEl().sheet?.insertRule(rule, getStyleEl().sheet.cssRules.length);
1835
+ } catch {
1836
+ }
1795
1837
  }
1796
- function injectCSS(classString, resolved, darkMode) {
1797
- if (!isWeb) return;
1798
- for (const [modifierKey, styles] of Object.entries(resolved)) {
1799
- if (!styles || Object.keys(styles).length === 0) continue;
1800
- const classes = classString.trim().split(/\s+/);
1801
- for (const cls of classes) {
1802
- const cssDecls = styleValueToCSS(styles);
1803
- if (!cssDecls) continue;
1804
- const escaped = escapeCSSSelector(cls);
1805
- if (modifierKey === "base") {
1806
- injectRule(`.${escaped} { ${cssDecls} }`);
1807
- continue;
1808
- }
1809
- const mods = modifierKey.split(":");
1810
- const pseudos = mods.filter((m) => !["dark", "light"].includes(m));
1811
- const hasDark = mods.includes("dark");
1812
- const pseudoSuffix = pseudos.map((p) => `:${p}`).join("");
1813
- let selector = `.${escaped}${pseudoSuffix}`;
1814
- let rule;
1815
- if (hasDark) {
1816
- if (darkMode === "media") {
1817
- rule = `@media (prefers-color-scheme: dark) { ${selector} { ${cssDecls} } }`;
1818
- } else if (darkMode === "class") {
1819
- rule = `.dark ${selector} { ${cssDecls} }`;
1820
- } else {
1821
- rule = `[data-theme="dark"] ${selector} { ${cssDecls} }`;
1822
- }
1823
- } else {
1824
- rule = `${selector} { ${cssDecls} }`;
1825
- }
1826
- injectRule(rule);
1838
+ function injectClassRule(cls, bucketKey, styles, darkMode) {
1839
+ const cssDecls = styleValueToCSS(styles);
1840
+ if (!cssDecls) return;
1841
+ const escaped = escapeCSSSelector(cls);
1842
+ if (bucketKey === "base") {
1843
+ injectRule(`.${escaped} { ${cssDecls} }`);
1844
+ return;
1845
+ }
1846
+ const mods = bucketKey.split(":");
1847
+ const pseudos = mods.filter((m) => !["dark", "light"].includes(m));
1848
+ const hasDark = mods.includes("dark");
1849
+ const pseudoSuffix = pseudos.map((p) => `:${p}`).join("");
1850
+ const selector = `.${escaped}${pseudoSuffix}`;
1851
+ let rule;
1852
+ if (hasDark) {
1853
+ if (darkMode === "media") {
1854
+ rule = `@media (prefers-color-scheme: dark) { ${selector} { ${cssDecls} } }`;
1855
+ } else if (darkMode === "class") {
1856
+ rule = `.dark ${selector} { ${cssDecls} }`;
1857
+ } else {
1858
+ rule = `[data-theme="dark"] ${selector} { ${cssDecls} }`;
1827
1859
  }
1860
+ } else {
1861
+ rule = `${selector} { ${cssDecls} }`;
1828
1862
  }
1863
+ injectRule(rule);
1829
1864
  }
1865
+ var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
1866
+ "shadowColor",
1867
+ "shadowOffset",
1868
+ "shadowOpacity",
1869
+ "shadowRadius",
1870
+ "elevation",
1871
+ "includeFontPadding",
1872
+ "textAlignVertical",
1873
+ "writingDirection"
1874
+ ]);
1875
+ var CSS_UNITLESS = /* @__PURE__ */ new Set([
1876
+ "opacity",
1877
+ "fontWeight",
1878
+ "flex",
1879
+ "flexGrow",
1880
+ "flexShrink",
1881
+ "order",
1882
+ "zIndex",
1883
+ "aspectRatio",
1884
+ "columnCount",
1885
+ "lineHeight"
1886
+ ]);
1830
1887
  function styleValueToCSS(styles) {
1831
- return Object.entries(styles).filter(([, v]) => v !== void 0 && v !== null).map(([prop, val]) => `${camelToKebab(prop)}: ${val}`).join("; ");
1888
+ return Object.entries(styles).filter(
1889
+ ([prop, v]) => v !== void 0 && v !== null && typeof v !== "object" && !RN_ONLY_PROPS.has(prop)
1890
+ ).map(([prop, val]) => {
1891
+ const cssProp = camelToKebab(prop);
1892
+ let cssVal;
1893
+ if (typeof val === "number") {
1894
+ cssVal = val === 0 || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
1895
+ } else {
1896
+ cssVal = String(val);
1897
+ }
1898
+ return `${cssProp}: ${cssVal}`;
1899
+ }).join("; ");
1832
1900
  }
1833
1901
  function camelToKebab(str) {
1834
1902
  return str.replace(/([A-Z])/g, "-$1").toLowerCase();
@@ -1839,16 +1907,24 @@ function resolve(classString, theme, darkMode = "attribute") {
1839
1907
  if (cached) return cached;
1840
1908
  const result = {};
1841
1909
  const parsedClasses = parseClasses(classString);
1910
+ const injectQueue = [];
1842
1911
  for (const parsed of parsedClasses) {
1843
1912
  const styles = resolveUtility(parsed, theme);
1844
1913
  if (!styles) continue;
1845
1914
  const bucketKey = parsed.modifiers.length === 0 ? "base" : parsed.modifiers.join(":");
1846
1915
  if (!result[bucketKey]) result[bucketKey] = {};
1847
1916
  Object.assign(result[bucketKey], styles);
1917
+ if (isWeb) {
1918
+ injectQueue.push({ cls: parsed.original, bucketKey, styles });
1919
+ }
1848
1920
  }
1849
1921
  styleCache.set(cacheKey, result);
1850
- if (isWeb) {
1851
- Promise.resolve().then(() => injectCSS(classString, result, darkMode));
1922
+ if (isWeb && injectQueue.length > 0) {
1923
+ Promise.resolve().then(() => {
1924
+ for (const { cls, bucketKey, styles } of injectQueue) {
1925
+ injectClassRule(cls, bucketKey, styles, darkMode);
1926
+ }
1927
+ });
1852
1928
  }
1853
1929
  return result;
1854
1930
  }
@@ -2,8 +2,8 @@ import {
2
2
  Fragment,
3
3
  jsx,
4
4
  jsxs
5
- } from "./chunk-XW4LEBBB.mjs";
6
- import "./chunk-7YSVROUJ.mjs";
5
+ } from "./chunk-YDKLJFFY.mjs";
6
+ import "./chunk-GN4JPJHC.mjs";
7
7
 
8
8
  // src/jsx-dev-runtime.tsx
9
9
  function jsxDEV(type, props, key, isStaticChildren, _source, _self) {
@@ -1751,6 +1751,45 @@ var RESOLVERS = {
1751
1751
  const ms = isArbitrary ? value : `${value}ms`;
1752
1752
  return { transitionDelay: ms };
1753
1753
  },
1754
+ // ── Ring (web-only: box-shadow outline ring) ──────────────────────────────
1755
+ ring: ({ value, isArbitrary }, { colors }) => {
1756
+ if (!isWeb) return null;
1757
+ const DEFAULT_COLOR = "rgba(59, 130, 246, 0.5)";
1758
+ const DEFAULT_WIDTH = 3;
1759
+ if (!value) {
1760
+ return { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
1761
+ }
1762
+ if (value === "inset") {
1763
+ return { boxShadow: `inset 0 0 0 ${DEFAULT_WIDTH}px ${DEFAULT_COLOR}` };
1764
+ }
1765
+ const widthTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1766
+ if (!isArbitrary && value in widthTokens) {
1767
+ const w = widthTokens[value];
1768
+ return { boxShadow: w === 0 ? "none" : `0 0 0 ${w}px ${DEFAULT_COLOR}` };
1769
+ }
1770
+ if (isArbitrary) {
1771
+ const numMatch = /^(\d+(?:\.\d+)?)(px|rem|em|vw|vh)?$/.exec(value);
1772
+ if (numMatch) {
1773
+ const unit = numMatch[2] ?? "px";
1774
+ return { boxShadow: `0 0 0 ${numMatch[1]}${unit} ${DEFAULT_COLOR}` };
1775
+ }
1776
+ return { boxShadow: value.replace(/_/g, " ") };
1777
+ }
1778
+ const color = resolveColor(value, colors, false);
1779
+ if (color) return { boxShadow: `0 0 0 ${DEFAULT_WIDTH}px ${color}` };
1780
+ return null;
1781
+ },
1782
+ "ring-offset": ({ value, isArbitrary }, _theme) => {
1783
+ if (!isWeb) return null;
1784
+ const DEFAULT_RING_COLOR = "rgba(59, 130, 246, 0.5)";
1785
+ const DEFAULT_RING_WIDTH = 3;
1786
+ const offsetTokens = { "0": 0, "1": 1, "2": 2, "4": 4, "8": 8 };
1787
+ const offsetWidth = !isArbitrary ? value in offsetTokens ? offsetTokens[value] : null : parseFloat(value);
1788
+ if (offsetWidth === null || isNaN(offsetWidth)) return null;
1789
+ return {
1790
+ 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}`
1791
+ };
1792
+ },
1754
1793
  // ── Space between (uses CSS gap or margin fallback) ────────────────────────
1755
1794
  "space-x": ({ value, negative, isArbitrary }, { spacing }) => {
1756
1795
  const v = resolveSpacing(value, negative, spacing, isArbitrary);
@@ -1790,44 +1829,73 @@ function getStyleEl() {
1790
1829
  function injectRule(rule) {
1791
1830
  if (_injectedRules.has(rule)) return;
1792
1831
  _injectedRules.add(rule);
1793
- getStyleEl().sheet?.insertRule(rule, getStyleEl().sheet.cssRules.length);
1832
+ try {
1833
+ getStyleEl().sheet?.insertRule(rule, getStyleEl().sheet.cssRules.length);
1834
+ } catch {
1835
+ }
1794
1836
  }
1795
- function injectCSS(classString, resolved, darkMode) {
1796
- if (!isWeb) return;
1797
- for (const [modifierKey, styles] of Object.entries(resolved)) {
1798
- if (!styles || Object.keys(styles).length === 0) continue;
1799
- const classes = classString.trim().split(/\s+/);
1800
- for (const cls of classes) {
1801
- const cssDecls = styleValueToCSS(styles);
1802
- if (!cssDecls) continue;
1803
- const escaped = escapeCSSSelector(cls);
1804
- if (modifierKey === "base") {
1805
- injectRule(`.${escaped} { ${cssDecls} }`);
1806
- continue;
1807
- }
1808
- const mods = modifierKey.split(":");
1809
- const pseudos = mods.filter((m) => !["dark", "light"].includes(m));
1810
- const hasDark = mods.includes("dark");
1811
- const pseudoSuffix = pseudos.map((p) => `:${p}`).join("");
1812
- let selector = `.${escaped}${pseudoSuffix}`;
1813
- let rule;
1814
- if (hasDark) {
1815
- if (darkMode === "media") {
1816
- rule = `@media (prefers-color-scheme: dark) { ${selector} { ${cssDecls} } }`;
1817
- } else if (darkMode === "class") {
1818
- rule = `.dark ${selector} { ${cssDecls} }`;
1819
- } else {
1820
- rule = `[data-theme="dark"] ${selector} { ${cssDecls} }`;
1821
- }
1822
- } else {
1823
- rule = `${selector} { ${cssDecls} }`;
1824
- }
1825
- injectRule(rule);
1837
+ function injectClassRule(cls, bucketKey, styles, darkMode) {
1838
+ const cssDecls = styleValueToCSS(styles);
1839
+ if (!cssDecls) return;
1840
+ const escaped = escapeCSSSelector(cls);
1841
+ if (bucketKey === "base") {
1842
+ injectRule(`.${escaped} { ${cssDecls} }`);
1843
+ return;
1844
+ }
1845
+ const mods = bucketKey.split(":");
1846
+ const pseudos = mods.filter((m) => !["dark", "light"].includes(m));
1847
+ const hasDark = mods.includes("dark");
1848
+ const pseudoSuffix = pseudos.map((p) => `:${p}`).join("");
1849
+ const selector = `.${escaped}${pseudoSuffix}`;
1850
+ let rule;
1851
+ if (hasDark) {
1852
+ if (darkMode === "media") {
1853
+ rule = `@media (prefers-color-scheme: dark) { ${selector} { ${cssDecls} } }`;
1854
+ } else if (darkMode === "class") {
1855
+ rule = `.dark ${selector} { ${cssDecls} }`;
1856
+ } else {
1857
+ rule = `[data-theme="dark"] ${selector} { ${cssDecls} }`;
1826
1858
  }
1859
+ } else {
1860
+ rule = `${selector} { ${cssDecls} }`;
1827
1861
  }
1862
+ injectRule(rule);
1828
1863
  }
1864
+ var RN_ONLY_PROPS = /* @__PURE__ */ new Set([
1865
+ "shadowColor",
1866
+ "shadowOffset",
1867
+ "shadowOpacity",
1868
+ "shadowRadius",
1869
+ "elevation",
1870
+ "includeFontPadding",
1871
+ "textAlignVertical",
1872
+ "writingDirection"
1873
+ ]);
1874
+ var CSS_UNITLESS = /* @__PURE__ */ new Set([
1875
+ "opacity",
1876
+ "fontWeight",
1877
+ "flex",
1878
+ "flexGrow",
1879
+ "flexShrink",
1880
+ "order",
1881
+ "zIndex",
1882
+ "aspectRatio",
1883
+ "columnCount",
1884
+ "lineHeight"
1885
+ ]);
1829
1886
  function styleValueToCSS(styles) {
1830
- return Object.entries(styles).filter(([, v]) => v !== void 0 && v !== null).map(([prop, val]) => `${camelToKebab(prop)}: ${val}`).join("; ");
1887
+ return Object.entries(styles).filter(
1888
+ ([prop, v]) => v !== void 0 && v !== null && typeof v !== "object" && !RN_ONLY_PROPS.has(prop)
1889
+ ).map(([prop, val]) => {
1890
+ const cssProp = camelToKebab(prop);
1891
+ let cssVal;
1892
+ if (typeof val === "number") {
1893
+ cssVal = val === 0 || CSS_UNITLESS.has(prop) ? String(val) : `${val}px`;
1894
+ } else {
1895
+ cssVal = String(val);
1896
+ }
1897
+ return `${cssProp}: ${cssVal}`;
1898
+ }).join("; ");
1831
1899
  }
1832
1900
  function camelToKebab(str) {
1833
1901
  return str.replace(/([A-Z])/g, "-$1").toLowerCase();
@@ -1838,16 +1906,24 @@ function resolve(classString, theme, darkMode = "attribute") {
1838
1906
  if (cached) return cached;
1839
1907
  const result = {};
1840
1908
  const parsedClasses = parseClasses(classString);
1909
+ const injectQueue = [];
1841
1910
  for (const parsed of parsedClasses) {
1842
1911
  const styles = resolveUtility(parsed, theme);
1843
1912
  if (!styles) continue;
1844
1913
  const bucketKey = parsed.modifiers.length === 0 ? "base" : parsed.modifiers.join(":");
1845
1914
  if (!result[bucketKey]) result[bucketKey] = {};
1846
1915
  Object.assign(result[bucketKey], styles);
1916
+ if (isWeb) {
1917
+ injectQueue.push({ cls: parsed.original, bucketKey, styles });
1918
+ }
1847
1919
  }
1848
1920
  styleCache.set(cacheKey, result);
1849
- if (isWeb) {
1850
- Promise.resolve().then(() => injectCSS(classString, result, darkMode));
1921
+ if (isWeb && injectQueue.length > 0) {
1922
+ Promise.resolve().then(() => {
1923
+ for (const { cls, bucketKey, styles } of injectQueue) {
1924
+ injectClassRule(cls, bucketKey, styles, darkMode);
1925
+ }
1926
+ });
1851
1927
  }
1852
1928
  return result;
1853
1929
  }
@@ -2,8 +2,8 @@ import {
2
2
  Fragment,
3
3
  jsx,
4
4
  jsxs
5
- } from "./chunk-XW4LEBBB.mjs";
6
- import "./chunk-7YSVROUJ.mjs";
5
+ } from "./chunk-YDKLJFFY.mjs";
6
+ import "./chunk-GN4JPJHC.mjs";
7
7
  export {
8
8
  Fragment,
9
9
  jsx,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
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",