@kbach/react-native 1.0.0-beta.7 → 1.0.0-beta.9

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.
@@ -0,0 +1,186 @@
1
+ import {
2
+ getGlobalDarkMode,
3
+ getGlobalThemeMode,
4
+ seedDefaultMode,
5
+ setGlobalThemeMode,
6
+ subscribeGlobalDarkMode,
7
+ toggleGlobalDarkMode
8
+ } from "./chunk-Z664AKW7.mjs";
9
+ import {
10
+ defaultTheme,
11
+ getTheme,
12
+ setTheme
13
+ } from "./chunk-HK6PDJJK.mjs";
14
+
15
+ // src/config.ts
16
+ function parseHexRgb(hex) {
17
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
18
+ if (!m) return null;
19
+ let h = m[1];
20
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
21
+ const n = parseInt(h, 16);
22
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
23
+ }
24
+ function applyOpacityToHex(hex, opacityText) {
25
+ const rgb = parseHexRgb(hex);
26
+ if (!rgb) return hex;
27
+ const a = Number(opacityText) / 100;
28
+ const clamped = Number.isFinite(a) ? Math.min(1, Math.max(0, a)) : 1;
29
+ return `rgba(${rgb.join(",")},${clamped})`;
30
+ }
31
+ function resolveColorEntry(value, lookup) {
32
+ const slash = value.indexOf("/");
33
+ const name = slash === -1 ? value : value.slice(0, slash);
34
+ const opacity = slash === -1 ? void 0 : value.slice(slash + 1);
35
+ const base = lookup(name);
36
+ if (base === void 0) return value;
37
+ return opacity === void 0 ? base : applyOpacityToHex(base, opacity);
38
+ }
39
+ function resolveColors(extend, existing) {
40
+ const resolved = {};
41
+ const lookup = (name) => {
42
+ const found = resolved[name] ?? existing[name];
43
+ return typeof found === "string" ? found : void 0;
44
+ };
45
+ for (const [name, value] of Object.entries(extend)) {
46
+ resolved[name] = resolveColorEntry(value, lookup);
47
+ }
48
+ return resolved;
49
+ }
50
+ function resolveKbachConfig(config) {
51
+ let theme = { ...defaultTheme, ...config.theme };
52
+ if (config.extend?.colors) {
53
+ theme = { ...theme, colors: { ...theme.colors, ...resolveColors(config.extend.colors, theme.colors) } };
54
+ }
55
+ if (config.extend?.spacing) {
56
+ theme = { ...theme, spacing: { ...theme.spacing, ...config.extend.spacing } };
57
+ }
58
+ if (config.extend?.screens) {
59
+ theme = { ...theme, screens: { ...theme.screens, ...config.extend.screens } };
60
+ }
61
+ if (config.extend?.fontFamily) {
62
+ theme = { ...theme, fontFamily: { ...theme.fontFamily, ...config.extend.fontFamily } };
63
+ }
64
+ if (config.extend?.container) {
65
+ theme = { ...theme, container: { ...theme.container, ...config.extend.container } };
66
+ }
67
+ if (config.darkMode) {
68
+ theme = { ...theme, darkMode: config.darkMode };
69
+ }
70
+ return theme;
71
+ }
72
+ function applyKbachConfig(config) {
73
+ const theme = resolveKbachConfig(config);
74
+ setTheme(theme);
75
+ return theme;
76
+ }
77
+
78
+ // src/useTheme.ts
79
+ import { useMemo, useSyncExternalStore } from "react";
80
+ function useTheme() {
81
+ const isDark = useSyncExternalStore(subscribeGlobalDarkMode, getGlobalDarkMode);
82
+ const mode = useSyncExternalStore(subscribeGlobalDarkMode, getGlobalThemeMode);
83
+ return useMemo(
84
+ () => ({ mode, isDark, setMode: setGlobalThemeMode, toggle: toggleGlobalDarkMode }),
85
+ [mode, isDark]
86
+ );
87
+ }
88
+
89
+ // src/ThemeProvider.tsx
90
+ import { useEffect, useRef } from "react";
91
+ import { Fragment, jsx } from "react/jsx-runtime";
92
+ var mountedProviderCount = 0;
93
+ var warnedMultipleProviders = false;
94
+ function ThemeProvider({ children, defaultMode = "system" }) {
95
+ useEffect(() => {
96
+ mountedProviderCount++;
97
+ if (mountedProviderCount > 1 && !warnedMultipleProviders) {
98
+ warnedMultipleProviders = true;
99
+ console.warn(
100
+ "[kbach] Multiple <ThemeProvider> instances are mounted at once. Dark-mode state is one global store useTheme() reads everywhere, not scoped per-provider, so their defaultMode seeds will fight over the same state."
101
+ );
102
+ }
103
+ return () => {
104
+ mountedProviderCount--;
105
+ };
106
+ }, []);
107
+ const seeded = useRef(false);
108
+ useEffect(() => {
109
+ if (seeded.current) return;
110
+ seeded.current = true;
111
+ seedDefaultMode(defaultMode);
112
+ }, []);
113
+ return /* @__PURE__ */ jsx(Fragment, { children });
114
+ }
115
+
116
+ // src/useColors.ts
117
+ function parseHexRgb2(hex) {
118
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
119
+ if (!m) return null;
120
+ let h = m[1];
121
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
122
+ const n = parseInt(h, 16);
123
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
124
+ }
125
+ function applyOpacity(color, opacity) {
126
+ const a = Math.max(0, Math.min(1, opacity / 100));
127
+ if (color.startsWith("#")) {
128
+ const rgb = parseHexRgb2(color);
129
+ return rgb ? `rgba(${rgb.join(",")},${a})` : color;
130
+ }
131
+ if (color.startsWith("rgb(")) return color.replace("rgb(", "rgba(").replace(")", `,${a})`);
132
+ if (color.startsWith("rgba(")) return color.replace(/,\s*[\d.]+\)$/, `,${a})`);
133
+ return color;
134
+ }
135
+ function resolveEntry(entry, isDark) {
136
+ return typeof entry === "string" ? entry : isDark ? entry.dark : entry.light;
137
+ }
138
+ function makeColorsProxy(theme, isDark) {
139
+ const alpha = (color, opacity) => opacity === void 0 ? color : applyOpacity(color, opacity);
140
+ const cache = /* @__PURE__ */ new Map();
141
+ function resolveNamed(key) {
142
+ let name = key;
143
+ let opacity;
144
+ if (key.includes("/")) {
145
+ const slash = key.indexOf("/");
146
+ name = key.slice(0, slash);
147
+ opacity = Number(key.slice(slash + 1));
148
+ }
149
+ const entry = theme.colors[name];
150
+ if (entry === void 0) return void 0;
151
+ const value = resolveEntry(entry, isDark);
152
+ return opacity === void 0 ? value : applyOpacity(value, opacity);
153
+ }
154
+ function makeFamilyProxy(family) {
155
+ return new Proxy({}, {
156
+ get(_, prop) {
157
+ const key = String(prop);
158
+ if (key === "then") return void 0;
159
+ return resolveNamed(`${family}-${key}`);
160
+ }
161
+ });
162
+ }
163
+ return new Proxy({ alpha }, {
164
+ get(_, prop) {
165
+ const key = String(prop);
166
+ if (key === "then") return void 0;
167
+ if (key === "alpha") return alpha;
168
+ const direct = resolveNamed(key);
169
+ if (direct !== void 0) return direct;
170
+ if (!cache.has(key)) cache.set(key, makeFamilyProxy(key));
171
+ return cache.get(key);
172
+ }
173
+ });
174
+ }
175
+ function useColors() {
176
+ const { isDark } = useTheme();
177
+ return makeColorsProxy(getTheme(), isDark);
178
+ }
179
+
180
+ export {
181
+ resolveKbachConfig,
182
+ applyKbachConfig,
183
+ useTheme,
184
+ ThemeProvider,
185
+ useColors
186
+ };
@@ -0,0 +1,40 @@
1
+ import {
2
+ resolveClassName
3
+ } from "./chunk-H5Z24XX5.mjs";
4
+
5
+ // src/jsxRuntimeCoreWeb.ts
6
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import { Fragment } from "react";
8
+ function makeElement(isStaticChildren, type, props, key) {
9
+ return (isStaticChildren ? _jsxs : _jsx)(type, props, key);
10
+ }
11
+ function processElement(resolveClassName2, type, rawProps, key, isStaticChildren) {
12
+ if (type === null || typeof type === "symbol" || !rawProps) {
13
+ return makeElement(isStaticChildren, type, rawProps ?? {}, key);
14
+ }
15
+ const { className: classStrRaw, dataSet: userDataSet, ...rest } = rawProps;
16
+ if (typeof classStrRaw !== "string") {
17
+ return makeElement(isStaticChildren, type, rawProps, key);
18
+ }
19
+ const resolved = resolveClassName2(classStrRaw);
20
+ const dataSet = { ...userDataSet, kb: resolved };
21
+ return makeElement(isStaticChildren, type, { ...rest, dataSet }, key);
22
+ }
23
+ function createJsxFunctionsWeb(resolveClassName2) {
24
+ function jsx2(type, props, key) {
25
+ return processElement(resolveClassName2, type, props, key, false);
26
+ }
27
+ function jsxs2(type, props, key) {
28
+ return processElement(resolveClassName2, type, props, key, true);
29
+ }
30
+ return { jsx: jsx2, jsxs: jsxs2 };
31
+ }
32
+
33
+ // src/jsx-runtime.web.tsx
34
+ var { jsx, jsxs } = createJsxFunctionsWeb(resolveClassName);
35
+
36
+ export {
37
+ Fragment,
38
+ jsx,
39
+ jsxs
40
+ };
@@ -0,0 +1,54 @@
1
+ // src/darkModeStore.ts
2
+ import { Appearance } from "react-native";
3
+ var listeners = /* @__PURE__ */ new Set();
4
+ function systemPrefersDark() {
5
+ return Appearance.getColorScheme() === "dark";
6
+ }
7
+ function resolveIsDark(m) {
8
+ return m === "system" ? systemPrefersDark() : m === "dark";
9
+ }
10
+ var mode = "system";
11
+ var isDark = resolveIsDark(mode);
12
+ var hasExplicitChoice = false;
13
+ var subscription = Appearance.addChangeListener(({ colorScheme }) => {
14
+ if (mode !== "system") return;
15
+ isDark = colorScheme === "dark";
16
+ notify();
17
+ });
18
+ function notify() {
19
+ for (const listener of listeners) listener();
20
+ }
21
+ function getGlobalDarkMode() {
22
+ return isDark;
23
+ }
24
+ function getGlobalThemeMode() {
25
+ return mode;
26
+ }
27
+ function setGlobalThemeMode(nextMode) {
28
+ mode = nextMode;
29
+ isDark = resolveIsDark(nextMode);
30
+ hasExplicitChoice = true;
31
+ notify();
32
+ }
33
+ function toggleGlobalDarkMode() {
34
+ setGlobalThemeMode(isDark ? "light" : "dark");
35
+ }
36
+ function subscribeGlobalDarkMode(listener) {
37
+ listeners.add(listener);
38
+ return () => listeners.delete(listener);
39
+ }
40
+ function seedDefaultMode(fallback) {
41
+ if (hasExplicitChoice || mode === fallback) return;
42
+ mode = fallback;
43
+ isDark = resolveIsDark(fallback);
44
+ notify();
45
+ }
46
+
47
+ export {
48
+ getGlobalDarkMode,
49
+ getGlobalThemeMode,
50
+ setGlobalThemeMode,
51
+ toggleGlobalDarkMode,
52
+ subscribeGlobalDarkMode,
53
+ seedDefaultMode
54
+ };
package/dist/index.d.mts CHANGED
@@ -1,19 +1,12 @@
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;
1
+ export { C as ColorEntry, a as ColorScale, b as ColorsAPI, c as ContainerConfig, K as KbachConfig, T as ThemeConfig, d as ThemeMode, e as ThemeProvider, f as ThemeProviderProps, g as ThemeState, h as applyKbachConfig, i as defaultTheme, j as getGlobalDarkMode, k as getGlobalThemeMode, l as getTheme, r as resolveKbachConfig, s as setGlobalThemeMode, m as setTheme, n as subscribeGlobalDarkMode, t as toggleGlobalDarkMode, u as useColors, o as useTheme } from './useColors-CeP5_HvM.mjs';
2
+ import 'react';
14
3
 
