@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/presets.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AgentWidgetConfig } from
|
|
1
|
+
import type { AgentWidgetConfig } from "./types";
|
|
2
|
+
import type { DeepPartial, PersonaTheme } from "./types/theme";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* A named preset containing partial widget configuration.
|
|
@@ -11,51 +12,71 @@ export interface WidgetPreset {
|
|
|
11
12
|
config: Partial<AgentWidgetConfig>;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
/** Shopping palette + semantic roles (matches prior shop preset visuals). */
|
|
16
|
+
const SHOP_THEME: DeepPartial<PersonaTheme> = {
|
|
17
|
+
palette: {
|
|
18
|
+
colors: {
|
|
19
|
+
primary: { 500: "#111827" },
|
|
20
|
+
accent: { 600: "#1d4ed8" },
|
|
21
|
+
gray: {
|
|
22
|
+
50: "#ffffff",
|
|
23
|
+
100: "#f8fafc",
|
|
24
|
+
200: "#f1f5f9",
|
|
25
|
+
500: "#6b7280",
|
|
26
|
+
900: "#000000",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
radius: {
|
|
30
|
+
sm: "0.75rem",
|
|
31
|
+
md: "1rem",
|
|
32
|
+
lg: "1.5rem",
|
|
33
|
+
launcher: "9999px",
|
|
34
|
+
button: "9999px",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
semantic: {
|
|
38
|
+
colors: {
|
|
39
|
+
primary: "palette.colors.primary.500",
|
|
40
|
+
textInverse: "palette.colors.gray.50",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const PANEL_EDGELESS_THEME: DeepPartial<PersonaTheme> = {
|
|
46
|
+
components: {
|
|
47
|
+
panel: {
|
|
48
|
+
borderRadius: "0",
|
|
49
|
+
shadow: "none",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
14
54
|
/**
|
|
15
55
|
* Shopping / e-commerce preset.
|
|
16
56
|
* Dark header, rounded launchers, shopping-oriented copy.
|
|
17
57
|
*/
|
|
18
58
|
export const PRESET_SHOP: WidgetPreset = {
|
|
19
|
-
id:
|
|
20
|
-
label:
|
|
59
|
+
id: "shop",
|
|
60
|
+
label: "Shopping Assistant",
|
|
21
61
|
config: {
|
|
22
|
-
theme:
|
|
23
|
-
primary: '#111827',
|
|
24
|
-
accent: '#1d4ed8',
|
|
25
|
-
surface: '#ffffff',
|
|
26
|
-
muted: '#6b7280',
|
|
27
|
-
container: '#f8fafc',
|
|
28
|
-
border: '#f1f5f9',
|
|
29
|
-
divider: '#f1f5f9',
|
|
30
|
-
messageBorder: '#f1f5f9',
|
|
31
|
-
inputBackground: '#ffffff',
|
|
32
|
-
callToAction: '#000000',
|
|
33
|
-
callToActionBackground: '#ffffff',
|
|
34
|
-
sendButtonBackgroundColor: '#111827',
|
|
35
|
-
sendButtonTextColor: '#ffffff',
|
|
36
|
-
radiusSm: '0.75rem',
|
|
37
|
-
radiusMd: '1rem',
|
|
38
|
-
radiusLg: '1.5rem',
|
|
39
|
-
launcherRadius: '9999px',
|
|
40
|
-
buttonRadius: '9999px',
|
|
41
|
-
},
|
|
62
|
+
theme: SHOP_THEME,
|
|
42
63
|
launcher: {
|
|
43
|
-
title:
|
|
44
|
-
subtitle:
|
|
45
|
-
agentIconText:
|
|
46
|
-
position:
|
|
47
|
-
width:
|
|
64
|
+
title: "Shopping Assistant",
|
|
65
|
+
subtitle: "Here to help you find what you need",
|
|
66
|
+
agentIconText: "🛍️",
|
|
67
|
+
position: "bottom-right",
|
|
68
|
+
width: "min(400px, calc(100vw - 24px))",
|
|
48
69
|
},
|
|
49
70
|
copy: {
|
|
50
|
-
welcomeTitle:
|
|
51
|
-
welcomeSubtitle:
|
|
52
|
-
inputPlaceholder:
|
|
53
|
-
sendButtonLabel:
|
|
71
|
+
welcomeTitle: "Welcome to our shop!",
|
|
72
|
+
welcomeSubtitle: "I can help you find products and answer questions",
|
|
73
|
+
inputPlaceholder: "Ask me anything...",
|
|
74
|
+
sendButtonLabel: "Send",
|
|
54
75
|
},
|
|
55
76
|
suggestionChips: [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
77
|
+
"What can you help me with?",
|
|
78
|
+
"Tell me about your features",
|
|
79
|
+
"How does this work?",
|
|
59
80
|
],
|
|
60
81
|
},
|
|
61
82
|
};
|
|
@@ -65,8 +86,8 @@ export const PRESET_SHOP: WidgetPreset = {
|
|
|
65
86
|
* Stripped-down header, no launcher button, suitable for inline embeds.
|
|
66
87
|
*/
|
|
67
88
|
export const PRESET_MINIMAL: WidgetPreset = {
|
|
68
|
-
id:
|
|
69
|
-
label:
|
|
89
|
+
id: "minimal",
|
|
90
|
+
label: "Minimal",
|
|
70
91
|
config: {
|
|
71
92
|
launcher: {
|
|
72
93
|
enabled: false,
|
|
@@ -74,17 +95,14 @@ export const PRESET_MINIMAL: WidgetPreset = {
|
|
|
74
95
|
},
|
|
75
96
|
layout: {
|
|
76
97
|
header: {
|
|
77
|
-
layout:
|
|
98
|
+
layout: "minimal",
|
|
78
99
|
showCloseButton: false,
|
|
79
100
|
},
|
|
80
101
|
messages: {
|
|
81
|
-
layout:
|
|
102
|
+
layout: "minimal",
|
|
82
103
|
},
|
|
83
104
|
},
|
|
84
|
-
theme:
|
|
85
|
-
panelBorderRadius: '0',
|
|
86
|
-
panelShadow: 'none',
|
|
87
|
-
},
|
|
105
|
+
theme: PANEL_EDGELESS_THEME,
|
|
88
106
|
},
|
|
89
107
|
};
|
|
90
108
|
|
|
@@ -93,8 +111,8 @@ export const PRESET_MINIMAL: WidgetPreset = {
|
|
|
93
111
|
* No launcher, content-max-width constrained, minimal header.
|
|
94
112
|
*/
|
|
95
113
|
export const PRESET_FULLSCREEN: WidgetPreset = {
|
|
96
|
-
id:
|
|
97
|
-
label:
|
|
114
|
+
id: "fullscreen",
|
|
115
|
+
label: "Fullscreen Assistant",
|
|
98
116
|
config: {
|
|
99
117
|
launcher: {
|
|
100
118
|
enabled: false,
|
|
@@ -102,15 +120,12 @@ export const PRESET_FULLSCREEN: WidgetPreset = {
|
|
|
102
120
|
},
|
|
103
121
|
layout: {
|
|
104
122
|
header: {
|
|
105
|
-
layout:
|
|
123
|
+
layout: "minimal",
|
|
106
124
|
showCloseButton: false,
|
|
107
125
|
},
|
|
108
|
-
contentMaxWidth:
|
|
109
|
-
},
|
|
110
|
-
theme: {
|
|
111
|
-
panelBorderRadius: '0',
|
|
112
|
-
panelShadow: 'none',
|
|
126
|
+
contentMaxWidth: "72ch",
|
|
113
127
|
},
|
|
128
|
+
theme: PANEL_EDGELESS_THEME,
|
|
114
129
|
},
|
|
115
130
|
};
|
|
116
131
|
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { createWidgetHostLayout } from "./host-layout";
|
|
6
|
+
|
|
7
|
+
describe("createWidgetHostLayout docked", () => {
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
document.body.innerHTML = "";
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("reserves no dock column when panel is closed (always 0px)", () => {
|
|
13
|
+
const parent = document.createElement("div");
|
|
14
|
+
document.body.appendChild(parent);
|
|
15
|
+
const target = document.createElement("div");
|
|
16
|
+
parent.appendChild(target);
|
|
17
|
+
|
|
18
|
+
const layout = createWidgetHostLayout(target, {
|
|
19
|
+
launcher: {
|
|
20
|
+
mountMode: "docked",
|
|
21
|
+
autoExpand: false,
|
|
22
|
+
dock: { width: "320px" },
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
27
|
+
expect(dockSlot).not.toBeNull();
|
|
28
|
+
expect(dockSlot?.style.minWidth).toBe("0px");
|
|
29
|
+
expect(dockSlot?.style.overflow).toBe("hidden");
|
|
30
|
+
|
|
31
|
+
layout.syncWidgetState({ open: true, launcherEnabled: true });
|
|
32
|
+
expect(dockSlot?.style.width).toBe("320px");
|
|
33
|
+
expect(dockSlot?.style.overflow).toBe("visible");
|
|
34
|
+
|
|
35
|
+
layout.syncWidgetState({ open: false, launcherEnabled: true });
|
|
36
|
+
expect(dockSlot?.style.minWidth).toBe("0px");
|
|
37
|
+
expect(dockSlot?.style.overflow).toBe("hidden");
|
|
38
|
+
|
|
39
|
+
layout.destroy();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("disables dock width transition when dock.animate is false", () => {
|
|
43
|
+
const parent = document.createElement("div");
|
|
44
|
+
document.body.appendChild(parent);
|
|
45
|
+
const target = document.createElement("div");
|
|
46
|
+
parent.appendChild(target);
|
|
47
|
+
|
|
48
|
+
const layout = createWidgetHostLayout(target, {
|
|
49
|
+
launcher: {
|
|
50
|
+
mountMode: "docked",
|
|
51
|
+
autoExpand: false,
|
|
52
|
+
dock: { width: "320px", animate: false },
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
57
|
+
expect(dockSlot?.style.transition).toBe("none");
|
|
58
|
+
|
|
59
|
+
layout.destroy();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("overlay reveal keeps panel width and translates off-screen when closed", () => {
|
|
63
|
+
const parent = document.createElement("div");
|
|
64
|
+
document.body.appendChild(parent);
|
|
65
|
+
const target = document.createElement("div");
|
|
66
|
+
parent.appendChild(target);
|
|
67
|
+
|
|
68
|
+
const layout = createWidgetHostLayout(target, {
|
|
69
|
+
launcher: {
|
|
70
|
+
mountMode: "docked",
|
|
71
|
+
autoExpand: false,
|
|
72
|
+
dock: { width: "320px", reveal: "overlay" },
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const shell = layout.shell;
|
|
77
|
+
const dockSlot = shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
78
|
+
expect(shell?.dataset.personaDockReveal).toBe("overlay");
|
|
79
|
+
expect(shell?.style.overflow).toBe("hidden");
|
|
80
|
+
expect(dockSlot?.style.width).toBe("320px");
|
|
81
|
+
expect(dockSlot?.style.transform).toBe("translateX(100%)");
|
|
82
|
+
|
|
83
|
+
layout.syncWidgetState({ open: true, launcherEnabled: true });
|
|
84
|
+
expect(dockSlot?.style.width).toBe("320px");
|
|
85
|
+
expect(dockSlot?.style.transform).toBe("translateX(0)");
|
|
86
|
+
|
|
87
|
+
layout.destroy();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("overlay reveal uses translateX(-100%) on the left side when closed", () => {
|
|
91
|
+
const parent = document.createElement("div");
|
|
92
|
+
document.body.appendChild(parent);
|
|
93
|
+
const target = document.createElement("div");
|
|
94
|
+
parent.appendChild(target);
|
|
95
|
+
|
|
96
|
+
const layout = createWidgetHostLayout(target, {
|
|
97
|
+
launcher: {
|
|
98
|
+
mountMode: "docked",
|
|
99
|
+
autoExpand: false,
|
|
100
|
+
dock: { width: "300px", side: "left", reveal: "overlay" },
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
105
|
+
expect(dockSlot?.style.transform).toBe("translateX(-100%)");
|
|
106
|
+
expect(dockSlot?.style.left).toBe("0px");
|
|
107
|
+
|
|
108
|
+
layout.destroy();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("push reveal translates a track; main column width stays fixed in px", () => {
|
|
112
|
+
const parent = document.createElement("div");
|
|
113
|
+
parent.style.width = "800px";
|
|
114
|
+
document.body.appendChild(parent);
|
|
115
|
+
const target = document.createElement("div");
|
|
116
|
+
parent.appendChild(target);
|
|
117
|
+
|
|
118
|
+
const dockConfig = {
|
|
119
|
+
mountMode: "docked" as const,
|
|
120
|
+
autoExpand: false,
|
|
121
|
+
dock: { width: "320px", reveal: "push" as const },
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const layout = createWidgetHostLayout(target, { launcher: dockConfig });
|
|
125
|
+
const shell = layout.shell!;
|
|
126
|
+
Object.defineProperty(shell, "clientWidth", { get: () => 800, configurable: true });
|
|
127
|
+
layout.updateConfig({ launcher: dockConfig });
|
|
128
|
+
|
|
129
|
+
const pushTrack = shell.querySelector<HTMLElement>('[data-persona-dock-role="push-track"]');
|
|
130
|
+
const contentSlot = shell.querySelector<HTMLElement>('[data-persona-dock-role="content"]');
|
|
131
|
+
expect(pushTrack).not.toBeNull();
|
|
132
|
+
expect(shell.dataset.personaDockReveal).toBe("push");
|
|
133
|
+
expect(contentSlot?.style.width).toBe("800px");
|
|
134
|
+
expect(pushTrack?.style.width).toBe("1120px");
|
|
135
|
+
expect(pushTrack?.style.transform).toBe("translateX(0)");
|
|
136
|
+
|
|
137
|
+
layout.syncWidgetState({ open: true, launcherEnabled: true });
|
|
138
|
+
expect(pushTrack?.style.transform).toBe("translateX(-320px)");
|
|
139
|
+
|
|
140
|
+
layout.destroy();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("push reveal on the left uses negative translate when closed", () => {
|
|
144
|
+
const parent = document.createElement("div");
|
|
145
|
+
parent.style.width = "600px";
|
|
146
|
+
document.body.appendChild(parent);
|
|
147
|
+
const target = document.createElement("div");
|
|
148
|
+
parent.appendChild(target);
|
|
149
|
+
|
|
150
|
+
const dockConfig = {
|
|
151
|
+
mountMode: "docked" as const,
|
|
152
|
+
autoExpand: false,
|
|
153
|
+
dock: { width: "200px", side: "left" as const, reveal: "push" as const },
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const layout = createWidgetHostLayout(target, { launcher: dockConfig });
|
|
157
|
+
const shell = layout.shell!;
|
|
158
|
+
Object.defineProperty(shell, "clientWidth", { get: () => 600, configurable: true });
|
|
159
|
+
layout.updateConfig({ launcher: dockConfig });
|
|
160
|
+
|
|
161
|
+
const pushTrack = shell.querySelector<HTMLElement>('[data-persona-dock-role="push-track"]');
|
|
162
|
+
expect(pushTrack?.style.transform).toBe("translateX(-200px)");
|
|
163
|
+
|
|
164
|
+
layout.syncWidgetState({ open: true, launcherEnabled: true });
|
|
165
|
+
expect(pushTrack?.style.transform).toBe("translateX(0)");
|
|
166
|
+
|
|
167
|
+
layout.destroy();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("emerge reveal keeps host width at dock.width while the column animates like resize", () => {
|
|
171
|
+
const parent = document.createElement("div");
|
|
172
|
+
document.body.appendChild(parent);
|
|
173
|
+
const target = document.createElement("div");
|
|
174
|
+
parent.appendChild(target);
|
|
175
|
+
|
|
176
|
+
const layout = createWidgetHostLayout(target, {
|
|
177
|
+
launcher: {
|
|
178
|
+
mountMode: "docked",
|
|
179
|
+
autoExpand: false,
|
|
180
|
+
dock: { width: "320px", reveal: "emerge" },
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const host = layout.host;
|
|
185
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
186
|
+
expect(layout.shell?.dataset.personaDockReveal).toBe("emerge");
|
|
187
|
+
expect(dockSlot?.style.minWidth).toBe("0px");
|
|
188
|
+
expect(host.style.width).toBe("320px");
|
|
189
|
+
|
|
190
|
+
layout.syncWidgetState({ open: true, launcherEnabled: true });
|
|
191
|
+
expect(dockSlot?.style.minWidth).toBe("320px");
|
|
192
|
+
expect(host.style.width).toBe("320px");
|
|
193
|
+
|
|
194
|
+
layout.destroy();
|
|
195
|
+
});
|
|
196
|
+
});
|