@sigx/lynx-zero 0.4.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.
Files changed (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/dist/components/SwiperIndicator.d.ts +43 -0
  4. package/dist/components/SwiperIndicator.d.ts.map +1 -0
  5. package/dist/components/SwiperIndicator.js +272 -0
  6. package/dist/components/SwiperIndicator.js.map +1 -0
  7. package/dist/contract.d.ts +95 -0
  8. package/dist/contract.d.ts.map +1 -0
  9. package/dist/contract.js +30 -0
  10. package/dist/contract.js.map +1 -0
  11. package/dist/index.d.ts +30 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +41 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/layout/Center.d.ts +7 -0
  16. package/dist/layout/Center.d.ts.map +1 -0
  17. package/dist/layout/Center.js +25 -0
  18. package/dist/layout/Center.js.map +1 -0
  19. package/dist/layout/Col.d.ts +8 -0
  20. package/dist/layout/Col.d.ts.map +1 -0
  21. package/dist/layout/Col.js +34 -0
  22. package/dist/layout/Col.js.map +1 -0
  23. package/dist/layout/Row.d.ts +8 -0
  24. package/dist/layout/Row.d.ts.map +1 -0
  25. package/dist/layout/Row.js +34 -0
  26. package/dist/layout/Row.js.map +1 -0
  27. package/dist/layout/ScrollView.d.ts +6 -0
  28. package/dist/layout/ScrollView.d.ts.map +1 -0
  29. package/dist/layout/ScrollView.js +19 -0
  30. package/dist/layout/ScrollView.js.map +1 -0
  31. package/dist/layout/Spacer.d.ts +4 -0
  32. package/dist/layout/Spacer.d.ts.map +1 -0
  33. package/dist/layout/Spacer.js +12 -0
  34. package/dist/layout/Spacer.js.map +1 -0
  35. package/dist/preset/index.d.ts +31 -0
  36. package/dist/preset/index.d.ts.map +1 -0
  37. package/dist/preset/index.js +72 -0
  38. package/dist/preset/index.js.map +1 -0
  39. package/dist/shared/press.d.ts +3 -0
  40. package/dist/shared/press.d.ts.map +1 -0
  41. package/dist/shared/press.js +7 -0
  42. package/dist/shared/press.js.map +1 -0
  43. package/dist/shared/styles.d.ts +27 -0
  44. package/dist/shared/styles.d.ts.map +1 -0
  45. package/dist/shared/styles.js +62 -0
  46. package/dist/shared/styles.js.map +1 -0
  47. package/dist/shared/tabs-selection.d.ts +25 -0
  48. package/dist/shared/tabs-selection.d.ts.map +1 -0
  49. package/dist/shared/tabs-selection.js +45 -0
  50. package/dist/shared/tabs-selection.js.map +1 -0
  51. package/dist/styles/tokens.css +98 -0
  52. package/dist/theme/StatusBarSync.d.ts +42 -0
  53. package/dist/theme/StatusBarSync.d.ts.map +1 -0
  54. package/dist/theme/StatusBarSync.js +89 -0
  55. package/dist/theme/StatusBarSync.js.map +1 -0
  56. package/dist/theme/ThemeProvider.d.ts +144 -0
  57. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  58. package/dist/theme/ThemeProvider.js +328 -0
  59. package/dist/theme/ThemeProvider.js.map +1 -0
  60. package/dist/theme/color-mix.d.ts +21 -0
  61. package/dist/theme/color-mix.d.ts.map +1 -0
  62. package/dist/theme/color-mix.js +65 -0
  63. package/dist/theme/color-mix.js.map +1 -0
  64. package/dist/theme/registry.d.ts +182 -0
  65. package/dist/theme/registry.d.ts.map +1 -0
  66. package/dist/theme/registry.js +182 -0
  67. package/dist/theme/registry.js.map +1 -0
  68. package/dist/theme/theme-state.d.ts +43 -0
  69. package/dist/theme/theme-state.d.ts.map +1 -0
  70. package/dist/theme/theme-state.js +94 -0
  71. package/dist/theme/theme-state.js.map +1 -0
  72. package/dist/theme/use-screen-theme.d.ts +4 -0
  73. package/dist/theme/use-screen-theme.d.ts.map +1 -0
  74. package/dist/theme/use-screen-theme.js +43 -0
  75. package/dist/theme/use-screen-theme.js.map +1 -0
  76. package/dist/theme/use-theme-colors.d.ts +48 -0
  77. package/dist/theme/use-theme-colors.d.ts.map +1 -0
  78. package/dist/theme/use-theme-colors.js +69 -0
  79. package/dist/theme/use-theme-colors.js.map +1 -0
  80. package/package.json +80 -0
  81. package/src/components/SwiperIndicator.tsx +519 -0
  82. package/src/contract.ts +136 -0
  83. package/src/index.ts +101 -0
  84. package/src/layout/Center.tsx +41 -0
  85. package/src/layout/Col.tsx +53 -0
  86. package/src/layout/Row.tsx +53 -0
  87. package/src/layout/ScrollView.tsx +38 -0
  88. package/src/layout/Spacer.tsx +18 -0
  89. package/src/preset/index.ts +77 -0
  90. package/src/shared/press.ts +6 -0
  91. package/src/shared/styles.ts +82 -0
  92. package/src/shared/tabs-selection.ts +57 -0
  93. package/src/styles/tokens.css +98 -0
  94. package/src/theme/StatusBarSync.tsx +104 -0
  95. package/src/theme/ThemeProvider.tsx +492 -0
  96. package/src/theme/color-mix.ts +68 -0
  97. package/src/theme/registry.ts +290 -0
  98. package/src/theme/theme-state.ts +112 -0
  99. package/src/theme/use-screen-theme.ts +42 -0
  100. package/src/theme/use-theme-colors.ts +99 -0
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Engine-side color mixing.
3
+ *
4
+ * Lynx's CSS engine has no `color-mix()` and can't alpha-compose `var()`
5
+ * colors — but theme palettes are plain JS data, so tints can be computed
6
+ * where the palette lives instead of in CSS. `registerTheme()` uses this to
7
+ * materialize the `*-soft` tokens (see `./registry.ts`).
8
+ *
9
+ * Accepted inputs are the same "engine-safe" color strings themes already
10
+ * use: `#rgb`, `#rrggbb`, `#rrggbbaa`, `rgb(r, g, b)`, `rgba(r, g, b, a)`.
11
+ * Anything else (named colors, `oklch()`, `var()`) is not parseable here —
12
+ * `mixColors` then falls back to the base color unchanged, which degrades to
13
+ * a neutral surface rather than a wrong tint.
14
+ */
15
+ function parseColor(value) {
16
+ const v = value.trim();
17
+ if (v.startsWith('#')) {
18
+ const hex = v.slice(1);
19
+ if (hex.length === 3) {
20
+ const r = parseInt(hex[0] + hex[0], 16);
21
+ const g = parseInt(hex[1] + hex[1], 16);
22
+ const b = parseInt(hex[2] + hex[2], 16);
23
+ if ([r, g, b].some(Number.isNaN))
24
+ return undefined;
25
+ return [r, g, b];
26
+ }
27
+ if (hex.length === 6 || hex.length === 8) {
28
+ const r = parseInt(hex.slice(0, 2), 16);
29
+ const g = parseInt(hex.slice(2, 4), 16);
30
+ const b = parseInt(hex.slice(4, 6), 16);
31
+ if ([r, g, b].some(Number.isNaN))
32
+ return undefined;
33
+ return [r, g, b];
34
+ }
35
+ return undefined;
36
+ }
37
+ const m = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*[\d.]+\s*)?\)$/i.exec(v);
38
+ if (m) {
39
+ const r = Number(m[1]);
40
+ const g = Number(m[2]);
41
+ const b = Number(m[3]);
42
+ if ([r, g, b].some((n) => n > 255))
43
+ return undefined;
44
+ return [r, g, b];
45
+ }
46
+ return undefined;
47
+ }
48
+ const toHex = (n) => Math.round(n).toString(16).padStart(2, '0');
49
+ /**
50
+ * Mix `ratio` of `color` into `base` (linear sRGB per-channel, like
51
+ * `color-mix(in srgb, color ratio, base)`), returning a hex string. If either
52
+ * input can't be parsed, returns `base` unchanged.
53
+ */
54
+ export function mixColors(color, base, ratio) {
55
+ const fg = parseColor(color);
56
+ const bg = parseColor(base);
57
+ // Non-finite ratios (NaN softMix etc.) are unmixable — fall back to the
58
+ // base rather than emitting `#NaNNaNNaN`.
59
+ if (!fg || !bg || !Number.isFinite(ratio))
60
+ return base;
61
+ const t = Math.min(1, Math.max(0, ratio));
62
+ const mix = (i) => fg[i] * t + bg[i] * (1 - t);
63
+ return `#${toHex(mix(0))}${toHex(mix(1))}${toHex(mix(2))}`;
64
+ }
65
+ //# sourceMappingURL=color-mix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-mix.js","sourceRoot":"","sources":["../../src/theme/color-mix.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Theme registry — the single source of truth for registered themes.
3
+ *
4
+ * A theme is *data*: a name, a light/dark variant, and a full color palette
5
+ * (plus an optional toggle `pair` and roundness overrides). Both rendering and
6
+ * any DS-specific consumers (e.g. icon tinting) read from here —
7
+ * `<ThemeProvider>` applies a theme's `colors` as inline CSS custom properties
8
+ * on its host view (Lynx inherits custom properties to descendants, so
9
+ * component classes resolve `var(--color-*)`). There is no per-theme CSS or
10
+ * parallel JS palette to keep in sync.
11
+ *
12
+ * The registry starts **empty** — design-system packages seed it at module
13
+ * load (e.g. `@sigx/lynx-daisyui` registers its six built-ins on import).
14
+ * Register more — including tenant themes fetched at runtime — with
15
+ * `registerTheme()`, or `extendTheme()` to derive one from a base. Order
16
+ * matters for `pickThemeFor()`: the first theme of a given variant is the
17
+ * follow-system default for that variant.
18
+ *
19
+ * Structural tokens (radius, sizing, component dimensions) are theme-agnostic
20
+ * and ship once in the bundled `.lynx-zero` base class (`styles/tokens.css`);
21
+ * a theme may override roundness via `radius` and base size units via `sizes`.
22
+ *
23
+ * Colors are engine-safe strings — hex or `rgb()`. Lynx's CSS engine does not
24
+ * parse `oklch()`, so convert before registering.
25
+ */
26
+ import { type ColorToken, type CoreColorToken, type SoftColorToken } from '../contract.js';
27
+ export type ThemeVariant = 'light' | 'dark';
28
+ /**
29
+ * Full *registered* color palette — every semantic token including the
30
+ * `*-soft` tints, no holes. This is what `colorsOf()` returns.
31
+ */
32
+ export type ThemePalette = Record<ColorToken, string>;
33
+ /**
34
+ * What a theme *author* writes: every core token, with the `*-soft` tints
35
+ * optional — any omitted soft is computed at registration (`softMix` of the
36
+ * variant color into `base-100`).
37
+ */
38
+ export type ThemePaletteInput = Record<CoreColorToken, string> & Partial<Record<SoftColorToken, string>>;
39
+ /**
40
+ * Roundness token overrides. Emitted as `--radius-selector` /
41
+ * `--radius-field` / `--radius-box`. Defaults live in the bundled
42
+ * `.lynx-zero` base.
43
+ */
44
+ export interface ThemeRadius {
45
+ /** Small selectable controls — checkbox, toggle, badge. */
46
+ selector?: string;
47
+ /** Fields — button, input, select, textarea. */
48
+ field?: string;
49
+ /** Boxes — card, modal, alert. */
50
+ box?: string;
51
+ }
52
+ /**
53
+ * Base size-unit overrides. Emitted as `--size-selector` / `--size-field`;
54
+ * component dimensions are integer multiples of these. Defaults live in the
55
+ * bundled `.lynx-zero` base.
56
+ */
57
+ export interface ThemeSizes {
58
+ /** Base unit for selector controls (checkbox, toggle, badge). */
59
+ selector?: string;
60
+ /** Base unit for fields (button, input, select). */
61
+ field?: string;
62
+ }
63
+ export interface ThemeInput {
64
+ /** Unique id — also the value of `theme.name`. */
65
+ name: string;
66
+ /** Light or dark — drives follow-system selection and status-bar tint. */
67
+ variant: ThemeVariant;
68
+ /** Color palette — core tokens required, `*-soft` tints optional. */
69
+ colors: ThemePaletteInput;
70
+ /**
71
+ * Which theme `toggle()` flips to. Defaults to the first registered theme of
72
+ * the opposite variant.
73
+ */
74
+ pair?: string;
75
+ /** Optional roundness overrides; unspecified tokens fall back to the base. */
76
+ radius?: ThemeRadius;
77
+ /** Optional base size-unit overrides; unspecified tokens fall back to the base. */
78
+ sizes?: ThemeSizes;
79
+ /**
80
+ * Whether this theme ships a build-time CSS class named after it (the DS
81
+ * package generates `.theme-name { --color-*: … }` at build time, e.g. via
82
+ * daisyui's `gen-theme-css.mjs`). Such themes paint correctly on the very
83
+ * first frame; themes without it apply via the runtime `setProperty` path
84
+ * post-mount, with their variant's static theme class as the first-frame
85
+ * fallback.
86
+ */
87
+ staticCss?: boolean;
88
+ /**
89
+ * How strong the computed `*-soft` tints are: the ratio of the variant
90
+ * color mixed into `base-100` for any soft token the palette doesn't set
91
+ * explicitly. Design-system flavor, carried as theme data — daisy's
92
+ * built-ins use `0.08` (daisyUI v5's ~8% tints), hero's use `0.2`
93
+ * (HeroUI's `color/20`). Default `0.16`.
94
+ */
95
+ softMix?: number;
96
+ }
97
+ /** A registered theme — same shape as the input, with the palette completed. */
98
+ export interface Theme extends Omit<ThemeInput, 'colors'> {
99
+ colors: ThemePalette;
100
+ }
101
+ /**
102
+ * Complete a theme's palette: any `*-soft` token the author didn't set is
103
+ * computed as `softMix` of the variant color mixed into `base-100` (in JS —
104
+ * Lynx CSS can't alpha-compose `var()` colors, so the tints are materialized
105
+ * in the palette). Idempotent; explicitly provided softs are kept verbatim.
106
+ *
107
+ * DS packages run their builtin arrays through this before exporting them so
108
+ * build scripts (gen-theme-css) see the same palette the registry serves.
109
+ */
110
+ export declare function completeTheme(input: ThemeInput): Theme;
111
+ /**
112
+ * Whether `name` is a registered theme that ships a build-time CSS class —
113
+ * i.e. it paints correctly on the first frame. Themes registered without
114
+ * `staticCss` return `false`; `<ThemeProvider>` falls back to their variant's
115
+ * static class for first paint and swaps in the exact palette via
116
+ * `setProperty`.
117
+ */
118
+ export declare function hasStaticCss(name: string | undefined): boolean;
119
+ /**
120
+ * All registered themes in insertion order. Returns a shallow copy so callers
121
+ * can't mutate the internal registry — re-registration goes through
122
+ * `registerTheme()`. Each entry is a full `Theme` (name, variant, palette),
123
+ * so consumers can render swatches in a picker.
124
+ */
125
+ export declare function listThemes(): readonly Theme[];
126
+ /**
127
+ * Register (or replace, by `name`) a theme. Call at module-load time before
128
+ * mounting `<ThemeProvider>` so it shows up in `listThemes()` / `pickThemeFor()`.
129
+ * The palette is completed on the way in (`completeTheme`): any `*-soft`
130
+ * token the author didn't set is computed from `softMix`.
131
+ */
132
+ export declare function registerTheme(theme: ThemeInput): void;
133
+ /**
134
+ * Derive a new theme from a registered base, overriding any colors / roundness.
135
+ * Ergonomic for "tenant tweaks a few tokens": the result is a full `Theme` you
136
+ * pass to `registerTheme()`. Throws if `base` isn't registered.
137
+ *
138
+ * ```ts
139
+ * registerTheme(extendTheme('daisy-dark', {
140
+ * name: 'acme-dark',
141
+ * colors: { primary: '#fb7185' },
142
+ * }));
143
+ * ```
144
+ */
145
+ export declare function extendTheme(base: string, patch: {
146
+ name: string;
147
+ variant?: ThemeVariant;
148
+ pair?: string;
149
+ colors?: Partial<ThemePalette>;
150
+ radius?: ThemeRadius;
151
+ sizes?: ThemeSizes;
152
+ softMix?: number;
153
+ }): Theme;
154
+ /** The variant of a registered theme, or `undefined` if not registered. */
155
+ export declare function variantOf(name: string | undefined): ThemeVariant | undefined;
156
+ /** The color palette of a registered theme, or `undefined` if not registered. */
157
+ export declare function colorsOf(name: string | undefined): ThemePalette | undefined;
158
+ /** The roundness overrides of a registered theme, if any. */
159
+ export declare function radiusOf(name: string | undefined): ThemeRadius | undefined;
160
+ /** The base size-unit overrides of a registered theme, if any. */
161
+ export declare function sizesOf(name: string | undefined): ThemeSizes | undefined;
162
+ /**
163
+ * The first registered palette — the engine's last-resort fallback when an
164
+ * active theme name isn't registered. `undefined` only when no design system
165
+ * has seeded the registry yet.
166
+ * @internal
167
+ */
168
+ export declare function fallbackPalette(): ThemePalette | undefined;
169
+ /**
170
+ * Pick a default theme for a given system color scheme — the first registered
171
+ * theme of that variant. Falls back to the first registered theme of any
172
+ * variant, or `''` while the registry is empty (a design-system package seeds
173
+ * it at module load, so this is only reachable before any DS import).
174
+ */
175
+ export declare function pickThemeFor(scheme: ThemeVariant): string;
176
+ /**
177
+ * Resolve the paired theme of a given name — used by `theme.toggle()`. Follows
178
+ * `pair` if set, otherwise the first theme of the opposite variant. Returns the
179
+ * input unchanged when the theme isn't registered.
180
+ */
181
+ export declare function pairOf(name: string): string;
182
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/theme/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Theme registry — the single source of truth for registered themes.
3
+ *
4
+ * A theme is *data*: a name, a light/dark variant, and a full color palette
5
+ * (plus an optional toggle `pair` and roundness overrides). Both rendering and
6
+ * any DS-specific consumers (e.g. icon tinting) read from here —
7
+ * `<ThemeProvider>` applies a theme's `colors` as inline CSS custom properties
8
+ * on its host view (Lynx inherits custom properties to descendants, so
9
+ * component classes resolve `var(--color-*)`). There is no per-theme CSS or
10
+ * parallel JS palette to keep in sync.
11
+ *
12
+ * The registry starts **empty** — design-system packages seed it at module
13
+ * load (e.g. `@sigx/lynx-daisyui` registers its six built-ins on import).
14
+ * Register more — including tenant themes fetched at runtime — with
15
+ * `registerTheme()`, or `extendTheme()` to derive one from a base. Order
16
+ * matters for `pickThemeFor()`: the first theme of a given variant is the
17
+ * follow-system default for that variant.
18
+ *
19
+ * Structural tokens (radius, sizing, component dimensions) are theme-agnostic
20
+ * and ship once in the bundled `.lynx-zero` base class (`styles/tokens.css`);
21
+ * a theme may override roundness via `radius` and base size units via `sizes`.
22
+ *
23
+ * Colors are engine-safe strings — hex or `rgb()`. Lynx's CSS engine does not
24
+ * parse `oklch()`, so convert before registering.
25
+ */
26
+ import { COLOR_VARIANT_LIST, } from '../contract.js';
27
+ import { mixColors } from './color-mix.js';
28
+ const DEFAULT_SOFT_MIX = 0.16;
29
+ /**
30
+ * Complete a theme's palette: any `*-soft` token the author didn't set is
31
+ * computed as `softMix` of the variant color mixed into `base-100` (in JS —
32
+ * Lynx CSS can't alpha-compose `var()` colors, so the tints are materialized
33
+ * in the palette). Idempotent; explicitly provided softs are kept verbatim.
34
+ *
35
+ * DS packages run their builtin arrays through this before exporting them so
36
+ * build scripts (gen-theme-css) see the same palette the registry serves.
37
+ */
38
+ export function completeTheme(input) {
39
+ const mix = input.softMix ?? DEFAULT_SOFT_MIX;
40
+ const colors = { ...input.colors };
41
+ for (const variant of COLOR_VARIANT_LIST) {
42
+ const soft = `${variant}-soft`;
43
+ if (colors[soft] === undefined) {
44
+ colors[soft] = mixColors(colors[variant], colors['base-100'], mix);
45
+ }
46
+ }
47
+ return { ...input, colors };
48
+ }
49
+ const registry = [];
50
+ /**
51
+ * Whether `name` is a registered theme that ships a build-time CSS class —
52
+ * i.e. it paints correctly on the first frame. Themes registered without
53
+ * `staticCss` return `false`; `<ThemeProvider>` falls back to their variant's
54
+ * static class for first paint and swaps in the exact palette via
55
+ * `setProperty`.
56
+ */
57
+ export function hasStaticCss(name) {
58
+ return findTheme(name)?.staticCss === true;
59
+ }
60
+ /**
61
+ * Resolve a `theme.name` to its registered `Theme`. Supports multi-class names
62
+ * like `'daisy-light daisy-rounded'` by matching the first registered id found.
63
+ */
64
+ function findTheme(name) {
65
+ if (!name)
66
+ return undefined;
67
+ for (const part of name.split(/\s+/)) {
68
+ const hit = registry.find((t) => t.name === part);
69
+ if (hit)
70
+ return hit;
71
+ }
72
+ return undefined;
73
+ }
74
+ /**
75
+ * All registered themes in insertion order. Returns a shallow copy so callers
76
+ * can't mutate the internal registry — re-registration goes through
77
+ * `registerTheme()`. Each entry is a full `Theme` (name, variant, palette),
78
+ * so consumers can render swatches in a picker.
79
+ */
80
+ export function listThemes() {
81
+ return registry.slice();
82
+ }
83
+ /**
84
+ * Register (or replace, by `name`) a theme. Call at module-load time before
85
+ * mounting `<ThemeProvider>` so it shows up in `listThemes()` / `pickThemeFor()`.
86
+ * The palette is completed on the way in (`completeTheme`): any `*-soft`
87
+ * token the author didn't set is computed from `softMix`.
88
+ */
89
+ export function registerTheme(theme) {
90
+ const complete = completeTheme(theme);
91
+ const i = registry.findIndex((t) => t.name === complete.name);
92
+ if (i >= 0)
93
+ registry[i] = complete;
94
+ else
95
+ registry.push(complete);
96
+ }
97
+ /**
98
+ * Derive a new theme from a registered base, overriding any colors / roundness.
99
+ * Ergonomic for "tenant tweaks a few tokens": the result is a full `Theme` you
100
+ * pass to `registerTheme()`. Throws if `base` isn't registered.
101
+ *
102
+ * ```ts
103
+ * registerTheme(extendTheme('daisy-dark', {
104
+ * name: 'acme-dark',
105
+ * colors: { primary: '#fb7185' },
106
+ * }));
107
+ * ```
108
+ */
109
+ export function extendTheme(base, patch) {
110
+ const src = findTheme(base);
111
+ if (!src) {
112
+ throw new Error(`[lynx-zero] extendTheme: unknown base theme "${base}". `
113
+ + `Register it first, or extend a theme your design system registered.`);
114
+ }
115
+ // Merge core tokens, then RECOMPUTE every soft tint the patch didn't set
116
+ // explicitly — patching `primary` must not leave the base's stale
117
+ // `primary-soft` behind. (A soft the base author set explicitly is
118
+ // indistinguishable from a computed one post-registration; explicit softs
119
+ // therefore live in the patch when extending.)
120
+ const merged = { ...src.colors };
121
+ for (const variant of COLOR_VARIANT_LIST)
122
+ delete merged[`${variant}-soft`];
123
+ Object.assign(merged, patch.colors);
124
+ return completeTheme({
125
+ name: patch.name,
126
+ variant: patch.variant ?? src.variant,
127
+ pair: patch.pair ?? src.pair,
128
+ colors: merged,
129
+ radius: patch.radius ?? src.radius,
130
+ sizes: patch.sizes ?? src.sizes,
131
+ softMix: patch.softMix ?? src.softMix,
132
+ });
133
+ }
134
+ /** The variant of a registered theme, or `undefined` if not registered. */
135
+ export function variantOf(name) {
136
+ return findTheme(name)?.variant;
137
+ }
138
+ /** The color palette of a registered theme, or `undefined` if not registered. */
139
+ export function colorsOf(name) {
140
+ return findTheme(name)?.colors;
141
+ }
142
+ /** The roundness overrides of a registered theme, if any. */
143
+ export function radiusOf(name) {
144
+ return findTheme(name)?.radius;
145
+ }
146
+ /** The base size-unit overrides of a registered theme, if any. */
147
+ export function sizesOf(name) {
148
+ return findTheme(name)?.sizes;
149
+ }
150
+ /**
151
+ * The first registered palette — the engine's last-resort fallback when an
152
+ * active theme name isn't registered. `undefined` only when no design system
153
+ * has seeded the registry yet.
154
+ * @internal
155
+ */
156
+ export function fallbackPalette() {
157
+ return registry[0]?.colors;
158
+ }
159
+ /**
160
+ * Pick a default theme for a given system color scheme — the first registered
161
+ * theme of that variant. Falls back to the first registered theme of any
162
+ * variant, or `''` while the registry is empty (a design-system package seeds
163
+ * it at module load, so this is only reachable before any DS import).
164
+ */
165
+ export function pickThemeFor(scheme) {
166
+ const hit = registry.find((t) => t.variant === scheme);
167
+ return hit?.name ?? registry[0]?.name ?? '';
168
+ }
169
+ /**
170
+ * Resolve the paired theme of a given name — used by `theme.toggle()`. Follows
171
+ * `pair` if set, otherwise the first theme of the opposite variant. Returns the
172
+ * input unchanged when the theme isn't registered.
173
+ */
174
+ export function pairOf(name) {
175
+ const hit = findTheme(name);
176
+ if (!hit)
177
+ return name;
178
+ if (hit.pair)
179
+ return hit.pair;
180
+ return pickThemeFor(hit.variant === 'light' ? 'dark' : 'light');
181
+ }
182
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/theme/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { ThemeController, ThemeName } from './ThemeProvider.js';
2
+ /** The mutable selection a `ThemeController` reads from and writes to. */
3
+ export interface ThemeState {
4
+ name: ThemeName;
5
+ following: boolean;
6
+ /**
7
+ * Global text-scale multiplier applied on top of the theme's `--text-*`
8
+ * ramp. Orthogonal to `name`: a theme switch / `toggle()` leaves it
9
+ * untouched, so a user/accessibility scale persists across appearance
10
+ * changes. `1` = the default ramp.
11
+ */
12
+ fontScale: number;
13
+ }
14
+ /**
15
+ * Coerce a font-scale input to a valid positive, finite multiplier. Rejects
16
+ * `NaN`, `±Infinity`, and non-positive values — which would otherwise emit
17
+ * invalid CSS (`NaNpx`, negative font sizes) and break `fontScale === 1`
18
+ * comparisons — by returning `fallback` instead.
19
+ */
20
+ export declare function normalizeFontScale(value: unknown, fallback?: number): number;
21
+ /**
22
+ * Build a `ThemeController` over a given state object. Used for both the global
23
+ * singleton (below) and each nested `<ThemeProvider>`'s local state — same
24
+ * behaviour, different backing store. `followSystem()` only flips the flag; the
25
+ * owning provider's follow effect performs the re-apply.
26
+ */
27
+ export declare function makeThemeController(state: ThemeState): ThemeController;
28
+ /**
29
+ * The backing signal for the global theme. Read/written by the root
30
+ * `<ThemeProvider>` and shared with `themeController`; not part of the public
31
+ * API.
32
+ * @internal
33
+ */
34
+ export declare const globalThemeState: import("@sigx/reactivity").Signal<ThemeState>;
35
+ /**
36
+ * The global theme controller — the headless handle. Import and call from
37
+ * anywhere (no `<ThemeProvider>` ancestor required); `useTheme()`'s default
38
+ * factory returns this same instance, and the root `<ThemeProvider>` provides
39
+ * it to its subtree. `StatusBarSync` binds to it so the OS bars always follow
40
+ * the global/screen theme, never a content sub-scope.
41
+ */
42
+ export declare const themeController: ThemeController;
43
+ //# sourceMappingURL=theme-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-state.d.ts","sourceRoot":"","sources":["../../src/theme/theme-state.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAErE,0EAA0E;AAC1E,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAI,GAAG,MAAM,CAIvE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,eAAe,CA2BtE;AAcD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,+CAAQ,CAAC;AAEtC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,eAA4C,CAAC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Global theme state — the headless DI singleton behind `useTheme()`.
3
+ *
4
+ * The active selection (current theme name + follow-system flag) lives here as
5
+ * a module-level signal, mirroring how `./registry.ts` is already a global
6
+ * module singleton. This is what makes theme control reachable from *headless*
7
+ * code — a store, a service, app-boot logic, an effect — not just from a
8
+ * component mounted under `<ThemeProvider>`.
9
+ *
10
+ * The root `<ThemeProvider>` (depth 0) binds to this state: it renders its host
11
+ * view from it, owns the system-color-scheme follow effect that writes to it
12
+ * while `following`, and seeds an `initial` prop into it. Nested providers
13
+ * (depth >= 1) build their own local state via `makeThemeController` so a
14
+ * subtree can be overridden without touching the global — see
15
+ * `./ThemeProvider.tsx`.
16
+ *
17
+ * `followSystem()` here only flips the flag; the actual re-apply on an OS color
18
+ * scheme change is driven by the root provider's follow effect (which has the
19
+ * appearance signal in scope).
20
+ */
21
+ import { signal } from '@sigx/lynx';
22
+ import { pairOf, pickThemeFor } from './registry.js';
23
+ /**
24
+ * Coerce a font-scale input to a valid positive, finite multiplier. Rejects
25
+ * `NaN`, `±Infinity`, and non-positive values — which would otherwise emit
26
+ * invalid CSS (`NaNpx`, negative font sizes) and break `fontScale === 1`
27
+ * comparisons — by returning `fallback` instead.
28
+ */
29
+ export function normalizeFontScale(value, fallback = 1) {
30
+ return typeof value === 'number' && Number.isFinite(value) && value > 0
31
+ ? value
32
+ : fallback;
33
+ }
34
+ /**
35
+ * Build a `ThemeController` over a given state object. Used for both the global
36
+ * singleton (below) and each nested `<ThemeProvider>`'s local state — same
37
+ * behaviour, different backing store. `followSystem()` only flips the flag; the
38
+ * owning provider's follow effect performs the re-apply.
39
+ */
40
+ export function makeThemeController(state) {
41
+ return {
42
+ get name() {
43
+ return state.name;
44
+ },
45
+ get followingSystem() {
46
+ return state.following;
47
+ },
48
+ get fontScale() {
49
+ return state.fontScale;
50
+ },
51
+ set(next) {
52
+ state.name = next;
53
+ state.following = false;
54
+ },
55
+ toggle() {
56
+ state.name = pairOf(state.name);
57
+ state.following = false;
58
+ },
59
+ followSystem() {
60
+ state.following = true;
61
+ },
62
+ setFontScale(scale) {
63
+ // Ignore invalid input (keep the current scale) so state stays valid.
64
+ state.fontScale = normalizeFontScale(scale, state.fontScale);
65
+ },
66
+ };
67
+ }
68
+ // Object signal (not primitive) so theme-name literal unions a DS layers on
69
+ // top survive — `signal<T>` widens primitive literals to plain `string` via
70
+ // `Widen<T>`. Seeded from whatever is registered at first import (a DS package
71
+ // seeds the registry at its own module load; until then the name is '') —
72
+ // the root <ThemeProvider> re-seeds from the system color scheme + its props
73
+ // on mount.
74
+ const state = signal({
75
+ name: pickThemeFor('light'),
76
+ following: true,
77
+ fontScale: 1,
78
+ });
79
+ /**
80
+ * The backing signal for the global theme. Read/written by the root
81
+ * `<ThemeProvider>` and shared with `themeController`; not part of the public
82
+ * API.
83
+ * @internal
84
+ */
85
+ export const globalThemeState = state;
86
+ /**
87
+ * The global theme controller — the headless handle. Import and call from
88
+ * anywhere (no `<ThemeProvider>` ancestor required); `useTheme()`'s default
89
+ * factory returns this same instance, and the root `<ThemeProvider>` provides
90
+ * it to its subtree. `StatusBarSync` binds to it so the OS bars always follow
91
+ * the global/screen theme, never a content sub-scope.
92
+ */
93
+ export const themeController = makeThemeController(state);
94
+ //# sourceMappingURL=theme-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-state.js","sourceRoot":"","sources":["../../src/theme/theme-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAgBrD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAc,EAAE,QAAQ,GAAG,CAAC;IAC3D,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QACnE,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,QAAQ,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAiB;IACjD,OAAO;QACH,IAAI,IAAI;YACJ,OAAO,KAAK,CAAC,IAAI,CAAC;QACtB,CAAC;QACD,IAAI,eAAe;YACf,OAAO,KAAK,CAAC,SAAS,CAAC;QAC3B,CAAC;QACD,IAAI,SAAS;YACT,OAAO,KAAK,CAAC,SAAS,CAAC;QAC3B,CAAC;QACD,GAAG,CAAC,IAAI;YACJ,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;YAClB,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC;QAC5B,CAAC;QACD,MAAM;YACF,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAChC,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC;QAC5B,CAAC;QACD,YAAY;YACR,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;QAC3B,CAAC;QACD,YAAY,CAAC,KAAK;YACd,sEAAsE;YACtE,KAAK,CAAC,SAAS,GAAG,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QACjE,CAAC;KACJ,CAAC;AACN,CAAC;AAED,4EAA4E;AAC5E,4EAA4E;AAC5E,+EAA+E;AAC/E,0EAA0E;AAC1E,6EAA6E;AAC7E,YAAY;AACZ,MAAM,KAAK,GAAG,MAAM,CAAa;IAC7B,IAAI,EAAE,YAAY,CAAC,OAAO,CAAC;IAC3B,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,CAAC;CACf,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAEtC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAoB,mBAAmB,CAAC,KAAK,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ThemeName } from './ThemeProvider.js';
2
+ /** Pin the global theme to `name` while this screen is focused; restore on blur. */
3
+ export declare function useScreenTheme(name: ThemeName): void;
4
+ //# sourceMappingURL=use-screen-theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-screen-theme.d.ts","sourceRoot":"","sources":["../../src/theme/use-screen-theme.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAUpD"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * `useScreenTheme(name)` — pin the **global** theme while a navigation
3
+ * screen is focused, restoring the previous selection when it blurs.
4
+ *
5
+ * This is the right tool for *per-screen* theming — "this screen is dark, that
6
+ * one is light." Because it drives the global theme (not a content sub-scope),
7
+ * the OS status/navigation bars follow automatically via `<StatusBarSync>`, so
8
+ * the bar icons stay legible against each screen's background. For recoloring a
9
+ * *region within* a screen without touching the bars, nest a `<ThemeProvider>`
10
+ * instead.
11
+ *
12
+ * Built on `useFocusEffect` from `@sigx/lynx-navigation` (an optional peer
13
+ * dependency): it must be called from inside a component rendered as a route by
14
+ * `<Stack>` / `<Tabs>` — the same constraint as `useFocusEffect`/`useIsFocused`.
15
+ *
16
+ * Save/restore composes with the stack (LIFO focus/blur): pushing a themed
17
+ * screen saves whatever was live, applies its own theme, and restores on pop —
18
+ * including resuming follow-system if that's what was active.
19
+ *
20
+ * ```tsx
21
+ * const Gallery = component(() => {
22
+ * useScreenTheme('daisy-dark'); // dark while this screen is on top
23
+ * return () => <view>…</view>;
24
+ * });
25
+ * ```
26
+ */
27
+ import { useFocusEffect } from '@sigx/lynx-navigation';
28
+ import { themeController } from './theme-state.js';
29
+ /** Pin the global theme to `name` while this screen is focused; restore on blur. */
30
+ export function useScreenTheme(name) {
31
+ useFocusEffect(() => {
32
+ const prevName = themeController.name;
33
+ const prevFollowing = themeController.followingSystem;
34
+ themeController.set(name);
35
+ return () => {
36
+ if (prevFollowing)
37
+ themeController.followSystem();
38
+ else
39
+ themeController.set(prevName);
40
+ };
41
+ });
42
+ }
43
+ //# sourceMappingURL=use-screen-theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-screen-theme.js","sourceRoot":"","sources":["../../src/theme/use-screen-theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGnD,oFAAoF;AACpF,MAAM,UAAU,cAAc,CAAC,IAAe;IAC1C,cAAc,CAAC,GAAG,EAAE;QAChB,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC;QACtC,MAAM,aAAa,GAAG,eAAe,CAAC,eAAe,CAAC;QACtD,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,GAAG,EAAE;YACR,IAAI,aAAa;gBAAE,eAAe,CAAC,YAAY,EAAE,CAAC;;gBAC7C,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC"}