@runtypelabs/persona 3.5.2 → 3.7.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.
- package/dist/index.cjs +46 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.global.js +70 -70
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +46 -46
- package/dist/index.js.map +1 -1
- package/dist/theme-editor.cjs +18015 -0
- package/dist/theme-editor.d.cts +3888 -0
- package/dist/theme-editor.d.ts +3888 -0
- package/dist/theme-editor.js +17909 -0
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +33 -0
- package/dist/theme-reference.d.ts +33 -0
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +69 -25
- package/package.json +9 -7
- package/src/components/artifact-card.ts +1 -1
- package/src/components/composer-builder.ts +16 -29
- package/src/components/demo-carousel.ts +5 -5
- package/src/components/event-stream-view.test.ts +142 -0
- package/src/components/event-stream-view.ts +68 -29
- package/src/components/header-builder.ts +2 -2
- package/src/components/launcher.ts +9 -0
- package/src/components/message-bubble.ts +9 -3
- package/src/components/suggestions.ts +1 -1
- package/src/defaults.ts +24 -9
- package/src/scroll-to-bottom-defaults.test.ts +13 -0
- package/src/styles/widget.css +69 -25
- package/src/theme-editor/color-utils.ts +252 -0
- package/src/theme-editor/index.ts +131 -0
- package/src/theme-editor/presets.ts +144 -0
- package/src/theme-editor/preview-utils.ts +265 -0
- package/src/theme-editor/preview.ts +445 -0
- package/src/theme-editor/role-mappings.ts +343 -0
- package/src/theme-editor/sections.test.ts +43 -0
- package/src/theme-editor/sections.ts +994 -0
- package/src/theme-editor/state.ts +298 -0
- package/src/theme-editor/types.ts +177 -0
- package/src/theme-editor.ts +2 -0
- package/src/theme-reference.ts +8 -0
- package/src/types/theme.ts +11 -0
- package/src/types.ts +22 -0
- package/src/ui.scroll.test.ts +554 -0
- package/src/ui.ts +223 -133
- package/src/utils/auto-follow.test.ts +110 -0
- package/src/utils/auto-follow.ts +112 -0
- package/src/utils/plugins.ts +1 -1
- package/src/utils/theme.test.ts +44 -8
- package/src/utils/theme.ts +11 -11
- package/src/utils/tokens.ts +137 -41
- package/widget.css +0 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/** Headless state management for the theme editor (no DOM, no localStorage, no side effects) */
|
|
2
|
+
|
|
3
|
+
import type { AgentWidgetConfig } from '../types';
|
|
4
|
+
import type { PersonaTheme } from '../types/theme';
|
|
5
|
+
import { createTheme } from '../utils/theme';
|
|
6
|
+
import { DEFAULT_WIDGET_CONFIG } from '../defaults';
|
|
7
|
+
import type { ConfiguratorSnapshot, ConfigChangeListener } from './types';
|
|
8
|
+
|
|
9
|
+
// ─── Dot-path utilities ─────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
function getByPath(obj: unknown, path: string): unknown {
|
|
12
|
+
const parts = path.split('.');
|
|
13
|
+
let current: unknown = obj;
|
|
14
|
+
for (const part of parts) {
|
|
15
|
+
if (current === undefined || current === null) return undefined;
|
|
16
|
+
current = (current as Record<string, unknown>)[part];
|
|
17
|
+
}
|
|
18
|
+
return current;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function setByPath(obj: unknown, path: string, value: unknown): unknown {
|
|
22
|
+
const parts = path.split('.');
|
|
23
|
+
if (parts.length === 1) {
|
|
24
|
+
return { ...(obj as Record<string, unknown>), [parts[0]]: value };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const [first, ...rest] = parts;
|
|
28
|
+
const current = obj as Record<string, unknown>;
|
|
29
|
+
return {
|
|
30
|
+
...current,
|
|
31
|
+
[first]: setByPath(current?.[first] ?? {}, rest.join('.'), value),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── ThemeEditorState ───────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export class ThemeEditorState {
|
|
38
|
+
private config: AgentWidgetConfig;
|
|
39
|
+
private theme: PersonaTheme;
|
|
40
|
+
private listeners: ConfigChangeListener[] = [];
|
|
41
|
+
private history: ConfiguratorSnapshot[] = [];
|
|
42
|
+
private historyIndex = -1;
|
|
43
|
+
private suppressHistory = false;
|
|
44
|
+
|
|
45
|
+
constructor(
|
|
46
|
+
initialTheme?: Partial<PersonaTheme>,
|
|
47
|
+
initialConfig?: Partial<AgentWidgetConfig>,
|
|
48
|
+
options?: { mergeDefaults?: boolean }
|
|
49
|
+
) {
|
|
50
|
+
const mergeDefaults = options?.mergeDefaults ?? true;
|
|
51
|
+
this.config = (mergeDefaults
|
|
52
|
+
? { ...DEFAULT_WIDGET_CONFIG, ...initialConfig }
|
|
53
|
+
: (initialConfig ?? DEFAULT_WIDGET_CONFIG)
|
|
54
|
+
) as AgentWidgetConfig;
|
|
55
|
+
this.theme = createTheme(initialTheme, { validate: false });
|
|
56
|
+
this.syncThemeIntoConfig();
|
|
57
|
+
this.pushHistorySnapshot(this.exportSnapshot(), true);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Read ───────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get a value using a dot-path.
|
|
64
|
+
* - `theme.*` → reads from the PersonaTheme
|
|
65
|
+
* - `darkTheme.*` → reads from config.darkTheme
|
|
66
|
+
* - everything else → reads from the AgentWidgetConfig
|
|
67
|
+
*/
|
|
68
|
+
get(path: string): unknown {
|
|
69
|
+
if (path.startsWith('theme.')) {
|
|
70
|
+
return getByPath(this.theme, path.replace('theme.', ''));
|
|
71
|
+
}
|
|
72
|
+
if (path.startsWith('darkTheme.')) {
|
|
73
|
+
return getByPath(this.config.darkTheme ?? {}, path.replace('darkTheme.', ''));
|
|
74
|
+
}
|
|
75
|
+
return getByPath(this.config, path);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getTheme(): PersonaTheme {
|
|
79
|
+
return this.theme;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getConfig(): AgentWidgetConfig {
|
|
83
|
+
return this.config;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── Write ──────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Set a value using a dot-path.
|
|
90
|
+
* - `theme.*` → writes into the PersonaTheme
|
|
91
|
+
* - `darkTheme.*` → writes into config.darkTheme
|
|
92
|
+
* - everything else → writes into AgentWidgetConfig
|
|
93
|
+
*/
|
|
94
|
+
set(path: string, value: unknown): void {
|
|
95
|
+
if (path.startsWith('theme.')) {
|
|
96
|
+
const themePath = path.replace('theme.', '');
|
|
97
|
+
this.theme = setByPath(this.theme, themePath, value) as PersonaTheme;
|
|
98
|
+
this.syncThemeIntoConfig();
|
|
99
|
+
} else if (path.startsWith('darkTheme.')) {
|
|
100
|
+
const themePath = path.replace('darkTheme.', '');
|
|
101
|
+
const dark = this.config.darkTheme ?? createTheme();
|
|
102
|
+
this.config = {
|
|
103
|
+
...this.config,
|
|
104
|
+
darkTheme: setByPath(dark, themePath, value) as AgentWidgetConfig['darkTheme'],
|
|
105
|
+
};
|
|
106
|
+
} else {
|
|
107
|
+
this.config = setByPath(this.config, path, value) as AgentWidgetConfig;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.recordHistory();
|
|
111
|
+
this.notifyListeners();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Batch-set multiple paths at once */
|
|
115
|
+
setBatch(updates: Record<string, unknown>): void {
|
|
116
|
+
let themeChanged = false;
|
|
117
|
+
let darkThemeChanged = false;
|
|
118
|
+
let configChanged = false;
|
|
119
|
+
|
|
120
|
+
for (const [path, value] of Object.entries(updates)) {
|
|
121
|
+
if (path.startsWith('theme.')) {
|
|
122
|
+
const themePath = path.replace('theme.', '');
|
|
123
|
+
this.theme = setByPath(this.theme, themePath, value) as PersonaTheme;
|
|
124
|
+
themeChanged = true;
|
|
125
|
+
} else if (path.startsWith('darkTheme.')) {
|
|
126
|
+
const themePath = path.replace('darkTheme.', '');
|
|
127
|
+
const dark = this.config.darkTheme ?? createTheme();
|
|
128
|
+
this.config = {
|
|
129
|
+
...this.config,
|
|
130
|
+
darkTheme: setByPath(dark, themePath, value) as AgentWidgetConfig['darkTheme'],
|
|
131
|
+
};
|
|
132
|
+
darkThemeChanged = true;
|
|
133
|
+
} else {
|
|
134
|
+
this.config = setByPath(this.config, path, value) as AgentWidgetConfig;
|
|
135
|
+
configChanged = true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (themeChanged) {
|
|
140
|
+
this.syncThemeIntoConfig();
|
|
141
|
+
}
|
|
142
|
+
if (themeChanged || darkThemeChanged || configChanged) {
|
|
143
|
+
this.recordHistory();
|
|
144
|
+
this.notifyListeners();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Replace the entire theme */
|
|
149
|
+
setTheme(theme: PersonaTheme): void {
|
|
150
|
+
this.theme = theme;
|
|
151
|
+
this.syncThemeIntoConfig();
|
|
152
|
+
this.recordHistory();
|
|
153
|
+
this.notifyListeners();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Replace the entire config (for preset loading) */
|
|
157
|
+
setFullConfig(config: AgentWidgetConfig, theme?: PersonaTheme): void {
|
|
158
|
+
this.config = { ...config };
|
|
159
|
+
if (theme) {
|
|
160
|
+
this.theme = theme;
|
|
161
|
+
}
|
|
162
|
+
this.syncThemeIntoConfig();
|
|
163
|
+
this.recordHistory();
|
|
164
|
+
this.notifyListeners();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Import a snapshot (v2 or raw theme) */
|
|
168
|
+
importSnapshot(snapshot: unknown): void {
|
|
169
|
+
if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) {
|
|
170
|
+
throw new Error('Snapshot must be a JSON object');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const parsed = snapshot as Partial<ConfiguratorSnapshot> & { config?: unknown; theme?: unknown };
|
|
174
|
+
|
|
175
|
+
if ('config' in parsed || 'theme' in parsed || parsed.version === 2) {
|
|
176
|
+
const config = (parsed.config ?? this.config) as AgentWidgetConfig;
|
|
177
|
+
const theme = createTheme(
|
|
178
|
+
(parsed.theme ?? this.theme) as Partial<PersonaTheme>,
|
|
179
|
+
{ validate: false }
|
|
180
|
+
);
|
|
181
|
+
this.setFullConfig(config, theme);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const theme = createTheme(parsed as Partial<PersonaTheme>, { validate: false });
|
|
186
|
+
this.setTheme(theme);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Reset to defaults */
|
|
190
|
+
resetToDefaults(): void {
|
|
191
|
+
this.config = { ...DEFAULT_WIDGET_CONFIG } as AgentWidgetConfig;
|
|
192
|
+
this.theme = createTheme();
|
|
193
|
+
this.syncThemeIntoConfig();
|
|
194
|
+
this.history = [];
|
|
195
|
+
this.historyIndex = -1;
|
|
196
|
+
this.pushHistorySnapshot(this.exportSnapshot());
|
|
197
|
+
this.notifyListeners();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── History ────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
canUndo(): boolean {
|
|
203
|
+
return this.historyIndex > 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
canRedo(): boolean {
|
|
207
|
+
return this.historyIndex >= 0 && this.historyIndex < this.history.length - 1;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
getHistoryLength(): number {
|
|
211
|
+
return this.history.length;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
getHistoryIndex(): number {
|
|
215
|
+
return this.historyIndex;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
undo(): void {
|
|
219
|
+
if (!this.canUndo()) return;
|
|
220
|
+
this.historyIndex -= 1;
|
|
221
|
+
this.restoreSnapshot(this.history[this.historyIndex]);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
redo(): void {
|
|
225
|
+
if (!this.canRedo()) return;
|
|
226
|
+
this.historyIndex += 1;
|
|
227
|
+
this.restoreSnapshot(this.history[this.historyIndex]);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ─── Snapshots ──────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
exportSnapshot(): ConfiguratorSnapshot {
|
|
233
|
+
return {
|
|
234
|
+
version: 2,
|
|
235
|
+
config: { ...this.config, theme: undefined } as unknown as Record<string, unknown>,
|
|
236
|
+
theme: this.theme,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── Listeners ──────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
onChange(listener: ConfigChangeListener): () => void {
|
|
243
|
+
this.listeners.push(listener);
|
|
244
|
+
return () => {
|
|
245
|
+
const idx = this.listeners.indexOf(listener);
|
|
246
|
+
if (idx >= 0) this.listeners.splice(idx, 1);
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ─── Private ────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
private syncThemeIntoConfig(): void {
|
|
253
|
+
this.config = {
|
|
254
|
+
...this.config,
|
|
255
|
+
theme: this.theme,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private notifyListeners(): void {
|
|
260
|
+
for (const listener of this.listeners) {
|
|
261
|
+
listener(this.config, this.theme);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private recordHistory(): void {
|
|
266
|
+
this.pushHistorySnapshot(this.exportSnapshot());
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private pushHistorySnapshot(snapshot: ConfiguratorSnapshot, replaceCurrent = false): void {
|
|
270
|
+
if (this.suppressHistory) return;
|
|
271
|
+
|
|
272
|
+
const serialized = JSON.stringify(snapshot);
|
|
273
|
+
const currentSerialized =
|
|
274
|
+
this.historyIndex >= 0 && this.history[this.historyIndex]
|
|
275
|
+
? JSON.stringify(this.history[this.historyIndex])
|
|
276
|
+
: null;
|
|
277
|
+
|
|
278
|
+
if (replaceCurrent && this.historyIndex >= 0) {
|
|
279
|
+
this.history[this.historyIndex] = snapshot;
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (serialized === currentSerialized) return;
|
|
284
|
+
|
|
285
|
+
this.history = this.history.slice(0, this.historyIndex + 1);
|
|
286
|
+
this.history.push(snapshot);
|
|
287
|
+
this.historyIndex = this.history.length - 1;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private restoreSnapshot(snapshot: ConfiguratorSnapshot): void {
|
|
291
|
+
this.suppressHistory = true;
|
|
292
|
+
this.config = snapshot.config as unknown as AgentWidgetConfig;
|
|
293
|
+
this.theme = createTheme(snapshot.theme, { validate: false });
|
|
294
|
+
this.syncThemeIntoConfig();
|
|
295
|
+
this.suppressHistory = false;
|
|
296
|
+
this.notifyListeners();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/** Field definition types for the declarative configurator system (headless — no DOM) */
|
|
2
|
+
|
|
3
|
+
import type { DeepPartial, PersonaTheme } from '../types/theme';
|
|
4
|
+
import type { AgentWidgetConfig } from '../types';
|
|
5
|
+
|
|
6
|
+
// ─── Field System ────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export type FieldType =
|
|
9
|
+
| 'color'
|
|
10
|
+
| 'slider'
|
|
11
|
+
| 'toggle'
|
|
12
|
+
| 'select'
|
|
13
|
+
| 'text'
|
|
14
|
+
| 'chip-list'
|
|
15
|
+
| 'color-scale'
|
|
16
|
+
| 'token-ref'
|
|
17
|
+
| 'role-assignment';
|
|
18
|
+
|
|
19
|
+
export interface SliderOptions {
|
|
20
|
+
min: number;
|
|
21
|
+
max: number;
|
|
22
|
+
step: number;
|
|
23
|
+
unit?: 'px' | 'rem' | 'none';
|
|
24
|
+
/** Treat max value as 9999px (border-radius: full) */
|
|
25
|
+
isRadiusFull?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SelectOption {
|
|
29
|
+
value: string;
|
|
30
|
+
label: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ColorScaleOptions {
|
|
34
|
+
/** Which palette color family (e.g., 'primary', 'gray') */
|
|
35
|
+
colorFamily: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TokenRefOptions {
|
|
39
|
+
/** Token type to filter available references */
|
|
40
|
+
tokenType: 'color' | 'spacing' | 'radius' | 'shadow' | 'typography';
|
|
41
|
+
/** Available palette families to reference */
|
|
42
|
+
families?: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── Role Assignment System ─────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/** Which kind of value a role target expects */
|
|
48
|
+
export type RoleTargetKind = 'background' | 'foreground' | 'border' | 'accent';
|
|
49
|
+
|
|
50
|
+
/** A single token path that a role assignment writes to */
|
|
51
|
+
export interface RoleTarget {
|
|
52
|
+
/** Theme token path (e.g., 'components.message.user.background') */
|
|
53
|
+
path: string;
|
|
54
|
+
/** What kind of value this target expects */
|
|
55
|
+
kind: RoleTargetKind;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** An intensity preset (e.g., Solid uses .500 shades, Soft uses .100 shades) */
|
|
59
|
+
export interface RoleIntensity {
|
|
60
|
+
id: string;
|
|
61
|
+
label: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Options for a role-assignment field */
|
|
65
|
+
export interface RoleAssignmentOptions {
|
|
66
|
+
/** Unique role identifier */
|
|
67
|
+
roleId: string;
|
|
68
|
+
/** Helper text shown below the role name */
|
|
69
|
+
helper: string;
|
|
70
|
+
/** Token paths this role writes to */
|
|
71
|
+
targets: RoleTarget[];
|
|
72
|
+
/** Available intensity presets */
|
|
73
|
+
intensities: RoleIntensity[];
|
|
74
|
+
/** Which data-persona-theme-zone this role corresponds to (for preview highlighting) */
|
|
75
|
+
previewZone?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface FieldDef {
|
|
79
|
+
id: string;
|
|
80
|
+
label: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
type: FieldType;
|
|
83
|
+
/** Dot-path into the config/theme object */
|
|
84
|
+
path: string;
|
|
85
|
+
defaultValue?: unknown;
|
|
86
|
+
/** Slider-specific options */
|
|
87
|
+
slider?: SliderOptions;
|
|
88
|
+
/** Select-specific options */
|
|
89
|
+
options?: SelectOption[];
|
|
90
|
+
/** Color-scale-specific options */
|
|
91
|
+
colorScale?: ColorScaleOptions;
|
|
92
|
+
/** Token-ref-specific options */
|
|
93
|
+
tokenRef?: TokenRefOptions;
|
|
94
|
+
/** Role-assignment-specific options */
|
|
95
|
+
roleAssignment?: RoleAssignmentOptions;
|
|
96
|
+
/** CSS property hint for value formatting */
|
|
97
|
+
cssProperty?: string;
|
|
98
|
+
/** Whether this is a theme path (vs config path) */
|
|
99
|
+
isThemePath?: boolean;
|
|
100
|
+
/** Convert stored value into a control-friendly value */
|
|
101
|
+
formatValue?: (value: unknown) => unknown;
|
|
102
|
+
/** Convert control input back into the stored value shape */
|
|
103
|
+
parseValue?: (value: unknown) => unknown;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface SectionDef {
|
|
107
|
+
id: string;
|
|
108
|
+
title: string;
|
|
109
|
+
description?: string;
|
|
110
|
+
fields: FieldDef[];
|
|
111
|
+
/** Whether the section starts collapsed */
|
|
112
|
+
collapsed?: boolean;
|
|
113
|
+
/** Preset buttons for this section */
|
|
114
|
+
presets?: SectionPreset[];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface SectionPreset {
|
|
118
|
+
id: string;
|
|
119
|
+
label: string;
|
|
120
|
+
values: Record<string, unknown>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface TabDef {
|
|
124
|
+
id: string;
|
|
125
|
+
label: string;
|
|
126
|
+
icon?: string;
|
|
127
|
+
sections: SectionDef[];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface SubGroupDef {
|
|
131
|
+
label: string;
|
|
132
|
+
sections: SectionDef[];
|
|
133
|
+
/** When true, the sub-group starts collapsed and must be explicitly expanded */
|
|
134
|
+
collapsedByDefault?: boolean;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── Preset System ───────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
/** Extract the toolCall config type from AgentWidgetConfig */
|
|
140
|
+
type AgentWidgetToolCallConfig = NonNullable<AgentWidgetConfig['toolCall']>;
|
|
141
|
+
|
|
142
|
+
export interface ThemeEditorPreset {
|
|
143
|
+
id: string;
|
|
144
|
+
name: string;
|
|
145
|
+
description: string;
|
|
146
|
+
theme: DeepPartial<PersonaTheme>;
|
|
147
|
+
darkTheme?: DeepPartial<PersonaTheme>;
|
|
148
|
+
/** Tool call styling for light mode */
|
|
149
|
+
toolCall?: AgentWidgetToolCallConfig;
|
|
150
|
+
/** Tool call styling for dark mode (falls back to toolCall if not set) */
|
|
151
|
+
darkToolCall?: AgentWidgetToolCallConfig;
|
|
152
|
+
preview: {
|
|
153
|
+
primary: string;
|
|
154
|
+
surface: string;
|
|
155
|
+
accent: string;
|
|
156
|
+
};
|
|
157
|
+
darkPreview?: {
|
|
158
|
+
primary: string;
|
|
159
|
+
surface: string;
|
|
160
|
+
accent: string;
|
|
161
|
+
};
|
|
162
|
+
/** Tags for filtering/categorization */
|
|
163
|
+
tags?: string[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ─── State ───────────────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
export interface ConfiguratorSnapshot {
|
|
169
|
+
version: 2;
|
|
170
|
+
config: Record<string, unknown>;
|
|
171
|
+
theme: PersonaTheme;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type ConfigChangeListener = (config: AgentWidgetConfig, theme: PersonaTheme) => void;
|
|
175
|
+
|
|
176
|
+
/** Callback for when a control value changes */
|
|
177
|
+
export type OnChangeCallback = (path: string, value: unknown) => void;
|
package/src/theme-reference.ts
CHANGED
|
@@ -138,6 +138,8 @@ export const THEME_TOKEN_DOCS = {
|
|
|
138
138
|
approval:
|
|
139
139
|
'requested (background, border, text), approve (background, foreground), deny (background, foreground).',
|
|
140
140
|
attachment: 'image (background, border).',
|
|
141
|
+
scrollToBottom:
|
|
142
|
+
'Floating scroll-to-bottom affordance shared by transcript and event stream: background, foreground, border, size, borderRadius, shadow, padding, gap, fontSize, iconSize.',
|
|
141
143
|
toolBubble: 'shadow — tool call row box-shadow.',
|
|
142
144
|
reasoningBubble: 'shadow — reasoning/thinking row box-shadow.',
|
|
143
145
|
composer: 'shadow — message input form box-shadow.',
|
|
@@ -201,6 +203,12 @@ export const THEME_TOKEN_DOCS = {
|
|
|
201
203
|
properties:
|
|
202
204
|
'enabled, iconColor, backgroundColor, borderWidth, borderColor, borderRadius, size.',
|
|
203
205
|
},
|
|
206
|
+
scrollToBottom: {
|
|
207
|
+
description:
|
|
208
|
+
'Shared transcript + event-stream jump-to-latest affordance.',
|
|
209
|
+
properties:
|
|
210
|
+
'features.scrollToBottom.enabled, features.scrollToBottom.iconName, features.scrollToBottom.label (empty string renders icon-only). Defaults: enabled=true, iconName="arrow-down", label="".',
|
|
211
|
+
},
|
|
204
212
|
toolCall: {
|
|
205
213
|
description: 'Tool call display styling.',
|
|
206
214
|
properties:
|
package/src/types/theme.ts
CHANGED
|
@@ -25,6 +25,7 @@ export interface ColorPalette {
|
|
|
25
25
|
success: ColorShade;
|
|
26
26
|
warning: ColorShade;
|
|
27
27
|
error: ColorShade;
|
|
28
|
+
info: ColorShade;
|
|
28
29
|
[key: string]: ColorShade;
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -392,6 +393,14 @@ export interface LabelButtonTokens {
|
|
|
392
393
|
gap?: string;
|
|
393
394
|
}
|
|
394
395
|
|
|
396
|
+
/** Scroll-to-bottom pill chrome shared by transcript + event stream. */
|
|
397
|
+
export interface ScrollToBottomTokens extends ComponentTokenSet {
|
|
398
|
+
size?: string;
|
|
399
|
+
gap?: string;
|
|
400
|
+
fontSize?: string;
|
|
401
|
+
iconSize?: string;
|
|
402
|
+
}
|
|
403
|
+
|
|
395
404
|
/** Toggle group chrome (used by createToggleGroup). */
|
|
396
405
|
export interface ToggleGroupTokens {
|
|
397
406
|
/** Gap between toggle buttons. Default: 0 (connected). */
|
|
@@ -419,6 +428,8 @@ export interface ComponentTokens {
|
|
|
419
428
|
iconButton?: IconButtonTokens;
|
|
420
429
|
/** Label button styling tokens. */
|
|
421
430
|
labelButton?: LabelButtonTokens;
|
|
431
|
+
/** Scroll-to-bottom indicator styling tokens. */
|
|
432
|
+
scrollToBottom?: ScrollToBottomTokens;
|
|
422
433
|
/** Toggle group styling tokens. */
|
|
423
434
|
toggleGroup?: ToggleGroupTokens;
|
|
424
435
|
/** Artifact toolbar, tab strip, and pane chrome. */
|
package/src/types.ts
CHANGED
|
@@ -552,10 +552,32 @@ export type AgentWidgetArtifactsFeature = {
|
|
|
552
552
|
}) => HTMLElement | null;
|
|
553
553
|
};
|
|
554
554
|
|
|
555
|
+
export type AgentWidgetScrollToBottomFeature = {
|
|
556
|
+
/**
|
|
557
|
+
* When true, Persona shows a scroll-to-bottom affordance when the user breaks
|
|
558
|
+
* away from the latest transcript or event stream content.
|
|
559
|
+
* @default true
|
|
560
|
+
*/
|
|
561
|
+
enabled?: boolean;
|
|
562
|
+
/**
|
|
563
|
+
* Lucide icon name used for the affordance.
|
|
564
|
+
* @default "arrow-down"
|
|
565
|
+
*/
|
|
566
|
+
iconName?: string;
|
|
567
|
+
/**
|
|
568
|
+
* Optional label text shown next to the icon. Set to an empty string for an
|
|
569
|
+
* icon-only affordance.
|
|
570
|
+
* @default ""
|
|
571
|
+
*/
|
|
572
|
+
label?: string;
|
|
573
|
+
};
|
|
574
|
+
|
|
555
575
|
export type AgentWidgetFeatureFlags = {
|
|
556
576
|
showReasoning?: boolean;
|
|
557
577
|
showToolCalls?: boolean;
|
|
558
578
|
showEventStreamToggle?: boolean;
|
|
579
|
+
/** Shared transcript + event stream scroll-to-bottom affordance. */
|
|
580
|
+
scrollToBottom?: AgentWidgetScrollToBottomFeature;
|
|
559
581
|
/** Configuration for the Event Stream inspector view */
|
|
560
582
|
eventStream?: EventStreamConfig;
|
|
561
583
|
/** Optional artifact sidebar (split pane / mobile drawer) */
|