@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.
Files changed (69) hide show
  1. package/README.md +140 -8
  2. package/dist/index.cjs +90 -39
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1098 -24
  5. package/dist/index.d.ts +1098 -24
  6. package/dist/index.global.js +134 -83
  7. package/dist/index.global.js.map +1 -1
  8. package/dist/index.js +90 -39
  9. package/dist/index.js.map +1 -1
  10. package/dist/install.global.js +1 -1
  11. package/dist/install.global.js.map +1 -1
  12. package/dist/widget.css +849 -513
  13. package/package.json +1 -1
  14. package/src/artifacts-session.test.ts +80 -0
  15. package/src/client.test.ts +20 -21
  16. package/src/client.ts +153 -4
  17. package/src/components/approval-bubble.ts +45 -42
  18. package/src/components/artifact-card.ts +91 -0
  19. package/src/components/artifact-pane.ts +501 -0
  20. package/src/components/composer-builder.ts +32 -27
  21. package/src/components/event-stream-view.ts +40 -40
  22. package/src/components/feedback.ts +36 -36
  23. package/src/components/forms.ts +11 -11
  24. package/src/components/header-builder.test.ts +32 -0
  25. package/src/components/header-builder.ts +55 -36
  26. package/src/components/header-layouts.ts +58 -125
  27. package/src/components/launcher.ts +36 -21
  28. package/src/components/message-bubble.ts +92 -65
  29. package/src/components/messages.ts +2 -2
  30. package/src/components/panel.ts +42 -11
  31. package/src/components/reasoning-bubble.ts +23 -23
  32. package/src/components/registry.ts +4 -0
  33. package/src/components/suggestions.ts +1 -1
  34. package/src/components/tool-bubble.ts +32 -32
  35. package/src/defaults.ts +30 -4
  36. package/src/index.ts +80 -2
  37. package/src/install.ts +22 -0
  38. package/src/plugins/types.ts +23 -0
  39. package/src/postprocessors.ts +2 -2
  40. package/src/runtime/host-layout.ts +174 -0
  41. package/src/runtime/init.test.ts +236 -0
  42. package/src/runtime/init.ts +114 -55
  43. package/src/session.ts +135 -2
  44. package/src/styles/tailwind.css +1 -1
  45. package/src/styles/widget.css +849 -513
  46. package/src/types/theme.ts +376 -0
  47. package/src/types.ts +338 -15
  48. package/src/ui.docked.test.ts +104 -0
  49. package/src/ui.ts +940 -227
  50. package/src/utils/artifact-gate.test.ts +255 -0
  51. package/src/utils/artifact-gate.ts +142 -0
  52. package/src/utils/artifact-resize.test.ts +64 -0
  53. package/src/utils/artifact-resize.ts +67 -0
  54. package/src/utils/attachment-manager.ts +10 -10
  55. package/src/utils/code-generators.test.ts +52 -0
  56. package/src/utils/code-generators.ts +40 -36
  57. package/src/utils/dock.ts +17 -0
  58. package/src/utils/dom-context.test.ts +504 -0
  59. package/src/utils/dom-context.ts +896 -0
  60. package/src/utils/dom.ts +12 -1
  61. package/src/utils/message-fingerprint.test.ts +187 -0
  62. package/src/utils/message-fingerprint.ts +105 -0
  63. package/src/utils/migration.ts +220 -0
  64. package/src/utils/morph.ts +1 -1
  65. package/src/utils/plugins.ts +175 -0
  66. package/src/utils/positioning.ts +4 -4
  67. package/src/utils/theme.test.ts +157 -0
  68. package/src/utils/theme.ts +224 -60
  69. 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 = 'tvw-feedback-container tvw-feedback-csat';
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 = 'tvw-feedback-content';
83
+ content.className = 'persona-feedback-content';
84
84
 
85
85
  // Header
86
86
  const header = document.createElement('div');
87
- header.className = 'tvw-feedback-header';
87
+ header.className = 'persona-feedback-header';
88
88
 
89
89
  const titleEl = document.createElement('h3');
