@kbach/react-native 1.0.0-beta.16 → 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.
@@ -1,17 +1,21 @@
1
1
  import {
2
+ deleteDynamicToken,
3
+ getDynamicToken,
2
4
  getGlobalDarkMode,
3
5
  getGlobalThemeMode,
4
6
  seedDefaultMode,
7
+ setDynamicToken,
5
8
  setGlobalThemeMode,
9
+ subscribeDynamicTokens,
6
10
  subscribeGlobalDarkMode,
7
11
  toggleGlobalDarkMode
8
- } from "./chunk-XGQWM65X.mjs";
12
+ } from "./chunk-45GOGUHJ.mjs";
9
13
  import {
10
14
  defaultTheme,
11
15
  getTheme,
12
16
  resolveClassName,
13
17
  setTheme
14
- } from "./chunk-3ED4FIO2.mjs";
18
+ } from "./chunk-OGFB3OXS.mjs";
15
19
 
16
20
  // src/config.ts
17
21
  function parseHexRgb(hex) {
@@ -177,19 +181,30 @@ function useColors() {
177
181
  const { isDark } = useTheme();
178
182
  return makeColorsProxy(getTheme(), isDark);
179
183
  }
184
+
185
+ // src/useDynamicToken.ts
186
+ import { useSyncExternalStore as useSyncExternalStore2 } from "react";
187
+ function useDynamicToken(name) {
188
+ return useSyncExternalStore2(subscribeDynamicTokens, () => getDynamicToken(name));
189
+ }
180
190
  export {
181
191
  ThemeProvider,
182
192
  applyKbachConfig,
183
193
  defaultTheme,
194
+ deleteDynamicToken,
195
+ getDynamicToken,
184
196
  getGlobalDarkMode,
185
197
  getGlobalThemeMode,
186
198
  getTheme,
187
199
  resolveClassName,
188
200
  resolveKbachConfig,
201
+ setDynamicToken,
189
202
  setGlobalThemeMode,
190
203
  setTheme,
204
+ subscribeDynamicTokens,
191
205
  subscribeGlobalDarkMode,
192
206
  toggleGlobalDarkMode,
193
207
  useColors,
208
+ useDynamicToken,
194
209
  useTheme
195
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