@kbach/react-native 1.0.0-beta.15 → 1.0.0-beta.17

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.
Files changed (35) hide show
  1. package/README.md +75 -0
  2. package/dist/{chunk-RBJXE32L.mjs → chunk-45GOGUHJ.mjs} +36 -2
  3. package/dist/{chunk-VD233W3L.mjs → chunk-MJWWZJMH.mjs} +1 -1
  4. package/dist/chunk-OGFB3OXS.mjs +552 -0
  5. package/dist/darkModeStore.d.ts +58 -0
  6. package/dist/dynamicTokens.d.ts +35 -0
  7. package/dist/dynamicTokens.js +75 -0
  8. package/dist/{index.d.mts → index.d.ts} +17 -1
  9. package/dist/index.js +223 -19
  10. package/dist/index.web.d.ts +18 -0
  11. package/dist/index.web.js +21 -1
  12. package/dist/index.web.mjs +184 -12
  13. package/dist/jsx-dev-runtime.js +222 -22
  14. package/dist/jsx-dev-runtime.web.js +2 -1
  15. package/dist/jsx-dev-runtime.web.mjs +18 -7
  16. package/dist/jsx-runtime.js +222 -22
  17. package/dist/jsx-runtime.web.js +1 -1
  18. package/dist/jsx-runtime.web.mjs +2 -3
  19. package/dist/theme.d.ts +54 -0
  20. package/dist/{useColors-C6jyCLuG.d.mts → useDynamicToken-Dr15c88t.d.ts} +16 -95
  21. package/package.json +1 -5
  22. package/dist/chunk-AXGAYOQ7.mjs +0 -192
  23. package/dist/chunk-EMZ47OSG.mjs +0 -100
  24. package/dist/chunk-HK6PDJJK.mjs +0 -367
  25. package/dist/chunk-JMNJCPGK.mjs +0 -1239
  26. package/dist/chunk-TDWBXJH5.mjs +0 -186
  27. package/dist/chunk-UNZ6PQVO.mjs +0 -14
  28. package/dist/index.mjs +0 -38
  29. package/dist/index.web.d.mts +0 -15
  30. package/dist/jsx-dev-runtime.mjs +0 -18
  31. package/dist/jsx-runtime.mjs +0 -15
  32. /package/dist/{jsx-dev-runtime.d.mts → jsx-dev-runtime.d.ts} +0 -0
  33. /package/dist/{jsx-dev-runtime.web.d.mts → jsx-dev-runtime.web.d.ts} +0 -0
  34. /package/dist/{jsx-runtime.d.mts → jsx-runtime.d.ts} +0 -0
  35. /package/dist/{jsx-runtime.web.d.mts → jsx-runtime.web.d.ts} +0 -0
