@runtypelabs/persona 3.5.2 → 3.6.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.
Files changed (45) hide show
  1. package/dist/index.cjs +30 -30
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +14 -0
  4. package/dist/index.d.ts +14 -0
  5. package/dist/index.global.js +41 -41
  6. package/dist/index.global.js.map +1 -1
  7. package/dist/index.js +29 -29
  8. package/dist/index.js.map +1 -1
  9. package/dist/theme-editor.cjs +17728 -0
  10. package/dist/theme-editor.d.cts +3857 -0
  11. package/dist/theme-editor.d.ts +3857 -0
  12. package/dist/theme-editor.js +17623 -0
  13. package/dist/theme-reference.cjs +1 -1
  14. package/dist/theme-reference.d.cts +14 -0
  15. package/dist/theme-reference.d.ts +14 -0
  16. package/dist/theme-reference.js +1 -1
  17. package/dist/widget.css +29 -25
  18. package/package.json +9 -7
  19. package/src/components/artifact-card.ts +1 -1
  20. package/src/components/composer-builder.ts +16 -29
  21. package/src/components/demo-carousel.ts +4 -4
  22. package/src/components/event-stream-view.ts +1 -1
  23. package/src/components/header-builder.ts +2 -2
  24. package/src/components/launcher.ts +9 -0
  25. package/src/components/message-bubble.ts +9 -3
  26. package/src/components/suggestions.ts +1 -1
  27. package/src/defaults.ts +9 -9
  28. package/src/styles/widget.css +29 -25
  29. package/src/theme-editor/color-utils.ts +252 -0
  30. package/src/theme-editor/index.ts +130 -0
  31. package/src/theme-editor/presets.ts +144 -0
  32. package/src/theme-editor/preview-utils.ts +265 -0
  33. package/src/theme-editor/preview.ts +445 -0
  34. package/src/theme-editor/role-mappings.ts +331 -0
  35. package/src/theme-editor/sections.ts +952 -0
  36. package/src/theme-editor/state.ts +298 -0
  37. package/src/theme-editor/types.ts +177 -0
  38. package/src/theme-editor.ts +2 -0
  39. package/src/types/theme.ts +1 -0
  40. package/src/ui.ts +53 -58
  41. package/src/utils/plugins.ts +1 -1
  42. package/src/utils/theme.test.ts +10 -8
  43. package/src/utils/theme.ts +11 -11
  44. package/src/utils/tokens.ts +88 -41
  45. package/widget.css +0 -1
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Interface Role → Token mapping layer.
3
+ *
4
+ * Maps high-level editor choices (family + intensity) to concrete palette
5
+ * token references across multiple component/semantic paths. This is the
6
+ * core of the "Interface Roles" editor section — one picker writes to
7
+ * many tokens atomically.
8
+ *
9
+ * All functions are pure and headless (no DOM).
10
+ */
11
+
12
+ import type { RoleTarget, RoleIntensity, RoleAssignmentOptions } from './types';
13
+
14
+ // ─── Intensities ────────────────────────────────────────────────
15
+
16
+ export const ROLE_INTENSITIES: RoleIntensity[] = [
17
+ { id: 'solid', label: 'Solid' },
18
+ { id: 'soft', label: 'Soft' },
19
+ ];
20
+
21
+ // ─── Palette families available for role assignment ─────────────
22
+
23
+ export const ROLE_FAMILIES = ['primary', 'secondary', 'accent', 'gray'] as const;
24
+ export type RoleFamily = (typeof ROLE_FAMILIES)[number];
25
+
26
+ /** Display labels for palette families in the editor */
27
+ export const ROLE_FAMILY_LABELS: Record<RoleFamily, string> = {
28
+ primary: 'Primary',
29
+ secondary: 'Secondary',
30
+ accent: 'Accent',
31
+ gray: 'Neutral',
32
+ };
33
+
34
+ // ─── Role Definitions ───────────────────────────────────────────
35
+
36
+ export const ROLE_SURFACES: RoleAssignmentOptions = {
37
+ roleId: 'role-surfaces',
38
+ helper: 'Page and panel backgrounds',
39
+ previewZone: 'container',
40
+ intensities: ROLE_INTENSITIES,
41
+ targets: [
42
+ { path: 'semantic.colors.background', kind: 'background' },
43
+ { path: 'semantic.colors.surface', kind: 'background' },
44
+ { path: 'semantic.colors.container', kind: 'background' },
45
+ ],
46
+ };
47
+
48
+ export const ROLE_HEADER: RoleAssignmentOptions = {
49
+ roleId: 'role-header',
50
+ helper: 'Widget header bar',
51
+ previewZone: 'header',
52
+ intensities: ROLE_INTENSITIES,
53
+ targets: [
54
+ { path: 'components.header.background', kind: 'background' },
55
+ { path: 'components.header.border', kind: 'border' },
56
+ { path: 'components.header.iconBackground', kind: 'accent' },
57
+ { path: 'components.header.iconForeground', kind: 'foreground' },
58
+ { path: 'components.header.titleForeground', kind: 'accent' },
59
+ { path: 'components.header.subtitleForeground', kind: 'foreground' },
60
+ { path: 'components.header.actionIconForeground', kind: 'foreground' },
61
+ ],
62
+ };
63
+
64
+ export const ROLE_USER_MESSAGES: RoleAssignmentOptions = {
65
+ roleId: 'role-user-messages',
66
+ helper: 'User chat bubbles',
67
+ previewZone: 'user-message',
68
+ intensities: ROLE_INTENSITIES,
69
+ targets: [
70
+ { path: 'components.message.user.background', kind: 'background' },
71
+ { path: 'components.message.user.text', kind: 'foreground' },
72
+ ],
73
+ };
74
+
75
+ export const ROLE_ASSISTANT_MESSAGES: RoleAssignmentOptions = {
76
+ roleId: 'role-assistant-messages',
77
+ helper: 'Assistant chat bubbles',
78
+ previewZone: 'assistant-message',
79
+ intensities: ROLE_INTENSITIES,
80
+ targets: [
81
+ { path: 'components.message.assistant.background', kind: 'background' },
82
+ { path: 'components.message.assistant.text', kind: 'foreground' },
83
+ ],
84
+ };
85
+
86
+ export const ROLE_PRIMARY_ACTIONS: RoleAssignmentOptions = {
87
+ roleId: 'role-primary-actions',
88
+ helper: 'Send button and primary buttons',
89
+ intensities: ROLE_INTENSITIES,
90
+ targets: [
91
+ { path: 'components.button.primary.background', kind: 'background' },
92
+ { path: 'components.button.primary.foreground', kind: 'foreground' },
93
+ { path: 'semantic.colors.interactive.default', kind: 'accent' },
94
+ { path: 'semantic.colors.interactive.hover', kind: 'accent' },
95
+ ],
96
+ };
97
+
98
+ export const ROLE_INPUT: RoleAssignmentOptions = {
99
+ roleId: 'role-input',
100
+ helper: 'Message input field',
101
+ previewZone: 'composer',
102
+ intensities: ROLE_INTENSITIES,
103
+ targets: [
104
+ { path: 'components.input.background', kind: 'background' },
105
+ { path: 'components.input.placeholder', kind: 'foreground' },
106
+ { path: 'components.input.focus.border', kind: 'accent' },
107
+ { path: 'components.input.focus.ring', kind: 'accent' },
108
+ ],
109
+ };
110
+
111
+ export const ROLE_LINKS_FOCUS: RoleAssignmentOptions = {
112
+ roleId: 'role-links-focus',
113
+ helper: 'Links, focus rings, and interactive highlights',
114
+ intensities: ROLE_INTENSITIES,
115
+ targets: [
116
+ { path: 'semantic.colors.accent', kind: 'accent' },
117
+ { path: 'semantic.colors.interactive.focus', kind: 'accent' },
118
+ { path: 'semantic.colors.interactive.active', kind: 'accent' },
119
+ { path: 'components.markdown.link.foreground', kind: 'accent' },
120
+ ],
121
+ };
122
+
123
+ export const ROLE_BORDERS: RoleAssignmentOptions = {
124
+ roleId: 'role-borders',
125
+ helper: 'Borders, dividers, and separators',
126
+ intensities: ROLE_INTENSITIES,
127
+ targets: [
128
+ { path: 'semantic.colors.border', kind: 'border' },
129
+ { path: 'semantic.colors.divider', kind: 'border' },
130
+ ],
131
+ };
132
+
133
+ /** All interface role definitions in display order */
134
+ export const ALL_ROLES: RoleAssignmentOptions[] = [
135
+ ROLE_SURFACES,
136
+ ROLE_HEADER,
137
+ ROLE_USER_MESSAGES,
138
+ ROLE_ASSISTANT_MESSAGES,
139
+ ROLE_PRIMARY_ACTIONS,
140
+ ROLE_INPUT,
141
+ ROLE_LINKS_FOCUS,
142
+ ROLE_BORDERS,
143
+ ];
144
+
145
+ // ─── Resolution ─────────────────────────────────────────────────
146
+
147
+ /**
148
+ * Resolve a role assignment (family + intensity) into concrete token writes.
149
+ *
150
+ * Returns a map of `{ "theme.{path}": "palette.colors.{family}.{shade}" }`.
151
+ * The `theme.` prefix is added so callers can pass the result directly to
152
+ * `state.setBatch()`.
153
+ */
154
+ export function resolveRoleAssignment(
155
+ family: string,
156
+ intensity: string,
157
+ role: RoleAssignmentOptions
158
+ ): Record<string, string> {
159
+ const writes: Record<string, string> = {};
160
+ const f = family === 'neutral' ? 'gray' : family;
161
+
162
+ for (const target of role.targets) {
163
+ const value = resolveTarget(f, intensity, target, role.roleId);
164
+ writes[`theme.${target.path}`] = value;
165
+ writes[`darkTheme.${target.path}`] = value;
166
+ }
167
+
168
+ // For primary-actions, also write the hover shade (one step darker than default)
169
+ if (role.roleId === 'role-primary-actions') {
170
+ const hoverValue = intensity === 'solid'
171
+ ? `palette.colors.${f}.700`
172
+ : `palette.colors.${f}.200`;
173
+ writes['theme.semantic.colors.interactive.hover'] = hoverValue;
174
+ writes['darkTheme.semantic.colors.interactive.hover'] = hoverValue;
175
+ }
176
+
177
+ return writes;
178
+ }
179
+
180
+ function resolveTarget(
181
+ family: string,
182
+ intensity: string,
183
+ target: RoleTarget,
184
+ roleId: string
185
+ ): string {
186
+ const solid = intensity === 'solid';
187
+
188
+ // Header has nuanced per-sub-target shading
189
+ if (roleId === 'role-header') {
190
+ return resolveHeaderTarget(family, solid, target);
191
+ }
192
+
193
+ // Input has special handling — foreground target is the placeholder
194
+ if (roleId === 'role-input') {
195
+ return resolveInputTarget(family, solid, target);
196
+ }
197
+
198
+ switch (target.kind) {
199
+ case 'background':
200
+ return solid
201
+ ? `palette.colors.${family}.500`
202
+ : `palette.colors.${family}.${family === 'gray' ? '50' : '100'}`;
203
+ case 'foreground':
204
+ return solid
205
+ ? `palette.colors.${family === 'gray' ? 'gray' : family}.50`
206
+ : `palette.colors.${family === 'gray' ? 'gray' : family}.900`;
207
+ case 'border':
208
+ return solid
209
+ ? `palette.colors.${family}.600`
210
+ : `palette.colors.${family}.200`;
211
+ case 'accent':
212
+ return solid
213
+ ? `palette.colors.${family}.600`
214
+ : `palette.colors.${family}.400`;
215
+ }
216
+ }
217
+
218
+ function resolveHeaderTarget(family: string, solid: boolean, target: RoleTarget): string {
219
+ const path = target.path;
220
+
221
+ if (path.endsWith('.background')) {
222
+ return solid
223
+ ? `palette.colors.${family}.500`
224
+ : `palette.colors.${family}.${family === 'gray' ? '50' : '100'}`;
225
+ }
226
+ if (path.endsWith('.border')) {
227
+ return solid
228
+ ? `palette.colors.${family}.600`
229
+ : `palette.colors.${family}.200`;
230
+ }
231
+ if (path.endsWith('.iconBackground')) {
232
+ return solid
233
+ ? `palette.colors.${family}.${family === 'gray' ? '700' : '600'}`
234
+ : `palette.colors.${family}.500`;
235
+ }
236
+ if (path.endsWith('.iconForeground')) {
237
+ return solid
238
+ ? `palette.colors.${family}.50`
239
+ : `palette.colors.${family}.50`;
240
+ }
241
+ if (path.endsWith('.titleForeground')) {
242
+ return solid
243
+ ? `palette.colors.${family}.50`
244
+ : `palette.colors.${family}.${family === 'gray' ? '900' : '700'}`;
245
+ }
246
+ if (path.endsWith('.subtitleForeground')) {
247
+ return solid
248
+ ? `palette.colors.${family}.200`
249
+ : `palette.colors.${family}.500`;
250
+ }
251
+ if (path.endsWith('.actionIconForeground')) {
252
+ return solid
253
+ ? `palette.colors.${family}.200`
254
+ : `palette.colors.${family}.500`;
255
+ }
256
+
257
+ // Fallback
258
+ return `palette.colors.${family}.500`;
259
+ }
260
+
261
+ function resolveInputTarget(family: string, solid: boolean, target: RoleTarget): string {
262
+ const path = target.path;
263
+
264
+ if (path.endsWith('.background')) {
265
+ return solid
266
+ ? `palette.colors.${family}.${family === 'gray' ? '100' : '50'}`
267
+ : `palette.colors.${family}.${family === 'gray' ? '50' : '50'}`;
268
+ }
269
+ if (path.endsWith('.placeholder')) {
270
+ return `palette.colors.${family}.${solid ? '400' : '400'}`;
271
+ }
272
+ if (path.endsWith('.border') || path.endsWith('.ring')) {
273
+ return solid
274
+ ? `palette.colors.${family}.500`
275
+ : `palette.colors.${family}.400`;
276
+ }
277
+
278
+ return `palette.colors.${family}.500`;
279
+ }
280
+
281
+ // ─── Detection (reverse mapping) ────────────────────────────────
282
+
283
+ /** Result of detecting a role assignment from current state */
284
+ export interface DetectedRoleAssignment {
285
+ family: RoleFamily;
286
+ intensity: string;
287
+ }
288
+
289
+ /** Pattern: palette.colors.{family}.{shade} */
290
+ const PALETTE_REF_RE = /^palette\.colors\.(\w+)\.(\d+)$/;
291
+
292
+ /**
293
+ * Detect the current role assignment by reading token values and matching
294
+ * against known palette reference patterns.
295
+ *
296
+ * @param getValue - Function to read a theme token value (e.g., `(p) => state.get('theme.' + p)`)
297
+ * @param role - The role definition to detect against
298
+ * @returns Detected assignment or null if tokens don't match a known pattern
299
+ */
300
+ export function detectRoleAssignment(
301
+ getValue: (path: string) => unknown,
302
+ role: RoleAssignmentOptions
303
+ ): DetectedRoleAssignment | null {
304
+ // Read the first background target (or first target of any kind) to determine the family
305
+ const probeTarget = role.targets.find((t) => t.kind === 'background') ?? role.targets[0];
306
+ if (!probeTarget) return null;
307
+
308
+ const bgValue = String(getValue(probeTarget.path) ?? '');
309
+ const bgMatch = bgValue.match(PALETTE_REF_RE);
310
+ if (!bgMatch) return null;
311
+
312
+ const detectedFamily = bgMatch[1] as string;
313
+
314
+ // Normalize gray → gray (it's already the canonical name)
315
+ const family = ROLE_FAMILIES.includes(detectedFamily as RoleFamily)
316
+ ? (detectedFamily as RoleFamily)
317
+ : null;
318
+ if (!family) return null;
319
+
320
+ // Try both intensities — shade-based guessing doesn't work for all target kinds
321
+ for (const intensity of ['solid', 'soft'] as const) {
322
+ const expected = resolveRoleAssignment(family, intensity, role);
323
+ const allMatch = role.targets.every((t) => {
324
+ const actual = String(getValue(t.path) ?? '');
325
+ return actual === expected[`theme.${t.path}`];
326
+ });
327
+ if (allMatch) return { family, intensity };
328
+ }
329
+
330
+ return null;
331
+ }