@adea-ai/ui 0.128.2 → 0.128.3

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 (132) hide show
  1. package/dist/components/theme/appearance-panel.js +1 -1
  2. package/dist/components/theme/index.js +1 -1
  3. package/dist/components/theme/lazy.d.ts +15 -0
  4. package/dist/components/theme/lazy.d.ts.map +1 -0
  5. package/dist/components/theme/lazy.js +9 -0
  6. package/dist/components/theme/theme-catalogue-source.d.ts +15 -0
  7. package/dist/components/theme/theme-catalogue-source.d.ts.map +1 -0
  8. package/dist/components/theme/theme-catalogue-source.js +51 -0
  9. package/dist/components/theme/theme-catalogue-source.js.map +1 -0
  10. package/dist/components/theme/theme-document-core.d.ts +123 -0
  11. package/dist/components/theme/theme-document-core.d.ts.map +1 -0
  12. package/dist/components/theme/{theme-document.js → theme-document-core.js} +12 -12
  13. package/dist/components/theme/theme-document-core.js.map +1 -0
  14. package/dist/components/theme/theme-document.d.ts +8 -107
  15. package/dist/components/theme/theme-document.d.ts.map +1 -1
  16. package/dist/components/theme/theme-persistence.d.ts +1 -1
  17. package/dist/components/theme/theme-persistence.d.ts.map +1 -1
  18. package/dist/components/theme/theme-persistence.js +9 -6
  19. package/dist/components/theme/theme-persistence.js.map +1 -1
  20. package/dist/components/theme/theme-provider-core.d.ts +19 -0
  21. package/dist/components/theme/theme-provider-core.d.ts.map +1 -0
  22. package/dist/components/theme/theme-provider-core.js +28 -0
  23. package/dist/components/theme/theme-provider-core.js.map +1 -0
  24. package/dist/components/theme/theme-provider-lazy.d.ts +28 -0
  25. package/dist/components/theme/theme-provider-lazy.d.ts.map +1 -0
  26. package/dist/components/theme/theme-provider-lazy.js +36 -0
  27. package/dist/components/theme/theme-provider-lazy.js.map +1 -0
  28. package/dist/components/theme/theme-provider-value.d.ts +175 -0
  29. package/dist/components/theme/theme-provider-value.d.ts.map +1 -0
  30. package/dist/components/theme/theme-provider-value.js +200 -0
  31. package/dist/components/theme/theme-provider-value.js.map +1 -0
  32. package/dist/components/theme/theme-provider.d.ts +11 -136
  33. package/dist/components/theme/theme-provider.d.ts.map +1 -1
  34. package/dist/components/theme/theme-provider.js +27 -149
  35. package/dist/components/theme/theme-provider.js.map +1 -1
  36. package/dist/components/theme/theme-script.js +4 -3
  37. package/dist/components/theme/theme-script.js.map +1 -1
  38. package/dist/components/theme/theme-state-core.d.ts +248 -0
  39. package/dist/components/theme/theme-state-core.d.ts.map +1 -0
  40. package/dist/components/theme/{theme-state.js → theme-state-core.js} +8 -7
  41. package/dist/components/theme/theme-state-core.js.map +1 -0
  42. package/dist/components/theme/theme-state.d.ts +8 -214
  43. package/dist/components/theme/theme-state.d.ts.map +1 -1
  44. package/dist/components/theme/theme-toggle.js.map +1 -1
  45. package/dist/index.js +7 -4
  46. package/dist/lib/accent-presets.d.ts +26 -0
  47. package/dist/lib/accent-presets.d.ts.map +1 -0
  48. package/dist/lib/accent-presets.js +11 -0
  49. package/dist/lib/accent-presets.js.map +1 -0
  50. package/dist/lib/theme-catalogue.d.ts +3 -0
  51. package/dist/lib/theme-catalogue.d.ts.map +1 -0
  52. package/dist/lib/theme-catalogue.js +22 -0
  53. package/dist/lib/theme-catalogue.js.map +1 -0
  54. package/dist/lib/theme-css.d.ts +43 -0
  55. package/dist/lib/theme-css.d.ts.map +1 -0
  56. package/dist/lib/theme-css.js +167 -0
  57. package/dist/lib/theme-css.js.map +1 -0
  58. package/dist/lib/theme-defaults.d.ts +46 -0
  59. package/dist/lib/theme-defaults.d.ts.map +1 -0
  60. package/dist/lib/theme-defaults.generated.d.ts +13 -0
  61. package/dist/lib/theme-defaults.generated.d.ts.map +1 -0
  62. package/dist/lib/theme-defaults.generated.js +318 -0
  63. package/dist/lib/theme-defaults.generated.js.map +1 -0
  64. package/dist/lib/theme-defaults.js +47 -0
  65. package/dist/lib/theme-defaults.js.map +1 -0
  66. package/dist/lib/theme-engine-light.d.ts +3 -0
  67. package/dist/lib/theme-engine-light.d.ts.map +1 -0
  68. package/dist/lib/theme-engine-light.js +75 -0
  69. package/dist/lib/theme-engine-light.js.map +1 -0
  70. package/dist/lib/theme-engine.d.ts +69 -0
  71. package/dist/lib/theme-engine.d.ts.map +1 -0
  72. package/dist/lib/theme-engine.js +33 -0
  73. package/dist/lib/theme-engine.js.map +1 -0
  74. package/dist/lib/themes.d.ts +14 -48
  75. package/dist/lib/themes.d.ts.map +1 -1
  76. package/dist/lib/themes.js +21 -163
  77. package/dist/lib/themes.js.map +1 -1
  78. package/dist/lib/tokens.d.ts +1 -25
  79. package/dist/lib/tokens.d.ts.map +1 -1
  80. package/dist/lib/tokens.js +1 -24
  81. package/dist/lib/tokens.js.map +1 -1
  82. package/dist/r/lib.json +35 -0
  83. package/dist/r/registry.json +70 -0
  84. package/dist/r/src/components/theme/lazy.ts +35 -0
  85. package/dist/r/src/components/theme/theme-catalogue-source.ts +59 -0
  86. package/dist/r/src/components/theme/theme-document-core.ts +262 -0
  87. package/dist/r/src/components/theme/theme-document.ts +23 -240
  88. package/dist/r/src/components/theme/theme-persistence.ts +14 -6
  89. package/dist/r/src/components/theme/theme-provider-core.tsx +40 -0
  90. package/dist/r/src/components/theme/theme-provider-lazy.tsx +34 -0
  91. package/dist/r/src/components/theme/theme-provider-value.ts +445 -0
  92. package/dist/r/src/components/theme/theme-provider.tsx +23 -346
  93. package/dist/r/src/components/theme/theme-script.ts +3 -3
  94. package/dist/r/src/components/theme/theme-state-core.ts +364 -0
  95. package/dist/r/src/components/theme/theme-state.ts +10 -329
  96. package/dist/r/src/components/theme/theme-toggle.tsx +1 -1
  97. package/dist/r/src/lib/accent-presets.ts +43 -0
  98. package/dist/r/src/lib/theme-catalogue.ts +26 -0
  99. package/dist/r/src/lib/theme-css.ts +201 -0
  100. package/dist/r/src/lib/theme-defaults.generated.ts +319 -0
  101. package/dist/r/src/lib/theme-defaults.ts +79 -0
  102. package/dist/r/src/lib/theme-engine-light.ts +117 -0
  103. package/dist/r/src/lib/theme-engine.ts +111 -0
  104. package/dist/r/src/lib/themes.ts +27 -191
  105. package/dist/r/src/lib/tokens.ts +1 -35
  106. package/dist/r/theming.json +35 -0
  107. package/package.json +10 -2
  108. package/registry.json +70 -0
  109. package/src/components/theme/lazy.ts +35 -0
  110. package/src/components/theme/theme-catalogue-source.ts +59 -0
  111. package/src/components/theme/theme-document-core.ts +262 -0
  112. package/src/components/theme/theme-document.ts +23 -240
  113. package/src/components/theme/theme-persistence.ts +14 -6
  114. package/src/components/theme/theme-provider-core.tsx +40 -0
  115. package/src/components/theme/theme-provider-lazy.tsx +34 -0
  116. package/src/components/theme/theme-provider-value.ts +445 -0
  117. package/src/components/theme/theme-provider.tsx +23 -346
  118. package/src/components/theme/theme-script.ts +3 -3
  119. package/src/components/theme/theme-state-core.ts +364 -0
  120. package/src/components/theme/theme-state.ts +10 -329
  121. package/src/components/theme/theme-toggle.tsx +1 -1
  122. package/src/lib/accent-presets.ts +43 -0
  123. package/src/lib/theme-catalogue.ts +26 -0
  124. package/src/lib/theme-css.ts +201 -0
  125. package/src/lib/theme-defaults.generated.ts +319 -0
  126. package/src/lib/theme-defaults.ts +79 -0
  127. package/src/lib/theme-engine-light.ts +117 -0
  128. package/src/lib/theme-engine.ts +111 -0
  129. package/src/lib/themes.ts +27 -191
  130. package/src/lib/tokens.ts +1 -35
  131. package/dist/components/theme/theme-document.js.map +0 -1
  132. package/dist/components/theme/theme-state.js.map +0 -1
