vanilla-agent 1.10.0 → 1.11.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 +57 -8
- package/dist/index.cjs +46 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +305 -1
- package/dist/index.d.ts +305 -1
- package/dist/index.global.js +69 -30
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +46 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/composer-builder.ts +366 -0
- package/src/components/header-builder.ts +454 -0
- package/src/components/header-layouts.ts +303 -0
- package/src/components/message-bubble.ts +251 -34
- package/src/components/panel.ts +46 -684
- package/src/defaults.ts +49 -1
- package/src/index.ts +43 -1
- package/src/runtime/init.ts +26 -0
- package/src/types.ts +231 -0
- package/src/ui.ts +452 -34
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import { createElement } from "../utils/dom";
|
|
2
|
+
import { renderLucideIcon } from "../utils/icons";
|
|
3
|
+
import { AgentWidgetConfig } from "../types";
|
|
4
|
+
|
|
5
|
+
export interface HeaderElements {
|
|
6
|
+
header: HTMLElement;
|
|
7
|
+
iconHolder: HTMLElement;
|
|
8
|
+
headerTitle: HTMLElement;
|
|
9
|
+
headerSubtitle: HTMLElement;
|
|
10
|
+
closeButton: HTMLButtonElement;
|
|
11
|
+
closeButtonWrapper: HTMLElement;
|
|
12
|
+
clearChatButton: HTMLButtonElement | null;
|
|
13
|
+
clearChatButtonWrapper: HTMLElement | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface HeaderBuildContext {
|
|
17
|
+
config?: AgentWidgetConfig;
|
|
18
|
+
showClose?: boolean;
|
|
19
|
+
onClose?: () => void;
|
|
20
|
+
onClearChat?: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build the header section of the panel.
|
|
25
|
+
* Extracted for reuse and plugin override support.
|
|
26
|
+
*/
|
|
27
|
+
export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
28
|
+
const { config, showClose = true } = context;
|
|
29
|
+
|
|
30
|
+
const header = createElement(
|
|
31
|
+
"div",
|
|
32
|
+
"tvw-flex tvw-items-center tvw-gap-3 tvw-bg-cw-surface tvw-px-6 tvw-py-5 tvw-border-b-cw-divider"
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const launcher = config?.launcher ?? {};
|
|
36
|
+
const headerIconSize = launcher.headerIconSize ?? "48px";
|
|
37
|
+
const closeButtonSize = launcher.closeButtonSize ?? "32px";
|
|
38
|
+
const closeButtonPlacement = launcher.closeButtonPlacement ?? "inline";
|
|
39
|
+
const headerIconHidden = launcher.headerIconHidden ?? false;
|
|
40
|
+
const headerIconName = launcher.headerIconName;
|
|
41
|
+
|
|
42
|
+
const iconHolder = createElement(
|
|
43
|
+
"div",
|
|
44
|
+
"tvw-flex tvw-items-center tvw-justify-center tvw-rounded-xl tvw-bg-cw-primary tvw-text-white tvw-text-xl"
|
|
45
|
+
);
|
|
46
|
+
iconHolder.style.height = headerIconSize;
|
|
47
|
+
iconHolder.style.width = headerIconSize;
|
|
48
|
+
|
|
49
|
+
// Render icon based on priority: Lucide icon > iconUrl > agentIconText
|
|
50
|
+
if (!headerIconHidden) {
|
|
51
|
+
if (headerIconName) {
|
|
52
|
+
// Use Lucide icon
|
|
53
|
+
const iconSize = parseFloat(headerIconSize) || 24;
|
|
54
|
+
const iconSvg = renderLucideIcon(headerIconName, iconSize * 0.6, "#ffffff", 2);
|
|
55
|
+
if (iconSvg) {
|
|
56
|
+
iconHolder.replaceChildren(iconSvg);
|
|
57
|
+
} else {
|
|
58
|
+
// Fallback to agentIconText if Lucide icon fails
|
|
59
|
+
iconHolder.textContent = config?.launcher?.agentIconText ?? "💬";
|
|
60
|
+
}
|
|
61
|
+
} else if (config?.launcher?.iconUrl) {
|
|
62
|
+
// Use image URL
|
|
63
|
+
const img = createElement("img") as HTMLImageElement;
|
|
64
|
+
img.src = config.launcher.iconUrl;
|
|
65
|
+
img.alt = "";
|
|
66
|
+
img.className = "tvw-rounded-xl tvw-object-cover";
|
|
67
|
+
img.style.height = headerIconSize;
|
|
68
|
+
img.style.width = headerIconSize;
|
|
69
|
+
iconHolder.replaceChildren(img);
|
|
70
|
+
} else {
|
|
71
|
+
// Use text/emoji
|
|
72
|
+
iconHolder.textContent = config?.launcher?.agentIconText ?? "💬";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const headerCopy = createElement("div", "tvw-flex tvw-flex-col");
|
|
77
|
+
const title = createElement("span", "tvw-text-base tvw-font-semibold");
|
|
78
|
+
title.textContent = config?.launcher?.title ?? "Chat Assistant";
|
|
79
|
+
const subtitle = createElement("span", "tvw-text-xs tvw-text-cw-muted");
|
|
80
|
+
subtitle.textContent =
|
|
81
|
+
config?.launcher?.subtitle ?? "Here to help you get answers fast";
|
|
82
|
+
|
|
83
|
+
headerCopy.append(title, subtitle);
|
|
84
|
+
|
|
85
|
+
// Only append iconHolder if not hidden
|
|
86
|
+
if (!headerIconHidden) {
|
|
87
|
+
header.append(iconHolder, headerCopy);
|
|
88
|
+
} else {
|
|
89
|
+
header.append(headerCopy);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Create clear chat button if enabled
|
|
93
|
+
const clearChatConfig = launcher.clearChat ?? {};
|
|
94
|
+
const clearChatEnabled = clearChatConfig.enabled ?? true;
|
|
95
|
+
const clearChatPlacement = clearChatConfig.placement ?? "inline";
|
|
96
|
+
let clearChatButton: HTMLButtonElement | null = null;
|
|
97
|
+
let clearChatButtonWrapper: HTMLElement | null = null;
|
|
98
|
+
|
|
99
|
+
if (clearChatEnabled) {
|
|
100
|
+
const clearChatSize = clearChatConfig.size ?? "32px";
|
|
101
|
+
const clearChatIconName = clearChatConfig.iconName ?? "refresh-cw";
|
|
102
|
+
const clearChatIconColor = clearChatConfig.iconColor ?? "";
|
|
103
|
+
const clearChatBgColor = clearChatConfig.backgroundColor ?? "";
|
|
104
|
+
const clearChatBorderWidth = clearChatConfig.borderWidth ?? "";
|
|
105
|
+
const clearChatBorderColor = clearChatConfig.borderColor ?? "";
|
|
106
|
+
const clearChatBorderRadius = clearChatConfig.borderRadius ?? "";
|
|
107
|
+
const clearChatPaddingX = clearChatConfig.paddingX ?? "";
|
|
108
|
+
const clearChatPaddingY = clearChatConfig.paddingY ?? "";
|
|
109
|
+
const clearChatTooltipText = clearChatConfig.tooltipText ?? "Clear chat";
|
|
110
|
+
const clearChatShowTooltip = clearChatConfig.showTooltip ?? true;
|
|
111
|
+
|
|
112
|
+
// Create button wrapper for tooltip - positioned based on placement
|
|
113
|
+
// Note: Don't use tvw-clear-chat-button-wrapper class for top-right mode as its
|
|
114
|
+
// display: inline-flex causes alignment issues with the close button
|
|
115
|
+
clearChatButtonWrapper = createElement(
|
|
116
|
+
"div",
|
|
117
|
+
clearChatPlacement === "top-right"
|
|
118
|
+
? "tvw-absolute tvw-top-4 tvw-z-50"
|
|
119
|
+
: "tvw-relative tvw-ml-auto tvw-clear-chat-button-wrapper"
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Position to the left of the close button (which is at right: 1rem/16px)
|
|
123
|
+
// Close button is ~32px wide, plus small gap = 48px from right
|
|
124
|
+
if (clearChatPlacement === "top-right") {
|
|
125
|
+
clearChatButtonWrapper.style.right = "48px";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
clearChatButton = createElement(
|
|
129
|
+
"button",
|
|
130
|
+
"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"
|
|
131
|
+
) as HTMLButtonElement;
|
|
132
|
+
|
|
133
|
+
clearChatButton.style.height = clearChatSize;
|
|
134
|
+
clearChatButton.style.width = clearChatSize;
|
|
135
|
+
clearChatButton.type = "button";
|
|
136
|
+
clearChatButton.setAttribute("aria-label", clearChatTooltipText);
|
|
137
|
+
|
|
138
|
+
// Add icon
|
|
139
|
+
const iconSvg = renderLucideIcon(
|
|
140
|
+
clearChatIconName,
|
|
141
|
+
"20px",
|
|
142
|
+
clearChatIconColor || "",
|
|
143
|
+
2
|
|
144
|
+
);
|
|
145
|
+
if (iconSvg) {
|
|
146
|
+
clearChatButton.appendChild(iconSvg);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Apply styling from config
|
|
150
|
+
if (clearChatIconColor) {
|
|
151
|
+
clearChatButton.style.color = clearChatIconColor;
|
|
152
|
+
clearChatButton.classList.remove("tvw-text-cw-muted");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (clearChatBgColor) {
|
|
156
|
+
clearChatButton.style.backgroundColor = clearChatBgColor;
|
|
157
|
+
clearChatButton.classList.remove("hover:tvw-bg-gray-100");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (clearChatBorderWidth || clearChatBorderColor) {
|
|
161
|
+
const borderWidth = clearChatBorderWidth || "0px";
|
|
162
|
+
const borderColor = clearChatBorderColor || "transparent";
|
|
163
|
+
clearChatButton.style.border = `${borderWidth} solid ${borderColor}`;
|
|
164
|
+
clearChatButton.classList.remove("tvw-border-none");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (clearChatBorderRadius) {
|
|
168
|
+
clearChatButton.style.borderRadius = clearChatBorderRadius;
|
|
169
|
+
clearChatButton.classList.remove("tvw-rounded-full");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Apply padding styling
|
|
173
|
+
if (clearChatPaddingX) {
|
|
174
|
+
clearChatButton.style.paddingLeft = clearChatPaddingX;
|
|
175
|
+
clearChatButton.style.paddingRight = clearChatPaddingX;
|
|
176
|
+
} else {
|
|
177
|
+
clearChatButton.style.paddingLeft = "";
|
|
178
|
+
clearChatButton.style.paddingRight = "";
|
|
179
|
+
}
|
|
180
|
+
if (clearChatPaddingY) {
|
|
181
|
+
clearChatButton.style.paddingTop = clearChatPaddingY;
|
|
182
|
+
clearChatButton.style.paddingBottom = clearChatPaddingY;
|
|
183
|
+
} else {
|
|
184
|
+
clearChatButton.style.paddingTop = "";
|
|
185
|
+
clearChatButton.style.paddingBottom = "";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
clearChatButtonWrapper.appendChild(clearChatButton);
|
|
189
|
+
|
|
190
|
+
// Add tooltip with portaling to document.body to escape overflow clipping
|
|
191
|
+
if (
|
|
192
|
+
clearChatShowTooltip &&
|
|
193
|
+
clearChatTooltipText &&
|
|
194
|
+
clearChatButton &&
|
|
195
|
+
clearChatButtonWrapper
|
|
196
|
+
) {
|
|
197
|
+
let portaledTooltip: HTMLElement | null = null;
|
|
198
|
+
|
|
199
|
+
const showTooltip = () => {
|
|
200
|
+
if (portaledTooltip || !clearChatButton) return; // Already showing or button doesn't exist
|
|
201
|
+
|
|
202
|
+
// Create tooltip element
|
|
203
|
+
portaledTooltip = createElement("div", "tvw-clear-chat-tooltip");
|
|
204
|
+
portaledTooltip.textContent = clearChatTooltipText;
|
|
205
|
+
|
|
206
|
+
// Add arrow
|
|
207
|
+
const arrow = createElement("div");
|
|
208
|
+
arrow.className = "tvw-clear-chat-tooltip-arrow";
|
|
209
|
+
portaledTooltip.appendChild(arrow);
|
|
210
|
+
|
|
211
|
+
// Get button position
|
|
212
|
+
const buttonRect = clearChatButton.getBoundingClientRect();
|
|
213
|
+
|
|
214
|
+
// Position tooltip above button
|
|
215
|
+
portaledTooltip.style.position = "fixed";
|
|
216
|
+
portaledTooltip.style.left = `${buttonRect.left + buttonRect.width / 2}px`;
|
|
217
|
+
portaledTooltip.style.top = `${buttonRect.top - 8}px`;
|
|
218
|
+
portaledTooltip.style.transform = "translate(-50%, -100%)";
|
|
219
|
+
|
|
220
|
+
// Append to body
|
|
221
|
+
document.body.appendChild(portaledTooltip);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const hideTooltip = () => {
|
|
225
|
+
if (portaledTooltip && portaledTooltip.parentNode) {
|
|
226
|
+
portaledTooltip.parentNode.removeChild(portaledTooltip);
|
|
227
|
+
portaledTooltip = null;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Add event listeners
|
|
232
|
+
clearChatButtonWrapper.addEventListener("mouseenter", showTooltip);
|
|
233
|
+
clearChatButtonWrapper.addEventListener("mouseleave", hideTooltip);
|
|
234
|
+
clearChatButton.addEventListener("focus", showTooltip);
|
|
235
|
+
clearChatButton.addEventListener("blur", hideTooltip);
|
|
236
|
+
|
|
237
|
+
// Store cleanup function on the button for later use
|
|
238
|
+
(clearChatButtonWrapper as any)._cleanupTooltip = () => {
|
|
239
|
+
hideTooltip();
|
|
240
|
+
if (clearChatButtonWrapper) {
|
|
241
|
+
clearChatButtonWrapper.removeEventListener("mouseenter", showTooltip);
|
|
242
|
+
clearChatButtonWrapper.removeEventListener("mouseleave", hideTooltip);
|
|
243
|
+
}
|
|
244
|
+
if (clearChatButton) {
|
|
245
|
+
clearChatButton.removeEventListener("focus", showTooltip);
|
|
246
|
+
clearChatButton.removeEventListener("blur", hideTooltip);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Only append to header if inline placement
|
|
252
|
+
if (clearChatPlacement === "inline") {
|
|
253
|
+
header.appendChild(clearChatButtonWrapper);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Create close button wrapper for tooltip positioning
|
|
258
|
+
// Only needs ml-auto if clear chat is disabled or top-right positioned
|
|
259
|
+
const closeButtonWrapper = createElement(
|
|
260
|
+
"div",
|
|
261
|
+
closeButtonPlacement === "top-right"
|
|
262
|
+
? "tvw-absolute tvw-top-4 tvw-right-4 tvw-z-50"
|
|
263
|
+
: clearChatEnabled && clearChatPlacement === "inline"
|
|
264
|
+
? ""
|
|
265
|
+
: "tvw-ml-auto"
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Create close button with base classes
|
|
269
|
+
const closeButton = createElement(
|
|
270
|
+
"button",
|
|
271
|
+
"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"
|
|
272
|
+
) as HTMLButtonElement;
|
|
273
|
+
closeButton.style.height = closeButtonSize;
|
|
274
|
+
closeButton.style.width = closeButtonSize;
|
|
275
|
+
closeButton.type = "button";
|
|
276
|
+
|
|
277
|
+
// Get tooltip config
|
|
278
|
+
const closeButtonTooltipText = launcher.closeButtonTooltipText ?? "Close chat";
|
|
279
|
+
const closeButtonShowTooltip = launcher.closeButtonShowTooltip ?? true;
|
|
280
|
+
|
|
281
|
+
closeButton.setAttribute("aria-label", closeButtonTooltipText);
|
|
282
|
+
closeButton.style.display = showClose ? "" : "none";
|
|
283
|
+
|
|
284
|
+
// Add icon or fallback text
|
|
285
|
+
const closeButtonIconName = launcher.closeButtonIconName ?? "x";
|
|
286
|
+
const closeButtonIconText = launcher.closeButtonIconText ?? "×";
|
|
287
|
+
|
|
288
|
+
// Try to render Lucide icon, fallback to text if not provided or fails
|
|
289
|
+
const closeIconSvg = renderLucideIcon(
|
|
290
|
+
closeButtonIconName,
|
|
291
|
+
"20px",
|
|
292
|
+
launcher.closeButtonColor || "",
|
|
293
|
+
2
|
|
294
|
+
);
|
|
295
|
+
if (closeIconSvg) {
|
|
296
|
+
closeButton.appendChild(closeIconSvg);
|
|
297
|
+
} else {
|
|
298
|
+
closeButton.textContent = closeButtonIconText;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Apply close button styling from config
|
|
302
|
+
if (launcher.closeButtonColor) {
|
|
303
|
+
closeButton.style.color = launcher.closeButtonColor;
|
|
304
|
+
closeButton.classList.remove("tvw-text-cw-muted");
|
|
305
|
+
} else {
|
|
306
|
+
closeButton.style.color = "";
|
|
307
|
+
closeButton.classList.add("tvw-text-cw-muted");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (launcher.closeButtonBackgroundColor) {
|
|
311
|
+
closeButton.style.backgroundColor = launcher.closeButtonBackgroundColor;
|
|
312
|
+
closeButton.classList.remove("hover:tvw-bg-gray-100");
|
|
313
|
+
} else {
|
|
314
|
+
closeButton.style.backgroundColor = "";
|
|
315
|
+
closeButton.classList.add("hover:tvw-bg-gray-100");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Apply border if width and/or color are provided
|
|
319
|
+
if (launcher.closeButtonBorderWidth || launcher.closeButtonBorderColor) {
|
|
320
|
+
const borderWidth = launcher.closeButtonBorderWidth || "0px";
|
|
321
|
+
const borderColor = launcher.closeButtonBorderColor || "transparent";
|
|
322
|
+
closeButton.style.border = `${borderWidth} solid ${borderColor}`;
|
|
323
|
+
closeButton.classList.remove("tvw-border-none");
|
|
324
|
+
} else {
|
|
325
|
+
closeButton.style.border = "";
|
|
326
|
+
closeButton.classList.add("tvw-border-none");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (launcher.closeButtonBorderRadius) {
|
|
330
|
+
closeButton.style.borderRadius = launcher.closeButtonBorderRadius;
|
|
331
|
+
closeButton.classList.remove("tvw-rounded-full");
|
|
332
|
+
} else {
|
|
333
|
+
closeButton.style.borderRadius = "";
|
|
334
|
+
closeButton.classList.add("tvw-rounded-full");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Apply padding styling
|
|
338
|
+
if (launcher.closeButtonPaddingX) {
|
|
339
|
+
closeButton.style.paddingLeft = launcher.closeButtonPaddingX;
|
|
340
|
+
closeButton.style.paddingRight = launcher.closeButtonPaddingX;
|
|
341
|
+
} else {
|
|
342
|
+
closeButton.style.paddingLeft = "";
|
|
343
|
+
closeButton.style.paddingRight = "";
|
|
344
|
+
}
|
|
345
|
+
if (launcher.closeButtonPaddingY) {
|
|
346
|
+
closeButton.style.paddingTop = launcher.closeButtonPaddingY;
|
|
347
|
+
closeButton.style.paddingBottom = launcher.closeButtonPaddingY;
|
|
348
|
+
} else {
|
|
349
|
+
closeButton.style.paddingTop = "";
|
|
350
|
+
closeButton.style.paddingBottom = "";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
closeButtonWrapper.appendChild(closeButton);
|
|
354
|
+
|
|
355
|
+
// Add tooltip with portaling to document.body to escape overflow clipping
|
|
356
|
+
if (closeButtonShowTooltip && closeButtonTooltipText) {
|
|
357
|
+
let portaledTooltip: HTMLElement | null = null;
|
|
358
|
+
|
|
359
|
+
const showTooltip = () => {
|
|
360
|
+
if (portaledTooltip) return; // Already showing
|
|
361
|
+
|
|
362
|
+
// Create tooltip element
|
|
363
|
+
portaledTooltip = createElement("div", "tvw-clear-chat-tooltip");
|
|
364
|
+
portaledTooltip.textContent = closeButtonTooltipText;
|
|
365
|
+
|
|
366
|
+
// Add arrow
|
|
367
|
+
const arrow = createElement("div");
|
|
368
|
+
arrow.className = "tvw-clear-chat-tooltip-arrow";
|
|
369
|
+
portaledTooltip.appendChild(arrow);
|
|
370
|
+
|
|
371
|
+
// Get button position
|
|
372
|
+
const buttonRect = closeButton.getBoundingClientRect();
|
|
373
|
+
|
|
374
|
+
// Position tooltip above button
|
|
375
|
+
portaledTooltip.style.position = "fixed";
|
|
376
|
+
portaledTooltip.style.left = `${buttonRect.left + buttonRect.width / 2}px`;
|
|
377
|
+
portaledTooltip.style.top = `${buttonRect.top - 8}px`;
|
|
378
|
+
portaledTooltip.style.transform = "translate(-50%, -100%)";
|
|
379
|
+
|
|
380
|
+
// Append to body
|
|
381
|
+
document.body.appendChild(portaledTooltip);
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const hideTooltip = () => {
|
|
385
|
+
if (portaledTooltip && portaledTooltip.parentNode) {
|
|
386
|
+
portaledTooltip.parentNode.removeChild(portaledTooltip);
|
|
387
|
+
portaledTooltip = null;
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Add event listeners
|
|
392
|
+
closeButtonWrapper.addEventListener("mouseenter", showTooltip);
|
|
393
|
+
closeButtonWrapper.addEventListener("mouseleave", hideTooltip);
|
|
394
|
+
closeButton.addEventListener("focus", showTooltip);
|
|
395
|
+
closeButton.addEventListener("blur", hideTooltip);
|
|
396
|
+
|
|
397
|
+
// Store cleanup function on the wrapper for later use
|
|
398
|
+
(closeButtonWrapper as any)._cleanupTooltip = () => {
|
|
399
|
+
hideTooltip();
|
|
400
|
+
closeButtonWrapper.removeEventListener("mouseenter", showTooltip);
|
|
401
|
+
closeButtonWrapper.removeEventListener("mouseleave", hideTooltip);
|
|
402
|
+
closeButton.removeEventListener("focus", showTooltip);
|
|
403
|
+
closeButton.removeEventListener("blur", hideTooltip);
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Inline placement: append close button to header
|
|
408
|
+
if (closeButtonPlacement !== "top-right") {
|
|
409
|
+
header.appendChild(closeButtonWrapper);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
header,
|
|
414
|
+
iconHolder,
|
|
415
|
+
headerTitle: title,
|
|
416
|
+
headerSubtitle: subtitle,
|
|
417
|
+
closeButton,
|
|
418
|
+
closeButtonWrapper,
|
|
419
|
+
clearChatButton,
|
|
420
|
+
clearChatButtonWrapper
|
|
421
|
+
};
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Attach header elements to the container, handling placement modes.
|
|
426
|
+
*/
|
|
427
|
+
export const attachHeaderToContainer = (
|
|
428
|
+
container: HTMLElement,
|
|
429
|
+
headerElements: HeaderElements,
|
|
430
|
+
config?: AgentWidgetConfig
|
|
431
|
+
): void => {
|
|
432
|
+
const launcher = config?.launcher ?? {};
|
|
433
|
+
const closeButtonPlacement = launcher.closeButtonPlacement ?? "inline";
|
|
434
|
+
const clearChatPlacement = launcher.clearChat?.placement ?? "inline";
|
|
435
|
+
|
|
436
|
+
// Add header to container
|
|
437
|
+
container.appendChild(headerElements.header);
|
|
438
|
+
|
|
439
|
+
// Position close button wrapper if top-right placement
|
|
440
|
+
if (closeButtonPlacement === "top-right") {
|
|
441
|
+
container.style.position = "relative";
|
|
442
|
+
container.appendChild(headerElements.closeButtonWrapper);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Position clear chat button wrapper if top-right placement
|
|
446
|
+
if (
|
|
447
|
+
headerElements.clearChatButtonWrapper &&
|
|
448
|
+
clearChatPlacement === "top-right"
|
|
449
|
+
) {
|
|
450
|
+
container.style.position = "relative";
|
|
451
|
+
container.appendChild(headerElements.clearChatButtonWrapper);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|