4
+ type StyleValue = string | number | {
5
+ width: number;
6
+ height: number;
7
+ };
15
8
  interface StyleObject {
16
- [key: string]: string | number;
9
+ [key: string]: StyleValue;
17
10
  }
18
11
  /**
19
12
  * Calls the Rust engine via the KbachModule TurboModule (see
@@ -32,10 +25,9 @@ interface StyleObject {
32
25
  * whenever the mode is `'system'` (its own default), so nothing regresses
33
26
  * for apps that never touch the override API at all. This function itself
34
27
  * 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.
28
+ * store's value changed — call `useTheme()` (its `isDark`) somewhere in
29
+ * the tree for that, NOT React Native's own `useColorScheme()`, which only
30
+ * reflects the raw OS setting and would miss an explicit override entirely.
39
31
  *
40
32
  * Phase 11 originally shipped with live in-place theme switching unverified
41
33
  * (a `uiMode` toggle triggered a re-render but `Appearance.getColorScheme()`
@@ -72,90 +64,20 @@ interface StyleObject {
72
64
  * re-render anything by itself — call RN's own `useWindowDimensions()`
73
65
  * somewhere in the tree for that (same role `useColorScheme()` plays for
74
66
  * `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
67
  *
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.
68
+ * Falls back to `./jsEngine` (a pure-JS, no-WASM/no-native reimplementation
69
+ * of this same resolution logic — see that directory's own doc comments)
70
+ * when `KbachModule` isn't registered at all — Expo Go, which can never
71
+ * load ANY custom native module (not even Expo's own Modules API), so the
72
+ * TurboModule this file otherwise depends on simply doesn't exist there.
73
+ * `TurboModuleRegistry.get` (not `.getEnforcing`, which throws) is what
74
+ * makes this detectable at all — `get` returns `null`/`undefined` instead.
75
+ * A real dev-client/CLI build always has the module registered, so this
76
+ * fallback only ever activates in Expo Go itself; everywhere else behaves
77
+ * exactly as before. The JS engine covers a strict subset of what the
78
+ * native/WASM engines resolve (see jsEngine/resolveUtilityNative.ts) — an
79
+ * intentional, documented parity gap, not a bug.
118
80
  */
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;
81
+ declare function resolveStyle(classString: string, pressed?: boolean): StyleObject;
160
82
 