@@ -0,0 +1,248 @@
1
+ import { type AppearanceEditorFontSettings } from '#lib/appearance-font-settings';
2
+ import type { AccentPreset } from '@adea-ai/themes';
3
+ import { type AccentRoles } from '#lib/accent-roles';
4
+ import { type FontZoomIntent } from '#lib/font-zoom';
5
+ import type { ThemeSurface } from '#lib/theme-surface';
6
+ import type { ThemeEngine } from '#lib/theme-engine';
7
+ import type { ThemeAppearance, ThemeVariant } from '#lib/themes';
8
+ /**
9
+ * The pure half of the theme provider: what a selection is, how two selections
10
+ * merge, and what a selection resolves to.
11
+ *
12
+ * Nothing here touches the document, storage or a signal, so each rule can be
13
+ * tested by calling it. `ThemeProvider` owns the reactivity and wires these to it;
14
+ * `theme-document.ts` owns the writes; `theme-persistence.ts` owns the store.
15
+ *
16
+ * This is the catalogue-free half: it imports no catalogue code or data, so the lazy
17
+ * provider can use it at start-up. `theme-state.ts` re-exports it and adds
18
+ * {@link accentRolesForState} bound to the full catalogue.
19
+ */
20
+ /**
21
+ * Four independent choices, and keeping them independent is the point:
22
+ *
23
+ * - **appearance** — light, dark, or follow the system.
24
+ * - **a theme per appearance** — so a user can run Catppuccin Mocha by night and
25
+ * Solarized Light by day without re-choosing when the system flips.
26
+ * - **an accent** — adea's six presets, or one of the accents the selected theme
27
+ * pair carries itself (`ansi-blue`…), which re-colour the interactive roles on
28
+ * top of whichever theme is active.
29
+ * - **fonts** — separate family and size choices for UI, content and code.
30
+ */
31
+ export type ThemeSelection = {
32
+ /** `system` follows `prefers-color-scheme` and updates when it changes. */
33
+ appearance: ThemeAppearance | 'system';
34
+ /** The variant to use when the resolved appearance is light. */
35
+ lightThemeId: string;
36
+ /** The variant to use when the resolved appearance is dark. */
37
+ darkThemeId: string;
38
+ /**
39
+ * An `accentPresets` id, a theme accent id from `themeAccents()` (`ansi-blue`…),
40
+ * `theme` for the variant's own primary, or a custom colour as `#rrggbb`. A theme
41
+ * accent the current pair does not offer falls back to the variant's own primary.
42
+ *
43
+ * A custom colour is stored lower case and is raised to the contrast floor when it
44
+ * is painted, never when it is stored: the selection keeps what the user chose.
45
+ * Anything else that is not an id is dropped at paint, not rejected here.
46
+ */
47
+ accent: string;
48
+ /** UI-family alias for consumers of the original single-font preference. */
49
+ font: string;
50
+ /** UI, content and code font settings. Omitted legacy records are migrated from `font`. */
51
+ fonts?: AppearanceEditorFontSettings;
52
+ /**
53
+ * `comfortable` is the system's default geometry; `compact` moves the control
54
+ * ladder down one rung.
55
+ *
56
+ * Optional, and defaulted on read, so a preference stored before density existed
57
+ * keeps working and `ThemeSelection` stays assignable without it.
58
+ */
59
+ density?: ThemeDensity;
60
+ /**
61
+ * The terminal either follows the interface theme (`theme`, or absent) or is pinned
62
+ * to another theme's terminal palette by that theme's id. The pin is written over
63
+ * the interface variant's own `--terminal-*` properties, so a light interface can
64
+ * carry a dark terminal.
65
+ *
66
+ * An id the registry does not hold paints the interface palette and stays in the
67
+ * selection, the same rule as an unknown theme id.
68
+ *
69
+ * Optional and not defaulted: see {@link defaultThemeSelection}.
70
+ */
71
+ terminalThemeId?: string;
72
+ /**
73
+ * How opaque elevated panels are. Written to `<html>` only when the provider's
74
+ * `surface` prop is on or this is set; absent, a host paints no surface axis at
75
+ * all. Resolved against the OS and the host's capability before it is painted —
76
+ * see `resolveSurface` — so the value stored is a preference, never the outcome.
77
+ */
78
+ surface?: ThemeSurface;
79
+ /**
80
+ * The user's own Reduce Transparency switch, which forces the `opaque` surface the
81
+ * same way the operating system's setting does.
82
+ */
83
+ reduceTransparency?: boolean;
84
+ };
85
+ /**
86
+ * The two density rungs.
87
+ *
88
+ * `comfortable` is the geometry in `theme.css`, so it is the absence of an
89
+ * attribute rather than a value: a document that ships no `data-density` at all
90
+ * renders correctly, which is what keeps this additive.
91
+ */
92
+ export type ThemeDensity = 'comfortable' | 'compact';
93
+ /**
94
+ * Deliberately lists only the fields every consumer has always had. A field added
95
+ * to {@link ThemeSelection} later gets no default here: `JSON.stringify` drops an
96
+ * `undefined`, so a store written by a consumer that never sets the field stays
97
+ * byte-identical to what it was before the field existed.
98
+ */
99
+ export declare const defaultThemeSelection: ThemeSelection;
100
+ /**
101
+ * A stored selection's density, defaulted.
102
+ *
103
+ * The stored value is read through here rather than trusted, because a preference
104
+ * written before density existed has no `density` key and a hand-edited store may
105
+ * hold anything.
106
+ */
107
+ export declare function resolvedDensity(selection: ThemeSelection): ThemeDensity;
108
+ export type ThemeContextValue = {
109
+ /** The preference as stored. A {@link ThemeContextValue.preview} does not change it. */
110
+ selection: () => ThemeSelection;
111
+ /** What the preference resolves to right now, after `system`. */
112
+ resolvedAppearance: () => ThemeAppearance;
113
+ /** The variant in use. */
114
+ variant: () => ThemeVariant;
115
+ /** Update part of the preference. */
116
+ setSelection: (patch: Partial<ThemeSelection>) => void;
117
+ /**
118
+ * The built-in catalogue, so a picker does not import it separately. A plain
119
+ * array: {@link ThemeContextValue.allThemes} is the one that follows a host's
120
+ * extra themes. Under the lazy provider it holds only the default pair until
121
+ * {@link ThemeContextValue.catalogueReady}; read it again afterwards.
122
+ */
123
+ themes: readonly ThemeVariant[];
124
+ /**
125
+ * The accents the selected light/dark pair carries, offered beside the presets.
126
+ * Follows the selection: a different theme pair may offer different slots.
127
+ */
128
+ themeAccents: () => readonly AccentPreset[];
129
+ /**
130
+ * The built-in catalogue followed by the provider's `themes` prop. Reactive when
131
+ * that prop is a function that reads signals.
132
+ */
133
+ allThemes: () => readonly ThemeVariant[];
134
+ /**
135
+ * Paint the document from `draft` laid over the stored selection, without
136
+ * persisting it or changing what {@link ThemeContextValue.selection} returns.
137
+ * `undefined` ends the preview. A {@link ThemeContextValue.setSelection} ends it
138
+ * too, because the committed selection is then what should be showing.
139
+ */
140
+ preview: (draft: Partial<ThemeSelection> | undefined) => void;
141
+ /** True while a {@link ThemeContextValue.preview} draft is on the document. */
142
+ previewing: () => boolean;
143
+ /**
144
+ * The roles `accent` paints over `variantId` (default: the variant in use), held
145
+ * to the provider's `accentContrastFloor`, for an element-scoped override with
146
+ * `paintAccentRoles` — a workspace that carries its own colour. `undefined` for
147
+ * `null`, for `theme`, for an id that resolves to nothing, and for a variant the
148
+ * registry does not hold: each means "keep the inherited accent".
149
+ */
150
+ accentRolesFor: (accent: string | null, variantId?: string) => AccentRoles | undefined;
151
+ /**
152
+ * The surface the committed selection paints, after Reduce Transparency and the
153
+ * host's translucency capability. `opaque` when the axis is off. Follows the
154
+ * committed selection, not a preview, like {@link ThemeContextValue.variant}.
155
+ */
156
+ effectiveSurface: () => ThemeSurface;
157
+ /**
158
+ * True while the operating system or the selection's `reduceTransparency` forces
159
+ * opaque surfaces. Always false when the surface axis is off, because nothing is
160
+ * then listening to the operating system.
161
+ */
162
+ reduceTransparencyActive: () => boolean;
163
+ /**
164
+ * Step the three text sizes together, or put them back to the defaults: what the
165
+ * `fontZoom` prop's chords do, for a host that binds its own keys or a menu. Goes
166
+ * through {@link ThemeContextValue.setSelection}, so it persists and ends a preview.
167
+ */
168
+ zoomFonts: (intent: FontZoomIntent) => void;
169
+ /**
170
+ * True when `themes`, `allThemes()` and the variants they resolve are backed by the
171
+ * whole catalogue. Always true under `ThemeProvider`. Under the lazy provider
172
+ * (`@adea-ai/ui/components/theme/lazy`) it turns true once the catalogue has loaded,
173
+ * and until then only the default light and dark themes are available: a stored
174
+ * theme beyond them resolves to its appearance's default (the document keeps painting
175
+ * what the stylesheet gave it) until `catalogueReady()` is true. Absent on a context
176
+ * a host builds itself, where it should be read as true.
177
+ */
178
+ catalogueReady?: () => boolean;
179
+ /**
180
+ * Start loading the whole catalogue (idempotent) and resolve when it is available.
181
+ * A picker, a preview or a settings page calls it when it mounts. A no-op under
182
+ * `ThemeProvider`, whose catalogue is already loaded.
183
+ */
184
+ loadCatalogue?: () => Promise<void>;
185
+ };
186
+ /** Keep the deprecated single-font view synchronized with the UI axis. */
187
+ export declare function mergeThemeSelection(base: ThemeSelection, patch: Partial<ThemeSelection>): ThemeSelection;
188
+ /**
189
+ * The selection a provider starts from: the defaults, then whatever the store
190
+ * returned, then `initial`.
191
+ *
192
+ * `initial` wins over the store on every mount. That precedence is part of the
193
+ * contract Cortana relies on (its `initial` pins `appearance` and `font`), and it
194
+ * is why the store's answer is only the middle layer. A store that returned
195
+ * nothing, or threw, or answered with explicit `undefined`s, leaves the defaults
196
+ * standing for those fields.
197
+ */
198
+ export declare function initialThemeSelection(stored: Partial<ThemeSelection> | undefined, initial: Partial<ThemeSelection> | undefined): ThemeSelection;
199
+ /** `system` resolved against what the operating system reports. */
200
+ export declare function resolveAppearance(preference: ThemeSelection['appearance'], system: ThemeAppearance): ThemeAppearance;
201
+ /** The theme id the selection names for a resolved appearance. */
202
+ export declare function selectedThemeId(selection: ThemeSelection, appearance: ThemeAppearance): string;
203
+ /**
204
+ * What a selection paints: the resolved appearance and the variant for it.
205
+ *
206
+ * An id the registry does not hold resolves to the appearance's default rather than
207
+ * being rewritten (see `resolveVariant`), so the selection a host stores is never
208
+ * changed by being unresolvable.
209
+ */
210
+ export declare function resolveThemeState(selection: ThemeSelection, system: ThemeAppearance, registry: readonly ThemeVariant[]): {
211
+ appearance: ThemeAppearance;
212
+ variant: ThemeVariant;
213
+ };
214
+ /**
215
+ * The variant whose terminal palette is pinned, or `undefined` when the terminal
216
+ * follows the interface theme.
217
+ *
218
+ * `theme`, an absent id and an id the registry does not hold all mean the interface
219
+ * palette: a terminal must never resolve to a blank or half-painted palette because a
220
+ * theme was deleted after it was pinned.
221
+ */
222
+ export declare function resolveTerminalVariant(terminalThemeId: string | undefined, registry: readonly ThemeVariant[]): ThemeVariant | undefined;
223
+ /**
224
+ * The font settings a zoom chord asks for: one pixel up or down on every axis, or
225
+ * the defaults for `reset`.
226
+ */
227
+ export declare function zoomedFontSettings(selection: ThemeSelection, intent: FontZoomIntent): AppearanceEditorFontSettings;
228
+ /**
229
+ * The registry a provider resolves against: the catalogue, then the host's extras.
230
+ *
231
+ * The catalogue wins an id collision, because it is listed first and `resolveVariant`
232
+ * takes the first match: a host's variant can add a theme but cannot shadow one.
233
+ */
234
+ export declare function mergeRegistry(builtin: readonly ThemeVariant[], extra: readonly ThemeVariant[] | undefined): readonly ThemeVariant[];
235
+ /**
236
+ * The roles an accent paints over a variant, for {@link ThemeContextValue.accentRolesFor}.
237
+ *
238
+ * Resolved against the committed selection's theme pair, so an `ansi-*` accent means
239
+ * the same colour here as it does on `<html>`. `engine` answers the catalogue
240
+ * questions; `theme-state.ts` binds the full catalogue for callers that have none.
241
+ */
242
+ export declare function accentRolesForStateWith(engine: Pick<ThemeEngine, 'accentRolesForSelection'>, accent: string | null, variantId: string | undefined, state: {
243
+ selection: ThemeSelection;
244
+ variant: ThemeVariant;
245
+ registry: readonly ThemeVariant[];
246
+ accentContrastFloor?: number;
247
+ }): AccentRoles | undefined;
248
+ //# sourceMappingURL=theme-state-core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-state-core.d.ts","sourceRoot":"","sources":["../../../src/components/theme/theme-state-core.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { normalizeCustomAccent } from "../../lib/accent-roles.js";
2
- import { accentRolesForSelection, defaultDarkThemeId, defaultLightThemeId, resolveVariant } from "../../lib/themes.js";
2
+ import { defaultDarkThemeId, defaultLightThemeId, resolveVariant } from "../../lib/theme-defaults.js";
3
3
  import { DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS, normalizeAppearanceEditorFontSettings } from "../../lib/appearance-font-settings.js";
