@kbach/react-native 1.0.0-beta.36 → 1.0.0-beta.37

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
@@ -307,6 +307,13 @@ parses without error but never takes effect. Supported today:
307
307
  `hover:focus:opacity-100`) requires every named condition to hold at once
308
308
  — chain order never matters (`dark:sm:` and `sm:dark:` are equivalent).
309
309
 
310
+ **Class order doesn't matter for a variant vs a base class.** When two
311
+ classes set the same property, the one with more active modifiers wins
312
+ regardless of which you write first — `dark:bg-black bg-white` and
313
+ `bg-white dark:bg-black` both render black in dark mode, matching how
314
+ Tailwind behaves on the web. Two equally-specific classes (`bg-white
315
+ bg-black`, or two `dark:` classes) still go last-one-wins.
316
+
310
317
  `group-hover:`/`peer-hover:` and friends are NOT implemented on native —
311
318
  doing so needs a React Context bridge (propagating an ancestor's hover/
312
319
  press/focus state down to descendants, since there's no `.group`/`.peer`
package/dist/index.js CHANGED
@@ -1605,7 +1605,9 @@ var Evaluator = class {
1605
1605
 
1606
1606
  // src/jsEngine/resolveStyle.ts
1607
1607
  var RESPONSIVE_BREAKPOINTS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
1608
+ var STATE_HOLD_MARKER = "_kbon";
1608
1609
  function nativeModifierState(modifier, theme, colorScheme, pressed, width) {
1610
+ if (modifier === STATE_HOLD_MARKER) return true;
1609
1611
  if (modifier === "dark") return colorScheme === "dark";
1610
1612
  if (modifier === "active") return pressed;
1611
1613
  if (RESPONSIVE_BREAKPOINTS.has(modifier)) {
@@ -1744,6 +1746,8 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
1744
1746
  const warnings = [];
1745
1747
  let shadowOffset = null;
1746
1748
  const transformOps = /* @__PURE__ */ new Map();
1749
+ const keySpecificity = /* @__PURE__ */ new Map();
1750
+ const transformSpecificity = /* @__PURE__ */ new Map();
1747
1751
  for (const rawToken of classString.split(/\s+/).filter(Boolean)) {
1748
1752
  const token = substituteModeAwareColorToken(rawToken, theme, colorScheme);
1749
1753
  const parsed = parseClass(token);
@@ -1753,31 +1757,44 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
1753
1757
  if (!allModifiersHold) continue;
1754
1758
  const decls = resolveUtilityNative(parsed, theme);
1755
1759
  if (decls === null) continue;
1760
+ const spec = parsed.modifiers.length;
1756
1761
  for (const d of decls) {
1757
1762
  if (d.property.startsWith("__")) {
1758
1763
  continue;
1759
1764
  }
1760
1765
  if (d.property === "shadow-offset-x" || d.property === "shadow-offset-y") {
1766
+ if (spec < (keySpecificity.get("shadowOffset") ?? 0)) continue;
1761
1767
  const n = Number(d.value);
1762
1768
  if (!Number.isFinite(n)) continue;
1763
1769
  if (shadowOffset === null) shadowOffset = {};
1764
1770
  shadowOffset[d.property === "shadow-offset-x" ? "width" : "height"] = n;
1771
+ keySpecificity.set("shadowOffset", spec);
1765
1772
  continue;
1766
1773
  }
1767
1774
  if (d.property === "transform-op-none") {
1768
1775
  transformOps.clear();
1776
+ transformSpecificity.clear();
1769
1777
  continue;
1770
1778
  }
1771
1779
  const opKey = TRANSFORM_OP_KEYS.get(d.property);
1772
1780
  if (opKey !== void 0) {
1781
+ if (spec < (transformSpecificity.get(opKey) ?? 0)) continue;
1773
1782
  const { value: value2, warning: warning2 } = rnStyleValue(d.property, d.value);
1774
1783
  if (warning2 !== null) warnings.push(warning2);
1775
- if (value2 !== null) transformOps.set(opKey, value2);
1784
+ if (value2 !== null) {
1785
+ transformOps.set(opKey, value2);
1786
+ transformSpecificity.set(opKey, spec);
1787
+ }
1776
1788
  continue;
1777
1789
  }
1790
+ const key = kebabToCamel(d.property);
1791
+ if (spec < (keySpecificity.get(key) ?? 0)) continue;
1778
1792
  const { value, warning } = rnStyleValue(d.property, d.value);
1779
1793
  if (warning !== null) warnings.push(warning);
1780
- if (value !== null) style[kebabToCamel(d.property)] = value;
1794
+ if (value !== null) {
1795
+ style[key] = value;
1796
+ keySpecificity.set(key, spec);
1797
+ }
1781
1798
  }
1782
1799
  }
1783
1800
  if (shadowOffset !== null) {
@@ -725,9 +725,10 @@ function propBasedModifierState(name, states, hostRest) {
725
725
  if (ariaInner !== null) return evalAttrCondition(hostRest, "aria-", ariaInner);
726
726
  return null;
727
727
  }
728
- function stripModifiers(parts, namesToStrip) {
728
+ var STATE_HOLD_MARKER = "_kbon";
729
+ function markSatisfiedStateModifiers(parts, namesToReplace) {
729
730
  const utility = parts[parts.length - 1] ?? "";
730
- const modifiers = parts.slice(0, -1).filter((m) => !namesToStrip.has(m));
731
+ const modifiers = parts.slice(0, -1).map((m) => namesToReplace.has(m) ? STATE_HOLD_MARKER : m);
731
732
  return [...modifiers, utility].join(":");
732
733
  }
733
734
  function substituteStateModifiers(classStrRaw, states, hostRest) {
@@ -745,7 +746,7 @@ function substituteStateModifiers(classStrRaw, states, hostRest) {
745
746
  }
746
747
  if (required.every(([, v]) => v === true)) {
747
748
  const requiredNames = new Set(required.map(([m]) => m));
748
- kept.push(stripModifiers(parts, requiredNames));
749
+ kept.push(markSatisfiedStateModifiers(parts, requiredNames));
749
750
  }
750
751
  }
751
752
  return kept.join(" ");
@@ -1988,7 +1989,9 @@ function resolveUtilityNative(parsed, theme) {
1988
1989
 
1989
1990
  // src/jsEngine/resolveStyle.ts
1990
1991
  var RESPONSIVE_BREAKPOINTS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
1992
+ var STATE_HOLD_MARKER2 = "_kbon";
1991
1993
  function nativeModifierState(modifier, theme, colorScheme, pressed, width) {
1994
+ if (modifier === STATE_HOLD_MARKER2) return true;
1992
1995
  if (modifier === "dark") return colorScheme === "dark";
1993
1996
  if (modifier === "active") return pressed;
1994
1997
  if (RESPONSIVE_BREAKPOINTS.has(modifier)) {
@@ -2127,6 +2130,8 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
2127
2130
  const warnings = [];
2128
2131
  let shadowOffset = null;
2129
2132
  const transformOps = /* @__PURE__ */ new Map();
2133
+ const keySpecificity = /* @__PURE__ */ new Map();
2134
+ const transformSpecificity = /* @__PURE__ */ new Map();
2130
2135
  for (const rawToken of classString.split(/\s+/).filter(Boolean)) {
2131
2136
  const token = substituteModeAwareColorToken(rawToken, theme, colorScheme);
2132
2137
  const parsed = parseClass(token);
@@ -2136,31 +2141,44 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
2136
2141
  if (!allModifiersHold) continue;
2137
2142
  const decls = resolveUtilityNative(parsed, theme);
2138
2143
  if (decls === null) continue;
2144
+ const spec = parsed.modifiers.length;
2139
2145
  for (const d of decls) {
2140
2146
  if (d.property.startsWith("__")) {
2141
2147
  continue;
2142
2148
  }
2143
2149
  if (d.property === "shadow-offset-x" || d.property === "shadow-offset-y") {
2150
+ if (spec < (keySpecificity.get("shadowOffset") ?? 0)) continue;
2144
2151
  const n = Number(d.value);
2145
2152
  if (!Number.isFinite(n)) continue;
2146
2153
  if (shadowOffset === null) shadowOffset = {};
2147
2154
  shadowOffset[d.property === "shadow-offset-x" ? "width" : "height"] = n;
2155
+ keySpecificity.set("shadowOffset", spec);
2148
2156
  continue;
2149
2157
  }
2150
2158
  if (d.property === "transform-op-none") {
2151
2159
  transformOps.clear();
2160
+ transformSpecificity.clear();
2152
2161
  continue;
2153
2162
  }
2154
2163
  const opKey = TRANSFORM_OP_KEYS.get(d.property);
2155
2164
  if (opKey !== void 0) {
2165
+ if (spec < (transformSpecificity.get(opKey) ?? 0)) continue;
2156
2166
  const { value: value2, warning: warning2 } = rnStyleValue(d.property, d.value);
2157
2167
  if (warning2 !== null) warnings.push(warning2);
2158
- if (value2 !== null) transformOps.set(opKey, value2);
2168
+ if (value2 !== null) {
2169
+ transformOps.set(opKey, value2);
2170
+ transformSpecificity.set(opKey, spec);
2171
+ }
2159
2172
  continue;
2160
2173
  }
2174
+ const key = kebabToCamel(d.property);
2175
+ if (spec < (keySpecificity.get(key) ?? 0)) continue;
2161
2176
  const { value, warning } = rnStyleValue(d.property, d.value);
2162
2177
  if (warning !== null) warnings.push(warning);
2163
- if (value !== null) style[kebabToCamel(d.property)] = value;
2178
+ if (value !== null) {
2179
+ style[key] = value;
2180
+ keySpecificity.set(key, spec);
2181
+ }
2164
2182
  }
2165
2183
  }
2166
2184
  if (shadowOffset !== null) {
@@ -726,9 +726,10 @@ function propBasedModifierState(name, states, hostRest) {
726
726
  if (ariaInner !== null) return evalAttrCondition(hostRest, "aria-", ariaInner);
727
727
  return null;
728
728
  }
729
- function stripModifiers(parts, namesToStrip) {
729
+ var STATE_HOLD_MARKER = "_kbon";
730
+ function markSatisfiedStateModifiers(parts, namesToReplace) {
730
731
  const utility = parts[parts.length - 1] ?? "";
731
- const modifiers = parts.slice(0, -1).filter((m) => !namesToStrip.has(m));
732
+ const modifiers = parts.slice(0, -1).map((m) => namesToReplace.has(m) ? STATE_HOLD_MARKER : m);
732
733
  return [...modifiers, utility].join(":");
733
734
  }
734
735
  function substituteStateModifiers(classStrRaw, states, hostRest) {
@@ -746,7 +747,7 @@ function substituteStateModifiers(classStrRaw, states, hostRest) {
746
747
  }
747
748
  if (required.every(([, v]) => v === true)) {
748
749
  const requiredNames = new Set(required.map(([m]) => m));
749
- kept.push(stripModifiers(parts, requiredNames));
750
+ kept.push(markSatisfiedStateModifiers(parts, requiredNames));
750
751
  }
751
752
  }
752
753
  return kept.join(" ");
@@ -1979,7 +1980,9 @@ function resolveUtilityNative(parsed, theme) {
1979
1980
 
1980
1981
  // src/jsEngine/resolveStyle.ts
1981
1982
  var RESPONSIVE_BREAKPOINTS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
1983
+ var STATE_HOLD_MARKER2 = "_kbon";
1982
1984
  function nativeModifierState(modifier, theme, colorScheme, pressed, width) {
1985
+ if (modifier === STATE_HOLD_MARKER2) return true;
1983
1986
  if (modifier === "dark") return colorScheme === "dark";
1984
1987
  if (modifier === "active") return pressed;
1985
1988
  if (RESPONSIVE_BREAKPOINTS.has(modifier)) {
@@ -2118,6 +2121,8 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
2118
2121
  const warnings = [];
2119
2122
  let shadowOffset = null;
2120
2123
  const transformOps = /* @__PURE__ */ new Map();
2124
+ const keySpecificity = /* @__PURE__ */ new Map();
2125
+ const transformSpecificity = /* @__PURE__ */ new Map();
2121
2126
  for (const rawToken of classString.split(/\s+/).filter(Boolean)) {
2122
2127
  const token = substituteModeAwareColorToken(rawToken, theme, colorScheme);
2123
2128
  const parsed = parseClass(token);
@@ -2127,31 +2132,44 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
2127
2132
  if (!allModifiersHold) continue;
2128
2133
  const decls = resolveUtilityNative(parsed, theme);
2129
2134
  if (decls === null) continue;
2135
+ const spec = parsed.modifiers.length;
2130
2136
  for (const d of decls) {
2131
2137
  if (d.property.startsWith("__")) {
2132
2138
  continue;
2133
2139
  }
2134
2140
  if (d.property === "shadow-offset-x" || d.property === "shadow-offset-y") {
2141
+ if (spec < (keySpecificity.get("shadowOffset") ?? 0)) continue;
2135
2142
  const n = Number(d.value);
2136
2143
  if (!Number.isFinite(n)) continue;
2137
2144
  if (shadowOffset === null) shadowOffset = {};
2138
2145
  shadowOffset[d.property === "shadow-offset-x" ? "width" : "height"] = n;
2146
+ keySpecificity.set("shadowOffset", spec);
2139
2147
  continue;
2140
2148
  }
2141
2149
  if (d.property === "transform-op-none") {
2142
2150
  transformOps.clear();
2151
+ transformSpecificity.clear();
2143
2152
  continue;
2144
2153
  }
2145
2154
  const opKey = TRANSFORM_OP_KEYS.get(d.property);
2146
2155
  if (opKey !== void 0) {
2156
+ if (spec < (transformSpecificity.get(opKey) ?? 0)) continue;
2147
2157
  const { value: value2, warning: warning2 } = rnStyleValue(d.property, d.value);
2148
2158
  if (warning2 !== null) warnings.push(warning2);
2149
- if (value2 !== null) transformOps.set(opKey, value2);
2159
+ if (value2 !== null) {
2160
+ transformOps.set(opKey, value2);
2161
+ transformSpecificity.set(opKey, spec);
2162
+ }
2150
2163
  continue;
2151
2164
  }
2165
+ const key = kebabToCamel(d.property);
2166
+ if (spec < (keySpecificity.get(key) ?? 0)) continue;
2152
2167
  const { value, warning } = rnStyleValue(d.property, d.value);
2153
2168
  if (warning !== null) warnings.push(warning);
2154
- if (value !== null) style[kebabToCamel(d.property)] = value;
2169
+ if (value !== null) {
2170
+ style[key] = value;
2171
+ keySpecificity.set(key, spec);
2172
+ }
2155
2173
  }
2156
2174
  }
2157
2175
  if (shadowOffset !== null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react-native",
3
- "version": "1.0.0-beta.36",
3
+ "version": "1.0.0-beta.37",
4
4
  "description": "Kbach's utility-class styling engine for React Native — the same class names as @kbach/react, resolved to a StyleSheet-ready style object via a Kotlin/JNI TurboModule (native), the same WASM engine @kbach/react uses (Expo Web / react-native-web), or a pure-JS fallback engine (Expo Go). Ships its own native Android module — one npm install, no separate native package. Beta. Native is Android-only (no iOS bridge yet); works with the React Native CLI, an Expo dev client, AND Expo Go (a reduced-coverage pure-JS fallback, since Expo Go can't load any custom native module).",
5
5
  "keywords": [
6
6
  "css",