161
- export { type StyleObject, type ThemeConfig, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, defaultTheme, getGlobalDarkMode, getGlobalThemeMode, getTheme, resolveStyle, setGlobalThemeMode, setTheme, subscribeGlobalDarkMode, toggleGlobalDarkMode, useGlobalDarkMode, useTheme };
83
+ export { type StyleObject, resolveStyle };
package/dist/index.d.ts CHANGED
@@ -1,19 +1,12 @@
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;
1
+ export { C as ColorEntry, a as ColorScale, b as ColorsAPI, c as ContainerConfig, K as KbachConfig, T as ThemeConfig, d as ThemeMode, e as ThemeProvider, f as ThemeProviderProps, g as ThemeState, h as applyKbachConfig, i as defaultTheme, j as getGlobalDarkMode, k as getGlobalThemeMode, l as getTheme, r as resolveKbachConfig, s as setGlobalThemeMode, m as setTheme, n as subscribeGlobalDarkMode, t as toggleGlobalDarkMode, u as useColors, o as useTheme } from './useColors-CeP5_HvM.js';
2
+ import 'react';
14
3
 
4
+ type StyleValue = string | number | {
5
+ width: number;
6
+ height: number;
7
+ };
15
8
  interface StyleObject {
16
- [key: string]: string | number;
9
+ [key: string]: StyleValue;
17
10
  }
