@runtypelabs/persona 2.3.0 → 3.0.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 +221 -4
- package/dist/index.cjs +42 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +832 -571
- package/dist/index.d.ts +832 -571
- package/dist/index.global.js +87 -87
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +42 -42
- package/dist/index.js.map +1 -1
- package/dist/widget.css +205 -15
- package/package.json +2 -2
- package/src/components/artifact-card.ts +39 -5
- package/src/components/artifact-pane.ts +67 -126
- package/src/components/composer-builder.ts +3 -23
- package/src/components/header-builder.ts +29 -34
- package/src/components/header-layouts.ts +109 -41
- package/src/components/launcher.ts +10 -7
- package/src/components/message-bubble.ts +7 -11
- package/src/components/panel.ts +4 -4
- package/src/defaults.ts +22 -93
- package/src/index.ts +20 -7
- package/src/presets.ts +66 -51
- package/src/runtime/host-layout.test.ts +196 -0
- package/src/runtime/host-layout.ts +265 -27
- package/src/runtime/init.test.ts +77 -7
- package/src/styles/widget.css +205 -15
- package/src/types/theme.ts +76 -0
- package/src/types.ts +86 -97
- package/src/ui.docked.test.ts +203 -7
- package/src/ui.ts +129 -88
- package/src/utils/buttons.ts +417 -0
- package/src/utils/code-generators.test.ts +43 -7
- package/src/utils/code-generators.ts +9 -25
- package/src/utils/deep-merge.ts +26 -0
- package/src/utils/dock.ts +18 -5
- package/src/utils/dropdown.ts +178 -0
- package/src/utils/sanitize.ts +1 -1
- package/src/utils/theme.test.ts +90 -15
- package/src/utils/theme.ts +20 -46
- package/src/utils/tokens.ts +108 -11
- package/src/utils/migration.ts +0 -220
package/src/ui.docked.test.ts
CHANGED
|
@@ -10,7 +10,7 @@ describe("createAgentExperience docked mode", () => {
|
|
|
10
10
|
document.body.innerHTML = "";
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
it("
|
|
13
|
+
it("toggles docked panel open/closed; built-in launcher stays hidden (open via controller.open)", () => {
|
|
14
14
|
const mount = document.createElement("div");
|
|
15
15
|
document.body.appendChild(mount);
|
|
16
16
|
|
|
@@ -22,7 +22,6 @@ describe("createAgentExperience docked mode", () => {
|
|
|
22
22
|
dock: {
|
|
23
23
|
side: "right",
|
|
24
24
|
width: "420px",
|
|
25
|
-
collapsedWidth: "72px",
|
|
26
25
|
},
|
|
27
26
|
},
|
|
28
27
|
});
|
|
@@ -40,7 +39,7 @@ describe("createAgentExperience docked mode", () => {
|
|
|
40
39
|
controller.close();
|
|
41
40
|
|
|
42
41
|
expect(wrapper?.style.display).toBe("none");
|
|
43
|
-
expect(launcherButton?.style.display).toBe("");
|
|
42
|
+
expect(launcherButton?.style.display).toBe("none");
|
|
44
43
|
|
|
45
44
|
controller.open();
|
|
46
45
|
|
|
@@ -50,7 +49,141 @@ describe("createAgentExperience docked mode", () => {
|
|
|
50
49
|
controller.destroy();
|
|
51
50
|
});
|
|
52
51
|
|
|
53
|
-
it("
|
|
52
|
+
it("keeps docked panel hidden when closed under mobile fullscreen breakpoint", () => {
|
|
53
|
+
const prevWidth = window.innerWidth;
|
|
54
|
+
try {
|
|
55
|
+
Object.defineProperty(window, "innerWidth", {
|
|
56
|
+
configurable: true,
|
|
57
|
+
value: 480,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const mount = document.createElement("div");
|
|
61
|
+
document.body.appendChild(mount);
|
|
62
|
+
|
|
63
|
+
const controller = createAgentExperience(mount, {
|
|
64
|
+
apiUrl: "https://api.example.com/chat",
|
|
65
|
+
launcher: {
|
|
66
|
+
mountMode: "docked",
|
|
67
|
+
autoExpand: false,
|
|
68
|
+
dock: {
|
|
69
|
+
side: "right",
|
|
70
|
+
width: "420px",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const wrapper = mount.firstElementChild as HTMLElement | null;
|
|
76
|
+
expect(wrapper?.style.display).toBe("none");
|
|
77
|
+
expect(getComputedStyle(wrapper!).display).toBe("none");
|
|
78
|
+
|
|
79
|
+
controller.open();
|
|
80
|
+
expect(wrapper?.style.display).toBe("flex");
|
|
81
|
+
|
|
82
|
+
controller.destroy();
|
|
83
|
+
} finally {
|
|
84
|
+
Object.defineProperty(window, "innerWidth", {
|
|
85
|
+
configurable: true,
|
|
86
|
+
writable: true,
|
|
87
|
+
value: prevWidth,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("keeps docked panel hidden after resize into mobile when closed", () => {
|
|
93
|
+
const prevWidth = window.innerWidth;
|
|
94
|
+
try {
|
|
95
|
+
Object.defineProperty(window, "innerWidth", {
|
|
96
|
+
configurable: true,
|
|
97
|
+
value: 900,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const mount = document.createElement("div");
|
|
101
|
+
document.body.appendChild(mount);
|
|
102
|
+
|
|
103
|
+
const controller = createAgentExperience(mount, {
|
|
104
|
+
apiUrl: "https://api.example.com/chat",
|
|
105
|
+
launcher: {
|
|
106
|
+
mountMode: "docked",
|
|
107
|
+
autoExpand: false,
|
|
108
|
+
dock: {
|
|
109
|
+
side: "right",
|
|
110
|
+
width: "420px",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const wrapper = mount.firstElementChild as HTMLElement | null;
|
|
116
|
+
expect(wrapper?.style.display).toBe("none");
|
|
117
|
+
|
|
118
|
+
Object.defineProperty(window, "innerWidth", {
|
|
119
|
+
configurable: true,
|
|
120
|
+
value: 480,
|
|
121
|
+
});
|
|
122
|
+
window.dispatchEvent(new Event("resize"));
|
|
123
|
+
|
|
124
|
+
expect(wrapper?.style.display).toBe("none");
|
|
125
|
+
|
|
126
|
+
controller.destroy();
|
|
127
|
+
} finally {
|
|
128
|
+
Object.defineProperty(window, "innerWidth", {
|
|
129
|
+
configurable: true,
|
|
130
|
+
writable: true,
|
|
131
|
+
value: prevWidth,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("collapses the dock width to 0 when the header close button is clicked", () => {
|
|
137
|
+
const target = document.createElement("div");
|
|
138
|
+
document.body.appendChild(target);
|
|
139
|
+
|
|
140
|
+
const hostLayout = createWidgetHostLayout(target, {
|
|
141
|
+
launcher: {
|
|
142
|
+
mountMode: "docked",
|
|
143
|
+
autoExpand: true,
|
|
144
|
+
dock: {
|
|
145
|
+
side: "right",
|
|
146
|
+
width: "420px",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
const mount = document.createElement("div");
|
|
151
|
+
hostLayout.host.appendChild(mount);
|
|
152
|
+
|
|
153
|
+
const controller = createAgentExperience(mount, {
|
|
154
|
+
apiUrl: "https://api.example.com/chat",
|
|
155
|
+
launcher: {
|
|
156
|
+
mountMode: "docked",
|
|
157
|
+
autoExpand: true,
|
|
158
|
+
dock: {
|
|
159
|
+
side: "right",
|
|
160
|
+
width: "420px",
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const syncDockState = () => hostLayout.syncWidgetState(controller.getState());
|
|
166
|
+
const openUnsub = controller.on("widget:opened", syncDockState);
|
|
167
|
+
const closeUnsub = controller.on("widget:closed", syncDockState);
|
|
168
|
+
syncDockState();
|
|
169
|
+
|
|
170
|
+
const dockSlot = hostLayout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
171
|
+
const closeButton = mount.querySelector<HTMLButtonElement>('[aria-label="Close chat"]');
|
|
172
|
+
|
|
173
|
+
expect(dockSlot?.style.width).toBe("420px");
|
|
174
|
+
expect(closeButton).not.toBeNull();
|
|
175
|
+
|
|
176
|
+
closeButton!.click();
|
|
177
|
+
|
|
178
|
+
expect(dockSlot?.style.width).toBe("0px");
|
|
179
|
+
|
|
180
|
+
openUnsub();
|
|
181
|
+
closeUnsub();
|
|
182
|
+
controller.destroy();
|
|
183
|
+
hostLayout.destroy();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("overlay reveal keeps width when closed; host-layout uses transform", () => {
|
|
54
187
|
const target = document.createElement("div");
|
|
55
188
|
document.body.appendChild(target);
|
|
56
189
|
|
|
@@ -61,7 +194,7 @@ describe("createAgentExperience docked mode", () => {
|
|
|
61
194
|
dock: {
|
|
62
195
|
side: "right",
|
|
63
196
|
width: "420px",
|
|
64
|
-
|
|
197
|
+
reveal: "overlay",
|
|
65
198
|
},
|
|
66
199
|
},
|
|
67
200
|
});
|
|
@@ -76,7 +209,7 @@ describe("createAgentExperience docked mode", () => {
|
|
|
76
209
|
dock: {
|
|
77
210
|
side: "right",
|
|
78
211
|
width: "420px",
|
|
79
|
-
|
|
212
|
+
reveal: "overlay",
|
|
80
213
|
},
|
|
81
214
|
},
|
|
82
215
|
});
|
|
@@ -94,7 +227,70 @@ describe("createAgentExperience docked mode", () => {
|
|
|
94
227
|
|
|
95
228
|
closeButton!.click();
|
|
96
229
|
|
|
97
|
-
expect(dockSlot?.style.width).toBe("
|
|
230
|
+
expect(dockSlot?.style.width).toBe("420px");
|
|
231
|
+
expect(dockSlot?.style.transform).toBe("translateX(100%)");
|
|
232
|
+
|
|
233
|
+
openUnsub();
|
|
234
|
+
closeUnsub();
|
|
235
|
+
controller.destroy();
|
|
236
|
+
hostLayout.destroy();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("push reveal translates the push-track when the header close button is clicked", () => {
|
|
240
|
+
const target = document.createElement("div");
|
|
241
|
+
document.body.appendChild(target);
|
|
242
|
+
|
|
243
|
+
const hostLayout = createWidgetHostLayout(target, {
|
|
244
|
+
launcher: {
|
|
245
|
+
mountMode: "docked",
|
|
246
|
+
autoExpand: true,
|
|
247
|
+
dock: {
|
|
248
|
+
side: "right",
|
|
249
|
+
width: "420px",
|
|
250
|
+
reveal: "push",
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
const shell = hostLayout.shell!;
|
|
255
|
+
Object.defineProperty(shell, "clientWidth", { get: () => 1000, configurable: true });
|
|
256
|
+
hostLayout.updateConfig({
|
|
257
|
+
launcher: {
|
|
258
|
+
mountMode: "docked",
|
|
259
|
+
autoExpand: true,
|
|
260
|
+
dock: { side: "right", width: "420px", reveal: "push" },
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const mount = document.createElement("div");
|
|
265
|
+
hostLayout.host.appendChild(mount);
|
|
266
|
+
|
|
267
|
+
const controller = createAgentExperience(mount, {
|
|
268
|
+
apiUrl: "https://api.example.com/chat",
|
|
269
|
+
launcher: {
|
|
270
|
+
mountMode: "docked",
|
|
271
|
+
autoExpand: true,
|
|
272
|
+
dock: {
|
|
273
|
+
side: "right",
|
|
274
|
+
width: "420px",
|
|
275
|
+
reveal: "push",
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const syncDockState = () => hostLayout.syncWidgetState(controller.getState());
|
|
281
|
+
const openUnsub = controller.on("widget:opened", syncDockState);
|
|
282
|
+
const closeUnsub = controller.on("widget:closed", syncDockState);
|
|
283
|
+
syncDockState();
|
|
284
|
+
|
|
285
|
+
const pushTrack = shell.querySelector<HTMLElement>('[data-persona-dock-role="push-track"]');
|
|
286
|
+
const closeButton = mount.querySelector<HTMLButtonElement>('[aria-label="Close chat"]');
|
|
287
|
+
|
|
288
|
+
expect(pushTrack).not.toBeNull();
|
|
289
|
+
expect(pushTrack?.style.transform).toBe("translateX(-420px)");
|
|
290
|
+
|
|
291
|
+
closeButton!.click();
|
|
292
|
+
|
|
293
|
+
expect(pushTrack?.style.transform).toBe("translateX(0)");
|
|
98
294
|
|
|
99
295
|
openUnsub();
|
|
100
296
|
closeUnsub();
|
package/src/ui.ts
CHANGED
|
@@ -27,15 +27,17 @@ import {
|
|
|
27
27
|
} from "./types";
|
|
28
28
|
import { AttachmentManager } from "./utils/attachment-manager";
|
|
29
29
|
import { createTextPart, ALL_SUPPORTED_MIME_TYPES } from "./utils/content";
|
|
30
|
-
import { applyThemeVariables, createThemeObserver } from "./utils/theme";
|
|
30
|
+
import { applyThemeVariables, createThemeObserver, getActiveTheme } from "./utils/theme";
|
|
31
|
+
import { resolveTokenValue } from "./utils/tokens";
|
|
31
32
|
import { renderLucideIcon } from "./utils/icons";
|
|
32
33
|
import { createElement, createElementInDocument } from "./utils/dom";
|
|
33
34
|
import { morphMessages } from "./utils/morph";
|
|
34
35
|
import { computeMessageFingerprint, createMessageCache, getCachedWrapper, setCachedWrapper, pruneCache } from "./utils/message-fingerprint";
|
|
35
36
|
import { statusCopy } from "./utils/constants";
|
|
36
|
-
import { isDockedMountMode } from "./utils/dock";
|
|
37
|
+
import { isDockedMountMode, resolveDockConfig } from "./utils/dock";
|
|
37
38
|
import { createLauncherButton } from "./components/launcher";
|
|
38
39
|
import { createWrapper, buildPanel, buildHeader, buildComposer, attachHeaderToContainer } from "./components/panel";
|
|
40
|
+
import { HEADER_THEME_CSS } from "./components/header-builder";
|
|
39
41
|
import { buildHeaderWithLayout } from "./components/header-layouts";
|
|
40
42
|
import { positionMap } from "./utils/positioning";
|
|
41
43
|
import type { HeaderElements as _HeaderElements, ComposerElements as _ComposerElements } from "./components/panel";
|
|
@@ -325,6 +327,16 @@ const buildPostprocessor = (
|
|
|
325
327
|
// Resolve sanitizer: enabled by default, can be disabled or replaced
|
|
326
328
|
const sanitize = resolveSanitizer(cfg?.sanitize);
|
|
327
329
|
|
|
330
|
+
// Warn developers when a custom postprocessor is used with the default sanitizer,
|
|
331
|
+
// since DOMPurify will strip any tags/attributes not in the allowlist.
|
|
332
|
+
if (cfg?.postprocessMessage && sanitize && cfg?.sanitize === undefined) {
|
|
333
|
+
console.warn(
|
|
334
|
+
"[Persona] A custom postprocessMessage is active with the default HTML sanitizer. " +
|
|
335
|
+
"Tags or attributes not in the built-in allowlist will be stripped. " +
|
|
336
|
+
"To keep custom HTML, set `sanitize: false` or provide a custom sanitize function."
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
328
340
|
return (context) => {
|
|
329
341
|
let nextText = context.text ?? "";
|
|
330
342
|
const rawPayload = context.message.rawContent ?? null;
|
|
@@ -1392,6 +1404,7 @@ export const createAgentExperience = (
|
|
|
1392
1404
|
if (!launcherEnabled || !artifactPaneApi) return;
|
|
1393
1405
|
const sidebarMode = config.launcher?.sidebarMode ?? false;
|
|
1394
1406
|
if (sidebarMode) return;
|
|
1407
|
+
if (isDockedMountMode(config) && resolveDockConfig(config).reveal === "emerge") return;
|
|
1395
1408
|
const ownerWindow = mount.ownerDocument.defaultView ?? window;
|
|
1396
1409
|
const mobileFullscreen = config.launcher?.mobileFullscreen ?? true;
|
|
1397
1410
|
const mobileBreakpoint = config.launcher?.mobileBreakpoint ?? 640;
|
|
@@ -1432,7 +1445,12 @@ export const createAgentExperience = (
|
|
|
1432
1445
|
const fullHeight = dockedMode || sidebarMode || (config.launcher?.fullHeight ?? false);
|
|
1433
1446
|
/** Script-tag / div embed: launcher off, host supplies a sized mount. */
|
|
1434
1447
|
const isInlineEmbed = config.launcher?.enabled === false;
|
|
1435
|
-
const
|
|
1448
|
+
const panelPartial = config.theme?.components?.panel;
|
|
1449
|
+
const activeTheme = getActiveTheme(config);
|
|
1450
|
+
const resolvePanelChrome = (raw: string | undefined, fallback: string): string => {
|
|
1451
|
+
if (raw == null || raw === "") return fallback;
|
|
1452
|
+
return resolveTokenValue(activeTheme, raw) ?? raw;
|
|
1453
|
+
};
|
|
1436
1454
|
|
|
1437
1455
|
// Mobile fullscreen detection
|
|
1438
1456
|
// Use mount's ownerDocument window to get correct viewport width when widget is inside an iframe
|
|
@@ -1447,20 +1465,25 @@ export const createAgentExperience = (
|
|
|
1447
1465
|
const isLeftSidebar = position === 'bottom-left' || position === 'top-left';
|
|
1448
1466
|
|
|
1449
1467
|
// Default values based on mode
|
|
1450
|
-
|
|
1451
|
-
|
|
1468
|
+
let defaultPanelBorder = (sidebarMode || shouldGoFullscreen) ? 'none' : '1px solid var(--persona-border)';
|
|
1469
|
+
let defaultPanelShadow = shouldGoFullscreen
|
|
1452
1470
|
? 'none'
|
|
1453
1471
|
: sidebarMode
|
|
1454
1472
|
? (isLeftSidebar ? 'var(--persona-palette-shadows-sidebar-left, 2px 0 12px rgba(0, 0, 0, 0.08))' : 'var(--persona-palette-shadows-sidebar-right, -2px 0 12px rgba(0, 0, 0, 0.08))')
|
|
1455
1473
|
: 'var(--persona-palette-shadows-xl, 0 25px 50px -12px rgba(0, 0, 0, 0.25))';
|
|
1474
|
+
|
|
1475
|
+
if (dockedMode && !shouldGoFullscreen) {
|
|
1476
|
+
defaultPanelShadow = 'none';
|
|
1477
|
+
defaultPanelBorder = 'none';
|
|
1478
|
+
}
|
|
1456
1479
|
const defaultPanelBorderRadius = (sidebarMode || shouldGoFullscreen)
|
|
1457
1480
|
? '0'
|
|
1458
1481
|
: 'var(--persona-panel-radius, var(--persona-radius-xl, 0.75rem))';
|
|
1459
1482
|
|
|
1460
|
-
// Apply theme overrides or defaults
|
|
1461
|
-
const panelBorder =
|
|
1462
|
-
const panelShadow =
|
|
1463
|
-
const panelBorderRadius =
|
|
1483
|
+
// Apply theme overrides or defaults (components.panel.*)
|
|
1484
|
+
const panelBorder = resolvePanelChrome(panelPartial?.border, defaultPanelBorder);
|
|
1485
|
+
const panelShadow = resolvePanelChrome(panelPartial?.shadow, defaultPanelShadow);
|
|
1486
|
+
const panelBorderRadius = resolvePanelChrome(panelPartial?.borderRadius, defaultPanelBorderRadius);
|
|
1464
1487
|
|
|
1465
1488
|
// Reset all inline styles first to handle mode toggling
|
|
1466
1489
|
// This ensures styles don't persist when switching between modes
|
|
@@ -1547,8 +1570,15 @@ export const createAgentExperience = (
|
|
|
1547
1570
|
panel.style.maxWidth = width;
|
|
1548
1571
|
}
|
|
1549
1572
|
} else if (dockedMode) {
|
|
1550
|
-
|
|
1551
|
-
|
|
1573
|
+
const dockReveal = resolveDockConfig(config).reveal;
|
|
1574
|
+
if (dockReveal === "emerge") {
|
|
1575
|
+
const dw = resolveDockConfig(config).width;
|
|
1576
|
+
panel.style.width = dw;
|
|
1577
|
+
panel.style.maxWidth = dw;
|
|
1578
|
+
} else {
|
|
1579
|
+
panel.style.width = "100%";
|
|
1580
|
+
panel.style.maxWidth = "100%";
|
|
1581
|
+
}
|
|
1552
1582
|
}
|
|
1553
1583
|
applyLauncherArtifactPanelWidth();
|
|
1554
1584
|
|
|
@@ -1561,6 +1591,16 @@ export const createAgentExperience = (
|
|
|
1561
1591
|
container.style.border = panelBorder;
|
|
1562
1592
|
container.style.borderRadius = panelBorderRadius;
|
|
1563
1593
|
|
|
1594
|
+
if (dockedMode && !shouldGoFullscreen && panelPartial?.border === undefined) {
|
|
1595
|
+
container.style.border = 'none';
|
|
1596
|
+
const dockSide = resolveDockConfig(config).side;
|
|
1597
|
+
if (dockSide === 'right') {
|
|
1598
|
+
container.style.borderLeft = '1px solid var(--persona-border)';
|
|
1599
|
+
} else {
|
|
1600
|
+
container.style.borderRight = '1px solid var(--persona-border)';
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1564
1604
|
if (fullHeight) {
|
|
1565
1605
|
// Mount container
|
|
1566
1606
|
mount.style.display = 'flex';
|
|
@@ -2422,7 +2462,19 @@ export const createAgentExperience = (
|
|
|
2422
2462
|
const updateOpenState = () => {
|
|
2423
2463
|
if (!launcherEnabled) return;
|
|
2424
2464
|
const dockedMode = isDockedMountMode(config);
|
|
2465
|
+
const ownerWindow = mount.ownerDocument.defaultView ?? window;
|
|
2466
|
+
const mobileBreakpoint = config.launcher?.mobileBreakpoint ?? 640;
|
|
2467
|
+
const mobileFullscreen = config.launcher?.mobileFullscreen ?? true;
|
|
2468
|
+
const isMobileViewport = ownerWindow.innerWidth <= mobileBreakpoint;
|
|
2469
|
+
const shouldGoFullscreen = mobileFullscreen && isMobileViewport && launcherEnabled;
|
|
2470
|
+
const dockReveal = resolveDockConfig(config).reveal;
|
|
2471
|
+
const dockRevealUsesTransform =
|
|
2472
|
+
dockedMode && (dockReveal === "overlay" || dockReveal === "push") && !shouldGoFullscreen;
|
|
2473
|
+
|
|
2425
2474
|
if (open) {
|
|
2475
|
+
// Clear any display:none !important from a closed docked state so mobile fullscreen
|
|
2476
|
+
// (display:flex !important) and dock layout can apply in recalcPanelHeight.
|
|
2477
|
+
wrapper.style.removeProperty("display");
|
|
2426
2478
|
wrapper.style.display = dockedMode ? "flex" : "";
|
|
2427
2479
|
wrapper.classList.remove("persona-pointer-events-none", "persona-opacity-0");
|
|
2428
2480
|
panel.classList.remove("persona-scale-95", "persona-opacity-0");
|
|
@@ -2435,20 +2487,29 @@ export const createAgentExperience = (
|
|
|
2435
2487
|
}
|
|
2436
2488
|
} else {
|
|
2437
2489
|
if (dockedMode) {
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2490
|
+
if (dockRevealUsesTransform) {
|
|
2491
|
+
// Slide/push reveal: keep the panel painted so host-layout `transform` can animate.
|
|
2492
|
+
wrapper.style.removeProperty("display");
|
|
2493
|
+
wrapper.style.display = "flex";
|
|
2494
|
+
wrapper.classList.remove("persona-pointer-events-none", "persona-opacity-0");
|
|
2495
|
+
panel.classList.remove("persona-scale-100", "persona-opacity-100", "persona-scale-95", "persona-opacity-0");
|
|
2496
|
+
} else {
|
|
2497
|
+
// Must beat applyFullHeightStyles() mobile shell: display:flex !important on wrapper
|
|
2498
|
+
wrapper.style.setProperty("display", "none", "important");
|
|
2499
|
+
wrapper.classList.remove("persona-pointer-events-none", "persona-opacity-0");
|
|
2500
|
+
panel.classList.remove("persona-scale-100", "persona-opacity-100", "persona-scale-95", "persona-opacity-0");
|
|
2501
|
+
}
|
|
2441
2502
|
} else {
|
|
2442
2503
|
wrapper.style.display = "";
|
|
2443
2504
|
wrapper.classList.add("persona-pointer-events-none", "persona-opacity-0");
|
|
2444
2505
|
panel.classList.remove("persona-scale-100", "persona-opacity-100");
|
|
2445
2506
|
panel.classList.add("persona-scale-95", "persona-opacity-0");
|
|
2446
2507
|
}
|
|
2447
|
-
// Show launcher
|
|
2508
|
+
// Show launcher when closed, except docked mode (0px column — use controller.open()).
|
|
2448
2509
|
if (launcherButtonInstance) {
|
|
2449
|
-
launcherButtonInstance.element.style.display = "";
|
|
2510
|
+
launcherButtonInstance.element.style.display = dockedMode ? "none" : "";
|
|
2450
2511
|
} else if (customLauncherElement) {
|
|
2451
|
-
customLauncherElement.style.display = "";
|
|
2512
|
+
customLauncherElement.style.display = dockedMode ? "none" : "";
|
|
2452
2513
|
}
|
|
2453
2514
|
}
|
|
2454
2515
|
};
|
|
@@ -2534,24 +2595,9 @@ export const createAgentExperience = (
|
|
|
2534
2595
|
sendButton.textContent = config.copy?.sendButtonLabel ?? "Send";
|
|
2535
2596
|
}
|
|
2536
2597
|
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
const getFontFamilyValue = (family: "sans-serif" | "serif" | "mono"): string => {
|
|
2542
|
-
switch (family) {
|
|
2543
|
-
case "serif":
|
|
2544
|
-
return 'Georgia, "Times New Roman", Times, serif';
|
|
2545
|
-
case "mono":
|
|
2546
|
-
return '"Courier New", Courier, "Lucida Console", Monaco, monospace';
|
|
2547
|
-
case "sans-serif":
|
|
2548
|
-
default:
|
|
2549
|
-
return '-apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif';
|
|
2550
|
-
}
|
|
2551
|
-
};
|
|
2552
|
-
|
|
2553
|
-
textarea.style.fontFamily = getFontFamilyValue(fontFamily);
|
|
2554
|
-
textarea.style.fontWeight = fontWeight;
|
|
2598
|
+
textarea.style.fontFamily =
|
|
2599
|
+
'var(--persona-input-font-family, var(--persona-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif))';
|
|
2600
|
+
textarea.style.fontWeight = "var(--persona-input-font-weight, var(--persona-font-weight, 400))";
|
|
2555
2601
|
};
|
|
2556
2602
|
|
|
2557
2603
|
// Add session ID persistence callbacks for client token mode
|
|
@@ -3373,43 +3419,49 @@ export const createAgentExperience = (
|
|
|
3373
3419
|
const isMobileViewport = ownerWindow.innerWidth <= mobileBreakpoint;
|
|
3374
3420
|
const shouldGoFullscreen = mobileFullscreen && isMobileViewport && launcherEnabled;
|
|
3375
3421
|
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
// Exiting mobile fullscreen (e.g., orientation change to landscape) — reset all styles
|
|
3383
|
-
if (wasMobileFullscreen) {
|
|
3384
|
-
wasMobileFullscreen = false;
|
|
3385
|
-
applyFullHeightStyles();
|
|
3386
|
-
applyThemeVariables(mount, config);
|
|
3387
|
-
}
|
|
3422
|
+
try {
|
|
3423
|
+
if (shouldGoFullscreen) {
|
|
3424
|
+
applyFullHeightStyles();
|
|
3425
|
+
applyThemeVariables(mount, config);
|
|
3426
|
+
return;
|
|
3427
|
+
}
|
|
3388
3428
|
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3429
|
+
// Exiting mobile fullscreen (e.g., orientation change to landscape) — reset all styles
|
|
3430
|
+
if (wasMobileFullscreen) {
|
|
3431
|
+
wasMobileFullscreen = false;
|
|
3432
|
+
applyFullHeightStyles();
|
|
3433
|
+
applyThemeVariables(mount, config);
|
|
3434
|
+
}
|
|
3394
3435
|
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
panel.style.maxWidth = width;
|
|
3401
|
-
}
|
|
3402
|
-
applyLauncherArtifactPanelWidth();
|
|
3436
|
+
if (!launcherEnabled && !dockedMode) {
|
|
3437
|
+
panel.style.height = "";
|
|
3438
|
+
panel.style.width = "";
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3403
3441
|
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3442
|
+
// In sidebar/fullHeight mode, don't override the width - it's handled by applyFullHeightStyles
|
|
3443
|
+
if (!sidebarMode && !dockedMode) {
|
|
3444
|
+
const launcherWidth = config?.launcher?.width ?? config?.launcherWidth;
|
|
3445
|
+
const width = launcherWidth ?? "min(400px, calc(100vw - 24px))";
|
|
3446
|
+
panel.style.width = width;
|
|
3447
|
+
panel.style.maxWidth = width;
|
|
3448
|
+
}
|
|
3449
|
+
applyLauncherArtifactPanelWidth();
|
|
3450
|
+
|
|
3451
|
+
// In fullHeight mode, don't set a fixed height
|
|
3452
|
+
if (!fullHeight) {
|
|
3453
|
+
const viewportHeight = ownerWindow.innerHeight;
|
|
3454
|
+
const verticalMargin = 64; // leave space for launcher's offset
|
|
3455
|
+
const heightOffset = config.launcher?.heightOffset ?? 0;
|
|
3456
|
+
const available = Math.max(200, viewportHeight - verticalMargin);
|
|
3457
|
+
const clamped = Math.min(640, available);
|
|
3458
|
+
const finalHeight = Math.max(200, clamped - heightOffset);
|
|
3459
|
+
panel.style.height = `${finalHeight}px`;
|
|
3460
|
+
}
|
|
3461
|
+
} finally {
|
|
3462
|
+
// applyFullHeightStyles() assigns wrapper.style.cssText (e.g. display:flex !important), which
|
|
3463
|
+
// overwrites updateOpenState()'s display:none when docked+closed. Re-sync after every recalc.
|
|
3464
|
+
updateOpenState();
|
|
3413
3465
|
}
|
|
3414
3466
|
};
|
|
3415
3467
|
|
|
@@ -3949,14 +4001,9 @@ export const createAgentExperience = (
|
|
|
3949
4001
|
}
|
|
3950
4002
|
}
|
|
3951
4003
|
|
|
3952
|
-
//
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
closeButton.classList.remove("persona-text-persona-muted");
|
|
3956
|
-
} else {
|
|
3957
|
-
closeButton.style.color = "";
|
|
3958
|
-
closeButton.classList.add("persona-text-persona-muted");
|
|
3959
|
-
}
|
|
4004
|
+
// Close icon: launcher color wins; else theme.components.header.actionIconForeground
|
|
4005
|
+
closeButton.style.color =
|
|
4006
|
+
launcher.closeButtonColor || HEADER_THEME_CSS.actionIconColor;
|
|
3960
4007
|
|
|
3961
4008
|
if (launcher.closeButtonBackgroundColor) {
|
|
3962
4009
|
closeButton.style.backgroundColor = launcher.closeButtonBackgroundColor;
|
|
@@ -4007,7 +4054,7 @@ export const createAgentExperience = (
|
|
|
4007
4054
|
|
|
4008
4055
|
// Clear existing content and render new icon
|
|
4009
4056
|
closeButton.innerHTML = "";
|
|
4010
|
-
const iconSvg = renderLucideIcon(closeButtonIconName, "20px",
|
|
4057
|
+
const iconSvg = renderLucideIcon(closeButtonIconName, "20px", "currentColor", 2);
|
|
4011
4058
|
if (iconSvg) {
|
|
4012
4059
|
closeButton.appendChild(iconSvg);
|
|
4013
4060
|
} else {
|
|
@@ -4174,22 +4221,16 @@ export const createAgentExperience = (
|
|
|
4174
4221
|
const clearChatIconName = clearChatConfig.iconName ?? "refresh-cw";
|
|
4175
4222
|
const clearChatIconColor = clearChatConfig.iconColor ?? "";
|
|
4176
4223
|
|
|
4224
|
+
clearChatButton.style.color =
|
|
4225
|
+
clearChatIconColor || HEADER_THEME_CSS.actionIconColor;
|
|
4226
|
+
|
|
4177
4227
|
// Clear existing icon and render new one
|
|
4178
4228
|
clearChatButton.innerHTML = "";
|
|
4179
|
-
const iconSvg = renderLucideIcon(clearChatIconName, "20px",
|
|
4229
|
+
const iconSvg = renderLucideIcon(clearChatIconName, "20px", "currentColor", 2);
|
|
4180
4230
|
if (iconSvg) {
|
|
4181
4231
|
clearChatButton.appendChild(iconSvg);
|
|
4182
4232
|
}
|
|
4183
4233
|
|
|
4184
|
-
// Update icon color
|
|
4185
|
-
if (clearChatIconColor) {
|
|
4186
|
-
clearChatButton.style.color = clearChatIconColor;
|
|
4187
|
-
clearChatButton.classList.remove("persona-text-persona-muted");
|
|
4188
|
-
} else {
|
|
4189
|
-
clearChatButton.style.color = "";
|
|
4190
|
-
clearChatButton.classList.add("persona-text-persona-muted");
|
|
4191
|
-
}
|
|
4192
|
-
|
|
4193
4234
|
// Update background color
|
|
4194
4235
|
if (clearChatConfig.backgroundColor) {
|
|
4195
4236
|
clearChatButton.style.backgroundColor = clearChatConfig.backgroundColor;
|