@kbach/react-native 1.0.0-beta.22 → 1.0.0-beta.23

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
@@ -30,9 +30,14 @@ Rebuild the Android app afterward so Gradle picks up the native module.
30
30
  Works out of the box — no prebuild, no dev client, nothing to configure.
31
31
  Expo Go can't load third-party native code, so this falls back to a pure-JS
32
32
  engine automatically. Covers layout, spacing, border/radius, plain colors,
33
- font-weight, text-transform/decoration, line-height, letter-spacing, and
34
- shadows. Doesn't cover grid, transforms, filters, gradients, or DOM-only
35
- typography — same scope native itself excludes.
33
+ font-weight, text-transform/decoration, line-height, letter-spacing,
34
+ shadows, and transforms (`translate-x`/`translate-y`, `scale`/`scale-x`/
35
+ `scale-y`, `rotate`/`rotate-x`/`rotate-y`/`rotate-z`, `skew-x`/`skew-y`,
36
+ `transform-none`, `backface-visible`/`backface-hidden`). Doesn't cover grid,
37
+ filters, gradients, DOM-only typography, or the rest of the transform family
38
+ (`translate-z`/`scale-z`, `perspective`/`perspective-origin`/`origin`,
39
+ `transform-gpu`/`transform-cpu` — no RN equivalent for any of these) — same
40
+ scope native itself excludes.
36
41
 
37
42
  ### Expo (dev client / prebuild)
38
43
 
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ import 'react';
7
7
  type StyleValue = string | number | {
8
8
  width: number;
9
9
  height: number;
10
- };
10
+ } | Array<Record<string, string | number>>;
11
11
  interface StyleObject {
12
12
  [key: string]: StyleValue;
13
13
  }
package/dist/index.js CHANGED
@@ -177,12 +177,19 @@ var VALUE_PREFIXES = [
177
177
  "justify-self-",
178
178
  "scale-x-",
179
179
  "scale-y-",
180
+ "scale-z-",
180
181
  "scale-",
182
+ "rotate-x-",
183
+ "rotate-y-",
184
+ "rotate-z-",
181
185
  "rotate-",
182
186
  "translate-x-",
183
187
  "translate-y-",
188
+ "translate-z-",
184
189
  "skew-x-",
185
190
  "skew-y-",
191
+ "perspective-origin-",
192
+ "perspective-",
186
193
  "origin-",
187
194
  "blur-",
188
195
  "brightness-",
@@ -1202,6 +1209,77 @@ function nativeShadowDeclarations(parsed) {
1202
1209
  ];
1203
1210
  }
1204
1211
 
1212
+ // src/jsEngine/resolvers/transform.ts
1213
+ function scaleFactor(parsed) {
1214
+ if (parsed.value === null) return null;
1215
+ const n = Number(parsed.value);
1216
+ if (!Number.isFinite(n)) return null;
1217
+ return parsed.isArbitrary ? n : n / 100;
1218
+ }
1219
+ function angle(parsed) {
1220
+ if (parsed.value === null) return null;
1221
+ if (parsed.isArbitrary) return parsed.value;
1222
+ const n = Number(parsed.value);
1223
+ if (!Number.isFinite(n)) return null;
1224
+ return `${parsed.negative ? -n : n}deg`;
1225
+ }
1226
+ function resolveNative(parsed, theme) {
1227
+ switch (parsed.utility) {
1228
+ case "transform-none":
1229
+ return [decl("transform-op-none", "1")];
1230
+ case "backface-visible":
1231
+ return [decl("backface-visibility", "visible")];
1232
+ case "backface-hidden":
1233
+ return [decl("backface-visibility", "hidden")];
1234
+ case "translate-x": {
1235
+ const v = resolveNegatableLength(theme, parsed);
1236
+ return v === null ? null : [decl("transform-op-translate-x", v)];
1237
+ }
1238
+ case "translate-y": {
1239
+ const v = resolveNegatableLength(theme, parsed);
1240
+ return v === null ? null : [decl("transform-op-translate-y", v)];
1241
+ }
1242
+ case "scale": {
1243
+ const n = scaleFactor(parsed);
1244
+ return n === null ? null : [decl("transform-op-scale-x", `${n}`), decl("transform-op-scale-y", `${n}`)];
1245
+ }
1246
+ case "scale-x": {
1247
+ const n = scaleFactor(parsed);
1248
+ return n === null ? null : [decl("transform-op-scale-x", `${n}`)];
1249
+ }
1250
+ case "scale-y": {
1251
+ const n = scaleFactor(parsed);
1252
+ return n === null ? null : [decl("transform-op-scale-y", `${n}`)];
1253
+ }
1254
+ case "rotate": {
1255
+ const deg = angle(parsed);
1256
+ return deg === null ? null : [decl("transform-op-rotate", deg)];
1257
+ }
1258
+ case "rotate-x": {
1259
+ const deg = angle(parsed);
1260
+ return deg === null ? null : [decl("transform-op-rotate-x", deg)];
1261
+ }
1262
+ case "rotate-y": {
1263
+ const deg = angle(parsed);
1264
+ return deg === null ? null : [decl("transform-op-rotate-y", deg)];
1265
+ }
1266
+ case "rotate-z": {
1267
+ const deg = angle(parsed);
1268
+ return deg === null ? null : [decl("transform-op-rotate-z", deg)];
1269
+ }
1270
+ case "skew-x": {
1271
+ const deg = angle(parsed);
1272
+ return deg === null ? null : [decl("transform-op-skew-x", deg)];
1273
+ }
1274
+ case "skew-y": {
1275
+ const deg = angle(parsed);
1276
+ return deg === null ? null : [decl("transform-op-skew-y", deg)];
1277
+ }
1278
+ default:
1279
+ return null;
1280
+ }
1281
+ }
1282
+
1205
1283
  // src/jsEngine/resolveUtilityNative.ts