90
- titleEl.className = 'tvw-feedback-title';
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 = 'tvw-feedback-subtitle';
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 = 'tvw-feedback-rating tvw-feedback-rating-csat';
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 = 'tvw-feedback-rating-btn tvw-feedback-star-btn';
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="tvw-feedback-star" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
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 = 'tvw-feedback-comment-container';
145
+ commentContainer.className = 'persona-feedback-comment-container';
146
146
 
147
147
  commentTextarea = document.createElement('textarea');
148
- commentTextarea.className = 'tvw-feedback-comment';
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 = 'tvw-feedback-actions';
159
+ actions.className = 'persona-feedback-actions';
160
160
 
161
161
  const skipButton = document.createElement('button');
162
162
  skipButton.type = 'button';
163
- skipButton.className = 'tvw-feedback-btn tvw-feedback-btn-skip';
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 = 'tvw-feedback-btn tvw-feedback-btn-submit';
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('tvw-feedback-shake');
178
- setTimeout(() => ratingContainer.classList.remove('tvw-feedback-shake'), 500);
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 = 'tvw-feedback-container tvw-feedback-nps';
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 = 'tvw-feedback-content';
233
+ content.className = 'persona-feedback-content';
234
234
 
235
235
  // Header
236
236
  const header = document.createElement('div');
237
- header.className = 'tvw-feedback-header';
237
+ header.className = 'persona-feedback-header';
238
238
 
239
239
  const titleEl = document.createElement('h3');
240
- titleEl.className = 'tvw-feedback-title';
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 = 'tvw-feedback-subtitle';
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 = 'tvw-feedback-rating tvw-feedback-rating-nps';
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 = 'tvw-feedback-labels';
259
+ labelsRow.className = 'persona-feedback-labels';
260
260
 
261
261
  const lowLabelEl = document.createElement('span');
262
- lowLabelEl.className = 'tvw-feedback-label-low';
262
+ lowLabelEl.className = 'persona-feedback-label-low';
263
263
  lowLabelEl.textContent = lowLabel;
264
264
 
265
265
  const highLabelEl = document.createElement('span');
266
- highLabelEl.className = 'tvw-feedback-label-high';
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 = 'tvw-feedback-numbers';
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 = 'tvw-feedback-rating-btn tvw-feedback-number-btn';
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('tvw-feedback-detractor');
290
+ ratingButton.classList.add('persona-feedback-detractor');
291
291
  } else if (i <= 8) {
292
- ratingButton.classList.add('tvw-feedback-passive');
292
+ ratingButton.classList.add('persona-feedback-passive');
293
293
  } else {
294
- ratingButton.classList.add('tvw-feedback-promoter');
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 = 'tvw-feedback-comment-container';
317
+ commentContainer.className = 'persona-feedback-comment-container';
318
318
 
319
319
  commentTextarea = document.createElement('textarea');
320
- commentTextarea.className = 'tvw-feedback-comment';
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 = 'tvw-feedback-actions';
331
+ actions.className = 'persona-feedback-actions';
332
332
 
333
333
  const skipButton = document.createElement('button');
334
334
  skipButton.type = 'button';
335
- skipButton.className = 'tvw-feedback-btn tvw-feedback-btn-skip';
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 = 'tvw-feedback-btn tvw-feedback-btn-submit';
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('tvw-feedback-shake');
350
- setTimeout(() => numbersRow.classList.remove('tvw-feedback-shake'), 500);
349
+ numbersRow.classList.add('persona-feedback-shake');
350
+ setTimeout(() => numbersRow.classList.remove('persona-feedback-shake'), 500);
351
351
  return;
352
352
  }
353
353
 
@@ -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("tvw-form-card", "tvw-space-y-4");
55
+ placeholder.classList.add("persona-form-card", "persona-space-y-4");
56
56
 
