@kbach/react-native 1.0.0-beta.21 → 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 +42 -11
- package/android/src/main/jniLibs/arm64-v8a/libkbach_core_engine.so +0 -0
- package/android/src/main/jniLibs/x86_64/libkbach_core_engine.so +0 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +148 -4
- package/dist/index.web.d.ts +1 -1
- package/dist/index.web.js +27 -2
- package/dist/index.web.mjs +27 -2
- package/dist/jsx-dev-runtime.js +165 -6
- package/dist/jsx-runtime.js +165 -6
- package/dist/{useDynamicToken-Dr15c88t.d.ts → useDynamicToken-DAHQob1u.d.ts} +37 -6
- package/package.json +1 -1
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,
|
|
34
|
-
shadows
|
|
35
|
-
|
|
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
|
|
|
@@ -173,8 +178,26 @@ optional, for seeding a startup default. Outside React, use
|
|
|
173
178
|
`getGlobalDarkMode()`/`toggleGlobalDarkMode()` instead.
|
|
174
179
|
|
|
175
180
|
Follows the OS setting live until overridden, and isn't persisted across
|
|
176
|
-
app launches (no
|
|
177
|
-
|
|
181
|
+
app launches by default (no forced storage dependency). To persist, pass
|
|
182
|
+
any async key-value store exposing `getItem`/`setItem` as `<ThemeProvider
|
|
183
|
+
persist>` — `@react-native-async-storage/async-storage`'s default export
|
|
184
|
+
already matches this shape exactly:
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
188
|
+
import { ThemeProvider } from '@kbach/react-native';
|
|
189
|
+
|
|
190
|
+
<ThemeProvider persist={AsyncStorage}>
|
|
191
|
+
<App />
|
|
192
|
+
</ThemeProvider>;
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The persisted mode (if any) is read once on mount and wins over
|
|
196
|
+
`defaultMode`; every later explicit choice is written back automatically.
|
|
197
|
+
`false` by default — this package still never imports a storage library
|
|
198
|
+
itself, so nothing changes for apps that don't opt in. Prefer wiring your
|
|
199
|
+
own storage manually instead? `setGlobalThemeMode()` before your first
|
|
200
|
+
render still works exactly as before.
|
|
178
201
|
|
|
179
202
|
## Reading colors as values
|
|
180
203
|
|
|
@@ -222,12 +245,15 @@ way — no such unit exists on native at all. It already works fine on web
|
|
|
222
245
|
(real CSS); on native the declaration is dropped and a `console.warn`
|
|
223
246
|
explains why (dev builds only).
|
|
224
247
|
|
|
225
|
-
**Percentage-relative width/height** (`calc(100%-3rem)`, `calc(50%+16px)
|
|
226
|
-
|
|
227
|
-
|
|
248
|
+
**Percentage-relative width/height** (`calc(100%-3rem)`, `calc(50%+16px)`,
|
|
249
|
+
`min(50%,20rem)`, `max(1rem,5%)`, `clamp(16rem,50%,32rem)`) IS supported,
|
|
250
|
+
differently: since native has no way to know "100% of what" ahead of time,
|
|
251
|
+
this measures the element's own real layout instead —
|
|
228
252
|
|
|
229
253
|
```tsx
|
|
230
254
|
<View className="w-[calc(100%_-_3rem)]" />
|
|
255
|
+
<View className="w-[min(50%,20rem)]" /> // half the parent, capped at a fixed size
|
|
256
|
+
<View className="w-[clamp(16rem,50%,32rem)]" />
|
|
231
257
|
```
|
|
232
258
|
|
|
233
259
|
Renders once at a plain `100%` (a real RN percentage, resolved correctly by
|
|
@@ -236,9 +262,14 @@ event, then immediately corrects to the actual computed number once
|
|
|
236
262
|
measured — a genuine one-frame measure-then-snap, not instant math like the
|
|
237
263
|
constant-only case above, and it keeps re-measuring on every layout (screen
|
|
238
264
|
rotation, parent resize, ...) rather than freezing after the first value.
|
|
239
|
-
Scoped
|
|
240
|
-
|
|
241
|
-
|
|
265
|
+
Scoped narrowly, same "reduce fully or bail" philosophy as the constant-only
|
|
266
|
+
case: `calc()` takes exactly one percentage term plus one constant term;
|
|
267
|
+
`min()`/`max()` take two or more comma-separated arguments, each EITHER a
|
|
268
|
+
plain percentage or a constant px/rem expression (not both mixed in one
|
|
269
|
+
argument — `min(50%-1rem,20rem)` is out of scope); `clamp()` takes exactly
|
|
270
|
+
three. `width`/`height` only in every case — other properties, or anything
|
|
271
|
+
that doesn't reduce fully, falls through to the same drop-and-warn behavior
|
|
272
|
+
as before.
|
|
242
273
|
|
|
243
274
|
**Arbitrary values can't contain literal spaces** — this is a pre-existing
|
|
244
275
|
rule (real Tailwind has the same one), not specific to `calc()`: a space
|
|
Binary file
|
|
Binary file
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ColorEntry, ContainerConfig, ThemeConfig, defaultTheme, getTheme, setTheme } from './theme';
|
|
2
|
-
export { C as ColorScale, a as ColorsAPI, K as KbachConfig, T as ThemeProvider, b as ThemeProviderProps, c as ThemeState, d as applyKbachConfig, r as resolveKbachConfig, u as useColors,
|
|
2
|
+
export { C as ColorScale, a as ColorsAPI, K as KbachConfig, T as ThemeProvider, b as ThemeProviderProps, c as ThemeState, d as ThemeStorageAdapter, e as applyKbachConfig, r as resolveKbachConfig, u as useColors, f as useDynamicToken, g as useTheme } from './useDynamicToken-DAHQob1u.js';
|
|
3
3
|
export { ThemeMode, getGlobalDarkMode, getGlobalThemeMode, setGlobalThemeMode, subscribeGlobalDarkMode, toggleGlobalDarkMode } from './darkModeStore';
|
|
4
4
|
export { deleteDynamicToken, getDynamicToken, setDynamicToken, subscribeDynamicTokens } from './dynamicTokens';
|
|
5
5
|
import 'react';
|
|
@@ -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"
|
|
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"]
|
|
1505
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
|
|
|
@@ -1680,9 +1799,13 @@ function useTheme() {
|
|
|
1680
1799
|
var import_react2 = require("react");
|
|
1681
1800
|
var import_darkModeStore3 = require("./darkModeStore");
|
|
1682
1801
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1802
|
+
var PERSIST_STORAGE_KEY = "kbach-theme";
|
|
1803
|
+
function isThemeMode(value) {
|
|
1804
|
+
return value === "light" || value === "dark" || value === "system";
|
|
1805
|
+
}
|
|
1683
1806
|
var mountedProviderCount = 0;
|
|
1684
1807
|
var warnedMultipleProviders = false;
|
|
1685
|
-
function ThemeProvider({ children, defaultMode = "system" }) {
|
|
1808
|
+
function ThemeProvider({ children, defaultMode = "system", persist = false }) {
|
|
1686
1809
|
(0, import_react2.useEffect)(() => {
|
|
1687
1810
|
mountedProviderCount++;
|
|
1688
1811
|
if (mountedProviderCount > 1 && !warnedMultipleProviders) {
|
|
@@ -1699,8 +1822,29 @@ function ThemeProvider({ children, defaultMode = "system" }) {
|
|
|
1699
1822
|
(0, import_react2.useEffect)(() => {
|
|
1700
1823
|
if (seeded.current) return;
|
|
1701
1824
|
seeded.current = true;
|
|
1702
|
-
|
|
1825
|
+
if (!persist) {
|
|
1826
|
+
(0, import_darkModeStore3.seedDefaultMode)(defaultMode);
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
let cancelled = false;
|
|
1830
|
+
persist.getItem(PERSIST_STORAGE_KEY).then((stored) => {
|
|
1831
|
+
if (cancelled) return;
|
|
1832
|
+
if (isThemeMode(stored)) (0, import_darkModeStore3.seedDefaultMode)(stored);
|
|
1833
|
+
else (0, import_darkModeStore3.seedDefaultMode)(defaultMode);
|
|
1834
|
+
}).catch(() => {
|
|
1835
|
+
if (!cancelled) (0, import_darkModeStore3.seedDefaultMode)(defaultMode);
|
|
1836
|
+
});
|
|
1837
|
+
return () => {
|
|
1838
|
+
cancelled = true;
|
|
1839
|
+
};
|
|
1703
1840
|
}, []);
|
|
1841
|
+
(0, import_react2.useEffect)(() => {
|
|
1842
|
+
if (!persist) return;
|
|
1843
|
+
return (0, import_darkModeStore3.subscribeGlobalDarkMode)(() => {
|
|
1844
|
+
persist.setItem(PERSIST_STORAGE_KEY, (0, import_darkModeStore3.getGlobalThemeMode)()).catch(() => {
|
|
1845
|
+
});
|
|
1846
|
+
});
|
|
1847
|
+
}, [persist]);
|
|
1704
1848
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
|
|
1705
1849
|
}
|
|
1706
1850
|
|
package/dist/index.web.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ColorEntry, ContainerConfig, ThemeConfig, defaultTheme, getTheme, setTheme } from './theme';
|
|
2
|
-
export { C as ColorScale, a as ColorsAPI, K as KbachConfig, T as ThemeProvider, b as ThemeProviderProps, c as ThemeState,
|
|
2
|
+
export { C as ColorScale, a as ColorsAPI, K as KbachConfig, T as ThemeProvider, b as ThemeProviderProps, c as ThemeState, e as applyKbachConfig, r as resolveKbachConfig, u as useColors, f as useDynamicToken, g as useTheme } from './useDynamicToken-DAHQob1u.js';
|
|
3
3
|
export { ThemeMode, getGlobalDarkMode, getGlobalThemeMode, setGlobalThemeMode, subscribeGlobalDarkMode, toggleGlobalDarkMode } from './darkModeStore';
|
|
4
4
|
export { deleteDynamicToken, getDynamicToken, setDynamicToken, subscribeDynamicTokens } from './dynamicTokens';
|
|
5
5
|
import 'react';
|
package/dist/index.web.js
CHANGED
|
@@ -311,9 +311,13 @@ function useTheme() {
|
|
|
311
311
|
var import_react2 = require("react");
|
|
312
312
|
var import_darkModeStore2 = require("./darkModeStore");
|
|
313
313
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
314
|
+
var PERSIST_STORAGE_KEY = "kbach-theme";
|
|
315
|
+
function isThemeMode(value) {
|
|
316
|
+
return value === "light" || value === "dark" || value === "system";
|
|
317
|
+
}
|
|
314
318
|
var mountedProviderCount = 0;
|
|
315
319
|
var warnedMultipleProviders = false;
|
|
316
|
-
function ThemeProvider({ children, defaultMode = "system" }) {
|
|
320
|
+
function ThemeProvider({ children, defaultMode = "system", persist = false }) {
|
|
317
321
|
(0, import_react2.useEffect)(() => {
|
|
318
322
|
mountedProviderCount++;
|
|
319
323
|
if (mountedProviderCount > 1 && !warnedMultipleProviders) {
|
|
@@ -330,8 +334,29 @@ function ThemeProvider({ children, defaultMode = "system" }) {
|
|
|
330
334
|
(0, import_react2.useEffect)(() => {
|
|
331
335
|
if (seeded.current) return;
|
|
332
336
|
seeded.current = true;
|
|
333
|
-
|
|
337
|
+
if (!persist) {
|
|
338
|
+
(0, import_darkModeStore2.seedDefaultMode)(defaultMode);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
let cancelled = false;
|
|
342
|
+
persist.getItem(PERSIST_STORAGE_KEY).then((stored) => {
|
|
343
|
+
if (cancelled) return;
|
|
344
|
+
if (isThemeMode(stored)) (0, import_darkModeStore2.seedDefaultMode)(stored);
|
|
345
|
+
else (0, import_darkModeStore2.seedDefaultMode)(defaultMode);
|
|
346
|
+
}).catch(() => {
|
|
347
|
+
if (!cancelled) (0, import_darkModeStore2.seedDefaultMode)(defaultMode);
|
|
348
|
+
});
|
|
349
|
+
return () => {
|
|
350
|
+
cancelled = true;
|
|
351
|
+
};
|
|
334
352
|
}, []);
|
|
353
|
+
(0, import_react2.useEffect)(() => {
|
|
354
|
+
if (!persist) return;
|
|
355
|
+
return (0, import_darkModeStore2.subscribeGlobalDarkMode)(() => {
|
|
356
|
+
persist.setItem(PERSIST_STORAGE_KEY, (0, import_darkModeStore2.getGlobalThemeMode)()).catch(() => {
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
}, [persist]);
|
|
335
360
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
|
|
336
361
|
}
|
|
337
362
|
|
package/dist/index.web.mjs
CHANGED
|
@@ -94,9 +94,13 @@ function useTheme() {
|
|
|
94
94
|
// src/ThemeProvider.tsx
|
|
95
95
|
import { useEffect, useRef } from "react";
|
|
96
96
|
import { Fragment, jsx } from "react/jsx-runtime";
|
|
97
|
+
var PERSIST_STORAGE_KEY = "kbach-theme";
|
|
98
|
+
function isThemeMode(value) {
|
|
99
|
+
return value === "light" || value === "dark" || value === "system";
|
|
100
|
+
}
|
|
97
101
|
var mountedProviderCount = 0;
|
|
98
102
|
var warnedMultipleProviders = false;
|
|
99
|
-
function ThemeProvider({ children, defaultMode = "system" }) {
|
|
103
|
+
function ThemeProvider({ children, defaultMode = "system", persist = false }) {
|
|
100
104
|
useEffect(() => {
|
|
101
105
|
mountedProviderCount++;
|
|
102
106
|
if (mountedProviderCount > 1 && !warnedMultipleProviders) {
|
|
@@ -113,8 +117,29 @@ function ThemeProvider({ children, defaultMode = "system" }) {
|
|
|
113
117
|
useEffect(() => {
|
|
114
118
|
if (seeded.current) return;
|
|
115
119
|
seeded.current = true;
|
|
116
|
-
|
|
120
|
+
if (!persist) {
|
|
121
|
+
seedDefaultMode(defaultMode);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
let cancelled = false;
|
|
125
|
+
persist.getItem(PERSIST_STORAGE_KEY).then((stored) => {
|
|
126
|
+
if (cancelled) return;
|
|
127
|
+
if (isThemeMode(stored)) seedDefaultMode(stored);
|
|
128
|
+
else seedDefaultMode(defaultMode);
|
|
129
|
+
}).catch(() => {
|
|
130
|
+
if (!cancelled) seedDefaultMode(defaultMode);
|
|
131
|
+
});
|
|
132
|
+
return () => {
|
|
133
|
+
cancelled = true;
|
|
134
|
+
};
|
|
117
135
|
}, []);
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!persist) return;
|
|
138
|
+
return subscribeGlobalDarkMode(() => {
|
|
139
|
+
persist.setItem(PERSIST_STORAGE_KEY, getGlobalThemeMode()).catch(() => {
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}, [persist]);
|
|
118
143
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
119
144
|
}
|
|
120
145
|
|
package/dist/jsx-dev-runtime.js
CHANGED
|
@@ -205,6 +205,46 @@ function parsePercentRelativeCalc(token) {
|
|
|
205
205
|
function resolvePercentRelativeCalc(basisPx, calc) {
|
|
206
206
|
return basisPx * calc.percentCoefficient / 100 + calc.constantPx;
|
|
207
207
|
}
|
|
208
|
+
var PERCENT_MIN_MAX_CLAMP_TOKEN_RE = /^(w|h)-\[(min|max|clamp)\(([^)]+)\)\]$/;
|
|
209
|
+
function parseOperand(arg) {
|
|
210
|
+
const trimmed = arg.trim();
|
|
211
|
+
const pct = /^(-?[\d.]+)%$/.exec(trimmed);
|
|
212
|
+
if (pct) {
|
|
213
|
+
const percentCoefficient = Number(pct[1]);
|
|
214
|
+
return Number.isFinite(percentCoefficient) ? { percentCoefficient, constantPx: 0 } : null;
|
|
215
|
+
}
|
|
216
|
+
const constantPx = reduceConstantMath(`calc(${trimmed})`);
|
|
217
|
+
return constantPx === null ? null : { percentCoefficient: 0, constantPx };
|
|
218
|
+
}
|
|
219
|
+
function parsePercentRelativeMinMaxClamp(token) {
|
|
220
|
+
const m = PERCENT_MIN_MAX_CLAMP_TOKEN_RE.exec(token);
|
|
221
|
+
if (!m) return null;
|
|
222
|
+
const property = m[1] === "w" ? "width" : "height";
|
|
223
|
+
const kind = m[2];
|
|
224
|
+
const args = splitTopLevelCommas(m[3].replace(/_/g, " "));
|
|
225
|
+
if (kind === "clamp" && args.length !== 3) return null;
|
|
226
|
+
if (kind !== "clamp" && args.length < 1) return null;
|
|
227
|
+
const operands = args.map(parseOperand);
|
|
228
|
+
if (operands.some((o) => o === null)) return null;
|
|
229
|
+
const resolved = operands;
|
|
230
|
+
if (!resolved.some((o) => o.percentCoefficient !== 0)) return null;
|
|
231
|
+
const resolveOperand = (o, basisPx) => basisPx * o.percentCoefficient / 100 + o.constantPx;
|
|
232
|
+
return {
|
|
233
|
+
property,
|
|
234
|
+
resolve(basisPx) {
|
|
235
|
+
const values = resolved.map((o) => resolveOperand(o, basisPx));
|
|
236
|
+
if (kind === "min") return Math.min(...values);
|
|
237
|
+
if (kind === "max") return Math.max(...values);
|
|
238
|
+
const [min, preferred, max] = values;
|
|
239
|
+
return Math.max(min, Math.min(preferred, max));
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function parsePercentRelativeExpr(token) {
|
|
244
|
+
const calc = parsePercentRelativeCalc(token);
|
|
245
|
+
if (calc) return { property: calc.property, resolve: (basisPx) => resolvePercentRelativeCalc(basisPx, calc) };
|
|
246
|
+
return parsePercentRelativeMinMaxClamp(token);
|
|
247
|
+
}
|
|
208
248
|
|
|
209
249
|
// src/jsEngine/parser.ts
|
|
210
250
|
var VALUE_PREFIXES = [
|
|
@@ -335,12 +375,19 @@ var VALUE_PREFIXES = [
|
|
|
335
375
|
"justify-self-",
|
|
336
376
|
"scale-x-",
|
|
337
377
|
"scale-y-",
|
|
378
|
+
"scale-z-",
|
|
338
379
|
"scale-",
|
|
380
|
+
"rotate-x-",
|
|
381
|
+
"rotate-y-",
|
|
382
|
+
"rotate-z-",
|
|
339
383
|
"rotate-",
|
|
340
384
|
"translate-x-",
|
|
341
385
|
"translate-y-",
|
|
386
|
+
"translate-z-",
|
|
342
387
|
"skew-x-",
|
|
343
388
|
"skew-y-",
|
|
389
|
+
"perspective-origin-",
|
|
390
|
+
"perspective-",
|
|
344
391
|
"origin-",
|
|
345
392
|
"blur-",
|
|
346
393
|
"brightness-",
|
|
@@ -471,17 +518,17 @@ function substituteDynamicTokens(classStrRaw) {
|
|
|
471
518
|
return value ?? whole;
|
|
472
519
|
});
|
|
473
520
|
}
|
|
474
|
-
var PERCENT_RELATIVE_CALC_HINT_RE = /[wh]-\[calc\([^)]*%[^)]*\)\]/;
|
|
521
|
+
var PERCENT_RELATIVE_CALC_HINT_RE = /[wh]-\[(calc|min|max|clamp)\([^)]*%[^)]*\)\]/;
|
|
475
522
|
function extractPercentRelativeCalcs(classStrRaw) {
|
|
476
523
|
const found = [];
|
|
477
524
|
for (const token of classStrRaw.split(/\s+/)) {
|
|
478
|
-
const parsed =
|
|
525
|
+
const parsed = parsePercentRelativeExpr(token);
|
|
479
526
|
if (parsed) found.push(parsed);
|
|
480
527
|
}
|
|
481
528
|
return found;
|
|
482
529
|
}
|
|
483
530
|
function stripPercentRelativeCalcTokens(classStrRaw) {
|
|
484
|
-
return classStrRaw.split(/\s+/).filter((token) => token &&
|
|
531
|
+
return classStrRaw.split(/\s+/).filter((token) => token && parsePercentRelativeExpr(token) === null).join(" ");
|
|
485
532
|
}
|
|
486
533
|
var STATE_MODIFIER_NAMES = /* @__PURE__ */ new Set(["hover", "focus", "disabled"]);
|
|
487
534
|
var STATE_MODIFIER_HINT_RE = /(^|\s|:)(hover|focus|disabled):/;
|
|
@@ -556,7 +603,7 @@ function ReactiveElement({
|
|
|
556
603
|
layoutOverrides = {};
|
|
557
604
|
for (const calc of percentCalcs) {
|
|
558
605
|
const basis = measuredBasis?.[calc.property];
|
|
559
|
-
layoutOverrides[calc.property] = basis === void 0 ? "100%" :
|
|
606
|
+
layoutOverrides[calc.property] = basis === void 0 ? "100%" : calc.resolve(basis);
|
|
560
607
|
}
|
|
561
608
|
const userOnLayout = hostRest.onLayout;
|
|
562
609
|
hostRestWithLayout = {
|
|
@@ -1543,6 +1590,77 @@ function nativeShadowDeclarations(parsed) {
|
|
|
1543
1590
|
];
|
|
1544
1591
|
}
|
|
1545
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
|
+
|
|
1546
1664
|
// src/jsEngine/resolveUtilityNative.ts
|
|
1547
1665
|
function firstFontName(stack) {
|
|
1548
1666
|
const first = stack.split(",")[0] ?? stack;
|
|
@@ -1624,7 +1742,7 @@ function resolveOpacityNative(parsed) {
|
|
|
1624
1742
|
return v === null ? null : [decl("opacity", v)];
|
|
1625
1743
|
}
|
|
1626
1744
|
function resolveUtilityNative(parsed, theme) {
|
|
1627
|
-
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);
|
|
1628
1746
|
}
|
|
1629
1747
|
|
|
1630
1748
|
// src/jsEngine/resolveStyle.ts
|
|
@@ -1695,8 +1813,32 @@ var NUMERIC_LENGTH_PROPS = /* @__PURE__ */ new Set([
|
|
|
1695
1813
|
"border-bottom-right-radius",
|
|
1696
1814
|
"border-bottom-left-radius",
|
|
1697
1815
|
"outline-width",
|
|
1698
|
-
"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"
|
|
1699
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"];
|
|
1700
1842
|
function isPlainPercentage(value) {
|
|
1701
1843
|
if (!value.endsWith("%")) return false;
|
|
1702
1844
|
let digits = value.slice(0, -1);
|
|
@@ -1733,6 +1875,7 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
|
|
|
1733
1875
|
const style = {};
|
|
1734
1876
|
const warnings = [];
|
|
1735
1877
|
let shadowOffset = null;
|
|
1878
|
+
const transformOps = /* @__PURE__ */ new Map();
|
|
1736
1879
|
for (const token of classString.split(/\s+/).filter(Boolean)) {
|
|
1737
1880
|
const parsed = parseClass(token);
|
|
1738
1881
|
const allModifiersHold = parsed.modifiers.every(
|
|
@@ -1752,6 +1895,17 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
|
|
|
1752
1895
|
shadowOffset[d.property === "shadow-offset-x" ? "width" : "height"] = n;
|
|
1753
1896
|
continue;
|
|
1754
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
|
+
}
|
|
1755
1909
|
const { value, warning } = rnStyleValue(d.property, d.value);
|
|
1756
1910
|
if (warning !== null) warnings.push(warning);
|
|
1757
1911
|
if (value !== null) style[kebabToCamel(d.property)] = value;
|
|
@@ -1760,6 +1914,11 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
|
|
|
1760
1914
|
if (shadowOffset !== null) {
|
|
1761
1915
|
style.shadowOffset = { width: shadowOffset.width ?? 0, height: shadowOffset.height ?? 0 };
|
|
1762
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
|
+
}
|
|
1763
1922
|
return { style, warnings };
|
|
1764
1923
|
}
|
|
1765
1924
|
|
package/dist/jsx-runtime.js
CHANGED
|
@@ -206,6 +206,46 @@ function parsePercentRelativeCalc(token) {
|
|
|
206
206
|
function resolvePercentRelativeCalc(basisPx, calc) {
|
|
207
207
|
return basisPx * calc.percentCoefficient / 100 + calc.constantPx;
|
|
208
208
|
}
|
|
209
|
+
var PERCENT_MIN_MAX_CLAMP_TOKEN_RE = /^(w|h)-\[(min|max|clamp)\(([^)]+)\)\]$/;
|
|
210
|
+
function parseOperand(arg) {
|
|
211
|
+
const trimmed = arg.trim();
|
|
212
|
+
const pct = /^(-?[\d.]+)%$/.exec(trimmed);
|
|
213
|
+
if (pct) {
|
|
214
|
+
const percentCoefficient = Number(pct[1]);
|
|
215
|
+
return Number.isFinite(percentCoefficient) ? { percentCoefficient, constantPx: 0 } : null;
|
|
216
|
+
}
|
|
217
|
+
const constantPx = reduceConstantMath(`calc(${trimmed})`);
|
|
218
|
+
return constantPx === null ? null : { percentCoefficient: 0, constantPx };
|
|
219
|
+
}
|
|
220
|
+
function parsePercentRelativeMinMaxClamp(token) {
|
|
221
|
+
const m = PERCENT_MIN_MAX_CLAMP_TOKEN_RE.exec(token);
|
|
222
|
+
if (!m) return null;
|
|
223
|
+
const property = m[1] === "w" ? "width" : "height";
|
|
224
|
+
const kind = m[2];
|
|
225
|
+
const args = splitTopLevelCommas(m[3].replace(/_/g, " "));
|
|
226
|
+
if (kind === "clamp" && args.length !== 3) return null;
|
|
227
|
+
if (kind !== "clamp" && args.length < 1) return null;
|
|
228
|
+
const operands = args.map(parseOperand);
|
|
229
|
+
if (operands.some((o) => o === null)) return null;
|
|
230
|
+
const resolved = operands;
|
|
231
|
+
if (!resolved.some((o) => o.percentCoefficient !== 0)) return null;
|
|
232
|
+
const resolveOperand = (o, basisPx) => basisPx * o.percentCoefficient / 100 + o.constantPx;
|
|
233
|
+
return {
|
|
234
|
+
property,
|
|
235
|
+
resolve(basisPx) {
|
|
236
|
+
const values = resolved.map((o) => resolveOperand(o, basisPx));
|
|
237
|
+
if (kind === "min") return Math.min(...values);
|
|
238
|
+
if (kind === "max") return Math.max(...values);
|
|
239
|
+
const [min, preferred, max] = values;
|
|
240
|
+
return Math.max(min, Math.min(preferred, max));
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function parsePercentRelativeExpr(token) {
|
|
245
|
+
const calc = parsePercentRelativeCalc(token);
|
|
246
|
+
if (calc) return { property: calc.property, resolve: (basisPx) => resolvePercentRelativeCalc(basisPx, calc) };
|
|
247
|
+
return parsePercentRelativeMinMaxClamp(token);
|
|
248
|
+
}
|
|
209
249
|
|
|
210
250
|
// src/jsEngine/parser.ts
|
|
211
251
|
var VALUE_PREFIXES = [
|
|
@@ -336,12 +376,19 @@ var VALUE_PREFIXES = [
|
|
|
336
376
|
"justify-self-",
|
|
337
377
|
"scale-x-",
|
|
338
378
|
"scale-y-",
|
|
379
|
+
"scale-z-",
|
|
339
380
|
"scale-",
|
|
381
|
+
"rotate-x-",
|
|
382
|
+
"rotate-y-",
|
|
383
|
+
"rotate-z-",
|
|
340
384
|
"rotate-",
|
|
341
385
|
"translate-x-",
|
|
342
386
|
"translate-y-",
|
|
387
|
+
"translate-z-",
|
|
343
388
|
"skew-x-",
|
|
344
389
|
"skew-y-",
|
|
390
|
+
"perspective-origin-",
|
|
391
|
+
"perspective-",
|
|
345
392
|
"origin-",
|
|
346
393
|
"blur-",
|
|
347
394
|
"brightness-",
|
|
@@ -472,17 +519,17 @@ function substituteDynamicTokens(classStrRaw) {
|
|
|
472
519
|
return value ?? whole;
|
|
473
520
|
});
|
|
474
521
|
}
|
|
475
|
-
var PERCENT_RELATIVE_CALC_HINT_RE = /[wh]-\[calc\([^)]*%[^)]*\)\]/;
|
|
522
|
+
var PERCENT_RELATIVE_CALC_HINT_RE = /[wh]-\[(calc|min|max|clamp)\([^)]*%[^)]*\)\]/;
|
|
476
523
|
function extractPercentRelativeCalcs(classStrRaw) {
|
|
477
524
|
const found = [];
|
|
478
525
|
for (const token of classStrRaw.split(/\s+/)) {
|
|
479
|
-
const parsed =
|
|
526
|
+
const parsed = parsePercentRelativeExpr(token);
|
|
480
527
|
if (parsed) found.push(parsed);
|
|
481
528
|
}
|
|
482
529
|
return found;
|
|
483
530
|
}
|
|
484
531
|
function stripPercentRelativeCalcTokens(classStrRaw) {
|
|
485
|
-
return classStrRaw.split(/\s+/).filter((token) => token &&
|
|
532
|
+
return classStrRaw.split(/\s+/).filter((token) => token && parsePercentRelativeExpr(token) === null).join(" ");
|
|
486
533
|
}
|
|
487
534
|
var STATE_MODIFIER_NAMES = /* @__PURE__ */ new Set(["hover", "focus", "disabled"]);
|
|
488
535
|
var STATE_MODIFIER_HINT_RE = /(^|\s|:)(hover|focus|disabled):/;
|
|
@@ -557,7 +604,7 @@ function ReactiveElement({
|
|
|
557
604
|
layoutOverrides = {};
|
|
558
605
|
for (const calc of percentCalcs) {
|
|
559
606
|
const basis = measuredBasis?.[calc.property];
|
|
560
|
-
layoutOverrides[calc.property] = basis === void 0 ? "100%" :
|
|
607
|
+
layoutOverrides[calc.property] = basis === void 0 ? "100%" : calc.resolve(basis);
|
|
561
608
|
}
|
|
562
609
|
const userOnLayout = hostRest.onLayout;
|
|
563
610
|
hostRestWithLayout = {
|
|
@@ -1534,6 +1581,77 @@ function nativeShadowDeclarations(parsed) {
|
|
|
1534
1581
|
];
|
|
1535
1582
|
}
|
|
1536
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
|
+
|
|
1537
1655
|
// src/jsEngine/resolveUtilityNative.ts
|
|
1538
1656
|
function firstFontName(stack) {
|
|
1539
1657
|
const first = stack.split(",")[0] ?? stack;
|
|
@@ -1615,7 +1733,7 @@ function resolveOpacityNative(parsed) {
|
|
|
1615
1733
|
return v === null ? null : [decl("opacity", v)];
|
|
1616
1734
|
}
|
|
1617
1735
|
function resolveUtilityNative(parsed, theme) {
|
|
1618
|
-
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);
|
|
1619
1737
|
}
|
|
1620
1738
|
|
|
1621
1739
|
// src/jsEngine/resolveStyle.ts
|
|
@@ -1686,8 +1804,32 @@ var NUMERIC_LENGTH_PROPS = /* @__PURE__ */ new Set([
|
|
|
1686
1804
|
"border-bottom-right-radius",
|
|
1687
1805
|
"border-bottom-left-radius",
|
|
1688
1806
|
"outline-width",
|
|
1689
|
-
"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"
|
|
1690
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"];
|
|
1691
1833
|
function isPlainPercentage(value) {
|
|
1692
1834
|
if (!value.endsWith("%")) return false;
|
|
1693
1835
|
let digits = value.slice(0, -1);
|
|
@@ -1724,6 +1866,7 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
|
|
|
1724
1866
|
const style = {};
|
|
1725
1867
|
const warnings = [];
|
|
1726
1868
|
let shadowOffset = null;
|
|
1869
|
+
const transformOps = /* @__PURE__ */ new Map();
|
|
1727
1870
|
for (const token of classString.split(/\s+/).filter(Boolean)) {
|
|
1728
1871
|
const parsed = parseClass(token);
|
|
1729
1872
|
const allModifiersHold = parsed.modifiers.every(
|
|
@@ -1743,6 +1886,17 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
|
|
|
1743
1886
|
shadowOffset[d.property === "shadow-offset-x" ? "width" : "height"] = n;
|
|
1744
1887
|
continue;
|
|
1745
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
|
+
}
|
|
1746
1900
|
const { value, warning } = rnStyleValue(d.property, d.value);
|
|
1747
1901
|
if (warning !== null) warnings.push(warning);
|
|
1748
1902
|
if (value !== null) style[kebabToCamel(d.property)] = value;
|
|
@@ -1751,6 +1905,11 @@ function resolveStyleJsWithWarnings(classString, theme, colorScheme, pressed, wi
|
|
|
1751
1905
|
if (shadowOffset !== null) {
|
|
1752
1906
|
style.shadowOffset = { width: shadowOffset.width ?? 0, height: shadowOffset.height ?? 0 };
|
|
1753
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
|
+
}
|
|
1754
1913
|
return { style, warnings };
|
|
1755
1914
|
}
|
|
1756
1915
|
|
|
@@ -80,18 +80,49 @@ interface ThemeState {
|
|
|
80
80
|
*/
|
|
81
81
|
declare function useTheme(): ThemeState;
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* The exact shape `@react-native-async-storage/async-storage`'s default
|
|
85
|
+
* export already has — pass it directly as `persist`. Any other async
|
|
86
|
+
* key-value store (MMKV, SecureStore, a custom backend, ...) works too, as
|
|
87
|
+
* long as it exposes these two methods with this signature. Kept minimal
|
|
88
|
+
* on purpose: `ThemeProvider` never imports a storage library itself (see
|
|
89
|
+
* `darkModeStore.ts`'s own doc comment on why this package forces no real
|
|
90
|
+
* native dependency on consumers who don't want persistence) — deliberately
|
|
91
|
+
* NOT a `boolean`-only option that reaches for AsyncStorage internally,
|
|
92
|
+
* since Metro resolves every `require()`/`import` target at BUNDLE time
|
|
93
|
+
* regardless of try/catch, so a library-internal "if installed, use it"
|
|
94
|
+
* check can't actually degrade gracefully when the package is absent — an
|
|
95
|
+
* injected adapter sidesteps that entirely.
|
|
96
|
+
*/
|
|
97
|
+
interface ThemeStorageAdapter {
|
|
98
|
+
getItem(key: string): Promise<string | null>;
|
|
99
|
+
setItem(key: string, value: string): Promise<void>;
|
|
100
|
+
}
|
|
83
101
|
interface ThemeProviderProps {
|
|
84
102
|
children: ReactNode;
|
|
85
103
|
/**
|
|
86
104
|
* Applied once, on this provider's first mount, but ONLY if nothing has
|
|
87
105
|
* made an explicit mode choice yet (see
|
|
88
106
|
* `darkModeStore.ts::seedDefaultMode`) — an explicit prior choice always
|
|
89
|
-
* wins over a component's own default.
|
|
90
|
-
*
|
|
91
|
-
* darkModeStore.ts's own doc comment on why this package doesn't persist
|
|
92
|
-
* the mode across app launches.
|
|
107
|
+
* wins over a component's own default. If `persist` is also set and a
|
|
108
|
+
* persisted mode is found, the persisted mode wins over this fallback.
|
|
93
109
|
*/
|
|
94
110
|
defaultMode?: ThemeMode;
|
|
111
|
+
/**
|
|
112
|
+
* Opt-in persistence across app launches — `false` by default (this
|
|
113
|
+
* package never persists on its own; see `darkModeStore.ts`'s doc
|
|
114
|
+
* comment). Pass a storage adapter to enable it:
|
|
115
|
+
* ```tsx
|
|
116
|
+
* import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
117
|
+
* <ThemeProvider persist={AsyncStorage}>...
|
|
118
|
+
* ```
|
|
119
|
+
* The persisted value (if any) is read once on mount and applied via the
|
|
120
|
+
* same "don't clobber a real choice already in flight" guard
|
|
121
|
+
* `seedDefaultMode`/`defaultMode` uses; every later explicit mode change
|
|
122
|
+
* is written back automatically for as long as this provider stays
|
|
123
|
+
* mounted.
|
|
124
|
+
*/
|
|
125
|
+
persist?: ThemeStorageAdapter | false;
|
|
95
126
|
}
|
|
96
127
|
/**
|
|
97
128
|
* Optional — `useTheme()` reads `darkModeStore.ts`'s global store directly
|
|
@@ -107,7 +138,7 @@ interface ThemeProviderProps {
|
|
|
107
138
|
* state — mounting more than one is very likely a mistake, so this warns
|
|
108
139
|
* once in dev.
|
|
109
140
|
*/
|
|
110
|
-
declare function ThemeProvider({ children, defaultMode }: ThemeProviderProps): react.JSX.Element;
|
|
141
|
+
declare function ThemeProvider({ children, defaultMode, persist }: ThemeProviderProps): react.JSX.Element;
|
|
111
142
|
|
|
112
143
|
interface ColorScale {
|
|
113
144
|
/** `colors.blue[6]` -> raw value */
|
|
@@ -157,4 +188,4 @@ declare function useColors(): ColorsAPI;
|
|
|
157
188
|
*/
|
|
158
189
|
declare function useDynamicToken(name: string): string | undefined;
|
|
159
190
|
|
|
160
|
-
export { type ColorScale as C, type KbachConfig as K, ThemeProvider as T, type ColorsAPI as a, type ThemeProviderProps as b, type ThemeState as c,
|
|
191
|
+
export { type ColorScale as C, type KbachConfig as K, ThemeProvider as T, type ColorsAPI as a, type ThemeProviderProps as b, type ThemeState as c, type ThemeStorageAdapter as d, applyKbachConfig as e, useDynamicToken as f, useTheme as g, resolveKbachConfig as r, useColors as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kbach/react-native",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
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",
|