@kbach/react-native 1.0.0-beta.3 → 1.0.0-beta.31
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 +303 -95
- 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/chunk-FLPPVP5G.mjs +40 -0
- package/dist/chunk-XPVSILI4.mjs +112 -0
- package/dist/chunk-ZYE4VIZE.mjs +564 -0
- package/dist/clsx-BCXALPGh.d.ts +239 -0
- package/dist/darkModeStore.d.ts +73 -0
- package/dist/darkModeStore.js +118 -0
- package/dist/dynamicTokens.d.ts +36 -0
- package/dist/dynamicTokens.js +75 -0
- package/dist/index.d.ts +11 -156
- package/dist/index.js +1994 -393
- package/dist/index.web.d.ts +18 -0
- package/dist/index.web.js +505 -0
- package/dist/index.web.mjs +266 -0
- package/dist/jsx-dev-runtime.d.ts +3 -12
- package/dist/jsx-dev-runtime.js +2143 -350
- package/dist/jsx-dev-runtime.web.d.ts +10 -0
- package/dist/jsx-dev-runtime.web.js +282 -0
- package/dist/jsx-dev-runtime.web.mjs +29 -0
- package/dist/jsx-runtime.d.ts +3 -26
- package/dist/jsx-runtime.js +2134 -342
- package/dist/jsx-runtime.web.d.ts +7 -0
- package/dist/jsx-runtime.web.js +264 -0
- package/dist/{jsx-runtime.mjs → jsx-runtime.web.mjs} +2 -2
- package/dist/theme.d.ts +54 -0
- package/dist/{chunk-7KUNKZBM.mjs → theme.js} +87 -72
- package/package.json +56 -11
- package/src/babel-plugin.js +74 -2
- package/dist/chunk-UP5ZN7CN.mjs +0 -44
- package/dist/index.d.mts +0 -161
- package/dist/index.mjs +0 -80
- package/dist/jsx-dev-runtime.d.mts +0 -19
- package/dist/jsx-dev-runtime.mjs +0 -20
- package/dist/jsx-runtime.d.mts +0 -30
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A plain hex value (static — same in both modes), or a `{ light, dark }`
|
|
3
|
+
* pair a caller sets manually in their own theme. Fully opt-in per color:
|
|
4
|
+
* nothing here picks a "dark variant" automatically. Only `useColors()`
|
|
5
|
+
* (a JS-value read, not a `className`) understands this shape — utility
|
|
6
|
+
* CLASS resolution (`bg-blue-6`) on native/Expo Go stays plain-string-only,
|
|
7
|
+
* same as it always was (dark mode for a CLASS is the separate `dark:`
|
|
8
|
+
* modifier, gated live at resolve time — see `resolve_style.rs`); a
|
|
9
|
+
* mode-aware entry referenced directly by a class name just doesn't
|
|
10
|
+
* resolve there, mirroring `resolvers/color.rs`'s own `lookup_hex`
|
|
11
|
+
* (`ColorValue::Plain` only, `ModeAware` -> `None`) exactly, same as it
|
|
12
|
+
* would on the web engine too.
|
|
13
|
+
*/
|
|
14
|
+
type ColorEntry = string | {
|
|
15
|
+
light: string;
|
|
16
|
+
dark: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Real Tailwind's `theme.container` config, mirroring
|
|
20
|
+
* `packages/core-engine/src/theme.rs`'s `ContainerConfig` exactly. `center`/
|
|
21
|
+
* `padding` DO reach native now (unlike the max-width breakpoint ladder,
|
|
22
|
+
* which is a `css.rs`-only concept with nothing to generate on native at
|
|
23
|
+
* all) — `resolvers::layout::resolve`'s `"container"` arm is shared by both
|
|
24
|
+
* dispatchers, and `margin`/`padding` are ordinary RN style properties.
|
|
25
|
+
*/
|
|
26
|
+
interface ContainerConfig {
|
|
27
|
+
center?: boolean;
|
|
28
|
+
padding?: string;
|
|
29
|
+
}
|
|
30
|
+
interface ThemeConfig {
|
|
31
|
+
colors: Record<string, ColorEntry>;
|
|
32
|
+
spacing: Record<string, number>;
|
|
33
|
+
screens: Record<string, number>;
|
|
34
|
+
/**
|
|
35
|
+
* e.g. "sans" -> "Inter, sans-serif". Optional, matching the Rust
|
|
36
|
+
* engine's own `#[serde(default)]`. Write it as a real web-shaped
|
|
37
|
+
* fallback stack even on this native-first package — native's own
|
|
38
|
+
* resolver strips it down to a single bare name at resolve time (RN's
|
|
39
|
+
* `fontFamily` prop can't take a fallback list — see
|
|
40
|
+
* `resolvers/mod.rs`'s `first_font_name`), so the SAME config still
|
|
41
|
+
* works unmodified on Expo Web, which needs the full stack.
|
|
42
|
+
*/
|
|
43
|
+
fontFamily?: Record<string, string>;
|
|
44
|
+
darkMode: 'attribute' | 'class' | 'media';
|
|
45
|
+
/** Optional, matching `ContainerConfig`'s own `#[serde(default)]` on the Rust side. */
|
|
46
|
+
container?: ContainerConfig;
|
|
47
|
+
}
|
|
48
|
+
declare const defaultTheme: ThemeConfig;
|
|
49
|
+
declare function setTheme(theme: ThemeConfig): void;
|
|
50
|
+
declare function getTheme(): ThemeConfig;
|
|
51
|
+
/** Memoized JSON serialization of the active theme — passed across the JNI boundary. */
|
|
52
|
+
declare function getThemeJson(): string;
|
|
53
|
+
|
|
54
|
+
export { type ColorEntry, type ContainerConfig, type ThemeConfig, defaultTheme, getTheme, getThemeJson, setTheme };
|
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/theme.ts
|
|
21
|
+
var theme_exports = {};
|
|
22
|
+
__export(theme_exports, {
|
|
23
|
+
defaultTheme: () => defaultTheme,
|
|
24
|
+
getTheme: () => getTheme,
|
|
25
|
+
getThemeJson: () => getThemeJson,
|
|
26
|
+
setTheme: () => setTheme
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(theme_exports);
|
|
29
|
+
|
|
1
30
|
// src/generatedPalette.ts
|
|
2
31
|
var PALETTE = {
|
|
3
32
|
"amber-1": "#fffbeb",
|
|
@@ -278,16 +307,51 @@ var PALETTE = {
|
|
|
278
307
|
// src/theme.ts
|
|
279
308
|
var defaultTheme = {
|
|
280
309
|
colors: PALETTE,
|
|
310
|
+
// Real Tailwind's default spacing scale in full — every step is exactly
|
|
311
|
+
// `n * 4px`, including the fractional `.5` steps (`1.5` -> 6px, `2.5` ->
|
|
312
|
+
// 10px, `3.5` -> 14px) and the "px" literal (a bare 1px). Only the
|
|
313
|
+
// "usual stops", though — `jsEngine/shared.ts`'s `spacingPx` (and the
|
|
314
|
+
// native Rust dispatcher's own `spacing_px`) falls back to Tailwind v4's
|
|
315
|
+
// live `n * 4px` formula for any bare numeric step not listed here
|
|
316
|
+
// (`p-0.25`, `w-13`, ...), so this table names the common values and
|
|
317
|
+
// gives a theme override something to target, not the full resolvable
|
|
318
|
+
// set. Mirrors @kbach/react's theme.ts exactly.
|
|
281
319
|
spacing: {
|
|
282
|
-
// "0" is always a hardcoded zero in real Tailwind's own scale too,
|
|
283
|
-
// regardless of the base spacing unit — needed for `p-0`/`m-0`/etc.
|
|
284
|
-
// Mirrors the identical fix in @kbach/react's theme.ts.
|
|
285
320
|
"0": 0,
|
|
321
|
+
"px": 1,
|
|
322
|
+
"0.5": 2,
|
|
286
323
|
"1": 4,
|
|
324
|
+
"1.5": 6,
|
|
287
325
|
"2": 8,
|
|
326
|
+
"2.5": 10,
|
|
327
|
+
"3": 12,
|
|
328
|
+
"3.5": 14,
|
|
288
329
|
"4": 16,
|
|
330
|
+
"5": 20,
|
|
289
331
|
"6": 24,
|
|
290
|
-
"
|
|
332
|
+
"7": 28,
|
|
333
|
+
"8": 32,
|
|
334
|
+
"9": 36,
|
|
335
|
+
"10": 40,
|
|
336
|
+
"11": 44,
|
|
337
|
+
"12": 48,
|
|
338
|
+
"14": 56,
|
|
339
|
+
"16": 64,
|
|
340
|
+
"20": 80,
|
|
341
|
+
"24": 96,
|
|
342
|
+
"28": 112,
|
|
343
|
+
"32": 128,
|
|
344
|
+
"36": 144,
|
|
345
|
+
"40": 160,
|
|
346
|
+
"44": 176,
|
|
347
|
+
"48": 192,
|
|
348
|
+
"52": 208,
|
|
349
|
+
"56": 224,
|
|
350
|
+
"60": 240,
|
|
351
|
+
"64": 256,
|
|
352
|
+
"72": 288,
|
|
353
|
+
"80": 320,
|
|
354
|
+
"96": 384
|
|
291
355
|
},
|
|
292
356
|
screens: {
|
|
293
357
|
sm: 640,
|
|
@@ -296,7 +360,20 @@ var defaultTheme = {
|
|
|
296
360
|
xl: 1280,
|
|
297
361
|
"2xl": 1536
|
|
298
362
|
},
|
|
299
|
-
|
|
363
|
+
// Byte-for-byte the same three stacks
|
|
364
|
+
// packages/core-engine/src/resolvers/typography.rs's `font_family_value`
|
|
365
|
+
// falls back to. On native these three still effectively no-op (see
|
|
366
|
+
// `first_font_name`'s own doc comment for why a generic CSS keyword
|
|
367
|
+
// isn't a real loadable font) — only a caller-configured custom name
|
|
368
|
+
// changes native rendering; Expo Web uses the full stack as-is.
|
|
369
|
+
fontFamily: {
|
|
370
|
+
sans: 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
|
|
371
|
+
serif: 'ui-serif, Georgia, Cambria, "Times New Roman", Times, serif',
|
|
372
|
+
mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
|
|
373
|
+
},
|
|
374
|
+
darkMode: "attribute",
|
|
375
|
+
// Empty by default, matching real Tailwind's own defaults.
|
|
376
|
+
container: {}
|
|
300
377
|
};
|
|
301
378
|
var activeTheme = defaultTheme;
|
|
302
379
|
var activeThemeJson = JSON.stringify(defaultTheme);
|
|
@@ -310,72 +387,10 @@ function getTheme() {
|
|
|
310
387
|
function getThemeJson() {
|
|
311
388
|
return activeThemeJson;
|
|
312
389
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
import { Appearance } from "react-native";
|
|
316
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
317
|
-
function systemPrefersDark() {
|
|
318
|
-
return Appearance.getColorScheme() === "dark";
|
|
319
|
-
}
|
|
320
|
-
function resolveIsDark(m) {
|
|
321
|
-
return m === "system" ? systemPrefersDark() : m === "dark";
|
|
322
|
-
}
|
|
323
|
-
var mode = "system";
|
|
324
|
-
var isDark = resolveIsDark(mode);
|
|
325
|
-
var hasExplicitChoice = false;
|
|
326
|
-
var subscription = Appearance.addChangeListener(({ colorScheme }) => {
|
|
327
|
-
if (mode !== "system") return;
|
|
328
|
-
isDark = colorScheme === "dark";
|
|
329
|
-
notify();
|
|
330
|
-
});
|
|
331
|
-
function notify() {
|
|
332
|
-
for (const listener of listeners) listener();
|
|
333
|
-
}
|
|
334
|
-
function getGlobalDarkMode() {
|
|
335
|
-
return isDark;
|
|
336
|
-
}
|
|
337
|
-
function getGlobalThemeMode() {
|
|
338
|
-
return mode;
|
|
339
|
-
}
|
|
340
|
-
function setGlobalThemeMode(nextMode) {
|
|
341
|
-
mode = nextMode;
|
|
342
|
-
isDark = resolveIsDark(nextMode);
|
|
343
|
-
hasExplicitChoice = true;
|
|
344
|
-
notify();
|
|
345
|
-
}
|
|
346
|
-
function toggleGlobalDarkMode() {
|
|
347
|
-
setGlobalThemeMode(isDark ? "light" : "dark");
|
|
348
|
-
}
|
|
349
|
-
function subscribeGlobalDarkMode(listener) {
|
|
350
|
-
listeners.add(listener);
|
|
351
|
-
return () => listeners.delete(listener);
|
|
352
|
-
}
|
|
353
|
-
function seedDefaultMode(fallback) {
|
|
354
|
-
if (hasExplicitChoice || mode === fallback) return;
|
|
355
|
-
mode = fallback;
|
|
356
|
-
isDark = resolveIsDark(fallback);
|
|
357
|
-
notify();
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// src/nativeBridge.ts
|
|
361
|
-
import { Dimensions, TurboModuleRegistry } from "react-native";
|
|
362
|
-
function resolveStyle(classString, pressed = false) {
|
|
363
|
-
const KbachModule = TurboModuleRegistry.getEnforcing("KbachModule");
|
|
364
|
-
const colorScheme = getGlobalDarkMode() ? "dark" : "light";
|
|
365
|
-
const width = Dimensions.get("window").width;
|
|
366
|
-
const json = KbachModule.resolveStyle(classString, getThemeJson(), colorScheme, pressed, width);
|
|
367
|
-
return JSON.parse(json);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export {
|
|
390
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
391
|
+
0 && (module.exports = {
|
|
371
392
|
defaultTheme,
|
|
372
|
-
setTheme,
|
|
373
393
|
getTheme,
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
toggleGlobalDarkMode,
|
|
378
|
-
subscribeGlobalDarkMode,
|
|
379
|
-
seedDefaultMode,
|
|
380
|
-
resolveStyle
|
|
381
|
-
};
|
|
394
|
+
getThemeJson,
|
|
395
|
+
setTheme
|
|
396
|
+
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kbach/react-native",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
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. Ships its own native Android module
|
|
5
|
-
"keywords": [
|
|
3
|
+
"version": "1.0.0-beta.31",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"css",
|
|
7
|
+
"react-native",
|
|
8
|
+
"expo",
|
|
9
|
+
"tailwind",
|
|
10
|
+
"utility-first",
|
|
11
|
+
"styling",
|
|
12
|
+
"android",
|
|
13
|
+
"web",
|
|
14
|
+
"wasm"
|
|
15
|
+
],
|
|
6
16
|
"license": "MIT",
|
|
7
17
|
"author": "semsakadanupol",
|
|
8
18
|
"repository": {
|
|
@@ -17,42 +27,77 @@
|
|
|
17
27
|
"publishConfig": {
|
|
18
28
|
"access": "public"
|
|
19
29
|
},
|
|
20
|
-
"files": [
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src/babel-plugin.js",
|
|
33
|
+
"android/build.gradle",
|
|
34
|
+
"android/src",
|
|
35
|
+
"specs",
|
|
36
|
+
"react-native.config.js"
|
|
37
|
+
],
|
|
21
38
|
"source": "./src/index.ts",
|
|
22
39
|
"main": "./dist/index.js",
|
|
23
|
-
"module": "./dist/index.mjs",
|
|
24
40
|
"types": "./dist/index.d.ts",
|
|
25
41
|
"exports": {
|
|
26
42
|
".": {
|
|
27
43
|
"types": "./dist/index.d.ts",
|
|
28
44
|
"source": "./src/index.ts",
|
|
29
|
-
"
|
|
45
|
+
"browser": {
|
|
46
|
+
"import": "./dist/index.web.mjs",
|
|
47
|
+
"require": "./dist/index.web.js"
|
|
48
|
+
},
|
|
49
|
+
"node": {
|
|
50
|
+
"import": "./dist/index.web.mjs",
|
|
51
|
+
"require": "./dist/index.web.js"
|
|
52
|
+
},
|
|
53
|
+
"react-native": "./dist/index.js",
|
|
30
54
|
"require": "./dist/index.js"
|
|
31
55
|
},
|
|
32
56
|
"./jsx-runtime": {
|
|
33
57
|
"types": "./dist/jsx-runtime.d.ts",
|
|
34
58
|
"source": "./src/jsx-runtime.tsx",
|
|
35
|
-
"
|
|
59
|
+
"browser": {
|
|
60
|
+
"import": "./dist/jsx-runtime.web.mjs",
|
|
61
|
+
"require": "./dist/jsx-runtime.web.js"
|
|
62
|
+
},
|
|
63
|
+
"node": {
|
|
64
|
+
"import": "./dist/jsx-runtime.web.mjs",
|
|
65
|
+
"require": "./dist/jsx-runtime.web.js"
|
|
66
|
+
},
|
|
67
|
+
"react-native": "./dist/jsx-runtime.js",
|
|
36
68
|
"require": "./dist/jsx-runtime.js"
|
|
37
69
|
},
|
|
38
70
|
"./jsx-dev-runtime": {
|
|
39
71
|
"types": "./dist/jsx-dev-runtime.d.ts",
|
|
40
72
|
"source": "./src/jsx-dev-runtime.tsx",
|
|
41
|
-
"
|
|
73
|
+
"browser": {
|
|
74
|
+
"import": "./dist/jsx-dev-runtime.web.mjs",
|
|
75
|
+
"require": "./dist/jsx-dev-runtime.web.js"
|
|
76
|
+
},
|
|
77
|
+
"node": {
|
|
78
|
+
"import": "./dist/jsx-dev-runtime.web.mjs",
|
|
79
|
+
"require": "./dist/jsx-dev-runtime.web.js"
|
|
80
|
+
},
|
|
81
|
+
"react-native": "./dist/jsx-dev-runtime.js",
|
|
42
82
|
"require": "./dist/jsx-dev-runtime.js"
|
|
43
83
|
},
|
|
44
84
|
"./babel-plugin": "./src/babel-plugin.js"
|
|
45
85
|
},
|
|
46
86
|
"scripts": {
|
|
47
|
-
"build": "tsup",
|
|
48
|
-
"dev": "tsup --watch",
|
|
87
|
+
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsup",
|
|
88
|
+
"dev": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsup --watch",
|
|
49
89
|
"test": "vitest run --passWithNoTests",
|
|
90
|
+
"test:coverage": "vitest run --coverage --passWithNoTests",
|
|
50
91
|
"lint": "tsc --noEmit",
|
|
51
|
-
"generate:colors": "node ../core-engine/scripts/generate-palette.mjs src/generatedPalette.ts"
|
|
92
|
+
"generate:colors": "node ../core-engine/scripts/generate-palette.mjs src/generatedPalette.ts",
|
|
93
|
+
"generate:wasm-base64": "node ../core-engine/scripts/generate-wasm-base64.mjs src/wasmBinary.generated.ts",
|
|
94
|
+
"generate:web-glue": "node ../core-engine/scripts/generate-web-glue.mjs src/wasmGlue.generated.ts"
|
|
52
95
|
},
|
|
53
96
|
"devDependencies": {
|
|
54
97
|
"@types/react": "^19.2.0",
|
|
55
98
|
"@types/react-test-renderer": "^19.1.0",
|
|
99
|
+
"@vitest/coverage-v8": "^1.6.1",
|
|
100
|
+
"jsdom": "^30.0.1",
|
|
56
101
|
"react-native": "^0.87.0",
|
|
57
102
|
"react-test-renderer": "^19.2.0",
|
|
58
103
|
"tsup": "^8.0.0",
|
package/src/babel-plugin.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const template = require('@babel/template').default;
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* Injects a per-file `@jsxImportSource @kbach/react-native` pragma comment
|
|
5
9
|
* so Metro's JSX transform routes className resolution through
|
|
@@ -19,9 +23,67 @@
|
|
|
19
23
|
* Deliberately NOT porting old-kbach's build-time class-resolution half
|
|
20
24
|
* (the __kbachStyles fast path) — resolution happens at the (already
|
|
21
25
|
* synchronous) native-bridge call inside the jsx-runtime instead (see
|
|
22
|
-
* nativeBridge.ts), so this plugin's only job is the pragma injection
|
|
26
|
+
* nativeBridge.ts), so this plugin's only job is the pragma injection —
|
|
27
|
+
* PLUS one more, added later: auto-applying `kbach.config.js` if one
|
|
28
|
+
* exists (see `getConfigStatement`'s own doc comment), so a project never
|
|
29
|
+
* needs its own explicit `applyKbachConfig(kbachConfig)` call at all. That
|
|
30
|
+
* addition deliberately still stops short of old-kbach's own version of
|
|
31
|
+
* this (an mtime-polling live-reload of the config, re-checked on every
|
|
32
|
+
* file) — a Babel-plugin-level change needs a dev-server restart to take
|
|
33
|
+
* effect either way, so a one-time check per (root, process) pair is
|
|
34
|
+
* enough, and much simpler to reason about.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// Keyed by the root directory checked — undefined = not checked yet for
|
|
38
|
+
// that root, null = checked, no kbach.config.js found there. A Babel/Metro
|
|
39
|
+
// worker process transforms many files over its lifetime — this makes the
|
|
40
|
+
// existsSync + template-build work happen ONCE per root, not on every
|
|
41
|
+
// single file `pre()` runs for.
|
|
42
|
+
const _configStatementCache = new Map();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Builds the (uncompiled) AST statement that applies `kbach.config.js`,
|
|
46
|
+
* cached per `root` after the first call — `null` if no such file exists
|
|
47
|
+
* there. `root` defaults to `process.cwd()` (how `metro start`/`expo
|
|
48
|
+
* start` are conventionally invoked) at the plugin's own call site below;
|
|
49
|
+
* pass `{ configRoot: '...' }` as this plugin's Babel options if your
|
|
50
|
+
* `kbach.config.js` lives somewhere else (a monorepo where Metro's actual
|
|
51
|
+
* working directory isn't the app's own root, for instance).
|
|
52
|
+
*
|
|
53
|
+
* The injected statement is a plain `require(...).applyKbachConfig(require(...))`
|
|
54
|
+
* — no try/catch around it. `kbach.config.js`'s own path is only ever
|
|
55
|
+
* baked in here after `existsSync` already confirmed it exists, so the
|
|
56
|
+
* only way this could still throw is a genuine bug in that file (a syntax
|
|
57
|
+
* error, a bad nested require) — letting that surface as a real, visible
|
|
58
|
+
* Metro bundling error is far better than silently swallowing it: a
|
|
59
|
+
* developer's real customization would otherwise go missing with zero
|
|
60
|
+
* indication why, exactly the kind of headache a good default should avoid.
|
|
61
|
+
*
|
|
62
|
+
* `@babel/template` builds this from a plain string with the absolute path
|
|
63
|
+
* JSON-escaped directly into it (correct on Windows too, backslashes and
|
|
64
|
+
* all) — simpler than building the equivalent `@babel/types` node graph by
|
|
65
|
+
* hand for one straightforward statement.
|
|
23
66
|
*/
|
|
24
|
-
|
|
67
|
+
function getConfigStatement(root) {
|
|
68
|
+
if (_configStatementCache.has(root)) return _configStatementCache.get(root);
|
|
69
|
+
|
|
70
|
+
const configPath = path.join(root, 'kbach.config.js');
|
|
71
|
+
if (!fs.existsSync(configPath)) {
|
|
72
|
+
_configStatementCache.set(root, null);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const buildStatement = template(
|
|
77
|
+
`require('@kbach/react-native').applyKbachConfig(require(${JSON.stringify(configPath)}));`,
|
|
78
|
+
);
|
|
79
|
+
const statement = buildStatement();
|
|
80
|
+
_configStatementCache.set(root, statement);
|
|
81
|
+
return statement;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = function kbachReactNativeBabelPlugin(_babel, options) {
|
|
85
|
+
const configRoot = (options && options.configRoot) || process.cwd();
|
|
86
|
+
|
|
25
87
|
return {
|
|
26
88
|
name: 'kbach-react-native',
|
|
27
89
|
pre(file) {
|
|
@@ -33,7 +95,17 @@ module.exports = function kbachReactNativeBabelPlugin() {
|
|
|
33
95
|
if (!alreadySet) {
|
|
34
96
|
comments.unshift({ type: 'CommentLine', value: ' @jsxImportSource @kbach/react-native' });
|
|
35
97
|
}
|
|
98
|
+
|
|
99
|
+
const configStatement = getConfigStatement(configRoot);
|
|
100
|
+
if (configStatement) {
|
|
101
|
+
file.ast.program.body.unshift(configStatement);
|
|
102
|
+
}
|
|
36
103
|
},
|
|
37
104
|
visitor: {},
|
|
38
105
|
};
|
|
39
106
|
};
|
|
107
|
+
|
|
108
|
+
/** Exported for tests only — clears the per-root config-file cache between test cases. */
|
|
109
|
+
module.exports._resetForTests = function resetForTests() {
|
|
110
|
+
_configStatementCache.clear();
|
|
111
|
+
};
|
package/dist/chunk-UP5ZN7CN.mjs
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
resolveStyle
|
|
3
|
-
} from "./chunk-7KUNKZBM.mjs";
|
|
4
|
-
|
|
5
|
-
// src/jsx-runtime.tsx
|
|
6
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
|
-
import { Fragment } from "react";
|
|
8
|
-
import { Pressable } from "react-native";
|
|
9
|
-
function makeElement(isStaticChildren, type, props, key) {
|
|
10
|
-
return (isStaticChildren ? _jsxs : _jsx)(type, props, key);
|
|
11
|
-
}
|
|
12
|
-
function resolvedStyleFor(classStrRaw, userStyle, pressed) {
|
|
13
|
-
const resolved = resolveStyle(classStrRaw, pressed);
|
|
14
|
-
if (userStyle === void 0) {
|
|
15
|
-
return resolved;
|
|
16
|
-
}
|
|
17
|
-
if (typeof userStyle === "function") {
|
|
18
|
-
return [resolved, userStyle({ pressed })];
|
|
19
|
-
}
|
|
20
|
-
return [resolved, userStyle];
|
|
21
|
-
}
|
|
22
|
-
function processElement(type, rawProps, key, isStaticChildren) {
|
|
23
|
-
if (type === null || typeof type === "symbol" || !rawProps) {
|
|
24
|
-
return makeElement(isStaticChildren, type, rawProps ?? {}, key);
|
|
25
|
-
}
|
|
26
|
-
const { className: classStrRaw, style: userStyle, ...rest } = rawProps;
|
|
27
|
-
if (typeof classStrRaw !== "string") {
|
|
28
|
-
return makeElement(isStaticChildren, type, rawProps, key);
|
|
29
|
-
}
|
|
30
|
-
const finalStyle = type === Pressable ? (state) => resolvedStyleFor(classStrRaw, userStyle, state.pressed) : resolvedStyleFor(classStrRaw, userStyle, false);
|
|
31
|
-
return makeElement(isStaticChildren, type, { ...rest, style: finalStyle }, key);
|
|
32
|
-
}
|
|
33
|
-
function jsx(type, props, key) {
|
|
34
|
-
return processElement(type, props, key, false);
|
|
35
|
-
}
|
|
36
|
-
function jsxs(type, props, key) {
|
|
37
|
-
return processElement(type, props, key, true);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export {
|
|
41
|
-
Fragment,
|
|
42
|
-
jsx,
|
|
43
|
-
jsxs
|
|
44
|
-
};
|
package/dist/index.d.mts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
3
|
-
|
|
4
|
-
interface ThemeConfig {
|
|
5
|
-
/** Plain hex only, this pass — e.g. "blue-6" -> "#2563eb" */
|
|
6
|
-
colors: Record<string, string>;
|
|
7
|
-
spacing: Record<string, number>;
|
|
8
|
-
screens: Record<string, number>;
|
|
9
|
-
darkMode: 'attribute' | 'class' | 'media';
|
|
10
|
-
}
|
|
11
|
-
declare const defaultTheme: ThemeConfig;
|
|
12
|
-
declare function setTheme(theme: ThemeConfig): void;
|
|
13
|
-
declare function getTheme(): ThemeConfig;
|
|
14
|
-
|
|
15
|
-
interface StyleObject {
|
|
16
|
-
[key: string]: string | number;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Calls the Rust engine via the KbachModule TurboModule (see
|
|
20
|
-
* apps/native-sandbox/android/.../KbachModule.kt and
|
|
21
|
-
* packages/core-engine/src/resolve_style.rs) and returns a plain style
|
|
22
|
-
* object ready for RN's `style` prop. Synchronous — no async init step,
|
|
23
|
-
* unlike the web/WASM path, since JSI-backed TurboModule methods are
|
|
24
|
-
* available as soon as the module is loaded.
|
|
25
|
-
*
|
|
26
|
-
* Reads `getGlobalDarkMode()` (darkModeStore.ts) fresh on every call (no
|
|
27
|
-
* caching) so `dark:` resolves correctly whenever this runs. That store —
|
|
28
|
-
* not a direct `Appearance.getColorScheme()` read, which this function
|
|
29
|
-
* used prior to `<ThemeProvider>` support — is what makes an explicit
|
|
30
|
-
* `setGlobalThemeMode('dark')`/`toggle()` override actually affect
|
|
31
|
-
* rendering; the store itself falls back to tracking `Appearance` live
|
|
32
|
-
* whenever the mode is `'system'` (its own default), so nothing regresses
|
|
33
|
-
* for apps that never touch the override API at all. This function itself
|
|
34
|
-
* has no subscription of its own, so nothing re-renders just because the
|
|
35
|
-
* store's value changed — call `useGlobalDarkMode()` (or consume
|
|
36
|
-
* `useTheme()`'s `isDark`) somewhere in the tree for that, NOT React
|
|
37
|
-
* Native's own `useColorScheme()`, which only reflects the raw OS setting
|
|
38
|
-
* and would miss an explicit override entirely.
|
|
39
|
-
*
|
|
40
|
-
* Phase 11 originally shipped with live in-place theme switching unverified
|
|
41
|
-
* (a `uiMode` toggle triggered a re-render but `Appearance.getColorScheme()`
|
|
42
|
-
* kept returning a stale value). Root-caused in Phase 12 while debugging an
|
|
43
|
-
* unrelated `active:` failure: `apps/native-sandbox` has its own separate
|
|
44
|
-
* `node_modules/react-native` (excluded from the npm workspace since Phase
|
|
45
|
-
* 5), while `packages/react-native` resolved `react-native` hierarchically
|
|
46
|
-
* up to a *different* copy at the repo root — two physical module
|
|
47
|
-
* instances, so a `useColorScheme()` subscription and an
|
|
48
|
-
* `Appearance.getColorScheme()` read were silently talking to two separate
|
|
49
|
-
* copies of the Appearance module. Fixed by forcing a single instance via
|
|
50
|
-
* `resolver.resolveRequest` in apps/native-sandbox/metro.config.js; live
|
|
51
|
-
* theme switching (and `active:`, the bug that surfaced this) both verified
|
|
52
|
-
* working after the fix — see Phase 12. Still relevant here: darkModeStore.ts
|
|
53
|
-
* itself calls `Appearance.addChangeListener`, so the same dual-instance
|
|
54
|
-
* failure mode would silently break the 'system' fallback path too if that
|
|
55
|
-
* metro.config.js fix ever regressed.
|
|
56
|
-
*
|
|
57
|
-
* `pressed` gates the `active:` modifier — unlike `colorScheme`, there's no
|
|
58
|
-
* system-level getter for "is this element currently pressed"; only RN's
|
|
59
|
-
* `Pressable` ever knows this, so it's passed in directly by the caller
|
|
60
|
-
* (jsx-runtime, from Pressable's own style-function callback) rather than
|
|
61
|
-
* queried here. Defaults to `false` for every other element type.
|
|
62
|
-
*
|
|
63
|
-
* `width` gates `sm:`/`md:`/`lg:`/`xl:`/`2xl:` against the active theme's
|
|
64
|
-
* `screens` scale (resolve_style.rs's `native_modifier_state`) — read fresh
|
|
65
|
-
* from `Dimensions.get('window').width` on every call, the same "no
|
|
66
|
-
* caching, no store" choice `colorScheme`/`pressed` already make, and for
|
|
67
|
-
* the same reason: unlike dark mode there's no explicit-override concept to
|
|
68
|
-
* preserve here (nobody manually overrides their screen width), so a plain
|
|
69
|
-
* synchronous read is enough — no need for the subscribable store
|
|
70
|
-
* darkModeStore.ts is for. As with `colorScheme`, this function has no
|
|
71
|
-
* subscription of its own, so rotating/resizing the window doesn't
|
|
72
|
-
* re-render anything by itself — call RN's own `useWindowDimensions()`
|
|
73
|
-
* somewhere in the tree for that (same role `useColorScheme()` plays for
|
|
74
|
-
* `dark:` in apps/native-sandbox/App.tsx).
|
|
75
|
-
*/
|
|
76
|
-
declare function resolveStyle(classString: string, pressed?: boolean): StyleObject;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Native port of @kbach/react's darkModeStore.ts — same architecture (a
|
|
80
|
-
* single module-level store is the real source of truth;
|
|
81
|
-
* ThemeProvider/useTheme are a thin, OPTIONAL wrapper around it; every
|
|
82
|
-
* function here works standalone with no provider mounted anywhere), with
|
|
83
|
-
* two real platform differences from the web version:
|
|
84
|
-
*
|
|
85
|
-
* 1. No persistence. There's no React-Native-builtin equivalent of
|
|
86
|
-
* localStorage — adding one would mean forcing a real native dependency
|
|
87
|
-
* (e.g. @react-native-async-storage/async-storage) on every consumer,
|
|
88
|
-
* even ones that don't want it. The mode always starts at 'system' on a
|
|
89
|
-
* fresh app launch. If you want persistence, read your own storage at
|
|
90
|
-
* startup (before your first render) and call setGlobalThemeMode() with
|
|
91
|
-
* the result — same "seed once, no built-in write-back" shape as
|
|
92
|
-
* seedDefaultMode() below, just driven by your own storage instead of
|
|
93
|
-
* one this package picks for you.
|
|
94
|
-
* 2. No DOM to "apply" the resolved mode to. On web, applyToDom() flips a
|
|
95
|
-
* class/attribute the generated CSS's `dark:` selectors match against.
|
|
96
|
-
* On native there's no CSS at all — nativeBridge.ts's resolveStyle()
|
|
97
|
-
* reads getGlobalDarkMode() directly on every call instead of querying
|
|
98
|
-
* Appearance.getColorScheme() itself, which is what actually makes an
|
|
99
|
-
* explicit override affect rendering (previously that function always
|
|
100
|
-
* read the raw OS setting, so it had no way to reflect a user choice —
|
|
101
|
-
* see nativeBridge.ts's own comment on this for why that's the one
|
|
102
|
-
* genuinely fixed bug this change introduces, not just a new feature).
|
|
103
|
-
*/
|
|
104
|
-
type ThemeMode = 'light' | 'dark' | 'system';
|
|
105
|
-
declare function getGlobalDarkMode(): boolean;
|
|
106
|
-
declare function getGlobalThemeMode(): ThemeMode;
|
|
107
|
-
/** Explicit user choice — takes priority over any later `<ThemeProvider defaultMode>` seed (see `seedDefaultMode` below). Not persisted; see this module's own doc comment. */
|
|
108
|
-
declare function setGlobalThemeMode(nextMode: ThemeMode): void;
|
|
109
|
-
declare function toggleGlobalDarkMode(): void;
|
|
110
|
-
declare function subscribeGlobalDarkMode(listener: () => void): () => void;
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Reactive read of the global dark-mode store — works with or without a
|
|
114
|
-
* `<ThemeProvider>` mounted anywhere in the tree, since it subscribes to
|
|
115
|
-
* `darkModeStore.ts` directly. No server-snapshot argument (unlike
|
|
116
|
-
* @kbach/react's web version) — React Native has no SSR concept at all,
|
|
117
|
-
* so there's no third argument for `useSyncExternalStore` to need.
|
|
118
|
-
*/
|
|
119
|
-
declare function useGlobalDarkMode(): boolean;
|
|
120
|
-
|
|
121
|
-
interface ThemeProviderProps {
|
|
122
|
-
children: ReactNode;
|
|
123
|
-
/**
|
|
124
|
-
* Applied once, on this provider's first mount, but ONLY if nothing has
|
|
125
|
-
* made an explicit mode choice yet (see
|
|
126
|
-
* `darkModeStore.ts::seedDefaultMode`) — an explicit prior choice always
|
|
127
|
-
* wins over a component's own default. Unlike @kbach/react's web
|
|
128
|
-
* version, there's no persisted value to check here — see
|
|
129
|
-
* darkModeStore.ts's own doc comment on why this package doesn't persist
|
|
130
|
-
* the mode across app launches.
|
|
131
|
-
*/
|
|
132
|
-
defaultMode?: ThemeMode;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Native port of @kbach/react's `ThemeProvider` — a thin, OPTIONAL React
|
|
136
|
-
* Context wrapper around `darkModeStore.ts`'s single global store, not the
|
|
137
|
-
* source of truth itself. Because the store is global, MULTIPLE mounted
|
|
138
|
-
* providers share the exact same `mode`/`isDark` state rather than each
|
|
139
|
-
* getting their own scoped value — mounting more than one is very likely a
|
|
140
|
-
* mistake (each fighting over the same state via a different
|
|
141
|
-
* `defaultMode`), so this warns once in dev, matching the web version's
|
|
142
|
-
* identical guard.
|
|
143
|
-
*/
|
|
144
|
-
declare function ThemeProvider({ children, defaultMode }: ThemeProviderProps): react.JSX.Element;
|
|
145
|
-
|
|
146
|
-
interface ThemeContextValue {
|
|
147
|
-
mode: ThemeMode;
|
|
148
|
-
isDark: boolean;
|
|
149
|
-
setMode: (mode: ThemeMode) => void;
|
|
150
|
-
toggle: () => void;
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Requires a `<ThemeProvider>` ancestor — throws otherwise, since a
|
|
154
|
-
* component that destructures `{ mode, setMode }` genuinely needs Context
|
|
155
|
-
* (unlike a component that just wants the dark/light boolean, which should
|
|
156
|
-
* reach for `useGlobalDarkMode()` instead and never needs a provider at
|
|
157
|
-
* all).
|
|
158
|
-
*/
|
|
159
|
-
declare function useTheme(): ThemeContextValue;
|
|
160
|
-
|
|
161
|
-
export { type StyleObject, type ThemeConfig, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, defaultTheme, getGlobalDarkMode, getGlobalThemeMode, getTheme, resolveStyle, setGlobalThemeMode, setTheme, subscribeGlobalDarkMode, toggleGlobalDarkMode, useGlobalDarkMode, useTheme };
|