@kbach/react-native 1.0.0-beta.4 → 1.0.0-beta.40
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 +84 -132
- 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-BLLSnqAa.d.ts +277 -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 +2072 -393
- package/dist/index.web.d.ts +15 -4
- package/dist/index.web.js +438 -401
- package/dist/index.web.mjs +268 -14
- package/dist/jsx-dev-runtime.js +2177 -332
- package/dist/jsx-dev-runtime.web.js +206 -361
- package/dist/jsx-dev-runtime.web.mjs +19 -7
- package/dist/jsx-runtime.js +2177 -332
- package/dist/jsx-runtime.web.js +191 -353
- package/dist/jsx-runtime.web.mjs +3 -6
- package/dist/theme.d.ts +54 -0
- package/dist/{chunk-AEVOM2NB.mjs → theme.js} +86 -61
- package/package.json +43 -14
- package/src/babel-plugin.js +144 -2
- package/dist/chunk-4KCCEMEN.mjs +0 -66
- package/dist/chunk-DLIXI5OW.mjs +0 -53
- package/dist/chunk-FG5E4NLJ.mjs +0 -18
- package/dist/chunk-KSDFO34H.mjs +0 -32
- package/dist/chunk-OJQAJLYT.mjs +0 -14
- package/dist/chunk-ZUE6SCTE.mjs +0 -14
- package/dist/index.d.mts +0 -161
- package/dist/index.mjs +0 -32
- package/dist/index.web.d.mts +0 -7
- package/dist/jsx-dev-runtime.d.mts +0 -10
- package/dist/jsx-dev-runtime.mjs +0 -17
- package/dist/jsx-dev-runtime.web.d.mts +0 -10
- package/dist/jsx-runtime.d.mts +0 -7
- package/dist/jsx-runtime.mjs +0 -14
- package/dist/jsx-runtime.web.d.mts +0 -7
package/dist/jsx-runtime.web.mjs
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Fragment,
|
|
2
3
|
jsx,
|
|
3
4
|
jsxs
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import {
|
|
7
|
-
Fragment
|
|
8
|
-
} from "./chunk-DLIXI5OW.mjs";
|
|
9
|
-
import "./chunk-AEVOM2NB.mjs";
|
|
5
|
+
} from "./chunk-FLPPVP5G.mjs";
|
|
6
|
+
import "./chunk-ZYE4VIZE.mjs";
|
|
10
7
|
export {
|
|
11
8
|
Fragment,
|
|
12
9
|
jsx,
|
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,62 +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
|
-
export {
|
|
390
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
391
|
+
0 && (module.exports = {
|
|
361
392
|
defaultTheme,
|
|
362
|
-
setTheme,
|
|
363
393
|
getTheme,
|
|
364
394
|
getThemeJson,
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
setGlobalThemeMode,
|
|
368
|
-
toggleGlobalDarkMode,
|
|
369
|
-
subscribeGlobalDarkMode,
|
|
370
|
-
seedDefaultMode
|
|
371
|
-
};
|
|
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 (native)
|
|
5
|
-
"keywords": [
|
|
3
|
+
"version": "1.0.0-beta.40",
|
|
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,10 +27,16 @@
|
|
|
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
|
".": {
|
|
@@ -30,7 +46,11 @@
|
|
|
30
46
|
"import": "./dist/index.web.mjs",
|
|
31
47
|
"require": "./dist/index.web.js"
|
|
32
48
|
},
|
|
33
|
-
"
|
|
49
|
+
"node": {
|
|
50
|
+
"import": "./dist/index.web.mjs",
|
|
51
|
+
"require": "./dist/index.web.js"
|
|
52
|
+
},
|
|
53
|
+
"react-native": "./dist/index.js",
|
|
34
54
|
"require": "./dist/index.js"
|
|
35
55
|
},
|
|
36
56
|
"./jsx-runtime": {
|
|
@@ -40,7 +60,11 @@
|
|
|
40
60
|
"import": "./dist/jsx-runtime.web.mjs",
|
|
41
61
|
"require": "./dist/jsx-runtime.web.js"
|
|
42
62
|
},
|
|
43
|
-
"
|
|
63
|
+
"node": {
|
|
64
|
+
"import": "./dist/jsx-runtime.web.mjs",
|
|
65
|
+
"require": "./dist/jsx-runtime.web.js"
|
|
66
|
+
},
|
|
67
|
+
"react-native": "./dist/jsx-runtime.js",
|
|
44
68
|
"require": "./dist/jsx-runtime.js"
|
|
45
69
|
},
|
|
46
70
|
"./jsx-dev-runtime": {
|
|
@@ -50,25 +74,30 @@
|
|
|
50
74
|
"import": "./dist/jsx-dev-runtime.web.mjs",
|
|
51
75
|
"require": "./dist/jsx-dev-runtime.web.js"
|
|
52
76
|
},
|
|
53
|
-
"
|
|
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",
|
|
54
82
|
"require": "./dist/jsx-dev-runtime.js"
|
|
55
83
|
},
|
|
56
84
|
"./babel-plugin": "./src/babel-plugin.js"
|
|
57
85
|
},
|
|
58
86
|
"scripts": {
|
|
59
|
-
"build": "tsup",
|
|
60
|
-
"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",
|
|
61
89
|
"test": "vitest run --passWithNoTests",
|
|
90
|
+
"test:coverage": "vitest run --coverage --passWithNoTests",
|
|
62
91
|
"lint": "tsc --noEmit",
|
|
63
92
|
"generate:colors": "node ../core-engine/scripts/generate-palette.mjs src/generatedPalette.ts",
|
|
64
|
-
"generate:wasm-base64": "node ../core-engine/scripts/generate-wasm-base64.mjs src/wasmBinary.generated.ts"
|
|
65
|
-
|
|
66
|
-
"dependencies": {
|
|
67
|
-
"@kbach/core-engine": "1.0.0-beta.0"
|
|
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"
|
|
68
95
|
},
|
|
69
96
|
"devDependencies": {
|
|
70
97
|
"@types/react": "^19.2.0",
|
|
71
98
|
"@types/react-test-renderer": "^19.1.0",
|
|
99
|
+
"@vitest/coverage-v8": "^1.6.1",
|
|
100
|
+
"jsdom": "^30.0.1",
|
|
72
101
|
"react-native": "^0.87.0",
|
|
73
102
|
"react-test-renderer": "^19.2.0",
|
|
74
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,21 +23,159 @@
|
|
|
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 `buildConfigStatement`'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 check happen ONCE per root, not on every single file `pre()`
|
|
41
|
+
// runs for. Caches only the resolved PATH (or null) — never the built AST
|
|
42
|
+
// statement itself; see `buildConfigStatement`'s own doc comment for why
|
|
43
|
+
// that distinction is load-bearing, not a style preference.
|
|
44
|
+
const _configPathCache = new Map();
|
|
45
|
+
|
|
46
|
+
function resolveConfigPath(root) {
|
|
47
|
+
if (_configPathCache.has(root)) return _configPathCache.get(root);
|
|
48
|
+
const configPath = path.join(root, 'kbach.config.js');
|
|
49
|
+
const resolved = fs.existsSync(configPath) ? configPath : null;
|
|
50
|
+
_configPathCache.set(root, resolved);
|
|
51
|
+
return resolved;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Windows' filesystem is case-insensitive but case-preserving, and Metro's
|
|
55
|
+
// own `file.opts.filename` isn't guaranteed to share the exact casing/
|
|
56
|
+
// separator normalization `path.join` produces — comparing case-sensitively
|
|
57
|
+
// risked a false NEGATIVE (kbach.config.js not recognized as itself,
|
|
58
|
+
// re-introducing the self-require bug `buildConfigStatement`'s doc comment
|
|
59
|
+
// describes) far more than a false positive risks colliding two genuinely
|
|
60
|
+
// different files, which would require two paths differing ONLY by case in
|
|
61
|
+
// the same project — effectively never happens in practice.
|
|
62
|
+
function samePath(a, b) {
|
|
63
|
+
return !!a && !!b && path.resolve(a).toLowerCase() === path.resolve(b).toLowerCase();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Builds a FRESH (uncompiled) AST statement that applies `kbach.config.js`
|
|
68
|
+
* — a NEW node on every single call, deliberately never cached or reused
|
|
69
|
+
* across files. `root` defaults to `process.cwd()` (how `metro start`/
|
|
70
|
+
* `expo start` are conventionally invoked) at the plugin's own call site
|
|
71
|
+
* below; pass `{ configRoot: '...' }` as this plugin's Babel options if
|
|
72
|
+
* your `kbach.config.js` lives somewhere else (a monorepo where Metro's
|
|
73
|
+
* actual working directory isn't the app's own root, for instance).
|
|
74
|
+
*
|
|
75
|
+
* `null` if no such file exists at `root` (checked via the cached
|
|
76
|
+
* `resolveConfigPath` above — `fs.existsSync` itself is cheap enough to
|
|
77
|
+
* memoize per root without any of the risk rebuilding the AST fresh here
|
|
78
|
+
* guards against).
|
|
79
|
+
*
|
|
80
|
+
* An EARLIER version of this function built the statement once per root
|
|
81
|
+
* and cached/reused that SAME AST node object across every file's `pre()`
|
|
82
|
+
* call. That's unsafe with Babel: a node spliced into one file's AST gets
|
|
83
|
+
* mutated in place as THAT file's own transform pipeline runs (location
|
|
84
|
+
* info, scope bindings, hoisting markers, ...) — sharing one mutable node
|
|
85
|
+
* instance across many independently-processed file ASTs corrupts it after
|
|
86
|
+
* the first file. Confirmed as the actual cause of a real bundling failure
|
|
87
|
+
* (not a hypothetical): Metro's own dependency collector needs a require()
|
|
88
|
+
* call's location info to validate/instrument it, and once the shared
|
|
89
|
+
* node's location got corrupted by an earlier file's pass, Metro rejected
|
|
90
|
+
* a LATER file's copy with "Invalid call at line <unknown>". Rebuilding
|
|
91
|
+
* fresh per call fixes this at negligible cost — `@babel/template`'s own
|
|
92
|
+
* parse is cheap, and this runs once per file Metro transforms regardless.
|
|
93
|
+
*
|
|
94
|
+
* The injected statement is a plain `require(...).applyKbachConfig(require(...))`
|
|
95
|
+
* — no try/catch around it. `kbach.config.js`'s own path is only ever
|
|
96
|
+
* baked in here after `existsSync` already confirmed it exists, so the
|
|
97
|
+
* only way this could still throw is a genuine bug in that file (a syntax
|
|
98
|
+
* error, a bad nested require) — letting that surface as a real, visible
|
|
99
|
+
* Metro bundling error is far better than silently swallowing it: a
|
|
100
|
+
* developer's real customization would otherwise go missing with zero
|
|
101
|
+
* indication why, exactly the kind of headache a good default should avoid.
|
|
102
|
+
*
|
|
103
|
+
* `@babel/template` builds this from a plain string with the absolute path
|
|
104
|
+
* JSON-escaped directly into it (correct on Windows too, backslashes and
|
|
105
|
+
* all) — simpler than building the equivalent `@babel/types` node graph by
|
|
106
|
+
* hand for one straightforward statement.
|
|
23
107
|
*/
|
|
24
|
-
|
|
108
|
+
function buildConfigStatement(root) {
|
|
109
|
+
const configPath = resolveConfigPath(root);
|
|
110
|
+
if (configPath === null) return null;
|
|
111
|
+
|
|
112
|
+
const buildStatement = template(
|
|
113
|
+
`require('@kbach/react-native').applyKbachConfig(require(${JSON.stringify(configPath)}));`,
|
|
114
|
+
);
|
|
115
|
+
return buildStatement();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = function kbachReactNativeBabelPlugin(_babel, options) {
|
|
119
|
+
const configRoot = (options && options.configRoot) || process.cwd();
|
|
120
|
+
|
|
25
121
|
return {
|
|
26
122
|
name: 'kbach-react-native',
|
|
27
123
|
pre(file) {
|
|
28
124
|
const filename = file.opts && file.opts.filename;
|
|
29
125
|
if (filename && filename.indexOf('node_modules') !== -1) return;
|
|
30
126
|
|
|
127
|
+
// Metro/Expo's own internal polyfills (the Node.js-external-require
|
|
128
|
+
// shim, the assets-registry shim, node:/weak-ref virtualizations, ...)
|
|
129
|
+
// are synthetic "virtual modules" with no real file on disk. Expo's
|
|
130
|
+
// own Metro config marks every one of these with an embedded NUL
|
|
131
|
+
// control character in its module ID (Rollup's/Metro's own
|
|
132
|
+
// convention for "this isn't a real file"). These never sit inside
|
|
133
|
+
// node_modules, so the check above doesn't catch them — and Metro
|
|
134
|
+
// places them directly in the bundle PRELUDE, entirely outside any
|
|
135
|
+
// wrapped module, where `require` isn't defined as a real function
|
|
136
|
+
// at all. Confirmed as a real, reproducible runtime crash (not a
|
|
137
|
+
// hypothetical): injecting this plugin's applyKbachConfig() require()
|
|
138
|
+
// call into that exact polyfill produced Expo's own "[runtime not
|
|
139
|
+
// ready]: ReferenceError: Property 'require' doesn't exist" at app
|
|
140
|
+
// startup, verified by exporting a real dev bundle and finding the
|
|
141
|
+
// offending, un-instrumented require() call sitting inside
|
|
142
|
+
// withMetroMultiPlatform.js's own external-require polyfill — a file
|
|
143
|
+
// this plugin was never meant to touch at all.
|
|
144
|
+
//
|
|
145
|
+
// The control character is NOT at the start of `filename`, so
|
|
146
|
+
// `startsWith`/`charCodeAt(0)` (what an earlier version of this
|
|
147
|
+
// check used) never matches — confirmed by actually logging Babel's
|
|
148
|
+
// real `filename` for this file, not assumed: Metro joins the
|
|
149
|
+
// project root onto the raw virtual module ID first, so the control
|
|
150
|
+
// character ends up in the MIDDLE of the full string, right after
|
|
151
|
+
// the trailing path separator, not at index 0. A search anywhere in
|
|
152
|
+
// the string (rather than just its start) is what actually catches
|
|
153
|
+
// it — see this file's own test suite for the exact filename shape
|
|
154
|
+
// this was verified against.
|
|
155
|
+
if (filename && filename.indexOf(String.fromCharCode(0)) !== -1) return;
|
|
156
|
+
|
|
31
157
|
const comments = file.ast.comments || (file.ast.comments = []);
|
|
32
158
|
const alreadySet = comments.some((c) => /@jsxImportSource|@jsxRuntime/.test(c.value));
|
|
33
159
|
if (!alreadySet) {
|
|
34
160
|
comments.unshift({ type: 'CommentLine', value: ' @jsxImportSource @kbach/react-native' });
|
|
35
161
|
}
|
|
162
|
+
|
|
163
|
+
// Never inject the auto-apply statement into kbach.config.js itself —
|
|
164
|
+
// it would require() its own absolute path, a real self-reference
|
|
165
|
+
// cycle in Metro's dependency graph for a statement that makes no
|
|
166
|
+
// sense there anyway (a config file applying itself to itself).
|
|
167
|
+
if (samePath(filename, resolveConfigPath(configRoot))) return;
|
|
168
|
+
|
|
169
|
+
const configStatement = buildConfigStatement(configRoot);
|
|
170
|
+
if (configStatement) {
|
|
171
|
+
file.ast.program.body.unshift(configStatement);
|
|
172
|
+
}
|
|
36
173
|
},
|
|
37
174
|
visitor: {},
|
|
38
175
|
};
|
|
39
176
|
};
|
|
177
|
+
|
|
178
|
+
/** Exported for tests only — clears the per-root config-path cache between test cases. */
|
|
179
|
+
module.exports._resetForTests = function resetForTests() {
|
|
180
|
+
_configPathCache.clear();
|
|
181
|
+
};
|
package/dist/chunk-4KCCEMEN.mjs
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getGlobalDarkMode,
|
|
3
|
-
getGlobalThemeMode,
|
|
4
|
-
seedDefaultMode,
|
|
5
|
-
setGlobalThemeMode,
|
|
6
|
-
subscribeGlobalDarkMode,
|
|
7
|
-
toggleGlobalDarkMode
|
|
8
|
-
} from "./chunk-AEVOM2NB.mjs";
|
|
9
|
-
|
|
10
|
-
// src/useGlobalDarkMode.ts
|
|
11
|
-
import { useSyncExternalStore } from "react";
|
|
12
|
-
function useGlobalDarkMode() {
|
|
13
|
-
return useSyncExternalStore(subscribeGlobalDarkMode, getGlobalDarkMode);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// src/ThemeContext.tsx
|
|
17
|
-
import { createContext, useContext } from "react";
|
|
18
|
-
var ThemeContext = createContext(null);
|
|
19
|
-
function useTheme() {
|
|
20
|
-
const ctx = useContext(ThemeContext);
|
|
21
|
-
if (!ctx) {
|
|
22
|
-
throw new Error(
|
|
23
|
-
'useTheme() must be called within a <ThemeProvider>. For dark-mode state without a provider, use useGlobalDarkMode() (read) or toggleGlobalDarkMode()/setGlobalThemeMode() (write) from "@kbach/react-native" instead.'
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
return ctx;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// src/ThemeProvider.tsx
|
|
30
|
-
import { useEffect, useMemo, useRef, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
31
|
-
import { jsx } from "react/jsx-runtime";
|
|
32
|
-
var mountedProviderCount = 0;
|
|
33
|
-
var warnedMultipleProviders = false;
|
|
34
|
-
function ThemeProvider({ children, defaultMode = "system" }) {
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
mountedProviderCount++;
|
|
37
|
-
if (mountedProviderCount > 1 && !warnedMultipleProviders) {
|
|
38
|
-
warnedMultipleProviders = true;
|
|
39
|
-
console.warn(
|
|
40
|
-
"[kbach] Multiple <ThemeProvider> instances are mounted at once. Dark-mode state is one global store (the same one useGlobalDarkMode()/toggleGlobalDarkMode() read and write), not scoped per-provider, so they will fight over the same state instead of each getting their own."
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
return () => {
|
|
44
|
-
mountedProviderCount--;
|
|
45
|
-
};
|
|
46
|
-
}, []);
|
|
47
|
-
const seeded = useRef(false);
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
if (seeded.current) return;
|
|
50
|
-
seeded.current = true;
|
|
51
|
-
seedDefaultMode(defaultMode);
|
|
52
|
-
}, []);
|
|
53
|
-
const isDark = useSyncExternalStore2(subscribeGlobalDarkMode, getGlobalDarkMode);
|
|
54
|
-
const mode = useSyncExternalStore2(subscribeGlobalDarkMode, getGlobalThemeMode);
|
|
55
|
-
const value = useMemo(
|
|
56
|
-
() => ({ mode, isDark, setMode: setGlobalThemeMode, toggle: toggleGlobalDarkMode }),
|
|
57
|
-
[mode, isDark]
|
|
58
|
-
);
|
|
59
|
-
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export {
|
|
63
|
-
useGlobalDarkMode,
|
|
64
|
-
useTheme,
|
|
65
|
-
ThemeProvider
|
|
66
|
-
};
|
package/dist/chunk-DLIXI5OW.mjs
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// src/jsxRuntimeCore.ts
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Fragment } from "react";
|
|
4
|
-
import { Pressable } from "react-native";
|
|
5
|
-
function makeElement(isStaticChildren, type, props, key) {
|
|
6
|
-
return (isStaticChildren ? _jsxs : _jsx)(type, props, key);
|
|
7
|
-
}
|
|
8
|
-
function resolvedStyleFor(resolveStyle, classStrRaw, userStyle, pressed) {
|
|
9
|
-
const resolved = resolveStyle(classStrRaw, pressed);
|
|
10
|
-
if (userStyle === void 0) {
|
|
11
|
-
return resolved;
|
|
12
|
-
}
|
|
13
|
-
if (typeof userStyle === "function") {
|
|
14
|
-
return [resolved, userStyle({ pressed })];
|
|
15
|
-
}
|
|
16
|
-
return [resolved, userStyle];
|
|
17
|
-
}
|
|
18
|
-
function processElement(resolveStyle, type, rawProps, key, isStaticChildren) {
|
|
19
|
-
if (type === null || typeof type === "symbol" || !rawProps) {
|
|
20
|
-
return makeElement(isStaticChildren, type, rawProps ?? {}, key);
|
|
21
|
-
}
|
|
22
|
-
const { className: classStrRaw, style: userStyle, ...rest } = rawProps;
|
|
23
|
-
if (typeof classStrRaw !== "string") {
|
|
24
|
-
return makeElement(isStaticChildren, type, rawProps, key);
|
|
25
|
-
}
|
|
26
|
-
const finalStyle = type === Pressable ? (state) => resolvedStyleFor(resolveStyle, classStrRaw, userStyle, state.pressed) : resolvedStyleFor(resolveStyle, classStrRaw, userStyle, false);
|
|
27
|
-
return makeElement(isStaticChildren, type, { ...rest, style: finalStyle }, key);
|
|
28
|
-
}
|
|
29
|
-
function createJsxFunctions(resolveStyle) {
|
|
30
|
-
function jsx(type, props, key) {
|
|
31
|
-
return processElement(resolveStyle, type, props, key, false);
|
|
32
|
-
}
|
|
33
|
-
function jsxs(type, props, key) {
|
|
34
|
-
return processElement(resolveStyle, type, props, key, true);
|
|
35
|
-
}
|
|
36
|
-
return { jsx, jsxs };
|
|
37
|
-
}
|
|
38
|
-
function createJsxDEV(jsx, jsxs) {
|
|
39
|
-
return function jsxDEV(type, props, key, isStaticChildren, source, self) {
|
|
40
|
-
const element = (isStaticChildren ? jsxs : jsx)(type, props, key);
|
|
41
|
-
if (source && element && typeof element === "object" && Object.isExtensible(element)) {
|
|
42
|
-
element._source = source;
|
|
43
|
-
element._self = self;
|
|
44
|
-
}
|
|
45
|
-
return element;
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export {
|
|
50
|
-
Fragment,
|
|
51
|
-
createJsxFunctions,
|
|
52
|
-
createJsxDEV
|
|
53
|
-
};
|
package/dist/chunk-FG5E4NLJ.mjs
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getGlobalDarkMode,
|
|
3
|
-
getThemeJson
|
|
4
|
-
} from "./chunk-AEVOM2NB.mjs";
|
|
5
|
-
|
|
6
|
-
// src/nativeBridge.ts
|
|
7
|
-
import { Dimensions, TurboModuleRegistry } from "react-native";
|
|
8
|
-
function resolveStyle(classString, pressed = false) {
|
|
9
|
-
const KbachModule = TurboModuleRegistry.getEnforcing("KbachModule");
|
|
10
|
-
const colorScheme = getGlobalDarkMode() ? "dark" : "light";
|
|
11
|
-
const width = Dimensions.get("window").width;
|
|
12
|
-
const json = KbachModule.resolveStyle(classString, getThemeJson(), colorScheme, pressed, width);
|
|
13
|
-
return JSON.parse(json);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
resolveStyle
|
|
18
|
-
};
|