@runtypelabs/persona 2.3.1 → 3.1.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 (43) hide show
  1. package/README.md +222 -5
  2. package/dist/index.cjs +42 -42
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +832 -571
  5. package/dist/index.d.ts +832 -571
  6. package/dist/index.global.js +88 -88
  7. package/dist/index.global.js.map +1 -1
  8. package/dist/index.js +42 -42
  9. package/dist/index.js.map +1 -1
  10. package/dist/widget.css +257 -67
  11. package/package.json +2 -4
  12. package/src/components/artifact-card.ts +39 -5
  13. package/src/components/artifact-pane.ts +68 -127
  14. package/src/components/composer-builder.ts +3 -23
  15. package/src/components/header-builder.ts +29 -34
  16. package/src/components/header-layouts.ts +109 -41
  17. package/src/components/launcher.ts +10 -7
  18. package/src/components/message-bubble.ts +7 -11
  19. package/src/components/panel.ts +4 -4
  20. package/src/defaults.ts +22 -93
  21. package/src/index.ts +20 -7
  22. package/src/presets.ts +66 -51
  23. package/src/runtime/host-layout.test.ts +333 -0
  24. package/src/runtime/host-layout.ts +346 -27
  25. package/src/runtime/init.test.ts +113 -8
  26. package/src/runtime/init.ts +1 -1
  27. package/src/styles/widget.css +257 -67
  28. package/src/types/theme.ts +76 -0
  29. package/src/types.ts +86 -97
  30. package/src/ui.docked.test.ts +203 -7
  31. package/src/ui.ts +125 -92
  32. package/src/utils/artifact-gate.ts +1 -1
  33. package/src/utils/buttons.ts +417 -0
  34. package/src/utils/code-generators.test.ts +43 -7
  35. package/src/utils/code-generators.ts +9 -25
  36. package/src/utils/deep-merge.ts +26 -0
  37. package/src/utils/dock.ts +18 -5
  38. package/src/utils/dropdown.ts +178 -0
  39. package/src/utils/theme.test.ts +90 -15
  40. package/src/utils/theme.ts +20 -46
  41. package/src/utils/tokens.ts +108 -11
  42. package/src/styles/tailwind.css +0 -20
  43. package/src/utils/migration.ts +0 -220
@@ -1,7 +1,7 @@
1
1
  import { createElement } from "../utils/dom";
2
2
  import { AgentWidgetConfig } from "../types";
3
3
  import { positionMap } from "../utils/positioning";
4
- import { isDockedMountMode, resolveDockConfig } from "../utils/dock";
4
+ import { isDockedMountMode } from "../utils/dock";
5
5
  import { renderLucideIcon } from "../utils/icons";
6
6
 
