@d0nj/dsh-ui-custom 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Families fetched from a font CDN on demand.
3
+ *
4
+ * @module @d0nj/dsh-ui-custom/hosted-fonts
5
+ */
6
+ /** Role a family is suited to, used to group the picker. */
7
+ export type FontCategory = 'sans' | 'serif' | 'mono' | 'display';
8
+ /** One hosted family this package can load. */
9
+ export interface HostedFont {
10
+ /** CSS family name, exactly as the stylesheet declares it. */
11
+ readonly family: string;
12
+ /** Grouping in the picker. */
13
+ readonly category: FontCategory;
14
+ /** Weights requested from the CDN; 400 is enough for body text. */
15
+ readonly weights?: readonly number[];
16
+ }
17
+ export declare const HOSTED_FONTS: readonly HostedFont[];
18
+ /**
19
+ * @param font - the family to request.
20
+ * @returns an absolute stylesheet URL.
21
+ */
22
+ export declare function hostedFontUrl(font: HostedFont): string;
23
+ /**
24
+ * Load one family into the page; resolves on failure too, because a font that
25
+ * cannot be fetched must not break the panel.
26
+ *
27
+ * @param font - the family to load.
28
+ * @param doc - document to load into; defaults to the global one.
29
+ */
30
+ export declare function loadHostedFont(font: HostedFont, doc?: Document | undefined): Promise<void>;
31
+ /**
32
+ * @param fonts - catalog to read.
33
+ * @returns family names, sorted.
34
+ */
35
+ export declare function hostedFamilies(fonts?: readonly HostedFont[]): string[];
36
+ /**
37
+ * @param family - family name to find.
38
+ * @param fonts - catalog to search.
39
+ * @returns the entry, or `undefined` when the name is not hosted here.
40
+ */
41
+ export declare function findHostedFont(family: string, fonts?: readonly HostedFont[]): HostedFont | undefined;
42
+ //# sourceMappingURL=hosted-fonts.d.ts.map
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Families fetched from a font CDN on demand.
3
+ *
4
+ * @module @d0nj/dsh-ui-custom/hosted-fonts
5
+ */
6
+ export const HOSTED_FONTS = [
7
+ { family: 'Inter', category: 'sans' },
8
+ { family: 'IBM Plex Sans', category: 'sans' },
9
+ { family: 'Source Sans 3', category: 'sans' },
10
+ { family: 'Public Sans', category: 'sans' },
11
+ { family: 'Work Sans', category: 'sans' },
12
+ { family: 'DM Sans', category: 'sans' },
13
+ { family: 'Manrope', category: 'sans' },
14
+ { family: 'Figtree', category: 'sans' },
15
+ { family: 'Nunito Sans', category: 'sans' },
16
+ { family: 'Open Sans', category: 'sans' },
17
+ { family: 'Roboto', category: 'sans' },
18
+ { family: 'Lato', category: 'sans' },
19
+ { family: 'Montserrat', category: 'sans' },
20
+ { family: 'Poppins', category: 'sans' },
21
+ { family: 'Lexend', category: 'sans' },
22
+ { family: 'Outfit', category: 'sans' },
23
+ { family: 'Sora', category: 'sans' },
24
+ { family: 'Space Grotesk', category: 'sans' },
25
+ { family: 'JetBrains Mono', category: 'mono' },
26
+ { family: 'Fira Code', category: 'mono' },
27
+ { family: 'Source Code Pro', category: 'mono' },
28
+ { family: 'IBM Plex Mono', category: 'mono' },
29
+ { family: 'Roboto Mono', category: 'mono' },
30
+ { family: 'Space Mono', category: 'mono' },
31
+ { family: 'Inconsolata', category: 'mono' },
32
+ { family: 'Ubuntu Mono', category: 'mono' },
33
+ { family: 'Anonymous Pro', category: 'mono' },
34
+ { family: 'Source Serif 4', category: 'serif' },
35
+ { family: 'IBM Plex Serif', category: 'serif' },
36
+ { family: 'Lora', category: 'serif' },
37
+ { family: 'Merriweather', category: 'serif' },
38
+ { family: 'Newsreader', category: 'serif' },
39
+ { family: 'Crimson Pro', category: 'serif' },
40
+ { family: 'Bitter', category: 'serif' },
41
+ { family: 'Bricolage Grotesque', category: 'display' },
42
+ { family: 'Instrument Serif', category: 'display' },
43
+ ];
44
+ const CDN_BASE = 'https://fonts.googleapis.com/css2';
45
+ /** Stylesheets outlive any one render, and a repeat request is a wasted round-trip. */
46
+ const requested = new Set();
47
+ /**
48
+ * @param font - the family to request.
49
+ * @returns an absolute stylesheet URL.
50
+ */
51
+ export function hostedFontUrl(font) {
52
+ const name = font.family.trim().replace(/\s+/gu, '+');
53
+ const weights = (font.weights ?? [400]).join(';');
54
+ return `${CDN_BASE}?family=${name}:wght@${weights}&display=swap`;
55
+ }
56
+ /**
57
+ * Load one family into the page; resolves on failure too, because a font that
58
+ * cannot be fetched must not break the panel.
59
+ *
60
+ * @param font - the family to load.
61
+ * @param doc - document to load into; defaults to the global one.
62
+ */
63
+ export async function loadHostedFont(font, doc = typeof document === 'undefined' ? undefined : document) {
64
+ if (doc === undefined)
65
+ return;
66
+ const key = font.family.toLowerCase();
67
+ if (requested.has(key))
68
+ return;
69
+ requested.add(key);
70
+ const stylesheet = doc.createElement('link');
71
+ stylesheet.rel = 'stylesheet';
72
+ stylesheet.href = hostedFontUrl(font);
73
+ stylesheet.dataset.d0njHostedFont = font.family;
74
+ await new Promise((resolve) => {
75
+ stylesheet.addEventListener('load', () => resolve(), { once: true });
76
+ stylesheet.addEventListener('error', () => resolve(), { once: true });
77
+ doc.head.append(stylesheet);
78
+ });
79
+ // Without this the first render after a pick can still measure the fallback.
80
+ await doc.fonts?.ready.catch(() => { });
81
+ }
82
+ /**
83
+ * @param fonts - catalog to read.
84
+ * @returns family names, sorted.
85
+ */
86
+ export function hostedFamilies(fonts = HOSTED_FONTS) {
87
+ return fonts.map((font) => font.family).sort((left, right) => left.localeCompare(right));
88
+ }
89
+ /**
90
+ * @param family - family name to find.
91
+ * @param fonts - catalog to search.
92
+ * @returns the entry, or `undefined` when the name is not hosted here.
93
+ */
94
+ export function findHostedFont(family, fonts = HOSTED_FONTS) {
95
+ const key = family.trim().replace(/^["']|["']$/gu, '').toLowerCase();
96
+ return fonts.find((font) => font.family.toLowerCase() === key);
97
+ }
98
+ //# sourceMappingURL=hosted-fonts.js.map
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Client half: keep the theme override layer in step with the document.
3
+ *
4
+ * The browser half owns exactly one thing — the `ctx.theme.overrideTokens` layer
5
+ * this plugin stacks over whatever theme is active. It re-derives that layer
6
+ * whenever the customization document changes, so a change made in the panel
7
+ * reaches the screen without a reload, and removing it restores whatever it
8
+ * covered.
9
+ *
10
+ * Two rules matter here:
11
+ *
12
+ * - **One source, one layer.** `overrideTokens` allows one layer per source and
13
+ * a repeat call replaces it wholesale, so the plugin never leaks layers: every
14
+ * update re-registers under the same source id.
15
+ * - **Both palette modes always travel together.** The resolver guarantees it, and
16
+ * the runtime rejects a bare string, so a half-specified token can never
17
+ * produce an illegible surface after a scheme switch.
18
+ *
19
+ * @module @d0nj/dsh-ui-custom/client
20
+ */
21
+ import type { Context } from '@deepseek-ai/cordis';
22
+ import { ThemeStudio } from './panel.js';
23
+ /** One token's value per palette mode, as ctx.theme.overrideTokens requires. */
24
+ export type ThemeTokenOverrides = Record<string, {
25
+ light: string;
26
+ dark: string;
27
+ }>;
28
+ /** Layer identity; the runtime keeps one layer per source. */
29
+ export declare const OVERRIDE_SOURCE = "d0nj-ui-custom";
30
+ /** The slice of the settings client this plugin uses to read and write its document. */
31
+ interface ConfigForm {
32
+ getSnapshot(): {
33
+ readonly value: unknown;
34
+ readonly revision: number | undefined;
35
+ readonly writable: boolean;
36
+ };
37
+ subscribe(listener: () => void): () => void;
38
+ mutate(ops: readonly {
39
+ op: 'set';
40
+ path: readonly string[];
41
+ value: unknown;
42
+ }[]): Promise<boolean>;
43
+ set(field: string, value: unknown): Promise<boolean>;
44
+ }
45
+ /** Services required before `apply` runs. */
46
+ export declare const inject: readonly ["theme", "configForms", "slots"];
47
+ /**
48
+ * Provide the React namespace to this plugin.
49
+ * @param react - the namespace the loader resolved.
50
+ */
51
+ export declare function useReact(react: Parameters<typeof ThemeStudio>[0]): void;
52
+ /**
53
+ * Client plugin body.
54
+ * @param ctx - client cordis context with `theme` and `configForms` injected.
55
+ */
56
+ export declare function apply(ctx: Context): void;
57
+ /**
58
+ * Register the Theme Studio page into the settings section list.
59
+ *
60
+ * Kept separate from `apply` so the section registration can be exercised in a
61
+ * unit test with a stub slots service, without a browser.
62
+ *
63
+ * @param ctx - client context.
64
+ * @param getState - supplies the current panel state.
65
+ * @param actions - the reactions the panel invokes.
66
+ */
67
+ export declare function registerPanel(ctx: Context, getState: () => import('./panel.js').PanelState, actions: import('./panel.js').PanelActions, settingsForm: ConfigForm): void;
68
+ export {};
69
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,324 @@
1
+ import { PRESETS, TOKEN_SPECS } from '../spec.js';
2
+ import { resolveCustomization } from '../resolve.js';
3
+ import { ThemeStudio } from './panel.js';
4
+ /** Layer identity; the runtime keeps one layer per source. */
5
+ export const OVERRIDE_SOURCE = 'd0nj-ui-custom';
6
+ /** Services required before `apply` runs. */
7
+ export const inject = ['theme', 'configForms', 'slots'];
8
+ /**
9
+ * The React namespace, handed in by the module envelope.
10
+ *
11
+ * The browser loader resolves React through its own module table, so a client
12
+ * half cannot import it. The stamped envelope calls useReact before apply,
13
+ * which keeps the dependency explicit instead of reading an ambient global.
14
+ */
15
+ let ReactImpl;
16
+ /**
17
+ * Provide the React namespace to this plugin.
18
+ * @param react - the namespace the loader resolved.
19
+ */
20
+ export function useReact(react) {
21
+ ReactImpl = react;
22
+ }
23
+ /**
24
+ * Flatten a preset's per-mode tokens to the single value the `register` arm
25
+ * expects, picking the mode that matches the preset's own base palette.
26
+ *
27
+ * `register` takes a flat `tokens` map rather than the two-mode shape
28
+ * `overrideTokens` accepts, so the mapping has to happen somewhere; doing it
29
+ * here keeps the colorScheme and the chosen values from disagreeing.
30
+ *
31
+ * @param preset - one shipped preset.
32
+ * @returns the flat token map for `register`.
33
+ */
34
+ function flatPresetTokens(preset) {
35
+ const tokens = {};
36
+ for (const [name, modes] of Object.entries(preset.tokens)) {
37
+ tokens[name] = preset.colorScheme === 'dark' ? modes.dark : modes.light;
38
+ }
39
+ return tokens;
40
+ }
41
+ /**
42
+ * Normalize the stored value into a document.
43
+ *
44
+ * The settings snapshot carries the parsed namespace value, which is already
45
+ * shaped like the config; this narrows it without trusting the transport.
46
+ * @param value - snapshot value.
47
+ * @returns the document to resolve.
48
+ */
49
+ function asDocument(value) {
50
+ const fallback = {
51
+ basePreset: 'none',
52
+ tokens: {},
53
+ typography: {
54
+ textFontFamily: '',
55
+ codeFontFamily: '',
56
+ terminalFontFamily: '',
57
+ fontSize: 14,
58
+ codeFontSize: 11,
59
+ terminalFontSize: 13,
60
+ },
61
+ geometry: { radius: 8, density: 'comfortable' },
62
+ };
63
+ if (value === null || typeof value !== 'object')
64
+ return fallback;
65
+ const record = value;
66
+ const typography = record.typography;
67
+ const geometry = record.geometry;
68
+ return {
69
+ basePreset: typeof record.basePreset === 'string' ? record.basePreset : 'none',
70
+ tokens: record.tokens !== null && typeof record.tokens === 'object'
71
+ ? record.tokens
72
+ : {},
73
+ typography: {
74
+ ...fallback.typography,
75
+ ...(typography !== null && typeof typography === 'object'
76
+ ? typography
77
+ : {}),
78
+ },
79
+ geometry: {
80
+ ...fallback.geometry,
81
+ ...(geometry !== null && typeof geometry === 'object'
82
+ ? geometry
83
+ : {}),
84
+ },
85
+ };
86
+ }
87
+ /**
88
+ * Client plugin body.
89
+ * @param ctx - client cordis context with `theme` and `configForms` injected.
90
+ */
91
+ export function apply(ctx) {
92
+ // A throw here fails the whole web boot (1 entry did not activate), which takes
93
+ // the UI down for a customization plugin. Every step is therefore guarded: a
94
+ // problem is reported once and the rest of the harness keeps working.
95
+ try {
96
+ applyInner(ctx);
97
+ }
98
+ catch (error) {
99
+ reportFailure(ctx, error);
100
+ }
101
+ }
102
+ /**
103
+ * Report one fatal setup failure without rethrowing.
104
+ * @param ctx - client context.
105
+ * @param error - the failure.
106
+ */
107
+ function reportFailure(ctx, error) {
108
+ const message = error instanceof Error ? error.message : String(error);
109
+ const logger = ctx.logger;
110
+ logger?.('ui-custom').error("theme customization disabled: " + message);
111
+ }
112
+ /**
113
+ * The real client body, called under a guard by {@link apply}.
114
+ * @param ctx - client context.
115
+ */
116
+ function applyInner(ctx) {
117
+ const theme = ctx.theme;
118
+ const forms = ctx.configForms;
119
+ // Presets register once, as real themes, so they appear among the normal
120
+ // choices and survive a reload even if this plugin's override layer is empty.
121
+ for (const preset of PRESETS) {
122
+ try {
123
+ ctx.effect(() => theme.register({
124
+ id: preset.id,
125
+ colorScheme: preset.colorScheme,
126
+ tokens: flatPresetTokens(preset),
127
+ }));
128
+ }
129
+ catch (error) {
130
+ // A preset id that is already taken (the built-in light/dark pair, or a
131
+ // re-registration after HMR) must not cost the user the editor.
132
+ reportFailure(ctx, error);
133
+ }
134
+ }
135
+ const form = forms.get('ui-custom');
136
+ let disposeLayer;
137
+ // The last typography this plugin applied, so it writes only real changes and
138
+ // never fights the shipped Appearance rows on every snapshot.
139
+ let appliedTypography = '';
140
+ /**
141
+ * Recompute the layer from the stored document.
142
+ */
143
+ const refresh = () => {
144
+ const snapshot = form.getSnapshot();
145
+ if (snapshot.value === undefined)
146
+ return;
147
+ const resolved = resolveCustomization(asDocument(snapshot.value));
148
+ disposeLayer?.();
149
+ disposeLayer = theme.overrideTokens(OVERRIDE_SOURCE, resolved.tokens);
150
+ // Typography goes through the theme service's own entries, so the built-in
151
+ // rows and this panel share one source of truth instead of shadowing each
152
+ // other. Sizes are only written when they differ, because each write is a
153
+ // settings mutation.
154
+ const typography = resolved.typography;
155
+ const key = JSON.stringify(typography);
156
+ if (key !== appliedTypography) {
157
+ appliedTypography = key;
158
+ const roles = [
159
+ ['text', typography.textFontFamily, typography.fontSize],
160
+ ['code', typography.codeFontFamily, typography.codeFontSize],
161
+ ['terminal', typography.terminalFontFamily, typography.terminalFontSize],
162
+ ];
163
+ for (const [role, family, size] of roles) {
164
+ try {
165
+ theme.setFontFamily(role, family);
166
+ theme.setFontSize(role, size);
167
+ }
168
+ catch {
169
+ // A font name the runtime rejects must not stop the colour layer from
170
+ // applying; the panel surfaces the rejected value instead.
171
+ }
172
+ }
173
+ }
174
+ };
175
+ ctx.effect(() => form.subscribe(refresh));
176
+ ctx.effect(() => () => {
177
+ disposeLayer?.();
178
+ });
179
+ refresh();
180
+ try {
181
+ registerPanel(ctx, () => panelState(form), panelActions(form, readDocument), form);
182
+ }
183
+ catch (error) {
184
+ reportFailure(ctx, error);
185
+ }
186
+ }
187
+ /**
188
+ * Register the Theme Studio page into the settings section list.
189
+ *
190
+ * Kept separate from `apply` so the section registration can be exercised in a
191
+ * unit test with a stub slots service, without a browser.
192
+ *
193
+ * @param ctx - client context.
194
+ * @param getState - supplies the current panel state.
195
+ * @param actions - the reactions the panel invokes.
196
+ */
197
+ export function registerPanel(ctx, getState, actions, settingsForm) {
198
+ const slots = ctx.slots;
199
+ if (ReactImpl === undefined)
200
+ return;
201
+ const React = ReactImpl;
202
+ /**
203
+ * The slot component.
204
+ *
205
+ * It is a real component rather than a direct call, so hooks belong to it and a
206
+ * settings change re-renders it. The subscription is what makes a panel write
207
+ * visible without reopening the page: `getState()` reads the same form the
208
+ * action just wrote, and the subscription fires when the Host echoes it back.
209
+ *
210
+ * @returns the Theme Studio element.
211
+ */
212
+ const Panel = () => {
213
+ const [, force] = React.useState(0);
214
+ React.useEffect(() => {
215
+ // Re-render when the Host publishes a new section for this namespace.
216
+ let tick = 0;
217
+ const dispose = settingsForm.subscribe(() => {
218
+ tick += 1;
219
+ force(tick);
220
+ });
221
+ return dispose;
222
+ }, []);
223
+ return ThemeStudio(React, { state: getState(), actions, presets: PRESETS });
224
+ };
225
+ ctx.effect(() => {
226
+ slots.inject('settings.section', () => slots.register({ name: 'settings.section', id: 'ui-custom', order: 11, label: () => 'Theme Studio' }, Panel));
227
+ return () => { };
228
+ });
229
+ }
230
+ /**
231
+ * Read the stored customization document.
232
+ * @param form - the settings form for this plugin's namespace.
233
+ * @returns the document the panel edits.
234
+ */
235
+ function readDocument(form) {
236
+ return asDocument(form.getSnapshot().value);
237
+ }
238
+ /**
239
+ * The document the panel last rendered, so its actions can merge one field
240
+ * without restating the whole section from a possibly stale snapshot.
241
+ */
242
+ let currentDocument;
243
+ /**
244
+ * Supply the current document for the panel's reads.
245
+ * @param form - the settings form.
246
+ * @returns the document.
247
+ */
248
+ function getDocument(form) {
249
+ currentDocument = readDocument(form);
250
+ return currentDocument;
251
+ }
252
+ /**
253
+ * Build the panel's state from the stored document.
254
+ * @param form - the settings form.
255
+ * @returns the state the panel renders.
256
+ */
257
+ function panelState(form) {
258
+ const document = getDocument(form);
259
+ const resolved = resolveCustomization(document);
260
+ const overrides = document.tokens;
261
+ return {
262
+ basePreset: document.basePreset,
263
+ tokens: TOKEN_SPECS.map((spec) => ({
264
+ name: spec.name,
265
+ label: spec.label,
266
+ description: spec.description,
267
+ override: overrides[spec.name],
268
+ })),
269
+ typography: resolved.typography,
270
+ geometry: document.geometry,
271
+ warnings: resolved.warnings,
272
+ writable: form.getSnapshot().writable,
273
+ };
274
+ }
275
+ /**
276
+ * Build the panel's write actions.
277
+ *
278
+ * Every action writes a whole field through the settings form, which serializes
279
+ * the mutation against the namespace revision. Tokens are written as one field
280
+ * (`tokens`) rather than per-name, because the schema models them as a dict and
281
+ * a partial write would drop the others.
282
+ *
283
+ * @param form - the settings form.
284
+ * @param document - reads the document the action should start from.
285
+ * @returns the actions the panel invokes.
286
+ */
287
+ function panelActions(form, document) {
288
+ const write = (field, value) => {
289
+ void form.set(field, value);
290
+ };
291
+ const withTokens = (mutate) => {
292
+ const base = document(form);
293
+ write('tokens', mutate({ ...base.tokens }));
294
+ };
295
+ return {
296
+ setPreset: (id) => write('basePreset', id),
297
+ setToken: (name, light, dark) => withTokens((tokens) => ({ ...tokens, [name]: { light, dark } })),
298
+ clearToken: (name) => withTokens((tokens) => {
299
+ const next = { ...tokens };
300
+ delete next[name];
301
+ return next;
302
+ }),
303
+ setFont: (role, value) => {
304
+ const base = document(form);
305
+ const key = role === 'text' ? 'textFontFamily' : role === 'code' ? 'codeFontFamily' : 'terminalFontFamily';
306
+ write('typography', { ...base.typography, [key]: value });
307
+ },
308
+ setFontSize: (role, px) => {
309
+ const base = document(form);
310
+ const key = role === 'text' ? 'fontSize' : role === 'code' ? 'codeFontSize' : 'terminalFontSize';
311
+ write('typography', { ...base.typography, [key]: px });
312
+ },
313
+ setRadius: (px) => {
314
+ const base = document(form);
315
+ write('geometry', { ...base.geometry, radius: px });
316
+ },
317
+ setDensity: (density) => {
318
+ const base = document(form);
319
+ write('geometry', { ...base.geometry, density: density });
320
+ },
321
+ resetAll: () => write('tokens', {}),
322
+ };
323
+ }
324
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Theme Studio: the settings page.
3
+ *
4
+ * Live preview is computed from the same resolver the stored document goes
5
+ * through, so what the panel shows and what the plugin applies cannot drift.
6
+ * Writes go through `configForms.get('ui-custom').set/unset`, which means the
7
+ * panel never touches YAML, never races a revision, and is reversible field by
8
+ * field.
9
+ *
10
+ * Styling follows the plugin rules: theme tokens only (`--dsw-alias-*`, plus the
11
+ * `--d0nj-*` geometry tokens this plugin derives), no literal colours for
12
+ * chrome. The swatches themselves are the one place a literal colour is correct,
13
+ * because they *are* the colour being chosen.
14
+ *
15
+ * @module @d0nj/dsh-ui-custom/panel
16
+ */
17
+ /** One token row's editable state. */
18
+ export interface TokenRowState {
19
+ readonly name: string;
20
+ readonly label: string;
21
+ readonly description: string;
22
+ /** Stored override, or `undefined` while the token inherits the preset. */
23
+ readonly override?: {
24
+ readonly light: string;
25
+ readonly dark: string;
26
+ } | undefined;
27
+ }
28
+ /** Panel state handed in by the client apply. */
29
+ export interface PanelState {
30
+ readonly basePreset: string;
31
+ readonly tokens: readonly TokenRowState[];
32
+ readonly typography: {
33
+ readonly textFontFamily: string;
34
+ readonly codeFontFamily: string;
35
+ readonly terminalFontFamily: string;
36
+ readonly fontSize: number;
37
+ readonly codeFontSize: number;
38
+ readonly terminalFontSize: number;
39
+ };
40
+ readonly geometry: {
41
+ readonly radius: number;
42
+ readonly density: string;
43
+ };
44
+ readonly warnings: readonly string[];
45
+ readonly writable: boolean;
46
+ }
47
+ /** Reactions the panel invokes. */
48
+ export interface PanelActions {
49
+ setPreset(id: string): void;
50
+ setToken(name: string, light: string, dark: string): void;
51
+ clearToken(name: string): void;
52
+ setFont(role: 'text' | 'code' | 'terminal', value: string): void;
53
+ setFontSize(role: 'text' | 'code' | 'terminal', px: number): void;
54
+ setRadius(px: number): void;
55
+ setDensity(density: string): void;
56
+ resetAll(): void;
57
+ }
58
+ /** Exported so tests do not inherit an open list between cases. */
59
+ export declare function resetCombos(): void;
60
+ /**
61
+ * Render the Theme Studio page.
62
+ *
63
+ * Written against a minimal `React.createElement` shim rather than JSX so the
64
+ * module needs no transform at build time; the client bundle supplies React.
65
+ *
66
+ * @param React - the React namespace from the module table.
67
+ * @param props - current state and the actions to invoke.
68
+ * @returns the section element.
69
+ */
70
+ export declare function ThemeStudio(React: {
71
+ createElement: (type: unknown, props?: unknown, ...children: unknown[]) => unknown;
72
+ useState: <T>(initial: T) => [T, (next: T) => void];
73
+ }, props: {
74
+ state: PanelState;
75
+ actions: PanelActions;
76
+ presets: readonly {
77
+ id: string;
78
+ label: string;
79
+ description: string;
80
+ colorScheme: 'light' | 'dark';
81
+ tokens: Record<string, {
82
+ light: string;
83
+ dark: string;
84
+ }>;
85
+ }[];
86
+ }): unknown;
87
+ //# sourceMappingURL=panel.d.ts.map