@runtypelabs/persona 4.15.0 → 4.16.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/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/context-mentions.d.cts +283 -9
- package/dist/context-mentions.d.ts +283 -9
- package/dist/index.cjs +68 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +299 -10
- package/dist/index.d.ts +299 -10
- package/dist/index.global.js +57 -57
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +69 -69
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/smart-dom-reader.d.cts +283 -9
- package/dist/smart-dom-reader.d.ts +283 -9
- package/dist/testing.cjs +3 -3
- package/dist/testing.d.cts +7 -0
- package/dist/testing.d.ts +7 -0
- package/dist/testing.js +3 -3
- package/dist/theme-editor-preview.cjs +64 -64
- package/dist/theme-editor-preview.d.cts +283 -9
- package/dist/theme-editor-preview.d.ts +283 -9
- package/dist/theme-editor-preview.js +56 -56
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +283 -10
- package/dist/theme-editor.d.ts +283 -10
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +66 -3
- package/dist/theme-reference.d.ts +66 -3
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +1 -1
- package/src/components/artifact-pane.test.ts +27 -0
- package/src/components/artifact-pane.ts +7 -3
- package/src/components/header-builder.ts +27 -2
- package/src/components/header-layouts.test.ts +56 -0
- package/src/components/header-layouts.ts +29 -9
- package/src/components/launcher-surface.test.ts +403 -0
- package/src/components/launcher.ts +297 -2
- package/src/components/panel.ts +69 -74
- package/src/components/suggestions.test.ts +40 -0
- package/src/components/suggestions.ts +213 -153
- package/src/components/welcome.test.ts +121 -0
- package/src/components/welcome.ts +305 -0
- package/src/components/widget-view.test.ts +5 -3
- package/src/components/widget-view.ts +14 -7
- package/src/defaults.ts +2 -2
- package/src/index-core.ts +12 -1
- package/src/launcher-global.ts +10 -7
- package/src/plugins/types.ts +66 -1
- package/src/styles/widget-styles.test.ts +220 -0
- package/src/styles/widget.css +353 -9
- package/src/testing/mock-stream.test.ts +19 -0
- package/src/testing/mock-stream.ts +20 -0
- package/src/theme-editor/sections.ts +30 -1
- package/src/theme-reference.ts +26 -4
- package/src/types/theme.ts +40 -3
- package/src/types.ts +164 -6
- package/src/ui.body-top-slot.test.ts +95 -0
- package/src/ui.content-width.test.ts +91 -0
- package/src/ui.docked.test.ts +7 -4
- package/src/ui.launcher-teaser.test.ts +162 -0
- package/src/ui.rebuild-composer.test.ts +491 -0
- package/src/ui.render-welcome.test.ts +505 -0
- package/src/ui.scroll-additive.test.ts +91 -21
- package/src/ui.scroll.test.ts +302 -14
- package/src/ui.suggest-replies.test.ts +46 -0
- package/src/ui.ts +747 -136
- package/src/ui.welcome.test.ts +504 -0
- package/src/utils/artifact-gate.test.ts +27 -0
- package/src/utils/artifact-gate.ts +9 -0
- package/src/utils/attachment-manager.test.ts +37 -0
- package/src/utils/attachment-manager.ts +15 -0
- package/src/utils/auto-follow.test.ts +23 -0
- package/src/utils/auto-follow.ts +11 -1
- package/src/utils/config-merge.ts +2 -0
- package/src/utils/content-width.test.ts +37 -0
- package/src/utils/content-width.ts +17 -0
- package/src/utils/plugin-storage.test.ts +94 -0
- package/src/utils/plugin-storage.ts +98 -0
- package/src/utils/theme.test.ts +137 -0
- package/src/utils/tokens.ts +49 -3
- package/src/welcome.test.ts +213 -0
- package/src/welcome.ts +130 -0
|
@@ -106,6 +106,36 @@ describe("suggestion interaction styles", () => {
|
|
|
106
106
|
expect(rule).toContain("max-width: 600px");
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
+
it("centers label-only items and top-aligns only description cards", () => {
|
|
110
|
+
const cardStart = widgetCss.indexOf(".persona-suggestion--card {");
|
|
111
|
+
const cardRule = widgetCss.slice(
|
|
112
|
+
cardStart,
|
|
113
|
+
widgetCss.indexOf("\n}", cardStart),
|
|
114
|
+
);
|
|
115
|
+
expect(cardRule).toContain("align-items: center");
|
|
116
|
+
|
|
117
|
+
const listStart = widgetCss.indexOf(".persona-suggestion--list {");
|
|
118
|
+
const listRule = widgetCss.slice(
|
|
119
|
+
listStart,
|
|
120
|
+
widgetCss.indexOf("\n}", listStart),
|
|
121
|
+
);
|
|
122
|
+
expect(listRule).toContain("align-items: center");
|
|
123
|
+
|
|
124
|
+
// Only content that actually wraps to two lines earns the top alignment.
|
|
125
|
+
const hasStart = widgetCss.indexOf(
|
|
126
|
+
".persona-suggestion--card:has(.persona-suggestion__description)",
|
|
127
|
+
);
|
|
128
|
+
const hasRule = widgetCss.slice(
|
|
129
|
+
hasStart,
|
|
130
|
+
widgetCss.indexOf("\n}", hasStart),
|
|
131
|
+
);
|
|
132
|
+
expect(hasStart).toBeGreaterThan(-1);
|
|
133
|
+
expect(hasRule).toContain(
|
|
134
|
+
".persona-suggestion--list:has(.persona-suggestion__description)",
|
|
135
|
+
);
|
|
136
|
+
expect(hasRule).toContain("align-items: flex-start");
|
|
137
|
+
});
|
|
138
|
+
|
|
109
139
|
it("masks only the scrollable chip edges that still contain content", () => {
|
|
110
140
|
expect(widgetCss).toContain(
|
|
111
141
|
'[data-overflow="scroll"][data-scroll-right]:not(',
|
|
@@ -120,6 +150,43 @@ describe("suggestion interaction styles", () => {
|
|
|
120
150
|
"--persona-suggestion-scroll-fade-size: 32px",
|
|
121
151
|
);
|
|
122
152
|
});
|
|
153
|
+
|
|
154
|
+
it("centers the chip row on the composer surface only", () => {
|
|
155
|
+
const selector =
|
|
156
|
+
'[data-persona-composer-suggestions].persona-suggestions[data-variant="chip"] {';
|
|
157
|
+
const start = widgetCss.indexOf(selector);
|
|
158
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
159
|
+
|
|
160
|
+
expect(start).toBeGreaterThan(-1);
|
|
161
|
+
// fit-content + auto margins center a single row while wrapped rows keep
|
|
162
|
+
// a shared left edge; per-row justify-content would rag them.
|
|
163
|
+
expect(rule).toContain("width: fit-content");
|
|
164
|
+
expect(rule).toContain("max-width: 100%");
|
|
165
|
+
expect(rule).toContain("margin-inline: auto");
|
|
166
|
+
expect(rule).not.toContain("justify-content");
|
|
167
|
+
|
|
168
|
+
// Transcript follow-ups and welcome rows keep the default left alignment.
|
|
169
|
+
const sharedStart = widgetCss.indexOf(
|
|
170
|
+
'.persona-suggestions[data-variant="chip"] {',
|
|
171
|
+
);
|
|
172
|
+
const sharedRule = widgetCss.slice(
|
|
173
|
+
sharedStart,
|
|
174
|
+
widgetCss.indexOf("\n}", sharedStart),
|
|
175
|
+
);
|
|
176
|
+
expect(sharedStart).toBeGreaterThan(-1);
|
|
177
|
+
expect(sharedRule).not.toContain("justify-content");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("only pulls follow-ups up against the roomy welcome-visible body gap", () => {
|
|
181
|
+
// Unscoped, the -12px offset would zero out the tightened 12px gap that
|
|
182
|
+
// applies when the welcome host is hidden, gluing follow-ups to the answer.
|
|
183
|
+
expect(widgetCss).toContain(
|
|
184
|
+
'.persona-gap-6 > [data-persona-suggestions="follow-up"] {',
|
|
185
|
+
);
|
|
186
|
+
expect(widgetCss).not.toMatch(
|
|
187
|
+
/\n\[data-persona-suggestions="follow-up"\] \{/,
|
|
188
|
+
);
|
|
189
|
+
});
|
|
123
190
|
});
|
|
124
191
|
|
|
125
192
|
describe("composer spacing styles", () => {
|
|
@@ -160,3 +227,156 @@ describe("composer spacing styles", () => {
|
|
|
160
227
|
expect(rule).toContain("gap: 8px");
|
|
161
228
|
});
|
|
162
229
|
});
|
|
230
|
+
|
|
231
|
+
describe("transcript layout styles", () => {
|
|
232
|
+
it("drops the empty messages wrapper from the flex flow", () => {
|
|
233
|
+
// A zero-height flex child still triggers the body's gap, so an empty
|
|
234
|
+
// transcript added phantom scrollable space below the welcome (and a
|
|
235
|
+
// scroll-to-bottom arrow with nothing to scroll to).
|
|
236
|
+
expect(widgetCss).toContain(
|
|
237
|
+
".persona-widget-messages:empty {\n display: none;\n}",
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe("launcher teaser styles", () => {
|
|
243
|
+
it("keeps the [hidden] dismiss control hidden despite its own display: grid", () => {
|
|
244
|
+
// dismissible: false sets the hidden property; without this companion
|
|
245
|
+
// rule the block's display: grid outranks the UA [hidden] stylesheet.
|
|
246
|
+
expect(widgetCss).toContain(
|
|
247
|
+
".persona-launcher-teaser-dismiss[hidden] {\n display: none;\n}",
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe("plugin welcome styles", () => {
|
|
253
|
+
it("hides the default welcome content while a plugin element owns the host", () => {
|
|
254
|
+
const selector =
|
|
255
|
+
'.persona-welcome[data-persona-welcome-content="plugin"]\n > :not([data-persona-welcome-plugin]) {';
|
|
256
|
+
const start = widgetCss.indexOf(selector);
|
|
257
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
258
|
+
|
|
259
|
+
expect(start).toBeGreaterThan(-1);
|
|
260
|
+
expect(rule).toContain("display: none !important");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("centers the welcome column at wide widths but never the overlay", () => {
|
|
264
|
+
const start = widgetCss.indexOf(
|
|
265
|
+
".persona-welcome:not([data-persona-welcome-overlay]) {",
|
|
266
|
+
);
|
|
267
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
268
|
+
|
|
269
|
+
expect(start).toBeGreaterThan(-1);
|
|
270
|
+
// The card variant shares the transcript column so its left-aligned
|
|
271
|
+
// text lines up with messages and composer.
|
|
272
|
+
expect(rule).toContain("max-width: var(--persona-content-max-width, 768px)");
|
|
273
|
+
expect(rule).toContain("margin-inline: auto");
|
|
274
|
+
// The var is published on the root so plugin content can consume it.
|
|
275
|
+
expect(widgetCss).toContain("--persona-welcome-max-width: 640px");
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("keeps the narrower centered column for the hero variant only", () => {
|
|
279
|
+
const start = widgetCss.indexOf(
|
|
280
|
+
'.persona-welcome[data-persona-welcome-variant="hero"]:not(',
|
|
281
|
+
);
|
|
282
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
283
|
+
|
|
284
|
+
expect(start).toBeGreaterThan(-1);
|
|
285
|
+
expect(rule).toContain("max-width: var(--persona-welcome-max-width, 640px)");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("aligns the greeting bubble to the transcript column", () => {
|
|
289
|
+
const start = widgetCss.indexOf(".persona-welcome-greeting {");
|
|
290
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
291
|
+
|
|
292
|
+
expect(start).toBeGreaterThan(-1);
|
|
293
|
+
expect(rule).toContain("max-width: var(--persona-content-max-width, 768px)");
|
|
294
|
+
expect(rule).toContain("margin-inline: auto");
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("pads the welcome host from the intro-card alias, full padding on overlays", () => {
|
|
298
|
+
// Flat default resolves the alias to `1.5rem 0` (tokens.ts), aligning
|
|
299
|
+
// the text with the content column; overlays are opaque surfaces and
|
|
300
|
+
// keep a fixed symmetric inset.
|
|
301
|
+
const start = widgetCss.indexOf(".persona-welcome {");
|
|
302
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
303
|
+
|
|
304
|
+
expect(start).toBeGreaterThan(-1);
|
|
305
|
+
expect(rule).toContain("padding: var(--persona-intro-card-padding, 1.5rem)");
|
|
306
|
+
|
|
307
|
+
const overlayStart = widgetCss.indexOf(
|
|
308
|
+
".persona-welcome[data-persona-welcome-overlay] {",
|
|
309
|
+
);
|
|
310
|
+
const overlayRule = widgetCss.slice(
|
|
311
|
+
overlayStart,
|
|
312
|
+
widgetCss.indexOf("\n}", overlayStart),
|
|
313
|
+
);
|
|
314
|
+
expect(overlayRule).toContain("padding: 1.5rem");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("overlays the messages area while plugin content is active", () => {
|
|
318
|
+
const start = widgetCss.indexOf(
|
|
319
|
+
".persona-welcome[data-persona-welcome-overlay] {",
|
|
320
|
+
);
|
|
321
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
322
|
+
|
|
323
|
+
expect(start).toBeGreaterThan(-1);
|
|
324
|
+
expect(rule).toContain("position: absolute");
|
|
325
|
+
expect(rule).toContain("inset: 0");
|
|
326
|
+
// !important: only way to beat the host's inline intro-card background
|
|
327
|
+
// (default transparent), which otherwise lets the transcript bleed
|
|
328
|
+
// through the overlay.
|
|
329
|
+
expect(rule).toMatch(/background:[\s\S]*!important/);
|
|
330
|
+
// Two classes so it outranks the body's own overflow utility.
|
|
331
|
+
expect(widgetCss).toContain(
|
|
332
|
+
".persona-widget-body.persona-welcome-overlay-active {\n overflow: hidden;\n}",
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe("scrollbar policy styles", () => {
|
|
338
|
+
it("themes every scroller from the shared tokens", () => {
|
|
339
|
+
// scrollbar-color inherits from the root; scrollbar-width does not, so
|
|
340
|
+
// the width needs the descendant star.
|
|
341
|
+
expect(widgetCss).toMatch(
|
|
342
|
+
/\[data-persona-root\] \{\n {2}scrollbar-color: var\(\n\s+--persona-scrollbar-thumb,/,
|
|
343
|
+
);
|
|
344
|
+
expect(widgetCss).toContain("var(--persona-scrollbar-track, transparent)");
|
|
345
|
+
expect(widgetCss).toContain(
|
|
346
|
+
"[data-persona-root],\n[data-persona-root] * {\n scrollbar-width: thin;\n}",
|
|
347
|
+
);
|
|
348
|
+
// The artifact tab strip keeps its own token as an alias layered on the
|
|
349
|
+
// shared thumb token.
|
|
350
|
+
expect(widgetCss).toContain(
|
|
351
|
+
"--persona-artifact-tab-list-scrollbar,\n var(--persona-scrollbar-thumb, var(--persona-border, #e5e7eb))",
|
|
352
|
+
);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("hides the transcript bar at rest under the on-scroll policy without reflow", () => {
|
|
356
|
+
// Rest state keys off the ui.ts-owned attributes; the reveal attribute
|
|
357
|
+
// must appear as a :not() so flipping it shows the bar again.
|
|
358
|
+
expect(widgetCss).toContain(
|
|
359
|
+
'[data-persona-scrollbar="on-scroll"]\n .persona-widget-body:not([data-persona-scrollbar-visible]),\n[data-persona-scrollbar="hidden"] .persona-widget-body {\n scrollbar-width: none;\n}',
|
|
360
|
+
);
|
|
361
|
+
// Old-Safari fallback: webkit hiding agrees with the standard property.
|
|
362
|
+
expect(widgetCss).toContain(
|
|
363
|
+
'[data-persona-scrollbar="hidden"] .persona-widget-body::-webkit-scrollbar {\n display: none;\n}',
|
|
364
|
+
);
|
|
365
|
+
// Classic-scrollbar platforms must not reflow the transcript on toggle.
|
|
366
|
+
expect(widgetCss).toContain(
|
|
367
|
+
'[data-persona-scrollbar="on-scroll"] .persona-widget-body {\n scrollbar-gutter: stable;\n}',
|
|
368
|
+
);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("hover-reveals inner scroller bars under the on-scroll policy", () => {
|
|
372
|
+
expect(widgetCss).toContain(
|
|
373
|
+
'[data-persona-scrollbar="on-scroll"] .persona-artifact-list:not(:hover)',
|
|
374
|
+
);
|
|
375
|
+
expect(widgetCss).toContain(
|
|
376
|
+
'[data-persona-scrollbar="on-scroll"]\n .persona-suggestions[data-overflow="scroll"]:not(:hover)',
|
|
377
|
+
);
|
|
378
|
+
expect(widgetCss).toContain(
|
|
379
|
+
'[data-persona-scrollbar="hidden"] .persona-artifact-list,',
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
});
|
package/src/styles/widget.css
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
font-size: var(--persona-font-size, 1rem);
|
|
5
5
|
font-weight: var(--persona-font-weight, 400);
|
|
6
6
|
line-height: var(--persona-line-height, 1.5);
|
|
7
|
+
|
|
8
|
+
/* Column widths plugin content can consume; --persona-content-max-width is
|
|
9
|
+
re-set inline from layout.contentMaxWidth (default 768px). */
|
|
10
|
+
--persona-welcome-max-width: 640px;
|
|
7
11
|
|
|
8
12
|
/* Theme-aware markdown variables - inherit from theme colors set by JavaScript */
|
|
9
13
|
--persona-md-code-block-bg: var(--persona-container, #f3f4f6);
|
|
@@ -236,6 +240,157 @@
|
|
|
236
240
|
border-radius: var(--persona-button-radius);
|
|
237
241
|
}
|
|
238
242
|
|
|
243
|
+
/* Launcher surface + teaser --------------------------------------------- */
|
|
244
|
+
|
|
245
|
+
/* Without a teaser the wrapper generates no box, so the launcher button keeps
|
|
246
|
+
its own fixed placement exactly as it did before the wrapper existed. */
|
|
247
|
+
.persona-launcher-surface {
|
|
248
|
+
display: contents;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.persona-launcher-surface[data-teaser] {
|
|
252
|
+
display: flex;
|
|
253
|
+
position: fixed;
|
|
254
|
+
flex-direction: column;
|
|
255
|
+
align-items: flex-end;
|
|
256
|
+
gap: 10px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.persona-launcher-surface[data-teaser^="top"] {
|
|
260
|
+
flex-direction: column-reverse;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.persona-launcher-surface[data-teaser$="left"] {
|
|
264
|
+
align-items: flex-start;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.persona-launcher-teaser {
|
|
268
|
+
display: flex;
|
|
269
|
+
align-items: flex-start;
|
|
270
|
+
gap: 2px;
|
|
271
|
+
max-width: min(260px, calc(100vw - 48px));
|
|
272
|
+
padding: 2px;
|
|
273
|
+
border: 1px solid var(--persona-border, #e5e7eb);
|
|
274
|
+
border-radius: var(--persona-radius-lg, 1rem);
|
|
275
|
+
background-color: var(--persona-surface, #ffffff);
|
|
276
|
+
box-shadow: var(
|
|
277
|
+
--persona-launcher-shadow,
|
|
278
|
+
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
|
279
|
+
0 4px 6px -4px rgba(0, 0, 0, 0.1)
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.persona-launcher-teaser[hidden] {
|
|
284
|
+
display: none;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.persona-launcher-teaser-text {
|
|
288
|
+
flex: 1 1 auto;
|
|
289
|
+
min-width: 0;
|
|
290
|
+
padding: 8px 10px;
|
|
291
|
+
border: 0;
|
|
292
|
+
border-radius: inherit;
|
|
293
|
+
background: transparent;
|
|
294
|
+
color: var(--persona-text, #111827);
|
|
295
|
+
font: inherit;
|
|
296
|
+
font-size: 0.875rem;
|
|
297
|
+
line-height: 1.4;
|
|
298
|
+
text-align: left;
|
|
299
|
+
cursor: pointer;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.persona-launcher-teaser-dismiss {
|
|
303
|
+
flex: 0 0 auto;
|
|
304
|
+
display: grid;
|
|
305
|
+
place-items: center;
|
|
306
|
+
width: 22px;
|
|
307
|
+
height: 22px;
|
|
308
|
+
margin-top: 4px;
|
|
309
|
+
padding: 0;
|
|
310
|
+
border: 0;
|
|
311
|
+
border-radius: var(--persona-launcher-radius, 9999px);
|
|
312
|
+
background: transparent;
|
|
313
|
+
color: var(--persona-muted, #6b7280);
|
|
314
|
+
font-size: 0.875rem;
|
|
315
|
+
line-height: 1;
|
|
316
|
+
cursor: pointer;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.persona-launcher-teaser-dismiss:hover {
|
|
320
|
+
background-color: var(--persona-border, #e5e7eb);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/* display: grid above beats the UA [hidden] rule; dismissible: false relies on this. */
|
|
324
|
+
.persona-launcher-teaser-dismiss[hidden] {
|
|
325
|
+
display: none;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* An empty transcript still counts as a flex child, so the body's gap
|
|
329
|
+
inserts phantom space below the welcome and fakes scrollable height. */
|
|
330
|
+
.persona-widget-messages:empty {
|
|
331
|
+
display: none;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* Scrollbars ------------------------------------------------------------- */
|
|
335
|
+
|
|
336
|
+
/* One appearance for every scroller in the widget: thin, themed thumb, bare
|
|
337
|
+
track. Standard properties only: mixing ::-webkit-scrollbar styling with
|
|
338
|
+
them makes Chromium drop the pseudo-element rules, so webkit selectors are
|
|
339
|
+
reserved for the hidden states below, where both mechanisms agree.
|
|
340
|
+
scrollbar-color inherits from the root; scrollbar-width does not, hence
|
|
341
|
+
the descendant rule. */
|
|
342
|
+
[data-persona-root] {
|
|
343
|
+
scrollbar-color: var(
|
|
344
|
+
--persona-scrollbar-thumb,
|
|
345
|
+
var(--persona-border, #e5e7eb)
|
|
346
|
+
)
|
|
347
|
+
var(--persona-scrollbar-track, transparent);
|
|
348
|
+
}
|
|
349
|
+
[data-persona-root],
|
|
350
|
+
[data-persona-root] * {
|
|
351
|
+
scrollbar-width: thin;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* "on-scroll" policy (the default): bars hide at rest. ui.ts stamps the
|
|
355
|
+
resolved policy on the root and flips data-persona-scrollbar-visible on
|
|
356
|
+
the transcript while the reader is scrolling or away from the resting
|
|
357
|
+
position. The reserved gutter keeps classic scrollbars (Windows,
|
|
358
|
+
always-show macOS) from reflowing the transcript on toggle. */
|
|
359
|
+
[data-persona-scrollbar="on-scroll"] .persona-widget-body {
|
|
360
|
+
scrollbar-gutter: stable;
|
|
361
|
+
}
|
|
362
|
+
[data-persona-scrollbar="on-scroll"]
|
|
363
|
+
.persona-widget-body:not([data-persona-scrollbar-visible]),
|
|
364
|
+
[data-persona-scrollbar="hidden"] .persona-widget-body {
|
|
365
|
+
scrollbar-width: none;
|
|
366
|
+
}
|
|
367
|
+
[data-persona-scrollbar="on-scroll"]
|
|
368
|
+
.persona-widget-body:not([data-persona-scrollbar-visible])::-webkit-scrollbar,
|
|
369
|
+
[data-persona-scrollbar="hidden"] .persona-widget-body::-webkit-scrollbar {
|
|
370
|
+
display: none;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* Inner scrollers under the same policy: hidden at rest, hover reveals for
|
|
374
|
+
pointer users. Edge fades stay the primary overflow affordance there, and
|
|
375
|
+
morph re-renders make per-element JS wiring fragile, so hover is the whole
|
|
376
|
+
reveal story for these surfaces. */
|
|
377
|
+
[data-persona-scrollbar="on-scroll"] .persona-artifact-list:not(:hover),
|
|
378
|
+
[data-persona-scrollbar="on-scroll"]
|
|
379
|
+
.persona-suggestions[data-overflow="scroll"]:not(:hover),
|
|
380
|
+
[data-persona-scrollbar="hidden"] .persona-artifact-list,
|
|
381
|
+
[data-persona-scrollbar="hidden"] .persona-suggestions[data-overflow="scroll"] {
|
|
382
|
+
scrollbar-width: none;
|
|
383
|
+
}
|
|
384
|
+
[data-persona-scrollbar="on-scroll"]
|
|
385
|
+
.persona-artifact-list:not(:hover)::-webkit-scrollbar,
|
|
386
|
+
[data-persona-scrollbar="on-scroll"]
|
|
387
|
+
.persona-suggestions[data-overflow="scroll"]:not(:hover)::-webkit-scrollbar,
|
|
388
|
+
[data-persona-scrollbar="hidden"] .persona-artifact-list::-webkit-scrollbar,
|
|
389
|
+
[data-persona-scrollbar="hidden"]
|
|
390
|
+
.persona-suggestions[data-overflow="scroll"]::-webkit-scrollbar {
|
|
391
|
+
display: none;
|
|
392
|
+
}
|
|
393
|
+
|
|
239
394
|
/* Suggestions ----------------------------------------------------------- */
|
|
240
395
|
|
|
241
396
|
.persona-suggestions {
|
|
@@ -254,13 +409,28 @@
|
|
|
254
409
|
display: none !important;
|
|
255
410
|
}
|
|
256
411
|
|
|
412
|
+
/* `itemGap` is the space between items; the variant-less 8px above stays the
|
|
413
|
+
fallback for containers rendered before a variant is assigned. */
|
|
414
|
+
.persona-suggestions[data-variant="chip"] {
|
|
415
|
+
gap: var(--persona-components-suggestion-chip-itemGap, 8px);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.persona-suggestions[data-variant="card"] {
|
|
419
|
+
gap: var(--persona-components-suggestion-card-itemGap, 8px);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.persona-suggestions[data-variant="list"] {
|
|
423
|
+
gap: var(--persona-components-suggestion-list-itemGap, 8px);
|
|
424
|
+
}
|
|
425
|
+
|
|
257
426
|
.persona-suggestions[data-persona-suggestion-surface="starter"] {
|
|
258
427
|
margin-top: 16px;
|
|
259
428
|
}
|
|
260
429
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
430
|
+
/* Follow-ups sit 12px from their answer. The body gap is 24px with the
|
|
431
|
+
welcome host visible and already 12px when it is hidden, so the pull-up
|
|
432
|
+
only applies in the roomy case. */
|
|
433
|
+
.persona-gap-6 > [data-persona-suggestions="follow-up"] {
|
|
264
434
|
margin-top: -12px;
|
|
265
435
|
}
|
|
266
436
|
|
|
@@ -352,6 +522,15 @@
|
|
|
352
522
|
flex-wrap: wrap;
|
|
353
523
|
}
|
|
354
524
|
|
|
525
|
+
/* Composer surface only: transcript follow-ups and welcome rows stay
|
|
526
|
+
left-aligned. fit-content centers a single row while wrapped rows keep a
|
|
527
|
+
shared left edge; per-row `justify-content: center` would rag them. */
|
|
528
|
+
[data-persona-composer-suggestions].persona-suggestions[data-variant="chip"] {
|
|
529
|
+
width: fit-content;
|
|
530
|
+
max-width: 100%;
|
|
531
|
+
margin-inline: auto;
|
|
532
|
+
}
|
|
533
|
+
|
|
355
534
|
.persona-suggestion {
|
|
356
535
|
appearance: none;
|
|
357
536
|
display: flex;
|
|
@@ -398,7 +577,7 @@
|
|
|
398
577
|
}
|
|
399
578
|
|
|
400
579
|
.persona-suggestion--card {
|
|
401
|
-
align-items:
|
|
580
|
+
align-items: center;
|
|
402
581
|
min-height: var(--persona-components-suggestion-card-minHeight, 72px);
|
|
403
582
|
gap: var(--persona-components-suggestion-card-gap, 0.625rem);
|
|
404
583
|
padding: var(--persona-components-suggestion-card-padding, 1rem);
|
|
@@ -422,7 +601,7 @@
|
|
|
422
601
|
}
|
|
423
602
|
|
|
424
603
|
.persona-suggestion--list {
|
|
425
|
-
align-items:
|
|
604
|
+
align-items: center;
|
|
426
605
|
width: 100%;
|
|
427
606
|
min-height: var(--persona-components-suggestion-list-minHeight, 44px);
|
|
428
607
|
gap: var(--persona-components-suggestion-list-gap, 0.625rem);
|
|
@@ -445,6 +624,13 @@
|
|
|
445
624
|
line-height: var(--persona-components-suggestion-list-lineHeight, 1.35);
|
|
446
625
|
}
|
|
447
626
|
|
|
627
|
+
/* Title + description content reads top-aligned; a lone label centers in
|
|
628
|
+
the min-height instead of leaving dead space under the text. */
|
|
629
|
+
.persona-suggestion--card:has(.persona-suggestion__description),
|
|
630
|
+
.persona-suggestion--list:has(.persona-suggestion__description) {
|
|
631
|
+
align-items: flex-start;
|
|
632
|
+
}
|
|
633
|
+
|
|
448
634
|
.persona-suggestion__icon,
|
|
449
635
|
.persona-suggestion__arrow {
|
|
450
636
|
flex: 0 0 auto;
|
|
@@ -648,6 +834,154 @@
|
|
|
648
834
|
}
|
|
649
835
|
}
|
|
650
836
|
|
|
837
|
+
/* Welcome surface ------------------------------------------------------- */
|
|
838
|
+
|
|
839
|
+
/* Interior padding rides the intro-card alias: the tokens layer zeroes the
|
|
840
|
+
horizontal component when the card resolves flat (no background, no
|
|
841
|
+
shadow), so default installs align the title with the content column
|
|
842
|
+
instead of indenting it by an invisible card inset. */
|
|
843
|
+
.persona-welcome {
|
|
844
|
+
padding: var(--persona-intro-card-padding, 1.5rem);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.persona-welcome[hidden],
|
|
848
|
+
.persona-welcome-greeting[hidden] {
|
|
849
|
+
display: none !important;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.persona-welcome-icon {
|
|
853
|
+
display: flex;
|
|
854
|
+
margin-bottom: 12px;
|
|
855
|
+
color: var(--persona-accent, currentColor);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.persona-welcome-icon[hidden] {
|
|
859
|
+
display: none !important;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.persona-welcome-icon-image {
|
|
863
|
+
width: 40px;
|
|
864
|
+
height: 40px;
|
|
865
|
+
border-radius: 999px;
|
|
866
|
+
object-fit: cover;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
.persona-welcome-icon-text {
|
|
870
|
+
font-size: 32px;
|
|
871
|
+
line-height: 1;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/* Welcome copy: `components.introCard.title` / `.subtitle` typography.
|
|
875
|
+
Fallbacks reproduce the utility classes these rules replaced, so an
|
|
876
|
+
unset token renders pixel-identically. Margins stay untokenized. */
|
|
877
|
+
.persona-welcome-title {
|
|
878
|
+
font-family: var(--persona-components-introCard-title-fontFamily, inherit);
|
|
879
|
+
font-size: var(--persona-components-introCard-title-fontSize, 1.125rem);
|
|
880
|
+
font-weight: var(--persona-components-introCard-title-fontWeight, 600);
|
|
881
|
+
line-height: var(--persona-components-introCard-title-lineHeight, 1.75rem);
|
|
882
|
+
letter-spacing: var(--persona-components-introCard-title-letterSpacing, normal);
|
|
883
|
+
color: var(--persona-components-introCard-title-color, var(--persona-primary, #171717));
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.persona-welcome-subtitle {
|
|
887
|
+
margin-top: 0.5rem;
|
|
888
|
+
font-family: var(--persona-components-introCard-subtitle-fontFamily, inherit);
|
|
889
|
+
font-size: var(--persona-components-introCard-subtitle-fontSize, 0.875rem);
|
|
890
|
+
font-weight: var(--persona-components-introCard-subtitle-fontWeight, inherit);
|
|
891
|
+
line-height: var(--persona-components-introCard-subtitle-lineHeight, 1.25rem);
|
|
892
|
+
letter-spacing: var(--persona-components-introCard-subtitle-letterSpacing, normal);
|
|
893
|
+
color: var(--persona-components-introCard-subtitle-color, var(--persona-muted, #6b7280));
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/* Plugin-owned welcome (`renderWelcome`): the host keeps its position and the
|
|
897
|
+
default children are hidden by selector, so nothing about their own hidden
|
|
898
|
+
state has to be saved and restored when the plugin returns null again. */
|
|
899
|
+
.persona-welcome[data-persona-welcome-content="plugin"]
|
|
900
|
+
> :not([data-persona-welcome-plugin]) {
|
|
901
|
+
display: none !important;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
.persona-welcome[data-persona-welcome-content="plugin"] {
|
|
905
|
+
background: transparent;
|
|
906
|
+
box-shadow: none;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/* Overlay state: a plugin element covers the messages area while it is active.
|
|
910
|
+
The body stops scrolling underneath so the absolute box maps to the visible
|
|
911
|
+
transcript rather than scrolling away with the content. */
|
|
912
|
+
/* Wide panels: the card variant shares the transcript column
|
|
913
|
+
(--persona-content-max-width, re-set inline from layout.contentMaxWidth)
|
|
914
|
+
so its left-aligned text lines up with messages and composer; at launcher
|
|
915
|
+
width the cap never bites. The plugin overlay stays full-bleed via the
|
|
916
|
+
:not(). */
|
|
917
|
+
.persona-welcome:not([data-persona-welcome-overlay]) {
|
|
918
|
+
width: 100%;
|
|
919
|
+
max-width: var(--persona-content-max-width, 768px);
|
|
920
|
+
margin-inline: auto;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/* The centered hero keeps the narrower consumer-product column, where
|
|
924
|
+
centered text makes the split against the transcript read as intended. */
|
|
925
|
+
.persona-welcome[data-persona-welcome-variant="hero"]:not(
|
|
926
|
+
[data-persona-welcome-overlay]
|
|
927
|
+
) {
|
|
928
|
+
max-width: var(--persona-welcome-max-width, 640px);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/* The greeting bubble is a sibling of the transcript wrapper, outside its
|
|
932
|
+
inline width cap; mirror the cap so it shares the same column. */
|
|
933
|
+
.persona-welcome-greeting {
|
|
934
|
+
width: 100%;
|
|
935
|
+
max-width: var(--persona-content-max-width, 768px);
|
|
936
|
+
margin-inline: auto;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
.persona-welcome[data-persona-welcome-overlay] {
|
|
940
|
+
position: absolute;
|
|
941
|
+
inset: 0;
|
|
942
|
+
z-index: 5;
|
|
943
|
+
overflow-y: auto;
|
|
944
|
+
/* Plugin overlays are opaque surfaces regardless of the intro-card theme,
|
|
945
|
+
so they keep full interior padding even when the card resolves flat. */
|
|
946
|
+
padding: 1.5rem;
|
|
947
|
+
/* !important: the host doubles as the intro card and carries an inline
|
|
948
|
+
background (--persona-intro-card-bg, default transparent); the overlay
|
|
949
|
+
must stay opaque or the transcript bleeds through the plugin stack. */
|
|
950
|
+
background: var(
|
|
951
|
+
--persona-container,
|
|
952
|
+
var(--persona-surface, #ffffff)
|
|
953
|
+
) !important;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
.persona-widget-body.persona-welcome-overlay-active {
|
|
957
|
+
overflow: hidden;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/* Hero: auto block margins center the surface in the empty conversation
|
|
961
|
+
without `justify-content`, which would make overflowing content
|
|
962
|
+
unreachable in the scroll body. */
|
|
963
|
+
.persona-welcome[data-persona-welcome-variant="hero"] {
|
|
964
|
+
margin-block: auto;
|
|
965
|
+
text-align: center;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
.persona-welcome[data-persona-welcome-variant="hero"] .persona-welcome-icon {
|
|
969
|
+
justify-content: center;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
.persona-welcome[data-persona-welcome-variant="hero"] .persona-suggestions {
|
|
973
|
+
margin-inline: auto;
|
|
974
|
+
justify-content: center;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
.persona-welcome[data-persona-welcome-variant="hero"]
|
|
978
|
+
.persona-suggestions[data-variant="card"],
|
|
979
|
+
.persona-welcome[data-persona-welcome-variant="hero"]
|
|
980
|
+
.persona-suggestions[data-variant="list"] {
|
|
981
|
+
text-align: start;
|
|
982
|
+
width: 100%;
|
|
983
|
+
}
|
|
984
|
+
|
|
651
985
|
/* Composer ghost icon buttons (attachment + mention): transparent, neutral
|
|
652
986
|
foreground, subtle hover. Themeable via `components.button.ghost.*`
|
|
653
987
|
→ `--persona-button-ghost-*`. The `[data-persona-root]` prefix outranks the
|
|
@@ -1410,6 +1744,9 @@
|
|
|
1410
1744
|
[data-persona-root] .persona-widget-composer:not(.persona-pill-composer) {
|
|
1411
1745
|
padding: var(--persona-composer-padding, 0.75rem 1rem);
|
|
1412
1746
|
gap: var(--persona-composer-gap, 0.5rem);
|
|
1747
|
+
/* `components.composer.borderColor`; the fallback matches the
|
|
1748
|
+
`persona-border-persona-border` utility the form carries. */
|
|
1749
|
+
border-color: var(--persona-composer-border-color, var(--persona-border, #e5e7eb));
|
|
1413
1750
|
}
|
|
1414
1751
|
|
|
1415
1752
|
/* Composer type scale, themeable via `components.composer.fontSize` /
|
|
@@ -3176,7 +3513,10 @@
|
|
|
3176
3513
|
}
|
|
3177
3514
|
|
|
3178
3515
|
[data-persona-root] .persona-artifact-list::-webkit-scrollbar-thumb {
|
|
3179
|
-
background: var(
|
|
3516
|
+
background: var(
|
|
3517
|
+
--persona-artifact-tab-list-scrollbar,
|
|
3518
|
+
var(--persona-scrollbar-thumb, var(--persona-border, #e5e7eb))
|
|
3519
|
+
);
|
|
3180
3520
|
border-radius: 9999px;
|
|
3181
3521
|
}
|
|
3182
3522
|
|
|
@@ -4237,13 +4577,17 @@
|
|
|
4237
4577
|
}
|
|
4238
4578
|
|
|
4239
4579
|
@media (min-width: 641px) {
|
|
4580
|
+
/* Drawer width is its own knob (`layout.drawerWidth`), not the split's
|
|
4581
|
+
pane vars: those are always materialized with split defaults, so reading
|
|
4582
|
+
them here would resize every host's drawer. The outer min() keeps an
|
|
4583
|
+
oversized value inside the panel. */
|
|
4240
4584
|
[data-persona-root].persona-artifact-narrow-host .persona-artifact-pane {
|
|
4241
4585
|
position: absolute;
|
|
4242
4586
|
top: 0;
|
|
4243
4587
|
right: 0;
|
|
4244
4588
|
bottom: 0;
|
|
4245
|
-
width: min(calc(100% - 1rem), 22rem) !important;
|
|
4246
|
-
max-width: 22rem !important;
|
|
4589
|
+
width: min(100%, var(--persona-artifact-drawer-width, min(calc(100% - 1rem), 22rem))) !important;
|
|
4590
|
+
max-width: var(--persona-artifact-drawer-width, 22rem) !important;
|
|
4247
4591
|
z-index: 60;
|
|
4248
4592
|
transform: translateX(100%);
|
|
4249
4593
|
transition: transform 0.2s ease;
|
|
@@ -4260,7 +4604,7 @@
|
|
|
4260
4604
|
top: 0;
|
|
4261
4605
|
right: 0;
|
|
4262
4606
|
bottom: 0;
|
|
4263
|
-
width: min(100vw - 1.5rem, 22rem);
|
|
4607
|
+
width: min(100vw, var(--persona-artifact-drawer-width, min(100vw - 1.5rem, 22rem)));
|
|
4264
4608
|
z-index: 60;
|
|
4265
4609
|
transform: translateX(100%);
|
|
4266
4610
|
transition: transform 0.2s ease;
|