7
7
  export interface LauncherButton {
@@ -175,18 +175,21 @@ export const createLauncherButton = (
175
175
  button.style.boxShadow = launcher.shadow ?? defaultShadow;
176
176
 
177
177
  if (dockedMode) {
178
- const dock = resolveDockConfig(newConfig);
179
- button.style.width = `calc(${dock.collapsedWidth} - 16px)`;
180
- button.style.minWidth = "40px";
181
- button.style.maxWidth = `calc(${dock.collapsedWidth} - 16px)`;
182
- button.style.justifyContent = "center";
183
- button.style.padding = "12px 0";
178
+ // Docked mode uses a 0px column when closed and hides this button; keep no hit target.
179
+ button.style.width = "0";
180
+ button.style.minWidth = "0";
181
+ button.style.maxWidth = "0";
182
+ button.style.padding = "0";
183
+ button.style.overflow = "hidden";
184
+ button.style.border = "none";
185
+ button.style.boxShadow = "none";
184
186
  } else {
185
187
  button.style.width = "";
186
188
  button.style.minWidth = "";
187
189
  button.style.maxWidth = "";
188
190
  button.style.justifyContent = "";
189
191
  button.style.padding = "";
192
+ button.style.overflow = "";
190
193
  }
191
194
  };
192
195
 
@@ -9,7 +9,7 @@ import {
9
9
  LoadingIndicatorRenderContext,
10
10
  ImageContentPart
11
11
  } from "../types";
12
- import { renderLucideIcon } from "../utils/icons";
12
+ import { createIconButton } from "../utils/buttons";
13
13
  import { IMAGE_ONLY_MESSAGE_FALLBACK_TEXT } from "../utils/content";
14
14
 
15
15
  /** Validate that an image src URL uses a safe scheme (blocks javascript: and SVG data URIs). */
@@ -414,17 +414,13 @@ export const createMessageActions = (
414
414
  label: string,
415
415
  dataAction: string
416
416
  ): HTMLButtonElement => {
417
- const button = document.createElement("button");
418
- button.className = "persona-message-action-btn";
419
- button.setAttribute("aria-label", label);
420
- button.setAttribute("title", label);
417
+ const button = createIconButton({
418
+ icon: iconName,
419
+ label,
420
+ size: 14,
421
+ className: "persona-message-action-btn",
422
+ });
421
423
  button.setAttribute("data-action", dataAction);
422
-
423
- const icon = renderLucideIcon(iconName, 14, "currentColor", 2);
424
- if (icon) {
425
- button.appendChild(icon);
426
- }
427
-
428
424
  return button;
429
425
  };
430
426
 
@@ -133,10 +133,10 @@ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelE
133
133
  body.id = "persona-scroll-container";
134
134
  body.setAttribute("data-persona-theme-zone", "messages");
135
135
 
136
- const introCard = createElement(
137
- "div",
138
- "persona-rounded-2xl persona-bg-persona-surface persona-p-6 persona-shadow-sm"
139
- );
136
+ const introCardClasses = isDockedMountMode(config)
137
+ ? "persona-rounded-2xl persona-bg-persona-surface persona-p-6"
138
+ : "persona-rounded-2xl persona-bg-persona-surface persona-p-6 persona-shadow-sm";
139
+ const introCard = createElement("div", introCardClasses);
140
140
  const introTitle = createElement(
141
141
  "h2",
142
142
  "persona-text-lg persona-font-semibold persona-text-persona-primary"
package/src/defaults.ts CHANGED
@@ -1,82 +1,6 @@
1
- import type { AgentWidgetConfig, AgentWidgetTheme } from "./types";
2
-
3
- /**
4
- * Default light theme colors
5
- */
6
- export const DEFAULT_LIGHT_THEME: AgentWidgetTheme = {
7
- primary: "#111827",
8
- accent: "#1d4ed8",
9
- surface: "#ffffff",
10
- muted: "#6b7280",
11
- container: "#f8fafc",
12
- border: "#f1f5f9",
13
- divider: "#f1f5f9",
14
- messageBorder: "#f1f5f9",
15
- inputBackground: "#ffffff",
16
- callToAction: "#000000",
17
- callToActionBackground: "#ffffff",
18
- sendButtonBackgroundColor: "#111827",
19
- sendButtonTextColor: "#ffffff",
20
- sendButtonBorderColor: "#60a5fa",
21
- closeButtonColor: "#6b7280",
22
- closeButtonBackgroundColor: "transparent",
23
- closeButtonBorderColor: "",
24
- clearChatIconColor: "#6b7280",
25
- clearChatBackgroundColor: "transparent",
26
- clearChatBorderColor: "transparent",
27
- micIconColor: "#111827",
28
- micBackgroundColor: "transparent",
29
- micBorderColor: "transparent",
30
- recordingIconColor: "#ffffff",
31
- recordingBackgroundColor: "#ef4444",
32
- recordingBorderColor: "transparent",
33
- inputFontFamily: "sans-serif",
34
- inputFontWeight: "400",
35
- radiusSm: "0.75rem",
36
- radiusMd: "1rem",
37
- radiusLg: "1.5rem",
38
- launcherRadius: "9999px",
39
- buttonRadius: "9999px",
40
- };
41
-
42
- /**
43
- * Default dark theme colors
44
- */
45
- export const DEFAULT_DARK_THEME: AgentWidgetTheme = {
46
- primary: "#f9fafb",
47
- accent: "#3b82f6",
48
- surface: "#1f2937",
49
- muted: "#9ca3af",
50
- container: "#111827",
51
- border: "#374151",
52
- divider: "#374151",
53
- messageBorder: "#374151",
54
- inputBackground: "#111827",
55
- callToAction: "#ffffff",
56
- callToActionBackground: "#374151",
57
- sendButtonBackgroundColor: "#3b82f6",
58
- sendButtonTextColor: "#ffffff",
59
- sendButtonBorderColor: "#60a5fa",
60
- closeButtonColor: "#9ca3af",
61
- closeButtonBackgroundColor: "transparent",
62
- closeButtonBorderColor: "",
63
- clearChatIconColor: "#9ca3af",
64
- clearChatBackgroundColor: "transparent",
65
- clearChatBorderColor: "transparent",
66
- micIconColor: "#f9fafb",
67
- micBackgroundColor: "transparent",
68
- micBorderColor: "transparent",
69
- recordingIconColor: "#ffffff",
70
- recordingBackgroundColor: "#ef4444",
71
- recordingBorderColor: "transparent",
72
- inputFontFamily: "sans-serif",
73
- inputFontWeight: "400",
74
- radiusSm: "0.75rem",
75
- radiusMd: "1rem",
76
- radiusLg: "1.5rem",
77
- launcherRadius: "9999px",
78
- buttonRadius: "9999px",
79
- };
1
+ import type { AgentWidgetConfig } from "./types";
2
+ import type { DeepPartial, PersonaTheme } from "./types/theme";
3
+ import { deepMerge } from "./utils/deep-merge";
80
4
 
81
5
  /**
82
6
  * Default widget configuration
@@ -86,8 +10,8 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
86
10
  apiUrl: "http://localhost:43111/api/chat/dispatch",
87
11
  // Client token mode defaults (optional, only used when clientToken is set)
88
12
  clientToken: undefined,
89
- theme: DEFAULT_LIGHT_THEME,
90
- darkTheme: DEFAULT_DARK_THEME,
13
+ theme: undefined,
14
+ darkTheme: undefined,
91
15
  colorScheme: "light",
92
16
  launcher: {
93
17
  enabled: true,
@@ -95,7 +19,6 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
95
19
  dock: {
96
20
  side: "right",
97
21
  width: "420px",
98
- collapsedWidth: "72px",
99
22
  },
100
23
  title: "Chat Assistant",
101
24
  subtitle: "Here to help you get answers fast",
@@ -114,10 +37,9 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
114
37
  callToActionIconPadding: "5px",
115
38
  callToActionIconColor: "#000000",
116
39
  callToActionIconBackgroundColor: "#ffffff",
117
- closeButtonColor: "#6b7280",
40
+ // closeButtonColor / clearChat.iconColor omitted so theme.components.header.actionIconForeground applies.
118
41
  closeButtonBackgroundColor: "transparent",
119
42
  clearChat: {
120
- iconColor: "#6b7280",
121
43
  backgroundColor: "transparent",
122
44
  borderColor: "transparent",
123
45
  enabled: true,
@@ -225,7 +147,7 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
225
147
  messageActions: {
226
148
  enabled: true,
227
149
  showCopy: true,
228
- showUpvote: false, // Requires backend - disabled by default
150
+ showUpvote: false, // Requires backend - disabled by default
229
151
  showDownvote: false, // Requires backend - disabled by default
230
152
  visibility: "hover",
231
153
  align: "right",
@@ -234,6 +156,19 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
234
156
  debug: false,
235
157
  };
236
158
 
159
+ function mergeThemePartials(
160
+ base: DeepPartial<PersonaTheme> | undefined,
161
+ override: DeepPartial<PersonaTheme> | undefined
162
+ ): DeepPartial<PersonaTheme> | undefined {
163
+ if (!base && !override) return undefined;
164
+ if (!base) return override;
165
+ if (!override) return base;
166
+ return deepMerge(
167
+ base as Record<string, unknown>,
168
+ override as Record<string, unknown>
169
+ ) as DeepPartial<PersonaTheme>;
170
+ }
171
+
237
172
  /**
238
173
  * Helper to deep merge user config with defaults
239
174
  * This ensures all default values are present while allowing selective overrides
@@ -246,14 +181,8 @@ export function mergeWithDefaults(
246
181
  return {
247
182
  ...DEFAULT_WIDGET_CONFIG,
248
183
  ...config,
249
- theme: {
250
- ...DEFAULT_WIDGET_CONFIG.theme,
251
- ...config.theme,
252
- },
253
- darkTheme: {
254
- ...DEFAULT_WIDGET_CONFIG.darkTheme,
255
- ...config.darkTheme,
256
- },
184
+ theme: mergeThemePartials(DEFAULT_WIDGET_CONFIG.theme, config.theme),
185
+ darkTheme: mergeThemePartials(DEFAULT_WIDGET_CONFIG.darkTheme, config.darkTheme),
257
186
  launcher: {
258
187
  ...DEFAULT_WIDGET_CONFIG.launcher,
259
188
  ...config.launcher,
package/src/index.ts CHANGED
@@ -5,7 +5,6 @@ import {
5
5
 
6
6
  export type {
7
7
  AgentWidgetConfig,
8
- AgentWidgetTheme,
9
8
  AgentWidgetFeatureFlags,
10
9
  AgentWidgetArtifactsFeature,
11
10
  AgentWidgetArtifactsLayoutConfig,
@@ -179,6 +178,22 @@ export type { AgentWidgetInitHandle };
179
178
  export type { AgentWidgetPlugin } from "./plugins/types";
180
179
  export { pluginRegistry } from "./plugins/registry";
181
180
 
181
+ // Dropdown utility exports
182
+ export { createDropdownMenu } from "./utils/dropdown";
183
+ export type { DropdownMenuItem, CreateDropdownOptions, DropdownMenuHandle } from "./utils/dropdown";
184
+
185
+ // Button utility exports
186
+ export { createIconButton, createLabelButton, createToggleGroup, createComboButton } from "./utils/buttons";
187
+ export type {
188
+ CreateIconButtonOptions,
189
+ CreateLabelButtonOptions,
190
+ CreateToggleGroupOptions,
191
+ ToggleGroupItem,
192
+ ToggleGroupHandle,
193
+ CreateComboButtonOptions,
194
+ ComboButtonHandle
195
+ } from "./utils/buttons";
196
+
182
197
  // Theme system exports
183
198
  export {
184
199
  createTheme,
@@ -206,11 +221,8 @@ export {
206
221
  highContrastPlugin,
207
222
  createPlugin
208
223
  } from "./utils/plugins";
209
- export {
210
- migrateV1Theme,
211
- validateV1Theme
212
- } from "./utils/migration";
213
224
  export type {
225
+ DeepPartial,
214
226
  PersonaTheme,
215
227
  PersonaThemePlugin,
216
228
  CreateThemeOptions,
@@ -229,6 +241,9 @@ export type {
229
241
  ArtifactToolbarTokens,
230
242
  ArtifactTabTokens,
231
243
  ArtifactPaneTokens,
244
+ IconButtonTokens,
245
+ LabelButtonTokens,
246
+ ToggleGroupTokens,
232
247
  ThemeValidationResult,
233
248
  ThemeValidationError
234
249
  } from "./types/theme";
@@ -251,8 +266,6 @@ export {
251
266
  // Default configuration exports
252
267
  export {
253
268
  DEFAULT_WIDGET_CONFIG,
254
- DEFAULT_LIGHT_THEME,
255
- DEFAULT_DARK_THEME,
256
269
  mergeWithDefaults
257
270
  } from "./defaults";
258
271
  export {
package/src/presets.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { AgentWidgetConfig } from './types';
1
+ import type { AgentWidgetConfig } from "./types";
2
+ import type { DeepPartial, PersonaTheme } from "./types/theme";
2
3
 
3
4
  /**
4
5
  * A named preset containing partial widget configuration.
@@ -11,51 +12,71 @@ export interface WidgetPreset {
11
12
  config: Partial<AgentWidgetConfig>;
12
13
  }
13
14
 
15
+ /** Shopping palette + semantic roles (matches prior shop preset visuals). */
16
+ const SHOP_THEME: DeepPartial<PersonaTheme> = {
17
+ palette: {
18
+ colors: {
19
+ primary: { 500: "#111827" },
20
+ accent: { 600: "#1d4ed8" },
21
+ gray: {
22
+ 50: "#ffffff",
23
+ 100: "#f8fafc",
24
+ 200: "#f1f5f9",
25
+ 500: "#6b7280",
26
+ 900: "#000000",
27
+ },
28
+ },
29
+ radius: {
30
+ sm: "0.75rem",
31
+ md: "1rem",
32
+ lg: "1.5rem",
33
+ launcher: "9999px",
34
+ button: "9999px",
35
+ },
36
+ },
37
+ semantic: {
38
+ colors: {
39
+ primary: "palette.colors.primary.500",
40
+ textInverse: "palette.colors.gray.50",
41
+ },
42
+ },
43
+ };
44
+
45
+ const PANEL_EDGELESS_THEME: DeepPartial<PersonaTheme> = {
46
+ components: {
47
+ panel: {
48
+ borderRadius: "0",
49
+ shadow: "none",
50
+ },
51
+ },
52
+ };
53
+
14
54
  /**
15
55
  * Shopping / e-commerce preset.
16
56
  * Dark header, rounded launchers, shopping-oriented copy.
17
57
  */
18
58
  export const PRESET_SHOP: WidgetPreset = {
19
- id: 'shop',
20
- label: 'Shopping Assistant',
59
+ id: "shop",
60
+ label: "Shopping Assistant",
21
61
  config: {
22
- theme: {
23
- primary: '#111827',
24
- accent: '#1d4ed8',
25
- surface: '#ffffff',
26
- muted: '#6b7280',
27
- container: '#f8fafc',
28
- border: '#f1f5f9',
29
- divider: '#f1f5f9',
30
- messageBorder: '#f1f5f9',
31
- inputBackground: '#ffffff',
32
- callToAction: '#000000',
33
- callToActionBackground: '#ffffff',
34
- sendButtonBackgroundColor: '#111827',
35
- sendButtonTextColor: '#ffffff',
36
- radiusSm: '0.75rem',
37
- radiusMd: '1rem',
38
- radiusLg: '1.5rem',
39
- launcherRadius: '9999px',
40
- buttonRadius: '9999px',
41
- },
62
+ theme: SHOP_THEME,
42
63
  launcher: {
43
- title: 'Shopping Assistant',
44
- subtitle: 'Here to help you find what you need',
45
- agentIconText: '🛍️',
46
- position: 'bottom-right',
47
- width: 'min(400px, calc(100vw - 24px))',
64
+ title: "Shopping Assistant",
65
+ subtitle: "Here to help you find what you need",
66
+ agentIconText: "🛍️",
67
+ position: "bottom-right",
68
+ width: "min(400px, calc(100vw - 24px))",
48
69
  },
49
70
  copy: {
50
- welcomeTitle: 'Welcome to our shop!',
51
- welcomeSubtitle: 'I can help you find products and answer questions',
52
- inputPlaceholder: 'Ask me anything...',
53
- sendButtonLabel: 'Send',
71
+ welcomeTitle: "Welcome to our shop!",
72
+ welcomeSubtitle: "I can help you find products and answer questions",
73
+ inputPlaceholder: "Ask me anything...",
74
+ sendButtonLabel: "Send",
54
75
  },
55
76
  suggestionChips: [
56
- 'What can you help me with?',
57
- 'Tell me about your features',
58
- 'How does this work?',
77
+ "What can you help me with?",
78
+ "Tell me about your features",
79
+ "How does this work?",
59
80
  ],
60
81
  },
61
82
  };
@@ -65,8 +86,8 @@ export const PRESET_SHOP: WidgetPreset = {
65
86
  * Stripped-down header, no launcher button, suitable for inline embeds.
66
87
  */
67
88
  export const PRESET_MINIMAL: WidgetPreset = {
68
- id: 'minimal',
69
- label: 'Minimal',
89
+ id: "minimal",
90
+ label: "Minimal",
70
91
  config: {
71
92
  launcher: {
72
93
  enabled: false,
@@ -74,17 +95,14 @@ export const PRESET_MINIMAL: WidgetPreset = {
74
95
  },
75
96
  layout: {
76
97
  header: {
77
- layout: 'minimal',
98
+ layout: "minimal",
78
99
  showCloseButton: false,
79
100
  },
80
101
  messages: {
81
- layout: 'minimal',
102
+ layout: "minimal",
82
103
  },
83
104
  },
84
- theme: {
85
- panelBorderRadius: '0',
86
- panelShadow: 'none',
87
- },
105
+ theme: PANEL_EDGELESS_THEME,
88
106
  },
89
107
  };
90
108
 
@@ -93,8 +111,8 @@ export const PRESET_MINIMAL: WidgetPreset = {
93
111
  * No launcher, content-max-width constrained, minimal header.
94
112
  */
95
113
  export const PRESET_FULLSCREEN: WidgetPreset = {
96
- id: 'fullscreen',
97
- label: 'Fullscreen Assistant',
114
+ id: "fullscreen",
115
+ label: "Fullscreen Assistant",
98
116
  config: {
99
117
  launcher: {
100
118
  enabled: false,
@@ -102,15 +120,12 @@ export const PRESET_FULLSCREEN: WidgetPreset = {
102
120
  },
103
121
  layout: {
104
122
  header: {
105
- layout: 'minimal',
123
+ layout: "minimal",
106
124
  showCloseButton: false,
107
125
  },
108
- contentMaxWidth: '72ch',
109
- },
110
- theme: {
111
- panelBorderRadius: '0',
112
- panelShadow: 'none',
126
+ contentMaxWidth: "72ch",
113
127
  },
128
+ theme: PANEL_EDGELESS_THEME,
114
129
  },
115
130
  };
116
131