57
- const heading = createElement("div", "tvw-space-y-1");
57
+ const heading = createElement("div", "persona-space-y-1");
58
58
  const title = createElement(
59
59
  "h3",
60
- "tvw-text-base tvw-font-semibold tvw-text-cw-primary"
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
- "tvw-text-sm tvw-text-cw-muted"
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 = "tvw-form-grid tvw-space-y-3";
74
+ form.className = "persona-form-grid persona-space-y-3";
75
75
 
76
76
  definition.fields.forEach((field) => {
77
- const group = createElement("label", "tvw-form-field tvw-flex tvw-flex-col tvw-gap-1");
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", "tvw-text-xs tvw-font-medium tvw-text-cw-muted");
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
- "tvw-rounded-xl tvw-border tvw-border-gray-200 tvw-bg-white tvw-px-3 tvw-py-2 tvw-text-sm tvw-text-cw-primary focus:tvw-outline-none focus:tvw-border-cw-primary";
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
- "tvw-flex tvw-items-center tvw-justify-between tvw-gap-2"
106
+ "persona-flex persona-items-center persona-justify-between persona-gap-2"
107
107
  );
108
108
  const status = createElement(
109
109
  "div",
110
- "tvw-text-xs tvw-text-cw-muted tvw-min-h-[1.5rem]"
110
+ "persona-text-xs persona-text-persona-muted persona-min-h-[1.5rem]"
111
111
  );
112
112
  const submit = createElement(
113
113
  "button",
114
- "tvw-inline-flex tvw-items-center tvw-rounded-full tvw-bg-cw-primary tvw-px-4 tvw-py-2 tvw-text-sm tvw-font-semibold tvw-text-white disabled:tvw-opacity-60 tvw-cursor-pointer"
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
- "tvw-widget-header tvw-flex tvw-items-center tvw-gap-3 tvw-bg-cw-surface tvw-px-6 tvw-py-5 tvw-border-b-cw-divider"
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
- "tvw-flex tvw-items-center tvw-justify-center tvw-rounded-xl tvw-bg-cw-primary tvw-text-white tvw-text-xl"
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 = "tvw-rounded-xl tvw-object-cover";
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", "tvw-flex tvw-flex-col");
77
- const title = createElement("span", "tvw-text-base tvw-font-semibold");
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", "tvw-text-xs tvw-text-cw-muted");
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 tvw-clear-chat-button-wrapper class for top-right mode as its
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
- ? "tvw-absolute tvw-top-4 tvw-z-50"
119
- : "tvw-relative tvw-ml-auto tvw-clear-chat-button-wrapper"
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
- "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"
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("tvw-text-cw-muted");
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:tvw-bg-gray-100");
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("tvw-border-none");
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("tvw-rounded-full");
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 = createElement("div", "tvw-clear-chat-tooltip");
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 = createElement("div");
208
- arrow.className = "tvw-clear-chat-tooltip-arrow";
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
- document.body.appendChild(portaledTooltip);
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
- ? "tvw-absolute tvw-top-4 tvw-right-4 tvw-z-50"
274
+ ? "persona-absolute persona-top-4 persona-right-4 persona-z-50"
263
275
  : clearChatEnabled && clearChatPlacement === "inline"
264
276
  ? ""
265
- : "tvw-ml-auto"
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
- "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"
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("tvw-text-cw-muted");
316
+ closeButton.classList.remove("persona-text-persona-muted");
305
317
  } else {
306
318
  closeButton.style.color = "";
307
- closeButton.classList.add("tvw-text-cw-muted");
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:tvw-bg-gray-100");
324
+ closeButton.classList.remove("hover:persona-bg-gray-100");
313
325
  } else {
314
326
  closeButton.style.backgroundColor = "";
315
- closeButton.classList.add("hover:tvw-bg-gray-100");
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("tvw-border-none");
335
+ closeButton.classList.remove("persona-border-none");
324
336
  } else {
325
337
  closeButton.style.border = "";
326
- closeButton.classList.add("tvw-border-none");
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("tvw-rounded-full");
343
+ closeButton.classList.remove("persona-rounded-full");
332
344
  } else {
333
345
  closeButton.style.borderRadius = "";
334
- closeButton.classList.add("tvw-rounded-full");
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 = createElement("div", "tvw-clear-chat-tooltip");
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 = createElement("div");
368
- arrow.className = "tvw-clear-chat-tooltip-arrow";
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
- document.body.appendChild(portaledTooltip);
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
-