@runtypelabs/persona 1.48.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -8
- package/dist/index.cjs +90 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1098 -24
- package/dist/index.d.ts +1098 -24
- package/dist/index.global.js +134 -83
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +90 -39
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/widget.css +849 -513
- package/package.json +1 -1
- package/src/artifacts-session.test.ts +80 -0
- package/src/client.test.ts +20 -21
- package/src/client.ts +153 -4
- package/src/components/approval-bubble.ts +45 -42
- package/src/components/artifact-card.ts +91 -0
- package/src/components/artifact-pane.ts +501 -0
- package/src/components/composer-builder.ts +32 -27
- package/src/components/event-stream-view.ts +40 -40
- package/src/components/feedback.ts +36 -36
- package/src/components/forms.ts +11 -11
- package/src/components/header-builder.test.ts +32 -0
- package/src/components/header-builder.ts +55 -36
- package/src/components/header-layouts.ts +58 -125
- package/src/components/launcher.ts +36 -21
- package/src/components/message-bubble.ts +92 -65
- package/src/components/messages.ts +2 -2
- package/src/components/panel.ts +42 -11
- package/src/components/reasoning-bubble.ts +23 -23
- package/src/components/registry.ts +4 -0
- package/src/components/suggestions.ts +1 -1
- package/src/components/tool-bubble.ts +32 -32
- package/src/defaults.ts +30 -4
- package/src/index.ts +80 -2
- package/src/install.ts +22 -0
- package/src/plugins/types.ts +23 -0
- package/src/postprocessors.ts +2 -2
- package/src/runtime/host-layout.ts +174 -0
- package/src/runtime/init.test.ts +236 -0
- package/src/runtime/init.ts +114 -55
- package/src/session.ts +135 -2
- package/src/styles/tailwind.css +1 -1
- package/src/styles/widget.css +849 -513
- package/src/types/theme.ts +376 -0
- package/src/types.ts +338 -15
- package/src/ui.docked.test.ts +104 -0
- package/src/ui.ts +940 -227
- package/src/utils/artifact-gate.test.ts +255 -0
- package/src/utils/artifact-gate.ts +142 -0
- package/src/utils/artifact-resize.test.ts +64 -0
- package/src/utils/artifact-resize.ts +67 -0
- package/src/utils/attachment-manager.ts +10 -10
- package/src/utils/code-generators.test.ts +52 -0
- package/src/utils/code-generators.ts +40 -36
- package/src/utils/dock.ts +17 -0
- package/src/utils/dom-context.test.ts +504 -0
- package/src/utils/dom-context.ts +896 -0
- package/src/utils/dom.ts +12 -1
- package/src/utils/message-fingerprint.test.ts +187 -0
- package/src/utils/message-fingerprint.ts +105 -0
- package/src/utils/migration.ts +220 -0
- package/src/utils/morph.ts +1 -1
- package/src/utils/plugins.ts +175 -0
- package/src/utils/positioning.ts +4 -4
- package/src/utils/theme.test.ts +157 -0
- package/src/utils/theme.ts +224 -60
- package/src/utils/tokens.ts +701 -0
|
@@ -72,7 +72,7 @@ export function createCSATFeedback(options: CSATFeedbackOptions): HTMLElement {
|
|
|
72
72
|
} = options;
|
|
73
73
|
|
|
74
74
|
const container = document.createElement('div');
|
|
75
|
-
container.className = '
|
|
75
|
+
container.className = 'persona-feedback-container persona-feedback-csat';
|
|
76
76
|
container.setAttribute('role', 'dialog');
|
|
77
77
|
container.setAttribute('aria-label', 'Customer satisfaction feedback');
|
|
78
78
|
|
|
@@ -80,19 +80,19 @@ export function createCSATFeedback(options: CSATFeedbackOptions): HTMLElement {
|
|
|
80
80
|
|
|
81
81
|
// Create inner content
|
|
82
82
|
const content = document.createElement('div');
|
|
83
|
-
content.className = '
|
|
83
|
+
content.className = 'persona-feedback-content';
|
|
84
84
|
|
|
85
85
|
// Header
|
|
86
86
|
const header = document.createElement('div');
|
|
87
|
-
header.className = '
|
|
87
|
+
header.className = 'persona-feedback-header';
|
|
88
88
|
|
|
89
89
|
const titleEl = document.createElement('h3');
|
|
90
|
-
titleEl.className = '
|
|
90
|
+
titleEl.className = 'persona-feedback-title';
|
|
91
91
|
titleEl.textContent = title;
|
|
92
92
|
header.appendChild(titleEl);
|
|
93
93
|
|
|
94
94
|
const subtitleEl = document.createElement('p');
|
|
95
|
-
subtitleEl.className = '
|
|
95
|
+
subtitleEl.className = 'persona-feedback-subtitle';
|
|
96
96
|
subtitleEl.textContent = subtitle;
|
|
97
97
|
header.appendChild(subtitleEl);
|
|
98
98
|
|
|
@@ -100,7 +100,7 @@ export function createCSATFeedback(options: CSATFeedbackOptions): HTMLElement {
|
|
|
100
100
|
|
|
101
101
|
// Rating buttons (1-5 stars or numbers)
|
|
102
102
|
const ratingContainer = document.createElement('div');
|
|
103
|
-
ratingContainer.className = '
|
|
103
|
+
ratingContainer.className = 'persona-feedback-rating persona-feedback-rating-csat';
|
|
104
104
|
ratingContainer.setAttribute('role', 'radiogroup');
|
|
105
105
|
ratingContainer.setAttribute('aria-label', 'Satisfaction rating from 1 to 5');
|
|
106
106
|
|
|
@@ -109,7 +109,7 @@ export function createCSATFeedback(options: CSATFeedbackOptions): HTMLElement {
|
|
|
109
109
|
for (let i = 1; i <= 5; i++) {
|
|
110
110
|
const ratingButton = document.createElement('button');
|
|
111
111
|
ratingButton.type = 'button';
|
|
112
|
-
ratingButton.className = '
|
|
112
|
+
ratingButton.className = 'persona-feedback-rating-btn persona-feedback-star-btn';
|
|
113
113
|
ratingButton.setAttribute('role', 'radio');
|
|
114
114
|
ratingButton.setAttribute('aria-checked', 'false');
|
|
115
115
|
ratingButton.setAttribute('aria-label', `${i} star${i > 1 ? 's' : ''}: ${ratingLabels[i - 1]}`);
|
|
@@ -118,7 +118,7 @@ export function createCSATFeedback(options: CSATFeedbackOptions): HTMLElement {
|
|
|
118
118
|
|
|
119
119
|
// Star icon (filled when selected)
|
|
120
120
|
ratingButton.innerHTML = `
|
|
121
|
-
<svg class="
|
|
121
|
+
<svg class="persona-feedback-star" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
122
122
|
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
|
|
123
123
|
</svg>
|
|
124
124
|
`;
|
|
@@ -142,10 +142,10 @@ export function createCSATFeedback(options: CSATFeedbackOptions): HTMLElement {
|
|
|
142
142
|
let commentTextarea: HTMLTextAreaElement | null = null;
|
|
143
143
|
if (showComment) {
|
|
144
144
|
const commentContainer = document.createElement('div');
|
|
145
|
-
commentContainer.className = '
|
|
145
|
+
commentContainer.className = 'persona-feedback-comment-container';
|
|
146
146
|
|
|
147
147
|
commentTextarea = document.createElement('textarea');
|
|
148
|
-
commentTextarea.className = '
|
|
148
|
+
commentTextarea.className = 'persona-feedback-comment';
|
|
149
149
|
commentTextarea.placeholder = commentPlaceholder;
|
|
150
150
|
commentTextarea.rows = 3;
|
|
151
151
|
commentTextarea.setAttribute('aria-label', 'Additional comments');
|
|
@@ -156,11 +156,11 @@ export function createCSATFeedback(options: CSATFeedbackOptions): HTMLElement {
|
|
|
156
156
|
|
|
157
157
|
// Action buttons
|
|
158
158
|
const actions = document.createElement('div');
|
|
159
|
-
actions.className = '
|
|
159
|
+
actions.className = 'persona-feedback-actions';
|
|
160
160
|
|
|
161
161
|
const skipButton = document.createElement('button');
|
|
162
162
|
skipButton.type = 'button';
|
|
163
|
-
skipButton.className = '
|
|
163
|
+
skipButton.className = 'persona-feedback-btn persona-feedback-btn-skip';
|
|
164
164
|
skipButton.textContent = skipText;
|
|
165
165
|
skipButton.addEventListener('click', () => {
|
|
166
166
|
onDismiss?.();
|
|
@@ -169,13 +169,13 @@ export function createCSATFeedback(options: CSATFeedbackOptions): HTMLElement {
|
|
|
169
169
|
|
|
170
170
|
const submitButton = document.createElement('button');
|
|
171
171
|
submitButton.type = 'button';
|
|
172
|
-
submitButton.className = '
|
|
172
|
+
submitButton.className = 'persona-feedback-btn persona-feedback-btn-submit';
|
|
173
173
|
submitButton.textContent = submitText;
|
|
174
174
|
submitButton.addEventListener('click', async () => {
|
|
175
175
|
if (selectedRating === null) {
|
|
176
176
|
// Shake the rating container to indicate selection required
|
|
177
|
-
ratingContainer.classList.add('
|
|
178
|
-
setTimeout(() => ratingContainer.classList.remove('
|
|
177
|
+
ratingContainer.classList.add('persona-feedback-shake');
|
|
178
|
+
setTimeout(() => ratingContainer.classList.remove('persona-feedback-shake'), 500);
|
|
179
179
|
return;
|
|
180
180
|
}
|
|
181
181
|
|
|
@@ -222,7 +222,7 @@ export function createNPSFeedback(options: NPSFeedbackOptions): HTMLElement {
|
|
|
222
222
|
} = options;
|
|
223
223
|
|
|
224
224
|
const container = document.createElement('div');
|
|
225
|
-
container.className = '
|
|
225
|
+
container.className = 'persona-feedback-container persona-feedback-nps';
|
|
226
226
|
container.setAttribute('role', 'dialog');
|
|
227
227
|
container.setAttribute('aria-label', 'Net Promoter Score feedback');
|
|
228
228
|
|
|
@@ -230,19 +230,19 @@ export function createNPSFeedback(options: NPSFeedbackOptions): HTMLElement {
|
|
|
230
230
|
|
|
231
231
|
// Create inner content
|
|
232
232
|
const content = document.createElement('div');
|
|
233
|
-
content.className = '
|
|
233
|
+
content.className = 'persona-feedback-content';
|
|
234
234
|
|
|
235
235
|
// Header
|
|
236
236
|
const header = document.createElement('div');
|
|
237
|
-
header.className = '
|
|
237
|
+
header.className = 'persona-feedback-header';
|
|
238
238
|
|
|
239
239
|
const titleEl = document.createElement('h3');
|
|
240
|
-
titleEl.className = '
|
|
240
|
+
titleEl.className = 'persona-feedback-title';
|
|
241
241
|
titleEl.textContent = title;
|
|
242
242
|
header.appendChild(titleEl);
|
|
243
243
|
|
|
244
244
|
const subtitleEl = document.createElement('p');
|
|
245
|
-
subtitleEl.className = '
|
|
245
|
+
subtitleEl.className = 'persona-feedback-subtitle';
|
|
246
246
|
subtitleEl.textContent = subtitle;
|
|
247
247
|
header.appendChild(subtitleEl);
|
|
248
248
|
|
|
@@ -250,20 +250,20 @@ export function createNPSFeedback(options: NPSFeedbackOptions): HTMLElement {
|
|
|
250
250
|
|
|
251
251
|
// Rating buttons (0-10)
|
|
252
252
|
const ratingContainer = document.createElement('div');
|
|
253
|
-
ratingContainer.className = '
|
|
253
|
+
ratingContainer.className = 'persona-feedback-rating persona-feedback-rating-nps';
|
|
254
254
|
ratingContainer.setAttribute('role', 'radiogroup');
|
|
255
255
|
ratingContainer.setAttribute('aria-label', 'Likelihood rating from 0 to 10');
|
|
256
256
|
|
|
257
257
|
// Labels row
|
|
258
258
|
const labelsRow = document.createElement('div');
|
|
259
|
-
labelsRow.className = '
|
|
259
|
+
labelsRow.className = 'persona-feedback-labels';
|
|
260
260
|
|
|
261
261
|
const lowLabelEl = document.createElement('span');
|
|
262
|
-
lowLabelEl.className = '
|
|
262
|
+
lowLabelEl.className = 'persona-feedback-label-low';
|
|
263
263
|
lowLabelEl.textContent = lowLabel;
|
|
264
264
|
|
|
265
265
|
const highLabelEl = document.createElement('span');
|
|
266
|
-
highLabelEl.className = '
|
|
266
|
+
highLabelEl.className = 'persona-feedback-label-high';
|
|
267
267
|
highLabelEl.textContent = highLabel;
|
|
268
268
|
|
|
269
269
|
labelsRow.appendChild(lowLabelEl);
|
|
@@ -271,14 +271,14 @@ export function createNPSFeedback(options: NPSFeedbackOptions): HTMLElement {
|
|
|
271
271
|
|
|
272
272
|
// Numbers row
|
|
273
273
|
const numbersRow = document.createElement('div');
|
|
274
|
-
numbersRow.className = '
|
|
274
|
+
numbersRow.className = 'persona-feedback-numbers';
|
|
275
275
|
|
|
276
276
|
const ratingButtons: HTMLButtonElement[] = [];
|
|
277
277
|
|
|
278
278
|
for (let i = 0; i <= 10; i++) {
|
|
279
279
|
const ratingButton = document.createElement('button');
|
|
280
280
|
ratingButton.type = 'button';
|
|
281
|
-
ratingButton.className = '
|
|
281
|
+
ratingButton.className = 'persona-feedback-rating-btn persona-feedback-number-btn';
|
|
282
282
|
ratingButton.setAttribute('role', 'radio');
|
|
283
283
|
ratingButton.setAttribute('aria-checked', 'false');
|
|
284
284
|
ratingButton.setAttribute('aria-label', `Rating ${i} out of 10`);
|
|
@@ -287,11 +287,11 @@ export function createNPSFeedback(options: NPSFeedbackOptions): HTMLElement {
|
|
|
287
287
|
|
|
288
288
|
// Color coding: detractors (0-6), passives (7-8), promoters (9-10)
|
|
289
289
|
if (i <= 6) {
|
|
290
|
-
ratingButton.classList.add('
|
|
290
|
+
ratingButton.classList.add('persona-feedback-detractor');
|
|
291
291
|
} else if (i <= 8) {
|
|
292
|
-
ratingButton.classList.add('
|
|
292
|
+
ratingButton.classList.add('persona-feedback-passive');
|
|
293
293
|
} else {
|
|
294
|
-
ratingButton.classList.add('
|
|
294
|
+
ratingButton.classList.add('persona-feedback-promoter');
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
ratingButton.addEventListener('click', () => {
|
|
@@ -314,10 +314,10 @@ export function createNPSFeedback(options: NPSFeedbackOptions): HTMLElement {
|
|
|
314
314
|
let commentTextarea: HTMLTextAreaElement | null = null;
|
|
315
315
|
if (showComment) {
|
|
316
316
|
const commentContainer = document.createElement('div');
|
|
317
|
-
commentContainer.className = '
|
|
317
|
+
commentContainer.className = 'persona-feedback-comment-container';
|
|
318
318
|
|
|
319
319
|
commentTextarea = document.createElement('textarea');
|
|
320
|
-
commentTextarea.className = '
|
|
320
|
+
commentTextarea.className = 'persona-feedback-comment';
|
|
321
321
|
commentTextarea.placeholder = commentPlaceholder;
|
|
322
322
|
commentTextarea.rows = 3;
|
|
323
323
|
commentTextarea.setAttribute('aria-label', 'Additional comments');
|
|
@@ -328,11 +328,11 @@ export function createNPSFeedback(options: NPSFeedbackOptions): HTMLElement {
|
|
|
328
328
|
|
|
329
329
|
// Action buttons
|
|
330
330
|
const actions = document.createElement('div');
|
|
331
|
-
actions.className = '
|
|
331
|
+
actions.className = 'persona-feedback-actions';
|
|
332
332
|
|
|
333
333
|
const skipButton = document.createElement('button');
|
|
334
334
|
skipButton.type = 'button';
|
|
335
|
-
skipButton.className = '
|
|
335
|
+
skipButton.className = 'persona-feedback-btn persona-feedback-btn-skip';
|
|
336
336
|
skipButton.textContent = skipText;
|
|
337
337
|
skipButton.addEventListener('click', () => {
|
|
338
338
|
onDismiss?.();
|
|
@@ -341,13 +341,13 @@ export function createNPSFeedback(options: NPSFeedbackOptions): HTMLElement {
|
|
|
341
341
|
|
|
342
342
|
const submitButton = document.createElement('button');
|
|
343
343
|
submitButton.type = 'button';
|
|
344
|
-
submitButton.className = '
|
|
344
|
+
submitButton.className = 'persona-feedback-btn persona-feedback-btn-submit';
|
|
345
345
|
submitButton.textContent = submitText;
|
|
346
346
|
submitButton.addEventListener('click', async () => {
|
|
347
347
|
if (selectedRating === null) {
|
|
348
348
|
// Shake the rating container to indicate selection required
|
|
349
|
-
numbersRow.classList.add('
|
|
350
|
-
setTimeout(() => numbersRow.classList.remove('
|
|
349
|
+
numbersRow.classList.add('persona-feedback-shake');
|
|
350
|
+
setTimeout(() => numbersRow.classList.remove('persona-feedback-shake'), 500);
|
|
351
351
|
return;
|
|
352
352
|
}
|
|
353
353
|
|
package/src/components/forms.ts
CHANGED
|
@@ -52,31 +52,31 @@ export const enhanceWithForms = (
|
|
|
52
52
|
placeholder.dataset.enhanced = "true";
|
|
53
53
|
|
|
54
54
|
const definition = formDefinitions[type] ?? formDefinitions.init;
|
|
55
|
-
placeholder.classList.add("
|
|
55
|
+
placeholder.classList.add("persona-form-card", "persona-space-y-4");
|
|
56
56
|
|
|
57
|
-
const heading = createElement("div", "
|
|
57
|
+
const heading = createElement("div", "persona-space-y-1");
|
|
58
58
|
const title = createElement(
|
|
59
59
|
"h3",
|
|
60
|
-
"
|
|
60
|
+
"persona-text-base persona-font-semibold persona-text-persona-primary"
|
|
61
61
|
);
|
|
62
62
|
title.textContent = definition.title;
|
|
63
63
|
heading.appendChild(title);
|
|
64
64
|
if (definition.description) {
|
|
65
65
|
const desc = createElement(
|
|
66
66
|
"p",
|
|
67
|
-
"
|
|
67
|
+
"persona-text-sm persona-text-persona-muted"
|
|
68
68
|
);
|
|
69
69
|
desc.textContent = definition.description;
|
|
70
70
|
heading.appendChild(desc);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
const form = document.createElement("form");
|
|
74
|
-
form.className = "
|
|
74
|
+
form.className = "persona-form-grid persona-space-y-3";
|
|
75
75
|
|
|
76
76
|
definition.fields.forEach((field) => {
|
|
77
|
-
const group = createElement("label", "
|
|
77
|
+
const group = createElement("label", "persona-form-field persona-flex persona-flex-col persona-gap-1");
|
|
78
78
|
group.htmlFor = `${message.id}-${type}-${field.name}`;
|
|
79
|
-
const label = createElement("span", "
|
|
79
|
+
const label = createElement("span", "persona-text-xs persona-font-medium persona-text-persona-muted");
|
|
80
80
|
label.textContent = field.label;
|
|
81
81
|
group.appendChild(label);
|
|
82
82
|
|
|
@@ -90,7 +90,7 @@ export const enhanceWithForms = (
|
|
|
90
90
|
control.type = inputType;
|
|
91
91
|
}
|
|
92
92
|
control.className =
|
|
93
|
-
"
|
|
93
|
+
"persona-rounded-xl persona-border persona-border-gray-200 persona-bg-white persona-px-3 persona-py-2 persona-text-sm persona-text-persona-primary focus:persona-outline-none focus:persona-border-persona-primary";
|
|
94
94
|
control.id = `${message.id}-${type}-${field.name}`;
|
|
95
95
|
control.name = field.name;
|
|
96
96
|
control.placeholder = field.placeholder ?? "";
|
|
@@ -103,15 +103,15 @@ export const enhanceWithForms = (
|
|
|
103
103
|
|
|
104
104
|
const actions = createElement(
|
|
105
105
|
"div",
|
|
106
|
-
"
|
|
106
|
+
"persona-flex persona-items-center persona-justify-between persona-gap-2"
|
|
107
107
|
);
|
|
108
108
|
const status = createElement(
|
|
109
109
|
"div",
|
|
110
|
-
"
|
|
110
|
+
"persona-text-xs persona-text-persona-muted persona-min-h-[1.5rem]"
|
|
111
111
|
);
|
|
112
112
|
const submit = createElement(
|
|
113
113
|
"button",
|
|
114
|
-
"
|
|
114
|
+
"persona-inline-flex persona-items-center persona-rounded-full persona-bg-persona-primary persona-px-4 persona-py-2 persona-text-sm persona-font-semibold persona-text-white disabled:persona-opacity-60 persona-cursor-pointer"
|
|
115
115
|
) as HTMLButtonElement;
|
|
116
116
|
submit.type = "submit";
|
|
117
117
|
submit.textContent = definition.submitLabel ?? "Submit";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { buildHeader } from "./header-builder";
|
|
5
|
+
|
|
6
|
+
describe("buildHeader tooltips", () => {
|
|
7
|
+
it("portals the close-button tooltip into the mounted document", () => {
|
|
8
|
+
const iframeDocument = document.implementation.createHTMLDocument("preview");
|
|
9
|
+
const { header, closeButton, closeButtonWrapper } = buildHeader({
|
|
10
|
+
config: {
|
|
11
|
+
launcher: {
|
|
12
|
+
clearChat: { enabled: false },
|
|
13
|
+
closeButtonShowTooltip: true,
|
|
14
|
+
closeButtonTooltipText: "Close chat"
|
|
15
|
+
}
|
|
16
|
+
} as any
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
iframeDocument.body.appendChild(header);
|
|
20
|
+
|
|
21
|
+
expect(closeButton.ownerDocument).toBe(iframeDocument);
|
|
22
|
+
|
|
23
|
+
closeButtonWrapper.dispatchEvent(new Event("mouseenter"));
|
|
24
|
+
|
|
25
|
+
expect(iframeDocument.body.querySelector(".persona-clear-chat-tooltip")).not.toBeNull();
|
|
26
|
+
expect(document.body.querySelector(".persona-clear-chat-tooltip")).toBeNull();
|
|
27
|
+
|
|
28
|
+
closeButtonWrapper.dispatchEvent(new Event("mouseleave"));
|
|
29
|
+
|
|
30
|
+
expect(iframeDocument.body.querySelector(".persona-clear-chat-tooltip")).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createElement } from "../utils/dom";
|
|
1
|
+
import { createElement, createElementInDocument } from "../utils/dom";
|
|
2
2
|
import { renderLucideIcon } from "../utils/icons";
|
|
3
3
|
import { AgentWidgetConfig } from "../types";
|
|
4
4
|
|
|
@@ -29,8 +29,12 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
29
29
|
|
|
30
30
|
const header = createElement(
|
|
31
31
|
"div",
|
|
32
|
-
"
|
|
32
|
+
"persona-widget-header persona-flex persona-items-center persona-gap-3 persona-px-6 persona-py-5"
|
|
33
33
|
);
|
|
34
|
+
header.style.backgroundColor = 'var(--persona-header-bg, var(--persona-surface, #ffffff))';
|
|
35
|
+
header.style.borderBottomWidth = '1px';
|
|
36
|
+
header.style.borderBottomStyle = 'solid';
|
|
37
|
+
header.style.borderBottomColor = 'var(--persona-header-border, var(--persona-divider, #f1f5f9))';
|
|
34
38
|
|
|
35
39
|
const launcher = config?.launcher ?? {};
|
|
36
40
|
const headerIconSize = launcher.headerIconSize ?? "48px";
|
|
@@ -41,7 +45,7 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
41
45
|
|
|
42
46
|
const iconHolder = createElement(
|
|
43
47
|
"div",
|
|
44
|
-
"
|
|
48
|
+
"persona-flex persona-items-center persona-justify-center persona-rounded-xl persona-bg-persona-primary persona-text-white persona-text-xl"
|
|
45
49
|
);
|
|
46
50
|
iconHolder.style.height = headerIconSize;
|
|
47
51
|
iconHolder.style.width = headerIconSize;
|
|
@@ -51,7 +55,7 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
51
55
|
if (headerIconName) {
|
|
52
56
|
// Use Lucide icon
|
|
53
57
|
const iconSize = parseFloat(headerIconSize) || 24;
|
|
54
|
-
const iconSvg = renderLucideIcon(headerIconName, iconSize * 0.6, "#ffffff", 1);
|
|
58
|
+
const iconSvg = renderLucideIcon(headerIconName, iconSize * 0.6, "var(--persona-text-inverse, #ffffff)", 1);
|
|
55
59
|
if (iconSvg) {
|
|
56
60
|
iconHolder.replaceChildren(iconSvg);
|
|
57
61
|
} else {
|
|
@@ -63,7 +67,7 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
63
67
|
const img = createElement("img") as HTMLImageElement;
|
|
64
68
|
img.src = config.launcher.iconUrl;
|
|
65
69
|
img.alt = "";
|
|
66
|
-
img.className = "
|
|
70
|
+
img.className = "persona-rounded-xl persona-object-cover";
|
|
67
71
|
img.style.height = headerIconSize;
|
|
68
72
|
img.style.width = headerIconSize;
|
|
69
73
|
iconHolder.replaceChildren(img);
|
|
@@ -73,10 +77,10 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
const headerCopy = createElement("div", "
|
|
77
|
-
const title = createElement("span", "
|
|
80
|
+
const headerCopy = createElement("div", "persona-flex persona-flex-col");
|
|
81
|
+
const title = createElement("span", "persona-text-base persona-font-semibold");
|
|
78
82
|
title.textContent = config?.launcher?.title ?? "Chat Assistant";
|
|
79
|
-
const subtitle = createElement("span", "
|
|
83
|
+
const subtitle = createElement("span", "persona-text-xs persona-text-persona-muted");
|
|
80
84
|
subtitle.textContent =
|
|
81
85
|
config?.launcher?.subtitle ?? "Here to help you get answers fast";
|
|
82
86
|
|
|
@@ -110,13 +114,13 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
110
114
|
const clearChatShowTooltip = clearChatConfig.showTooltip ?? true;
|
|
111
115
|
|
|
112
116
|
// Create button wrapper for tooltip - positioned based on placement
|
|
113
|
-
// Note: Don't use
|
|
117
|
+
// Note: Don't use persona-clear-chat-button-wrapper class for top-right mode as its
|
|
114
118
|
// display: inline-flex causes alignment issues with the close button
|
|
115
119
|
clearChatButtonWrapper = createElement(
|
|
116
120
|
"div",
|
|
117
121
|
clearChatPlacement === "top-right"
|
|
118
|
-
? "
|
|
119
|
-
: "
|
|
122
|
+
? "persona-absolute persona-top-4 persona-z-50"
|
|
123
|
+
: "persona-relative persona-ml-auto persona-clear-chat-button-wrapper"
|
|
120
124
|
);
|
|
121
125
|
|
|
122
126
|
// Position to the left of the close button (which is at right: 1rem/16px)
|
|
@@ -127,7 +131,7 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
127
131
|
|
|
128
132
|
clearChatButton = createElement(
|
|
129
133
|
"button",
|
|
130
|
-
"
|
|
134
|
+
"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full persona-text-persona-muted hover:persona-bg-gray-100 persona-cursor-pointer persona-border-none"
|
|
131
135
|
) as HTMLButtonElement;
|
|
132
136
|
|
|
133
137
|
clearChatButton.style.height = clearChatSize;
|
|
@@ -149,24 +153,24 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
149
153
|
// Apply styling from config
|
|
150
154
|
if (clearChatIconColor) {
|
|
151
155
|
clearChatButton.style.color = clearChatIconColor;
|
|
152
|
-
clearChatButton.classList.remove("
|
|
156
|
+
clearChatButton.classList.remove("persona-text-persona-muted");
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
if (clearChatBgColor) {
|
|
156
160
|
clearChatButton.style.backgroundColor = clearChatBgColor;
|
|
157
|
-
clearChatButton.classList.remove("hover:
|
|
161
|
+
clearChatButton.classList.remove("hover:persona-bg-gray-100");
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
if (clearChatBorderWidth || clearChatBorderColor) {
|
|
161
165
|
const borderWidth = clearChatBorderWidth || "0px";
|
|
162
166
|
const borderColor = clearChatBorderColor || "transparent";
|
|
163
167
|
clearChatButton.style.border = `${borderWidth} solid ${borderColor}`;
|
|
164
|
-
clearChatButton.classList.remove("
|
|
168
|
+
clearChatButton.classList.remove("persona-border-none");
|
|
165
169
|
}
|
|
166
170
|
|
|
167
171
|
if (clearChatBorderRadius) {
|
|
168
172
|
clearChatButton.style.borderRadius = clearChatBorderRadius;
|
|
169
|
-
clearChatButton.classList.remove("
|
|
173
|
+
clearChatButton.classList.remove("persona-rounded-full");
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
// Apply padding styling
|
|
@@ -199,13 +203,21 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
199
203
|
const showTooltip = () => {
|
|
200
204
|
if (portaledTooltip || !clearChatButton) return; // Already showing or button doesn't exist
|
|
201
205
|
|
|
206
|
+
const tooltipDocument = clearChatButton.ownerDocument;
|
|
207
|
+
const tooltipContainer = tooltipDocument.body;
|
|
208
|
+
if (!tooltipContainer) return;
|
|
209
|
+
|
|
202
210
|
// Create tooltip element
|
|
203
|
-
portaledTooltip =
|
|
211
|
+
portaledTooltip = createElementInDocument(
|
|
212
|
+
tooltipDocument,
|
|
213
|
+
"div",
|
|
214
|
+
"persona-clear-chat-tooltip"
|
|
215
|
+
);
|
|
204
216
|
portaledTooltip.textContent = clearChatTooltipText;
|
|
205
217
|
|
|
206
218
|
// Add arrow
|
|
207
|
-
const arrow =
|
|
208
|
-
arrow.className = "
|
|
219
|
+
const arrow = createElementInDocument(tooltipDocument, "div");
|
|
220
|
+
arrow.className = "persona-clear-chat-tooltip-arrow";
|
|
209
221
|
portaledTooltip.appendChild(arrow);
|
|
210
222
|
|
|
211
223
|
// Get button position
|
|
@@ -218,7 +230,7 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
218
230
|
portaledTooltip.style.transform = "translate(-50%, -100%)";
|
|
219
231
|
|
|
220
232
|
// Append to body
|
|
221
|
-
|
|
233
|
+
tooltipContainer.appendChild(portaledTooltip);
|
|
222
234
|
};
|
|
223
235
|
|
|
224
236
|
const hideTooltip = () => {
|
|
@@ -259,16 +271,16 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
259
271
|
const closeButtonWrapper = createElement(
|
|
260
272
|
"div",
|
|
261
273
|
closeButtonPlacement === "top-right"
|
|
262
|
-
? "
|
|
274
|
+
? "persona-absolute persona-top-4 persona-right-4 persona-z-50"
|
|
263
275
|
: clearChatEnabled && clearChatPlacement === "inline"
|
|
264
276
|
? ""
|
|
265
|
-
: "
|
|
277
|
+
: "persona-ml-auto"
|
|
266
278
|
);
|
|
267
279
|
|
|
268
280
|
// Create close button with base classes
|
|
269
281
|
const closeButton = createElement(
|
|
270
282
|
"button",
|
|
271
|
-
"
|
|
283
|
+
"persona-inline-flex persona-items-center persona-justify-center persona-rounded-full persona-text-persona-muted hover:persona-bg-gray-100 persona-cursor-pointer persona-border-none"
|
|
272
284
|
) as HTMLButtonElement;
|
|
273
285
|
closeButton.style.height = closeButtonSize;
|
|
274
286
|
closeButton.style.width = closeButtonSize;
|
|
@@ -301,18 +313,18 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
301
313
|
// Apply close button styling from config
|
|
302
314
|
if (launcher.closeButtonColor) {
|
|
303
315
|
closeButton.style.color = launcher.closeButtonColor;
|
|
304
|
-
closeButton.classList.remove("
|
|
316
|
+
closeButton.classList.remove("persona-text-persona-muted");
|
|
305
317
|
} else {
|
|
306
318
|
closeButton.style.color = "";
|
|
307
|
-
closeButton.classList.add("
|
|
319
|
+
closeButton.classList.add("persona-text-persona-muted");
|
|
308
320
|
}
|
|
309
321
|
|
|
310
322
|
if (launcher.closeButtonBackgroundColor) {
|
|
311
323
|
closeButton.style.backgroundColor = launcher.closeButtonBackgroundColor;
|
|
312
|
-
closeButton.classList.remove("hover:
|
|
324
|
+
closeButton.classList.remove("hover:persona-bg-gray-100");
|
|
313
325
|
} else {
|
|
314
326
|
closeButton.style.backgroundColor = "";
|
|
315
|
-
closeButton.classList.add("hover:
|
|
327
|
+
closeButton.classList.add("hover:persona-bg-gray-100");
|
|
316
328
|
}
|
|
317
329
|
|
|
318
330
|
// Apply border if width and/or color are provided
|
|
@@ -320,18 +332,18 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
320
332
|
const borderWidth = launcher.closeButtonBorderWidth || "0px";
|
|
321
333
|
const borderColor = launcher.closeButtonBorderColor || "transparent";
|
|
322
334
|
closeButton.style.border = `${borderWidth} solid ${borderColor}`;
|
|
323
|
-
closeButton.classList.remove("
|
|
335
|
+
closeButton.classList.remove("persona-border-none");
|
|
324
336
|
} else {
|
|
325
337
|
closeButton.style.border = "";
|
|
326
|
-
closeButton.classList.add("
|
|
338
|
+
closeButton.classList.add("persona-border-none");
|
|
327
339
|
}
|
|
328
340
|
|
|
329
341
|
if (launcher.closeButtonBorderRadius) {
|
|
330
342
|
closeButton.style.borderRadius = launcher.closeButtonBorderRadius;
|
|
331
|
-
closeButton.classList.remove("
|
|
343
|
+
closeButton.classList.remove("persona-rounded-full");
|
|
332
344
|
} else {
|
|
333
345
|
closeButton.style.borderRadius = "";
|
|
334
|
-
closeButton.classList.add("
|
|
346
|
+
closeButton.classList.add("persona-rounded-full");
|
|
335
347
|
}
|
|
336
348
|
|
|
337
349
|
// Apply padding styling
|
|
@@ -359,13 +371,21 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
359
371
|
const showTooltip = () => {
|
|
360
372
|
if (portaledTooltip) return; // Already showing
|
|
361
373
|
|
|
374
|
+
const tooltipDocument = closeButton.ownerDocument;
|
|
375
|
+
const tooltipContainer = tooltipDocument.body;
|
|
376
|
+
if (!tooltipContainer) return;
|
|
377
|
+
|
|
362
378
|
// Create tooltip element
|
|
363
|
-
portaledTooltip =
|
|
379
|
+
portaledTooltip = createElementInDocument(
|
|
380
|
+
tooltipDocument,
|
|
381
|
+
"div",
|
|
382
|
+
"persona-clear-chat-tooltip"
|
|
383
|
+
);
|
|
364
384
|
portaledTooltip.textContent = closeButtonTooltipText;
|
|
365
385
|
|
|
366
386
|
// Add arrow
|
|
367
|
-
const arrow =
|
|
368
|
-
arrow.className = "
|
|
387
|
+
const arrow = createElementInDocument(tooltipDocument, "div");
|
|
388
|
+
arrow.className = "persona-clear-chat-tooltip-arrow";
|
|
369
389
|
portaledTooltip.appendChild(arrow);
|
|
370
390
|
|
|
371
391
|
// Get button position
|
|
@@ -378,7 +398,7 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
378
398
|
portaledTooltip.style.transform = "translate(-50%, -100%)";
|
|
379
399
|
|
|
380
400
|
// Append to body
|
|
381
|
-
|
|
401
|
+
tooltipContainer.appendChild(portaledTooltip);
|
|
382
402
|
};
|
|
383
403
|
|
|
384
404
|
const hideTooltip = () => {
|
|
@@ -452,4 +472,3 @@ export const attachHeaderToContainer = (
|
|
|
452
472
|
}
|
|
453
473
|
};
|
|
454
474
|
|
|
455
|
-
|