@runtypelabs/persona 4.19.0 → 4.20.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/approval-ui.d.cts +77 -5
- package/dist/approval-ui.d.ts +77 -5
- package/dist/artifacts-ui.d.cts +77 -5
- package/dist/artifacts-ui.d.ts +77 -5
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/context-mentions.d.cts +77 -5
- package/dist/context-mentions.d.ts +77 -5
- package/dist/event-stream-view.d.cts +77 -5
- package/dist/event-stream-view.d.ts +77 -5
- package/dist/forms-ui.d.cts +77 -5
- package/dist/forms-ui.d.ts +77 -5
- package/dist/index.cjs +50 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -6
- package/dist/index.d.ts +78 -6
- package/dist/index.global.js +31 -31
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +50 -50
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js.map +1 -1
- package/dist/session-reconnect.d.cts +77 -5
- package/dist/session-reconnect.d.ts +77 -5
- package/dist/smart-dom-reader.d.cts +77 -5
- package/dist/smart-dom-reader.d.ts +77 -5
- package/dist/theme-editor-preview.cjs +49 -49
- package/dist/theme-editor-preview.d.cts +77 -5
- package/dist/theme-editor-preview.d.ts +77 -5
- package/dist/theme-editor-preview.js +28 -28
- package/dist/theme-editor.d.cts +77 -5
- package/dist/theme-editor.d.ts +77 -5
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +46 -5
- package/dist/theme-reference.d.ts +46 -5
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +1 -1
- package/src/components/composer-actions.ts +27 -1
- package/src/components/composer-model-picker.ts +4 -14
- package/src/components/composer-overflow-menu.ts +14 -0
- package/src/components/composer-parts.ts +20 -0
- package/src/index-core.ts +2 -0
- package/src/styles/widget-styles.test.ts +83 -1
- package/src/styles/widget.css +50 -14
- package/src/theme-reference.ts +1 -1
- package/src/types/theme.ts +47 -5
- package/src/types.ts +33 -0
- package/src/ui.composer-action-visibility.test.ts +202 -0
- package/src/ui.composer-models.test.ts +37 -0
- package/src/ui.composer-modes.test.ts +82 -0
- package/src/ui.composer-overflow.test.ts +78 -0
- package/src/utils/__snapshots__/tokens-parity.test.ts.snap +1633 -686
- package/src/utils/composer-modes.test.ts +33 -0
- package/src/utils/composer-modes.ts +19 -2
- package/src/utils/tokens-parity.test.ts +20 -0
- package/src/utils/tokens.test.ts +76 -0
- package/src/utils/tokens.ts +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/persona",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.20.0",
|
|
4
4
|
"description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
ComposerActionOverflowConfig,
|
|
33
33
|
ComposerActionPlacement,
|
|
34
34
|
ComposerActionPresentation,
|
|
35
|
+
ComposerActionVisibility,
|
|
35
36
|
ComposerButtonAction,
|
|
36
37
|
ComposerCustomAction,
|
|
37
38
|
ComposerState,
|
|
@@ -345,6 +346,28 @@ const evaluate = (
|
|
|
345
346
|
return typeof value === "function" ? value(state) === true : value === true;
|
|
346
347
|
};
|
|
347
348
|
|
|
349
|
+
/**
|
|
350
|
+
* The draft state `sendButton.visibility` reads: text, a pending attachment, or
|
|
351
|
+
* a live stream all count as "there is something in flight".
|
|
352
|
+
*/
|
|
353
|
+
const hasDraftContent = (state: Readonly<ComposerState>): boolean =>
|
|
354
|
+
state.phase === "streaming" ||
|
|
355
|
+
state.text.trim().length > 0 ||
|
|
356
|
+
state.attachments.length > 0;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* `ComposerAction.visibility` against the current draft. Evaluated on every
|
|
360
|
+
* state-driven pass, so the answer is always render-time, never patched on.
|
|
361
|
+
*/
|
|
362
|
+
export function matchesComposerActionVisibility(
|
|
363
|
+
visibility: ComposerActionVisibility | undefined,
|
|
364
|
+
state: Readonly<ComposerState>
|
|
365
|
+
): boolean {
|
|
366
|
+
if (!visibility || visibility === "always") return true;
|
|
367
|
+
const drafting = hasDraftContent(state);
|
|
368
|
+
return visibility === "when-text" ? drafting : !drafting;
|
|
369
|
+
}
|
|
370
|
+
|
|
348
371
|
const isCustom = (action: ComposerAction): action is ComposerCustomAction =>
|
|
349
372
|
action.kind === "custom";
|
|
350
373
|
|
|
@@ -937,7 +960,10 @@ export function createComposerActionRenderer(
|
|
|
937
960
|
let entry = entries.get(resolved.id);
|
|
938
961
|
if (!entry) continue;
|
|
939
962
|
const action = resolved.action;
|
|
940
|
-
const visible = action
|
|
963
|
+
const visible = action
|
|
964
|
+
? evaluate(action.visible, state, true) &&
|
|
965
|
+
matchesComposerActionVisibility(action.visibility, state)
|
|
966
|
+
: true;
|
|
941
967
|
if (entry.visible !== visible) {
|
|
942
968
|
entry.visible = visible;
|
|
943
969
|
// Managed elements leave the DOM when hidden; the node (and any custom
|
|
@@ -18,6 +18,7 @@ import { createPopover, type PopoverHandle } from "../plugin-kit";
|
|
|
18
18
|
import type { ComposerCustomAction, ComposerModel } from "../types";
|
|
19
19
|
import { createElement, createNode } from "../utils/dom";
|
|
20
20
|
import { renderLucideIcon } from "../utils/icons";
|
|
21
|
+
import { forwardMenuTokens } from "./composer-parts";
|
|
21
22
|
|
|
22
23
|
export const COMPOSER_MODEL_PICKER_ACTION_ID = "core:model";
|
|
23
24
|
export const COMPOSER_MODEL_PICKER_ORDER = 700;
|
|
@@ -25,9 +26,8 @@ export const COMPOSER_MODEL_PICKER_ORDER = 700;
|
|
|
25
26
|
const ROW_ICON_SIZE = 16;
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
|
-
* Tokens the popover panel reads
|
|
29
|
-
*
|
|
30
|
-
* the stylesheet's fallbacks stand.
|
|
29
|
+
* Tokens the popover panel reads, forwarded onto the portaled panel on every
|
|
30
|
+
* open; unset ones clear so the stylesheet's fallbacks stand.
|
|
31
31
|
*/
|
|
32
32
|
const MENU_TOKEN_VARS = [
|
|
33
33
|
"--persona-components-composer-modelPicker-menuBackground",
|
|
@@ -62,16 +62,6 @@ export interface ComposerModelPicker {
|
|
|
62
62
|
repaint: () => void;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
const forwardMenuTokens = (from: HTMLElement, to: HTMLElement): void => {
|
|
66
|
-
const view = from.ownerDocument?.defaultView;
|
|
67
|
-
if (!view) return;
|
|
68
|
-
const computed = view.getComputedStyle(from);
|
|
69
|
-
for (const name of MENU_TOKEN_VARS) {
|
|
70
|
-
const value = computed.getPropertyValue(name).trim();
|
|
71
|
-
if (value) to.style.setProperty(name, value);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
65
|
/** The wrapper both presentations share: one grid cell, control plus chevron. */
|
|
76
66
|
const createWrapper = (): { wrapper: HTMLElement; chevron: HTMLElement } => {
|
|
77
67
|
// `appearance: none` drops the platform chevron, which Chrome draws
|
|
@@ -221,7 +211,7 @@ export function createComposerModelPickerAction(
|
|
|
221
211
|
onDismiss: () => trigger.setAttribute("aria-expanded", "false"),
|
|
222
212
|
});
|
|
223
213
|
}
|
|
224
|
-
forwardMenuTokens(trigger, menu);
|
|
214
|
+
forwardMenuTokens(trigger, menu, MENU_TOKEN_VARS);
|
|
225
215
|
popover.open();
|
|
226
216
|
trigger.setAttribute("aria-expanded", "true");
|
|
227
217
|
const ids = [...rows.keys()];
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
COMPOSER_CONTROL_CLASS,
|
|
20
20
|
COMPOSER_CONTROL_GLYPH_CLASS,
|
|
21
21
|
COMPOSER_CONTROL_ICON_FALLBACK_PX,
|
|
22
|
+
forwardMenuTokens,
|
|
22
23
|
} from "./composer-parts";
|
|
23
24
|
|
|
24
25
|
export interface ComposerOverflowMenuOptions {
|
|
@@ -54,6 +55,18 @@ export interface ComposerOverflowMenu {
|
|
|
54
55
|
const focusableSelector =
|
|
55
56
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Panel surface tokens, forwarded onto the portaled panel on every open; unset
|
|
60
|
+
* ones clear so the stylesheet's fallbacks stand.
|
|
61
|
+
*/
|
|
62
|
+
const MENU_TOKEN_VARS = [
|
|
63
|
+
"--persona-components-composer-overflowMenu-background",
|
|
64
|
+
"--persona-components-composer-overflowMenu-borderColor",
|
|
65
|
+
"--persona-components-composer-overflowMenu-borderRadius",
|
|
66
|
+
"--persona-components-composer-overflowMenu-foreground",
|
|
67
|
+
"--persona-components-composer-overflowMenu-shadow",
|
|
68
|
+
] as const;
|
|
69
|
+
|
|
57
70
|
/**
|
|
58
71
|
* Set on the menu row that holds keyboard focus. Rows that shade on
|
|
59
72
|
* `:focus-visible` do not need it; wrapper rows around a live control do.
|
|
@@ -249,6 +262,7 @@ export function createComposerOverflowMenu(
|
|
|
249
262
|
const open = (focusIndex = 0, viaKeyboard = false): void => {
|
|
250
263
|
if (destroyed || isOpen()) return;
|
|
251
264
|
keyboardModality = viaKeyboard;
|
|
265
|
+
forwardMenuTokens(triggerButton, panel, MENU_TOKEN_VARS);
|
|
252
266
|
ensurePopover().open();
|
|
253
267
|
triggerButton.setAttribute("aria-expanded", "true");
|
|
254
268
|
const targets = focusables();
|
|
@@ -41,6 +41,26 @@ export const COMPOSER_CONTROL_FALLBACK_PX = 40;
|
|
|
41
41
|
/** Icon-size token default in CSS pixels, for the same JS-derived cases. */
|
|
42
42
|
export const COMPOSER_CONTROL_ICON_FALLBACK_PX = 24;
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Forward theme vars onto a portaled menu panel, which lives outside the
|
|
46
|
+
* themed mount. The panel is reused across opens, so an unset var must clear
|
|
47
|
+
* any previously stamped value for the stylesheet fallback to paint again.
|
|
48
|
+
*/
|
|
49
|
+
export const forwardMenuTokens = (
|
|
50
|
+
from: HTMLElement,
|
|
51
|
+
to: HTMLElement,
|
|
52
|
+
names: readonly string[]
|
|
53
|
+
): void => {
|
|
54
|
+
const view = from.ownerDocument?.defaultView;
|
|
55
|
+
if (!view) return;
|
|
56
|
+
const computed = view.getComputedStyle(from);
|
|
57
|
+
for (const name of names) {
|
|
58
|
+
const value = computed.getPropertyValue(name).trim();
|
|
59
|
+
if (value) to.style.setProperty(name, value);
|
|
60
|
+
else to.style.removeProperty(name);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
44
64
|
export interface ComposerTextareaParts {
|
|
45
65
|
textarea: HTMLTextAreaElement;
|
|
46
66
|
/**
|
package/src/index-core.ts
CHANGED
|
@@ -91,6 +91,7 @@ export type {
|
|
|
91
91
|
ComposerActionBase,
|
|
92
92
|
ComposerActionPlacement,
|
|
93
93
|
ComposerActionPresentation,
|
|
94
|
+
ComposerActionVisibility,
|
|
94
95
|
ComposerButtonAction,
|
|
95
96
|
ComposerCustomAction,
|
|
96
97
|
ComposerActionContext,
|
|
@@ -103,6 +104,7 @@ export type {
|
|
|
103
104
|
ComposerModelPickerPresentation,
|
|
104
105
|
// Composer modes + mode groups (composer.modes, composer.modeGroups)
|
|
105
106
|
ComposerMode,
|
|
107
|
+
ComposerModeChipVisibility,
|
|
106
108
|
ComposerModeGroup,
|
|
107
109
|
ComposerModeGroupPresentation,
|
|
108
110
|
// Composer baseline configuration (submit keys, sizing, locks)
|
|
@@ -585,6 +585,38 @@ describe("overflow menu row shading", () => {
|
|
|
585
585
|
);
|
|
586
586
|
expect(widgetCss).toContain(".persona-composer-overflow-menu__slot:hover");
|
|
587
587
|
});
|
|
588
|
+
|
|
589
|
+
it("reads every overflowMenu token as a full path over the stock fallback", () => {
|
|
590
|
+
const at = widgetCss.indexOf(".persona-composer-overflow-menu {");
|
|
591
|
+
expect(at, "overflow menu panel rule not found").toBeGreaterThan(-1);
|
|
592
|
+
const panel = widgetCss.slice(at, widgetCss.indexOf("}", at));
|
|
593
|
+
|
|
594
|
+
expect(panel).toContain(
|
|
595
|
+
"--persona-components-composer-overflowMenu-background,"
|
|
596
|
+
);
|
|
597
|
+
expect(panel).toContain("var(--persona-surface, #ffffff)");
|
|
598
|
+
expect(panel).toContain(
|
|
599
|
+
"--persona-components-composer-overflowMenu-borderColor,"
|
|
600
|
+
);
|
|
601
|
+
expect(panel).toContain("var(--persona-border, #e5e7eb)");
|
|
602
|
+
expect(panel).toContain(
|
|
603
|
+
"--persona-components-composer-overflowMenu-borderRadius,"
|
|
604
|
+
);
|
|
605
|
+
expect(panel).toContain(
|
|
606
|
+
"--persona-components-composer-overflowMenu-foreground,"
|
|
607
|
+
);
|
|
608
|
+
expect(panel).toContain(
|
|
609
|
+
"--persona-components-composer-overflowMenu-shadow,"
|
|
610
|
+
);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it("keeps the portaled panel rule unprefixed", () => {
|
|
614
|
+
// createPopover mounts the panel outside the widget root, so a
|
|
615
|
+
// `[data-persona-root]` prefix would never match it.
|
|
616
|
+
expect(widgetCss).not.toContain(
|
|
617
|
+
"[data-persona-root] .persona-composer-overflow-menu"
|
|
618
|
+
);
|
|
619
|
+
});
|
|
588
620
|
});
|
|
589
621
|
|
|
590
622
|
describe("composer control focus rings", () => {
|
|
@@ -658,7 +690,11 @@ describe("model picker chevron", () => {
|
|
|
658
690
|
|
|
659
691
|
it("colors the chevron from the theme token, never a baked-in hex", () => {
|
|
660
692
|
const rule = ruleFor(".persona-composer-model-picker-chevron");
|
|
661
|
-
|
|
693
|
+
// Same chain as the label, so the glyph and the text never diverge.
|
|
694
|
+
expect(rule).toContain(
|
|
695
|
+
"--persona-components-composer-modelPicker-labelColor"
|
|
696
|
+
);
|
|
697
|
+
expect(rule).toContain("var(--persona-button-ghost-fg");
|
|
662
698
|
// The mask only needs opaque alpha, so the SVG's own stroke is not a color.
|
|
663
699
|
expect(rule).toContain("mask-image");
|
|
664
700
|
expect(rule).toContain("-webkit-mask-image");
|
|
@@ -840,6 +876,52 @@ describe("model picker popover", () => {
|
|
|
840
876
|
});
|
|
841
877
|
});
|
|
842
878
|
|
|
879
|
+
describe("model picker closed control surface", () => {
|
|
880
|
+
const ruleFor = (selector: string): string => {
|
|
881
|
+
const at = widgetCss.indexOf(selector);
|
|
882
|
+
expect(at, `selector not found: ${selector}`).toBeGreaterThan(-1);
|
|
883
|
+
return widgetCss.slice(at, widgetCss.indexOf("}", at));
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
it("reads the surface tokens over the ghost button fallbacks", () => {
|
|
887
|
+
// One vocabulary for both presentations: the native select and the popover
|
|
888
|
+
// trigger share this rule, so a page that stays native still themes.
|
|
889
|
+
const rule = ruleFor("[data-persona-root] .persona-composer-model-picker {");
|
|
890
|
+
expect(rule).toContain(
|
|
891
|
+
"--persona-components-composer-modelPicker-background,"
|
|
892
|
+
);
|
|
893
|
+
expect(rule).toContain("var(--persona-button-ghost-bg, transparent)");
|
|
894
|
+
expect(rule).toContain(
|
|
895
|
+
"--persona-components-composer-modelPicker-borderRadius,"
|
|
896
|
+
);
|
|
897
|
+
expect(rule).toContain("var(--persona-button-ghost-radius");
|
|
898
|
+
expect(rule).toContain(
|
|
899
|
+
"--persona-components-composer-modelPicker-labelColor,"
|
|
900
|
+
);
|
|
901
|
+
expect(rule).toContain("var(--persona-button-ghost-fg");
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it("draws borderColor as an inset ring so the control never resizes", () => {
|
|
905
|
+
// A real border would eat 1px of the content box under `border-box` sizing
|
|
906
|
+
// and shift the label on every theme that leaves the token unset.
|
|
907
|
+
const rule = ruleFor("[data-persona-root] .persona-composer-model-picker {");
|
|
908
|
+
expect(rule).toContain("border: none");
|
|
909
|
+
expect(rule).toContain(
|
|
910
|
+
"box-shadow: inset 0 0 0 1px\n var(--persona-components-composer-modelPicker-borderColor, transparent)"
|
|
911
|
+
);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
it("keeps hover on its own token so a themed fill survives the pointer", () => {
|
|
915
|
+
const rule = ruleFor(
|
|
916
|
+
"[data-persona-root] .persona-composer-model-picker:hover:not(:disabled) {"
|
|
917
|
+
);
|
|
918
|
+
expect(rule).toContain(
|
|
919
|
+
"--persona-components-composer-modelPicker-hoverBackground,"
|
|
920
|
+
);
|
|
921
|
+
expect(rule).toContain("var(--persona-button-ghost-hover-bg");
|
|
922
|
+
});
|
|
923
|
+
});
|
|
924
|
+
|
|
843
925
|
describe("composer motion", () => {
|
|
844
926
|
/** Every `@media (prefers-reduced-motion: no-preference)` block, joined. */
|
|
845
927
|
const reducedMotionSafeCss = (): string => {
|
package/src/styles/widget.css
CHANGED
|
@@ -1371,7 +1371,8 @@
|
|
|
1371
1371
|
*
|
|
1372
1372
|
* The panel is mounted by createPopover next to the widget root (or inside the
|
|
1373
1373
|
* shadow root), so its rules are deliberately un-prefixed by
|
|
1374
|
-
* `[data-persona-root]` and must stand on their own.
|
|
1374
|
+
* `[data-persona-root]` and must stand on their own. It sits outside the themed
|
|
1375
|
+
* mount, so the menu forwards its configured tokens onto the panel on open. */
|
|
1375
1376
|
.persona-composer-overflow-menu {
|
|
1376
1377
|
display: flex;
|
|
1377
1378
|
flex-direction: column;
|
|
@@ -1380,11 +1381,27 @@
|
|
|
1380
1381
|
max-height: 320px;
|
|
1381
1382
|
overflow-y: auto;
|
|
1382
1383
|
padding: 4px;
|
|
1383
|
-
border-radius: var(
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1384
|
+
border-radius: var(
|
|
1385
|
+
--persona-components-composer-overflowMenu-borderRadius,
|
|
1386
|
+
var(--persona-radius-lg, 0.5rem)
|
|
1387
|
+
);
|
|
1388
|
+
border: 1px solid
|
|
1389
|
+
var(
|
|
1390
|
+
--persona-components-composer-overflowMenu-borderColor,
|
|
1391
|
+
var(--persona-border, #e5e7eb)
|
|
1392
|
+
);
|
|
1393
|
+
background: var(
|
|
1394
|
+
--persona-components-composer-overflowMenu-background,
|
|
1395
|
+
var(--persona-surface, #ffffff)
|
|
1396
|
+
);
|
|
1397
|
+
color: var(
|
|
1398
|
+
--persona-components-composer-overflowMenu-foreground,
|
|
1399
|
+
var(--persona-text, #111827)
|
|
1400
|
+
);
|
|
1401
|
+
box-shadow: var(
|
|
1402
|
+
--persona-components-composer-overflowMenu-shadow,
|
|
1403
|
+
var(--persona-shadow-md, 0 8px 24px rgba(0, 0, 0, 0.12))
|
|
1404
|
+
);
|
|
1388
1405
|
font-family: var(--persona-font-family, inherit);
|
|
1389
1406
|
}
|
|
1390
1407
|
|
|
@@ -1598,9 +1615,22 @@
|
|
|
1598
1615
|
padding-inline-start: 0.625rem;
|
|
1599
1616
|
padding-inline-end: 1.75rem;
|
|
1600
1617
|
border: none;
|
|
1601
|
-
border-radius: var(
|
|
1602
|
-
|
|
1603
|
-
|
|
1618
|
+
border-radius: var(
|
|
1619
|
+
--persona-components-composer-modelPicker-borderRadius,
|
|
1620
|
+
var(--persona-button-ghost-radius, var(--persona-radius-md, 0.375rem))
|
|
1621
|
+
);
|
|
1622
|
+
background-color: var(
|
|
1623
|
+
--persona-components-composer-modelPicker-background,
|
|
1624
|
+
var(--persona-button-ghost-bg, transparent)
|
|
1625
|
+
);
|
|
1626
|
+
color: var(
|
|
1627
|
+
--persona-components-composer-modelPicker-labelColor,
|
|
1628
|
+
var(--persona-button-ghost-fg, var(--persona-text, #111827))
|
|
1629
|
+
);
|
|
1630
|
+
/* A ring, not a border: `border-box` sizing would pull the label in by 1px on
|
|
1631
|
+
every theme that never sets the token. */
|
|
1632
|
+
box-shadow: inset 0 0 0 1px
|
|
1633
|
+
var(--persona-components-composer-modelPicker-borderColor, transparent);
|
|
1604
1634
|
font-family: inherit;
|
|
1605
1635
|
font-size: 0.8125rem;
|
|
1606
1636
|
cursor: pointer;
|
|
@@ -1616,16 +1646,19 @@
|
|
|
1616
1646
|
text-align: start;
|
|
1617
1647
|
}
|
|
1618
1648
|
|
|
1619
|
-
/* The chevron is a MASK, not a data-URI stroke color: the fill
|
|
1620
|
-
|
|
1621
|
-
Lucide `chevron-down` geometry as the icon registry draws. */
|
|
1649
|
+
/* The chevron is a MASK, not a data-URI stroke color: the fill follows the
|
|
1650
|
+
closed control's own text color, so the glyph and the label never diverge.
|
|
1651
|
+
Same Lucide `chevron-down` geometry as the icon registry draws. */
|
|
1622
1652
|
.persona-composer-model-picker-chevron {
|
|
1623
1653
|
justify-self: end;
|
|
1624
1654
|
margin-inline-end: 0.625rem;
|
|
1625
1655
|
width: 0.875rem;
|
|
1626
1656
|
height: 0.875rem;
|
|
1627
1657
|
pointer-events: none;
|
|
1628
|
-
background-color: var(
|
|
1658
|
+
background-color: var(
|
|
1659
|
+
--persona-components-composer-modelPicker-labelColor,
|
|
1660
|
+
var(--persona-button-ghost-fg, var(--persona-text, #111827))
|
|
1661
|
+
);
|
|
1629
1662
|
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
|
|
1630
1663
|
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
|
|
1631
1664
|
-webkit-mask-repeat: no-repeat;
|
|
@@ -1653,7 +1686,10 @@
|
|
|
1653
1686
|
}
|
|
1654
1687
|
|
|
1655
1688
|
[data-persona-root] .persona-composer-model-picker:hover:not(:disabled) {
|
|
1656
|
-
background-color: var(
|
|
1689
|
+
background-color: var(
|
|
1690
|
+
--persona-components-composer-modelPicker-hoverBackground,
|
|
1691
|
+
var(--persona-button-ghost-hover-bg, rgba(0, 0, 0, 0.05))
|
|
1692
|
+
);
|
|
1657
1693
|
}
|
|
1658
1694
|
|
|
1659
1695
|
[data-persona-root] .persona-composer-model-picker:disabled {
|
package/src/theme-reference.ts
CHANGED
|
@@ -173,7 +173,7 @@ export const THEME_TOKEN_DOCS = {
|
|
|
173
173
|
'Shared scrollbar appearance for every scroller: thumb (default semantic.colors.border), track (default transparent).',
|
|
174
174
|
reasoningBubble: 'shadow: reasoning/thinking row box-shadow.',
|
|
175
175
|
composer:
|
|
176
|
-
'Message input form: shadow (box-shadow), borderColor, padding (form inset), gap (textarea row to actions row), fontSize and lineHeight (textarea type), controlSize (edge of every icon control in the action row: attachment, mention, mic, action buttons, overflow trigger, icon-mode send; default 40px) and controlIconSize (glyph inside them, default 24px). Per-control config keys (sendButton.size, voiceRecognition.iconSize) override controlSize; coarse-pointer devices floor the hit area at 40px. overlayBand paints the band behind an overlaid footer (composer.placement: "overlay"). segmented styles a segmented mode-group track (trackBackground, trackBorderRadius, padding, activeBackground, activeForeground, activeShadow, inactiveForeground, itemPadding); every key is optional and the stylesheet owns the defaults. modelPicker styles the
|
|
176
|
+
'Message input form: shadow (box-shadow), borderColor, padding (form inset), gap (textarea row to actions row), fontSize and lineHeight (textarea type), controlSize (edge of every icon control in the action row: attachment, mention, mic, action buttons, overflow trigger, icon-mode send; default 40px) and controlIconSize (glyph inside them, default 24px). Per-control config keys (sendButton.size, voiceRecognition.iconSize) override controlSize; coarse-pointer devices floor the hit area at 40px. overlayBand paints the band behind an overlaid footer (composer.placement: "overlay"). segmented styles a segmented mode-group track (trackBackground, trackBorderRadius, padding, activeBackground, activeForeground, activeShadow, inactiveForeground, itemPadding); every key is optional and the stylesheet owns the defaults. modelPicker styles the model picker (background, hoverBackground, borderColor, borderRadius and labelColor on the closed control, which the native select and the popover trigger share; menuBackground, menuBorderRadius, rowHoverBackground, descriptionColor and suffixColor on the popover only), also all optional. overflowMenu styles the composer overflow "+" menu panel (background, borderColor, borderRadius, foreground, shadow), also all optional.',
|
|
177
177
|
artifact:
|
|
178
178
|
'toolbar (icon styling, copy menu), tab (background, active states), pane (background, toolbarBackground).',
|
|
179
179
|
},
|
package/src/types/theme.ts
CHANGED
|
@@ -663,21 +663,41 @@ export interface ComposerSegmentedTokens {
|
|
|
663
663
|
}
|
|
664
664
|
|
|
665
665
|
/**
|
|
666
|
-
* Model picker
|
|
666
|
+
* Model picker chrome (`composer.models`).
|
|
667
|
+
*
|
|
668
|
+
* The surface keys (`background`, `hoverBackground`, `borderColor`,
|
|
669
|
+
* `borderRadius`, `labelColor`) style the CLOSED control, so the native
|
|
670
|
+
* `<select>` and the `presentation: "popover"` trigger share one vocabulary.
|
|
671
|
+
* The `menu*`, `rowHoverBackground`, and `descriptionColor` keys style the
|
|
672
|
+
* popover panel and are inert under the native select, as is `suffixColor`,
|
|
673
|
+
* which a `<select>` cannot carry either.
|
|
667
674
|
*
|
|
668
675
|
* Every key is optional and unset ones emit nothing: the stylesheet's `var()`
|
|
669
676
|
* fallbacks own the default look, which derives from the surface and ghost
|
|
670
|
-
* button tokens.
|
|
671
|
-
* `suffixColor`, which the native control cannot carry either.
|
|
677
|
+
* button tokens.
|
|
672
678
|
*/
|
|
673
679
|
export interface ComposerModelPickerTokens {
|
|
680
|
+
/** Closed control fill. @default the ghost button fill (transparent) */
|
|
681
|
+
background?: TokenReference<'color'>;
|
|
682
|
+
/** Closed control fill under hover. @default the ghost hover wash */
|
|
683
|
+
hoverBackground?: TokenReference<'color'>;
|
|
684
|
+
/**
|
|
685
|
+
* Closed control outline, drawn as a 1px inset ring so setting it never
|
|
686
|
+
* resizes the control. @default none
|
|
687
|
+
*/
|
|
688
|
+
borderColor?: TokenReference<'color'>;
|
|
689
|
+
/** Closed control corner radius. @default the ghost button radius */
|
|
690
|
+
borderRadius?: TokenReference<'radius'>;
|
|
674
691
|
/** Panel fill behind the rows. @default semantic.colors.surface */
|
|
675
692
|
menuBackground?: TokenReference<'color'>;
|
|
676
693
|
/** Panel corner radius. @default palette.radius.lg */
|
|
677
694
|
menuBorderRadius?: TokenReference<'radius'>;
|
|
678
695
|
/** Row fill under hover and the focused option. @default the ghost hover wash */
|
|
679
696
|
rowHoverBackground?: TokenReference<'color'>;
|
|
680
|
-
/**
|
|
697
|
+
/**
|
|
698
|
+
* Row label color, plus the closed control's text and chevron in both
|
|
699
|
+
* presentations. @default the ghost button foreground
|
|
700
|
+
*/
|
|
681
701
|
labelColor?: TokenReference<'color'>;
|
|
682
702
|
/** Second line under a row label. @default semantic.colors.textMuted */
|
|
683
703
|
descriptionColor?: TokenReference<'color'>;
|
|
@@ -685,6 +705,26 @@ export interface ComposerModelPickerTokens {
|
|
|
685
705
|
suffixColor?: TokenReference<'color'>;
|
|
686
706
|
}
|
|
687
707
|
|
|
708
|
+
/**
|
|
709
|
+
* Composer overflow `+` menu panel (`composer.actionOverflow`).
|
|
710
|
+
*
|
|
711
|
+
* Every key is optional and unset ones emit nothing: the stylesheet's `var()`
|
|
712
|
+
* fallbacks own the default look. The panel is portaled outside the themed
|
|
713
|
+
* mount, so the menu forwards the configured values onto it when it opens.
|
|
714
|
+
*/
|
|
715
|
+
export interface ComposerOverflowMenuTokens {
|
|
716
|
+
/** Panel fill behind the rows. @default semantic.colors.surface */
|
|
717
|
+
background?: TokenReference<'color'>;
|
|
718
|
+
/** Panel border color. @default semantic.colors.border */
|
|
719
|
+
borderColor?: TokenReference<'color'>;
|
|
720
|
+
/** Panel corner radius. @default palette.radius.lg */
|
|
721
|
+
borderRadius?: TokenReference<'radius'>;
|
|
722
|
+
/** Row text and glyph color. @default semantic.colors.text */
|
|
723
|
+
foreground?: TokenReference<'color'>;
|
|
724
|
+
/** Panel elevation (raw CSS box-shadow, `none` allowed). @default palette.shadows.md */
|
|
725
|
+
shadow?: string;
|
|
726
|
+
}
|
|
727
|
+
|
|
688
728
|
/** Composer (message input) chrome. */
|
|
689
729
|
export interface ComposerChromeTokens {
|
|
690
730
|
/** Box-shadow on the composer form (raw CSS, e.g. `none`). */
|
|
@@ -719,8 +759,10 @@ export interface ComposerChromeTokens {
|
|
|
719
759
|
overlayBand?: string;
|
|
720
760
|
/** Segmented mode-group track. */
|
|
721
761
|
segmented?: ComposerSegmentedTokens;
|
|
722
|
-
/** Model picker popover
|
|
762
|
+
/** Model picker chrome (closed control plus popover panel). */
|
|
723
763
|
modelPicker?: ComposerModelPickerTokens;
|
|
764
|
+
/** Overflow `+` menu panel chrome. */
|
|
765
|
+
overflowMenu?: ComposerOverflowMenuTokens;
|
|
724
766
|
}
|
|
725
767
|
|
|
726
768
|
/**
|
package/src/types.ts
CHANGED
|
@@ -4007,6 +4007,16 @@ export type ComposerActionPlacement = "start" | "end";
|
|
|
4007
4007
|
*/
|
|
4008
4008
|
export type ComposerActionPresentation = "bar" | "overflow" | "auto";
|
|
4009
4009
|
|
|
4010
|
+
/**
|
|
4011
|
+
* When a composer action is mounted, as a function of the draft.
|
|
4012
|
+
*
|
|
4013
|
+
* - `"always"` (default): the control is always mounted.
|
|
4014
|
+
* - `"when-empty"`: hidden as soon as the draft holds text or an attachment,
|
|
4015
|
+
* and while a response streams.
|
|
4016
|
+
* - `"when-text"`: the inverse, on the same draft state the send control reads.
|
|
4017
|
+
*/
|
|
4018
|
+
export type ComposerActionVisibility = "always" | "when-empty" | "when-text";
|
|
4019
|
+
|
|
4010
4020
|
/**
|
|
4011
4021
|
* Fields shared by every composer action.
|
|
4012
4022
|
*
|
|
@@ -4022,6 +4032,13 @@ export type ComposerActionBase = {
|
|
|
4022
4032
|
presentation?: ComposerActionPresentation;
|
|
4023
4033
|
order?: number;
|
|
4024
4034
|
visible?: boolean | ((state: Readonly<ComposerState>) => boolean);
|
|
4035
|
+
/**
|
|
4036
|
+
* Draft-driven visibility, on the same state `sendButton.visibility` reads.
|
|
4037
|
+
* Hidden removes the control from layout and tab order. Combined with
|
|
4038
|
+
* `visible`: both must pass.
|
|
4039
|
+
* @default "always"
|
|
4040
|
+
*/
|
|
4041
|
+
visibility?: ComposerActionVisibility;
|
|
4025
4042
|
disabled?: boolean | ((state: Readonly<ComposerState>) => boolean);
|
|
4026
4043
|
/** Disable while a response streams, through the composer's own mechanism. */
|
|
4027
4044
|
disableWhenStreaming?: boolean;
|
|
@@ -4180,6 +4197,16 @@ export type ComposerMode = {
|
|
|
4180
4197
|
*/
|
|
4181
4198
|
export type ComposerModeGroupPresentation = "buttons" | "segmented";
|
|
4182
4199
|
|
|
4200
|
+
/**
|
|
4201
|
+
* Whether a group's active modes also render as removable chips.
|
|
4202
|
+
*
|
|
4203
|
+
* - `"auto"` (default): one chip per active mode, except under `"segmented"`
|
|
4204
|
+
* presentation, where the track already shows the state.
|
|
4205
|
+
* - `"hidden"`: no chips for this group under any presentation. The pressed bar
|
|
4206
|
+
* button carries the state, and deselection stays on the button.
|
|
4207
|
+
*/
|
|
4208
|
+
export type ComposerModeChipVisibility = "auto" | "hidden";
|
|
4209
|
+
|
|
4183
4210
|
/**
|
|
4184
4211
|
* Selection policy for a set of modes. `"single"` deselects siblings when one
|
|
4185
4212
|
* is chosen; `"multiple"` toggles independently.
|
|
@@ -4189,6 +4216,12 @@ export type ComposerModeGroup = {
|
|
|
4189
4216
|
selection: "single" | "multiple";
|
|
4190
4217
|
/** @default "buttons" */
|
|
4191
4218
|
presentation?: ComposerModeGroupPresentation;
|
|
4219
|
+
/**
|
|
4220
|
+
* Chip row policy for this group's active modes. The chip row itself is
|
|
4221
|
+
* derived from its children, so a group with no other chips leaves no gap.
|
|
4222
|
+
* @default "auto"
|
|
4223
|
+
*/
|
|
4224
|
+
chipVisibility?: ComposerModeChipVisibility;
|
|
4192
4225
|
/**
|
|
4193
4226
|
* Accessible name for a segmented track. With it the track is a labelled
|
|
4194
4227
|
* `role="group"`; without it the segments stand on their own names.
|