@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
|
@@ -15,6 +15,16 @@ export type WidgetHostLayout = {
|
|
|
15
15
|
destroy: () => void;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
/** Parse `dock.width` for push layout math (px or % of shell). Fallback: 420. */
|
|
19
|
+
const parseDockWidthToPx = (width: string, shellClientWidth: number): number => {
|
|
20
|
+
const w = width.trim();
|
|
21
|
+
const px = /^(\d+(?:\.\d+)?)px$/i.exec(w);
|
|
22
|
+
if (px) return Math.max(0, parseFloat(px[1]));
|
|
23
|
+
const pct = /^(\d+(?:\.\d+)?)%$/i.exec(w);
|
|
24
|
+
if (pct) return Math.max(0, (shellClientWidth * parseFloat(pct[1])) / 100);
|
|
25
|
+
return 420;
|
|
26
|
+
};
|
|
27
|
+
|
|
18
28
|
const setDirectHostStyles = (host: HTMLElement, config?: AgentWidgetConfig): void => {
|
|
19
29
|
const launcherEnabled = config?.launcher?.enabled ?? true;
|
|
20
30
|
host.className = "persona-host";
|
|
@@ -25,8 +35,100 @@ const setDirectHostStyles = (host: HTMLElement, config?: AgentWidgetConfig): voi
|
|
|
25
35
|
host.style.minHeight = launcherEnabled ? "" : "0";
|
|
26
36
|
};
|
|
27
37
|
|
|
38
|
+
const clearOverlayDockSlotStyles = (dockSlot: HTMLElement): void => {
|
|
39
|
+
dockSlot.style.position = "";
|
|
40
|
+
dockSlot.style.top = "";
|
|
41
|
+
dockSlot.style.bottom = "";
|
|
42
|
+
dockSlot.style.left = "";
|
|
43
|
+
dockSlot.style.right = "";
|
|
44
|
+
dockSlot.style.zIndex = "";
|
|
45
|
+
dockSlot.style.transform = "";
|
|
46
|
+
dockSlot.style.pointerEvents = "";
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Clears viewport-escape fullscreen styles so reveal modes can re-apply dock layout. */
|
|
50
|
+
const clearMobileFullscreenDockSlotStyles = (dockSlot: HTMLElement): void => {
|
|
51
|
+
dockSlot.style.inset = "";
|
|
52
|
+
dockSlot.style.width = "";
|
|
53
|
+
dockSlot.style.height = "";
|
|
54
|
+
dockSlot.style.maxWidth = "";
|
|
55
|
+
dockSlot.style.minWidth = "";
|
|
56
|
+
clearOverlayDockSlotStyles(dockSlot);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const clearResizeDockSlotTransition = (dockSlot: HTMLElement): void => {
|
|
60
|
+
dockSlot.style.transition = "";
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const clearPushTrackStyles = (pushTrack: HTMLElement): void => {
|
|
64
|
+
pushTrack.style.display = "";
|
|
65
|
+
pushTrack.style.flexDirection = "";
|
|
66
|
+
pushTrack.style.flex = "";
|
|
67
|
+
pushTrack.style.minHeight = "";
|
|
68
|
+
pushTrack.style.minWidth = "";
|
|
69
|
+
pushTrack.style.width = "";
|
|
70
|
+
pushTrack.style.height = "";
|
|
71
|
+
pushTrack.style.alignItems = "";
|
|
72
|
+
pushTrack.style.transition = "";
|
|
73
|
+
pushTrack.style.transform = "";
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const resetContentSlotFlexSizing = (contentSlot: HTMLElement): void => {
|
|
77
|
+
contentSlot.style.width = "";
|
|
78
|
+
contentSlot.style.maxWidth = "";
|
|
79
|
+
contentSlot.style.minWidth = "";
|
|
80
|
+
contentSlot.style.flex = "1 1 auto";
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const clearEmergeDockStyles = (host: HTMLElement, dockSlot: HTMLElement): void => {
|
|
84
|
+
host.style.width = "";
|
|
85
|
+
host.style.minWidth = "";
|
|
86
|
+
host.style.maxWidth = "";
|
|
87
|
+
host.style.boxSizing = "";
|
|
88
|
+
dockSlot.style.alignItems = "";
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const migrateDockChildren = (
|
|
92
|
+
shell: HTMLElement,
|
|
93
|
+
pushTrack: HTMLElement,
|
|
94
|
+
contentSlot: HTMLElement,
|
|
95
|
+
dockSlot: HTMLElement,
|
|
96
|
+
usePush: boolean
|
|
97
|
+
): void => {
|
|
98
|
+
if (usePush) {
|
|
99
|
+
if (contentSlot.parentElement !== pushTrack) {
|
|
100
|
+
shell.replaceChildren();
|
|
101
|
+
pushTrack.replaceChildren(contentSlot, dockSlot);
|
|
102
|
+
shell.appendChild(pushTrack);
|
|
103
|
+
}
|
|
104
|
+
} else if (contentSlot.parentElement === pushTrack) {
|
|
105
|
+
pushTrack.replaceChildren();
|
|
106
|
+
shell.appendChild(contentSlot);
|
|
107
|
+
shell.appendChild(dockSlot);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const orderDockChildren = (
|
|
112
|
+
shell: HTMLElement,
|
|
113
|
+
pushTrack: HTMLElement,
|
|
114
|
+
contentSlot: HTMLElement,
|
|
115
|
+
dockSlot: HTMLElement,
|
|
116
|
+
side: "left" | "right",
|
|
117
|
+
usePush: boolean
|
|
118
|
+
): void => {
|
|
119
|
+
const parent = usePush ? pushTrack : shell;
|
|
120
|
+
if (side === "left") {
|
|
121
|
+
if (parent.firstElementChild !== dockSlot) {
|
|
122
|
+
parent.replaceChildren(dockSlot, contentSlot);
|
|
123
|
+
}
|
|
124
|
+
} else if (parent.lastElementChild !== dockSlot) {
|
|
125
|
+
parent.replaceChildren(contentSlot, dockSlot);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
28
129
|
const applyDockStyles = (
|
|
29
130
|
shell: HTMLElement,
|
|
131
|
+
pushTrack: HTMLElement,
|
|
30
132
|
contentSlot: HTMLElement,
|
|
31
133
|
dockSlot: HTMLElement,
|
|
32
134
|
host: HTMLElement,
|
|
@@ -34,14 +136,14 @@ const applyDockStyles = (
|
|
|
34
136
|
expanded: boolean
|
|
35
137
|
): void => {
|
|
36
138
|
const dock = resolveDockConfig(config);
|
|
37
|
-
const
|
|
139
|
+
const usePush = dock.reveal === "push";
|
|
140
|
+
|
|
141
|
+
migrateDockChildren(shell, pushTrack, contentSlot, dockSlot, usePush);
|
|
142
|
+
orderDockChildren(shell, pushTrack, contentSlot, dockSlot, dock.side, usePush);
|
|
38
143
|
|
|
39
144
|
shell.dataset.personaHostLayout = "docked";
|
|
40
145
|
shell.dataset.personaDockSide = dock.side;
|
|
41
146
|
shell.dataset.personaDockOpen = expanded ? "true" : "false";
|
|
42
|
-
shell.style.display = "flex";
|
|
43
|
-
shell.style.flexDirection = "row";
|
|
44
|
-
shell.style.alignItems = "stretch";
|
|
45
147
|
shell.style.width = "100%";
|
|
46
148
|
shell.style.maxWidth = "100%";
|
|
47
149
|
shell.style.minWidth = "0";
|
|
@@ -51,22 +153,9 @@ const applyDockStyles = (
|
|
|
51
153
|
|
|
52
154
|
contentSlot.style.display = "flex";
|
|
53
155
|
contentSlot.style.flexDirection = "column";
|
|
54
|
-
contentSlot.style.flex = "1 1 auto";
|
|
55
|
-
contentSlot.style.minWidth = "0";
|
|
56
156
|
contentSlot.style.minHeight = "0";
|
|
57
157
|
contentSlot.style.position = "relative";
|
|
58
158
|
|
|
59
|
-
dockSlot.style.display = "flex";
|
|
60
|
-
dockSlot.style.flexDirection = "column";
|
|
61
|
-
dockSlot.style.flex = `0 0 ${width}`;
|
|
62
|
-
dockSlot.style.width = width;
|
|
63
|
-
dockSlot.style.maxWidth = width;
|
|
64
|
-
dockSlot.style.minWidth = width;
|
|
65
|
-
dockSlot.style.minHeight = "0";
|
|
66
|
-
dockSlot.style.position = "relative";
|
|
67
|
-
dockSlot.style.overflow = "visible";
|
|
68
|
-
dockSlot.style.transition = "width 180ms ease, min-width 180ms ease, max-width 180ms ease, flex-basis 180ms ease";
|
|
69
|
-
|
|
70
159
|
host.className = "persona-host";
|
|
71
160
|
host.style.height = "100%";
|
|
72
161
|
host.style.minHeight = "0";
|
|
@@ -74,12 +163,202 @@ const applyDockStyles = (
|
|
|
74
163
|
host.style.flexDirection = "column";
|
|
75
164
|
host.style.flex = "1 1 auto";
|
|
76
165
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
166
|
+
const ownerWindow = shell.ownerDocument.defaultView;
|
|
167
|
+
const mobileFullscreenEnabled = config?.launcher?.mobileFullscreen ?? true;
|
|
168
|
+
const mobileBreakpoint = config?.launcher?.mobileBreakpoint ?? 640;
|
|
169
|
+
const isMobileViewport =
|
|
170
|
+
ownerWindow != null ? ownerWindow.innerWidth <= mobileBreakpoint : false;
|
|
171
|
+
const useMobileFullscreen = mobileFullscreenEnabled && isMobileViewport && expanded;
|
|
172
|
+
|
|
173
|
+
if (useMobileFullscreen) {
|
|
174
|
+
shell.dataset.personaDockMobileFullscreen = "true";
|
|
175
|
+
shell.removeAttribute("data-persona-dock-reveal");
|
|
176
|
+
clearPushTrackStyles(pushTrack);
|
|
177
|
+
clearResizeDockSlotTransition(dockSlot);
|
|
178
|
+
clearMobileFullscreenDockSlotStyles(dockSlot);
|
|
179
|
+
resetContentSlotFlexSizing(contentSlot);
|
|
180
|
+
clearEmergeDockStyles(host, dockSlot);
|
|
181
|
+
|
|
182
|
+
shell.style.display = "flex";
|
|
183
|
+
shell.style.flexDirection = "column";
|
|
184
|
+
shell.style.alignItems = "stretch";
|
|
185
|
+
shell.style.overflow = "hidden";
|
|
186
|
+
|
|
187
|
+
contentSlot.style.flex = "1 1 auto";
|
|
188
|
+
contentSlot.style.width = "100%";
|
|
189
|
+
contentSlot.style.minWidth = "0";
|
|
190
|
+
|
|
191
|
+
dockSlot.style.display = "flex";
|
|
192
|
+
dockSlot.style.flexDirection = "column";
|
|
193
|
+
dockSlot.style.position = "fixed";
|
|
194
|
+
dockSlot.style.inset = "0";
|
|
195
|
+
dockSlot.style.width = "100%";
|
|
196
|
+
dockSlot.style.height = "100%";
|
|
197
|
+
dockSlot.style.maxWidth = "100%";
|
|
198
|
+
dockSlot.style.minWidth = "0";
|
|
199
|
+
dockSlot.style.minHeight = "0";
|
|
200
|
+
dockSlot.style.overflow = "hidden";
|
|
201
|
+
dockSlot.style.zIndex = "9999";
|
|
202
|
+
dockSlot.style.transform = "none";
|
|
203
|
+
dockSlot.style.transition = "none";
|
|
204
|
+
dockSlot.style.pointerEvents = "auto";
|
|
205
|
+
dockSlot.style.flex = "none";
|
|
206
|
+
|
|
207
|
+
if (usePush) {
|
|
208
|
+
pushTrack.style.display = "flex";
|
|
209
|
+
pushTrack.style.flexDirection = "column";
|
|
210
|
+
pushTrack.style.width = "100%";
|
|
211
|
+
pushTrack.style.height = "100%";
|
|
212
|
+
pushTrack.style.minHeight = "0";
|
|
213
|
+
pushTrack.style.minWidth = "0";
|
|
214
|
+
pushTrack.style.flex = "1 1 auto";
|
|
215
|
+
pushTrack.style.alignItems = "stretch";
|
|
216
|
+
pushTrack.style.transform = "none";
|
|
217
|
+
pushTrack.style.transition = "none";
|
|
218
|
+
contentSlot.style.flex = "1 1 auto";
|
|
219
|
+
contentSlot.style.width = "100%";
|
|
220
|
+
contentSlot.style.maxWidth = "100%";
|
|
221
|
+
contentSlot.style.minWidth = "0";
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
shell.removeAttribute("data-persona-dock-mobile-fullscreen");
|
|
228
|
+
clearMobileFullscreenDockSlotStyles(dockSlot);
|
|
229
|
+
|
|
230
|
+
if (dock.reveal === "overlay") {
|
|
231
|
+
shell.style.display = "flex";
|
|
232
|
+
shell.style.flexDirection = "row";
|
|
233
|
+
shell.style.alignItems = "stretch";
|
|
234
|
+
shell.style.overflow = "hidden";
|
|
235
|
+
shell.dataset.personaDockReveal = "overlay";
|
|
236
|
+
clearPushTrackStyles(pushTrack);
|
|
237
|
+
clearResizeDockSlotTransition(dockSlot);
|
|
238
|
+
resetContentSlotFlexSizing(contentSlot);
|
|
239
|
+
clearEmergeDockStyles(host, dockSlot);
|
|
240
|
+
|
|
241
|
+
const dockTransition = dock.animate ? "transform 180ms ease" : "none";
|
|
242
|
+
const translateClosed = dock.side === "right" ? "translateX(100%)" : "translateX(-100%)";
|
|
243
|
+
const translate = expanded ? "translateX(0)" : translateClosed;
|
|
244
|
+
|
|
245
|
+
dockSlot.style.display = "flex";
|
|
246
|
+
dockSlot.style.flexDirection = "column";
|
|
247
|
+
dockSlot.style.flex = "none";
|
|
248
|
+
dockSlot.style.position = "absolute";
|
|
249
|
+
dockSlot.style.top = "0";
|
|
250
|
+
dockSlot.style.bottom = "0";
|
|
251
|
+
dockSlot.style.width = dock.width;
|
|
252
|
+
dockSlot.style.maxWidth = dock.width;
|
|
253
|
+
dockSlot.style.minWidth = dock.width;
|
|
254
|
+
dockSlot.style.minHeight = "0";
|
|
255
|
+
dockSlot.style.overflow = "hidden";
|
|
256
|
+
dockSlot.style.transition = dockTransition;
|
|
257
|
+
dockSlot.style.transform = translate;
|
|
258
|
+
dockSlot.style.pointerEvents = expanded ? "auto" : "none";
|
|
259
|
+
dockSlot.style.zIndex = "2";
|
|
260
|
+
if (dock.side === "right") {
|
|
261
|
+
dockSlot.style.right = "0";
|
|
262
|
+
dockSlot.style.left = "";
|
|
263
|
+
} else {
|
|
264
|
+
dockSlot.style.left = "0";
|
|
265
|
+
dockSlot.style.right = "";
|
|
266
|
+
}
|
|
267
|
+
} else if (dock.reveal === "push") {
|
|
268
|
+
// Row flex so the wide push track is laid out on the horizontal axis; column was stretching
|
|
269
|
+
// the track to the shell width and fighting explicit width, which could confuse overflow.
|
|
270
|
+
shell.style.display = "flex";
|
|
271
|
+
shell.style.flexDirection = "row";
|
|
272
|
+
shell.style.alignItems = "stretch";
|
|
273
|
+
shell.style.overflow = "hidden";
|
|
274
|
+
shell.dataset.personaDockReveal = "push";
|
|
275
|
+
clearResizeDockSlotTransition(dockSlot);
|
|
276
|
+
clearOverlayDockSlotStyles(dockSlot);
|
|
277
|
+
clearEmergeDockStyles(host, dockSlot);
|
|
278
|
+
|
|
279
|
+
const panelPx = parseDockWidthToPx(dock.width, shell.clientWidth);
|
|
280
|
+
const contentPx = Math.max(0, shell.clientWidth);
|
|
281
|
+
const dockTransition = dock.animate ? "transform 180ms ease" : "none";
|
|
282
|
+
const translate =
|
|
283
|
+
dock.side === "right"
|
|
284
|
+
? expanded
|
|
285
|
+
? `translateX(-${panelPx}px)`
|
|
286
|
+
: "translateX(0)"
|
|
287
|
+
: expanded
|
|
288
|
+
? "translateX(0)"
|
|
289
|
+
: `translateX(-${panelPx}px)`;
|
|
290
|
+
|
|
291
|
+
pushTrack.style.display = "flex";
|
|
292
|
+
pushTrack.style.flexDirection = "row";
|
|
293
|
+
pushTrack.style.flex = "0 0 auto";
|
|
294
|
+
pushTrack.style.minHeight = "0";
|
|
295
|
+
pushTrack.style.minWidth = "0";
|
|
296
|
+
pushTrack.style.alignItems = "stretch";
|
|
297
|
+
pushTrack.style.height = "100%";
|
|
298
|
+
pushTrack.style.width = `${contentPx + panelPx}px`;
|
|
299
|
+
pushTrack.style.transition = dockTransition;
|
|
300
|
+
pushTrack.style.transform = translate;
|
|
301
|
+
|
|
302
|
+
contentSlot.style.flex = "0 0 auto";
|
|
303
|
+
contentSlot.style.flexGrow = "0";
|
|
304
|
+
contentSlot.style.flexShrink = "0";
|
|
305
|
+
contentSlot.style.width = `${contentPx}px`;
|
|
306
|
+
contentSlot.style.maxWidth = `${contentPx}px`;
|
|
307
|
+
contentSlot.style.minWidth = `${contentPx}px`;
|
|
308
|
+
|
|
309
|
+
dockSlot.style.display = "flex";
|
|
310
|
+
dockSlot.style.flexDirection = "column";
|
|
311
|
+
dockSlot.style.flex = "0 0 auto";
|
|
312
|
+
dockSlot.style.flexShrink = "0";
|
|
313
|
+
dockSlot.style.width = dock.width;
|
|
314
|
+
dockSlot.style.minWidth = dock.width;
|
|
315
|
+
dockSlot.style.maxWidth = dock.width;
|
|
316
|
+
dockSlot.style.position = "relative";
|
|
317
|
+
dockSlot.style.overflow = "hidden";
|
|
318
|
+
dockSlot.style.transition = "none";
|
|
319
|
+
dockSlot.style.pointerEvents = expanded ? "auto" : "none";
|
|
320
|
+
} else {
|
|
321
|
+
shell.style.display = "flex";
|
|
322
|
+
shell.style.flexDirection = "row";
|
|
323
|
+
shell.style.alignItems = "stretch";
|
|
324
|
+
shell.style.overflow = "";
|
|
325
|
+
clearPushTrackStyles(pushTrack);
|
|
326
|
+
clearOverlayDockSlotStyles(dockSlot);
|
|
327
|
+
resetContentSlotFlexSizing(contentSlot);
|
|
328
|
+
clearEmergeDockStyles(host, dockSlot);
|
|
329
|
+
|
|
330
|
+
const isEmerge = dock.reveal === "emerge";
|
|
331
|
+
if (isEmerge) {
|
|
332
|
+
shell.dataset.personaDockReveal = "emerge";
|
|
333
|
+
} else {
|
|
334
|
+
shell.removeAttribute("data-persona-dock-reveal");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const width = expanded ? dock.width : "0px";
|
|
338
|
+
const dockTransition = dock.animate
|
|
339
|
+
? "width 180ms ease, min-width 180ms ease, max-width 180ms ease, flex-basis 180ms ease"
|
|
340
|
+
: "none";
|
|
341
|
+
const collapsedClosed = !expanded;
|
|
342
|
+
|
|
343
|
+
dockSlot.style.display = "flex";
|
|
344
|
+
dockSlot.style.flexDirection = "column";
|
|
345
|
+
dockSlot.style.flex = `0 0 ${width}`;
|
|
346
|
+
dockSlot.style.width = width;
|
|
347
|
+
dockSlot.style.maxWidth = width;
|
|
348
|
+
dockSlot.style.minWidth = width;
|
|
349
|
+
dockSlot.style.minHeight = "0";
|
|
350
|
+
dockSlot.style.position = "relative";
|
|
351
|
+
dockSlot.style.overflow =
|
|
352
|
+
isEmerge ? "hidden" : collapsedClosed ? "hidden" : "visible";
|
|
353
|
+
dockSlot.style.transition = dockTransition;
|
|
354
|
+
|
|
355
|
+
if (isEmerge) {
|
|
356
|
+
dockSlot.style.alignItems = dock.side === "right" ? "flex-start" : "flex-end";
|
|
357
|
+
host.style.width = dock.width;
|
|
358
|
+
host.style.minWidth = dock.width;
|
|
359
|
+
host.style.maxWidth = dock.width;
|
|
360
|
+
host.style.boxSizing = "border-box";
|
|
80
361
|
}
|
|
81
|
-
} else if (shell.lastElementChild !== dockSlot) {
|
|
82
|
-
shell.replaceChildren(contentSlot, dockSlot);
|
|
83
362
|
}
|
|
84
363
|
};
|
|
85
364
|
|
|
@@ -117,11 +396,13 @@ const createDockedLayout = (target: HTMLElement, config?: AgentWidgetConfig): Wi
|
|
|
117
396
|
|
|
118
397
|
const originalNextSibling = target.nextSibling;
|
|
119
398
|
const shell = ownerDocument.createElement("div");
|
|
399
|
+
const pushTrack = ownerDocument.createElement("div");
|
|
120
400
|
const contentSlot = ownerDocument.createElement("div");
|
|
121
401
|
const dockSlot = ownerDocument.createElement("aside");
|
|
122
402
|
const host = ownerDocument.createElement("div");
|
|
123
403
|
let expanded = (config?.launcher?.enabled ?? true) ? (config?.launcher?.autoExpand ?? false) : true;
|
|
124
404
|
|
|
405
|
+
pushTrack.dataset.personaDockRole = "push-track";
|
|
125
406
|
contentSlot.dataset.personaDockRole = "content";
|
|
126
407
|
dockSlot.dataset.personaDockRole = "panel";
|
|
127
408
|
host.dataset.personaDockRole = "host";
|
|
@@ -129,9 +410,45 @@ const createDockedLayout = (target: HTMLElement, config?: AgentWidgetConfig): Wi
|
|
|
129
410
|
dockSlot.appendChild(host);
|
|
130
411
|
originalParent.insertBefore(shell, target);
|
|
131
412
|
contentSlot.appendChild(target);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
413
|
+
|
|
414
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
415
|
+
|
|
416
|
+
const disconnectResizeObserver = (): void => {
|
|
417
|
+
resizeObserver?.disconnect();
|
|
418
|
+
resizeObserver = null;
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const syncPushResizeObserver = (): void => {
|
|
422
|
+
disconnectResizeObserver();
|
|
423
|
+
if (resolveDockConfig(config).reveal !== "push") return;
|
|
424
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
425
|
+
resizeObserver = new ResizeObserver(() => {
|
|
426
|
+
applyDockStyles(shell, pushTrack, contentSlot, dockSlot, host, config, expanded);
|
|
427
|
+
});
|
|
428
|
+
resizeObserver.observe(shell);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const layout = (): void => {
|
|
432
|
+
applyDockStyles(shell, pushTrack, contentSlot, dockSlot, host, config, expanded);
|
|
433
|
+
syncPushResizeObserver();
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const ownerWindow = shell.ownerDocument.defaultView;
|
|
437
|
+
const onViewportResize = (): void => {
|
|
438
|
+
layout();
|
|
439
|
+
};
|
|
440
|
+
ownerWindow?.addEventListener("resize", onViewportResize);
|
|
441
|
+
|
|
442
|
+
if (resolveDockConfig(config).reveal === "push") {
|
|
443
|
+
pushTrack.appendChild(contentSlot);
|
|
444
|
+
pushTrack.appendChild(dockSlot);
|
|
445
|
+
shell.appendChild(pushTrack);
|
|
446
|
+
} else {
|
|
447
|
+
shell.appendChild(contentSlot);
|
|
448
|
+
shell.appendChild(dockSlot);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
layout();
|
|
135
452
|
|
|
136
453
|
return {
|
|
137
454
|
mode: "docked",
|
|
@@ -141,16 +458,18 @@ const createDockedLayout = (target: HTMLElement, config?: AgentWidgetConfig): Wi
|
|
|
141
458
|
const nextExpanded = state.launcherEnabled ? state.open : true;
|
|
142
459
|
if (expanded === nextExpanded) return;
|
|
143
460
|
expanded = nextExpanded;
|
|
144
|
-
|
|
461
|
+
layout();
|
|
145
462
|
},
|
|
146
463
|
updateConfig(nextConfig?: AgentWidgetConfig) {
|
|
147
464
|
config = nextConfig;
|
|
148
465
|
if ((config?.launcher?.enabled ?? true) === false) {
|
|
149
466
|
expanded = true;
|
|
150
467
|
}
|
|
151
|
-
|
|
468
|
+
layout();
|
|
152
469
|
},
|
|
153
470
|
destroy() {
|
|
471
|
+
ownerWindow?.removeEventListener("resize", onViewportResize);
|
|
472
|
+
disconnectResizeObserver();
|
|
154
473
|
if (originalParent.isConnected) {
|
|
155
474
|
if (originalNextSibling && originalNextSibling.parentNode === originalParent) {
|
|
156
475
|
originalParent.insertBefore(target, originalNextSibling);
|
package/src/runtime/init.test.ts
CHANGED
|
@@ -104,7 +104,7 @@ describe("initAgentWidget docked mode", () => {
|
|
|
104
104
|
config: {
|
|
105
105
|
launcher: {
|
|
106
106
|
mountMode: "docked",
|
|
107
|
-
dock: { width: "420px"
|
|
107
|
+
dock: { width: "420px" },
|
|
108
108
|
},
|
|
109
109
|
},
|
|
110
110
|
});
|
|
@@ -146,7 +146,7 @@ describe("initAgentWidget docked mode", () => {
|
|
|
146
146
|
launcher: {
|
|
147
147
|
mountMode: "docked",
|
|
148
148
|
autoExpand: true,
|
|
149
|
-
dock: { width: "400px"
|
|
149
|
+
dock: { width: "400px" },
|
|
150
150
|
},
|
|
151
151
|
},
|
|
152
152
|
});
|
|
@@ -155,12 +155,82 @@ describe("initAgentWidget docked mode", () => {
|
|
|
155
155
|
expect(panelSlot.style.width).toBe("400px");
|
|
156
156
|
|
|
157
157
|
handle.close();
|
|
158
|
-
expect(panelSlot.style.width).toBe("
|
|
158
|
+
expect(panelSlot.style.width).toBe("0px");
|
|
159
159
|
|
|
160
160
|
handle.open();
|
|
161
161
|
expect(panelSlot.style.width).toBe("400px");
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
+
it("overlay dock reveal keeps width and uses transform when closing", async () => {
|
|
165
|
+
const { initAgentWidget } = await import("./init");
|
|
166
|
+
document.body.innerHTML = `<div id="content">Workspace</div>`;
|
|
167
|
+
|
|
168
|
+
const handle = initAgentWidget({
|
|
169
|
+
target: "#content",
|
|
170
|
+
config: {
|
|
171
|
+
launcher: {
|
|
172
|
+
mountMode: "docked",
|
|
173
|
+
autoExpand: true,
|
|
174
|
+
dock: { width: "400px", reveal: "overlay" },
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const panelSlot = document.querySelector<HTMLElement>('[data-persona-dock-role="panel"]')!;
|
|
180
|
+
expect(panelSlot.style.width).toBe("400px");
|
|
181
|
+
|
|
182
|
+
handle.close();
|
|
183
|
+
expect(panelSlot.style.width).toBe("400px");
|
|
184
|
+
expect(panelSlot.style.transform).toBe("translateX(100%)");
|
|
185
|
+
|
|
186
|
+
handle.open();
|
|
187
|
+
expect(panelSlot.style.transform).toBe("translateX(0)");
|
|
188
|
+
|
|
189
|
+
handle.destroy();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("push dock reveal translates the push-track; panel keeps width when closing", async () => {
|
|
193
|
+
const { initAgentWidget } = await import("./init");
|
|
194
|
+
const wrapper = document.createElement("div");
|
|
195
|
+
wrapper.style.width = "900px";
|
|
196
|
+
document.body.appendChild(wrapper);
|
|
197
|
+
wrapper.innerHTML = `<div id="content">Workspace</div>`;
|
|
198
|
+
|
|
199
|
+
const handle = initAgentWidget({
|
|
200
|
+
target: "#content",
|
|
201
|
+
config: {
|
|
202
|
+
launcher: {
|
|
203
|
+
mountMode: "docked",
|
|
204
|
+
autoExpand: true,
|
|
205
|
+
dock: { width: "400px", reveal: "push" },
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const shell = document.querySelector<HTMLElement>('[data-persona-host-layout="docked"]')!;
|
|
211
|
+
Object.defineProperty(shell, "clientWidth", { get: () => 900, configurable: true });
|
|
212
|
+
handle.update({
|
|
213
|
+
launcher: {
|
|
214
|
+
mountMode: "docked",
|
|
215
|
+
autoExpand: true,
|
|
216
|
+
dock: { width: "400px", reveal: "push" },
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const pushTrack = shell.querySelector<HTMLElement>('[data-persona-dock-role="push-track"]');
|
|
221
|
+
const panelSlot = shell.querySelector<HTMLElement>('[data-persona-dock-role="panel"]')!;
|
|
222
|
+
expect(pushTrack).not.toBeNull();
|
|
223
|
+
expect(panelSlot.style.width).toBe("400px");
|
|
224
|
+
expect(pushTrack?.style.transform).toBe("translateX(-400px)");
|
|
225
|
+
|
|
226
|
+
handle.close();
|
|
227
|
+
expect(panelSlot.style.width).toBe("400px");
|
|
228
|
+
expect(pushTrack?.style.transform).toBe("translateX(0)");
|
|
229
|
+
|
|
230
|
+
handle.destroy();
|
|
231
|
+
wrapper.remove();
|
|
232
|
+
});
|
|
233
|
+
|
|
164
234
|
it("rebuilds when mount mode changes from floating to docked", async () => {
|
|
165
235
|
const { initAgentWidget } = await import("./init");
|
|
166
236
|
document.body.innerHTML = `<div id="content">Workspace</div>`;
|
|
@@ -178,7 +248,7 @@ describe("initAgentWidget docked mode", () => {
|
|
|
178
248
|
handle.update({
|
|
179
249
|
launcher: {
|
|
180
250
|
mountMode: "docked",
|
|
181
|
-
dock: { side: "left", width: "460px"
|
|
251
|
+
dock: { side: "left", width: "460px" },
|
|
182
252
|
},
|
|
183
253
|
});
|
|
184
254
|
|
|
@@ -197,7 +267,7 @@ describe("initAgentWidget docked mode", () => {
|
|
|
197
267
|
config: {
|
|
198
268
|
launcher: {
|
|
199
269
|
mountMode: "docked",
|
|
200
|
-
dock: { side: "right", width: "420px"
|
|
270
|
+
dock: { side: "right", width: "420px" },
|
|
201
271
|
},
|
|
202
272
|
},
|
|
203
273
|
});
|
|
@@ -205,7 +275,7 @@ describe("initAgentWidget docked mode", () => {
|
|
|
205
275
|
expect(createAgentExperienceMock).toHaveBeenCalledTimes(1);
|
|
206
276
|
handle.update({
|
|
207
277
|
launcher: {
|
|
208
|
-
dock: { side: "left", width: "500px"
|
|
278
|
+
dock: { side: "left", width: "500px" },
|
|
209
279
|
},
|
|
210
280
|
});
|
|
211
281
|
|
|
@@ -213,7 +283,7 @@ describe("initAgentWidget docked mode", () => {
|
|
|
213
283
|
const shell = document.querySelector<HTMLElement>('[data-persona-host-layout="docked"]');
|
|
214
284
|
const panelSlot = document.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
215
285
|
expect(shell?.firstElementChild?.getAttribute("data-persona-dock-role")).toBe("panel");
|
|
216
|
-
expect(panelSlot?.style.width).toBe("
|
|
286
|
+
expect(panelSlot?.style.width).toBe("0px");
|
|
217
287
|
});
|
|
218
288
|
|
|
219
289
|
it("supports shadow DOM hosts in docked mode", async () => {
|
|
@@ -231,6 +301,41 @@ describe("initAgentWidget docked mode", () => {
|
|
|
231
301
|
});
|
|
232
302
|
|
|
233
303
|
expect(handle.host.shadowRoot).not.toBeNull();
|
|
234
|
-
expect(handle.host.shadowRoot?.querySelector("
|
|
304
|
+
expect(handle.host.shadowRoot?.querySelector("[data-persona-root]")).not.toBeNull();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("mounts two widgets with independent roots in light DOM", async () => {
|
|
308
|
+
const { initAgentWidget } = await import("./init");
|
|
309
|
+
document.body.innerHTML = `
|
|
310
|
+
<div id="widget-a"></div>
|
|
311
|
+
<div id="widget-b"></div>
|
|
312
|
+
`;
|
|
313
|
+
|
|
314
|
+
const handleA = initAgentWidget({
|
|
315
|
+
target: "#widget-a",
|
|
316
|
+
config: {
|
|
317
|
+
launcher: { enabled: false },
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const handleB = initAgentWidget({
|
|
322
|
+
target: "#widget-b",
|
|
323
|
+
config: {
|
|
324
|
+
launcher: { enabled: false },
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const roots = document.querySelectorAll("[data-persona-root]");
|
|
329
|
+
expect(roots.length).toBe(2);
|
|
330
|
+
|
|
331
|
+
// Each root should be inside its respective target
|
|
332
|
+
const rootA = document.querySelector("#widget-a [data-persona-root]");
|
|
333
|
+
const rootB = document.querySelector("#widget-b [data-persona-root]");
|
|
334
|
+
expect(rootA).not.toBeNull();
|
|
335
|
+
expect(rootB).not.toBeNull();
|
|
336
|
+
expect(rootA).not.toBe(rootB);
|
|
337
|
+
|
|
338
|
+
handleA.destroy();
|
|
339
|
+
handleB.destroy();
|
|
235
340
|
});
|
|
236
341
|
});
|
package/src/runtime/init.ts
CHANGED
|
@@ -103,7 +103,7 @@ export const initAgentWidget = (
|
|
|
103
103
|
const launcherEnabled = nextConfig?.launcher?.enabled ?? true;
|
|
104
104
|
const shouldFillHost = !launcherEnabled || isDockedMountMode(nextConfig);
|
|
105
105
|
const mount = ownerDocument.createElement("div");
|
|
106
|
-
mount.
|
|
106
|
+
mount.setAttribute("data-persona-root", "true");
|
|
107
107
|
|
|
108
108
|
if (shouldFillHost) {
|
|
109
109
|
mount.style.height = "100%";
|