@runtypelabs/persona 2.0.0 → 2.2.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/src/ui.ts CHANGED
@@ -1142,6 +1142,9 @@ export const createAgentExperience = (
1142
1142
  event.stopPropagation();
1143
1143
  const artifactId = dlBtn.getAttribute('data-download-artifact');
1144
1144
  if (!artifactId) return;
1145
+ // Let integrator intercept
1146
+ const dlPrevented = config.features?.artifacts?.onArtifactAction?.({ type: 'download', artifactId });
1147
+ if (dlPrevented === true) return;
1145
1148
  // Try session state first, fall back to content stored in the card's rawContent props
1146
1149
  const artifact = session.getArtifactById(artifactId);
1147
1150
  let markdown = artifact?.markdown;
@@ -1180,6 +1183,9 @@ export const createAgentExperience = (
1180
1183
  if (!card) return;
1181
1184
  const artifactId = card.getAttribute('data-open-artifact');
1182
1185
  if (!artifactId) return;
1186
+ // Let integrator intercept
1187
+ const openPrevented = config.features?.artifacts?.onArtifactAction?.({ type: 'open', artifactId });
1188
+ if (openPrevented === true) return;
1183
1189
  event.preventDefault();
1184
1190
  event.stopPropagation();
1185
1191
  session.selectArtifact(artifactId);
@@ -3560,6 +3566,8 @@ export const createAgentExperience = (
3560
3566
  const previousMessageActions = config.messageActions;
3561
3567
  const previousLayoutMessages = config.layout?.messages;
3562
3568
  const previousColorScheme = config.colorScheme;
3569
+ const previousLoadingIndicator = config.loadingIndicator;
3570
+ const previousIterationDisplay = config.iterationDisplay;
3563
3571
  config = { ...config, ...nextConfig };
3564
3572
  // applyFullHeightStyles resets mount.style.cssText, so call it before applyThemeVariables
3565
3573
  applyFullHeightStyles();
@@ -3790,7 +3798,12 @@ export const createAgentExperience = (
3790
3798
  const toolCallConfigChanged = JSON.stringify(nextConfig.toolCall) !== JSON.stringify(previousToolCallConfig);
3791
3799
  const messageActionsChanged = JSON.stringify(config.messageActions) !== JSON.stringify(previousMessageActions);
3792
3800
  const layoutMessagesChanged = JSON.stringify(config.layout?.messages) !== JSON.stringify(previousLayoutMessages);
3793
- const messagesConfigChanged = toolCallConfigChanged || messageActionsChanged || layoutMessagesChanged;
3801
+ const loadingIndicatorChanged = config.loadingIndicator?.render !== previousLoadingIndicator?.render
3802
+ || config.loadingIndicator?.renderIdle !== previousLoadingIndicator?.renderIdle
3803
+ || config.loadingIndicator?.showBubble !== previousLoadingIndicator?.showBubble;
3804
+ const iterationDisplayChanged = config.iterationDisplay !== previousIterationDisplay;
3805
+ const messagesConfigChanged = toolCallConfigChanged || messageActionsChanged || layoutMessagesChanged
3806
+ || loadingIndicatorChanged || iterationDisplayChanged;
3794
3807
  if (messagesConfigChanged && session) {
3795
3808
  configVersion++;
3796
3809
  renderMessagesWithPlugins(messagesWrapper, session.getMessages(), postprocess);
@@ -139,6 +139,28 @@ export function migrateV1Theme(
139
139
  migrated.components.panel = {};
140
140
  }
141
141
  migrated.components.panel.borderRadius = value;
142
+ } else if (key === 'messageUserShadow') {
143
+ if (!migrated.components) migrated.components = {};
144
+ if (!migrated.components.message) migrated.components.message = {};
145
+ if (!migrated.components.message.user) migrated.components.message.user = {};
146
+ (migrated.components.message.user as { shadow?: string }).shadow = value as string;
147
+ } else if (key === 'messageAssistantShadow') {
148
+ if (!migrated.components) migrated.components = {};
149
+ if (!migrated.components.message) migrated.components.message = {};
150
+ if (!migrated.components.message.assistant) migrated.components.message.assistant = {};
151
+ (migrated.components.message.assistant as { shadow?: string }).shadow = value as string;
152
+ } else if (key === 'toolBubbleShadow') {
153
+ if (!migrated.components) migrated.components = {};
154
+ if (!migrated.components.toolBubble) migrated.components.toolBubble = {};
155
+ (migrated.components.toolBubble as { shadow?: string }).shadow = value as string;
156
+ } else if (key === 'reasoningBubbleShadow') {
157
+ if (!migrated.components) migrated.components = {};
158
+ if (!migrated.components.reasoningBubble) migrated.components.reasoningBubble = {};
159
+ (migrated.components.reasoningBubble as { shadow?: string }).shadow = value as string;
160
+ } else if (key === 'composerShadow') {
161
+ if (!migrated.components) migrated.components = {};
162
+ if (!migrated.components.composer) migrated.components.composer = {};
163
+ (migrated.components.composer as { shadow?: string }).shadow = value as string;
142
164
  }
143
165
  }
144
166
 
@@ -165,8 +187,27 @@ export function validateV1Theme(v1Theme: unknown): {
165
187
  return { valid: true, warnings: [] };
166
188
  }
167
189
 
190
+ const v1ThemeChromeKeys = new Set([
191
+ 'panelBorder',
192
+ 'panelShadow',
193
+ 'panelBorderRadius',
194
+ 'messageUserShadow',
195
+ 'messageAssistantShadow',
196
+ 'toolBubbleShadow',
197
+ 'reasoningBubbleShadow',
198
+ 'composerShadow',
199
+ ]);
200
+
168
201
  const deprecatedProperties = Object.keys(theme).filter(
169
- (key) => !(key in v1ToV2Mapping || key in v1RadiusMapping || key === 'inputFontFamily' || key === 'inputFontWeight' || key.startsWith('panel'))
202
+ (key) =>
203
+ !(
204
+ key in v1ToV2Mapping ||
205
+ key in v1RadiusMapping ||
206
+ key === 'inputFontFamily' ||
207
+ key === 'inputFontWeight' ||
208
+ key.startsWith('panel') ||
209
+ v1ThemeChromeKeys.has(key)
210
+ )
170
211
  );
171
212
 
172
213
  if (deprecatedProperties.length > 0) {
@@ -1,7 +1,7 @@
1
1
  // @vitest-environment jsdom
2
2
 
3
3
  import { afterEach, describe, expect, it } from 'vitest';
4
- import { createTheme, getActiveTheme, themeToCssVariables } from './theme';
4
+ import { applyThemeVariables, createTheme, getActiveTheme, themeToCssVariables } from './theme';
5
5
 
6
6
  describe('theme utils', () => {
7
7
  afterEach(() => {
@@ -122,4 +122,36 @@ describe('theme utils', () => {
122
122
  expect(cssVars['--persona-md-h2-weight']).toBe('600');
123
123
  expect(cssVars['--persona-md-prose-font-family']).toBe('Georgia, serif');
124
124
  });
125
+
126
+ it('maps flat AgentWidgetTheme bubble shadow keys to consumer CSS variables', () => {
127
+ const cfg = {
128
+ colorScheme: 'light' as const,
129
+ theme: {
130
+ toolBubbleShadow: 'none',
131
+ reasoningBubbleShadow: 'none',
132
+ messageUserShadow: 'none',
133
+ messageAssistantShadow: 'none',
134
+ composerShadow: 'none',
135
+ },
136
+ };
137
+
138
+ const active = getActiveTheme(cfg as any);
139
+ const cssVars = themeToCssVariables(active);
140
+
141
+ expect(cssVars['--persona-tool-bubble-shadow']).toBe('none');
142
+ expect(cssVars['--persona-reasoning-bubble-shadow']).toBe('none');
143
+ expect(cssVars['--persona-message-user-shadow']).toBe('none');
144
+ expect(cssVars['--persona-message-assistant-shadow']).toBe('none');
145
+ expect(cssVars['--persona-composer-shadow']).toBe('none');
146
+ });
147
+
148
+ it('lets config.toolCall.shadow override theme tool bubble shadow on the root element', () => {
149
+ const el = document.createElement('div');
150
+ applyThemeVariables(el, {
151
+ colorScheme: 'light',
152
+ theme: { toolBubbleShadow: '0 1px 2px rgba(255,0,0,0.5)' },
153
+ toolCall: { shadow: 'none' },
154
+ } as any);
155
+ expect(el.style.getPropertyValue('--persona-tool-bubble-shadow').trim()).toBe('none');
156
+ });
125
157
  });
@@ -220,6 +220,14 @@ export const applyThemeVariables = (
220
220
  for (const [name, value] of Object.entries(cssVars)) {
221
221
  element.style.setProperty(name, value);
222
222
  }
223
+
224
+ const toolCallShadow = (config as AgentWidgetConfig | undefined)?.toolCall?.shadow;
225
+ if (toolCallShadow !== undefined) {
226
+ element.style.setProperty(
227
+ '--persona-tool-bubble-shadow',
228
+ toolCallShadow.trim() === '' ? 'none' : toolCallShadow
229
+ );
230
+ }
223
231
  };
224
232
 
225
233
  export const createThemeObserver = (
@@ -271,6 +271,7 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
271
271
  background: 'semantic.colors.primary',
272
272
  text: 'semantic.colors.textInverse',
273
273
  borderRadius: 'palette.radius.lg',
274
+ shadow: 'palette.shadows.sm',
274
275
  },
275
276
  assistant: {
276
277
  background: 'semantic.colors.container',
@@ -280,6 +281,15 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
280
281
  shadow: 'palette.shadows.sm',
281
282
  },
282
283
  },
284
+ toolBubble: {
285
+ shadow: 'palette.shadows.sm',
286
+ },
287
+ reasoningBubble: {
288
+ shadow: 'palette.shadows.sm',
289
+ },
290
+ composer: {
291
+ shadow: 'none',
292
+ },
283
293
  markdown: {
284
294
  inlineCode: {
285
295
  background: 'semantic.colors.container',
@@ -637,6 +647,8 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
637
647
  cssVars['--persona-components-message-user-background'] ?? cssVars['--persona-accent'];
638
648
  cssVars['--persona-message-user-text'] =
639
649
  cssVars['--persona-components-message-user-text'] ?? cssVars['--persona-text-inverse'];
650
+ cssVars['--persona-message-user-shadow'] =
651
+ cssVars['--persona-components-message-user-shadow'] ?? '0 5px 15px rgba(15, 23, 42, 0.08)';
640
652
  cssVars['--persona-message-assistant-bg'] =
641
653
  cssVars['--persona-components-message-assistant-background'] ?? cssVars['--persona-surface'];
642
654
  cssVars['--persona-message-assistant-text'] =
@@ -646,6 +658,13 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
646
658
  cssVars['--persona-message-assistant-shadow'] =
647
659
  cssVars['--persona-components-message-assistant-shadow'] ?? '0 1px 2px 0 rgb(0 0 0 / 0.05)';
648
660
 
661
+ cssVars['--persona-tool-bubble-shadow'] =
662
+ cssVars['--persona-components-toolBubble-shadow'] ?? '0 5px 15px rgba(15, 23, 42, 0.08)';
663
+ cssVars['--persona-reasoning-bubble-shadow'] =
664
+ cssVars['--persona-components-reasoningBubble-shadow'] ?? '0 5px 15px rgba(15, 23, 42, 0.08)';
665
+ cssVars['--persona-composer-shadow'] =
666
+ cssVars['--persona-components-composer-shadow'] ?? 'none';
667
+
649
668
  cssVars['--persona-md-inline-code-bg'] =
650
669
  cssVars['--persona-components-markdown-inlineCode-background'] ?? cssVars['--persona-container'];
651
670
  cssVars['--persona-md-inline-code-color'] =
@@ -670,6 +689,42 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
670
689
  cssVars['--persona-md-prose-font-family'] = mdProseFont;
671
690
  }
672
691
 
692
+ // Artifact tokens
693
+ const components = theme.components;
694
+ const artifact = components?.artifact;
695
+ if (artifact?.toolbar) {
696
+ const t = artifact.toolbar;
697
+ if (t.iconHoverColor) cssVars['--persona-artifact-toolbar-icon-hover-color'] = t.iconHoverColor;
698
+ if (t.iconHoverBackground) cssVars['--persona-artifact-toolbar-icon-hover-bg'] = t.iconHoverBackground;
699
+ if (t.iconPadding) cssVars['--persona-artifact-toolbar-icon-padding'] = t.iconPadding;
700
+ if (t.iconBorderRadius) cssVars['--persona-artifact-toolbar-icon-radius'] = t.iconBorderRadius;
701
+ if (t.iconBorder) cssVars['--persona-artifact-toolbar-icon-border'] = t.iconBorder;
702
+ if (t.toggleGroupGap) cssVars['--persona-artifact-toolbar-toggle-group-gap'] = t.toggleGroupGap;
703
+ if (t.toggleBorderRadius) cssVars['--persona-artifact-toolbar-toggle-radius'] = t.toggleBorderRadius;
704
+ if (t.copyBackground) cssVars['--persona-artifact-toolbar-copy-bg'] = t.copyBackground;
705
+ if (t.copyBorder) cssVars['--persona-artifact-toolbar-copy-border'] = t.copyBorder;
706
+ if (t.copyColor) cssVars['--persona-artifact-toolbar-copy-color'] = t.copyColor;
707
+ if (t.copyBorderRadius) cssVars['--persona-artifact-toolbar-copy-radius'] = t.copyBorderRadius;
708
+ if (t.copyPadding) cssVars['--persona-artifact-toolbar-copy-padding'] = t.copyPadding;
709
+ if (t.copyMenuBackground) cssVars['--persona-artifact-toolbar-copy-menu-bg'] = t.copyMenuBackground;
710
+ if (t.copyMenuBorder) cssVars['--persona-artifact-toolbar-copy-menu-border'] = t.copyMenuBorder;
711
+ if (t.copyMenuShadow) cssVars['--persona-artifact-toolbar-copy-menu-shadow'] = t.copyMenuShadow;
712
+ if (t.copyMenuBorderRadius) cssVars['--persona-artifact-toolbar-copy-menu-radius'] = t.copyMenuBorderRadius;
713
+ if (t.copyMenuItemHoverBackground) cssVars['--persona-artifact-toolbar-copy-menu-item-hover-bg'] = t.copyMenuItemHoverBackground;
714
+ }
715
+ if (artifact?.tab) {
716
+ const t = artifact.tab;
717
+ if (t.background) cssVars['--persona-artifact-tab-bg'] = t.background;
718
+ if (t.activeBackground) cssVars['--persona-artifact-tab-active-bg'] = t.activeBackground;
719
+ if (t.activeBorder) cssVars['--persona-artifact-tab-active-border'] = t.activeBorder;
720
+ if (t.borderRadius) cssVars['--persona-artifact-tab-radius'] = t.borderRadius;
721
+ if (t.textColor) cssVars['--persona-artifact-tab-color'] = t.textColor;
722
+ }
723
+ if (artifact?.pane) {
724
+ const t = artifact.pane;
725
+ if (t.toolbarBackground) cssVars['--persona-artifact-toolbar-bg'] = t.toolbarBackground;
726
+ }
727
+
673
728
  return cssVars;
674
729
  }
675
730
 
@@ -680,3 +735,21 @@ export function applyThemeVariables(element: HTMLElement, theme: PersonaTheme):
680
735
  element.style.setProperty(name, value);
681
736
  }
682
737
  }
738
+
739
+ /**
740
+ * Stable `data-persona-theme-zone` values applied to key widget regions.
741
+ * Visual editors should use `[data-persona-theme-zone="header"]` selectors
742
+ * rather than internal class names.
743
+ */
744
+ export const THEME_ZONES = {
745
+ header: 'Widget header bar',
746
+ messages: 'Message list area',
747
+ 'user-message': 'User message bubble',
748
+ 'assistant-message': 'Assistant message bubble',
749
+ composer: 'Footer / composer area',
750
+ container: 'Main widget container',
751
+ 'artifact-pane': 'Artifact sidebar',
752
+ 'artifact-toolbar': 'Artifact toolbar',
753
+ } as const;
754
+
755
+ export type ThemeZone = keyof typeof THEME_ZONES;