@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.
@@ -0,0 +1,239 @@
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
+ /**
61
+ * `resolveKbachConfig()` + `setTheme()` in one call — the usual entry point
62
+ * from app code. Also re-syncs the DOM (Expo Web only) to the CURRENT
63
+ * dark-mode state right after `setTheme()` — see
64
+ * `resyncDomWithActiveTheme`'s own doc comment for why this is required,
65
+ * not optional.
66
+ */
67
+ declare function applyKbachConfig(config: KbachConfig): ThemeConfig;
68
+
69
+ interface ThemeState {
70
+ mode: ThemeMode;
71
+ isDark: boolean;
72
+ setMode: (mode: ThemeMode) => void;
73
+ toggle: () => void;
74
+ }
75
+ /**
76
+ * The one hook for reading and writing Kbach's dark-mode state. Reads
77
+ * `darkModeStore.ts`'s global store directly via `useSyncExternalStore` —
78
+ * that module-level store, not React Context, is the actual source of
79
+ * truth (every provider used to just mirror it into a Context value), so
80
+ * this works from ANY component, with or without a `<ThemeProvider>`
81
+ * mounted anywhere in the tree. `<ThemeProvider>` still exists, but purely
82
+ * as an optional convenience for seeding an app's initial `defaultMode` —
83
+ * see its own doc comment. No server-snapshot argument (unlike
84
+ * @kbach/react's web version) — React Native has no SSR concept at all,
85
+ * so there's no third argument for `useSyncExternalStore` to need.
86
+ */
87
+ declare function useTheme(): ThemeState;
88
+
89
+ /**
90
+ * The exact shape `@react-native-async-storage/async-storage`'s default
91
+ * export already has — pass it directly as `persist`. Any other async
92
+ * key-value store (MMKV, SecureStore, a custom backend, ...) works too, as
93
+ * long as it exposes these two methods with this signature. Kept minimal
94
+ * on purpose: `ThemeProvider` never imports a storage library itself (see
95
+ * `darkModeStore.ts`'s own doc comment on why this package forces no real
96
+ * native dependency on consumers who don't want persistence) — deliberately
97
+ * NOT a `boolean`-only option that reaches for AsyncStorage internally,
98
+ * since Metro resolves every `require()`/`import` target at BUNDLE time
99
+ * regardless of try/catch, so a library-internal "if installed, use it"
100
+ * check can't actually degrade gracefully when the package is absent — an
101
+ * injected adapter sidesteps that entirely.
102
+ */
103
+ interface ThemeStorageAdapter {
104
+ getItem(key: string): Promise<string | null>;
105
+ setItem(key: string, value: string): Promise<void>;
106
+ }
107
+ interface ThemeProviderProps {
108
+ children: ReactNode;
109
+ /**
110
+ * Applied once, on this provider's first mount, but ONLY if nothing has
111
+ * made an explicit mode choice yet (see
112
+ * `darkModeStore.ts::seedDefaultMode`) — an explicit prior choice always
113
+ * wins over a component's own default. If `persist` is also set and a
114
+ * persisted mode is found, the persisted mode wins over this fallback.
115
+ */
116
+ defaultMode?: ThemeMode;
117
+ /**
118
+ * Opt-in persistence across app launches — `false` by default (this
119
+ * package never persists on its own; see `darkModeStore.ts`'s doc
120
+ * comment). Pass a storage adapter to enable it:
121
+ * ```tsx
122
+ * import AsyncStorage from '@react-native-async-storage/async-storage';
123
+ * <ThemeProvider persist={AsyncStorage}>...
124
+ * ```
125
+ * The persisted value (if any) is read once on mount and applied via the
126
+ * same "don't clobber a real choice already in flight" guard
127
+ * `seedDefaultMode`/`defaultMode` uses; every later explicit mode change
128
+ * is written back automatically for as long as this provider stays
129
+ * mounted.
130
+ */
131
+ persist?: ThemeStorageAdapter | false;
132
+ }
133
+ /**
134
+ * Optional — `useTheme()` reads `darkModeStore.ts`'s global store directly
135
+ * and works anywhere without this mounted at all. The only thing this
136
+ * component does is seed that store's initial `defaultMode` once, for apps
137
+ * that want to declare a startup default in JSX rather than calling
138
+ * `setGlobalThemeMode()` imperatively before their first render. It
139
+ * renders `children` as-is — no Context.Provider, since there's no value
140
+ * left to distribute; `useTheme()` never reads from Context.
141
+ *
142
+ * Because the store is global, MULTIPLE mounted providers would each try
143
+ * to seed the exact same store rather than getting their own scoped
144
+ * state — mounting more than one is very likely a mistake, so this warns
145
+ * once in dev.
146
+ */
147
+ declare function ThemeProvider({ children, defaultMode, persist }: ThemeProviderProps): react.JSX.Element;
148
+
149
+ interface ColorScale {
150
+ /** `colors.blue[6]` -> raw value */
151
+ readonly [shade: number]: string;
152
+ /** `colors.blue['6/50']` -> shade 6 at 50% opacity */
153
+ readonly [key: string]: string;
154
+ }
155
+ type ColorsAPI = {
156
+ /** `colors.white`, `colors.blue` (a shade family), ... */
157
+ readonly [name: string]: string | ColorScale;
158
+ } & {
159
+ /** Applies an opacity (0–100) to any color string this hook can return. */
160
+ readonly alpha: (color: string, opacity?: number) => string;
161
+ };
162
+ /**
163
+ * Resolved theme colors as real values — for anywhere you need an actual
164
+ * color (a chart library prop, a raw SVG `fill`), not a `className`. Ported
165
+ * from old-kbach's `useColors()`, adapted to this engine's flat
166
+ * `"blue-6"`-keyed palette (`colors.blue[6]` looks up `"blue-6"`).
167
+ *
168
+ * Any color — a shade-family entry like `"blue-6"` or a standalone name —
169
+ * can be defined in the theme as either a plain string (static, same in
170
+ * both modes) or a `{ light, dark }` pair — fully manual, no automatic
171
+ * shade-shifting or other assumption about what a "dark variant" should
172
+ * be; see `resolveEntry`'s own doc comment for how a `{ light, dark }`
173
+ * entry resolves on this platform specifically.
174
+ *
175
+ * ```ts
176
+ * const colors = useColors();
177
+ * colors.blue[6]; // "#2563eb" — static, same value in both modes
178
+ * colors.blue['6/50']; // same, at 50% opacity
179
+ * colors.brand; // resolves per isDark if defined as { light, dark }
180
+ * ```
181
+ */
182
+ declare function useColors(): ColorsAPI;
183
+
184
+ /**
185
+ * Reads a dynamic token's CURRENT value reactively — the JS-value
186
+ * counterpart to using `var(--name)` directly in a `className` (see
187
+ * `jsxRuntimeCore.ts`'s own `substituteDynamicTokens`/`ReactiveElement` for
188
+ * how that path stays live). Useful when a component needs the raw value
189
+ * itself (e.g. to drive an Animated value, or a calculation), not just a
190
+ * resolved style. Subscribes to the store's shared version counter (same
191
+ * one every token change bumps — see `dynamicTokens.ts`'s own doc comment
192
+ * on why there's no per-token subscription), so this re-renders on ANY
193
+ * token change, then re-reads just the one name it was asked for.
194
+ */
195
+ declare function useDynamicToken(name: string): string | undefined;
196
+
197
+ /**
198
+ * A tiny class-name composer — combine strings, arrays, and objects (with
199
+ * boolean values) into one space-separated className, skipping anything
200
+ * falsy. Matches the widely-used `clsx` npm package's public API and
201
+ * behavior exactly (a from-scratch reimplementation, not a fork or a thin
202
+ * wrapper around it) — this package ships its own copy so using it adds
203
+ * zero runtime dependencies, and so it works identically whether or not
204
+ * `clsx`/`classnames` happens to already be installed.
205
+ *
206
+ * Kbach class strings never NEED this — a Kbach `className` is just a
207
+ * plain string, composable with plain JS (`"bg-" + color + "-6"`,
208
+ * `[base, active && "opacity-100"].join(" ")`, see this package's own
209
+ * README) with no special API required. This exists purely as an
210
+ * ergonomic convenience for the common "several conditional classes on one
211
+ * element" shape, for anyone who'd rather write it declaratively:
212
+ *
213
+ * ```ts
214
+ * clsx("px-4 py-2", isActive && "bg-blue-6", { "opacity-50": disabled });
215
+ * // "px-4 py-2 bg-blue-6" (when isActive is true and disabled is false)
216
+ * ```
217
+ *
218
+ * Identical to `@kbach/react`'s own `clsx.ts` — deliberately duplicated
219
+ * rather than factored into a shared package, since this is genuinely all
220
+ * there is to it and a whole extra published package for one ~20-line
221
+ * function would be more ceremony than the thing itself.
222
+ */
223
+ /** Anything `clsx` accepts as one argument — recursively, for arrays. */
224
+ type ClassValue = ClassArray | ClassDictionary | string | number | bigint | boolean | null | undefined;
225
+ /** A key is kept (as a literal class name) only when its value is truthy — `{ "opacity-50": disabled }`. */
226
+ interface ClassDictionary {
227
+ [className: string]: unknown;
228
+ }
229
+ type ClassArray = ClassValue[];
230
+ /**
231
+ * Joins every truthy piece across all arguments with a single space,
232
+ * skipping falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`)
233
+ * entirely — they contribute nothing, not even an extra space. Arrays are
234
+ * flattened (recursively, to any depth); a plain object keeps only the
235
+ * keys whose value is truthy.
236
+ */
237
+ declare function clsx(...inputs: ClassValue[]): string;
238
+
239
+ export { type ClassArray as C, type KbachConfig as K, ThemeProvider as T, type ClassDictionary as a, type ClassValue as b, type ColorScale as c, type ColorsAPI as d, type ThemeProviderProps as e, type ThemeState as f, type ThemeStorageAdapter as g, applyKbachConfig as h, clsx as i, useDynamicToken as j, useTheme as k, resolveKbachConfig as r, useColors as u };
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Native port of @kbach/react's darkModeStore.ts — same architecture (a
3
+ * single module-level store is the real source of truth;
4
+ * ThemeProvider/useTheme are a thin, OPTIONAL wrapper around it; every
5
+ * function here works standalone with no provider mounted anywhere), with
6
+ * two real platform differences from the web version:
7
+ *
8
+ * 1. No persistence. There's no React-Native-builtin equivalent of
9
+ * localStorage — adding one would mean forcing a real native dependency
10
+ * (e.g. @react-native-async-storage/async-storage) on every consumer,
11
+ * even ones that don't want it. The mode always starts at 'system' on a
12
+ * fresh app launch. If you want persistence, read your own storage at
13
+ * startup (before your first render) and call setGlobalThemeMode() with
14
+ * the result — same "seed once, no built-in write-back" shape as
15
+ * seedDefaultMode() below, just driven by your own storage instead of
16
+ * one this package picks for you.
17
+ * 2. `applyToDom` below IS needed here, unlike what an earlier version of
18
+ * this file's own comment claimed ("no DOM to apply the resolved mode
19
+ * to") — that reasoning only holds for real native (Android/iOS), where
20
+ * nativeBridge.ts's resolveStyle() reads getGlobalDarkMode() directly on
21
+ * every call, no DOM involved at all. This file is ALSO used by Expo
22
+ * Web, though (no `.web.ts` sibling — Metro has nothing to swap in), and
23
+ * Expo Web resolves `dark:` classes to REAL CSS with `[data-theme=
24
+ * "dark"]`-style selectors (see nativeBridge.web.ts) that only ever
25
+ * match if something writes that attribute to the DOM — nothing did,
26
+ * which is exactly why toggling the mode changed `useTheme()`'s own
27
+ * state (a consumer reading `mode`/`isDark` directly, e.g. for a label,
28
+ * updated correctly) while every `dark:`-prefixed style silently never
29
+ * applied. `typeof document === 'undefined'` is what makes one function
30
+ * safe on both platforms: real native's Hermes has no `document` global
31
+ * at all (not even `undefined` as a defined-but-empty value — genuinely
32
+ * unset, which `typeof` can check without throwing), so this always
33
+ * no-ops there, while Expo Web's react-native-web renders into a real
34
+ * browser DOM where it's needed.
35
+ */
36
+ type ThemeMode = 'light' | 'dark' | 'system';
37
+ /**
38
+ * Re-applies the CURRENT `isDark` value to the DOM (Expo Web only — a
39
+ * genuine no-op on real native, same as `applyToDom` itself) under whatever
40
+ * `getTheme().darkMode` strategy is active right now, with no change to
41
+ * `mode`/`isDark` themselves, so it deliberately does NOT `notify()`.
42
+ * Mirrors @kbach/react's identical `resyncDomWithActiveTheme` — see that
43
+ * one's own doc comment for the full race it fixes: this module's initial
44
+ * `applyToDom(isDark)` call (above) runs at IMPORT time against whichever
45
+ * `darkMode` strategy `defaultTheme` had at that moment, which on Expo Web
46
+ * is BEFORE an app's own `applyKbachConfig({darkMode: 'attribute'})` call
47
+ * (in config.ts) has had a chance to switch it — without this, that config
48
+ * call would silently never get its `dark:` classes to apply on initial
49
+ * load, even though `useTheme().isDark` already reads correctly.
50
+ */
51
+ declare function resyncDomWithActiveTheme(): void;
52
+ declare function getGlobalDarkMode(): boolean;
53
+ declare function getGlobalThemeMode(): ThemeMode;
54
+ /** Explicit user choice — takes priority over any later `<ThemeProvider defaultMode>` seed (see `seedDefaultMode` below). Not persisted; see this module's own doc comment. */
55
+ declare function setGlobalThemeMode(nextMode: ThemeMode): void;
56
+ declare function toggleGlobalDarkMode(): void;
57
+ declare function subscribeGlobalDarkMode(listener: () => void): () => void;
58
+ /**
59
+ * Applies `fallback` as the initial mode, but ONLY if nothing has made an
60
+ * explicit choice yet — called once by `<ThemeProvider defaultMode>` on
61
+ * mount, never exported for direct use elsewhere. Unlike the web store,
62
+ * there's no persisted-value check here (nothing to check); the only
63
+ * thing that can beat a seed is a `setGlobalThemeMode()` call that
64
+ * happened first (e.g. your own storage-restore logic run before the
65
+ * provider mounts).
66
+ */
67
+ declare function seedDefaultMode(fallback: ThemeMode): void;
68
+ /** Exported for tests only — resets every module-level field back to its as-if-just-imported state. */
69
+ declare function _resetForTests(): void;
70
+ /** Exported for tests only — removes the Appearance change subscription so a test's mock doesn't leak into the next one. */
71
+ declare function _unsubscribeForTests(): void;
72
+
73
+ export { type ThemeMode, _resetForTests, _unsubscribeForTests, getGlobalDarkMode, getGlobalThemeMode, resyncDomWithActiveTheme, seedDefaultMode, setGlobalThemeMode, subscribeGlobalDarkMode, toggleGlobalDarkMode };
@@ -0,0 +1,118 @@
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/darkModeStore.ts
21
+ var darkModeStore_exports = {};
22
+ __export(darkModeStore_exports, {
23
+ _resetForTests: () => _resetForTests,
24
+ _unsubscribeForTests: () => _unsubscribeForTests,
25
+ getGlobalDarkMode: () => getGlobalDarkMode,
26
+ getGlobalThemeMode: () => getGlobalThemeMode,
27
+ resyncDomWithActiveTheme: () => resyncDomWithActiveTheme,
28
+ seedDefaultMode: () => seedDefaultMode,
29
+ setGlobalThemeMode: () => setGlobalThemeMode,
30
+ subscribeGlobalDarkMode: () => subscribeGlobalDarkMode,
31
+ toggleGlobalDarkMode: () => toggleGlobalDarkMode
32
+ });
33
+ module.exports = __toCommonJS(darkModeStore_exports);
34
+ var import_react_native = require("react-native");
35
+ var import_theme = require("./theme");
36
+ var listeners = /* @__PURE__ */ new Set();
37
+ function systemPrefersDark() {
38
+ return import_react_native.Appearance.getColorScheme() === "dark";
39
+ }
40
+ function resolveIsDark(m) {
41
+ return m === "system" ? systemPrefersDark() : m === "dark";
42
+ }
43
+ function applyToDom(dark) {
44
+ if (typeof document === "undefined") return;
45
+ const root = document.documentElement;
46
+ const strategy = (0, import_theme.getTheme)().darkMode;
47
+ root.classList.toggle("dark", strategy === "class" && dark);
48
+ root.classList.toggle("light", strategy === "class" && !dark);
49
+ if (strategy === "attribute") {
50
+ root.setAttribute("data-theme", dark ? "dark" : "light");
51
+ } else {
52
+ root.removeAttribute("data-theme");
53
+ }
54
+ }
55
+ var mode = "system";
56
+ var isDark = resolveIsDark(mode);
57
+ applyToDom(isDark);
58
+ var hasExplicitChoice = false;
59
+ var subscription = import_react_native.Appearance.addChangeListener(({ colorScheme }) => {
60
+ if (mode !== "system") return;
61
+ isDark = colorScheme === "dark";
62
+ applyToDom(isDark);
63
+ notify();
64
+ });
65
+ function notify() {
66
+ for (const listener of listeners) listener();
67
+ }
68
+ function resyncDomWithActiveTheme() {
69
+ applyToDom(isDark);
70
+ }
71
+ function getGlobalDarkMode() {
72
+ return isDark;
73
+ }
74
+ function getGlobalThemeMode() {
75
+ return mode;
76
+ }
77
+ function setGlobalThemeMode(nextMode) {
78
+ mode = nextMode;
79
+ isDark = resolveIsDark(nextMode);
80
+ hasExplicitChoice = true;
81
+ applyToDom(isDark);
82
+ notify();
83
+ }
84
+ function toggleGlobalDarkMode() {
85
+ setGlobalThemeMode(isDark ? "light" : "dark");
86
+ }
87
+ function subscribeGlobalDarkMode(listener) {
88
+ listeners.add(listener);
89
+ return () => listeners.delete(listener);
90
+ }
91
+ function seedDefaultMode(fallback) {
92
+ if (hasExplicitChoice || mode === fallback) return;
93
+ mode = fallback;
94
+ isDark = resolveIsDark(fallback);
95
+ applyToDom(isDark);
96
+ notify();
97
+ }
98
+ function _resetForTests() {
99
+ mode = "system";
100
+ isDark = resolveIsDark(mode);
101
+ hasExplicitChoice = false;
102
+ listeners.clear();
103
+ }
104
+ function _unsubscribeForTests() {
105
+ subscription.remove();
106
+ }
107
+ // Annotate the CommonJS export names for ESM import in node:
108
+ 0 && (module.exports = {
109
+ _resetForTests,
110
+ _unsubscribeForTests,
111
+ getGlobalDarkMode,
112
+ getGlobalThemeMode,
113
+ resyncDomWithActiveTheme,
114
+ seedDefaultMode,
115
+ setGlobalThemeMode,
116
+ subscribeGlobalDarkMode,
117
+ toggleGlobalDarkMode
118
+ });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Cross-platform runtime-settable style values — the native counterpart to
3
+ * a real CSS custom property. Real `var(--sidebar-width)` already works on
4
+ * Expo Web today with ZERO code here (raw CSS text, resolved by the
5
+ * browser's own engine — see `jsxRuntimeCoreWeb.ts`, which does no
6
+ * className rewriting at all). Native has no such thing: RN's style system
7
+ * needs an actual JS-computed value at render time, so this module exists
8
+ * to give `var(--x)` an equivalent that works there too — same
9
+ * "module-level store is the real source of truth, no Context required"
10
+ * architecture as `darkModeStore.ts`, and `setDynamicToken` ALSO writes the
11
+ * real CSS custom property to the DOM when one exists (mirroring that
12
+ * file's own `applyToDom`), so a single call updates both platforms
13
+ * identically: instantly via native CSS reactivity on web, via this
14
+ * store's subscribers (see `jsxRuntimeCore.ts`'s `ReactiveElement`, which
15
+ * substitutes a registered token's current value into the className
16
+ * string before every resolve) on native.
17
+ *
18
+ * Deliberately ONE listener set shared by every token, not per-token
19
+ * subscriptions — same trade-off `darkModeStore.ts` already makes for
20
+ * "any relevant change re-renders every subscribed element" simplicity.
21
+ * An unrelated token's change still re-renders every subscribed element
22
+ * (there's no per-token filtering), but `substituteDynamicTokens` in
23
+ * `jsxRuntimeCore.ts` re-reads each token's CURRENT value fresh on every
24
+ * one of those re-renders, so the resolved style stays correct regardless
25
+ * — the redundant re-render is a cheap, accepted trade-off, not a bug.
26
+ */
27
+ /** `name` is the bare token name, WITHOUT the leading `--` (matching how `getDynamicToken`/`var(--name)` substitution both address it). */
28
+ declare function setDynamicToken(name: string, value: string): void;
29
+ declare function getDynamicToken(name: string): string | undefined;
30
+ declare function deleteDynamicToken(name: string): void;
31
+ declare function subscribeDynamicTokens(listener: () => void): () => void;
32
+ declare function getDynamicTokensVersion(): number;
33
+ /** Exported for tests only — resets every module-level field back to its as-if-just-imported state. */
34
+ declare function _resetForTests(): void;
35
+
36
+ export { _resetForTests, deleteDynamicToken, getDynamicToken, getDynamicTokensVersion, setDynamicToken, subscribeDynamicTokens };
@@ -0,0 +1,75 @@
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/dynamicTokens.ts
21
+ var dynamicTokens_exports = {};
22
+ __export(dynamicTokens_exports, {
23
+ _resetForTests: () => _resetForTests,
24
+ deleteDynamicToken: () => deleteDynamicToken,
25
+ getDynamicToken: () => getDynamicToken,
26
+ getDynamicTokensVersion: () => getDynamicTokensVersion,
27
+ setDynamicToken: () => setDynamicToken,
28
+ subscribeDynamicTokens: () => subscribeDynamicTokens
29
+ });
30
+ module.exports = __toCommonJS(dynamicTokens_exports);
31
+ var tokens = /* @__PURE__ */ new Map();
32
+ var listeners = /* @__PURE__ */ new Set();
33
+ var version = 0;
34
+ function applyToDom(name, value) {
35
+ if (typeof document === "undefined") return;
36
+ document.documentElement.style.setProperty(`--${name}`, value);
37
+ }
38
+ function notify() {
39
+ version++;
40
+ for (const listener of listeners) listener();
41
+ }
42
+ function setDynamicToken(name, value) {
43
+ tokens.set(name, value);
44
+ applyToDom(name, value);
45
+ notify();
46
+ }
47
+ function getDynamicToken(name) {
48
+ return tokens.get(name);
49
+ }
50
+ function deleteDynamicToken(name) {
51
+ if (!tokens.delete(name)) return;
52
+ if (typeof document !== "undefined") document.documentElement.style.removeProperty(`--${name}`);
53
+ notify();
54
+ }
55
+ function subscribeDynamicTokens(listener) {
56
+ listeners.add(listener);
57
+ return () => listeners.delete(listener);
58
+ }
59
+ function getDynamicTokensVersion() {
60
+ return version;
61
+ }
62
+ function _resetForTests() {
63
+ tokens.clear();
64
+ listeners.clear();
65
+ version = 0;
66
+ }
67
+ // Annotate the CommonJS export names for ESM import in node:
68
+ 0 && (module.exports = {
69
+ _resetForTests,
70
+ deleteDynamicToken,
71
+ getDynamicToken,
72
+ getDynamicTokensVersion,
73
+ setDynamicToken,
74
+ subscribeDynamicTokens
75
+ });