1206
1284
  function firstFontName(stack) {
1207
1285
  const first = stack.split(",")[0] ?? stack;
@@ -1283,7 +1361,7 @@ function resolveOpacityNative(parsed) {
1283
1361
  return v === null ? null : [decl("opacity", v)];
1284
1362
  }
1285
1363
  function resolveUtilityNative(parsed, theme) {
1286
- return resolveFlex(parsed, false) ?? resolve(parsed, theme) ?? resolve2(parsed, theme) ?? resolve3(parsed, theme) ?? resolveColorNative(parsed, theme) ?? resolveLeadingTrackingNative(parsed) ?? resolveTypographyNative(parsed, theme) ?? resolveOpacityNative(parsed) ?? nativeShadowDeclarations(parsed);
1364
+ return resolveFlex(parsed, false) ?? resolve(parsed, theme) ?? resolve2(parsed, theme) ?? resolve3(parsed, theme) ?? resolveColorNative(parsed, theme) ?? resolveLeadingTrackingNative(parsed) ?? resolveTypographyNative(parsed, theme) ?? resolveOpacityNative(parsed) ?? nativeShadowDeclarations(parsed) ?? resolveNative(parsed, theme);
1287
1365
  }
1288
1366
 
1289
1367
  // src/jsEngine/calc.ts
@@ -1501,8 +1579,32 @@ var NUMERIC_LENGTH_PROPS = /* @__PURE__ */ new Set([
1501
1579
  "border-bottom-right-radius",
1502
1580
  "border-bottom-left-radius",
1503
1581
  "outline-width",
1504
- "outline-offset"
1582
+ "outline-offset",
1583
+ // The transform ops whose RN value is a plain JS number — translate takes
1584
+ // a length (px/percentage, same as top/left/etc. above) and scale takes a
1585
+ // bare decimal factor (parsed by the same "raw value" fallback branch
1586
+ // below). transform-op-rotate*/transform-op-skew* stay OUT of this set on
1587
+ // purpose, since RN wants those as literal "45deg"-shaped strings, not
1588
+ // numbers. See the transform-ops accumulator below for how these markers
1589
+ // get collected into the final array.
1590
+ "transform-op-translate-x",
1591
+ "transform-op-translate-y",
1592
+ "transform-op-scale-x",
1593
+ "transform-op-scale-y"
1505
1594
  ]);
1595
+ var TRANSFORM_OP_KEYS = /* @__PURE__ */ new Map([
1596
+ ["transform-op-translate-x", "translateX"],
1597
+ ["transform-op-translate-y", "translateY"],
1598
+ ["transform-op-rotate", "rotate"],
1599
+ ["transform-op-rotate-x", "rotateX"],
1600
+ ["transform-op-rotate-y", "rotateY"],
1601
+ ["transform-op-rotate-z", "rotateZ"],
1602
+ ["transform-op-skew-x", "skewX"],
1603
+ ["transform-op-skew-y", "skewY"],
1604
+ ["transform-op-scale-x", "scaleX"],
1605
+ ["transform-op-scale-y", "scaleY"]
1606
+ ]);
1607
+ var TRANSFORM_OP_ORDER = ["translateX", "translateY", "rotate", "rotateX", "rotateY", "rotateZ", "skewX", "skewY", "scaleX", "scaleY"];
1506
1608
  function isPlainPercentage(value) {
1507
1609
  if (!value.endsWith("%")) return false;
1508
1610
  let digits = value.slice(0, -1);
@@ -1539,6 +1641,7 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
1539
1641
  const style = {};
1540
1642
  const warnings = [];
1541
1643
  let shadowOffset = null;
1644
+ const transformOps = /* @__PURE__ */ new Map();
1542
1645
  for (const token of classString.split(/\s+/).filter(Boolean)) {
1543
1646
  const parsed = parseClass(token);
1544
1647
  const allModifiersHold = parsed.modifiers.every(
@@ -1558,6 +1661,17 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
1558
1661
  shadowOffset[d.property === "shadow-offset-x" ? "width" : "height"] = n;
1559
1662
  continue;
1560
1663
  }
1664
+ if (d.property === "transform-op-none") {
1665
+ transformOps.clear();
1666
+ continue;
1667
+ }
1668
+ const opKey = TRANSFORM_OP_KEYS.get(d.property);
1669
+ if (opKey !== void 0) {
1670
+ const { value: value2, warning: warning2 } = rnStyleValue(d.property, d.value);
1671
+ if (warning2 !== null) warnings.push(warning2);
1672
+ if (value2 !== null) transformOps.set(opKey, value2);
1673
+ continue;
1674
+ }
1561
1675
  const { value, warning } = rnStyleValue(d.property, d.value);
1562
1676
  if (warning !== null) warnings.push(warning);
1563
1677
  if (value !== null) style[kebabToCamel(d.property)] = value;
@@ -1566,6 +1680,11 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
1566
1680
  if (shadowOffset !== null) {
1567
1681
  style.shadowOffset = { width: shadowOffset.width ?? 0, height: shadowOffset.height ?? 0 };
1568
1682
  }
1683
+ if (transformOps.size > 0) {
1684
+ style.transform = TRANSFORM_OP_ORDER.filter((key) => transformOps.has(key)).map((key) => ({
1685
+ [key]: transformOps.get(key)
1686
+ }));
1687
+ }
1569
1688
  return { style, warnings };
1570
1689
  }
1571
1690
 
@@ -375,12 +375,19 @@ var VALUE_PREFIXES = [
375
375
  "justify-self-",
376
376
  "scale-x-",
377
377
  "scale-y-",
378
+ "scale-z-",
378
379
  "scale-",
380
+ "rotate-x-",
381
+ "rotate-y-",
382
+ "rotate-z-",
379
383
  "rotate-",
380
384
  "translate-x-",
381
385
  "translate-y-",
386
+ "translate-z-",
382
387
  "skew-x-",
383
388
  "skew-y-",
389
+ "perspective-origin-",
390
+ "perspective-",
384
391
  "origin-",
385
392
  "blur-",
386
393
  "brightness-",
@@ -1583,6 +1590,77 @@ function nativeShadowDeclarations(parsed) {
1583
1590
  ];
1584
1591
  }
1585
1592
 
1593
+ // src/jsEngine/resolvers/transform.ts
1594
+ function scaleFactor(parsed) {
1595
+ if (parsed.value === null) return null;
1596
+ const n = Number(parsed.value);
1597
+ if (!Number.isFinite(n)) return null;
1598
+ return parsed.isArbitrary ? n : n / 100;
1599
+ }
1600
+ function angle(parsed) {
1601
+ if (parsed.value === null) return null;
1602
+ if (parsed.isArbitrary) return parsed.value;
1603
+ const n = Number(parsed.value);
1604
+ if (!Number.isFinite(n)) return null;
1605
+ return `${parsed.negative ? -n : n}deg`;
1606
+ }
1607
+ function resolveNative(parsed, theme) {
1608
+ switch (parsed.utility) {
1609
+ case "transform-none":
1610
+ return [decl("transform-op-none", "1")];
1611
+ case "backface-visible":
1612
+ return [decl("backface-visibility", "visible")];
1613
+ case "backface-hidden":
1614
+ return [decl("backface-visibility", "hidden")];
1615
+ case "translate-x": {
1616
+ const v = resolveNegatableLength(theme, parsed);
1617
+ return v === null ? null : [decl("transform-op-translate-x", v)];
1618
+ }
1619
+ case "translate-y": {
1620
+ const v = resolveNegatableLength(theme, parsed);
1621
+ return v === null ? null : [decl("transform-op-translate-y", v)];
1622
+ }
1623
+ case "scale": {
1624
+ const n = scaleFactor(parsed);
1625
+ return n === null ? null : [decl("transform-op-scale-x", `${n}`), decl("transform-op-scale-y", `${n}`)];
1626
+ }
1627
+ case "scale-x": {
1628
+ const n = scaleFactor(parsed);
1629
+ return n === null ? null : [decl("transform-op-scale-x", `${n}`)];
1630
+ }
1631
+ case "scale-y": {
1632
+ const n = scaleFactor(parsed);
1633
+ return n === null ? null : [decl("transform-op-scale-y", `${n}`)];
1634
+ }
1635
+ case "rotate": {
1636
+ const deg = angle(parsed);
1637
+ return deg === null ? null : [decl("transform-op-rotate", deg)];
1638
+ }
1639
+ case "rotate-x": {
1640
+ const deg = angle(parsed);
1641
+ return deg === null ? null : [decl("transform-op-rotate-x", deg)];
1642
+ }
1643
+ case "rotate-y": {
1644
+ const deg = angle(parsed);
1645
+ return deg === null ? null : [decl("transform-op-rotate-y", deg)];
1646
+ }
1647
+ case "rotate-z": {
1648
+ const deg = angle(parsed);
1649
+ return deg === null ? null : [decl("transform-op-rotate-z", deg)];
1650
+ }
1651
+ case "skew-x": {
1652
+ const deg = angle(parsed);
1653
+ return deg === null ? null : [decl("transform-op-skew-x", deg)];
1654
+ }
1655
+ case "skew-y": {
1656
+ const deg = angle(parsed);
1657
+ return deg === null ? null : [decl("transform-op-skew-y", deg)];
1658
+ }
1659
+ default:
1660
+ return null;
1661
+ }
1662
+ }
1663
+
1586
1664
  // src/jsEngine/resolveUtilityNative.ts
1587
1665
  function firstFontName(stack) {
1588
1666
  const first = stack.split(",")[0] ?? stack;
@@ -1664,7 +1742,7 @@ function resolveOpacityNative(parsed) {
1664
1742
  return v === null ? null : [decl("opacity", v)];
1665
1743
  }
1666
1744
  function resolveUtilityNative(parsed, theme) {
1667
- return resolveFlex(parsed, false) ?? resolve(parsed, theme) ?? resolve2(parsed, theme) ?? resolve3(parsed, theme) ?? resolveColorNative(parsed, theme) ?? resolveLeadingTrackingNative(parsed) ?? resolveTypographyNative(parsed, theme) ?? resolveOpacityNative(parsed) ?? nativeShadowDeclarations(parsed);
1745
+ return resolveFlex(parsed, false) ?? resolve(parsed, theme) ?? resolve2(parsed, theme) ?? resolve3(parsed, theme) ?? resolveColorNative(parsed, theme) ?? resolveLeadingTrackingNative(parsed) ?? resolveTypographyNative(parsed, theme) ?? resolveOpacityNative(parsed) ?? nativeShadowDeclarations(parsed) ?? resolveNative(parsed, theme);
1668
1746
  }
1669
1747
 
1670
1748
  // src/jsEngine/resolveStyle.ts
@@ -1735,8 +1813,32 @@ var NUMERIC_LENGTH_PROPS = /* @__PURE__ */ new Set([
1735
1813
  "border-bottom-right-radius",
1736
1814
  "border-bottom-left-radius",
1737
1815
  "outline-width",
1738
- "outline-offset"
1816
+ "outline-offset",
1817
+ // The transform ops whose RN value is a plain JS number — translate takes
1818
+ // a length (px/percentage, same as top/left/etc. above) and scale takes a
1819
+ // bare decimal factor (parsed by the same "raw value" fallback branch
1820
+ // below). transform-op-rotate*/transform-op-skew* stay OUT of this set on
1821
+ // purpose, since RN wants those as literal "45deg"-shaped strings, not
1822
+ // numbers. See the transform-ops accumulator below for how these markers
1823
+ // get collected into the final array.
1824
+ "transform-op-translate-x",
1825
+ "transform-op-translate-y",
1826
+ "transform-op-scale-x",
1827
+ "transform-op-scale-y"
1739
1828
  ]);
1829
+ var TRANSFORM_OP_KEYS = /* @__PURE__ */ new Map([
1830
+ ["transform-op-translate-x", "translateX"],
1831
+ ["transform-op-translate-y", "translateY"],
1832
+ ["transform-op-rotate", "rotate"],
1833
+ ["transform-op-rotate-x", "rotateX"],
1834
+ ["transform-op-rotate-y", "rotateY"],
1835
+ ["transform-op-rotate-z", "rotateZ"],
1836
+ ["transform-op-skew-x", "skewX"],
1837
+ ["transform-op-skew-y", "skewY"],
1838
+ ["transform-op-scale-x", "scaleX"],
1839
+ ["transform-op-scale-y", "scaleY"]
1840
+ ]);
1841
+ var TRANSFORM_OP_ORDER = ["translateX", "translateY", "rotate", "rotateX", "rotateY", "rotateZ", "skewX", "skewY", "scaleX", "scaleY"];
1740
1842
  function isPlainPercentage(value) {
1741
1843
  if (!value.endsWith("%")) return false;
1742
1844
  let digits = value.slice(0, -1);
@@ -1773,6 +1875,7 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
1773
1875
  const style = {};
1774
1876
  const warnings = [];
1775
1877
  let shadowOffset = null;
1878
+ const transformOps = /* @__PURE__ */ new Map();
1776
1879
  for (const token of classString.split(/\s+/).filter(Boolean)) {
1777
1880
  const parsed = parseClass(token);
1778
1881
  const allModifiersHold = parsed.modifiers.every(
@@ -1792,6 +1895,17 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
1792
1895
  shadowOffset[d.property === "shadow-offset-x" ? "width" : "height"] = n;
1793
1896
  continue;
1794
1897
  }
1898
+ if (d.property === "transform-op-none") {
1899
+ transformOps.clear();
1900
+ continue;
1901
+ }
1902
+ const opKey = TRANSFORM_OP_KEYS.get(d.property);
1903
+ if (opKey !== void 0) {
1904
+ const { value: value2, warning: warning2 } = rnStyleValue(d.property, d.value);
1905
+ if (warning2 !== null) warnings.push(warning2);
1906
+ if (value2 !== null) transformOps.set(opKey, value2);
1907
+ continue;
1908
+ }
1795
1909
  const { value, warning } = rnStyleValue(d.property, d.value);
1796
1910
  if (warning !== null) warnings.push(warning);
1797
1911
  if (value !== null) style[kebabToCamel(d.property)] = value;
@@ -1800,6 +1914,11 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
1800
1914
  if (shadowOffset !== null) {
1801
1915
  style.shadowOffset = { width: shadowOffset.width ?? 0, height: shadowOffset.height ?? 0 };
1802
1916
  }
1917
+ if (transformOps.size > 0) {
1918
+ style.transform = TRANSFORM_OP_ORDER.filter((key) => transformOps.has(key)).map((key) => ({
1919
+ [key]: transformOps.get(key)
1920
+ }));
1921
+ }
1803
1922
  return { style, warnings };
1804
1923
  }
1805
1924
 
@@ -376,12 +376,19 @@ var VALUE_PREFIXES = [
376
376
  "justify-self-",
377
377
  "scale-x-",
378
378
  "scale-y-",
379
+ "scale-z-",
379
380
  "scale-",
381
+ "rotate-x-",
382
+ "rotate-y-",
383
+ "rotate-z-",
380
384
  "rotate-",
381
385
  "translate-x-",
382
386
  "translate-y-",
387
+ "translate-z-",
383
388
  "skew-x-",
384
389
  "skew-y-",
390
+ "perspective-origin-",
391
+ "perspective-",
385
392
  "origin-",
386
393
  "blur-",
387
394
  "brightness-",
@@ -1574,6 +1581,77 @@ function nativeShadowDeclarations(parsed) {
1574
1581
  ];
1575
1582
  }
1576
1583
 
1584
+ // src/jsEngine/resolvers/transform.ts
1585
+ function scaleFactor(parsed) {
1586
+ if (parsed.value === null) return null;
1587
+ const n = Number(parsed.value);
1588
+ if (!Number.isFinite(n)) return null;
1589
+ return parsed.isArbitrary ? n : n / 100;
1590
+ }
1591
+ function angle(parsed) {
1592
+ if (parsed.value === null) return null;
1593
+ if (parsed.isArbitrary) return parsed.value;
1594
+ const n = Number(parsed.value);
1595
+ if (!Number.isFinite(n)) return null;
1596
+ return `${parsed.negative ? -n : n}deg`;
1597
+ }
1598
+ function resolveNative(parsed, theme) {
1599
+ switch (parsed.utility) {
1600
+ case "transform-none":
1601
+ return [decl("transform-op-none", "1")];
1602
+ case "backface-visible":
1603
+ return [decl("backface-visibility", "visible")];
1604
+ case "backface-hidden":
1605
+ return [decl("backface-visibility", "hidden")];
1606
+ case "translate-x": {
1607
+ const v = resolveNegatableLength(theme, parsed);
1608
+ return v === null ? null : [decl("transform-op-translate-x", v)];
1609
+ }
1610
+ case "translate-y": {
1611
+ const v = resolveNegatableLength(theme, parsed);
1612
+ return v === null ? null : [decl("transform-op-translate-y", v)];
1613
+ }
1614
+ case "scale": {
1615
+ const n = scaleFactor(parsed);
1616
+ return n === null ? null : [decl("transform-op-scale-x", `${n}`), decl("transform-op-scale-y", `${n}`)];
1617
+ }
1618
+ case "scale-x": {
1619
+ const n = scaleFactor(parsed);
1620
+ return n === null ? null : [decl("transform-op-scale-x", `${n}`)];
1621
+ }
1622
+ case "scale-y": {
1623
+ const n = scaleFactor(parsed);
1624
+ return n === null ? null : [decl("transform-op-scale-y", `${n}`)];
1625
+ }
1626
+ case "rotate": {
1627
+ const deg = angle(parsed);
1628
+ return deg === null ? null : [decl("transform-op-rotate", deg)];
1629
+ }
1630
+ case "rotate-x": {
1631
+ const deg = angle(parsed);
1632
+ return deg === null ? null : [decl("transform-op-rotate-x", deg)];
1633
+ }
1634
+ case "rotate-y": {
1635
+ const deg = angle(parsed);
1636
+ return deg === null ? null : [decl("transform-op-rotate-y", deg)];
1637
+ }
1638
+ case "rotate-z": {
1639
+ const deg = angle(parsed);
1640
+ return deg === null ? null : [decl("transform-op-rotate-z", deg)];
1641
+ }
1642
+ case "skew-x": {
1643
+ const deg = angle(parsed);
1644
+ return deg === null ? null : [decl("transform-op-skew-x", deg)];
1645
+ }
1646
+ case "skew-y": {
1647
+ const deg = angle(parsed);
1648
+ return deg === null ? null : [decl("transform-op-skew-y", deg)];
1649
+ }
1650
+ default:
1651
+ return null;
1652
+ }
1653
+ }
1654
+
1577
1655
  // src/jsEngine/resolveUtilityNative.ts
1578
1656
  function firstFontName(stack) {
1579
1657
  const first = stack.split(",")[0] ?? stack;
@@ -1655,7 +1733,7 @@ function resolveOpacityNative(parsed) {
1655
1733
  return v === null ? null : [decl("opacity", v)];
1656
1734
  }
1657
1735
  function resolveUtilityNative(parsed, theme) {
1658
- return resolveFlex(parsed, false) ?? resolve(parsed, theme) ?? resolve2(parsed, theme) ?? resolve3(parsed, theme) ?? resolveColorNative(parsed, theme) ?? resolveLeadingTrackingNative(parsed) ?? resolveTypographyNative(parsed, theme) ?? resolveOpacityNative(parsed) ?? nativeShadowDeclarations(parsed);
1736
+ return resolveFlex(parsed, false) ?? resolve(parsed, theme) ?? resolve2(parsed, theme) ?? resolve3(parsed, theme) ?? resolveColorNative(parsed, theme) ?? resolveLeadingTrackingNative(parsed) ?? resolveTypographyNative(parsed, theme) ?? resolveOpacityNative(parsed) ?? nativeShadowDeclarations(parsed) ?? resolveNative(parsed, theme);
1659
1737
  }
1660
1738
 
1661
1739
  // src/jsEngine/resolveStyle.ts
@@ -1726,8 +1804,32 @@ var NUMERIC_LENGTH_PROPS = /* @__PURE__ */ new Set([
1726
1804
  "border-bottom-right-radius",
1727
1805
  "border-bottom-left-radius",
1728
1806
  "outline-width",
1729
- "outline-offset"
1807
+ "outline-offset",
1808
+ // The transform ops whose RN value is a plain JS number — translate takes
1809
+ // a length (px/percentage, same as top/left/etc. above) and scale takes a
1810
+ // bare decimal factor (parsed by the same "raw value" fallback branch
1811
+ // below). transform-op-rotate*/transform-op-skew* stay OUT of this set on
1812
+ // purpose, since RN wants those as literal "45deg"-shaped strings, not
1813
+ // numbers. See the transform-ops accumulator below for how these markers
1814
+ // get collected into the final array.
1815
+ "transform-op-translate-x",
1816
+ "transform-op-translate-y",
1817
+ "transform-op-scale-x",
1818
+ "transform-op-scale-y"
1730
1819
  ]);
1820
+ var TRANSFORM_OP_KEYS = /* @__PURE__ */ new Map([
1821
+ ["transform-op-translate-x", "translateX"],
1822
+ ["transform-op-translate-y", "translateY"],
1823
+ ["transform-op-rotate", "rotate"],
1824
+ ["transform-op-rotate-x", "rotateX"],
1825
+ ["transform-op-rotate-y", "rotateY"],
1826
+ ["transform-op-rotate-z", "rotateZ"],
1827
+ ["transform-op-skew-x", "skewX"],
1828
+ ["transform-op-skew-y", "skewY"],
1829
+ ["transform-op-scale-x", "scaleX"],
1830
+ ["transform-op-scale-y", "scaleY"]
1831
+ ]);
1832
+ var TRANSFORM_OP_ORDER = ["translateX", "translateY", "rotate", "rotateX", "rotateY", "rotateZ", "skewX", "skewY", "scaleX", "scaleY"];
1731
1833
  function isPlainPercentage(value) {
1732
1834
  if (!value.endsWith("%")) return false;
1733
1835
  let digits = value.slice(0, -1);
@@ -1764,6 +1866,7 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
1764
1866
  const style = {};
1765
1867
  const warnings = [];
1766
1868
  let shadowOffset = null;
1869
+ const transformOps = /* @__PURE__ */ new Map();
1767
1870
  for (const token of classString.split(/\s+/).filter(Boolean)) {
1768
1871
  const parsed = parseClass(token);
1769
1872
  const allModifiersHold = parsed.modifiers.every(
@@ -1783,6 +1886,17 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
1783
1886
  shadowOffset[d.property === "shadow-offset-x" ? "width" : "height"] = n;
1784
1887
  continue;
1785
1888
  }
1889
+ if (d.property === "transform-op-none") {
1890
+ transformOps.clear();
1891
+ continue;
1892
+ }
1893
+ const opKey = TRANSFORM_OP_KEYS.get(d.property);
1894
+ if (opKey !== void 0) {
1895
+ const { value: value2, warning: warning2 } = rnStyleValue(d.property, d.value);
1896
+ if (warning2 !== null) warnings.push(warning2);
1897
+ if (value2 !== null) transformOps.set(opKey, value2);
1898
+ continue;
1899
+ }
1786
1900
  const { value, warning } = rnStyleValue(d.property, d.value);
1787
1901
  if (warning !== null) warnings.push(warning);
1788
1902
  if (value !== null) style[kebabToCamel(d.property)] = value;
@@ -1791,6 +1905,11 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
1791
1905
  if (shadowOffset !== null) {
1792
1906
  style.shadowOffset = { width: shadowOffset.width ?? 0, height: shadowOffset.height ?? 0 };
1793
1907
  }
1908
+ if (transformOps.size > 0) {
1909
+ style.transform = TRANSFORM_OP_ORDER.filter((key) => transformOps.has(key)).map((key) => ({
1910
+ [key]: transformOps.get(key)
1911
+ }));
1912
+ }
1794
1913
  return { style, warnings };
1795
1914
  }
1796
1915
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kbach/react-native",
3
- "version": "1.0.0-beta.22",
3
+ "version": "1.0.0-beta.23",
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",