@kbach/react-native 1.0.0-beta.2 → 1.0.0-beta.20

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.
Files changed (42) hide show
  1. package/README.md +220 -87
  2. package/android/build.gradle +36 -0
  3. package/android/src/main/AndroidManifest.xml +14 -0
  4. package/android/src/main/java/com/kbachnative/KbachModule.kt +83 -0
  5. package/android/src/main/java/com/kbachnative/KbachPackage.kt +54 -0
  6. package/android/src/main/jniLibs/arm64-v8a/libkbach_core_engine.so +0 -0
  7. package/android/src/main/jniLibs/x86_64/libkbach_core_engine.so +0 -0
  8. package/dist/chunk-45GOGUHJ.mjs +107 -0
  9. package/dist/chunk-MJWWZJMH.mjs +40 -0
  10. package/dist/chunk-OGFB3OXS.mjs +552 -0
  11. package/dist/darkModeStore.d.ts +58 -0
  12. package/dist/darkModeStore.js +112 -0
  13. package/dist/dynamicTokens.d.ts +35 -0
  14. package/dist/dynamicTokens.js +75 -0
  15. package/dist/index.d.ts +53 -102
  16. package/dist/index.js +1578 -385
  17. package/dist/index.web.d.ts +18 -0
  18. package/dist/index.web.js +436 -0
  19. package/dist/index.web.mjs +210 -0
  20. package/dist/jsx-dev-runtime.d.ts +3 -12
  21. package/dist/jsx-dev-runtime.js +1565 -344
  22. package/dist/jsx-dev-runtime.web.d.ts +10 -0
  23. package/dist/jsx-dev-runtime.web.js +271 -0
  24. package/dist/jsx-dev-runtime.web.mjs +29 -0
  25. package/dist/jsx-runtime.d.ts +3 -23
  26. package/dist/jsx-runtime.js +1556 -336
  27. package/dist/jsx-runtime.web.d.ts +7 -0
  28. package/dist/jsx-runtime.web.js +252 -0
  29. package/dist/{jsx-runtime.mjs → jsx-runtime.web.mjs} +2 -2
  30. package/dist/theme.d.ts +54 -0
  31. package/dist/{chunk-YHF3QLRL.mjs → theme.js} +94 -72
  32. package/dist/useDynamicToken-Dr15c88t.d.ts +160 -0
  33. package/package.json +62 -14
  34. package/react-native.config.js +29 -0
  35. package/specs/NativeKbachModule.ts +31 -0
  36. package/src/babel-plugin.js +74 -2
  37. package/dist/chunk-SVMYVXY6.mjs +0 -44
  38. package/dist/index.d.mts +0 -148
  39. package/dist/index.mjs +0 -80
  40. package/dist/jsx-dev-runtime.d.mts +0 -19
  41. package/dist/jsx-dev-runtime.mjs +0 -20
  42. package/dist/jsx-runtime.d.mts +0 -27
@@ -2,8 +2,8 @@ import {
2
2
  Fragment,
3
3
  jsx,
4
4
  jsxs
5
- } from "./chunk-SVMYVXY6.mjs";
6
- import "./chunk-YHF3QLRL.mjs";
5
+ } from "./chunk-MJWWZJMH.mjs";
6
+ import "./chunk-OGFB3OXS.mjs";
7
7
  export {
8
8
  Fragment,
9
9
  jsx,
@@ -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,19 +307,73 @@ 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
- "8": 32
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
- screens: {},
293
- darkMode: "attribute"
356
+ screens: {
357
+ sm: 640,
358
+ md: 768,
359
+ lg: 1024,
360
+ xl: 1280,
361
+ "2xl": 1536
362
+ },
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: {}
294
377
  };
295
378
  var activeTheme = defaultTheme;
296
379
  var activeThemeJson = JSON.stringify(defaultTheme);