18
11
  /**
19
12
  * Calls the Rust engine via the KbachModule TurboModule (see
@@ -32,10 +25,9 @@ interface StyleObject {
32
25
  * whenever the mode is `'system'` (its own default), so nothing regresses
33
26
  * for apps that never touch the override API at all. This function itself
34
27
  * 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.
28
+ * store's value changed — call `useTheme()` (its `isDark`) somewhere in
29
+ * the tree for that, NOT React Native's own `useColorScheme()`, which only
30
+ * reflects the raw OS setting and would miss an explicit override entirely.
39
31
  *
40
32
  * Phase 11 originally shipped with live in-place theme switching unverified
41
33
  * (a `uiMode` toggle triggered a re-render but `Appearance.getColorScheme()`
@@ -72,90 +64,20 @@ interface StyleObject {
72
64
  * re-render anything by itself — call RN's own `useWindowDimensions()`
73
65
  * somewhere in the tree for that (same role `useColorScheme()` plays for
74
66
  * `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
67
  *
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.
68
+ * Falls back to `./jsEngine` (a pure-JS, no-WASM/no-native reimplementation
69
+ * of this same resolution logic — see that directory's own doc comments)
70
+ * when `KbachModule` isn't registered at all — Expo Go, which can never
71
+ * load ANY custom native module (not even Expo's own Modules API), so the
72
+ * TurboModule this file otherwise depends on simply doesn't exist there.
73
+ * `TurboModuleRegistry.get` (not `.getEnforcing`, which throws) is what
74
+ * makes this detectable at all — `get` returns `null`/`undefined` instead.
75
+ * A real dev-client/CLI build always has the module registered, so this
76
+ * fallback only ever activates in Expo Go itself; everywhere else behaves
77
+ * exactly as before. The JS engine covers a strict subset of what the
78
+ * native/WASM engines resolve (see jsEngine/resolveUtilityNative.ts) — an
79
+ * intentional, documented parity gap, not a bug.
118
80
  */
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;
81
+ declare function resolveStyle(classString: string, pressed?: boolean): StyleObject;
160
82
 
161
- export { type StyleObject, type ThemeConfig, type ThemeContextValue, type ThemeMode, ThemeProvider, type ThemeProviderProps, defaultTheme, getGlobalDarkMode, getGlobalThemeMode, getTheme, resolveStyle, setGlobalThemeMode, setTheme, subscribeGlobalDarkMode, toggleGlobalDarkMode, useGlobalDarkMode, useTheme };
83
+ export { type StyleObject, resolveStyle };