@runtypelabs/persona 2.3.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -5
- 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 +88 -88
- 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 +257 -67
- package/package.json +2 -4
- package/src/components/artifact-card.ts +39 -5
- package/src/components/artifact-pane.ts +68 -127
- 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 +333 -0
- package/src/runtime/host-layout.ts +346 -27
- package/src/runtime/init.test.ts +113 -8
- package/src/runtime/init.ts +1 -1
- package/src/styles/widget.css +257 -67
- 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 +125 -92
- package/src/utils/artifact-gate.ts +1 -1
- 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/theme.test.ts +90 -15
- package/src/utils/theme.ts +20 -46
- package/src/utils/tokens.ts +108 -11
- package/src/styles/tailwind.css +0 -20
- package/src/utils/migration.ts +0 -220
|
@@ -0,0 +1,333 @@
|
|
|
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
|
+
|
|
197
|
+
const withInnerWidth = (width: number, fn: () => void): void => {
|
|
198
|
+
const prev = window.innerWidth;
|
|
199
|
+
try {
|
|
200
|
+
Object.defineProperty(window, "innerWidth", {
|
|
201
|
+
configurable: true,
|
|
202
|
+
value: width,
|
|
203
|
+
});
|
|
204
|
+
fn();
|
|
205
|
+
} finally {
|
|
206
|
+
Object.defineProperty(window, "innerWidth", {
|
|
207
|
+
configurable: true,
|
|
208
|
+
value: prev,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
it("uses fixed fullscreen dock slot on mobile viewport when open", () => {
|
|
214
|
+
withInnerWidth(500, () => {
|
|
215
|
+
const parent = document.createElement("div");
|
|
216
|
+
document.body.appendChild(parent);
|
|
217
|
+
const target = document.createElement("div");
|
|
218
|
+
parent.appendChild(target);
|
|
219
|
+
|
|
220
|
+
const layout = createWidgetHostLayout(target, {
|
|
221
|
+
launcher: {
|
|
222
|
+
mountMode: "docked",
|
|
223
|
+
autoExpand: false,
|
|
224
|
+
dock: { width: "320px" },
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
229
|
+
layout.syncWidgetState({ open: true, launcherEnabled: true });
|
|
230
|
+
expect(dockSlot?.style.position).toBe("fixed");
|
|
231
|
+
expect(dockSlot?.style.zIndex).toBe("9999");
|
|
232
|
+
expect(layout.shell?.dataset.personaDockMobileFullscreen).toBe("true");
|
|
233
|
+
|
|
234
|
+
layout.destroy();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("does not use fixed fullscreen above mobile breakpoint", () => {
|
|
239
|
+
withInnerWidth(800, () => {
|
|
240
|
+
const parent = document.createElement("div");
|
|
241
|
+
document.body.appendChild(parent);
|
|
242
|
+
const target = document.createElement("div");
|
|
243
|
+
parent.appendChild(target);
|
|
244
|
+
|
|
245
|
+
const layout = createWidgetHostLayout(target, {
|
|
246
|
+
launcher: {
|
|
247
|
+
mountMode: "docked",
|
|
248
|
+
autoExpand: false,
|
|
249
|
+
dock: { width: "320px", reveal: "overlay" },
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
254
|
+
layout.syncWidgetState({ open: true, launcherEnabled: true });
|
|
255
|
+
expect(dockSlot?.style.position).toBe("absolute");
|
|
256
|
+
expect(layout.shell?.dataset.personaDockMobileFullscreen).toBeUndefined();
|
|
257
|
+
|
|
258
|
+
layout.destroy();
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("respects mobileFullscreen: false on narrow viewport", () => {
|
|
263
|
+
withInnerWidth(500, () => {
|
|
264
|
+
const parent = document.createElement("div");
|
|
265
|
+
document.body.appendChild(parent);
|
|
266
|
+
const target = document.createElement("div");
|
|
267
|
+
parent.appendChild(target);
|
|
268
|
+
|
|
269
|
+
const layout = createWidgetHostLayout(target, {
|
|
270
|
+
launcher: {
|
|
271
|
+
mountMode: "docked",
|
|
272
|
+
autoExpand: false,
|
|
273
|
+
mobileFullscreen: false,
|
|
274
|
+
dock: { width: "320px" },
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
279
|
+
layout.syncWidgetState({ open: true, launcherEnabled: true });
|
|
280
|
+
expect(dockSlot?.style.position).toBe("relative");
|
|
281
|
+
expect(layout.shell?.dataset.personaDockMobileFullscreen).toBeUndefined();
|
|
282
|
+
|
|
283
|
+
layout.destroy();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("respects custom mobileBreakpoint", () => {
|
|
288
|
+
withInnerWidth(900, () => {
|
|
289
|
+
const parent = document.createElement("div");
|
|
290
|
+
document.body.appendChild(parent);
|
|
291
|
+
const target = document.createElement("div");
|
|
292
|
+
parent.appendChild(target);
|
|
293
|
+
|
|
294
|
+
const layout = createWidgetHostLayout(target, {
|
|
295
|
+
launcher: {
|
|
296
|
+
mountMode: "docked",
|
|
297
|
+
autoExpand: false,
|
|
298
|
+
mobileBreakpoint: 1024,
|
|
299
|
+
dock: { width: "320px" },
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
304
|
+
layout.syncWidgetState({ open: true, launcherEnabled: true });
|
|
305
|
+
expect(dockSlot?.style.position).toBe("fixed");
|
|
306
|
+
|
|
307
|
+
layout.destroy();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("does not use fixed fullscreen when panel is closed on mobile", () => {
|
|
312
|
+
withInnerWidth(500, () => {
|
|
313
|
+
const parent = document.createElement("div");
|
|
314
|
+
document.body.appendChild(parent);
|
|
315
|
+
const target = document.createElement("div");
|
|
316
|
+
parent.appendChild(target);
|
|
317
|
+
|
|
318
|
+
const layout = createWidgetHostLayout(target, {
|
|
319
|
+
launcher: {
|
|
320
|
+
mountMode: "docked",
|
|
321
|
+
autoExpand: false,
|
|
322
|
+
dock: { width: "320px" },
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
327
|
+
layout.syncWidgetState({ open: false, launcherEnabled: true });
|
|
328
|
+
expect(dockSlot?.style.position).toBe("relative");
|
|
329
|
+
|
|
330
|
+
layout.destroy();
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
});
|