@@ -304,71 +387,10 @@ function getTheme() {
304
387
  function getThemeJson() {
305
388
  return activeThemeJson;
306
389
  }
307
-
308
- // src/darkModeStore.ts
309
- import { Appearance } from "react-native";
310
- var listeners = /* @__PURE__ */ new Set();
311
- function systemPrefersDark() {
312
- return Appearance.getColorScheme() === "dark";
313
- }
314
- function resolveIsDark(m) {
315
- return m === "system" ? systemPrefersDark() : m === "dark";
316
- }
317
- var mode = "system";
318
- var isDark = resolveIsDark(mode);
319
- var hasExplicitChoice = false;
320
- var subscription = Appearance.addChangeListener(({ colorScheme }) => {
321
- if (mode !== "system") return;
322
- isDark = colorScheme === "dark";
323
- notify();
324
- });
325
- function notify() {
326
- for (const listener of listeners) listener();
327
- }
328
- function getGlobalDarkMode() {
329
- return isDark;
330
- }
331
- function getGlobalThemeMode() {
332
- return mode;
333
- }
334
- function setGlobalThemeMode(nextMode) {
335
- mode = nextMode;
336
- isDark = resolveIsDark(nextMode);
337
- hasExplicitChoice = true;
338
- notify();
339
- }
340
- function toggleGlobalDarkMode() {
341
- setGlobalThemeMode(isDark ? "light" : "dark");
342
- }
343
- function subscribeGlobalDarkMode(listener) {
344
- listeners.add(listener);
345
- return () => listeners.delete(listener);
346
- }
347
- function seedDefaultMode(fallback) {
348
- if (hasExplicitChoice || mode === fallback) return;
349
- mode = fallback;
350
- isDark = resolveIsDark(fallback);
351
- notify();
352
- }
353
-
354
- // src/nativeBridge.ts
355
- import { TurboModuleRegistry } from "react-native";
356
- function resolveStyle(classString, pressed = false) {
357
- const KbachModule = TurboModuleRegistry.getEnforcing("KbachModule");
358
- const colorScheme = getGlobalDarkMode() ? "dark" : "light";
359
- const json = KbachModule.resolveStyle(classString, getThemeJson(), colorScheme, pressed);
360
- return JSON.parse(json);
361
- }
362
-
363
- export {
390
+ // Annotate the CommonJS export names for ESM import in node:
391
+ 0 && (module.exports = {
364
392
  defaultTheme,
365
- setTheme,
366
393
  getTheme,
367
- getGlobalDarkMode,
368
- getGlobalThemeMode,
369
- setGlobalThemeMode,
370
- toggleGlobalDarkMode,
371
- subscribeGlobalDarkMode,
372
- seedDefaultMode,
373
- resolveStyle
374
- };
394
+ getThemeJson,
395
+ setTheme
396
+ });
@@ -0,0 +1,160 @@
1
+ import { ThemeConfig, ContainerConfig } from './theme';
2
+ import { ThemeMode } from './darkModeStore';
3
+ import * as react from 'react';
4
+ import { ReactNode } from 'react';
5
+
6
+ /**
7
+ * A `kbach.config.js`-style theme customization — same shape as
8
+ * @kbach/react's `KbachConfig`, minus mode-aware `{ light, dark }` color
9
+ * entries in `extend.colors` specifically: aliasing/opacity math here only
10
+ * ever operates on plain hex (`brand: 'blue-6'`, `brandSoft: 'brand/30'`),
11
+ * matching `jsEngine/resolvers/color.ts`'s `lookupHex`'s own Plain-only
12
+ * convention — a reference to an EXISTING color that happens to be
13
+ * mode-aware (`theme.colors` can hold those now, for `useColors()`; see
14
+ * `theme.ts`'s own `ColorEntry` doc comment) just doesn't resolve as an
15
+ * alias, falling through to "literal CSS value" the same way an unknown
16
+ * name would. A config object goes in, a resolved `ThemeConfig` comes out,
17
+ * via one PURE function (`resolveKbachConfig`) — no plugins, no
18
+ * live-update store, nothing to keep "in sync."
19
+ */
20
+ interface KbachConfig {
21
+ darkMode?: ThemeConfig['darkMode'];
22
+ /**
23
+ * REPLACES the named section of `defaultTheme` entirely — use `extend` to
24
+ * add instead of replace. `container` isn't here (only under `extend`
25
+ * below) — see @kbach/react's identical `KbachConfig.theme` doc comment
26
+ * for why.
27
+ */
28
+ theme?: Partial<Pick<ThemeConfig, 'colors' | 'spacing' | 'screens' | 'fontFamily'>>;
29
+ /** Merges INTO `defaultTheme` (or `theme` above, if also supplied) — the common case. */
30
+ extend?: {
31
+ /**
32
+ * A value is either a literal hex color, or a REFERENCE to another
33
+ * color name, optionally with an opacity suffix. Two things can be
34
+ * referenced: a BUILT-IN palette color, always available regardless of
35
+ * ordering (`accent: 'orange-5'`, `accentSoft: 'orange-5/50'`), or a
36
+ * custom color defined EARLIER in this same object (`{ brand:
37
+ * '#ff6b35', brandSoft: 'brand/30' }` — `brandSoft` resolves to
38
+ * `brand`'s own value at 30% opacity). A string that doesn't match any
39
+ * known color name is treated as a literal CSS value instead.
40
+ */
41
+ colors?: Record<string, string>;
42
+ spacing?: Record<string, number>;
43
+ screens?: Record<string, number>;
44
+ /**
45
+ * Named font stacks, e.g. `{ display: '"Cal Sans", sans-serif' }` —
46
+ * adds to (or overrides one of) `defaultTheme.fontFamily`'s three
47
+ * names. Write it as a real web-shaped fallback stack even though this
48
+ * is the React Native package — native's own resolver strips it down
49
+ * to a single bare name at resolve time (RN's `fontFamily` prop can't
50
+ * take a fallback list), so the SAME config still works unmodified on
51
+ * Expo Web, which needs the full stack.
52
+ */
53
+ fontFamily?: Record<string, string>;
54
+ /** `{ center: true, padding: '2rem' }` — see `ContainerConfig`'s own doc comment. */
55
+ container?: ContainerConfig;
56
+ };
57
+ }
58
+ /** Merges a `KbachConfig` into `defaultTheme` — a pure function, no side effects. */
59
+ declare function resolveKbachConfig(config: KbachConfig): ThemeConfig;
60
+ /** `resolveKbachConfig()` + `setTheme()` in one call — the usual entry point from app code. */
61
+ declare function applyKbachConfig(config: KbachConfig): ThemeConfig;
62
+
63
+ interface ThemeState {
64
+ mode: ThemeMode;
65
+ isDark: boolean;
66
+ setMode: (mode: ThemeMode) => void;
67
+ toggle: () => void;
68
+ }
69
+ /**
70
+ * The one hook for reading and writing Kbach's dark-mode state. Reads
71
+ * `darkModeStore.ts`'s global store directly via `useSyncExternalStore` —
72
+ * that module-level store, not React Context, is the actual source of
73
+ * truth (every provider used to just mirror it into a Context value), so
74
+ * this works from ANY component, with or without a `<ThemeProvider>`
75
+ * mounted anywhere in the tree. `<ThemeProvider>` still exists, but purely
76
+ * as an optional convenience for seeding an app's initial `defaultMode` —
77
+ * see its own doc comment. No server-snapshot argument (unlike
78
+ * @kbach/react's web version) — React Native has no SSR concept at all,
79
+ * so there's no third argument for `useSyncExternalStore` to need.
80
+ */
81
+ declare function useTheme(): ThemeState;
82
+
83
+ interface ThemeProviderProps {
84
+ children: ReactNode;
85
+ /**
86
+ * Applied once, on this provider's first mount, but ONLY if nothing has
87
+ * made an explicit mode choice yet (see
88
+ * `darkModeStore.ts::seedDefaultMode`) — an explicit prior choice always
89
+ * wins over a component's own default. Unlike @kbach/react's web
90
+ * version, there's no persisted value to check here — see
91
+ * darkModeStore.ts's own doc comment on why this package doesn't persist
92
+ * the mode across app launches.
93
+ */
94
+ defaultMode?: ThemeMode;
95
+ }
96
+ /**
97
+ * Optional — `useTheme()` reads `darkModeStore.ts`'s global store directly
98
+ * and works anywhere without this mounted at all. The only thing this
99
+ * component does is seed that store's initial `defaultMode` once, for apps
100
+ * that want to declare a startup default in JSX rather than calling
101
+ * `setGlobalThemeMode()` imperatively before their first render. It
102
+ * renders `children` as-is — no Context.Provider, since there's no value
103
+ * left to distribute; `useTheme()` never reads from Context.
104
+ *
105
+ * Because the store is global, MULTIPLE mounted providers would each try
106
+ * to seed the exact same store rather than getting their own scoped
107
+ * state — mounting more than one is very likely a mistake, so this warns
108
+ * once in dev.
109
+ */
110
+ declare function ThemeProvider({ children, defaultMode }: ThemeProviderProps): react.JSX.Element;
111
+
112
+ interface ColorScale {
113
+ /** `colors.blue[6]` -> raw value */
114
+ readonly [shade: number]: string;
115
+ /** `colors.blue['6/50']` -> shade 6 at 50% opacity */
116
+ readonly [key: string]: string;
117
+ }
118
+ type ColorsAPI = {
119
+ /** `colors.white`, `colors.blue` (a shade family), ... */
120
+ readonly [name: string]: string | ColorScale;
121
+ } & {
122
+ /** Applies an opacity (0–100) to any color string this hook can return. */
123
+ readonly alpha: (color: string, opacity?: number) => string;
124
+ };
125
+ /**
126
+ * Resolved theme colors as real values — for anywhere you need an actual
127
+ * color (a chart library prop, a raw SVG `fill`), not a `className`. Ported
128
+ * from old-kbach's `useColors()`, adapted to this engine's flat
129
+ * `"blue-6"`-keyed palette (`colors.blue[6]` looks up `"blue-6"`).
130
+ *
131
+ * Any color — a shade-family entry like `"blue-6"` or a standalone name —
132
+ * can be defined in the theme as either a plain string (static, same in
133
+ * both modes) or a `{ light, dark }` pair — fully manual, no automatic
134
+ * shade-shifting or other assumption about what a "dark variant" should
135
+ * be; see `resolveEntry`'s own doc comment for how a `{ light, dark }`
136
+ * entry resolves on this platform specifically.
137
+ *
138
+ * ```ts
139
+ * const colors = useColors();
140
+ * colors.blue[6]; // "#2563eb" — static, same value in both modes
141
+ * colors.blue['6/50']; // same, at 50% opacity
142
+ * colors.brand; // resolves per isDark if defined as { light, dark }
143
+ * ```
144
+ */
145
+ declare function useColors(): ColorsAPI;
146
+
147
+ /**
148
+ * Reads a dynamic token's CURRENT value reactively — the JS-value
149
+ * counterpart to using `var(--name)` directly in a `className` (see
150
+ * `jsxRuntimeCore.ts`'s own `substituteDynamicTokens`/`ReactiveElement` for
151
+ * how that path stays live). Useful when a component needs the raw value
152
+ * itself (e.g. to drive an Animated value, or a calculation), not just a
153
+ * resolved style. Subscribes to the store's shared version counter (same
154
+ * one every token change bumps — see `dynamicTokens.ts`'s own doc comment
155
+ * on why there's no per-token subscription), so this re-renders on ANY
156
+ * token change, then re-reads just the one name it was asked for.
157
+ */
158
+ declare function useDynamicToken(name: string): string | undefined;
159
+
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, applyKbachConfig as d, useDynamicToken as e, useTheme as f, resolveKbachConfig as r, useColors as u };
package/package.json CHANGED
@@ -1,8 +1,18 @@
1
1
  {
2
2
  "name": "@kbach/react-native",
3
- "version": "1.0.0-beta.2",
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. Beta, Android-only. Works with the React Native CLI or an Expo dev client (not Expo Go).",
5
- "keywords": ["css", "react-native", "expo", "tailwind", "utility-first", "styling", "android"],
3
+ "version": "1.0.0-beta.20",
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,45 +27,75 @@
17
27
  "publishConfig": {
18
28
  "access": "public"
19
29
  },
20
- "files": ["dist", "src/babel-plugin.js"],
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
- "import": "./dist/index.mjs",
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
- "import": "./dist/jsx-runtime.mjs",
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
- "import": "./dist/jsx-dev-runtime.mjs",
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",
50
90
  "lint": "tsc --noEmit",
51
- "generate:colors": "node ../core-engine/scripts/generate-palette.mjs src/generatedPalette.ts"
52
- },
53
- "dependencies": {
54
- "@kbach/android": "1.0.0-beta.0"
91
+ "generate:colors": "node ../core-engine/scripts/generate-palette.mjs src/generatedPalette.ts",
92
+ "generate:wasm-base64": "node ../core-engine/scripts/generate-wasm-base64.mjs src/wasmBinary.generated.ts",
93
+ "generate:web-glue": "node ../core-engine/scripts/generate-web-glue.mjs src/wasmGlue.generated.ts"
55
94
  },
