@kbach/react-native 1.0.0-beta.21 → 1.0.0-beta.22
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 +34 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +27 -2
- 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 +44 -4
- package/dist/jsx-runtime.js +44 -4
- package/dist/{useDynamicToken-Dr15c88t.d.ts → useDynamicToken-DAHQob1u.d.ts} +37 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -173,8 +173,26 @@ optional, for seeding a startup default. Outside React, use
|
|
|
173
173
|
`getGlobalDarkMode()`/`toggleGlobalDarkMode()` instead.
|
|
174
174
|
|
|
175
175
|
Follows the OS setting live until overridden, and isn't persisted across
|
|
176
|
-
app launches (no
|
|
177
|
-
|
|
176
|
+
app launches by default (no forced storage dependency). To persist, pass
|
|
177
|
+
any async key-value store exposing `getItem`/`setItem` as `<ThemeProvider
|
|
178
|
+
persist>` — `@react-native-async-storage/async-storage`'s default export
|
|
179
|
+
already matches this shape exactly:
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
183
|
+
import { ThemeProvider } from '@kbach/react-native';
|
|
184
|
+
|
|
185
|
+
<ThemeProvider persist={AsyncStorage}>
|
|
186
|
+
<App />
|
|
187
|
+
</ThemeProvider>;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The persisted mode (if any) is read once on mount and wins over
|
|
191
|
+
`defaultMode`; every later explicit choice is written back automatically.
|
|
192
|
+
`false` by default — this package still never imports a storage library
|
|
193
|
+
itself, so nothing changes for apps that don't opt in. Prefer wiring your
|
|
194
|
+
own storage manually instead? `setGlobalThemeMode()` before your first
|
|
195
|
+
render still works exactly as before.
|
|
178
196
|
|
|
179
197
|
## Reading colors as values
|
|
180
198
|
|
|
@@ -222,12 +240,15 @@ way — no such unit exists on native at all. It already works fine on web
|
|
|
222
240
|
(real CSS); on native the declaration is dropped and a `console.warn`
|
|
223
241
|
explains why (dev builds only).
|
|
224
242
|
|
|
225
|
-
**Percentage-relative width/height** (`calc(100%-3rem)`, `calc(50%+16px)
|
|
226
|
-
|
|
227
|
-
|
|
243
|
+
**Percentage-relative width/height** (`calc(100%-3rem)`, `calc(50%+16px)`,
|
|
244
|
+
`min(50%,20rem)`, `max(1rem,5%)`, `clamp(16rem,50%,32rem)`) IS supported,
|
|
245
|
+
differently: since native has no way to know "100% of what" ahead of time,
|
|
246
|
+
this measures the element's own real layout instead —
|
|
228
247
|
|
|
229
248
|
```tsx
|
|
230
249
|
<View className="w-[calc(100%_-_3rem)]" />
|
|
250
|
+
<View className="w-[min(50%,20rem)]" /> // half the parent, capped at a fixed size
|
|
251
|
+
<View className="w-[clamp(16rem,50%,32rem)]" />
|
|
231
252
|
```
|
|
232
253
|
|
|
233
254
|
Renders once at a plain `100%` (a real RN percentage, resolved correctly by
|
|
@@ -236,9 +257,14 @@ event, then immediately corrects to the actual computed number once
|
|
|
236
257
|
measured — a genuine one-frame measure-then-snap, not instant math like the
|
|
237
258
|
constant-only case above, and it keeps re-measuring on every layout (screen
|
|
238
259
|
rotation, parent resize, ...) rather than freezing after the first value.
|
|
239
|
-
Scoped
|
|
240
|
-
|
|
241
|
-
|
|
260
|
+
Scoped narrowly, same "reduce fully or bail" philosophy as the constant-only
|
|
261
|
+
case: `calc()` takes exactly one percentage term plus one constant term;
|
|
262
|
+
`min()`/`max()` take two or more comma-separated arguments, each EITHER a
|
|
263
|
+
plain percentage or a constant px/rem expression (not both mixed in one
|
|
264
|
+
argument — `min(50%-1rem,20rem)` is out of scope); `clamp()` takes exactly
|
|
265
|
+
three. `width`/`height` only in every case — other properties, or anything
|
|
266
|
+
that doesn't reduce fully, falls through to the same drop-and-warn behavior
|
|
267
|
+
as before.
|
|
242
268
|
|
|
243
269
|
**Arbitrary values can't contain literal spaces** — this is a pre-existing
|
|
244
270
|
rule (real Tailwind has the same one), not specific to `calc()`: a space
|
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';
|
package/dist/index.js
CHANGED
|
@@ -1680,9 +1680,13 @@ function useTheme() {
|
|
|
1680
1680
|
var import_react2 = require("react");
|
|
1681
1681
|
var import_darkModeStore3 = require("./darkModeStore");
|
|
1682
1682
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1683
|
+
var PERSIST_STORAGE_KEY = "kbach-theme";
|
|
1684
|
+
function isThemeMode(value) {
|
|
1685
|
+
return value === "light" || value === "dark" || value === "system";
|
|
1686
|
+
}
|
|
1683
1687
|
var mountedProviderCount = 0;
|
|
1684
1688
|
var warnedMultipleProviders = false;
|
|
1685
|
-
function ThemeProvider({ children, defaultMode = "system" }) {
|
|
1689
|
+
function ThemeProvider({ children, defaultMode = "system", persist = false }) {
|
|
1686
1690
|
(0, import_react2.useEffect)(() => {
|
|
1687
1691
|
mountedProviderCount++;
|
|
1688
1692
|
if (mountedProviderCount > 1 && !warnedMultipleProviders) {
|
|
@@ -1699,8 +1703,29 @@ function ThemeProvider({ children, defaultMode = "system" }) {
|
|
|
1699
1703
|
(0, import_react2.useEffect)(() => {
|
|
1700
1704
|
if (seeded.current) return;
|
|
1701
1705
|
seeded.current = true;
|
|
1702
|
-
|
|
1706
|
+
if (!persist) {
|
|
1707
|
+
(0, import_darkModeStore3.seedDefaultMode)(defaultMode);
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
let cancelled = false;
|
|
1711
|
+
persist.getItem(PERSIST_STORAGE_KEY).then((stored) => {
|
|
1712
|
+
if (cancelled) return;
|
|
1713
|
+
if (isThemeMode(stored)) (0, import_darkModeStore3.seedDefaultMode)(stored);
|
|
1714
|
+
else (0, import_darkModeStore3.seedDefaultMode)(defaultMode);
|
|
1715
|
+
}).catch(() => {
|
|
1716
|
+
if (!cancelled) (0, import_darkModeStore3.seedDefaultMode)(defaultMode);
|
|
1717
|
+
});
|
|
1718
|
+
return () => {
|
|
1719
|
+
cancelled = true;
|
|
1720
|
+
};
|
|
1703
1721
|
}, []);
|
|
1722
|
+
(0, import_react2.useEffect)(() => {
|
|
1723
|
+
if (!persist) return;
|
|
1724
|
+
return (0, import_darkModeStore3.subscribeGlobalDarkMode)(() => {
|
|
1725
|
+
persist.setItem(PERSIST_STORAGE_KEY, (0, import_darkModeStore3.getGlobalThemeMode)()).catch(() => {
|
|
1726
|
+
});
|
|
1727
|
+
});
|
|
1728
|
+
}, [persist]);
|
|
1704
1729
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children });
|
|
1705
1730
|
}
|
|
1706
1731
|
|
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 = [
|
|
@@ -471,17 +511,17 @@ function substituteDynamicTokens(classStrRaw) {
|
|
|
471
511
|
return value ?? whole;
|
|
472
512
|
});
|
|
473
513
|
}
|
|
474
|
-
var PERCENT_RELATIVE_CALC_HINT_RE = /[wh]-\[calc\([^)]*%[^)]*\)\]/;
|
|
514
|
+
var PERCENT_RELATIVE_CALC_HINT_RE = /[wh]-\[(calc|min|max|clamp)\([^)]*%[^)]*\)\]/;
|
|
475
515
|
function extractPercentRelativeCalcs(classStrRaw) {
|
|
476
516
|
const found = [];
|
|
477
517
|
for (const token of classStrRaw.split(/\s+/)) {
|
|
478
|
-
const parsed =
|
|
518
|
+
const parsed = parsePercentRelativeExpr(token);
|
|
479
519
|
if (parsed) found.push(parsed);
|
|
480
520
|
}
|
|
481
521
|
return found;
|
|
482
522
|
}
|
|
483
523
|
function stripPercentRelativeCalcTokens(classStrRaw) {
|
|
484
|
-
return classStrRaw.split(/\s+/).filter((token) => token &&
|
|
524
|
+
return classStrRaw.split(/\s+/).filter((token) => token && parsePercentRelativeExpr(token) === null).join(" ");
|
|
485
525
|
}
|
|
486
526
|
var STATE_MODIFIER_NAMES = /* @__PURE__ */ new Set(["hover", "focus", "disabled"]);
|
|
487
527
|
var STATE_MODIFIER_HINT_RE = /(^|\s|:)(hover|focus|disabled):/;
|
|
@@ -556,7 +596,7 @@ function ReactiveElement({
|
|
|
556
596
|
layoutOverrides = {};
|
|
557
597
|
for (const calc of percentCalcs) {
|
|
558
598
|
const basis = measuredBasis?.[calc.property];
|
|
559
|
-
layoutOverrides[calc.property] = basis === void 0 ? "100%" :
|
|
599
|
+
layoutOverrides[calc.property] = basis === void 0 ? "100%" : calc.resolve(basis);
|
|
560
600
|
}
|
|
561
601
|
const userOnLayout = hostRest.onLayout;
|
|
562
602
|
hostRestWithLayout = {
|
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 = [
|
|
@@ -472,17 +512,17 @@ function substituteDynamicTokens(classStrRaw) {
|
|
|
472
512
|
return value ?? whole;
|
|
473
513
|
});
|
|
474
514
|
}
|
|
475
|
-
var PERCENT_RELATIVE_CALC_HINT_RE = /[wh]-\[calc\([^)]*%[^)]*\)\]/;
|
|
515
|
+
var PERCENT_RELATIVE_CALC_HINT_RE = /[wh]-\[(calc|min|max|clamp)\([^)]*%[^)]*\)\]/;
|
|
476
516
|
function extractPercentRelativeCalcs(classStrRaw) {
|
|
477
517
|
const found = [];
|
|
478
518
|
for (const token of classStrRaw.split(/\s+/)) {
|
|
479
|
-
const parsed =
|
|
519
|
+
const parsed = parsePercentRelativeExpr(token);
|
|
480
520
|
if (parsed) found.push(parsed);
|
|
481
521
|
}
|
|
482
522
|
return found;
|
|
483
523
|
}
|
|
484
524
|
function stripPercentRelativeCalcTokens(classStrRaw) {
|
|
485
|
-
return classStrRaw.split(/\s+/).filter((token) => token &&
|
|
525
|
+
return classStrRaw.split(/\s+/).filter((token) => token && parsePercentRelativeExpr(token) === null).join(" ");
|
|
486
526
|
}
|
|
487
527
|
var STATE_MODIFIER_NAMES = /* @__PURE__ */ new Set(["hover", "focus", "disabled"]);
|
|
488
528
|
var STATE_MODIFIER_HINT_RE = /(^|\s|:)(hover|focus|disabled):/;
|
|
@@ -557,7 +597,7 @@ function ReactiveElement({
|
|
|
557
597
|
layoutOverrides = {};
|
|
558
598
|
for (const calc of percentCalcs) {
|
|
559
599
|
const basis = measuredBasis?.[calc.property];
|
|
560
|
-
layoutOverrides[calc.property] = basis === void 0 ? "100%" :
|
|
600
|
+
layoutOverrides[calc.property] = basis === void 0 ? "100%" : calc.resolve(basis);
|
|
561
601
|
}
|
|
562
602
|
const userOnLayout = hostRest.onLayout;
|
|
563
603
|
hostRestWithLayout = {
|
|
@@ -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.22",
|
|
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",
|