@@ -1,38 +1,210 @@
1
1
  import {
2
- ThemeProvider,
3
- applyKbachConfig,
4
- resolveKbachConfig,
5
- useColors,
6
- useTheme
7
- } from "./chunk-TDWBXJH5.mjs";
8
- import {
9
- resolveClassName
10
- } from "./chunk-AXGAYOQ7.mjs";
11
- import {
2
+ deleteDynamicToken,
3
+ getDynamicToken,
12
4
  getGlobalDarkMode,
13
5
  getGlobalThemeMode,
6
+ seedDefaultMode,
7
+ setDynamicToken,
14
8
  setGlobalThemeMode,
9
+ subscribeDynamicTokens,
15
10
  subscribeGlobalDarkMode,
16
11
  toggleGlobalDarkMode
17
- } from "./chunk-RBJXE32L.mjs";
12
+ } from "./chunk-45GOGUHJ.mjs";
18
13
  import {
19
14
  defaultTheme,
20
15
  getTheme,
16
+ resolveClassName,
21
17
  setTheme
22
- } from "./chunk-HK6PDJJK.mjs";
18
+ } from "./chunk-OGFB3OXS.mjs";
19
+
20
+ // src/config.ts
21
+ function parseHexRgb(hex) {
22
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
23
+ if (!m) return null;
24
+ let h = m[1];
25
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
26
+ const n = parseInt(h, 16);
27
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
28
+ }
29
+ function applyOpacityToHex(hex, opacityText) {
30
+ const rgb = parseHexRgb(hex);
31
+ if (!rgb) return hex;
32
+ const a = Number(opacityText) / 100;
33
+ const clamped = Number.isFinite(a) ? Math.min(1, Math.max(0, a)) : 1;
34
+ return `rgba(${rgb.join(",")},${clamped})`;
35
+ }
36
+ function resolveColorEntry(value, lookup) {
37
+ const slash = value.indexOf("/");
38
+ const name = slash === -1 ? value : value.slice(0, slash);
39
+ const opacity = slash === -1 ? void 0 : value.slice(slash + 1);
40
+ const base = lookup(name);
41
+ if (base === void 0) return value;
42
+ return opacity === void 0 ? base : applyOpacityToHex(base, opacity);
43
+ }
44
+ function resolveColors(extend, existing) {
45
+ const resolved = {};
46
+ const lookup = (name) => {
47
+ const found = resolved[name] ?? existing[name];
48
+ return typeof found === "string" ? found : void 0;
49
+ };
50
+ for (const [name, value] of Object.entries(extend)) {
51
+ resolved[name] = resolveColorEntry(value, lookup);
52
+ }
53
+ return resolved;
54
+ }
55
+ function resolveKbachConfig(config) {
56
+ let theme = { ...defaultTheme, ...config.theme };
57
+ if (config.extend?.colors) {
58
+ theme = { ...theme, colors: { ...theme.colors, ...resolveColors(config.extend.colors, theme.colors) } };
59
+ }
60
+ if (config.extend?.spacing) {
61
+ theme = { ...theme, spacing: { ...theme.spacing, ...config.extend.spacing } };
62
+ }
63
+ if (config.extend?.screens) {
64
+ theme = { ...theme, screens: { ...theme.screens, ...config.extend.screens } };
65
+ }
66
+ if (config.extend?.fontFamily) {
67
+ theme = { ...theme, fontFamily: { ...theme.fontFamily, ...config.extend.fontFamily } };
68
+ }
69
+ if (config.extend?.container) {
70
+ theme = { ...theme, container: { ...theme.container, ...config.extend.container } };
71
+ }
72
+ if (config.darkMode) {
73
+ theme = { ...theme, darkMode: config.darkMode };
74
+ }
75
+ return theme;
76
+ }
77
+ function applyKbachConfig(config) {
78
+ const theme = resolveKbachConfig(config);
79
+ setTheme(theme);
80
+ return theme;
81
+ }
82
+
83
+ // src/useTheme.ts
84
+ import { useMemo, useSyncExternalStore } from "react";
85
+ function useTheme() {
86
+ const isDark = useSyncExternalStore(subscribeGlobalDarkMode, getGlobalDarkMode);
87
+ const mode = useSyncExternalStore(subscribeGlobalDarkMode, getGlobalThemeMode);
88
+ return useMemo(
89
+ () => ({ mode, isDark, setMode: setGlobalThemeMode, toggle: toggleGlobalDarkMode }),
90
+ [mode, isDark]
91
+ );
92
+ }
93
+
94
+ // src/ThemeProvider.tsx
95
+ import { useEffect, useRef } from "react";
96
+ import { Fragment, jsx } from "react/jsx-runtime";
97
+ var mountedProviderCount = 0;
98
+ var warnedMultipleProviders = false;
99
+ function ThemeProvider({ children, defaultMode = "system" }) {
100
+ useEffect(() => {
101
+ mountedProviderCount++;
102
+ if (mountedProviderCount > 1 && !warnedMultipleProviders) {
103
+ warnedMultipleProviders = true;
104
+ console.warn(
105
+ "[kbach] Multiple <ThemeProvider> instances are mounted at once. Dark-mode state is one global store useTheme() reads everywhere, not scoped per-provider, so their defaultMode seeds will fight over the same state."
106
+ );
107
+ }
108
+ return () => {
109
+ mountedProviderCount--;
110
+ };
111
+ }, []);
112
+ const seeded = useRef(false);
113
+ useEffect(() => {
114
+ if (seeded.current) return;
115
+ seeded.current = true;
116
+ seedDefaultMode(defaultMode);
117
+ }, []);
118
+ return /* @__PURE__ */ jsx(Fragment, { children });
119
+ }
120
+
121
+ // src/useColors.ts
122
+ function parseHexRgb2(hex) {
123
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
124
+ if (!m) return null;
125
+ let h = m[1];
126
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
127
+ const n = parseInt(h, 16);
128
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
129
+ }
130
+ function applyOpacity(color, opacity) {
131
+ const a = Math.max(0, Math.min(1, opacity / 100));
132
+ if (color.startsWith("#")) {
133
+ const rgb = parseHexRgb2(color);
134
+ return rgb ? `rgba(${rgb.join(",")},${a})` : color;
135
+ }
136
+ if (color.startsWith("rgb(")) return color.replace("rgb(", "rgba(").replace(")", `,${a})`);
137
+ if (color.startsWith("rgba(")) return color.replace(/,\s*[\d.]+\)$/, `,${a})`);
138
+ return color;
139
+ }
140
+ function resolveEntry(entry, isDark) {
141
+ return typeof entry === "string" ? entry : isDark ? entry.dark : entry.light;
142
+ }
143
+ function makeColorsProxy(theme, isDark) {
144
+ const alpha = (color, opacity) => opacity === void 0 ? color : applyOpacity(color, opacity);
145
+ const cache = /* @__PURE__ */ new Map();
146
+ function resolveNamed(key) {
147
+ let name = key;
148
+ let opacity;
149
+ if (key.includes("/")) {
150
+ const slash = key.indexOf("/");
151
+ name = key.slice(0, slash);
152
+ opacity = Number(key.slice(slash + 1));
153
+ }
154
+ const entry = theme.colors[name];
155
+ if (entry === void 0) return void 0;
156
+ const value = resolveEntry(entry, isDark);
157
+ return opacity === void 0 ? value : applyOpacity(value, opacity);
158
+ }
159
+ function makeFamilyProxy(family) {
160
+ return new Proxy({}, {
161
+ get(_, prop) {
162
+ const key = String(prop);
163
+ if (key === "then") return void 0;
164
+ return resolveNamed(`${family}-${key}`);
165
+ }
166
+ });
167
+ }
168
+ return new Proxy({ alpha }, {
169
+ get(_, prop) {
170
+ const key = String(prop);
171
+ if (key === "then") return void 0;
172
+ if (key === "alpha") return alpha;
173
+ const direct = resolveNamed(key);
174
+ if (direct !== void 0) return direct;
175
+ if (!cache.has(key)) cache.set(key, makeFamilyProxy(key));
176
+ return cache.get(key);
177
+ }
178
+ });
179
+ }
180
+ function useColors() {
181
+ const { isDark } = useTheme();
182
+ return makeColorsProxy(getTheme(), isDark);
183
+ }
184
+
185
+ // src/useDynamicToken.ts
186
+ import { useSyncExternalStore as useSyncExternalStore2 } from "react";
187
+ function useDynamicToken(name) {
188
+ return useSyncExternalStore2(subscribeDynamicTokens, () => getDynamicToken(name));
189
+ }
23
190
  export {
24
191
  ThemeProvider,
25
192
  applyKbachConfig,
26
193
  defaultTheme,
194
+ deleteDynamicToken,
195
+ getDynamicToken,
27
196
  getGlobalDarkMode,
28
197
  getGlobalThemeMode,
29
198
  getTheme,
30
199
  resolveClassName,
31
200
  resolveKbachConfig,
201
+ setDynamicToken,
32
202
  setGlobalThemeMode,
33
203
  setTheme,
204
+ subscribeDynamicTokens,
34
205
  subscribeGlobalDarkMode,
35
206
  toggleGlobalDarkMode,
36
207
  useColors,
208
+ useDynamicToken,
37
209
  useTheme
38
210
  };
