@runtypelabs/persona 1.36.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 +1080 -0
- package/dist/index.cjs +140 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2626 -0
- package/dist/index.d.ts +2626 -0
- package/dist/index.global.js +1843 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +140 -0
- package/dist/index.js.map +1 -0
- package/dist/install.global.js +2 -0
- package/dist/install.global.js.map +1 -0
- package/dist/widget.css +1627 -0
- package/package.json +79 -0
- package/src/@types/idiomorph.d.ts +37 -0
- package/src/client.test.ts +387 -0
- package/src/client.ts +1589 -0
- package/src/components/composer-builder.ts +530 -0
- package/src/components/feedback.ts +379 -0
- package/src/components/forms.ts +170 -0
- package/src/components/header-builder.ts +455 -0
- package/src/components/header-layouts.ts +303 -0
- package/src/components/launcher.ts +193 -0
- package/src/components/message-bubble.ts +528 -0
- package/src/components/messages.ts +54 -0
- package/src/components/panel.ts +204 -0
- package/src/components/reasoning-bubble.ts +144 -0
- package/src/components/registry.ts +87 -0
- package/src/components/suggestions.ts +97 -0
- package/src/components/tool-bubble.ts +288 -0
- package/src/defaults.ts +321 -0
- package/src/index.ts +175 -0
- package/src/install.ts +284 -0
- package/src/plugins/registry.ts +77 -0
- package/src/plugins/types.ts +95 -0
- package/src/postprocessors.ts +194 -0
- package/src/runtime/init.ts +162 -0
- package/src/session.ts +376 -0
- package/src/styles/tailwind.css +20 -0
- package/src/styles/widget.css +1627 -0
- package/src/types.ts +1635 -0
- package/src/ui.ts +3341 -0
- package/src/utils/actions.ts +227 -0
- package/src/utils/attachment-manager.ts +384 -0
- package/src/utils/code-generators.test.ts +500 -0
- package/src/utils/code-generators.ts +1806 -0
- package/src/utils/component-middleware.ts +137 -0
- package/src/utils/component-parser.ts +119 -0
- package/src/utils/constants.ts +16 -0
- package/src/utils/content.ts +306 -0
- package/src/utils/dom.ts +25 -0
- package/src/utils/events.ts +41 -0
- package/src/utils/formatting.test.ts +166 -0
- package/src/utils/formatting.ts +470 -0
- package/src/utils/icons.ts +92 -0
- package/src/utils/message-id.ts +37 -0
- package/src/utils/morph.ts +36 -0
- package/src/utils/positioning.ts +17 -0
- package/src/utils/storage.ts +72 -0
- package/src/utils/theme.ts +105 -0
- package/src/widget.css +1 -0
- package/widget.css +1 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { createElement } from "../utils/dom";
|
|
2
|
+
import { renderLucideIcon } from "../utils/icons";
|
|
3
|
+
import { AgentWidgetConfig, AgentWidgetHeaderLayoutConfig } from "../types";
|
|
4
|
+
import { buildHeader, HeaderElements, attachHeaderToContainer } from "./header-builder";
|
|
5
|
+
|
|
6
|
+
export interface HeaderLayoutContext {
|
|
7
|
+
config: AgentWidgetConfig;
|
|
8
|
+
showClose?: boolean;
|
|
9
|
+
onClose?: () => void;
|
|
10
|
+
onClearChat?: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type HeaderLayoutRenderer = (context: HeaderLayoutContext) => HeaderElements;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build default header layout
|
|
17
|
+
* Full header with icon, title, subtitle, clear chat, and close button
|
|
18
|
+
*/
|
|
19
|
+
export const buildDefaultHeader: HeaderLayoutRenderer = (context) => {
|
|
20
|
+
return buildHeader({
|
|
21
|
+
config: context.config,
|
|
22
|
+
showClose: context.showClose,
|
|
23
|
+
onClose: context.onClose,
|
|
24
|
+
onClearChat: context.onClearChat
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build minimal header layout
|
|
30
|
+
* Simplified layout with just title and close button
|
|
31
|
+
*/
|
|
32
|
+
export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
|
|
33
|
+
const { config, showClose = true, onClose } = context;
|
|
34
|
+
const launcher = config?.launcher ?? {};
|
|
35
|
+
|
|
36
|
+
const header = createElement(
|
|
37
|
+
"div",
|
|
38
|
+
"tvw-flex tvw-items-center tvw-justify-between tvw-bg-cw-surface tvw-px-6 tvw-py-4 tvw-border-b-cw-divider"
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Title only (no icon, no subtitle)
|
|
42
|
+
const title = createElement("span", "tvw-text-base tvw-font-semibold");
|
|
43
|
+
title.textContent = launcher.title ?? "Chat Assistant";
|
|
44
|
+
|
|
45
|
+
header.appendChild(title);
|
|
46
|
+
|
|
47
|
+
// Close button
|
|
48
|
+
const closeButtonSize = launcher.closeButtonSize ?? "32px";
|
|
49
|
+
const closeButtonWrapper = createElement("div", "");
|
|
50
|
+
|
|
51
|
+
const closeButton = createElement(
|
|
52
|
+
"button",
|
|
53
|
+
"tvw-inline-flex tvw-items-center tvw-justify-center tvw-rounded-full tvw-text-cw-muted hover:tvw-bg-gray-100 tvw-cursor-pointer tvw-border-none"
|
|
54
|
+
) as HTMLButtonElement;
|
|
55
|
+
closeButton.style.height = closeButtonSize;
|
|
56
|
+
closeButton.style.width = closeButtonSize;
|
|
57
|
+
closeButton.type = "button";
|
|
58
|
+
closeButton.setAttribute("aria-label", "Close chat");
|
|
59
|
+
closeButton.style.display = showClose ? "" : "none";
|
|
60
|
+
|
|
61
|
+
const closeButtonIconName = launcher.closeButtonIconName ?? "x";
|
|
62
|
+
const closeIconSvg = renderLucideIcon(
|
|
63
|
+
closeButtonIconName,
|
|
64
|
+
"20px",
|
|
65
|
+
launcher.closeButtonColor || "",
|
|
66
|
+
2
|
|
67
|
+
);
|
|
68
|
+
if (closeIconSvg) {
|
|
69
|
+
closeButton.appendChild(closeIconSvg);
|
|
70
|
+
} else {
|
|
71
|
+
closeButton.textContent = "×";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (onClose) {
|
|
75
|
+
closeButton.addEventListener("click", onClose);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
closeButtonWrapper.appendChild(closeButton);
|
|
79
|
+
header.appendChild(closeButtonWrapper);
|
|
80
|
+
|
|
81
|
+
// Create placeholder elements for compatibility
|
|
82
|
+
const iconHolder = createElement("div");
|
|
83
|
+
iconHolder.style.display = "none";
|
|
84
|
+
const headerSubtitle = createElement("span");
|
|
85
|
+
headerSubtitle.style.display = "none";
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
header,
|
|
89
|
+
iconHolder,
|
|
90
|
+
headerTitle: title,
|
|
91
|
+
headerSubtitle,
|
|
92
|
+
closeButton,
|
|
93
|
+
closeButtonWrapper,
|
|
94
|
+
clearChatButton: null,
|
|
95
|
+
clearChatButtonWrapper: null
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Build expanded header layout
|
|
101
|
+
* Full branding area with additional space for custom content
|
|
102
|
+
*/
|
|
103
|
+
export const buildExpandedHeader: HeaderLayoutRenderer = (context) => {
|
|
104
|
+
const { config, showClose = true, onClose, onClearChat } = context;
|
|
105
|
+
const launcher = config?.launcher ?? {};
|
|
106
|
+
|
|
107
|
+
const header = createElement(
|
|
108
|
+
"div",
|
|
109
|
+
"tvw-flex tvw-flex-col tvw-bg-cw-surface tvw-px-6 tvw-py-5 tvw-border-b-cw-divider"
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Top row: icon + text + buttons
|
|
113
|
+
const topRow = createElement(
|
|
114
|
+
"div",
|
|
115
|
+
"tvw-flex tvw-items-center tvw-gap-3"
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Icon
|
|
119
|
+
const headerIconSize = launcher.headerIconSize ?? "56px";
|
|
120
|
+
const iconHolder = createElement(
|
|
121
|
+
"div",
|
|
122
|
+
"tvw-flex tvw-items-center tvw-justify-center tvw-rounded-xl tvw-bg-cw-primary tvw-text-white tvw-text-2xl"
|
|
123
|
+
);
|
|
124
|
+
iconHolder.style.height = headerIconSize;
|
|
125
|
+
iconHolder.style.width = headerIconSize;
|
|
126
|
+
|
|
127
|
+
const headerIconName = launcher.headerIconName;
|
|
128
|
+
if (headerIconName) {
|
|
129
|
+
const iconSize = parseFloat(headerIconSize) || 24;
|
|
130
|
+
const iconSvg = renderLucideIcon(headerIconName, iconSize * 0.5, "#ffffff", 2);
|
|
131
|
+
if (iconSvg) {
|
|
132
|
+
iconHolder.replaceChildren(iconSvg);
|
|
133
|
+
} else {
|
|
134
|
+
iconHolder.textContent = launcher.agentIconText ?? "💬";
|
|
135
|
+
}
|
|
136
|
+
} else if (launcher.iconUrl) {
|
|
137
|
+
const img = createElement("img") as HTMLImageElement;
|
|
138
|
+
img.src = launcher.iconUrl;
|
|
139
|
+
img.alt = "";
|
|
140
|
+
img.className = "tvw-rounded-xl tvw-object-cover";
|
|
141
|
+
img.style.height = headerIconSize;
|
|
142
|
+
img.style.width = headerIconSize;
|
|
143
|
+
iconHolder.replaceChildren(img);
|
|
144
|
+
} else {
|
|
145
|
+
iconHolder.textContent = launcher.agentIconText ?? "💬";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Title and subtitle
|
|
149
|
+
const headerCopy = createElement("div", "tvw-flex tvw-flex-col tvw-flex-1");
|
|
150
|
+
const title = createElement("span", "tvw-text-lg tvw-font-semibold");
|
|
151
|
+
title.textContent = launcher.title ?? "Chat Assistant";
|
|
152
|
+
const subtitle = createElement("span", "tvw-text-sm tvw-text-cw-muted");
|
|
153
|
+
subtitle.textContent = launcher.subtitle ?? "Here to help you get answers fast";
|
|
154
|
+
headerCopy.append(title, subtitle);
|
|
155
|
+
|
|
156
|
+
topRow.append(iconHolder, headerCopy);
|
|
157
|
+
|
|
158
|
+
// Close button
|
|
159
|
+
const closeButtonSize = launcher.closeButtonSize ?? "32px";
|
|
160
|
+
const closeButtonWrapper = createElement("div", "");
|
|
161
|
+
|
|
162
|
+
const closeButton = createElement(
|
|
163
|
+
"button",
|
|
164
|
+
"tvw-inline-flex tvw-items-center tvw-justify-center tvw-rounded-full tvw-text-cw-muted hover:tvw-bg-gray-100 tvw-cursor-pointer tvw-border-none"
|
|
165
|
+
) as HTMLButtonElement;
|
|
166
|
+
closeButton.style.height = closeButtonSize;
|
|
167
|
+
closeButton.style.width = closeButtonSize;
|
|
168
|
+
closeButton.type = "button";
|
|
169
|
+
closeButton.setAttribute("aria-label", "Close chat");
|
|
170
|
+
closeButton.style.display = showClose ? "" : "none";
|
|
171
|
+
|
|
172
|
+
const closeButtonIconName = launcher.closeButtonIconName ?? "x";
|
|
173
|
+
const closeIconSvg = renderLucideIcon(
|
|
174
|
+
closeButtonIconName,
|
|
175
|
+
"20px",
|
|
176
|
+
launcher.closeButtonColor || "",
|
|
177
|
+
2
|
|
178
|
+
);
|
|
179
|
+
if (closeIconSvg) {
|
|
180
|
+
closeButton.appendChild(closeIconSvg);
|
|
181
|
+
} else {
|
|
182
|
+
closeButton.textContent = "×";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (onClose) {
|
|
186
|
+
closeButton.addEventListener("click", onClose);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
closeButtonWrapper.appendChild(closeButton);
|
|
190
|
+
topRow.appendChild(closeButtonWrapper);
|
|
191
|
+
|
|
192
|
+
header.appendChild(topRow);
|
|
193
|
+
|
|
194
|
+
// Bottom row: additional space for status or branding
|
|
195
|
+
const bottomRow = createElement(
|
|
196
|
+
"div",
|
|
197
|
+
"tvw-mt-3 tvw-pt-3 tvw-border-t tvw-border-gray-100 tvw-text-xs tvw-text-cw-muted"
|
|
198
|
+
);
|
|
199
|
+
bottomRow.textContent = "Online and ready to help";
|
|
200
|
+
header.appendChild(bottomRow);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
header,
|
|
204
|
+
iconHolder,
|
|
205
|
+
headerTitle: title,
|
|
206
|
+
headerSubtitle: subtitle,
|
|
207
|
+
closeButton,
|
|
208
|
+
closeButtonWrapper,
|
|
209
|
+
clearChatButton: null,
|
|
210
|
+
clearChatButtonWrapper: null
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Header layout registry
|
|
216
|
+
* Maps layout names to their renderer functions
|
|
217
|
+
*/
|
|
218
|
+
export const headerLayouts: Record<string, HeaderLayoutRenderer> = {
|
|
219
|
+
default: buildDefaultHeader,
|
|
220
|
+
minimal: buildMinimalHeader,
|
|
221
|
+
expanded: buildExpandedHeader
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get header layout renderer by name
|
|
226
|
+
*/
|
|
227
|
+
export const getHeaderLayout = (layoutName: string): HeaderLayoutRenderer => {
|
|
228
|
+
return headerLayouts[layoutName] ?? headerLayouts.default;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Build header based on layout configuration
|
|
233
|
+
* Applies layout config settings to determine which layout to use
|
|
234
|
+
*/
|
|
235
|
+
export const buildHeaderWithLayout = (
|
|
236
|
+
config: AgentWidgetConfig,
|
|
237
|
+
layoutConfig?: AgentWidgetHeaderLayoutConfig,
|
|
238
|
+
context?: Partial<HeaderLayoutContext>
|
|
239
|
+
): HeaderElements => {
|
|
240
|
+
// If custom render is provided, use it
|
|
241
|
+
if (layoutConfig?.render) {
|
|
242
|
+
const customHeader = layoutConfig.render({
|
|
243
|
+
config,
|
|
244
|
+
onClose: context?.onClose,
|
|
245
|
+
onClearChat: context?.onClearChat
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Wrap in HeaderElements structure
|
|
249
|
+
const iconHolder = createElement("div");
|
|
250
|
+
iconHolder.style.display = "none";
|
|
251
|
+
const headerTitle = createElement("span");
|
|
252
|
+
const headerSubtitle = createElement("span");
|
|
253
|
+
const closeButton = createElement("button") as HTMLButtonElement;
|
|
254
|
+
closeButton.style.display = "none";
|
|
255
|
+
const closeButtonWrapper = createElement("div");
|
|
256
|
+
closeButtonWrapper.style.display = "none";
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
header: customHeader,
|
|
260
|
+
iconHolder,
|
|
261
|
+
headerTitle,
|
|
262
|
+
headerSubtitle,
|
|
263
|
+
closeButton,
|
|
264
|
+
closeButtonWrapper,
|
|
265
|
+
clearChatButton: null,
|
|
266
|
+
clearChatButtonWrapper: null
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Get layout renderer
|
|
271
|
+
const layoutName = layoutConfig?.layout ?? "default";
|
|
272
|
+
const layoutRenderer = getHeaderLayout(layoutName);
|
|
273
|
+
|
|
274
|
+
// Build header with layout
|
|
275
|
+
const headerElements = layoutRenderer({
|
|
276
|
+
config,
|
|
277
|
+
showClose: layoutConfig?.showCloseButton ?? context?.showClose ?? true,
|
|
278
|
+
onClose: context?.onClose,
|
|
279
|
+
onClearChat: context?.onClearChat
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Apply visibility settings from layout config
|
|
283
|
+
if (layoutConfig) {
|
|
284
|
+
if (layoutConfig.showIcon === false) {
|
|
285
|
+
headerElements.iconHolder.style.display = "none";
|
|
286
|
+
}
|
|
287
|
+
if (layoutConfig.showTitle === false) {
|
|
288
|
+
headerElements.headerTitle.style.display = "none";
|
|
289
|
+
}
|
|
290
|
+
if (layoutConfig.showSubtitle === false) {
|
|
291
|
+
headerElements.headerSubtitle.style.display = "none";
|
|
292
|
+
}
|
|
293
|
+
if (layoutConfig.showCloseButton === false) {
|
|
294
|
+
headerElements.closeButton.style.display = "none";
|
|
295
|
+
}
|
|
296
|
+
if (layoutConfig.showClearChat === false && headerElements.clearChatButtonWrapper) {
|
|
297
|
+
headerElements.clearChatButtonWrapper.style.display = "none";
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return headerElements;
|
|
302
|
+
};
|
|
303
|
+
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { createElement } from "../utils/dom";
|
|
2
|
+
import { AgentWidgetConfig } from "../types";
|
|
3
|
+
import { positionMap } from "../utils/positioning";
|
|
4
|
+
import { renderLucideIcon } from "../utils/icons";
|
|
5
|
+
|
|
6
|
+
export interface LauncherButton {
|
|
7
|
+
element: HTMLButtonElement;
|
|
8
|
+
update: (config: AgentWidgetConfig) => void;
|
|
9
|
+
destroy: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const createLauncherButton = (
|
|
13
|
+
config: AgentWidgetConfig | undefined,
|
|
14
|
+
onToggle: () => void
|
|
15
|
+
): LauncherButton => {
|
|
16
|
+
const button = createElement("button") as HTMLButtonElement;
|
|
17
|
+
button.type = "button";
|
|
18
|
+
button.innerHTML = `
|
|
19
|
+
<span class="tvw-inline-flex tvw-items-center tvw-justify-center tvw-rounded-full tvw-bg-cw-primary tvw-text-white" data-role="launcher-icon">💬</span>
|
|
20
|
+
<img data-role="launcher-image" class="tvw-rounded-full tvw-object-cover" alt="" style="display:none" />
|
|
21
|
+
<span class="tvw-flex tvw-flex-col tvw-items-start tvw-text-left">
|
|
22
|
+
<span class="tvw-text-sm tvw-font-semibold tvw-text-cw-primary" data-role="launcher-title"></span>
|
|
23
|
+
<span class="tvw-text-xs tvw-text-cw-muted" data-role="launcher-subtitle"></span>
|
|
24
|
+
</span>
|
|
25
|
+
<span class="tvw-ml-2 tvw-grid tvw-place-items-center tvw-rounded-full tvw-bg-cw-primary tvw-text-cw-call-to-action" data-role="launcher-call-to-action-icon">↗</span>
|
|
26
|
+
`;
|
|
27
|
+
button.addEventListener("click", onToggle);
|
|
28
|
+
|
|
29
|
+
const update = (newConfig: AgentWidgetConfig) => {
|
|
30
|
+
const launcher = newConfig.launcher ?? {};
|
|
31
|
+
|
|
32
|
+
const titleEl = button.querySelector("[data-role='launcher-title']");
|
|
33
|
+
if (titleEl) {
|
|
34
|
+
titleEl.textContent = launcher.title ?? "Chat Assistant";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const subtitleEl = button.querySelector("[data-role='launcher-subtitle']");
|
|
38
|
+
if (subtitleEl) {
|
|
39
|
+
subtitleEl.textContent = launcher.subtitle ?? "Get answers fast";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Hide/show text container
|
|
43
|
+
const textContainer = button.querySelector(".tvw-flex-col");
|
|
44
|
+
if (textContainer) {
|
|
45
|
+
if (launcher.textHidden) {
|
|
46
|
+
(textContainer as HTMLElement).style.display = "none";
|
|
47
|
+
} else {
|
|
48
|
+
(textContainer as HTMLElement).style.display = "";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const icon = button.querySelector<HTMLSpanElement>("[data-role='launcher-icon']");
|
|
53
|
+
if (icon) {
|
|
54
|
+
if (launcher.agentIconHidden) {
|
|
55
|
+
icon.style.display = "none";
|
|
56
|
+
} else {
|
|
57
|
+
const iconSize = launcher.agentIconSize ?? "40px";
|
|
58
|
+
icon.style.height = iconSize;
|
|
59
|
+
icon.style.width = iconSize;
|
|
60
|
+
|
|
61
|
+
// Clear existing content
|
|
62
|
+
icon.innerHTML = "";
|
|
63
|
+
|
|
64
|
+
// Render icon based on priority: Lucide icon > iconUrl > agentIconText
|
|
65
|
+
if (launcher.agentIconName) {
|
|
66
|
+
// Use Lucide icon
|
|
67
|
+
const iconSizeNum = parseFloat(iconSize) || 24;
|
|
68
|
+
const iconSvg = renderLucideIcon(launcher.agentIconName, iconSizeNum * 0.6, "#ffffff", 2);
|
|
69
|
+
if (iconSvg) {
|
|
70
|
+
icon.appendChild(iconSvg);
|
|
71
|
+
icon.style.display = "";
|
|
72
|
+
} else {
|
|
73
|
+
// Fallback to agentIconText if Lucide icon fails
|
|
74
|
+
icon.textContent = launcher.agentIconText ?? "💬";
|
|
75
|
+
icon.style.display = "";
|
|
76
|
+
}
|
|
77
|
+
} else if (launcher.iconUrl) {
|
|
78
|
+
// Use image URL - hide icon span and show img
|
|
79
|
+
icon.style.display = "none";
|
|
80
|
+
} else {
|
|
81
|
+
// Use text/emoji
|
|
82
|
+
icon.textContent = launcher.agentIconText ?? "💬";
|
|
83
|
+
icon.style.display = "";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const img = button.querySelector<HTMLImageElement>("[data-role='launcher-image']");
|
|
89
|
+
if (img) {
|
|
90
|
+
const iconSize = launcher.agentIconSize ?? "40px";
|
|
91
|
+
img.style.height = iconSize;
|
|
92
|
+
img.style.width = iconSize;
|
|
93
|
+
if (launcher.iconUrl && !launcher.agentIconName && !launcher.agentIconHidden) {
|
|
94
|
+
// Only show image if not using Lucide icon and not hidden
|
|
95
|
+
img.src = launcher.iconUrl;
|
|
96
|
+
img.style.display = "block";
|
|
97
|
+
} else {
|
|
98
|
+
img.style.display = "none";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const callToActionIconEl = button.querySelector<HTMLSpanElement>("[data-role='launcher-call-to-action-icon']");
|
|
103
|
+
if (callToActionIconEl) {
|
|
104
|
+
const callToActionIconSize = launcher.callToActionIconSize ?? "32px";
|
|
105
|
+
callToActionIconEl.style.height = callToActionIconSize;
|
|
106
|
+
callToActionIconEl.style.width = callToActionIconSize;
|
|
107
|
+
|
|
108
|
+
// Apply background color if configured
|
|
109
|
+
if (launcher.callToActionIconBackgroundColor) {
|
|
110
|
+
callToActionIconEl.style.backgroundColor = launcher.callToActionIconBackgroundColor;
|
|
111
|
+
callToActionIconEl.classList.remove("tvw-bg-cw-primary");
|
|
112
|
+
} else {
|
|
113
|
+
callToActionIconEl.style.backgroundColor = "";
|
|
114
|
+
callToActionIconEl.classList.add("tvw-bg-cw-primary");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Calculate padding to adjust icon size
|
|
118
|
+
let paddingTotal = 0;
|
|
119
|
+
if (launcher.callToActionIconPadding) {
|
|
120
|
+
callToActionIconEl.style.boxSizing = "border-box";
|
|
121
|
+
callToActionIconEl.style.padding = launcher.callToActionIconPadding;
|
|
122
|
+
// Parse padding value to calculate total padding (padding applies to both sides)
|
|
123
|
+
const paddingValue = parseFloat(launcher.callToActionIconPadding) || 0;
|
|
124
|
+
paddingTotal = paddingValue * 2; // padding on both sides
|
|
125
|
+
} else {
|
|
126
|
+
callToActionIconEl.style.boxSizing = "";
|
|
127
|
+
callToActionIconEl.style.padding = "";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (launcher.callToActionIconHidden) {
|
|
131
|
+
callToActionIconEl.style.display = "none";
|
|
132
|
+
} else {
|
|
133
|
+
callToActionIconEl.style.display = "";
|
|
134
|
+
|
|
135
|
+
// Clear existing content
|
|
136
|
+
callToActionIconEl.innerHTML = "";
|
|
137
|
+
|
|
138
|
+
// Use Lucide icon if provided, otherwise fall back to text
|
|
139
|
+
if (launcher.callToActionIconName) {
|
|
140
|
+
// Calculate actual icon size by subtracting padding
|
|
141
|
+
const containerSize = parseFloat(callToActionIconSize) || 24;
|
|
142
|
+
const iconSize = Math.max(containerSize - paddingTotal, 8); // Ensure minimum size of 8px
|
|
143
|
+
const iconSvg = renderLucideIcon(launcher.callToActionIconName, iconSize, "currentColor", 2);
|
|
144
|
+
if (iconSvg) {
|
|
145
|
+
callToActionIconEl.appendChild(iconSvg);
|
|
146
|
+
} else {
|
|
147
|
+
// Fallback to text if icon fails to render
|
|
148
|
+
callToActionIconEl.textContent = launcher.callToActionIconText ?? "↗";
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
callToActionIconEl.textContent = launcher.callToActionIconText ?? "↗";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const positionClass =
|
|
157
|
+
launcher.position && positionMap[launcher.position]
|
|
158
|
+
? positionMap[launcher.position]
|
|
159
|
+
: positionMap["bottom-right"];
|
|
160
|
+
|
|
161
|
+
// Removed hardcoded border/shadow classes (tvw-shadow-lg, tvw-border, tvw-border-gray-200)
|
|
162
|
+
// These are now applied via inline styles from config
|
|
163
|
+
const base =
|
|
164
|
+
"tvw-fixed tvw-flex tvw-items-center tvw-gap-3 tvw-rounded-launcher tvw-bg-cw-surface tvw-py-2.5 tvw-pl-3 tvw-pr-3 tvw-transition hover:tvw-translate-y-[-2px] tvw-cursor-pointer tvw-z-50";
|
|
165
|
+
|
|
166
|
+
button.className = `${base} ${positionClass}`;
|
|
167
|
+
|
|
168
|
+
// Apply launcher border and shadow from config (with defaults matching previous Tailwind classes)
|
|
169
|
+
const defaultBorder = "1px solid #e5e7eb";
|
|
170
|
+
const defaultShadow = "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)";
|
|
171
|
+
|
|
172
|
+
button.style.border = launcher.border ?? defaultBorder;
|
|
173
|
+
button.style.boxShadow = launcher.shadow ?? defaultShadow;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const destroy = () => {
|
|
177
|
+
button.removeEventListener("click", onToggle);
|
|
178
|
+
button.remove();
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Initial update
|
|
182
|
+
if (config) {
|
|
183
|
+
update(config);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
element: button,
|
|
188
|
+
update,
|
|
189
|
+
destroy
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
|