@runtypelabs/persona 2.3.0 → 3.0.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/README.md +221 -4
- package/dist/index.cjs +42 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +832 -571
- package/dist/index.d.ts +832 -571
- package/dist/index.global.js +87 -87
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +42 -42
- package/dist/index.js.map +1 -1
- package/dist/widget.css +205 -15
- package/package.json +2 -2
- package/src/components/artifact-card.ts +39 -5
- package/src/components/artifact-pane.ts +67 -126
- package/src/components/composer-builder.ts +3 -23
- package/src/components/header-builder.ts +29 -34
- package/src/components/header-layouts.ts +109 -41
- package/src/components/launcher.ts +10 -7
- package/src/components/message-bubble.ts +7 -11
- package/src/components/panel.ts +4 -4
- package/src/defaults.ts +22 -93
- package/src/index.ts +20 -7
- package/src/presets.ts +66 -51
- package/src/runtime/host-layout.test.ts +196 -0
- package/src/runtime/host-layout.ts +265 -27
- package/src/runtime/init.test.ts +77 -7
- package/src/styles/widget.css +205 -15
- package/src/types/theme.ts +76 -0
- package/src/types.ts +86 -97
- package/src/ui.docked.test.ts +203 -7
- package/src/ui.ts +129 -88
- package/src/utils/buttons.ts +417 -0
- package/src/utils/code-generators.test.ts +43 -7
- package/src/utils/code-generators.ts +9 -25
- package/src/utils/deep-merge.ts +26 -0
- package/src/utils/dock.ts +18 -5
- package/src/utils/dropdown.ts +178 -0
- package/src/utils/sanitize.ts +1 -1
- package/src/utils/theme.test.ts +90 -15
- package/src/utils/theme.ts +20 -46
- package/src/utils/tokens.ts +108 -11
- package/src/utils/migration.ts +0 -220
|
@@ -2,6 +2,16 @@ import { createElement, createElementInDocument } from "../utils/dom";
|
|
|
2
2
|
import { renderLucideIcon } from "../utils/icons";
|
|
3
3
|
import { AgentWidgetConfig } from "../types";
|
|
4
4
|
|
|
5
|
+
/** CSS `color` values; variables are set on `#persona-root` from `theme.components.header`. */
|
|
6
|
+
export const HEADER_THEME_CSS = {
|
|
7
|
+
titleColor:
|
|
8
|
+
"var(--persona-header-title-fg, var(--persona-primary, #2563eb))",
|
|
9
|
+
subtitleColor:
|
|
10
|
+
"var(--persona-header-subtitle-fg, var(--persona-text-muted, var(--persona-muted, #9ca3af)))",
|
|
11
|
+
actionIconColor:
|
|
12
|
+
"var(--persona-header-action-icon-fg, var(--persona-muted, #9ca3af))",
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
5
15
|
export interface HeaderElements {
|
|
6
16
|
header: HTMLElement;
|
|
7
17
|
iconHolder: HTMLElement;
|
|
@@ -33,9 +43,9 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
33
43
|
);
|
|
34
44
|
header.setAttribute("data-persona-theme-zone", "header");
|
|
35
45
|
header.style.backgroundColor = 'var(--persona-header-bg, var(--persona-surface, #ffffff))';
|
|
36
|
-
header.style.borderBottomWidth = '1px';
|
|
37
|
-
header.style.borderBottomStyle = 'solid';
|
|
38
46
|
header.style.borderBottomColor = 'var(--persona-header-border, var(--persona-divider, #f1f5f9))';
|
|
47
|
+
header.style.boxShadow = 'var(--persona-header-shadow, none)';
|
|
48
|
+
header.style.borderBottom = 'var(--persona-header-border-bottom, 1px solid var(--persona-header-border, var(--persona-divider, #f1f5f9)))';
|
|
39
49
|
|
|
40
50
|
const launcher = config?.launcher ?? {};
|
|
41
51
|
const headerIconSize = launcher.headerIconSize ?? "48px";
|
|
@@ -46,17 +56,21 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
46
56
|
|
|
47
57
|
const iconHolder = createElement(
|
|
48
58
|
"div",
|
|
49
|
-
"persona-flex persona-items-center persona-justify-center persona-rounded-xl persona-
|
|
59
|
+
"persona-flex persona-items-center persona-justify-center persona-rounded-xl persona-text-xl"
|
|
50
60
|
);
|
|
51
61
|
iconHolder.style.height = headerIconSize;
|
|
52
62
|
iconHolder.style.width = headerIconSize;
|
|
63
|
+
iconHolder.style.backgroundColor =
|
|
64
|
+
"var(--persona-header-icon-bg, var(--persona-primary, #2563eb))";
|
|
65
|
+
iconHolder.style.color =
|
|
66
|
+
"var(--persona-header-icon-fg, var(--persona-text-inverse, #ffffff))";
|
|
53
67
|
|
|
54
68
|
// Render icon based on priority: Lucide icon > iconUrl > agentIconText
|
|
55
69
|
if (!headerIconHidden) {
|
|
56
70
|
if (headerIconName) {
|
|
57
71
|
// Use Lucide icon
|
|
58
72
|
const iconSize = parseFloat(headerIconSize) || 24;
|
|
59
|
-
const iconSvg = renderLucideIcon(headerIconName, iconSize * 0.6, "
|
|
73
|
+
const iconSvg = renderLucideIcon(headerIconName, iconSize * 0.6, "currentColor", 1);
|
|
60
74
|
if (iconSvg) {
|
|
61
75
|
iconHolder.replaceChildren(iconSvg);
|
|
62
76
|
} else {
|
|
@@ -80,8 +94,10 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
80
94
|
|
|
81
95
|
const headerCopy = createElement("div", "persona-flex persona-flex-col");
|
|
82
96
|
const title = createElement("span", "persona-text-base persona-font-semibold");
|
|
97
|
+
title.style.color = HEADER_THEME_CSS.titleColor;
|
|
83
98
|
title.textContent = config?.launcher?.title ?? "Chat Assistant";
|
|
84
|
-
const subtitle = createElement("span", "persona-text-xs
|
|
99
|
+
const subtitle = createElement("span", "persona-text-xs");
|
|
100
|
+
subtitle.style.color = HEADER_THEME_CSS.subtitleColor;
|
|
85
101
|
subtitle.textContent =
|
|
86
102
|
config?.launcher?.subtitle ?? "Here to help you get answers fast";
|
|
87
103
|
|
|
@@ -132,31 +148,22 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
132
148
|
|
|
133
149
|
clearChatButton = createElement(
|
|
134
150
|
"button",
|
|
135
|
-
"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full
|
|
151
|
+
"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full hover:persona-bg-gray-100 persona-cursor-pointer persona-border-none"
|
|
136
152
|
) as HTMLButtonElement;
|
|
137
153
|
|
|
138
154
|
clearChatButton.style.height = clearChatSize;
|
|
139
155
|
clearChatButton.style.width = clearChatSize;
|
|
140
156
|
clearChatButton.type = "button";
|
|
141
157
|
clearChatButton.setAttribute("aria-label", clearChatTooltipText);
|
|
158
|
+
clearChatButton.style.color =
|
|
159
|
+
clearChatIconColor || HEADER_THEME_CSS.actionIconColor;
|
|
142
160
|
|
|
143
161
|
// Add icon
|
|
144
|
-
const iconSvg = renderLucideIcon(
|
|
145
|
-
clearChatIconName,
|
|
146
|
-
"20px",
|
|
147
|
-
clearChatIconColor || "",
|
|
148
|
-
1
|
|
149
|
-
);
|
|
162
|
+
const iconSvg = renderLucideIcon(clearChatIconName, "20px", "currentColor", 1);
|
|
150
163
|
if (iconSvg) {
|
|
151
164
|
clearChatButton.appendChild(iconSvg);
|
|
152
165
|
}
|
|
153
166
|
|
|
154
|
-
// Apply styling from config
|
|
155
|
-
if (clearChatIconColor) {
|
|
156
|
-
clearChatButton.style.color = clearChatIconColor;
|
|
157
|
-
clearChatButton.classList.remove("persona-text-persona-muted");
|
|
158
|
-
}
|
|
159
|
-
|
|
160
167
|
if (clearChatBgColor) {
|
|
161
168
|
clearChatButton.style.backgroundColor = clearChatBgColor;
|
|
162
169
|
clearChatButton.classList.remove("hover:persona-bg-gray-100");
|
|
@@ -281,7 +288,7 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
281
288
|
// Create close button with base classes
|
|
282
289
|
const closeButton = createElement(
|
|
283
290
|
"button",
|
|
284
|
-
"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full
|
|
291
|
+
"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full hover:persona-bg-gray-100 persona-cursor-pointer persona-border-none"
|
|
285
292
|
) as HTMLButtonElement;
|
|
286
293
|
closeButton.style.height = closeButtonSize;
|
|
287
294
|
closeButton.style.width = closeButtonSize;
|
|
@@ -297,29 +304,17 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
297
304
|
// Add icon or fallback text
|
|
298
305
|
const closeButtonIconName = launcher.closeButtonIconName ?? "x";
|
|
299
306
|
const closeButtonIconText = launcher.closeButtonIconText ?? "×";
|
|
307
|
+
closeButton.style.color =
|
|
308
|
+
launcher.closeButtonColor || HEADER_THEME_CSS.actionIconColor;
|
|
300
309
|
|
|
301
310
|
// Try to render Lucide icon, fallback to text if not provided or fails
|
|
302
|
-
const closeIconSvg = renderLucideIcon(
|
|
303
|
-
closeButtonIconName,
|
|
304
|
-
"20px",
|
|
305
|
-
launcher.closeButtonColor || "",
|
|
306
|
-
1
|
|
307
|
-
);
|
|
311
|
+
const closeIconSvg = renderLucideIcon(closeButtonIconName, "20px", "currentColor", 1);
|
|
308
312
|
if (closeIconSvg) {
|
|
309
313
|
closeButton.appendChild(closeIconSvg);
|
|
310
314
|
} else {
|
|
311
315
|
closeButton.textContent = closeButtonIconText;
|
|
312
316
|
}
|
|
313
317
|
|
|
314
|
-
// Apply close button styling from config
|
|
315
|
-
if (launcher.closeButtonColor) {
|
|
316
|
-
closeButton.style.color = launcher.closeButtonColor;
|
|
317
|
-
closeButton.classList.remove("persona-text-persona-muted");
|
|
318
|
-
} else {
|
|
319
|
-
closeButton.style.color = "";
|
|
320
|
-
closeButton.classList.add("persona-text-persona-muted");
|
|
321
|
-
}
|
|
322
|
-
|
|
323
318
|
if (launcher.closeButtonBackgroundColor) {
|
|
324
319
|
closeButton.style.backgroundColor = launcher.closeButtonBackgroundColor;
|
|
325
320
|
closeButton.classList.remove("hover:persona-bg-gray-100");
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { createElement } from "../utils/dom";
|
|
2
2
|
import { renderLucideIcon } from "../utils/icons";
|
|
3
|
+
import { createDropdownMenu } from "../utils/dropdown";
|
|
4
|
+
import { createComboButton } from "../utils/buttons";
|
|
3
5
|
import {
|
|
4
6
|
AgentWidgetConfig,
|
|
5
7
|
AgentWidgetHeaderLayoutConfig,
|
|
6
8
|
AgentWidgetHeaderTrailingAction
|
|
7
9
|
} from "../types";
|
|
8
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
buildHeader,
|
|
12
|
+
HEADER_THEME_CSS,
|
|
13
|
+
HeaderElements,
|
|
14
|
+
attachHeaderToContainer as _attachHeaderToContainer,
|
|
15
|
+
} from "./header-builder";
|
|
9
16
|
|
|
10
17
|
export interface HeaderLayoutContext {
|
|
11
18
|
config: AgentWidgetConfig;
|
|
@@ -75,8 +82,27 @@ function appendTrailingHeaderActions(
|
|
|
75
82
|
} else if (a.label) {
|
|
76
83
|
btn.textContent = a.label;
|
|
77
84
|
}
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
|
|
86
|
+
if (a.menuItems?.length) {
|
|
87
|
+
// Wrap in a relative container for dropdown positioning
|
|
88
|
+
const wrapper = createElement("div", "persona-relative");
|
|
89
|
+
wrapper.appendChild(btn);
|
|
90
|
+
const dropdown = createDropdownMenu({
|
|
91
|
+
items: a.menuItems,
|
|
92
|
+
onSelect: (itemId) => onAction?.(itemId),
|
|
93
|
+
anchor: wrapper,
|
|
94
|
+
position: 'bottom-left',
|
|
95
|
+
});
|
|
96
|
+
wrapper.appendChild(dropdown.element);
|
|
97
|
+
btn.addEventListener("click", (e) => {
|
|
98
|
+
e.stopPropagation();
|
|
99
|
+
dropdown.toggle();
|
|
100
|
+
});
|
|
101
|
+
container.appendChild(wrapper);
|
|
102
|
+
} else {
|
|
103
|
+
btn.addEventListener("click", () => onAction?.(a.id));
|
|
104
|
+
container.appendChild(btn);
|
|
105
|
+
}
|
|
80
106
|
}
|
|
81
107
|
}
|
|
82
108
|
|
|
@@ -86,43 +112,88 @@ export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
|
|
|
86
112
|
|
|
87
113
|
const header = createElement(
|
|
88
114
|
"div",
|
|
89
|
-
"persona-flex persona-items-center persona-justify-between persona-
|
|
115
|
+
"persona-flex persona-items-center persona-justify-between persona-px-6 persona-py-4"
|
|
90
116
|
);
|
|
91
117
|
header.setAttribute("data-persona-theme-zone", "header");
|
|
118
|
+
header.style.backgroundColor = 'var(--persona-header-bg, var(--persona-surface, #ffffff))';
|
|
119
|
+
header.style.borderBottomColor = 'var(--persona-header-border, var(--persona-divider, #f1f5f9))';
|
|
120
|
+
header.style.boxShadow = 'var(--persona-header-shadow, none)';
|
|
121
|
+
header.style.borderBottom =
|
|
122
|
+
'var(--persona-header-border-bottom, 1px solid var(--persona-header-border, var(--persona-divider, #f1f5f9)))';
|
|
92
123
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
124
|
+
// Build the title area — either a combo button (titleMenu) or standard title row
|
|
125
|
+
const titleMenuConfig = layoutHeaderConfig?.titleMenu;
|
|
126
|
+
let titleRow: HTMLElement;
|
|
127
|
+
let headerTitle: HTMLElement;
|
|
128
|
+
|
|
129
|
+
if (titleMenuConfig) {
|
|
130
|
+
// Combo button replaces title + trailing actions + hover
|
|
131
|
+
const combo = createComboButton({
|
|
132
|
+
label: launcher.title ?? "Chat Assistant",
|
|
133
|
+
menuItems: titleMenuConfig.menuItems,
|
|
134
|
+
onSelect: titleMenuConfig.onSelect,
|
|
135
|
+
hover: titleMenuConfig.hover,
|
|
136
|
+
className: "",
|
|
137
|
+
});
|
|
138
|
+
titleRow = combo.element;
|
|
139
|
+
titleRow.style.color = HEADER_THEME_CSS.titleColor;
|
|
140
|
+
// The combo button's label span acts as headerTitle for update()
|
|
141
|
+
headerTitle = titleRow.querySelector(".persona-combo-btn-label") ?? titleRow;
|
|
142
|
+
} else {
|
|
143
|
+
titleRow = createElement(
|
|
144
|
+
"div",
|
|
145
|
+
"persona-flex persona-min-w-0 persona-flex-1 persona-items-center persona-gap-1"
|
|
146
|
+
);
|
|
97
147
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
148
|
+
// Title only (no icon, no subtitle)
|
|
149
|
+
headerTitle = createElement("span", "persona-text-base persona-font-semibold persona-truncate");
|
|
150
|
+
headerTitle.style.color = HEADER_THEME_CSS.titleColor;
|
|
151
|
+
headerTitle.textContent = launcher.title ?? "Chat Assistant";
|
|
101
152
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
153
|
+
titleRow.appendChild(headerTitle);
|
|
154
|
+
appendTrailingHeaderActions(
|
|
155
|
+
titleRow,
|
|
156
|
+
layoutHeaderConfig?.trailingActions,
|
|
157
|
+
layoutHeaderConfig?.onAction ?? onHeaderAction
|
|
158
|
+
);
|
|
108
159
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if ((e.target as HTMLElement).closest("button")) return;
|
|
118
|
-
handleTitleClick();
|
|
119
|
-
});
|
|
120
|
-
titleRow.addEventListener("keydown", (e) => {
|
|
121
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
122
|
-
e.preventDefault();
|
|
160
|
+
// Make title row clickable when onTitleClick is provided
|
|
161
|
+
if (layoutHeaderConfig?.onTitleClick) {
|
|
162
|
+
titleRow.style.cursor = "pointer";
|
|
163
|
+
titleRow.setAttribute("role", "button");
|
|
164
|
+
titleRow.setAttribute("tabindex", "0");
|
|
165
|
+
const handleTitleClick = layoutHeaderConfig.onTitleClick;
|
|
166
|
+
titleRow.addEventListener("click", (e) => {
|
|
167
|
+
if ((e.target as HTMLElement).closest("button")) return;
|
|
123
168
|
handleTitleClick();
|
|
124
|
-
}
|
|
125
|
-
|
|
169
|
+
});
|
|
170
|
+
titleRow.addEventListener("keydown", (e) => {
|
|
171
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
172
|
+
e.preventDefault();
|
|
173
|
+
handleTitleClick();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Title row hover pill effect
|
|
179
|
+
const hoverCfg = layoutHeaderConfig?.titleRowHover;
|
|
180
|
+
if (hoverCfg) {
|
|
181
|
+
titleRow.style.borderRadius = hoverCfg.borderRadius ?? '10px';
|
|
182
|
+
titleRow.style.padding = hoverCfg.padding ?? '6px 4px 6px 12px';
|
|
183
|
+
titleRow.style.margin = '-6px 0 -6px -12px';
|
|
184
|
+
titleRow.style.border = '1px solid transparent';
|
|
185
|
+
titleRow.style.transition = 'background-color 0.15s ease, border-color 0.15s ease';
|
|
186
|
+
titleRow.style.width = 'fit-content';
|
|
187
|
+
titleRow.style.flex = 'none';
|
|
188
|
+
titleRow.addEventListener('mouseenter', () => {
|
|
189
|
+
titleRow.style.backgroundColor = hoverCfg.background ?? '';
|
|
190
|
+
titleRow.style.borderColor = hoverCfg.border ?? '';
|
|
191
|
+
});
|
|
192
|
+
titleRow.addEventListener('mouseleave', () => {
|
|
193
|
+
titleRow.style.backgroundColor = '';
|
|
194
|
+
titleRow.style.borderColor = 'transparent';
|
|
195
|
+
});
|
|
196
|
+
}
|
|
126
197
|
}
|
|
127
198
|
|
|
128
199
|
header.appendChild(titleRow);
|
|
@@ -133,21 +204,18 @@ export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
|
|
|
133
204
|
|
|
134
205
|
const closeButton = createElement(
|
|
135
206
|
"button",
|
|
136
|
-
"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full
|
|
207
|
+
"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full hover:persona-bg-gray-100 persona-cursor-pointer persona-border-none"
|
|
137
208
|
) as HTMLButtonElement;
|
|
138
209
|
closeButton.style.height = closeButtonSize;
|
|
139
210
|
closeButton.style.width = closeButtonSize;
|
|
140
211
|
closeButton.type = "button";
|
|
141
212
|
closeButton.setAttribute("aria-label", "Close chat");
|
|
142
213
|
closeButton.style.display = showClose ? "" : "none";
|
|
214
|
+
closeButton.style.color =
|
|
215
|
+
launcher.closeButtonColor || HEADER_THEME_CSS.actionIconColor;
|
|
143
216
|
|
|
144
217
|
const closeButtonIconName = launcher.closeButtonIconName ?? "x";
|
|
145
|
-
const closeIconSvg = renderLucideIcon(
|
|
146
|
-
closeButtonIconName,
|
|
147
|
-
"20px",
|
|
148
|
-
launcher.closeButtonColor || "",
|
|
149
|
-
2
|
|
150
|
-
);
|
|
218
|
+
const closeIconSvg = renderLucideIcon(closeButtonIconName, "20px", "currentColor", 2);
|
|
151
219
|
if (closeIconSvg) {
|
|
152
220
|
closeButton.appendChild(closeIconSvg);
|
|
153
221
|
} else {
|
|
@@ -172,7 +240,7 @@ export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
|
|
|
172
240
|
return {
|
|
173
241
|
header,
|
|
174
242
|
iconHolder,
|
|
175
|
-
headerTitle
|
|
243
|
+
headerTitle,
|
|
176
244
|
headerSubtitle,
|
|
177
245
|
closeButton,
|
|
178
246
|
closeButtonWrapper,
|
|
@@ -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
|
|
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
|
-
|
|
179
|
-
button.style.width =
|
|
180
|
-
button.style.minWidth = "
|
|
181
|
-
button.style.maxWidth =
|
|
182
|
-
button.style.
|
|
183
|
-
button.style.
|
|
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 {
|
|
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 =
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
|
package/src/components/panel.ts
CHANGED
|
@@ -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
|
|
137
|
-
"
|
|
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
|
|
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:
|
|
90
|
-
darkTheme:
|
|
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
|
|
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,
|
|
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
|
-
|
|
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 {
|