56
95
  "devDependencies": {
57
96
  "@types/react": "^19.2.0",
58
97
  "@types/react-test-renderer": "^19.1.0",
98
+ "jsdom": "^30.0.1",
59
99
  "react-native": "^0.87.0",
60
100
  "react-test-renderer": "^19.2.0",
61
101
  "tsup": "^8.0.0",
@@ -65,5 +105,13 @@
65
105
  "peerDependencies": {
66
106
  "react": ">=18.0.0",
67
107
  "react-native": ">=0.70.0"
108
+ },
109
+ "codegenConfig": {
110
+ "name": "KbachSpec",
111
+ "type": "modules",
112
+ "jsSrcsDir": "specs",
113
+ "android": {
114
+ "javaPackageName": "com.kbachnative"
115
+ }
68
116
  }
69
117
  }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Explicit override for React Native autolinking's Android package
3
+ * detection, which otherwise guesses `<packageName>.<PackageClassName>`
4
+ * from either AndroidManifest.xml's `package` attribute or (when absent,
5
+ * our case) build.gradle's `namespace` — see cli-config-android's
6
+ * getPackageName. That guess is wrong here on purpose: KbachModule.kt/
7
+ * KbachPackage.kt declare `package com.kbachnative` (chosen to match the
8
+ * JNI extern symbol names in packages/core-engine/src/jni_bridge.rs), but
9
+ * this module's Gradle `namespace` has to be a DIFFERENT value
10
+ * (com.kbachnative.nativemodule, see android/build.gradle's own comment)
11
+ * since AGP requires unique namespaces per module and the consuming app
12
+ * itself already claims "com.kbachnative" as its own. Without this
13
+ * override, the generated PackageList.java imports the non-existent
14
+ * `com.kbachnative.nativemodule.KbachPackage` and the app fails to
15
+ * compile. (A `package="com.kbachnative"` attribute in AndroidManifest.xml
16
+ * was tried first instead — simpler on paper — but AGP 9+ hard-errors on a
17
+ * manifest package that doesn't match the namespace, so this explicit
18
+ * config override is the only working fix.)
19
+ */
20
+ module.exports = {
21
+ dependency: {
22
+ platforms: {
23
+ android: {
24
+ packageImportPath: 'import com.kbachnative.KbachPackage;',
25
+ packageInstance: 'new KbachPackage()',
26
+ },
27
+ },
28
+ },
29
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * TurboModule spec for KbachModule — RN's Codegen convention requires this
3
+ * exact `Native<Name>.ts` filename and a default-exported
4
+ * `TurboModuleRegistry.getEnforcing<Spec>(...)`. Read by Codegen (see this
5
+ * package's own `codegenConfig` in package.json) to generate the native
6
+ * glue that ../android/src/main/java/com/kbachnative/KbachModule.kt
7
+ * implements. Lives in this same package (not the consuming app) alongside
8
+ * the native Android code it describes — Android autolinking only
9
+ * discovers native modules from npm dependencies with their own `android/`
10
+ * folder, not from native code declared directly inside an app, which is
11
+ * why `@kbach/react-native` ships its own `android/` + `specs/` at all
12
+ * (see Phase 13) rather than relying on the consuming app to provide one.
13
+ *
14
+ * Method names have no `Sync` suffix (unlike the pre-Phase-13 bridge's
15
+ * `generateCssSync`/`resolveStyleSync`) — every TurboModule call is
16
+ * synchronous by nature via JSI, so the suffix's whole point (flagging
17
+ * "this one blocks, unlike normal bridge methods") no longer applies.
18
+ */
19
+ import type { TurboModule } from 'react-native';
20
+ import { TurboModuleRegistry } from 'react-native';
21
+
22
+ export interface Spec extends TurboModule {
23
+ generateCss(classString: string, themeJson: string): string;
24
+ // `width`: a plain `number` (not the `Int32`/`Float` CodegenTypes
25
+ // aliases) is Codegen's own default float-numeric mapping — it already
26
+ // compiles to Kotlin's `Double` on the native side, matching
27
+ // KbachModule.kt's `width: Double` parameter with no extra type alias.
28
+ resolveStyle(classString: string, themeJson: string, colorScheme: string, pressed: boolean, width: number): string;
29
+ }
30
+
31
+ export default TurboModuleRegistry.getEnforcing<Spec>('KbachModule');