@@ -30,12 +30,21 @@ var import_jsx_runtime = require("react/jsx-runtime");
30
30
  var import_react = require("react");
31
31
  var import_react_native = require("react-native");
32
32
  var import_darkModeStore = require("./darkModeStore");
33
+ var import_dynamicTokens = require("./dynamicTokens");
33
34
  var import_theme = require("./theme");
35
+ var DYNAMIC_TOKEN_MATCH_RE = /var\(--([a-zA-Z0-9-]+)\)/g;
36
+ function substituteDynamicTokens(classStrRaw) {
37
+ if (!classStrRaw.includes("var(--")) return classStrRaw;
38
+ return classStrRaw.replace(DYNAMIC_TOKEN_MATCH_RE, (whole, name) => {
39
+ const value = (0, import_dynamicTokens.getDynamicToken)(name);
40
+ return value ?? whole;
41
+ });
42
+ }
34
43
  function makeElement(isStaticChildren, type, props, key) {
35
44
  return (isStaticChildren ? import_jsx_runtime.jsxs : import_jsx_runtime.jsx)(type, props, key);
36
45
  }
37
46
  function resolvedStyleFor(resolveStyle2, classStrRaw, userStyle, pressed) {
38
- const resolved = resolveStyle2(classStrRaw, pressed);
47
+ const resolved = resolveStyle2(substituteDynamicTokens(classStrRaw), pressed);
39
48
  if (userStyle === void 0) {
40
49
  return resolved;
41
50
  }
@@ -60,6 +69,12 @@ function breakpointKeySuffix(classStrRaw, width) {
60
69
  const state = present.map((name) => screens[name] !== void 0 && width >= screens[name] ? "1" : "0").join("");
61
70
  return `:kb-bp-${state}`;
62
71
  }
72
+ function dynamicTokenKeySuffix(classStrRaw) {
73
+ const matches = classStrRaw.match(DYNAMIC_TOKEN_MATCH_RE);
74
+ if (!matches) return "";
75
+ const values = matches.map((m) => (0, import_dynamicTokens.getDynamicToken)(m.slice("var(--".length, -1)) ?? "");
76
+ return `:kb-dt-${values.join("|")}`;
77
+ }
63
78
  function ReactiveElement({
64
79
  hostType,
65
80
  hostRest,
@@ -70,9 +85,10 @@ function ReactiveElement({
70
85
  isStaticChildren
71
86
  }) {
72
87
  (0, import_react.useSyncExternalStore)(import_darkModeStore.subscribeGlobalDarkMode, import_darkModeStore.getGlobalDarkMode);
88
+ (0, import_react.useSyncExternalStore)(import_dynamicTokens.subscribeDynamicTokens, import_dynamicTokens.getDynamicTokensVersion);
73
89
  const { width } = (0, import_react_native.useWindowDimensions)();
74
90
  const finalStyle = hostType === import_react_native.Pressable ? (state) => resolvedStyleFor(resolveStyle2, classStrRaw, userStyle, state.pressed) : resolvedStyleFor(resolveStyle2, classStrRaw, userStyle, false);
75
- const suffix = darkModeKeySuffix(classStrRaw) + breakpointKeySuffix(classStrRaw, width);
91
+ const suffix = darkModeKeySuffix(classStrRaw) + breakpointKeySuffix(classStrRaw, width) + dynamicTokenKeySuffix(classStrRaw);
76
92
  const finalKey = suffix ? `${elementKey ?? ""}${suffix}` : elementKey;
77
93
  return makeElement(isStaticChildren, hostType, { ...hostRest, style: finalStyle }, finalKey);
78
94
  }
@@ -84,7 +100,7 @@ function processElement(resolveStyle2, type, rawProps, key, isStaticChildren) {
84
100
  if (typeof classStrRaw !== "string") {
85
101
  return makeElement(isStaticChildren, type, rawProps, key);
86
102
  }
87
- if (DARK_MODIFIER_RE.test(classStrRaw) || BREAKPOINT_MODIFIER_RE.test(classStrRaw)) {
103
+ if (DARK_MODIFIER_RE.test(classStrRaw) || BREAKPOINT_MODIFIER_RE.test(classStrRaw) || classStrRaw.includes("var(--")) {
88
104
  return (0, import_jsx_runtime.jsx)(
89
105
  ReactiveElement,
90
106
  { hostType: type, hostRest: rest, classStrRaw, userStyle, resolveStyle: resolveStyle2, elementKey: key, isStaticChildren },
@@ -1215,6 +1231,153 @@ function resolveUtilityNative(parsed, theme) {
1215
1231
  return resolveFlex(parsed, false) ?? resolve(parsed, theme) ?? resolve2(parsed, theme) ?? resolve3(parsed, theme) ?? resolveColorNative(parsed, theme) ?? resolveLeadingTrackingNative(parsed) ?? resolveTypographyNative(parsed) ?? nativeShadowDeclarations(parsed);
1216
1232
  }
1217
1233
 
1234
+ // src/jsEngine/calc.ts
1235
+ function reduceConstantMath(raw) {
1236
+ const trimmed = raw.trim();
1237
+ const calc = stripCall(trimmed, "calc");
1238
+ if (calc !== null) {
1239
+ return new Evaluator(calc).parseExprToEnd();
1240
+ }
1241
+ const clamp = stripCall(trimmed, "clamp");
1242
+ if (clamp !== null) {
1243
+ const args = splitTopLevelCommas(clamp);
1244
+ if (args.length !== 3) return null;
1245
+ const [min2, preferred, max2] = args.map((a) => new Evaluator(a).parseExprToEnd());
1246
+ if (min2 === null || preferred === null || max2 === null) return null;
1247
+ return Math.max(min2, Math.min(preferred, max2));
1248
+ }
1249
+ const min = stripCall(trimmed, "min");
1250
+ if (min !== null) {
1251
+ const values = splitTopLevelCommas(min).map((a) => new Evaluator(a).parseExprToEnd());
1252
+ return values.some((v) => v === null) ? null : Math.min(...values);
1253
+ }
1254
+ const max = stripCall(trimmed, "max");
1255
+ if (max !== null) {
1256
+ const values = splitTopLevelCommas(max).map((a) => new Evaluator(a).parseExprToEnd());
1257
+ return values.some((v) => v === null) ? null : Math.max(...values);
1258
+ }
1259
+ return null;
1260
+ }
1261
+ function stripCall(raw, name) {
1262
+ if (!raw.startsWith(name)) return null;
1263
+ const afterName = raw.slice(name.length).trimStart();
1264
+ if (!afterName.startsWith("(") || !afterName.endsWith(")")) return null;
1265
+ return afterName.slice(1, -1);
1266
+ }
1267
+ function splitTopLevelCommas(s) {
1268
+ const parts = [];
1269
+ let depth = 0;
1270
+ let start = 0;
1271
+ for (let i = 0; i < s.length; i++) {
1272
+ const ch = s[i];
1273
+ if (ch === "(") depth++;
1274
+ else if (ch === ")") depth--;
1275
+ else if (ch === "," && depth === 0) {
1276
+ parts.push(s.slice(start, i).trim());
1277
+ start = i + 1;
1278
+ }
1279
+ }
1280
+ parts.push(s.slice(start).trim());
1281
+ return parts;
1282
+ }
1283
+ var Evaluator = class {
1284
+ constructor(s) {
1285
+ this.pos = 0;
1286
+ this.s = s;
1287
+ }
1288
+ parseExprToEnd() {
1289
+ const value = this.expr();
1290
+ if (value === null) return null;
1291
+ this.skipWs();
1292
+ return this.pos === this.s.length ? value : null;
1293
+ }
1294
+ skipWs() {
1295
+ while (this.pos < this.s.length && /\s/.test(this.s[this.pos])) this.pos++;
1296
+ }
1297
+ peek() {
1298
+ this.skipWs();
1299
+ return this.s[this.pos];
1300
+ }
1301
+ expr() {
1302
+ let value = this.term();
1303
+ if (value === null) return null;
1304
+ for (; ; ) {
1305
+ const ch = this.peek();
1306
+ if (ch === "+") {
1307
+ this.pos++;
1308
+ const rhs = this.term();
1309
+ if (rhs === null) return null;
1310
+ value += rhs;
1311
+ } else if (ch === "-") {
1312
+ this.pos++;
1313
+ const rhs = this.term();
1314
+ if (rhs === null) return null;
1315
+ value -= rhs;
1316
+ } else {
1317
+ return value;
1318
+ }
1319
+ }
1320
+ }
1321
+ term() {
1322
+ let value = this.factor();
1323
+ if (value === null) return null;
1324
+ for (; ; ) {
1325
+ const ch = this.peek();
1326
+ if (ch === "*") {
1327
+ this.pos++;
1328
+ const rhs = this.factor();
1329
+ if (rhs === null) return null;
1330
+ value *= rhs;
1331
+ } else if (ch === "/") {
1332
+ this.pos++;
1333
+ const divisor = this.factor();
1334
+ if (divisor === null || divisor === 0) return null;
1335
+ value /= divisor;
1336
+ } else {
1337
+ return value;
1338
+ }
1339
+ }
1340
+ }
1341
+ factor() {
1342
+ const ch = this.peek();
1343
+ if (ch === void 0) return null;
1344
+ if (ch === "(") {
1345
+ this.pos++;
1346
+ const value = this.expr();
1347
+ if (value === null || this.peek() !== ")") return null;
1348
+ this.pos++;
1349
+ return value;
1350
+ }
1351
+ if (ch === "-") {
1352
+ this.pos++;
1353
+ const value = this.factor();
1354
+ return value === null ? null : -value;
1355
+ }
1356
+ return this.numberWithUnit();
1357
+ }
1358
+ numberWithUnit() {
1359
+ this.skipWs();
1360
+ const start = this.pos;
1361
+ while (this.pos < this.s.length && (/[0-9]/.test(this.s[this.pos]) || this.s[this.pos] === ".")) this.pos++;
1362
+ if (this.pos === start) return null;
1363
+ const n = Number(this.s.slice(start, this.pos));
1364
+ if (!Number.isFinite(n)) return null;
1365
+ const unitStart = this.pos;
1366
+ while (this.pos < this.s.length && /[a-zA-Z]/.test(this.s[this.pos])) this.pos++;
1367
+ const unit = this.s.slice(unitStart, this.pos);
1368
+ switch (unit) {
1369
+ case "":
1370
+ return n;
1371
+ case "px":
1372
+ return n;
1373
+ case "rem":
1374
+ return n * 16;
1375
+ default:
1376
+ return null;
1377
+ }
1378
+ }
1379
+ };
1380
+
1218
1381
  // src/jsEngine/resolveStyle.ts
1219
1382
  var RESPONSIVE_BREAKPOINTS = /* @__PURE__ */ new Set(["sm", "md", "lg", "xl", "2xl"]);
1220
1383
  function nativeModifierState(modifier, theme, colorScheme, pressed, width) {
@@ -1284,25 +1447,41 @@ var NUMERIC_LENGTH_PROPS = /* @__PURE__ */ new Set([
1284
1447
  "outline-width",
1285
1448
  "outline-offset"
1286
1449
  ]);
1450
+ function isPlainPercentage(value) {
1451
+ if (!value.endsWith("%")) return false;
1452
+ let digits = value.slice(0, -1);
1453
+ if (digits.startsWith("-")) digits = digits.slice(1);
1454
+ return digits.length > 0 && /^[0-9.]+$/.test(digits);
1455
+ }
1287
1456
  function rnStyleValue(property, value) {
1288
- if (NUMERIC_LENGTH_PROPS.has(property)) {
1289
- let px = null;
1290
- if (value.endsWith("px")) {
1291
- const n = Number(value.slice(0, -2));
1292
- px = Number.isFinite(n) ? n : null;
1293
- } else if (value.endsWith("rem")) {
1294
- const n = Number(value.slice(0, -3));
1295
- px = Number.isFinite(n) ? n * 16 : null;
1296
- } else {
1297
- const n = Number(value);
1298
- px = value.trim() !== "" && Number.isFinite(n) ? n : null;
1299
- }
1300
- if (px !== null) return px;
1457
+ if (!NUMERIC_LENGTH_PROPS.has(property)) {
1458
+ return { value, warning: null };
1459
+ }
1460
+ let px = null;
1461
+ if (value.endsWith("px")) {
1462
+ const n = Number(value.slice(0, -2));
1463
+ px = Number.isFinite(n) ? n : null;
1464
+ } else if (value.endsWith("rem")) {
1465
+ const n = Number(value.slice(0, -3));
1466
+ px = Number.isFinite(n) ? n * 16 : null;
1467
+ } else {
1468
+ const n = Number(value);
1469
+ px = value.trim() !== "" && Number.isFinite(n) ? n : null;
1470
+ }
1471
+ if (px !== null) return { value: px, warning: null };
1472
+ if (isPlainPercentage(value)) {
1473
+ return { value, warning: null };
1474
+ }
1475
+ const reduced = reduceConstantMath(value);
1476
+ if (reduced !== null) {
1477
+ return { value: reduced, warning: null };
1301
1478
  }
1302
- return value;
1479
+ const warning = `Kbach: "${value}" is not a valid native value for "${property}" \u2014 dropped. calc()/clamp()/min()/max() only resolve on native when every operand is a constant px/rem length (no %, vw, vh, var(), or other viewport/CSS-variable units \u2014 those need real layout/DOM, which doesn't exist on native at paint time). Use a plain px/rem calc, a fraction utility (e.g. w-1/2), or resolve this value in JS instead.`;
1480
+ return { value: null, warning };
1303
1481
  }
1304
- function resolveStyleJs(classString, theme, colorScheme, pressed, width) {
1482
+ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, width) {
1305
1483
  const style = {};
1484
+ const warnings = [];
1306
1485
  let shadowOffset = null;
1307
1486
  for (const token of classString.split(/\s+/).filter(Boolean)) {
1308
1487
  const parsed = parseClass(token);
@@ -1323,13 +1502,15 @@ function resolveStyleJs(classString, theme, colorScheme, pressed, width) {
1323
1502
  shadowOffset[d.property === "shadow-offset-x" ? "width" : "height"] = n;
1324
1503
  continue;
1325
1504
  }
1326
- style[kebabToCamel(d.property)] = rnStyleValue(d.property, d.value);
1505
+ const { value, warning } = rnStyleValue(d.property, d.value);
1506
+ if (warning !== null) warnings.push(warning);
1507
+ if (value !== null) style[kebabToCamel(d.property)] = value;
1327
1508
  }
1328
1509
  }
1329
1510
  if (shadowOffset !== null) {
1330
1511
  style.shadowOffset = { width: shadowOffset.width ?? 0, height: shadowOffset.height ?? 0 };
1331
1512
  }
1332
- return style;
1513
+ return { style, warnings };
1333
1514
  }
1334
1515
 
1335
1516
  // src/nativeBridge.ts
@@ -1338,10 +1519,29 @@ function resolveStyle(classString, pressed = false) {
1338
1519
  const width = import_react_native2.Dimensions.get("window").width;
1339
1520
  const KbachModule = import_react_native2.TurboModuleRegistry.get("KbachModule");
1340
1521
  if (!KbachModule) {
1341
- return resolveStyleJs(classString, (0, import_theme2.getTheme)(), colorScheme, pressed, width);
1522
+ const { style: style2, warnings: warnings2 } = resolveStyleJsWithWarnings(classString, (0, import_theme2.getTheme)(), colorScheme, pressed, width);
1523
+ warnIfDev(classString, warnings2);
1524
+ return style2;
1342
1525
  }
1343
1526
  const json = KbachModule.resolveStyle(classString, (0, import_theme2.getThemeJson)(), colorScheme, pressed, width);
1344
- return JSON.parse(json);
1527
+ const style = JSON.parse(json);
1528
+ const warnings = style.__kbachWarnings;
1529
+ if (warnings !== void 0) {
1530
+ delete style.__kbachWarnings;
1531
+ warnIfDev(classString, warnings);
1532
+ }
1533
+ return style;
1534
+ }
1535
+ var warnedMessages = /* @__PURE__ */ new Set();
1536
+ function warnIfDev(classString, warnings) {
1537
+ if (warnings.length === 0) return;
1538
+ if (typeof __DEV__ !== "undefined" && !__DEV__) return;
1539
+ for (const warning of warnings) {
1540
+ const message = `${warning} (from className "${classString}")`;
1541
+ if (warnedMessages.has(message)) continue;
1542
+ warnedMessages.add(message);
1543
+ console.warn(message);
1544
+ }
1345
1545
  }
1346
1546
 
1347
1547
  // src/jsx-runtime.tsx