@runtypelabs/persona 4.23.1 → 4.24.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/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-9Vxm-PPz.d.cts → types-Db61aI9J.d.cts} +25 -2
- package/dist/animations/{types-9Vxm-PPz.d.ts → types-Db61aI9J.d.ts} +25 -2
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/animations-extra.d.cts +25 -2
- package/dist/animations-extra.d.ts +25 -2
- package/dist/approval-ui-3NMYNXBD.js +1 -0
- package/dist/approval-ui.cjs +2 -2
- package/dist/approval-ui.d.cts +261 -40
- package/dist/approval-ui.d.ts +261 -40
- package/dist/approval-ui.js +2 -2
- package/dist/{artifacts-ui-F4N6VMKF.js → artifacts-ui-IGB5YKLO.js} +4 -4
- package/dist/artifacts-ui.d.cts +256 -28
- package/dist/artifacts-ui.d.ts +256 -28
- package/dist/{chunk-NIKYKSNZ.js → chunk-7CO4T5KB.js} +1 -1
- package/dist/codegen.cjs +11 -11
- package/dist/codegen.js +13 -13
- package/dist/context-mentions.d.cts +256 -28
- package/dist/context-mentions.d.ts +256 -28
- package/dist/event-stream-view.d.cts +256 -28
- package/dist/event-stream-view.d.ts +256 -28
- package/dist/forms-ui.d.cts +291 -30
- package/dist/forms-ui.d.ts +291 -30
- package/dist/history-view.d.cts +25 -2
- package/dist/history-view.d.ts +25 -2
- package/dist/index.cjs +70 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +337 -51
- package/dist/index.d.ts +337 -51
- package/dist/index.global.js +57 -54
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +70 -69
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/session-reconnect.d.cts +256 -28
- package/dist/session-reconnect.d.ts +256 -28
- package/dist/smart-dom-reader.d.cts +256 -28
- package/dist/smart-dom-reader.d.ts +256 -28
- package/dist/theme-editor-preview.cjs +74 -73
- package/dist/theme-editor-preview.d.cts +263 -28
- package/dist/theme-editor-preview.d.ts +263 -28
- package/dist/theme-editor-preview.js +51 -50
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +276 -30
- package/dist/theme-editor.d.ts +276 -30
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +1522 -11
- package/dist/theme-reference.d.ts +1522 -11
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +3 -2
- package/src/__fixtures__/defaults-parity.v4.json +776 -0
- package/src/approval-ui-entry.ts +0 -1
- package/src/approval-ui-loader.ts +5 -4
- package/src/approval-ui.ts +0 -1
- package/src/client-identity-proof.test.ts +193 -0
- package/src/client-session-warm.test.ts +216 -0
- package/src/client-visitor-history.test.ts +6 -0
- package/src/client.tool-pair-replay.test.ts +325 -0
- package/src/client.ts +154 -61
- package/src/codegen.test.ts +24 -0
- package/src/components/activity-row.test.ts +100 -0
- package/src/components/activity-row.ts +136 -0
- package/src/components/approval-actions.test.ts +8 -5
- package/src/components/approval-actions.ts +19 -6
- package/src/components/approval-bubble.test.ts +56 -9
- package/src/components/approval-bubble.ts +36 -41
- package/src/components/composer-builder.ts +5 -2
- package/src/components/composer-parts.ts +10 -2
- package/src/components/header-builder.test.ts +32 -0
- package/src/components/header-builder.ts +7 -4
- package/src/components/header-layouts.test.ts +12 -0
- package/src/components/header-layouts.ts +3 -2
- package/src/components/launcher.ts +11 -2
- package/src/components/message-bubble.test.ts +22 -0
- package/src/components/message-bubble.ts +7 -3
- package/src/components/panel.test.ts +52 -1
- package/src/components/panel.ts +47 -8
- package/src/components/pill-composer-builder.test.ts +2 -0
- package/src/components/pill-composer-builder.ts +0 -1
- package/src/components/reasoning-bubble.ts +29 -17
- package/src/components/suggestions.test.ts +1 -0
- package/src/components/suggestions.ts +0 -16
- package/src/components/tool-bubble.ts +131 -114
- package/src/components/tool-details.test.ts +87 -0
- package/src/components/tool-details.ts +102 -0
- package/src/components/welcome.test.ts +8 -0
- package/src/components/welcome.ts +4 -2
- package/src/defaults-parity.test.ts +158 -0
- package/src/defaults.test.ts +69 -0
- package/src/defaults.ts +128 -49
- package/src/generated/runtype-openapi-contract.ts +28 -13
- package/src/index-core.ts +2 -0
- package/src/install.test.ts +24 -0
- package/src/install.ts +3 -1
- package/src/launcher-global.ts +2 -1
- package/src/launcher-variants.test.ts +52 -0
- package/src/runtime/init-update-reset.test.ts +21 -0
- package/src/session.ts +61 -9
- package/src/styles/widget-styles.test.ts +19 -0
- package/src/styles/widget.css +295 -23
- package/src/theme-editor/index.ts +2 -0
- package/src/theme-editor/preview-utils.test.ts +8 -0
- package/src/theme-editor/preview-utils.ts +7 -3
- package/src/theme-editor/sections.test.ts +31 -0
- package/src/theme-editor/sections.ts +55 -1
- package/src/theme-editor/state-preferences.test.ts +49 -0
- package/src/theme-editor/state.ts +73 -15
- package/src/theme-reference.test.ts +20 -0
- package/src/theme-reference.ts +11 -5
- package/src/tool-call-display-defaults.test.ts +33 -1
- package/src/types/theme.ts +80 -3
- package/src/types.ts +179 -23
- package/src/ui.activity-row.test.ts +91 -0
- package/src/ui.approval-plugin.test.ts +63 -0
- package/src/ui.composer-compact.test.ts +24 -2
- package/src/ui.composer-placement.test.ts +23 -0
- package/src/ui.detached-panel.test.ts +33 -6
- package/src/ui.docked.test.ts +19 -0
- package/src/ui.header-update-stability.test.ts +25 -0
- package/src/ui.identity-proof.test.ts +58 -0
- package/src/ui.launcher-teaser.test.ts +25 -0
- package/src/ui.message-width.test.ts +17 -2
- package/src/ui.reasoning-display.test.ts +92 -0
- package/src/ui.rebuild-composer.test.ts +20 -3
- package/src/ui.scroll.test.ts +32 -0
- package/src/ui.session-init.test.ts +312 -0
- package/src/ui.status-indicator.test.ts +82 -0
- package/src/ui.suggest-replies.test.ts +11 -0
- package/src/ui.ts +363 -74
- package/src/ui.v5-defaults.test.ts +144 -0
- package/src/ui.welcome.test.ts +92 -3
- package/src/utils/__snapshots__/tokens-parity.test.ts.snap +811 -140
- package/src/utils/code-generators.ts +14 -0
- package/src/utils/composer-compact.test.ts +0 -2
- package/src/utils/composer-compact.ts +2 -4
- package/src/utils/composer-placement.ts +2 -3
- package/src/utils/config-merge.test.ts +81 -5
- package/src/utils/config-merge.ts +13 -3
- package/src/utils/defaults-provenance.ts +67 -0
- package/src/utils/dock.ts +7 -1
- package/src/utils/launcher-variant.ts +16 -0
- package/src/utils/panel-config.ts +97 -0
- package/src/utils/status-indicator.ts +39 -0
- package/src/utils/theme.test.ts +182 -0
- package/src/utils/theme.ts +131 -18
- package/src/utils/tokens.test.ts +122 -1
- package/src/utils/tokens.ts +177 -17
- package/src/utils/tool-pair-replay.ts +100 -0
- package/src/welcome.test.ts +23 -0
- package/src/welcome.ts +16 -9
- package/dist/approval-ui-K7R5Q5OC.js +0 -1
|
@@ -9,14 +9,6 @@ import { ChevronDown, ChevronUp, ShieldAlert, ShieldCheck, ShieldX } from "lucid
|
|
|
9
9
|
import { renderIconNode } from "../utils/icon-node";
|
|
10
10
|
import { WEBMCP_TOOL_PREFIX } from "../webmcp-bridge";
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Per-message expanded/collapsed state for the technical-details section.
|
|
14
|
-
* Absent means "use the config default" (`approval.detailsDisplay`). Lives at
|
|
15
|
-
* module scope so the choice survives idiomorph re-renders, mirroring
|
|
16
|
-
* `toolExpansionState` in tool-bubble.ts.
|
|
17
|
-
*/
|
|
18
|
-
export const approvalDetailsExpansionState = new Map<string, boolean>();
|
|
19
|
-
|
|
20
12
|
/**
|
|
21
13
|
* Turn a wire tool name into a user-facing label: strips the `webmcp:`
|
|
22
14
|
* prefix and splits snake_case / kebab-case / camelCase into a sentence
|
|
@@ -41,10 +33,11 @@ const resolveApprovalConfig = (config?: AgentWidgetConfig) =>
|
|
|
41
33
|
|
|
42
34
|
const isDetailsExpanded = (
|
|
43
35
|
messageId: string,
|
|
36
|
+
expansionState: Map<string, boolean>,
|
|
44
37
|
config?: AgentWidgetConfig
|
|
45
38
|
): boolean => {
|
|
46
39
|
const detailsMode = resolveApprovalConfig(config)?.detailsDisplay ?? "collapsed";
|
|
47
|
-
return
|
|
40
|
+
return expansionState.get(messageId) ?? detailsMode === "expanded";
|
|
48
41
|
};
|
|
49
42
|
|
|
50
43
|
const applyDetailsToggleState = (
|
|
@@ -72,18 +65,19 @@ const applyDetailsToggleState = (
|
|
|
72
65
|
|
|
73
66
|
/**
|
|
74
67
|
* Sync the technical-details section (toggle label/chevron + visibility) with
|
|
75
|
-
*
|
|
68
|
+
* the widget instance's expansion state. Called from the ui.ts expansion event
|
|
76
69
|
* delegation after a toggle click.
|
|
77
70
|
*/
|
|
78
71
|
export const updateApprovalDetailsUI = (
|
|
79
72
|
messageId: string,
|
|
80
73
|
bubble: HTMLElement,
|
|
81
|
-
config?: AgentWidgetConfig
|
|
74
|
+
config?: AgentWidgetConfig,
|
|
75
|
+
expansionState = new Map<string, boolean>()
|
|
82
76
|
): void => {
|
|
83
77
|
const toggle = bubble.querySelector('button[data-bubble-type="approval"]') as HTMLElement | null;
|
|
84
78
|
const details = bubble.querySelector("[data-approval-details]") as HTMLElement | null;
|
|
85
79
|
if (!toggle || !details) return;
|
|
86
|
-
const expanded = isDetailsExpanded(messageId, config);
|
|
80
|
+
const expanded = isDetailsExpanded(messageId, expansionState, config);
|
|
87
81
|
applyDetailsToggleState(toggle, expanded, config);
|
|
88
82
|
details.style.display = expanded ? "" : "none";
|
|
89
83
|
};
|
|
@@ -112,16 +106,16 @@ export const updateApprovalBubbleUI = (
|
|
|
112
106
|
// Update badge color
|
|
113
107
|
if (approval.status === "approved") {
|
|
114
108
|
statusBadge.className = "persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium";
|
|
115
|
-
statusBadge.style.backgroundColor = "var(--persona-palette-colors-success-100
|
|
116
|
-
statusBadge.style.color = "var(--persona-palette-colors-success-700
|
|
109
|
+
statusBadge.style.backgroundColor = "var(--persona-palette-colors-success-100)";
|
|
110
|
+
statusBadge.style.color = "var(--persona-palette-colors-success-700)";
|
|
117
111
|
} else if (approval.status === "denied") {
|
|
118
112
|
statusBadge.className = "persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium";
|
|
119
|
-
statusBadge.style.backgroundColor = "var(--persona-palette-colors-error-100
|
|
120
|
-
statusBadge.style.color = "var(--persona-palette-colors-error-700
|
|
113
|
+
statusBadge.style.backgroundColor = "var(--persona-palette-colors-error-100)";
|
|
114
|
+
statusBadge.style.color = "var(--persona-palette-colors-error-700)";
|
|
121
115
|
} else if (approval.status === "timeout") {
|
|
122
116
|
statusBadge.className = "persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium";
|
|
123
|
-
statusBadge.style.backgroundColor = "var(--persona-palette-colors-warning-100
|
|
124
|
-
statusBadge.style.color = "var(--persona-palette-colors-warning-700
|
|
117
|
+
statusBadge.style.backgroundColor = "var(--persona-palette-colors-warning-100)";
|
|
118
|
+
statusBadge.style.color = "var(--persona-palette-colors-warning-700)";
|
|
125
119
|
}
|
|
126
120
|
statusBadge.setAttribute("data-approval-status", approval.status);
|
|
127
121
|
}
|
|
@@ -133,9 +127,9 @@ export const updateApprovalBubbleUI = (
|
|
|
133
127
|
const iconData = approval.status === "denied" ? ShieldX
|
|
134
128
|
: approval.status === "timeout" ? ShieldAlert
|
|
135
129
|
: ShieldCheck;
|
|
136
|
-
const iconColor = approval.status === "approved" ? "var(--persona-feedback-success,
|
|
137
|
-
: approval.status === "denied" ? "var(--persona-feedback-error,
|
|
138
|
-
: approval.status === "timeout" ? "var(--persona-feedback-warning,
|
|
130
|
+
const iconColor = approval.status === "approved" ? "var(--persona-feedback-success, var(--persona-palette-colors-success-600))"
|
|
131
|
+
: approval.status === "denied" ? "var(--persona-feedback-error, var(--persona-palette-colors-error-600))"
|
|
132
|
+
: approval.status === "timeout" ? "var(--persona-feedback-warning, var(--persona-palette-colors-warning-600))"
|
|
139
133
|
: (approvalConfig?.titleColor ?? "currentColor");
|
|
140
134
|
const icon = renderIconNode(iconData, 20, iconColor, 2);
|
|
141
135
|
if (icon) {
|
|
@@ -155,7 +149,8 @@ export const updateApprovalBubbleUI = (
|
|
|
155
149
|
*/
|
|
156
150
|
export const createApprovalBubble = (
|
|
157
151
|
message: AgentWidgetMessage,
|
|
158
|
-
config?: AgentWidgetConfig
|
|
152
|
+
config?: AgentWidgetConfig,
|
|
153
|
+
expansionState = new Map<string, boolean>()
|
|
159
154
|
): HTMLElement => {
|
|
160
155
|
const approval = message.approval;
|
|
161
156
|
const approvalConfig = config?.approval !== false ? config?.approval : undefined;
|
|
@@ -178,12 +173,12 @@ export const createApprovalBubble = (
|
|
|
178
173
|
bubble.setAttribute("data-message-id", message.id);
|
|
179
174
|
|
|
180
175
|
// Apply styling: use semantic tokens with config overrides
|
|
181
|
-
bubble.style.backgroundColor = approvalConfig?.backgroundColor ?? "var(--persona-approval-bg
|
|
182
|
-
bubble.style.borderColor = approvalConfig?.borderColor ?? "var(--persona-approval-border
|
|
176
|
+
bubble.style.backgroundColor = approvalConfig?.backgroundColor ?? "var(--persona-approval-bg)";
|
|
177
|
+
bubble.style.borderColor = approvalConfig?.borderColor ?? "var(--persona-approval-border)";
|
|
183
178
|
bubble.style.boxShadow =
|
|
184
179
|
approvalConfig?.shadow !== undefined
|
|
185
180
|
? (approvalConfig.shadow.trim() === "" ? "none" : approvalConfig.shadow)
|
|
186
|
-
: "var(--persona-approval-shadow
|
|
181
|
+
: "var(--persona-approval-shadow)";
|
|
187
182
|
|
|
188
183
|
if (!approval) {
|
|
189
184
|
return bubble;
|
|
@@ -201,9 +196,9 @@ export const createApprovalBubble = (
|
|
|
201
196
|
const iconData = approval.status === "denied" ? ShieldX
|
|
202
197
|
: approval.status === "timeout" ? ShieldAlert
|
|
203
198
|
: ShieldCheck;
|
|
204
|
-
const iconColor = approval.status === "approved" ? "var(--persona-feedback-success,
|
|
205
|
-
: approval.status === "denied" ? "var(--persona-feedback-error,
|
|
206
|
-
: approval.status === "timeout" ? "var(--persona-feedback-warning,
|
|
199
|
+
const iconColor = approval.status === "approved" ? "var(--persona-feedback-success, var(--persona-palette-colors-success-600))"
|
|
200
|
+
: approval.status === "denied" ? "var(--persona-feedback-error, var(--persona-palette-colors-error-600))"
|
|
201
|
+
: approval.status === "timeout" ? "var(--persona-feedback-warning, var(--persona-palette-colors-warning-600))"
|
|
207
202
|
: (approvalConfig?.titleColor ?? "currentColor");
|
|
208
203
|
const icon = renderIconNode(iconData, 20, iconColor, 2);
|
|
209
204
|
if (icon) {
|
|
@@ -227,16 +222,16 @@ export const createApprovalBubble = (
|
|
|
227
222
|
const badge = createElement("span", "persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium");
|
|
228
223
|
badge.setAttribute("data-approval-status", approval.status);
|
|
229
224
|
if (approval.status === "approved") {
|
|
230
|
-
badge.style.backgroundColor = "var(--persona-palette-colors-success-100
|
|
231
|
-
badge.style.color = "var(--persona-palette-colors-success-700
|
|
225
|
+
badge.style.backgroundColor = "var(--persona-palette-colors-success-100)";
|
|
226
|
+
badge.style.color = "var(--persona-palette-colors-success-700)";
|
|
232
227
|
badge.textContent = "Approved";
|
|
233
228
|
} else if (approval.status === "denied") {
|
|
234
|
-
badge.style.backgroundColor = "var(--persona-palette-colors-error-100
|
|
235
|
-
badge.style.color = "var(--persona-palette-colors-error-700
|
|
229
|
+
badge.style.backgroundColor = "var(--persona-palette-colors-error-100)";
|
|
230
|
+
badge.style.color = "var(--persona-palette-colors-error-700)";
|
|
236
231
|
badge.textContent = "Denied";
|
|
237
232
|
} else if (approval.status === "timeout") {
|
|
238
|
-
badge.style.backgroundColor = "var(--persona-palette-colors-warning-100
|
|
239
|
-
badge.style.color = "var(--persona-palette-colors-warning-700
|
|
233
|
+
badge.style.backgroundColor = "var(--persona-palette-colors-warning-100)";
|
|
234
|
+
badge.style.color = "var(--persona-palette-colors-warning-700)";
|
|
240
235
|
badge.textContent = "Timeout";
|
|
241
236
|
}
|
|
242
237
|
titleRow.appendChild(badge);
|
|
@@ -303,7 +298,7 @@ export const createApprovalBubble = (
|
|
|
303
298
|
const showDescriptionInDetails = Boolean(approval.description) && !summaryFallsBackToDescription;
|
|
304
299
|
const hasDetails = showDescriptionInDetails || Boolean(approval.parameters);
|
|
305
300
|
if (detailsMode !== "hidden" && hasDetails) {
|
|
306
|
-
const expanded = isDetailsExpanded(message.id, config);
|
|
301
|
+
const expanded = isDetailsExpanded(message.id, expansionState, config);
|
|
307
302
|
|
|
308
303
|
const toggle = createElement(
|
|
309
304
|
"button",
|
|
@@ -364,10 +359,10 @@ export const createApprovalBubble = (
|
|
|
364
359
|
// Approve button
|
|
365
360
|
const approveBtn = createElement("button", "persona-inline-flex persona-items-center persona-px-3 persona-py-1.5 persona-rounded-md persona-text-xs persona-font-medium persona-border-none persona-cursor-pointer") as HTMLButtonElement;
|
|
366
361
|
approveBtn.type = "button";
|
|
367
|
-
approveBtn.style.backgroundColor = approvalConfig?.approveButtonColor ?? "var(--persona-approval-approve-bg
|
|
368
|
-
approveBtn.style.color = approvalConfig?.approveButtonTextColor ?? "
|
|
362
|
+
approveBtn.style.backgroundColor = approvalConfig?.approveButtonColor ?? "var(--persona-approval-approve-bg)";
|
|
363
|
+
approveBtn.style.color = approvalConfig?.approveButtonTextColor ?? "var(--persona-button-primary-fg)";
|
|
369
364
|
approveBtn.setAttribute("data-approval-action", "approve");
|
|
370
|
-
const approveIcon = renderIconNode(ShieldCheck, 14, approvalConfig?.approveButtonTextColor ?? "
|
|
365
|
+
const approveIcon = renderIconNode(ShieldCheck, 14, approvalConfig?.approveButtonTextColor ?? "var(--persona-button-primary-fg)", 2);
|
|
371
366
|
if (approveIcon) {
|
|
372
367
|
approveIcon.style.marginRight = "4px";
|
|
373
368
|
approveBtn.appendChild(approveIcon);
|
|
@@ -379,10 +374,10 @@ export const createApprovalBubble = (
|
|
|
379
374
|
const denyBtn = createElement("button", "persona-inline-flex persona-items-center persona-px-3 persona-py-1.5 persona-rounded-md persona-text-xs persona-font-medium persona-cursor-pointer") as HTMLButtonElement;
|
|
380
375
|
denyBtn.type = "button";
|
|
381
376
|
denyBtn.style.backgroundColor = approvalConfig?.denyButtonColor ?? "transparent";
|
|
382
|
-
denyBtn.style.color = approvalConfig?.denyButtonTextColor ?? "var(--persona-feedback-error,
|
|
383
|
-
denyBtn.style.border = `1px solid ${approvalConfig?.denyButtonTextColor ? approvalConfig.denyButtonTextColor : "var(--persona-palette-colors-error-200
|
|
377
|
+
denyBtn.style.color = approvalConfig?.denyButtonTextColor ?? "var(--persona-feedback-error, var(--persona-palette-colors-error-600))";
|
|
378
|
+
denyBtn.style.border = `1px solid ${approvalConfig?.denyButtonTextColor ? approvalConfig.denyButtonTextColor : "var(--persona-palette-colors-error-200)"}`;
|
|
384
379
|
denyBtn.setAttribute("data-approval-action", "deny");
|
|
385
|
-
const denyIcon = renderIconNode(ShieldX, 14, approvalConfig?.denyButtonTextColor ?? "var(--persona-feedback-error,
|
|
380
|
+
const denyIcon = renderIconNode(ShieldX, 14, approvalConfig?.denyButtonTextColor ?? "var(--persona-feedback-error, var(--persona-palette-colors-error-600))", 2);
|
|
386
381
|
if (denyIcon) {
|
|
387
382
|
denyIcon.style.marginRight = "4px";
|
|
388
383
|
denyBtn.appendChild(denyIcon);
|
|
@@ -75,7 +75,6 @@ export const buildComposer = (context: ComposerBuildContext): ComposerElements =
|
|
|
75
75
|
className:
|
|
76
76
|
"persona-widget-composer persona-flex persona-flex-col persona-gap-2 persona-rounded-2xl persona-border persona-border-persona-border persona-bg-persona-input-background persona-px-4 persona-py-3",
|
|
77
77
|
attrs: { "data-persona-composer-form": "" },
|
|
78
|
-
style: { outline: "none" },
|
|
79
78
|
});
|
|
80
79
|
|
|
81
80
|
const { textarea, attachAutoResize } = createComposerTextarea(config);
|
|
@@ -144,7 +143,11 @@ export const buildComposer = (context: ComposerBuildContext): ComposerElements =
|
|
|
144
143
|
}
|
|
145
144
|
});
|
|
146
145
|
|
|
147
|
-
|
|
146
|
+
if (config?.statusIndicator?.mode === "transient") {
|
|
147
|
+
footer.append(suggestions, statusText, composerForm);
|
|
148
|
+
} else {
|
|
149
|
+
footer.append(suggestions, composerForm, statusText);
|
|
150
|
+
}
|
|
148
151
|
|
|
149
152
|
return {
|
|
150
153
|
footer,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { applyStatusIndicatorState } from "../utils/status-indicator";
|
|
1
2
|
import { createElement, createNode, cx } from "../utils/dom";
|
|
2
3
|
import { renderLucideIcon } from "../utils/icons";
|
|
3
4
|
import { AgentWidgetConfig } from "../types";
|
|
@@ -293,6 +294,14 @@ export const createSendButton = (config?: AgentWidgetConfig): SendButtonParts =>
|
|
|
293
294
|
sendIcon = renderLucideIcon(iconName, iconSize, iconColor, iconStroke);
|
|
294
295
|
}
|
|
295
296
|
stopIcon = renderLucideIcon(stopIconName, iconSize, iconColor, iconStroke);
|
|
297
|
+
// Keep explicit per-control sizing authoritative; otherwise use the theme.
|
|
298
|
+
if (!sendButtonConfig.iconSize) {
|
|
299
|
+
for (const icon of [sendIcon, stopIcon]) {
|
|
300
|
+
if (!icon) continue;
|
|
301
|
+
icon.style.width = `var(--persona-components-composer-sendIconSize, ${iconSize}px)`;
|
|
302
|
+
icon.style.height = `var(--persona-components-composer-sendIconSize, ${iconSize}px)`;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
296
305
|
|
|
297
306
|
if (sendIcon && stopIcon) {
|
|
298
307
|
// One grid cell, both glyphs in it. `data-mode` on the stack decides
|
|
@@ -538,8 +547,7 @@ export const createStatusText = (config?: AgentWidgetConfig): HTMLElement => {
|
|
|
538
547
|
);
|
|
539
548
|
statusText.setAttribute("data-persona-composer-status", "");
|
|
540
549
|
|
|
541
|
-
|
|
542
|
-
statusText.style.display = isVisible ? "" : "none";
|
|
550
|
+
applyStatusIndicatorState(statusText, statusConfig, "idle");
|
|
543
551
|
const idleLabel = statusConfig.idleText ?? "Online";
|
|
544
552
|
if (statusConfig.idleLink) {
|
|
545
553
|
const link = createElement("a") as HTMLAnchorElement;
|
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_HEADER_ICON_SIZE,
|
|
6
|
+
DEFAULT_LAUNCHER_CONFIG,
|
|
7
|
+
} from "../defaults";
|
|
4
8
|
import { buildHeader } from "./header-builder";
|
|
5
9
|
|
|
6
10
|
describe("buildHeader tooltips", () => {
|
|
11
|
+
it.each([{ v5Defaults: false }, { v5Defaults: true }])(
|
|
12
|
+
"uses the standard padding token under v5Defaults=%s",
|
|
13
|
+
(future) => {
|
|
14
|
+
const { header } = buildHeader({ config: { future } as any });
|
|
15
|
+
expect(header.style.padding).toBe(
|
|
16
|
+
"var(--persona-components-header-padding, 20px 24px)"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
it.each([false, true])("preserves legacy numeric glyph sizing for non-pixel icon boxes (v5=%s)", (v5Defaults) => {
|
|
21
|
+
const { iconHolder } = buildHeader({ config: {
|
|
22
|
+
apiUrl: "/dispatch", future: { v5Defaults },
|
|
23
|
+
launcher: { headerIconSize: "2rem", headerIconName: "bot" },
|
|
24
|
+
} });
|
|
25
|
+
const glyph = iconHolder.querySelector("svg")!;
|
|
26
|
+
expect(iconHolder.style.width).toBe("2rem");
|
|
27
|
+
expect(glyph.getAttribute("width")).toBe("1.2");
|
|
28
|
+
expect(glyph.style.width).toBe("calc(2px * var(--persona-components-header-iconScale, 0.6))");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("uses the shared launcher icon default", () => {
|
|
32
|
+
const { iconHolder } = buildHeader({});
|
|
33
|
+
|
|
34
|
+
expect(DEFAULT_LAUNCHER_CONFIG.headerIconSize).toBe(DEFAULT_HEADER_ICON_SIZE);
|
|
35
|
+
expect(iconHolder.style.height).toBe(DEFAULT_HEADER_ICON_SIZE);
|
|
36
|
+
expect(iconHolder.style.width).toBe(DEFAULT_HEADER_ICON_SIZE);
|
|
37
|
+
});
|
|
38
|
+
|
|
7
39
|
it("portals the close-button tooltip into the mounted document", () => {
|
|
8
40
|
const iframeDocument = document.implementation.createHTMLDocument("preview");
|
|
9
41
|
const { header, closeButton, closeButtonWrapper } = buildHeader({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createElement, createNode } from "../utils/dom";
|
|
2
2
|
import { renderLucideIcon } from "../utils/icons";
|
|
3
3
|
import { AgentWidgetConfig } from "../types";
|
|
4
|
+
import { DEFAULT_HEADER_ICON_SIZE } from "../defaults";
|
|
4
5
|
import { createCloseButton, createClearChatButton } from "./header-parts";
|
|
5
6
|
|
|
6
7
|
/** CSS `color` values; variables are set on `[data-persona-root]` from `theme.components.header`. */
|
|
@@ -10,7 +11,7 @@ export const HEADER_THEME_CSS = {
|
|
|
10
11
|
subtitleColor:
|
|
11
12
|
"var(--persona-header-subtitle-fg, var(--persona-text-muted, var(--persona-muted, #9ca3af)))",
|
|
12
13
|
actionIconColor:
|
|
13
|
-
"var(--persona-header-action-icon-fg, var(--persona-muted, #9ca3af))",
|
|
14
|
+
"var(--persona-header-control-state-fg, var(--persona-header-action-icon-fg, var(--persona-muted, #9ca3af)))",
|
|
14
15
|
/** One knob for both header builders; the Messages rail header shares it. */
|
|
15
16
|
minHeight: "var(--persona-header-min-height, auto)",
|
|
16
17
|
} as const;
|
|
@@ -64,9 +65,10 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
64
65
|
|
|
65
66
|
const header = createNode("div", {
|
|
66
67
|
className:
|
|
67
|
-
"persona-widget-header persona-flex persona-items-center persona-gap-3
|
|
68
|
+
"persona-widget-header persona-flex persona-items-center persona-gap-3",
|
|
68
69
|
attrs: { "data-persona-theme-zone": "header" },
|
|
69
70
|
style: {
|
|
71
|
+
padding: "var(--persona-components-header-padding, 20px 24px)",
|
|
70
72
|
minHeight: HEADER_THEME_CSS.minHeight,
|
|
71
73
|
backgroundColor: "var(--persona-header-bg, var(--persona-surface, #ffffff))",
|
|
72
74
|
borderBottomColor: "var(--persona-header-border, var(--persona-divider, #f1f5f9))",
|
|
@@ -77,7 +79,7 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
77
79
|
});
|
|
78
80
|
|
|
79
81
|
const launcher = config?.launcher ?? {};
|
|
80
|
-
const headerIconSize = launcher.headerIconSize ??
|
|
82
|
+
const headerIconSize = launcher.headerIconSize ?? DEFAULT_HEADER_ICON_SIZE;
|
|
81
83
|
const closeButtonPlacement = launcher.closeButtonPlacement ?? "inline";
|
|
82
84
|
const headerIconHidden = launcher.headerIconHidden ?? false;
|
|
83
85
|
const headerIconName = launcher.headerIconName;
|
|
@@ -100,6 +102,8 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
100
102
|
const iconSize = parseFloat(headerIconSize) || 24;
|
|
101
103
|
const iconSvg = renderLucideIcon(headerIconName, iconSize * 0.6, "currentColor", 1);
|
|
102
104
|
if (iconSvg) {
|
|
105
|
+
iconSvg.style.width = `calc(${iconSize}px * var(--persona-components-header-iconScale, 0.6))`;
|
|
106
|
+
iconSvg.style.height = `calc(${iconSize}px * var(--persona-components-header-iconScale, 0.6))`;
|
|
103
107
|
iconHolder.replaceChildren(iconSvg);
|
|
104
108
|
} else {
|
|
105
109
|
// Fallback to agentIconText if Lucide icon fails
|
|
@@ -238,4 +242,3 @@ export const attachHeaderToContainer = (
|
|
|
238
242
|
container.appendChild(headerElements.clearChatButtonWrapper);
|
|
239
243
|
}
|
|
240
244
|
};
|
|
241
|
-
|
|
@@ -16,6 +16,18 @@ const build = (layoutHeaderConfig: Parameters<typeof buildMinimalHeader>[0]["lay
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
describe("minimal header trailing actions", () => {
|
|
19
|
+
it.each([{ v5Defaults: false }, { v5Defaults: true }])(
|
|
20
|
+
"uses the minimal padding token under v5Defaults=%s",
|
|
21
|
+
(future) => {
|
|
22
|
+
const elements = buildMinimalHeader({
|
|
23
|
+
config: { apiUrl: "https://example.com/api", future } as AgentWidgetConfig,
|
|
24
|
+
});
|
|
25
|
+
expect(elements.header.style.padding).toBe(
|
|
26
|
+
"var(--persona-components-header-minimalPadding, var(--persona-components-header-padding, 16px 24px))"
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
19
31
|
it("clusters actions at the trailing edge with the close button's chrome", () => {
|
|
20
32
|
const onAction = vi.fn();
|
|
21
33
|
const elements = build({
|
|
@@ -122,10 +122,12 @@ export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
|
|
|
122
122
|
// max(title 24px, cluster 32px) + py-4 + border = the strip's 65px.
|
|
123
123
|
const header = createElement(
|
|
124
124
|
"div",
|
|
125
|
-
"persona-flex persona-items-center persona-justify-between
|
|
125
|
+
"persona-flex persona-items-center persona-justify-between"
|
|
126
126
|
);
|
|
127
127
|
header.setAttribute("data-persona-theme-zone", "header");
|
|
128
128
|
header.style.minHeight = HEADER_THEME_CSS.minHeight;
|
|
129
|
+
header.style.padding =
|
|
130
|
+
"var(--persona-components-header-minimalPadding, var(--persona-components-header-padding, 16px 24px))";
|
|
129
131
|
header.style.backgroundColor = 'var(--persona-header-bg, var(--persona-surface, #ffffff))';
|
|
130
132
|
header.style.borderBottomColor = 'var(--persona-header-border, var(--persona-divider, #f1f5f9))';
|
|
131
133
|
header.style.boxShadow = 'var(--persona-header-shadow, none)';
|
|
@@ -393,4 +395,3 @@ export const buildHeaderWithLayout = (
|
|
|
393
395
|
|
|
394
396
|
return headerElements;
|
|
395
397
|
};
|
|
396
|
-
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveLauncherVariant } from "../utils/launcher-variant";
|
|
1
2
|
import { createElement, createNode, cx } from "../utils/dom";
|
|
2
3
|
import { AgentWidgetConfig, AgentWidgetLauncherTeaserConfig } from "../types";
|
|
3
4
|
import { positionMap } from "../utils/positioning";
|
|
@@ -31,6 +32,14 @@ export const createLauncherButton = (
|
|
|
31
32
|
const update = (newConfig: AgentWidgetConfig) => {
|
|
32
33
|
const launcher = newConfig.launcher ?? {};
|
|
33
34
|
const dockedMode = isDockedMountMode(newConfig);
|
|
35
|
+
const circle = resolveLauncherVariant(newConfig) === "circle";
|
|
36
|
+
if (circle) {
|
|
37
|
+
button.dataset.personaLauncherVariant = "circle";
|
|
38
|
+
button.setAttribute("aria-label", launcher.title ?? "Open chat");
|
|
39
|
+
} else {
|
|
40
|
+
button.removeAttribute("data-persona-launcher-variant");
|
|
41
|
+
button.removeAttribute("aria-label");
|
|
42
|
+
}
|
|
34
43
|
|
|
35
44
|
const titleEl = button.querySelector("[data-role='launcher-title']");
|
|
36
45
|
if (titleEl) {
|
|
@@ -49,7 +58,7 @@ export const createLauncherButton = (
|
|
|
49
58
|
// Hide/show text container
|
|
50
59
|
const textContainer = button.querySelector(".persona-flex-col");
|
|
51
60
|
if (textContainer) {
|
|
52
|
-
if (launcher.textHidden || dockedMode) {
|
|
61
|
+
if (launcher.textHidden || dockedMode || circle) {
|
|
53
62
|
(textContainer as HTMLElement).style.display = "none";
|
|
54
63
|
} else {
|
|
55
64
|
(textContainer as HTMLElement).style.display = "";
|
|
@@ -153,7 +162,7 @@ export const createLauncherButton = (
|
|
|
153
162
|
callToActionIconEl.style.padding = "";
|
|
154
163
|
}
|
|
155
164
|
|
|
156
|
-
if (launcher.callToActionIconHidden) {
|
|
165
|
+
if (launcher.callToActionIconHidden || circle) {
|
|
157
166
|
callToActionIconEl.style.display = "none";
|
|
158
167
|
} else {
|
|
159
168
|
callToActionIconEl.style.display = dockedMode ? "none" : "";
|
|
@@ -359,6 +359,20 @@ describe("createStandardBubble: components.message geometry tokens", () => {
|
|
|
359
359
|
});
|
|
360
360
|
});
|
|
361
361
|
|
|
362
|
+
it.each([false, true])("consumes selected default geometry with v5Defaults=%s", (v5Defaults) => {
|
|
363
|
+
for (const role of ["user", "assistant"] as const) {
|
|
364
|
+
const bubble = createStandardBubble(
|
|
365
|
+
makeMessage({ id: `default-${role}`, role, content: "Hi" }),
|
|
366
|
+
({ text }) => text, undefined, undefined, undefined,
|
|
367
|
+
{ widgetConfig: { future: { v5Defaults } } as AgentWidgetConfig }
|
|
368
|
+
);
|
|
369
|
+
expect(bubble.style.padding).toBe(v5Defaults
|
|
370
|
+
? `var(--persona-message-${role}-padding, 0.75rem 1.25rem)` : "");
|
|
371
|
+
expect(bubble.style.lineHeight).toBe(v5Defaults
|
|
372
|
+
? `var(--persona-message-${role}-line-height, 1.75)` : "");
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
362
376
|
it("routes each configured property through its role token with the bubble preset as fallback", () => {
|
|
363
377
|
const bubble = createStandardBubble(
|
|
364
378
|
makeMessage({ id: "geo-user", role: "user", content: "Hi" }),
|
|
@@ -896,6 +910,14 @@ describe("createMessageActions: chromeless action buttons", () => {
|
|
|
896
910
|
const actionButtons = (row: HTMLElement) =>
|
|
897
911
|
Array.from(row.querySelectorAll<HTMLButtonElement>("button[data-action]"));
|
|
898
912
|
|
|
913
|
+
it("keeps voting controls hidden unless explicitly enabled", () => {
|
|
914
|
+
const row = createMessageActions(makeMessage({ id: "actions-default-votes" }), {});
|
|
915
|
+
|
|
916
|
+
expect(actionButtons(row).map((btn) => btn.getAttribute("data-action"))).toEqual([
|
|
917
|
+
"copy",
|
|
918
|
+
]);
|
|
919
|
+
});
|
|
920
|
+
|
|
899
921
|
it("styles every built-in action through the message-action class alone", () => {
|
|
900
922
|
const row = createMessageActions(makeMessage({ id: "actions-builtin" }), {
|
|
901
923
|
showCopy: true,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
StopReasonKind
|
|
19
19
|
} from "../types";
|
|
20
20
|
import type { MessageGeometryTokens } from "../types/theme";
|
|
21
|
+
import { resolveThemeDefaults } from "../utils/tokens";
|
|
21
22
|
import { createMentionTokenElement } from "../utils/mention-token";
|
|
22
23
|
import { createIconButton } from "../utils/buttons";
|
|
23
24
|
import { renderLucideIcon } from "../utils/icons";
|
|
@@ -820,8 +821,8 @@ export const createMessageActions = (
|
|
|
820
821
|
eligibility?: MessageActionEligibility
|
|
821
822
|
): HTMLElement => {
|
|
822
823
|
const showCopy = actionsConfig.showCopy ?? true;
|
|
823
|
-
const showUpvote = actionsConfig.showUpvote ??
|
|
824
|
-
const showDownvote = actionsConfig.showDownvote ??
|
|
824
|
+
const showUpvote = actionsConfig.showUpvote ?? false;
|
|
825
|
+
const showDownvote = actionsConfig.showDownvote ?? false;
|
|
825
826
|
const showReadAloud = actionsConfig.showReadAloud ?? false;
|
|
826
827
|
// Retry and edit are per-message: the config flag only opts in, the caller's
|
|
827
828
|
// eligibility decides which single message actually shows the control.
|
|
@@ -1002,12 +1003,15 @@ export const createStandardBubble = (
|
|
|
1002
1003
|
|
|
1003
1004
|
bubble.setAttribute("data-persona-theme-zone", message.role === "user" ? "user-message" : "assistant-message");
|
|
1004
1005
|
|
|
1006
|
+
const defaultMessageTokens = resolveThemeDefaults(options?.widgetConfig?.future).components.message;
|
|
1005
1007
|
const messageTokens = options?.widgetConfig?.theme?.components?.message;
|
|
1006
1008
|
applyBubbleGeometry(
|
|
1007
1009
|
bubble,
|
|
1008
1010
|
message.role,
|
|
1009
1011
|
layout,
|
|
1010
|
-
message.role === "user"
|
|
1012
|
+
message.role === "user"
|
|
1013
|
+
? { ...defaultMessageTokens.user, ...messageTokens?.user, ...options?.widgetConfig?.darkTheme?.components?.message?.user }
|
|
1014
|
+
: { ...defaultMessageTokens.assistant, ...messageTokens?.assistant, ...options?.widgetConfig?.darkTheme?.components?.message?.assistant }
|
|
1011
1015
|
);
|
|
1012
1016
|
|
|
1013
1017
|
// Apply component-level color overrides via CSS variables
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
|
-
import { createWrapper } from "./panel";
|
|
4
|
+
import { buildPanel, createWrapper, resolvePanelGeometry } from "./panel";
|
|
5
|
+
import { mergeWithDefaults } from "../defaults";
|
|
5
6
|
import type { AgentWidgetConfig } from "../types";
|
|
6
7
|
|
|
7
8
|
describe("createWrapper: composer-bar mode", () => {
|
|
9
|
+
it.each([{ v5Defaults: false }, { v5Defaults: true }])(
|
|
10
|
+
"routes the transcript gap through the message token under v5Defaults=%s",
|
|
11
|
+
(future) => {
|
|
12
|
+
const { messagesWrapper } = buildPanel({ apiUrl: "/api", future } as AgentWidgetConfig);
|
|
13
|
+
expect(messagesWrapper.style.gap).toBe("var(--persona-components-message-gap, 12px)");
|
|
14
|
+
}
|
|
15
|
+
);
|
|
16
|
+
|
|
8
17
|
it("marks the wrapper with composer-bar data-attrs and leaves geometry to updateOpenState", () => {
|
|
9
18
|
const config: AgentWidgetConfig = {
|
|
10
19
|
apiUrl: "/api",
|
|
@@ -59,3 +68,45 @@ describe("createWrapper: composer-bar mode", () => {
|
|
|
59
68
|
expect(wrapper.hasAttribute("data-persona-composer-bar")).toBe(false);
|
|
60
69
|
});
|
|
61
70
|
});
|
|
71
|
+
|
|
72
|
+
describe.each([false, true])("resolvePanelGeometry (v5=%s)", (v5Defaults) => {
|
|
73
|
+
it("uses panel token variables when no legacy width alias was supplied", () => {
|
|
74
|
+
const config = mergeWithDefaults({ apiUrl: "/api", future: { v5Defaults } }) as AgentWidgetConfig;
|
|
75
|
+
expect(resolvePanelGeometry(config)).toMatchObject({
|
|
76
|
+
width: "var(--persona-components-panel-width)",
|
|
77
|
+
maxWidth: "var(--persona-components-panel-maxWidth)",
|
|
78
|
+
height: "max(200px, var(--persona-components-panel-height))",
|
|
79
|
+
maxHeight: "var(--persona-components-panel-maxHeight)",
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("keeps an explicit launcher width ahead of panel tokens", () => {
|
|
84
|
+
const config = mergeWithDefaults({
|
|
85
|
+
apiUrl: "/api",
|
|
86
|
+
future: { v5Defaults },
|
|
87
|
+
launcher: { width: "600px" },
|
|
88
|
+
theme: { components: { panel: { width: "320px", maxWidth: "320px" } } },
|
|
89
|
+
}) as AgentWidgetConfig;
|
|
90
|
+
expect(resolvePanelGeometry(config)).toMatchObject({ width: "600px", maxWidth: "600px" });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("applies heightOffset regardless of which width source wins", () => {
|
|
94
|
+
const config = mergeWithDefaults({
|
|
95
|
+
apiUrl: "/api",
|
|
96
|
+
future: { v5Defaults },
|
|
97
|
+
launcher: { width: "600px", heightOffset: 24 },
|
|
98
|
+
}) as AgentWidgetConfig;
|
|
99
|
+
expect(resolvePanelGeometry(config).height).toBe(
|
|
100
|
+
"max(200px, calc(var(--persona-components-panel-height) - 24px))"
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe.each([false, true])('inline width (V5=%s)', v5Defaults => {
|
|
106
|
+
it('fills the host with inherited defaults and honors explicit widths', () => {
|
|
107
|
+
const config = { future: { v5Defaults }, launcher: { enabled: false } };
|
|
108
|
+
expect(createWrapper(mergeWithDefaults(config)).wrapper.style.width).toBe('100%');
|
|
109
|
+
expect(createWrapper(mergeWithDefaults({ ...config, launcher: { enabled: false, width: '600px' } })).wrapper.style.width).toBe('600px');
|
|
110
|
+
expect(createWrapper(mergeWithDefaults({ ...config, theme: { components: { panel: { width: '700px' } } } })).wrapper.style.width).toBe('var(--persona-components-panel-width)');
|
|
111
|
+
});
|
|
112
|
+
});
|
package/src/components/panel.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createElement, createNode } from "../utils/dom";
|
|
2
|
-
import { DEFAULT_FLOATING_LAUNCHER_WIDTH } from "../defaults";
|
|
3
2
|
import { AgentWidgetConfig, AgentWidgetSuggestionSurface } from "../types";
|
|
4
3
|
import { suggestionSurfaceAttr } from "./suggestions";
|
|
5
4
|
import { positionMap } from "../utils/positioning";
|
|
@@ -20,6 +19,42 @@ import {
|
|
|
20
19
|
} from "./welcome";
|
|
21
20
|
import { isWelcomeVisible, resolveWelcomeConfig } from "../welcome";
|
|
22
21
|
import { resolveContentMaxWidth } from "../utils/content-width";
|
|
22
|
+
import { getPanelAliasProvenance, PANEL_WIDTH_VAR } from "../utils/panel-config";
|
|
23
|
+
|
|
24
|
+
const PANEL_MAX_WIDTH_VAR = "var(--persona-components-panel-maxWidth)";
|
|
25
|
+
const PANEL_HEIGHT_VAR = "var(--persona-components-panel-height)";
|
|
26
|
+
const PANEL_MAX_HEIGHT_VAR = "var(--persona-components-panel-maxHeight)";
|
|
27
|
+
|
|
28
|
+
export type PanelGeometry = {
|
|
29
|
+
width: string;
|
|
30
|
+
maxWidth: string;
|
|
31
|
+
height: string;
|
|
32
|
+
maxHeight: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Legacy aliases win when a caller explicitly supplied them; otherwise the
|
|
37
|
+
* resolved panel token variables own geometry. Provenance keeps a materialized
|
|
38
|
+
* launcher default from masquerading as a legacy override after config merge.
|
|
39
|
+
*/
|
|
40
|
+
export const resolvePanelGeometry = (config?: AgentWidgetConfig): PanelGeometry => {
|
|
41
|
+
const aliases = getPanelAliasProvenance(config);
|
|
42
|
+
const legacyWidth = aliases.launcherWidth
|
|
43
|
+
? config?.launcher?.width
|
|
44
|
+
: aliases.legacyLauncherWidth
|
|
45
|
+
? config?.launcherWidth
|
|
46
|
+
: undefined;
|
|
47
|
+
const heightOffset = config?.launcher?.heightOffset ?? 0;
|
|
48
|
+
const height = heightOffset === 0
|
|
49
|
+
? `max(200px, ${PANEL_HEIGHT_VAR})`
|
|
50
|
+
: `max(200px, calc(${PANEL_HEIGHT_VAR} - ${heightOffset}px))`;
|
|
51
|
+
return {
|
|
52
|
+
width: legacyWidth ?? PANEL_WIDTH_VAR,
|
|
53
|
+
maxWidth: legacyWidth ?? PANEL_MAX_WIDTH_VAR,
|
|
54
|
+
height,
|
|
55
|
+
maxHeight: PANEL_MAX_HEIGHT_VAR,
|
|
56
|
+
};
|
|
57
|
+
};
|
|
23
58
|
|
|
24
59
|
/**
|
|
25
60
|
* Composer-bar chrome sizes. Not header controls: these float over the
|
|
@@ -116,7 +151,10 @@ export const createWrapper = (config?: AgentWidgetConfig): PanelWrapper => {
|
|
|
116
151
|
);
|
|
117
152
|
|
|
118
153
|
// Apply width from config, defaulting to 100% for inline embed mode
|
|
119
|
-
const
|
|
154
|
+
const aliases = getPanelAliasProvenance(config);
|
|
155
|
+
const hasWidth = aliases.launcherWidth || aliases.legacyLauncherWidth ||
|
|
156
|
+
config?.theme?.components?.panel?.width !== undefined;
|
|
157
|
+
const inlineWidth = hasWidth ? resolvePanelGeometry(config).width : "100%";
|
|
120
158
|
wrapper.style.width = inlineWidth;
|
|
121
159
|
panel.style.width = "100%";
|
|
122
160
|
|
|
@@ -140,10 +178,9 @@ export const createWrapper = (config?: AgentWidgetConfig): PanelWrapper => {
|
|
|
140
178
|
"div",
|
|
141
179
|
"persona-widget-panel persona-relative persona-min-h-[320px]"
|
|
142
180
|
);
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
panel.style.
|
|
146
|
-
panel.style.maxWidth = width;
|
|
181
|
+
const geometry = resolvePanelGeometry(config);
|
|
182
|
+
panel.style.width = geometry.width;
|
|
183
|
+
panel.style.maxWidth = geometry.maxWidth;
|
|
147
184
|
|
|
148
185
|
wrapper.appendChild(panel);
|
|
149
186
|
return { wrapper, panel };
|
|
@@ -339,8 +376,9 @@ const buildComposerBarPanel = (
|
|
|
339
376
|
|
|
340
377
|
const messagesWrapper = createElement(
|
|
341
378
|
"div",
|
|
342
|
-
"persona-widget-messages persona-flex persona-flex-col
|
|
379
|
+
"persona-widget-messages persona-flex persona-flex-col"
|
|
343
380
|
);
|
|
381
|
+
messagesWrapper.style.gap = "var(--persona-components-message-gap, 12px)";
|
|
344
382
|
const transcriptSuggestions = createSuggestionHost("followUp");
|
|
345
383
|
const contentMaxWidth = resolveContentMaxWidth(config, true);
|
|
346
384
|
if (contentMaxWidth) {
|
|
@@ -471,8 +509,9 @@ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelE
|
|
|
471
509
|
|
|
472
510
|
const messagesWrapper = createElement(
|
|
473
511
|
"div",
|
|
474
|
-
"persona-widget-messages persona-flex persona-flex-col
|
|
512
|
+
"persona-widget-messages persona-flex persona-flex-col"
|
|
475
513
|
);
|
|
514
|
+
messagesWrapper.style.gap = "var(--persona-components-message-gap, 12px)";
|
|
476
515
|
const transcriptSuggestions = createSuggestionHost("followUp");
|
|
477
516
|
|
|
478
517
|
const contentMaxWidth = resolveContentMaxWidth(config, false);
|
|
@@ -22,6 +22,8 @@ describe("buildPillComposer (single-row pill composer)", () => {
|
|
|
22
22
|
expect(elements.composerForm.classList.contains("persona-rounded-2xl")).toBe(false);
|
|
23
23
|
|
|
24
24
|
expect(elements.composerForm.getAttribute("data-persona-composer-form")).toBe("");
|
|
25
|
+
// The shared focusRing token must not be suppressed by an inline outline.
|
|
26
|
+
expect(elements.composerForm.style.outline).toBe("");
|
|
25
27
|
expect(elements.textarea.getAttribute("data-persona-composer-input")).toBe("");
|
|
26
28
|
expect(elements.sendButton.getAttribute("data-persona-composer-submit")).toBe("");
|
|
27
29
|
});
|