@runtypelabs/persona 3.18.0 → 3.19.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 +1 -1
- package/dist/index.cjs +47 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +281 -4
- package/dist/index.d.ts +281 -4
- package/dist/index.global.js +102 -1636
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +47 -47
- package/dist/index.js.map +1 -1
- package/dist/theme-editor.cjs +1438 -619
- package/dist/theme-editor.d.cts +119 -1
- package/dist/theme-editor.d.ts +119 -1
- package/dist/theme-editor.js +1552 -619
- package/dist/widget.css +348 -0
- package/package.json +1 -1
- package/src/components/composer-builder.test.ts +52 -0
- package/src/components/composer-builder.ts +67 -490
- package/src/components/composer-parts.test.ts +152 -0
- package/src/components/composer-parts.ts +452 -0
- package/src/components/header-builder.ts +22 -299
- package/src/components/header-parts.ts +360 -0
- package/src/components/panel.test.ts +61 -0
- package/src/components/panel.ts +262 -5
- package/src/components/pill-composer-builder.test.ts +85 -0
- package/src/components/pill-composer-builder.ts +183 -0
- package/src/index.ts +4 -0
- package/src/runtime/init.ts +4 -2
- package/src/runtime/persist-state.test.ts +152 -0
- package/src/styles/widget.css +348 -0
- package/src/types.ts +121 -1
- package/src/ui.component-directive.test.ts +183 -0
- package/src/ui.composer-bar.test.ts +1009 -0
- package/src/ui.ts +809 -72
- package/src/utils/attachment-manager.ts +1 -1
- package/src/utils/dock.test.ts +45 -0
- package/src/utils/dock.ts +3 -0
- package/src/utils/icons.ts +314 -58
- package/src/utils/stream-animation.ts +7 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createElement
|
|
1
|
+
import { createElement } from "../utils/dom";
|
|
2
2
|
import { renderLucideIcon } from "../utils/icons";
|
|
3
3
|
import { AgentWidgetConfig } from "../types";
|
|
4
|
-
import {
|
|
4
|
+
import { createCloseButton, createClearChatButton } from "./header-parts";
|
|
5
5
|
|
|
6
6
|
/** CSS `color` values; variables are set on `[data-persona-root]` from `theme.components.header`. */
|
|
7
7
|
export const HEADER_THEME_CSS = {
|
|
@@ -50,7 +50,6 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
50
50
|
|
|
51
51
|
const launcher = config?.launcher ?? {};
|
|
52
52
|
const headerIconSize = launcher.headerIconSize ?? "48px";
|
|
53
|
-
const closeButtonSize = launcher.closeButtonSize ?? "32px";
|
|
54
53
|
const closeButtonPlacement = launcher.closeButtonPlacement ?? "inline";
|
|
55
54
|
const headerIconHidden = launcher.headerIconHidden ?? false;
|
|
56
55
|
const headerIconName = launcher.headerIconName;
|
|
@@ -119,320 +118,44 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
119
118
|
let clearChatButtonWrapper: HTMLElement | null = null;
|
|
120
119
|
|
|
121
120
|
if (clearChatEnabled) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
const clearChatBorderWidth = clearChatConfig.borderWidth ?? "";
|
|
127
|
-
const clearChatBorderColor = clearChatConfig.borderColor ?? "";
|
|
128
|
-
const clearChatBorderRadius = clearChatConfig.borderRadius ?? "";
|
|
129
|
-
const clearChatPaddingX = clearChatConfig.paddingX ?? "";
|
|
130
|
-
const clearChatPaddingY = clearChatConfig.paddingY ?? "";
|
|
131
|
-
const clearChatTooltipText = clearChatConfig.tooltipText ?? "Clear chat";
|
|
132
|
-
const clearChatShowTooltip = clearChatConfig.showTooltip ?? true;
|
|
133
|
-
|
|
134
|
-
// Create button wrapper for tooltip - positioned based on placement
|
|
135
|
-
// Note: Don't use persona-clear-chat-button-wrapper class for top-right mode as its
|
|
136
|
-
// display: inline-flex causes alignment issues with the close button
|
|
137
|
-
clearChatButtonWrapper = createElement(
|
|
138
|
-
"div",
|
|
121
|
+
// Top-right placement uses an absolute wrapper offset from the close
|
|
122
|
+
// button (which lives at right: 16px and is ~32px wide, leaving ~48px
|
|
123
|
+
// from the panel's right edge for the clear icon).
|
|
124
|
+
const wrapperClassName =
|
|
139
125
|
clearChatPlacement === "top-right"
|
|
140
126
|
? "persona-absolute persona-top-4 persona-z-50"
|
|
141
|
-
: "persona-relative persona-ml-auto persona-clear-chat-button-wrapper"
|
|
142
|
-
|
|
127
|
+
: "persona-relative persona-ml-auto persona-clear-chat-button-wrapper";
|
|
128
|
+
|
|
129
|
+
const parts = createClearChatButton(config, { wrapperClassName });
|
|
130
|
+
clearChatButton = parts.button;
|
|
131
|
+
clearChatButtonWrapper = parts.wrapper;
|
|
143
132
|
|
|
144
|
-
// Position to the left of the close button (which is at right: 1rem/16px)
|
|
145
|
-
// Close button is ~32px wide, plus small gap = 48px from right
|
|
146
133
|
if (clearChatPlacement === "top-right") {
|
|
147
134
|
clearChatButtonWrapper.style.right = "48px";
|
|
148
135
|
}
|
|
149
136
|
|
|
150
|
-
clearChatButton = createElement(
|
|
151
|
-
"button",
|
|
152
|
-
"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full hover:persona-bg-gray-100 persona-cursor-pointer persona-border-none"
|
|
153
|
-
) as HTMLButtonElement;
|
|
154
|
-
|
|
155
|
-
clearChatButton.style.height = clearChatSize;
|
|
156
|
-
clearChatButton.style.width = clearChatSize;
|
|
157
|
-
clearChatButton.type = "button";
|
|
158
|
-
clearChatButton.setAttribute("aria-label", clearChatTooltipText);
|
|
159
|
-
clearChatButton.style.color =
|
|
160
|
-
clearChatIconColor || HEADER_THEME_CSS.actionIconColor;
|
|
161
|
-
|
|
162
|
-
// Add icon. display:block eliminates inline-baseline spacing that can
|
|
163
|
-
// push the icon a fractional pixel off-center inside the button.
|
|
164
|
-
const iconSvg = renderLucideIcon(clearChatIconName, "20px", "currentColor", 1);
|
|
165
|
-
if (iconSvg) {
|
|
166
|
-
iconSvg.style.display = "block";
|
|
167
|
-
clearChatButton.appendChild(iconSvg);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (clearChatBgColor) {
|
|
171
|
-
clearChatButton.style.backgroundColor = clearChatBgColor;
|
|
172
|
-
clearChatButton.classList.remove("hover:persona-bg-gray-100");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (clearChatBorderWidth || clearChatBorderColor) {
|
|
176
|
-
const borderWidth = clearChatBorderWidth || "0px";
|
|
177
|
-
const borderColor = clearChatBorderColor || "transparent";
|
|
178
|
-
clearChatButton.style.border = `${borderWidth} solid ${borderColor}`;
|
|
179
|
-
clearChatButton.classList.remove("persona-border-none");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (clearChatBorderRadius) {
|
|
183
|
-
clearChatButton.style.borderRadius = clearChatBorderRadius;
|
|
184
|
-
clearChatButton.classList.remove("persona-rounded-full");
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Apply padding styling
|
|
188
|
-
if (clearChatPaddingX) {
|
|
189
|
-
clearChatButton.style.paddingLeft = clearChatPaddingX;
|
|
190
|
-
clearChatButton.style.paddingRight = clearChatPaddingX;
|
|
191
|
-
} else {
|
|
192
|
-
clearChatButton.style.paddingLeft = "";
|
|
193
|
-
clearChatButton.style.paddingRight = "";
|
|
194
|
-
}
|
|
195
|
-
if (clearChatPaddingY) {
|
|
196
|
-
clearChatButton.style.paddingTop = clearChatPaddingY;
|
|
197
|
-
clearChatButton.style.paddingBottom = clearChatPaddingY;
|
|
198
|
-
} else {
|
|
199
|
-
clearChatButton.style.paddingTop = "";
|
|
200
|
-
clearChatButton.style.paddingBottom = "";
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
clearChatButtonWrapper.appendChild(clearChatButton);
|
|
204
|
-
|
|
205
|
-
// Add tooltip with portaling to document.body to escape overflow clipping
|
|
206
|
-
if (
|
|
207
|
-
clearChatShowTooltip &&
|
|
208
|
-
clearChatTooltipText &&
|
|
209
|
-
clearChatButton &&
|
|
210
|
-
clearChatButtonWrapper
|
|
211
|
-
) {
|
|
212
|
-
let portaledTooltip: HTMLElement | null = null;
|
|
213
|
-
|
|
214
|
-
const showTooltip = () => {
|
|
215
|
-
if (portaledTooltip || !clearChatButton) return; // Already showing or button doesn't exist
|
|
216
|
-
|
|
217
|
-
const tooltipDocument = clearChatButton.ownerDocument;
|
|
218
|
-
const tooltipContainer = tooltipDocument.body;
|
|
219
|
-
if (!tooltipContainer) return;
|
|
220
|
-
|
|
221
|
-
// Create tooltip element
|
|
222
|
-
portaledTooltip = createElementInDocument(
|
|
223
|
-
tooltipDocument,
|
|
224
|
-
"div",
|
|
225
|
-
"persona-clear-chat-tooltip"
|
|
226
|
-
);
|
|
227
|
-
portaledTooltip.textContent = clearChatTooltipText;
|
|
228
|
-
|
|
229
|
-
// Add arrow
|
|
230
|
-
const arrow = createElementInDocument(tooltipDocument, "div");
|
|
231
|
-
arrow.className = "persona-clear-chat-tooltip-arrow";
|
|
232
|
-
portaledTooltip.appendChild(arrow);
|
|
233
|
-
|
|
234
|
-
// Get button position
|
|
235
|
-
const buttonRect = clearChatButton.getBoundingClientRect();
|
|
236
|
-
|
|
237
|
-
// Position tooltip above button
|
|
238
|
-
portaledTooltip.style.position = "fixed";
|
|
239
|
-
portaledTooltip.style.zIndex = String(PORTALED_OVERLAY_Z_INDEX);
|
|
240
|
-
portaledTooltip.style.left = `${buttonRect.left + buttonRect.width / 2}px`;
|
|
241
|
-
portaledTooltip.style.top = `${buttonRect.top - 8}px`;
|
|
242
|
-
portaledTooltip.style.transform = "translate(-50%, -100%)";
|
|
243
|
-
|
|
244
|
-
// Append to body
|
|
245
|
-
tooltipContainer.appendChild(portaledTooltip);
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
const hideTooltip = () => {
|
|
249
|
-
if (portaledTooltip && portaledTooltip.parentNode) {
|
|
250
|
-
portaledTooltip.parentNode.removeChild(portaledTooltip);
|
|
251
|
-
portaledTooltip = null;
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
// Add event listeners
|
|
256
|
-
clearChatButtonWrapper.addEventListener("mouseenter", showTooltip);
|
|
257
|
-
clearChatButtonWrapper.addEventListener("mouseleave", hideTooltip);
|
|
258
|
-
clearChatButton.addEventListener("focus", showTooltip);
|
|
259
|
-
clearChatButton.addEventListener("blur", hideTooltip);
|
|
260
|
-
|
|
261
|
-
// Store cleanup function on the button for later use
|
|
262
|
-
(clearChatButtonWrapper as any)._cleanupTooltip = () => {
|
|
263
|
-
hideTooltip();
|
|
264
|
-
if (clearChatButtonWrapper) {
|
|
265
|
-
clearChatButtonWrapper.removeEventListener("mouseenter", showTooltip);
|
|
266
|
-
clearChatButtonWrapper.removeEventListener("mouseleave", hideTooltip);
|
|
267
|
-
}
|
|
268
|
-
if (clearChatButton) {
|
|
269
|
-
clearChatButton.removeEventListener("focus", showTooltip);
|
|
270
|
-
clearChatButton.removeEventListener("blur", hideTooltip);
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
137
|
// Only append to header if inline placement
|
|
276
138
|
if (clearChatPlacement === "inline") {
|
|
277
139
|
header.appendChild(clearChatButtonWrapper);
|
|
278
140
|
}
|
|
279
141
|
}
|
|
280
142
|
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
143
|
+
// Build the close (×) button via the shared factory. The wrapper class
|
|
144
|
+
// mirrors the clear-chat wrapper's inline-flex centering so both header
|
|
145
|
+
// action buttons vertically align identically within the header's flex
|
|
146
|
+
// row. composer-bar mode uses the same factory directly with its own
|
|
147
|
+
// wrapper class to render a top-right-only close button.
|
|
148
|
+
const closeButtonWrapperClass =
|
|
287
149
|
closeButtonPlacement === "top-right"
|
|
288
150
|
? "persona-absolute persona-top-4 persona-right-4 persona-z-50"
|
|
289
151
|
: clearChatEnabled && clearChatPlacement === "inline"
|
|
290
152
|
? "persona-relative persona-inline-flex persona-items-center persona-justify-center"
|
|
291
|
-
: "persona-relative persona-ml-auto persona-inline-flex persona-items-center persona-justify-center"
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
// Create close button with base classes
|
|
295
|
-
const closeButton = createElement(
|
|
296
|
-
"button",
|
|
297
|
-
"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full hover:persona-bg-gray-100 persona-cursor-pointer persona-border-none"
|
|
298
|
-
) as HTMLButtonElement;
|
|
299
|
-
closeButton.style.height = closeButtonSize;
|
|
300
|
-
closeButton.style.width = closeButtonSize;
|
|
301
|
-
closeButton.type = "button";
|
|
302
|
-
|
|
303
|
-
// Get tooltip config
|
|
304
|
-
const closeButtonTooltipText = launcher.closeButtonTooltipText ?? "Close chat";
|
|
305
|
-
const closeButtonShowTooltip = launcher.closeButtonShowTooltip ?? true;
|
|
153
|
+
: "persona-relative persona-ml-auto persona-inline-flex persona-items-center persona-justify-center";
|
|
306
154
|
|
|
307
|
-
closeButton
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const closeButtonIconName = launcher.closeButtonIconName ?? "x";
|
|
312
|
-
const closeButtonIconText = launcher.closeButtonIconText ?? "×";
|
|
313
|
-
closeButton.style.color =
|
|
314
|
-
launcher.closeButtonColor || HEADER_THEME_CSS.actionIconColor;
|
|
315
|
-
|
|
316
|
-
// Try to render Lucide icon, fallback to text if not provided or fails.
|
|
317
|
-
// The X glyph's paths occupy only the middle 50% of its 24x24 viewBox
|
|
318
|
-
// (from 6,6 to 18,18), while other header icons (e.g. refresh-cw) span
|
|
319
|
-
// ~75% of the viewBox. Rendering X at a larger intrinsic size brings
|
|
320
|
-
// its visible extent into parity with sibling icons in the header.
|
|
321
|
-
// display:block eliminates inline-baseline spacing that can push the
|
|
322
|
-
// icon a fractional pixel off-center inside the button.
|
|
323
|
-
const closeIconSvg = renderLucideIcon(closeButtonIconName, "28px", "currentColor", 1);
|
|
324
|
-
if (closeIconSvg) {
|
|
325
|
-
closeIconSvg.style.display = "block";
|
|
326
|
-
closeButton.appendChild(closeIconSvg);
|
|
327
|
-
} else {
|
|
328
|
-
closeButton.textContent = closeButtonIconText;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (launcher.closeButtonBackgroundColor) {
|
|
332
|
-
closeButton.style.backgroundColor = launcher.closeButtonBackgroundColor;
|
|
333
|
-
closeButton.classList.remove("hover:persona-bg-gray-100");
|
|
334
|
-
} else {
|
|
335
|
-
closeButton.style.backgroundColor = "";
|
|
336
|
-
closeButton.classList.add("hover:persona-bg-gray-100");
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Apply border if width and/or color are provided
|
|
340
|
-
if (launcher.closeButtonBorderWidth || launcher.closeButtonBorderColor) {
|
|
341
|
-
const borderWidth = launcher.closeButtonBorderWidth || "0px";
|
|
342
|
-
const borderColor = launcher.closeButtonBorderColor || "transparent";
|
|
343
|
-
closeButton.style.border = `${borderWidth} solid ${borderColor}`;
|
|
344
|
-
closeButton.classList.remove("persona-border-none");
|
|
345
|
-
} else {
|
|
346
|
-
closeButton.style.border = "";
|
|
347
|
-
closeButton.classList.add("persona-border-none");
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (launcher.closeButtonBorderRadius) {
|
|
351
|
-
closeButton.style.borderRadius = launcher.closeButtonBorderRadius;
|
|
352
|
-
closeButton.classList.remove("persona-rounded-full");
|
|
353
|
-
} else {
|
|
354
|
-
closeButton.style.borderRadius = "";
|
|
355
|
-
closeButton.classList.add("persona-rounded-full");
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Apply padding styling
|
|
359
|
-
if (launcher.closeButtonPaddingX) {
|
|
360
|
-
closeButton.style.paddingLeft = launcher.closeButtonPaddingX;
|
|
361
|
-
closeButton.style.paddingRight = launcher.closeButtonPaddingX;
|
|
362
|
-
} else {
|
|
363
|
-
closeButton.style.paddingLeft = "";
|
|
364
|
-
closeButton.style.paddingRight = "";
|
|
365
|
-
}
|
|
366
|
-
if (launcher.closeButtonPaddingY) {
|
|
367
|
-
closeButton.style.paddingTop = launcher.closeButtonPaddingY;
|
|
368
|
-
closeButton.style.paddingBottom = launcher.closeButtonPaddingY;
|
|
369
|
-
} else {
|
|
370
|
-
closeButton.style.paddingTop = "";
|
|
371
|
-
closeButton.style.paddingBottom = "";
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
closeButtonWrapper.appendChild(closeButton);
|
|
375
|
-
|
|
376
|
-
// Add tooltip with portaling to document.body to escape overflow clipping
|
|
377
|
-
if (closeButtonShowTooltip && closeButtonTooltipText) {
|
|
378
|
-
let portaledTooltip: HTMLElement | null = null;
|
|
379
|
-
|
|
380
|
-
const showTooltip = () => {
|
|
381
|
-
if (portaledTooltip) return; // Already showing
|
|
382
|
-
|
|
383
|
-
const tooltipDocument = closeButton.ownerDocument;
|
|
384
|
-
const tooltipContainer = tooltipDocument.body;
|
|
385
|
-
if (!tooltipContainer) return;
|
|
386
|
-
|
|
387
|
-
// Create tooltip element
|
|
388
|
-
portaledTooltip = createElementInDocument(
|
|
389
|
-
tooltipDocument,
|
|
390
|
-
"div",
|
|
391
|
-
"persona-clear-chat-tooltip"
|
|
392
|
-
);
|
|
393
|
-
portaledTooltip.textContent = closeButtonTooltipText;
|
|
394
|
-
|
|
395
|
-
// Add arrow
|
|
396
|
-
const arrow = createElementInDocument(tooltipDocument, "div");
|
|
397
|
-
arrow.className = "persona-clear-chat-tooltip-arrow";
|
|
398
|
-
portaledTooltip.appendChild(arrow);
|
|
399
|
-
|
|
400
|
-
// Get button position
|
|
401
|
-
const buttonRect = closeButton.getBoundingClientRect();
|
|
402
|
-
|
|
403
|
-
// Position tooltip above button
|
|
404
|
-
portaledTooltip.style.position = "fixed";
|
|
405
|
-
portaledTooltip.style.zIndex = String(PORTALED_OVERLAY_Z_INDEX);
|
|
406
|
-
portaledTooltip.style.left = `${buttonRect.left + buttonRect.width / 2}px`;
|
|
407
|
-
portaledTooltip.style.top = `${buttonRect.top - 8}px`;
|
|
408
|
-
portaledTooltip.style.transform = "translate(-50%, -100%)";
|
|
409
|
-
|
|
410
|
-
// Append to body
|
|
411
|
-
tooltipContainer.appendChild(portaledTooltip);
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
const hideTooltip = () => {
|
|
415
|
-
if (portaledTooltip && portaledTooltip.parentNode) {
|
|
416
|
-
portaledTooltip.parentNode.removeChild(portaledTooltip);
|
|
417
|
-
portaledTooltip = null;
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
// Add event listeners
|
|
422
|
-
closeButtonWrapper.addEventListener("mouseenter", showTooltip);
|
|
423
|
-
closeButtonWrapper.addEventListener("mouseleave", hideTooltip);
|
|
424
|
-
closeButton.addEventListener("focus", showTooltip);
|
|
425
|
-
closeButton.addEventListener("blur", hideTooltip);
|
|
426
|
-
|
|
427
|
-
// Store cleanup function on the wrapper for later use
|
|
428
|
-
(closeButtonWrapper as any)._cleanupTooltip = () => {
|
|
429
|
-
hideTooltip();
|
|
430
|
-
closeButtonWrapper.removeEventListener("mouseenter", showTooltip);
|
|
431
|
-
closeButtonWrapper.removeEventListener("mouseleave", hideTooltip);
|
|
432
|
-
closeButton.removeEventListener("focus", showTooltip);
|
|
433
|
-
closeButton.removeEventListener("blur", hideTooltip);
|
|
434
|
-
};
|
|
435
|
-
}
|
|
155
|
+
const { button: closeButton, wrapper: closeButtonWrapper } = createCloseButton(
|
|
156
|
+
config,
|
|
157
|
+
{ showClose, wrapperClassName: closeButtonWrapperClass }
|
|
158
|
+
);
|
|
436
159
|
|
|
437
160
|
// Inline placement: append close button to header
|
|
438
161
|
if (closeButtonPlacement !== "top-right") {
|