@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
package/README.md
CHANGED
|
@@ -77,7 +77,6 @@ const docked = initAgentWidget({
|
|
|
77
77
|
dock: {
|
|
78
78
|
side: 'right',
|
|
79
79
|
width: '420px',
|
|
80
|
-
collapsedWidth: '72px'
|
|
81
80
|
}
|
|
82
81
|
}
|
|
83
82
|
}
|
|
@@ -98,6 +97,12 @@ const docked = initAgentWidget({
|
|
|
98
97
|
|
|
99
98
|
When `config.launcher.mountMode` is `'docked'`, `target` is treated as the page container that Persona should wrap. Use a concrete element such as `#workspace-main`; `body` and `html` are rejected.
|
|
100
99
|
|
|
100
|
+
With **`dock.reveal: 'resize'`** (default), a **closed** dock uses a **`0px`** column. **`'emerge'`** uses the same **column width** animation (content reflows) but the chat panel stays **`dock.width`** wide and is **clipped** by the growing slot—like a normal-width widget emerging from the edge. **`'overlay'`** overlays with `transform`. **`'push'`** uses a sliding track (Shopify-style). The built-in launcher stays hidden in docked mode—open with **`controller.open()`** (or your own chrome).
|
|
101
|
+
|
|
102
|
+
**Rounded / card layout:** `initAgentWidget` inserts a flex **shell** as the **direct child** of your target’s **parent**, with your `target` in the content column and the dock beside it. Put border-radius, border, and `overflow: hidden` on that **parent** (or an ancestor that wraps only the shell) so the dock column sits inside the same visual card as your content.
|
|
103
|
+
|
|
104
|
+
**Inner push/overlay:** With `reveal: 'push'` or `'overlay'`, only the wrapped node moves. Use a **narrow `target`** (e.g. a main canvas div). For **`dock.side: 'left'`**, place a persistent rail **in flow** next to the stage (e.g. flex `[nav | stage]`) so the dock doesn’t open **under** the sidebar. For a **right** dock, you can instead use a **full-width** stage with an **absolute** left rail if you want the canvas to translate **behind** that rail.
|
|
105
|
+
|
|
101
106
|
> **Security note:** When you return HTML from `postprocessMessage`, make sure you sanitise it before injecting into the page. The provided postprocessors (`markdownPostprocessor`, `directivePostprocessor`) do not perform sanitisation.
|
|
102
107
|
|
|
103
108
|
|
|
@@ -890,6 +895,218 @@ Indicators are resolved in this order:
|
|
|
890
895
|
2. **Config function** (`loadingIndicator.render` / `loadingIndicator.renderIdle`)
|
|
891
896
|
3. **Default** (3-dot bouncing animation for loading, `null` for idle)
|
|
892
897
|
|
|
898
|
+
### Dropdown Menu
|
|
899
|
+
|
|
900
|
+
A reusable dropdown menu utility for building custom menus in plugins, custom components, or host-page UI that matches the widget's theme.
|
|
901
|
+
|
|
902
|
+
#### Basic usage
|
|
903
|
+
|
|
904
|
+
```ts
|
|
905
|
+
import { createDropdownMenu } from '@runtypelabs/persona';
|
|
906
|
+
|
|
907
|
+
const button = document.querySelector('#my-button')!;
|
|
908
|
+
const wrapper = document.createElement('div');
|
|
909
|
+
wrapper.style.position = 'relative';
|
|
910
|
+
button.parentElement!.insertBefore(wrapper, button);
|
|
911
|
+
wrapper.appendChild(button);
|
|
912
|
+
|
|
913
|
+
const dropdown = createDropdownMenu({
|
|
914
|
+
items: [
|
|
915
|
+
{ id: 'edit', label: 'Edit', icon: 'pencil' },
|
|
916
|
+
{ id: 'duplicate', label: 'Duplicate', icon: 'copy' },
|
|
917
|
+
{ id: 'delete', label: 'Delete', icon: 'trash-2', destructive: true, dividerBefore: true },
|
|
918
|
+
],
|
|
919
|
+
onSelect: (id) => console.log('Selected:', id),
|
|
920
|
+
anchor: wrapper,
|
|
921
|
+
position: 'bottom-left', // or 'bottom-right'
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
wrapper.appendChild(dropdown.element);
|
|
925
|
+
button.addEventListener('click', () => dropdown.toggle());
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
#### Escaping overflow containers
|
|
929
|
+
|
|
930
|
+
When the anchor is inside a container with `overflow: hidden`, use the `portal` option to render the menu at a higher DOM level while keeping CSS variable inheritance:
|
|
931
|
+
|
|
932
|
+
```ts
|
|
933
|
+
const dropdown = createDropdownMenu({
|
|
934
|
+
items: [...],
|
|
935
|
+
onSelect: (id) => { /* handle */ },
|
|
936
|
+
anchor: myButton,
|
|
937
|
+
position: 'bottom-right',
|
|
938
|
+
portal: document.querySelector('[data-persona-root]')!,
|
|
939
|
+
});
|
|
940
|
+
// No need to append — portal mode appends automatically
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
#### Header dropdown menus
|
|
944
|
+
|
|
945
|
+
Trailing header actions support built-in dropdown menus via the `menuItems` property:
|
|
946
|
+
|
|
947
|
+
```ts
|
|
948
|
+
createAgentExperience(mount, {
|
|
949
|
+
layout: {
|
|
950
|
+
header: {
|
|
951
|
+
layout: 'minimal',
|
|
952
|
+
trailingActions: [
|
|
953
|
+
{
|
|
954
|
+
id: 'options',
|
|
955
|
+
icon: 'chevron-down',
|
|
956
|
+
ariaLabel: 'Options',
|
|
957
|
+
menuItems: [
|
|
958
|
+
{ id: 'settings', label: 'Settings', icon: 'settings' },
|
|
959
|
+
{ id: 'help', label: 'Help', icon: 'help-circle' },
|
|
960
|
+
{ id: 'logout', label: 'Log out', icon: 'log-out', destructive: true, dividerBefore: true },
|
|
961
|
+
]
|
|
962
|
+
}
|
|
963
|
+
],
|
|
964
|
+
onAction: (actionId) => {
|
|
965
|
+
// Receives the menu item id when selected
|
|
966
|
+
console.log('Action:', actionId);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
#### Theming
|
|
974
|
+
|
|
975
|
+
Dropdown menus are styled via CSS custom properties with semantic fallbacks:
|
|
976
|
+
|
|
977
|
+
| Variable | Description | Fallback |
|
|
978
|
+
|----------|-------------|----------|
|
|
979
|
+
| `--persona-dropdown-bg` | Menu background | `--persona-surface` |
|
|
980
|
+
| `--persona-dropdown-border` | Menu border | `--persona-border` |
|
|
981
|
+
| `--persona-dropdown-radius` | Border radius | `0.625rem` |
|
|
982
|
+
| `--persona-dropdown-shadow` | Box shadow | `0 4px 16px rgba(0,0,0,0.12)` |
|
|
983
|
+
| `--persona-dropdown-item-color` | Item text color | `--persona-text` |
|
|
984
|
+
| `--persona-dropdown-item-hover-bg` | Item hover background | `--persona-container` |
|
|
985
|
+
| `--persona-dropdown-destructive-color` | Destructive item color | `#ef4444` |
|
|
986
|
+
|
|
987
|
+
Artifact toolbar copy menu tokens (`copyMenuBackground`, `copyMenuBorder`, etc.) also set the dropdown variables as defaults, so dropdown theming works with the existing artifact token config.
|
|
988
|
+
|
|
989
|
+
#### Type definitions
|
|
990
|
+
|
|
991
|
+
```ts
|
|
992
|
+
interface DropdownMenuItem {
|
|
993
|
+
id: string;
|
|
994
|
+
label: string;
|
|
995
|
+
icon?: string; // Lucide icon name
|
|
996
|
+
destructive?: boolean;
|
|
997
|
+
dividerBefore?: boolean;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
interface CreateDropdownOptions {
|
|
1001
|
+
items: DropdownMenuItem[];
|
|
1002
|
+
onSelect: (id: string) => void;
|
|
1003
|
+
anchor: HTMLElement;
|
|
1004
|
+
position?: 'bottom-left' | 'bottom-right';
|
|
1005
|
+
portal?: HTMLElement;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
interface DropdownMenuHandle {
|
|
1009
|
+
element: HTMLElement;
|
|
1010
|
+
show: () => void;
|
|
1011
|
+
hide: () => void;
|
|
1012
|
+
toggle: () => void;
|
|
1013
|
+
destroy: () => void;
|
|
1014
|
+
}
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
### Button Utilities
|
|
1018
|
+
|
|
1019
|
+
Composable button factories for building custom toolbars, actions, and toggle controls that match the widget's theme.
|
|
1020
|
+
|
|
1021
|
+
#### Icon button
|
|
1022
|
+
|
|
1023
|
+
```ts
|
|
1024
|
+
import { createIconButton } from '@runtypelabs/persona';
|
|
1025
|
+
|
|
1026
|
+
const refreshBtn = createIconButton({
|
|
1027
|
+
icon: 'refresh-cw',
|
|
1028
|
+
label: 'Refresh',
|
|
1029
|
+
onClick: () => handleRefresh(),
|
|
1030
|
+
});
|
|
1031
|
+
toolbar.appendChild(refreshBtn);
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
#### Label button
|
|
1035
|
+
|
|
1036
|
+
```ts
|
|
1037
|
+
import { createLabelButton } from '@runtypelabs/persona';
|
|
1038
|
+
|
|
1039
|
+
const copyBtn = createLabelButton({
|
|
1040
|
+
icon: 'copy',
|
|
1041
|
+
label: 'Copy',
|
|
1042
|
+
variant: 'default', // 'default' | 'primary' | 'destructive' | 'ghost'
|
|
1043
|
+
onClick: () => copyToClipboard(),
|
|
1044
|
+
});
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
#### Toggle group
|
|
1048
|
+
|
|
1049
|
+
```ts
|
|
1050
|
+
import { createToggleGroup } from '@runtypelabs/persona';
|
|
1051
|
+
|
|
1052
|
+
const toggle = createToggleGroup({
|
|
1053
|
+
items: [
|
|
1054
|
+
{ id: 'preview', icon: 'eye', label: 'Preview' },
|
|
1055
|
+
{ id: 'source', icon: 'code-2', label: 'Source' },
|
|
1056
|
+
],
|
|
1057
|
+
selectedId: 'preview',
|
|
1058
|
+
onSelect: (id) => setViewMode(id),
|
|
1059
|
+
});
|
|
1060
|
+
toolbar.appendChild(toggle.element);
|
|
1061
|
+
|
|
1062
|
+
// Programmatic update (does not fire onSelect)
|
|
1063
|
+
toggle.setSelected('source');
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
#### Theming
|
|
1067
|
+
|
|
1068
|
+
All button utilities are styled via CSS custom properties:
|
|
1069
|
+
|
|
1070
|
+
| Variable | Component | Description | Fallback |
|
|
1071
|
+
|----------|-----------|-------------|----------|
|
|
1072
|
+
| `--persona-icon-btn-bg` | Icon button | Background | `--persona-surface` |
|
|
1073
|
+
| `--persona-icon-btn-border` | Icon button | Border | `--persona-border` |
|
|
1074
|
+
| `--persona-icon-btn-color` | Icon button | Icon color | `--persona-text` |
|
|
1075
|
+
| `--persona-icon-btn-hover-bg` | Icon button | Hover background | `--persona-container` |
|
|
1076
|
+
| `--persona-icon-btn-hover-color` | Icon button | Hover color | `inherit` |
|
|
1077
|
+
| `--persona-icon-btn-active-bg` | Icon button | Pressed/active bg | `--persona-container` |
|
|
1078
|
+
| `--persona-icon-btn-active-border` | Icon button | Pressed/active border | `--persona-border` |
|
|
1079
|
+
| `--persona-icon-btn-padding` | Icon button | Padding | `0.25rem` |
|
|
1080
|
+
| `--persona-icon-btn-radius` | Icon button | Border radius | `--persona-radius-md` |
|
|
1081
|
+
| `--persona-label-btn-bg` | Label button | Background | `--persona-surface` |
|
|
1082
|
+
| `--persona-label-btn-border` | Label button | Border | `--persona-border` |
|
|
1083
|
+
| `--persona-label-btn-color` | Label button | Text color | `--persona-text` |
|
|
1084
|
+
| `--persona-label-btn-hover-bg` | Label button | Hover background | `--persona-container` |
|
|
1085
|
+
| `--persona-label-btn-font-size` | Label button | Font size | `0.75rem` |
|
|
1086
|
+
| `--persona-toggle-group-gap` | Toggle group | Gap between items | `0` |
|
|
1087
|
+
| `--persona-toggle-group-radius` | Toggle group | First/last radius | `--persona-icon-btn-radius` |
|
|
1088
|
+
|
|
1089
|
+
These can also be set via the widget config's theme token system:
|
|
1090
|
+
|
|
1091
|
+
```ts
|
|
1092
|
+
createAgentExperience(mount, {
|
|
1093
|
+
darkTheme: {
|
|
1094
|
+
components: {
|
|
1095
|
+
iconButton: {
|
|
1096
|
+
background: 'transparent',
|
|
1097
|
+
border: 'none',
|
|
1098
|
+
hoverBackground: '#2B2B2B',
|
|
1099
|
+
hoverColor: '#E5E5E5',
|
|
1100
|
+
},
|
|
1101
|
+
toggleGroup: {
|
|
1102
|
+
gap: '0',
|
|
1103
|
+
borderRadius: '8px',
|
|
1104
|
+
},
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
```
|
|
1109
|
+
|
|
893
1110
|
### Runtype adapter
|
|
894
1111
|
|
|
895
1112
|
This package ships with a Runtype adapter by default. The proxy handles all flow configuration, keeping the client lightweight and flexible.
|
|
@@ -1465,8 +1682,8 @@ config: {
|
|
|
1465
1682
|
|
|
1466
1683
|
| Option | Type | Description |
|
|
1467
1684
|
| --- | --- | --- |
|
|
1468
|
-
| `theme` | `
|
|
1469
|
-
| `darkTheme` | `
|
|
1685
|
+
| `theme` | `DeepPartial<PersonaTheme>` | Semantic tokens (`palette`, `semantic`, `components`). See [THEME-CONFIG.md](./THEME-CONFIG.md). Flat v1-style objects are still accepted at runtime (with a console warning) and migrated internally. |
|
|
1686
|
+
| `darkTheme` | `DeepPartial<PersonaTheme>` | Dark-mode token overrides, merged over `theme` when the active scheme is dark. |
|
|
1470
1687
|
| `colorScheme` | `'light' \| 'dark' \| 'auto'` | Color scheme mode. `'auto'` detects from `<html class="dark">` or `prefers-color-scheme`. Default: `'light'`. |
|
|
1471
1688
|
| `copy` | `{ welcomeTitle?, welcomeSubtitle?, inputPlaceholder?, sendButtonLabel? }` | Customize user-facing text strings. |
|
|
1472
1689
|
| `autoFocusInput` | `boolean` | Focus the chat input after the panel opens. Skips when voice is active. Default: `false`. |
|
|
@@ -1491,7 +1708,7 @@ Controls the floating launcher button and panel.
|
|
|
1491
1708
|
| `iconUrl` | `string?` | URL for the launcher icon image. |
|
|
1492
1709
|
| `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'?` | Screen corner position. |
|
|
1493
1710
|
| `mountMode` | `'floating' \| 'docked'?` | Mount as the existing floating launcher or wrap the target with a docked side panel. Default: `'floating'`. |
|
|
1494
|
-
| `dock` | `{ side
|
|
1711
|
+
| `dock` | `{ side?, width?, animate?, reveal? }?` | Dock layout. Defaults: right / `420px` / `animate: true` / `reveal: 'resize'`. `reveal: 'emerge'` = content column animates like resize but the panel stays fixed `dock.width` (clip-in). `reveal: 'overlay'` = transform overlay; `reveal: 'push'` = sliding track. `animate: false` snaps without transition. |
|
|
1495
1712
|
| `width` | `string?` | Width of the launcher button. |
|
|
1496
1713
|
| `fullHeight` | `boolean?` | Fill the full height of the container. Default: `false`. |
|
|
1497
1714
|
| `sidebarMode` | `boolean?` | Flush sidebar layout with no border-radius or margins. Default: `false`. |
|
|
@@ -2105,6 +2322,6 @@ Add `RUNTYPE_API_KEY` to your environment. The proxy constructs the Runtype payl
|
|
|
2105
2322
|
### Development notes
|
|
2106
2323
|
|
|
2107
2324
|
- The widget streams results using SSE and mirrors the backend `flow_complete`/`step_chunk` events.
|
|
2108
|
-
- Tailwind-esc classes are prefixed with `tvw-` and scoped to
|
|
2325
|
+
- Tailwind-esc classes are prefixed with `tvw-` and scoped to `[data-persona-root]`, so they won't collide with the host page.
|
|
2109
2326
|
- Run `pnpm dev` from the repository root to boot the example proxy (`examples/proxy`) and the vanilla demo (`examples/embedded-app`).
|
|
2110
2327
|
- The proxy prefers port `43111` but automatically selects the next free port if needed.
|