4
4
  import { stepAppearanceFontSizes } from "../../lib/font-zoom.js";
5
- //#region src/components/theme/theme-state.ts
5
+ //#region src/components/theme/theme-state-core.ts
6
6
  /**
7
7
  * Deliberately lists only the fields every consumer has always had. A field added
8
8
  * to {@link ThemeSelection} later gets no default here: `JSON.stringify` drops an
@@ -125,19 +125,20 @@ function mergeRegistry(builtin, extra) {
125
125
  * The roles an accent paints over a variant, for {@link ThemeContextValue.accentRolesFor}.
126
126
  *
127
127
  * Resolved against the committed selection's theme pair, so an `ansi-*` accent means
128
- * the same colour here as it does on `<html>`.
128
+ * the same colour here as it does on `<html>`. `engine` answers the catalogue
129
+ * questions; `theme-state.ts` binds the full catalogue for callers that have none.
129
130
  */
130
- function accentRolesForState(accent, variantId, state) {
131
+ function accentRolesForStateWith(engine, accent, variantId, state) {
131
132
  if (accent === null) return void 0;
132
133
  const variant = variantId === void 0 ? state.variant : state.registry.find((candidate) => candidate.id === variantId);
133
134
  if (!variant) return void 0;
134
- return accentRolesForSelection(accent, variant.appearance, state.selection, {
135
+ return engine.accentRolesForSelection(accent, variant.appearance, state.selection, {
135
136
  background: variant.colors.background,
136
137
  floor: state.accentContrastFloor,
137
138
  variantId: variant.id
138
139
  });
139
140
  }
140
141
  //#endregion
141
- export { accentRolesForState, defaultThemeSelection, initialThemeSelection, mergeRegistry, mergeThemeSelection, resolveAppearance, resolveTerminalVariant, resolveThemeState, resolvedDensity, selectedThemeId, zoomedFontSettings };
142
+ export { accentRolesForStateWith, defaultThemeSelection, initialThemeSelection, mergeRegistry, mergeThemeSelection, resolveAppearance, resolveTerminalVariant, resolveThemeState, resolvedDensity, selectedThemeId, zoomedFontSettings };
142
143
 
143
- //# sourceMappingURL=theme-state.js.map
144
+ //# sourceMappingURL=theme-state-core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-state-core.js","names":[],"sources":["../../../src/components/theme/theme-state-core.ts"],"sourcesContent":["import {\n DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS,\n normalizeAppearanceEditorFontSettings,\n type AppearanceEditorFontSettings,\n} from '#lib/appearance-font-settings'\nimport type { AccentPreset } from '@adea-ai/themes'\nimport { normalizeCustomAccent, type AccentRoles } from '#lib/accent-roles'\nimport { stepAppearanceFontSizes, type FontZoomIntent } from '#lib/font-zoom'\nimport type { ThemeSurface } from '#lib/theme-surface'\nimport { defaultDarkThemeId, defaultLightThemeId, resolveVariant } from '#lib/theme-defaults'\nimport type { ThemeEngine } from '#lib/theme-engine'\nimport type { ThemeAppearance, ThemeVariant } from '#lib/themes'\n\n/**\n * The pure half of the theme provider: what a selection is, how two selections\n * merge, and what a selection resolves to.\n *\n * Nothing here touches the document, storage or a signal, so each rule can be\n * tested by calling it. `ThemeProvider` owns the reactivity and wires these to it;\n * `theme-document.ts` owns the writes; `theme-persistence.ts` owns the store.\n *\n * This is the catalogue-free half: it imports no catalogue code or data, so the lazy\n * provider can use it at start-up. `theme-state.ts` re-exports it and adds\n * {@link accentRolesForState} bound to the full catalogue.\n */\n\n/**\n * Four independent choices, and keeping them independent is the point:\n *\n * - **appearance** — light, dark, or follow the system.\n * - **a theme per appearance** — so a user can run Catppuccin Mocha by night and\n * Solarized Light by day without re-choosing when the system flips.\n * - **an accent** — adea's six presets, or one of the accents the selected theme\n * pair carries itself (`ansi-blue`…), which re-colour the interactive roles on\n * top of whichever theme is active.\n * - **fonts** — separate family and size choices for UI, content and code.\n */\nexport type ThemeSelection = {\n /** `system` follows `prefers-color-scheme` and updates when it changes. */\n appearance: ThemeAppearance | 'system'\n /** The variant to use when the resolved appearance is light. */\n lightThemeId: string\n /** The variant to use when the resolved appearance is dark. */\n darkThemeId: string\n /**\n * An `accentPresets` id, a theme accent id from `themeAccents()` (`ansi-blue`…),\n * `theme` for the variant's own primary, or a custom colour as `#rrggbb`. A theme\n * accent the current pair does not offer falls back to the variant's own primary.\n *\n * A custom colour is stored lower case and is raised to the contrast floor when it\n * is painted, never when it is stored: the selection keeps what the user chose.\n * Anything else that is not an id is dropped at paint, not rejected here.\n */\n accent: string\n /** UI-family alias for consumers of the original single-font preference. */\n font: string\n /** UI, content and code font settings. Omitted legacy records are migrated from `font`. */\n fonts?: AppearanceEditorFontSettings\n /**\n * `comfortable` is the system's default geometry; `compact` moves the control\n * ladder down one rung.\n *\n * Optional, and defaulted on read, so a preference stored before density existed\n * keeps working and `ThemeSelection` stays assignable without it.\n */\n density?: ThemeDensity\n /**\n * The terminal either follows the interface theme (`theme`, or absent) or is pinned\n * to another theme's terminal palette by that theme's id. The pin is written over\n * the interface variant's own `--terminal-*` properties, so a light interface can\n * carry a dark terminal.\n *\n * An id the registry does not hold paints the interface palette and stays in the\n * selection, the same rule as an unknown theme id.\n *\n * Optional and not defaulted: see {@link defaultThemeSelection}.\n */\n terminalThemeId?: string\n /**\n * How opaque elevated panels are. Written to `<html>` only when the provider's\n * `surface` prop is on or this is set; absent, a host paints no surface axis at\n * all. Resolved against the OS and the host's capability before it is painted —\n * see `resolveSurface` — so the value stored is a preference, never the outcome.\n */\n surface?: ThemeSurface\n /**\n * The user's own Reduce Transparency switch, which forces the `opaque` surface the\n * same way the operating system's setting does.\n */\n reduceTransparency?: boolean\n}\n\n/**\n * The two density rungs.\n *\n * `comfortable` is the geometry in `theme.css`, so it is the absence of an\n * attribute rather than a value: a document that ships no `data-density` at all\n * renders correctly, which is what keeps this additive.\n */\nexport type ThemeDensity = 'comfortable' | 'compact'\n\n/**\n * Deliberately lists only the fields every consumer has always had. A field added\n * to {@link ThemeSelection} later gets no default here: `JSON.stringify` drops an\n * `undefined`, so a store written by a consumer that never sets the field stays\n * byte-identical to what it was before the field existed.\n */\nexport const defaultThemeSelection: ThemeSelection = {\n appearance: 'system',\n lightThemeId: defaultLightThemeId,\n darkThemeId: defaultDarkThemeId,\n accent: 'theme',\n font: 'system',\n fonts: DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS,\n density: 'comfortable',\n}\n\n/**\n * A stored selection's density, defaulted.\n *\n * The stored value is read through here rather than trusted, because a preference\n * written before density existed has no `density` key and a hand-edited store may\n * hold anything.\n */\nexport function resolvedDensity(selection: ThemeSelection): ThemeDensity {\n return selection.density === 'compact' ? 'compact' : 'comfortable'\n}\n\nexport type ThemeContextValue = {\n /** The preference as stored. A {@link ThemeContextValue.preview} does not change it. */\n selection: () => ThemeSelection\n /** What the preference resolves to right now, after `system`. */\n resolvedAppearance: () => ThemeAppearance\n /** The variant in use. */\n variant: () => ThemeVariant\n /** Update part of the preference. */\n setSelection: (patch: Partial<ThemeSelection>) => void\n /**\n * The built-in catalogue, so a picker does not import it separately. A plain\n * array: {@link ThemeContextValue.allThemes} is the one that follows a host's\n * extra themes. Under the lazy provider it holds only the default pair until\n * {@link ThemeContextValue.catalogueReady}; read it again afterwards.\n */\n themes: readonly ThemeVariant[]\n /**\n * The accents the selected light/dark pair carries, offered beside the presets.\n * Follows the selection: a different theme pair may offer different slots.\n */\n themeAccents: () => readonly AccentPreset[]\n /**\n * The built-in catalogue followed by the provider's `themes` prop. Reactive when\n * that prop is a function that reads signals.\n */\n allThemes: () => readonly ThemeVariant[]\n /**\n * Paint the document from `draft` laid over the stored selection, without\n * persisting it or changing what {@link ThemeContextValue.selection} returns.\n * `undefined` ends the preview. A {@link ThemeContextValue.setSelection} ends it\n * too, because the committed selection is then what should be showing.\n */\n preview: (draft: Partial<ThemeSelection> | undefined) => void\n /** True while a {@link ThemeContextValue.preview} draft is on the document. */\n previewing: () => boolean\n /**\n * The roles `accent` paints over `variantId` (default: the variant in use), held\n * to the provider's `accentContrastFloor`, for an element-scoped override with\n * `paintAccentRoles` — a workspace that carries its own colour. `undefined` for\n * `null`, for `theme`, for an id that resolves to nothing, and for a variant the\n * registry does not hold: each means \"keep the inherited accent\".\n */\n accentRolesFor: (accent: string | null, variantId?: string) => AccentRoles | undefined\n /**\n * The surface the committed selection paints, after Reduce Transparency and the\n * host's translucency capability. `opaque` when the axis is off. Follows the\n * committed selection, not a preview, like {@link ThemeContextValue.variant}.\n */\n effectiveSurface: () => ThemeSurface\n /**\n * True while the operating system or the selection's `reduceTransparency` forces\n * opaque surfaces. Always false when the surface axis is off, because nothing is\n * then listening to the operating system.\n */\n reduceTransparencyActive: () => boolean\n /**\n * Step the three text sizes together, or put them back to the defaults: what the\n * `fontZoom` prop's chords do, for a host that binds its own keys or a menu. Goes\n * through {@link ThemeContextValue.setSelection}, so it persists and ends a preview.\n */\n zoomFonts: (intent: FontZoomIntent) => void\n /**\n * True when `themes`, `allThemes()` and the variants they resolve are backed by the\n * whole catalogue. Always true under `ThemeProvider`. Under the lazy provider\n * (`@adea-ai/ui/components/theme/lazy`) it turns true once the catalogue has loaded,\n * and until then only the default light and dark themes are available: a stored\n * theme beyond them resolves to its appearance's default (the document keeps painting\n * what the stylesheet gave it) until `catalogueReady()` is true. Absent on a context\n * a host builds itself, where it should be read as true.\n */\n catalogueReady?: () => boolean\n /**\n * Start loading the whole catalogue (idempotent) and resolve when it is available.\n * A picker, a preview or a settings page calls it when it mounts. A no-op under\n * `ThemeProvider`, whose catalogue is already loaded.\n */\n loadCatalogue?: () => Promise<void>\n}\n\n/** A selection with the `undefined` keys of a patch left out. */\nfunction definedOnly<T extends object>(value: T): Partial<T> {\n return Object.fromEntries(\n Object.entries(value).filter(([, entry]) => entry !== undefined)\n ) as Partial<T>\n}\n\n/** Keep the deprecated single-font view synchronized with the UI axis. */\nexport function mergeThemeSelection(\n base: ThemeSelection,\n patch: Partial<ThemeSelection>\n): ThemeSelection {\n const merged = { ...base, ...patch }\n let fonts: AppearanceEditorFontSettings\n\n if (patch.fonts !== undefined) {\n fonts = normalizeAppearanceEditorFontSettings(patch.fonts).settings\n } else if (patch.font !== undefined) {\n const currentFonts = normalizeAppearanceEditorFontSettings(base.fonts, base.font).settings\n fonts = normalizeAppearanceEditorFontSettings({\n ...currentFonts,\n ui: { ...currentFonts.ui, family: patch.font },\n }).settings\n } else {\n fonts = normalizeAppearanceEditorFontSettings(merged.fonts, merged.font).settings\n }\n\n return {\n ...merged,\n // One spelling for a custom colour, so equal choices compare and store equal.\n accent: normalizeCustomAccent(merged.accent) ?? merged.accent,\n fonts,\n font: fonts.ui.family,\n }\n}\n\n/**\n * The selection a provider starts from: the defaults, then whatever the store\n * returned, then `initial`.\n *\n * `initial` wins over the store on every mount. That precedence is part of the\n * contract Cortana relies on (its `initial` pins `appearance` and `font`), and it\n * is why the store's answer is only the middle layer. A store that returned\n * nothing, or threw, or answered with explicit `undefined`s, leaves the defaults\n * standing for those fields.\n */\nexport function initialThemeSelection(\n stored: Partial<ThemeSelection> | undefined,\n initial: Partial<ThemeSelection> | undefined\n): ThemeSelection {\n const base = mergeThemeSelection(defaultThemeSelection, definedOnly(stored ?? {}))\n return mergeThemeSelection(base, initial ?? {})\n}\n\n/** `system` resolved against what the operating system reports. */\nexport function resolveAppearance(\n preference: ThemeSelection['appearance'],\n system: ThemeAppearance\n): ThemeAppearance {\n return preference === 'system' ? system : preference\n}\n\n/** The theme id the selection names for a resolved appearance. */\nexport function selectedThemeId(selection: ThemeSelection, appearance: ThemeAppearance): string {\n return appearance === 'dark' ? selection.darkThemeId : selection.lightThemeId\n}\n\n/**\n * What a selection paints: the resolved appearance and the variant for it.\n *\n * An id the registry does not hold resolves to the appearance's default rather than\n * being rewritten (see `resolveVariant`), so the selection a host stores is never\n * changed by being unresolvable.\n */\nexport function resolveThemeState(\n selection: ThemeSelection,\n system: ThemeAppearance,\n registry: readonly ThemeVariant[]\n): { appearance: ThemeAppearance; variant: ThemeVariant } {\n const appearance = resolveAppearance(selection.appearance, system)\n return {\n appearance,\n variant: resolveVariant(registry, selectedThemeId(selection, appearance), appearance),\n }\n}\n\n/**\n * The variant whose terminal palette is pinned, or `undefined` when the terminal\n * follows the interface theme.\n *\n * `theme`, an absent id and an id the registry does not hold all mean the interface\n * palette: a terminal must never resolve to a blank or half-painted palette because a\n * theme was deleted after it was pinned.\n */\nexport function resolveTerminalVariant(\n terminalThemeId: string | undefined,\n registry: readonly ThemeVariant[]\n): ThemeVariant | undefined {\n if (terminalThemeId === undefined || terminalThemeId === 'theme') return undefined\n return registry.find((candidate) => candidate.id === terminalThemeId)\n}\n\n/**\n * The font settings a zoom chord asks for: one pixel up or down on every axis, or\n * the defaults for `reset`.\n */\nexport function zoomedFontSettings(\n selection: ThemeSelection,\n intent: FontZoomIntent\n): AppearanceEditorFontSettings {\n if (intent === 'reset') return { ...DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS }\n return stepAppearanceFontSizes(selection.fonts, intent === 'in' ? 1 : -1)\n}\n\n/**\n * The registry a provider resolves against: the catalogue, then the host's extras.\n *\n * The catalogue wins an id collision, because it is listed first and `resolveVariant`\n * takes the first match: a host's variant can add a theme but cannot shadow one.\n */\nexport function mergeRegistry(\n builtin: readonly ThemeVariant[],\n extra: readonly ThemeVariant[] | undefined\n): readonly ThemeVariant[] {\n return extra && extra.length > 0 ? [...builtin, ...extra] : builtin\n}\n\n/**\n * The roles an accent paints over a variant, for {@link ThemeContextValue.accentRolesFor}.\n *\n * Resolved against the committed selection's theme pair, so an `ansi-*` accent means\n * the same colour here as it does on `<html>`. `engine` answers the catalogue\n * questions; `theme-state.ts` binds the full catalogue for callers that have none.\n */\nexport function accentRolesForStateWith(\n engine: Pick<ThemeEngine, 'accentRolesForSelection'>,\n accent: string | null,\n variantId: string | undefined,\n state: {\n selection: ThemeSelection\n variant: ThemeVariant\n registry: readonly ThemeVariant[]\n accentContrastFloor?: number\n }\n): AccentRoles | undefined {\n if (accent === null) return undefined\n const variant =\n variantId === undefined\n ? state.variant\n : state.registry.find((candidate) => candidate.id === variantId)\n if (!variant) return undefined\n return engine.accentRolesForSelection(accent, variant.appearance, state.selection, {\n background: variant.colors.background,\n floor: state.accentContrastFloor,\n variantId: variant.id,\n })\n}\n"],"mappings":";;;;;;;;;;;AA2GA,IAAa,wBAAwC;CACnD,YAAY;CACZ,cAAc;CACd,aAAa;CACb,QAAQ;CACR,MAAM;CACN,OAAO;CACP,SAAS;AACX;;;;;;;;AASA,SAAgB,gBAAgB,WAAyC;CACvE,OAAO,UAAU,YAAY,YAAY,YAAY;AACvD;;AAkFA,SAAS,YAA8B,OAAsB;CAC3D,OAAO,OAAO,YACZ,OAAO,QAAQ,KAAK,CAAC,CAAC,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,CACjE;AACF;;AAGA,SAAgB,oBACd,MACA,OACgB;CAChB,MAAM,SAAS;EAAE,GAAG;EAAM,GAAG;CAAM;CACnC,IAAI;CAEJ,IAAI,MAAM,UAAU,KAAA,GAClB,QAAQ,sCAAsC,MAAM,KAAK,CAAC,CAAC;MACtD,IAAI,MAAM,SAAS,KAAA,GAAW;EACnC,MAAM,eAAe,sCAAsC,KAAK,OAAO,KAAK,IAAI,CAAC,CAAC;EAClF,QAAQ,sCAAsC;GAC5C,GAAG;GACH,IAAI;IAAE,GAAG,aAAa;IAAI,QAAQ,MAAM;GAAK;EAC/C,CAAC,CAAC,CAAC;CACL,OACE,QAAQ,sCAAsC,OAAO,OAAO,OAAO,IAAI,CAAC,CAAC;CAG3E,OAAO;EACL,GAAG;EAEH,QAAQ,sBAAsB,OAAO,MAAM,KAAK,OAAO;EACvD;EACA,MAAM,MAAM,GAAG;CACjB;AACF;;;;;;;;;;;AAYA,SAAgB,sBACd,QACA,SACgB;CAEhB,OAAO,oBADM,oBAAoB,uBAAuB,YAAY,UAAU,CAAC,CAAC,CACrD,GAAM,WAAW,CAAC,CAAC;AAChD;;AAGA,SAAgB,kBACd,YACA,QACiB;CACjB,OAAO,eAAe,WAAW,SAAS;AAC5C;;AAGA,SAAgB,gBAAgB,WAA2B,YAAqC;CAC9F,OAAO,eAAe,SAAS,UAAU,cAAc,UAAU;AACnE;;;;;;;;AASA,SAAgB,kBACd,WACA,QACA,UACwD;CACxD,MAAM,aAAa,kBAAkB,UAAU,YAAY,MAAM;CACjE,OAAO;EACL;EACA,SAAS,eAAe,UAAU,gBAAgB,WAAW,UAAU,GAAG,UAAU;CACtF;AACF;;;;;;;;;AAUA,SAAgB,uBACd,iBACA,UAC0B;CAC1B,IAAI,oBAAoB,KAAA,KAAa,oBAAoB,SAAS,OAAO,KAAA;CACzE,OAAO,SAAS,MAAM,cAAc,UAAU,OAAO,eAAe;AACtE;;;;;AAMA,SAAgB,mBACd,WACA,QAC8B;CAC9B,IAAI,WAAW,SAAS,OAAO,EAAE,GAAG,wCAAwC;CAC5E,OAAO,wBAAwB,UAAU,OAAO,WAAW,OAAO,IAAI,EAAE;AAC1E;;;;;;;AAQA,SAAgB,cACd,SACA,OACyB;CACzB,OAAO,SAAS,MAAM,SAAS,IAAI,CAAC,GAAG,SAAS,GAAG,KAAK,IAAI;AAC9D;;;;;;;;AASA,SAAgB,wBACd,QACA,QACA,WACA,OAMyB;CACzB,IAAI,WAAW,MAAM,OAAO,KAAA;CAC5B,MAAM,UACJ,cAAc,KAAA,IACV,MAAM,UACN,MAAM,SAAS,MAAM,cAAc,UAAU,OAAO,SAAS;CACnE,IAAI,CAAC,SAAS,OAAO,KAAA;CACrB,OAAO,OAAO,wBAAwB,QAAQ,QAAQ,YAAY,MAAM,WAAW;EACjF,YAAY,QAAQ,OAAO;EAC3B,OAAO,MAAM;EACb,WAAW,QAAQ;CACrB,CAAC;AACH"}
@@ -1,220 +1,14 @@
1
- import { type AppearanceEditorFontSettings } from '#lib/appearance-font-settings';
2
- import type { AccentPreset } from '@adea-ai/themes';
3
- import { type AccentRoles } from '#lib/accent-roles';
4
- import { type FontZoomIntent } from '#lib/font-zoom';
5
- import type { ThemeSurface } from '#lib/theme-surface';
6
- import { type ThemeAppearance, type ThemeVariant } from '#lib/themes';
1
+ import type { AccentRoles } from '#lib/accent-roles';
2
+ import type { ThemeVariant } from '#lib/themes';
3
+ import { type ThemeSelection } from './theme-state-core';
4
+ export * from './theme-state-core';
7
5
  /**
8
- * The pure half of the theme provider: what a selection is, how two selections
9
- * merge, and what a selection resolves to.
10
- *
11
- * Nothing here touches the document, storage or a signal, so each rule can be
12
- * tested by calling it. `ThemeProvider` owns the reactivity and wires these to it;
13
- * `theme-document.ts` owns the writes; `theme-persistence.ts` owns the store.
14
- */
15
- /**
16
- * Four independent choices, and keeping them independent is the point:
17
- *
18
- * - **appearance** — light, dark, or follow the system.
19
- * - **a theme per appearance** — so a user can run Catppuccin Mocha by night and
20
- * Solarized Light by day without re-choosing when the system flips.
21
- * - **an accent** — adea's six presets, or one of the accents the selected theme
22
- * pair carries itself (`ansi-blue`…), which re-colour the interactive roles on
23
- * top of whichever theme is active.
24
- * - **fonts** — separate family and size choices for UI, content and code.
25
- */
26
- export type ThemeSelection = {
27
- /** `system` follows `prefers-color-scheme` and updates when it changes. */
28
- appearance: ThemeAppearance | 'system';
29
- /** The variant to use when the resolved appearance is light. */
30
- lightThemeId: string;
31
- /** The variant to use when the resolved appearance is dark. */
32
- darkThemeId: string;
33
- /**
34
- * An `accentPresets` id, a theme accent id from `themeAccents()` (`ansi-blue`…),
35
- * `theme` for the variant's own primary, or a custom colour as `#rrggbb`. A theme
36
- * accent the current pair does not offer falls back to the variant's own primary.
37
- *
38
- * A custom colour is stored lower case and is raised to the contrast floor when it
39
- * is painted, never when it is stored: the selection keeps what the user chose.
40
- * Anything else that is not an id is dropped at paint, not rejected here.
41
- */
42
- accent: string;
43
- /** UI-family alias for consumers of the original single-font preference. */
44
- font: string;
45
- /** UI, content and code font settings. Omitted legacy records are migrated from `font`. */
46
- fonts?: AppearanceEditorFontSettings;
47
- /**
48
- * `comfortable` is the system's default geometry; `compact` moves the control
49
- * ladder down one rung.
50
- *
51
- * Optional, and defaulted on read, so a preference stored before density existed
52
- * keeps working and `ThemeSelection` stays assignable without it.
53
- */
54
- density?: ThemeDensity;
55
- /**
56
- * The terminal either follows the interface theme (`theme`, or absent) or is pinned
57
- * to another theme's terminal palette by that theme's id. The pin is written over
58
- * the interface variant's own `--terminal-*` properties, so a light interface can
59
- * carry a dark terminal.
60
- *
61
- * An id the registry does not hold paints the interface palette and stays in the
62
- * selection, the same rule as an unknown theme id.
63
- *
64
- * Optional and not defaulted: see {@link defaultThemeSelection}.
65
- */
66
- terminalThemeId?: string;
67
- /**
68
- * How opaque elevated panels are. Written to `<html>` only when the provider's
69
- * `surface` prop is on or this is set; absent, a host paints no surface axis at
70
- * all. Resolved against the OS and the host's capability before it is painted —
71
- * see `resolveSurface` — so the value stored is a preference, never the outcome.
72
- */
73
- surface?: ThemeSurface;
74
- /**
75
- * The user's own Reduce Transparency switch, which forces the `opaque` surface the
76
- * same way the operating system's setting does.
77
- */
78
- reduceTransparency?: boolean;
79
- };
80
- /**
81
- * The two density rungs.
82
- *
83
- * `comfortable` is the geometry in `theme.css`, so it is the absence of an
84
- * attribute rather than a value: a document that ships no `data-density` at all
85
- * renders correctly, which is what keeps this additive.
86
- */
87
- export type ThemeDensity = 'comfortable' | 'compact';
88
- /**
89
- * Deliberately lists only the fields every consumer has always had. A field added
90
- * to {@link ThemeSelection} later gets no default here: `JSON.stringify` drops an
91
- * `undefined`, so a store written by a consumer that never sets the field stays
92
- * byte-identical to what it was before the field existed.
93
- */
94
- export declare const defaultThemeSelection: ThemeSelection;
95
- /**
96
- * A stored selection's density, defaulted.
97
- *
98
- * The stored value is read through here rather than trusted, because a preference
99
- * written before density existed has no `density` key and a hand-edited store may
100
- * hold anything.
101
- */
102
- export declare function resolvedDensity(selection: ThemeSelection): ThemeDensity;
103
- export type ThemeContextValue = {
104
- /** The preference as stored. A {@link ThemeContextValue.preview} does not change it. */
105
- selection: () => ThemeSelection;
106
- /** What the preference resolves to right now, after `system`. */
107
- resolvedAppearance: () => ThemeAppearance;
108
- /** The variant in use. */
109
- variant: () => ThemeVariant;
110
- /** Update part of the preference. */
111
- setSelection: (patch: Partial<ThemeSelection>) => void;
112
- /**
113
- * The built-in catalogue, so a picker does not import it separately. A plain
114
- * array: {@link ThemeContextValue.allThemes} is the one that follows a host's
115
- * extra themes.
116
- */
117
- themes: readonly ThemeVariant[];
118
- /**
119
- * The accents the selected light/dark pair carries, offered beside the presets.
120
- * Follows the selection: a different theme pair may offer different slots.
121
- */
122
- themeAccents: () => readonly AccentPreset[];
123
- /**
124
- * The built-in catalogue followed by the provider's `themes` prop. Reactive when
125
- * that prop is a function that reads signals.
126
- */
127
- allThemes: () => readonly ThemeVariant[];
128
- /**
129
- * Paint the document from `draft` laid over the stored selection, without
130
- * persisting it or changing what {@link ThemeContextValue.selection} returns.
131
- * `undefined` ends the preview. A {@link ThemeContextValue.setSelection} ends it
132
- * too, because the committed selection is then what should be showing.
133
- */
134
- preview: (draft: Partial<ThemeSelection> | undefined) => void;
135
- /** True while a {@link ThemeContextValue.preview} draft is on the document. */
136
- previewing: () => boolean;
137
- /**
138
- * The roles `accent` paints over `variantId` (default: the variant in use), held
139
- * to the provider's `accentContrastFloor`, for an element-scoped override with
140
- * `paintAccentRoles` — a workspace that carries its own colour. `undefined` for
141
- * `null`, for `theme`, for an id that resolves to nothing, and for a variant the
142
- * registry does not hold: each means "keep the inherited accent".
143
- */
144
- accentRolesFor: (accent: string | null, variantId?: string) => AccentRoles | undefined;
145
- /**
146
- * The surface the committed selection paints, after Reduce Transparency and the
147
- * host's translucency capability. `opaque` when the axis is off. Follows the
148
- * committed selection, not a preview, like {@link ThemeContextValue.variant}.
149
- */
150
- effectiveSurface: () => ThemeSurface;
151
- /**
152
- * True while the operating system or the selection's `reduceTransparency` forces
153
- * opaque surfaces. Always false when the surface axis is off, because nothing is
154
- * then listening to the operating system.
155
- */
156
- reduceTransparencyActive: () => boolean;
157
- /**
158
- * Step the three text sizes together, or put them back to the defaults: what the
159
- * `fontZoom` prop's chords do, for a host that binds its own keys or a menu. Goes
160
- * through {@link ThemeContextValue.setSelection}, so it persists and ends a preview.
161
- */
162
- zoomFonts: (intent: FontZoomIntent) => void;
163
- };
164
- /** Keep the deprecated single-font view synchronized with the UI axis. */
165
- export declare function mergeThemeSelection(base: ThemeSelection, patch: Partial<ThemeSelection>): ThemeSelection;
166
- /**
167
- * The selection a provider starts from: the defaults, then whatever the store
168
- * returned, then `initial`.
169
- *
170
- * `initial` wins over the store on every mount. That precedence is part of the
171
- * contract Cortana relies on (its `initial` pins `appearance` and `font`), and it
172
- * is why the store's answer is only the middle layer. A store that returned
173
- * nothing, or threw, or answered with explicit `undefined`s, leaves the defaults
174
- * standing for those fields.
175
- */
176
- export declare function initialThemeSelection(stored: Partial<ThemeSelection> | undefined, initial: Partial<ThemeSelection> | undefined): ThemeSelection;
177
- /** `system` resolved against what the operating system reports. */
178
- export declare function resolveAppearance(preference: ThemeSelection['appearance'], system: ThemeAppearance): ThemeAppearance;
179
- /** The theme id the selection names for a resolved appearance. */
180
- export declare function selectedThemeId(selection: ThemeSelection, appearance: ThemeAppearance): string;
181
- /**
182
- * What a selection paints: the resolved appearance and the variant for it.
183
- *
184
- * An id the registry does not hold resolves to the appearance's default rather than
185
- * being rewritten (see `resolveVariant`), so the selection a host stores is never
186
- * changed by being unresolvable.
187
- */
188
- export declare function resolveThemeState(selection: ThemeSelection, system: ThemeAppearance, registry: readonly ThemeVariant[]): {
189
- appearance: ThemeAppearance;
190
- variant: ThemeVariant;
191
- };
192
- /**
193
- * The variant whose terminal palette is pinned, or `undefined` when the terminal
194
- * follows the interface theme.
195
- *
196
- * `theme`, an absent id and an id the registry does not hold all mean the interface
197
- * palette: a terminal must never resolve to a blank or half-painted palette because a
198
- * theme was deleted after it was pinned.
199
- */
200
- export declare function resolveTerminalVariant(terminalThemeId: string | undefined, registry: readonly ThemeVariant[]): ThemeVariant | undefined;
201
- /**
202
- * The font settings a zoom chord asks for: one pixel up or down on every axis, or
203
- * the defaults for `reset`.
204
- */
205
- export declare function zoomedFontSettings(selection: ThemeSelection, intent: FontZoomIntent): AppearanceEditorFontSettings;
206
- /**
207
- * The registry a provider resolves against: the catalogue, then the host's extras.
208
- *
209
- * The catalogue wins an id collision, because it is listed first and `resolveVariant`
210
- * takes the first match: a host's variant can add a theme but cannot shadow one.
211
- */
212
- export declare function mergeRegistry(builtin: readonly ThemeVariant[], extra: readonly ThemeVariant[] | undefined): readonly ThemeVariant[];
213
- /**
214
- * The roles an accent paints over a variant, for {@link ThemeContextValue.accentRolesFor}.
6
+ * The roles an accent paints over a variant, for `ThemeContextValue.accentRolesFor`,
7
+ * resolved against the whole catalogue.
215
8
  *
216
9
  * Resolved against the committed selection's theme pair, so an `ansi-*` accent means
217
- * the same colour here as it does on `<html>`.
10
+ * the same colour here as it does on `<html>`. The lazy provider uses
11
+ * {@link accentRolesForStateWith} with the engine it has.
218
12
  */
219
13
  export declare function accentRolesForState(accent: string | null, variantId: string | undefined, state: {
220
14
  selection: ThemeSelection;
@@ -1 +1 @@
1
- {"version":3,"file":"theme-state.d.ts","sourceRoot":"","sources":["../../../src/components/theme/theme-state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"theme-state.d.ts","sourceRoot":"","sources":["../../../src/components/theme/theme-state.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAA2B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAEjF,cAAc,oBAAoB,CAAA;AAElC;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,KAAK,EAAE;IACL,SAAS,EAAE,cAAc,CAAA;IACzB,OAAO,EAAE,YAAY,CAAA;IACrB,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAA;IACjC,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,GACA,WAAW,GAAG,SAAS,CAEzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"theme-toggle.js","names":["Monitor","Moon","Sun","ComponentProps","For","splitProps","cn","ToggleGroup","ToggleGroupItem","useTheme","ThemeToggleProps","Omit","withLabels","APPEARANCES","value","label","icon","const","ThemeToggle","props","local","rest","selection","setSelection","_$createComponent","_$mergeProps","attached","appearance","onChange","class","children","each","variant","size","aria-label","_$memo","_el$","_tmpl$","_$insert","_$createComponent","_$mergeProps","_$memo"],"sources":["../../../src/components/theme/theme-toggle.tsx"],"sourcesContent":["import { Monitor, Moon, Sun } from 'lucide-solid'\nimport type { ComponentProps } from 'solid-js'\nimport { For, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group/toggle-group'\nimport { useTheme } from './theme-provider'\n\n/**\n * ThemeToggle.\n *\n * Three states, not two: light, dark, and follow the system. A two-state switch is\n * the common mistake — it forces a choice on someone who wanted the application to\n * track their OS, and it has no way to say \"I have not decided\", so the first click\n * silently overrides a system preference the user set deliberately.\n *\n * The icons carry the state and the accessible name comes from the label, so the\n * control is usable without seeing which glyph is lit.\n */\nexport type ThemeToggleProps = Omit<ComponentProps<typeof ToggleGroup>, 'children'> & {\n /** Draw labels beside the icons. Off by default: a toolbar wants icons. */\n withLabels?: boolean\n}\n\nconst APPEARANCES = [\n { value: 'light', label: 'Light', icon: Sun },\n { value: 'dark', label: 'Dark', icon: Moon },\n { value: 'system', label: 'System', icon: Monitor },\n] as const\n\nexport function ThemeToggle(props: ThemeToggleProps) {\n const [local, rest] = splitProps(props, ['class', 'withLabels'])\n const { selection, setSelection } = useTheme()\n\n return (\n <ToggleGroup\n attached\n value={selection().appearance}\n onChange={(value: string | null) => {\n // A toggle group reports the pressed value or null; null would leave the\n // appearance unset, so a re-press keeps the current one.\n if (typeof value === 'string') {\n setSelection({ appearance: value as 'light' | 'dark' | 'system' })\n }\n }}\n aria-label=\"Appearance\"\n class={cn(local.class)}\n {...rest}\n >\n <For each={APPEARANCES}>\n {(appearance) => (\n <ToggleGroupItem\n value={appearance.value}\n variant=\"outline\"\n size={local.withLabels ? 'sm' : 'icon-sm'}\n aria-label={appearance.label}\n >\n <appearance.icon />\n {local.withLabels ? <span>{appearance.label}</span> : null}\n </ToggleGroupItem>\n )}\n </For>\n </ToggleGroup>\n )\n}\n"],"mappings":";;;;;;;;AAuBA,IAAMa,cAAc;CAClB;EAAEC,OAAO;EAASC,OAAO;EAASC,MAAMd;CAAI;CAC5C;EAAEY,OAAO;EAAQC,OAAO;EAAQC,MAAMf;CAAK;CAC3C;EAAEa,OAAO;EAAUC,OAAO;EAAUC,MAAMhB;CAAQ;AAAC;AAGrD,SAAgBkB,YAAYC,OAAyB;CACnD,MAAM,CAACC,OAAOC,QAAQhB,WAAWc,OAAO,CAAC,SAAS,YAAY,CAAC;CAC/D,MAAM,EAAEG,WAAWC,iBAAiBd,SAAS;CAE7C,OAAAe,gBACGjB,aAAWkB,WAAA;EACVC,UAAQ;EAAA,IACRZ,QAAK;GAAA,OAAEQ,UAAU,CAAC,CAACK;EAAU;EAC7BC,WAAWd,UAAyB;GAGlC,IAAI,OAAOA,UAAU,UACnBS,aAAa,EAAEI,YAAYb,MAAqC,CAAC;EAErE;EAAC,cAAA;EAAA,KAAA,WAAA;GAAA,OAEMR,GAAGc,MAAMS,KAAK;EAAC;CAAA,GAClBR,MAAI,EAAA,IAAAS,WAAA;EAAA,OAAAN,gBAEPpB,KAAG;GAAC2B,MAAMlB;GAAWiB,WAClBH,eAAUH,gBACThB,iBAAe;IAAA,IACdM,QAAK;KAAA,OAAEa,WAAWb;IAAK;IACvBkB,SAAO;IAAA,IACPC,OAAI;KAAA,OAAEb,MAAMR,aAAa,OAAO;IAAS;IAAA,KAAA,gBAAA;KAAA,OAC7Be,WAAWZ;IAAK;IAAA,IAAAe,WAAA;KAAA,OAAA,CAAAN,gBAE3BG,WAAWX,MAAI,CAAA,CAAA,GAAAmB,WACfA,WAAA,CAAA,CAAAf,MAAMR,UAAU,CAAA,CAAA,WAAA;MAAA,IAAAwB,OAAAC,OAAA;MAAAC,OAAAF,YAAUT,WAAWZ,KAAK;MAAA,OAAAqB;KAAA,EAAA,CAAA,IAAW,IAAI,CAAA;IAAA;GAAA,CAAA;EAE7D,CAAA;CAAA,EAAA,CAAA,CAAA;AAIT"}
1
+ {"version":3,"file":"theme-toggle.js","names":["Monitor","Moon","Sun","ComponentProps","For","splitProps","cn","ToggleGroup","ToggleGroupItem","useTheme","ThemeToggleProps","Omit","withLabels","APPEARANCES","value","label","icon","const","ThemeToggle","props","local","rest","selection","setSelection","_$createComponent","_$mergeProps","attached","appearance","onChange","class","children","each","variant","size","aria-label","_$memo","_el$","_tmpl$","_$insert","_$createComponent","_$mergeProps","_$memo"],"sources":["../../../src/components/theme/theme-toggle.tsx"],"sourcesContent":["import { Monitor, Moon, Sun } from 'lucide-solid'\nimport type { ComponentProps } from 'solid-js'\nimport { For, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group/toggle-group'\nimport { useTheme } from './theme-context'\n\n/**\n * ThemeToggle.\n *\n * Three states, not two: light, dark, and follow the system. A two-state switch is\n * the common mistake — it forces a choice on someone who wanted the application to\n * track their OS, and it has no way to say \"I have not decided\", so the first click\n * silently overrides a system preference the user set deliberately.\n *\n * The icons carry the state and the accessible name comes from the label, so the\n * control is usable without seeing which glyph is lit.\n */\nexport type ThemeToggleProps = Omit<ComponentProps<typeof ToggleGroup>, 'children'> & {\n /** Draw labels beside the icons. Off by default: a toolbar wants icons. */\n withLabels?: boolean\n}\n\nconst APPEARANCES = [\n { value: 'light', label: 'Light', icon: Sun },\n { value: 'dark', label: 'Dark', icon: Moon },\n { value: 'system', label: 'System', icon: Monitor },\n] as const\n\nexport function ThemeToggle(props: ThemeToggleProps) {\n const [local, rest] = splitProps(props, ['class', 'withLabels'])\n const { selection, setSelection } = useTheme()\n\n return (\n <ToggleGroup\n attached\n value={selection().appearance}\n onChange={(value: string | null) => {\n // A toggle group reports the pressed value or null; null would leave the\n // appearance unset, so a re-press keeps the current one.\n if (typeof value === 'string') {\n setSelection({ appearance: value as 'light' | 'dark' | 'system' })\n }\n }}\n aria-label=\"Appearance\"\n class={cn(local.class)}\n {...rest}\n >\n <For each={APPEARANCES}>\n {(appearance) => (\n <ToggleGroupItem\n value={appearance.value}\n variant=\"outline\"\n size={local.withLabels ? 'sm' : 'icon-sm'}\n aria-label={appearance.label}\n >\n <appearance.icon />\n {local.withLabels ? <span>{appearance.label}</span> : null}\n </ToggleGroupItem>\n )}\n </For>\n </ToggleGroup>\n )\n}\n"],"mappings":";;;;;;;;AAuBA,IAAMa,cAAc;CAClB;EAAEC,OAAO;EAASC,OAAO;EAASC,MAAMd;CAAI;CAC5C;EAAEY,OAAO;EAAQC,OAAO;EAAQC,MAAMf;CAAK;CAC3C;EAAEa,OAAO;EAAUC,OAAO;EAAUC,MAAMhB;CAAQ;AAAC;AAGrD,SAAgBkB,YAAYC,OAAyB;CACnD,MAAM,CAACC,OAAOC,QAAQhB,WAAWc,OAAO,CAAC,SAAS,YAAY,CAAC;CAC/D,MAAM,EAAEG,WAAWC,iBAAiBd,SAAS;CAE7C,OAAAe,gBACGjB,aAAWkB,WAAA;EACVC,UAAQ;EAAA,IACRZ,QAAK;GAAA,OAAEQ,UAAU,CAAC,CAACK;EAAU;EAC7BC,WAAWd,UAAyB;GAGlC,IAAI,OAAOA,UAAU,UACnBS,aAAa,EAAEI,YAAYb,MAAqC,CAAC;EAErE;EAAC,cAAA;EAAA,KAAA,WAAA;GAAA,OAEMR,GAAGc,MAAMS,KAAK;EAAC;CAAA,GAClBR,MAAI,EAAA,IAAAS,WAAA;EAAA,OAAAN,gBAEPpB,KAAG;GAAC2B,MAAMlB;GAAWiB,WAClBH,eAAUH,gBACThB,iBAAe;IAAA,IACdM,QAAK;KAAA,OAAEa,WAAWb;IAAK;IACvBkB,SAAO;IAAA,IACPC,OAAI;KAAA,OAAEb,MAAMR,aAAa,OAAO;IAAS;IAAA,KAAA,gBAAA;KAAA,OAC7Be,WAAWZ;IAAK;IAAA,IAAAe,WAAA;KAAA,OAAA,CAAAN,gBAE3BG,WAAWX,MAAI,CAAA,CAAA,GAAAmB,WACfA,WAAA,CAAA,CAAAf,MAAMR,UAAU,CAAA,CAAA,WAAA;MAAA,IAAAwB,OAAAC,OAAA;MAAAC,OAAAF,YAAUT,WAAWZ,KAAK;MAAA,OAAAqB;KAAA,EAAA,CAAA,IAAW,IAAI,CAAA;IAAA;GAAA,CAAA;EAE7D,CAAA;CAAA,EAAA,CAAA,CAAA;AAIT"}
package/dist/index.js CHANGED
@@ -102,17 +102,20 @@ import { TranscriptComposition, transcriptDisclosureKey } from "./components/con
102
102
  import { AttachmentCard, ThreadPanel } from "./components/conversation/thread-panel.js";
103
103
  import { PASTE_THRESHOLD_CHARS, PASTE_THRESHOLD_LINES, PASTE_TOKEN_REGEX, countLines, expandAll, findTokenRanges, formatToken, isPasteBlock, nextSeq, pruneBlocks, recollapsePastes, remapCarriedBlocks, shouldCollapse, stripTrailingBlankLines, tokenRangeAt } from "./components/conversation/paste-tokens.js";
104
104
  import { fontOptions } from "./lib/font-catalog.js";
105
- import { accentPresets, allTokens, colorTokens, densityTokens, designTokens, elevationTokens, motionTokens, radiusTokens, typographyTokens, undocumentedTokenAliases, zIndexTokens } from "./lib/tokens.js";
105
+ import { accentPresets } from "./lib/accent-presets.js";
106
+ import { allTokens, colorTokens, densityTokens, designTokens, elevationTokens, motionTokens, radiusTokens, typographyTokens, undocumentedTokenAliases, zIndexTokens } from "./lib/tokens.js";
106
107
  import { isCustomAccent, normalizeCustomAccent, paintAccentRoles, resolveAccentRoles } from "./lib/accent-roles.js";
107
108
  import { isThemeAccentId } from "./lib/theme-accents.js";
108
- import { appearancePreviewThemes, builtinThemes, contrastRatio, defaultDarkThemeId, defaultLightThemeId, editorPaletteFor, resolveAccentPreset, resolveVariant, terminalCssVariables, themeAccentsFor, themeById, themeCssVariables, themeFamilies, themeRecordById, themeRecords, themesForAppearance, tokensAsHex, validateTheme, validateThemeRegistry, variantFromRecord, withAccent } from "./lib/themes.js";
109
+ import { terminalCssVariables, tokensAsHex } from "./lib/theme-css.js";
110
+ import { defaultDarkThemeId, defaultLightThemeId, resolveVariant } from "./lib/theme-defaults.js";
111
+ import { appearancePreviewThemes, builtinThemes, contrastRatio, editorPaletteFor, resolveAccentPreset, themeAccentsFor, themeById, themeCssVariables, themeFamilies, themeRecordById, themeRecords, themesForAppearance, validateTheme, validateThemeRegistry, variantFromRecord, withAccent } from "./lib/themes.js";
109
112
  import { APPEARANCE_EDITOR_FONT_AXES, APPEARANCE_EDITOR_FONT_SIZE_MAX, APPEARANCE_EDITOR_FONT_SIZE_MIN, DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS, applyAppearanceFontSettings, fontSettingsBootstrapScript, normalizeAppearanceEditorFontSettings, normalizeAppearanceEditorFontSize } from "./lib/appearance-font-settings.js";
110
113
  import { AppearanceFontSettingsGroup } from "./components/composites/appearance-editor/font-settings-group.js";
111
114
  import { ThemePicker, ThemePreview, ThemeSwatch } from "./components/theme/theme-picker.js";
115
+ import { useOptionalTheme, useTheme } from "./components/theme/theme-context.js";
112
116
  import { fontZoomShortcut, stepAppearanceFontSizes } from "./lib/font-zoom.js";
113
117
  import { REDUCED_TRANSPARENCY_QUERY, normalizeSurface, resolveSurface, resolveSurfaceState, surfaceAlpha, themeSurfaces } from "./lib/theme-surface.js";
114
- import { useOptionalTheme, useTheme } from "./components/theme/theme-context.js";
115
- import { defaultThemeSelection, resolvedDensity } from "./components/theme/theme-state.js";
118
+ import { defaultThemeSelection, resolvedDensity } from "./components/theme/theme-state-core.js";
116
119
  import { defaultPreferenceStorage, quarantineRecord, readLegacyValue, readVersionedRecord, recoveryEnvelope, recoveryKeyFor, writeVersionedRecord } from "./lib/preference-store.js";
117
120
  import { createVersionedPersistence, localStoragePersistence } from "./components/theme/theme-persistence.js";
118
121
  import { inlineScriptLiteral, themeScript } from "./components/theme/theme-script.js";