@runtypelabs/persona 3.2.1 → 3.3.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/index.ts CHANGED
@@ -194,6 +194,14 @@ export type {
194
194
  ComboButtonHandle
195
195
  } from "./utils/buttons";
196
196
 
197
+ // Demo carousel exports
198
+ export { createDemoCarousel } from "./components/demo-carousel";
199
+ export type {
200
+ DemoCarouselItem,
201
+ DemoCarouselOptions,
202
+ DemoCarouselHandle
203
+ } from "./components/demo-carousel";
204
+
197
205
  // Theme system exports
198
206
  export {
199
207
  createTheme,
@@ -198,7 +198,7 @@ const applyDockStyles = (
198
198
  dockSlot.style.minWidth = "0";
199
199
  dockSlot.style.minHeight = "0";
200
200
  dockSlot.style.overflow = "hidden";
201
- dockSlot.style.zIndex = "9999";
201
+ dockSlot.style.zIndex = String(config?.launcher?.zIndex ?? 9999);
202
202
  dockSlot.style.transform = "none";
203
203
  dockSlot.style.transition = "none";
204
204
  dockSlot.style.pointerEvents = "auto";
@@ -1222,6 +1222,68 @@
1222
1222
  font-size: 0.8125rem;
1223
1223
  }
1224
1224
 
1225
+ /* Code block copy button */
1226
+ .persona-code-block-wrapper {
1227
+ position: relative;
1228
+ margin: 0.5rem 0;
1229
+ }
1230
+
1231
+ .persona-code-block-wrapper pre {
1232
+ margin: 0 !important;
1233
+ border-top-left-radius: 0 !important;
1234
+ border-top-right-radius: 0 !important;
1235
+ }
1236
+
1237
+ .persona-code-block-header {
1238
+ display: flex;
1239
+ align-items: center;
1240
+ justify-content: space-between;
1241
+ background-color: var(--persona-md-code-block-bg, #f3f4f6);
1242
+ border: 1px solid var(--persona-md-code-block-border-color, #e5e7eb);
1243
+ border-bottom: none;
1244
+ border-top-left-radius: var(--persona-md-code-block-border-radius, 0.375rem);
1245
+ border-top-right-radius: var(--persona-md-code-block-border-radius, 0.375rem);
1246
+ padding: 0.25rem 0.5rem 0.25rem 0.75rem;
1247
+ font-size: 0.75rem;
1248
+ color: var(--persona-text-muted, #6b7280);
1249
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
1250
+ }
1251
+
1252
+ .persona-code-copy-btn {
1253
+ display: inline-flex;
1254
+ align-items: center;
1255
+ gap: 0.25rem;
1256
+ background: none;
1257
+ border: 1px solid transparent;
1258
+ border-radius: 0.25rem;
1259
+ padding: 0.25rem 0.5rem;
1260
+ cursor: pointer;
1261
+ color: var(--persona-text-muted, #6b7280);
1262
+ font-size: 0.75rem;
1263
+ font-family: inherit;
1264
+ line-height: 1;
1265
+ transition: color 0.15s ease, border-color 0.15s ease;
1266
+ }
1267
+
1268
+ .persona-code-copy-btn:hover {
1269
+ color: var(--persona-text, #111827);
1270
+ border-color: var(--persona-md-code-block-border-color, #e5e7eb);
1271
+ }
1272
+
1273
+ .persona-code-copy-btn.persona-code-copied {
1274
+ color: #16a34a;
1275
+ }
1276
+
1277
+ .persona-code-copy-btn.persona-code-copy-generating {
1278
+ cursor: default;
1279
+ opacity: 0.5;
1280
+ }
1281
+
1282
+ .persona-code-copy-btn.persona-code-copy-generating:hover {
1283
+ color: var(--persona-text-muted, #6b7280);
1284
+ border-color: transparent;
1285
+ }
1286
+
1225
1287
  /* Ensure all links in chat bubbles have underlines */
1226
1288
  .vanilla-message-bubble a {
1227
1289
  text-decoration: underline;
package/src/types.ts CHANGED
@@ -790,6 +790,14 @@ export type AgentWidgetLauncherConfig = {
790
790
  * @default 640
791
791
  */
792
792
  mobileBreakpoint?: number;
793
+ /**
794
+ * CSS z-index applied to the widget wrapper when it is in a positioned mode
795
+ * (floating panel, mobile fullscreen, or sidebar). Increase this value if
796
+ * other elements on the host page appear on top of the widget.
797
+ *
798
+ * @default 9999 in overlay modes (mobile fullscreen / sidebar); 50 for the regular floating panel
799
+ */
800
+ zIndex?: number;
793
801
  callToActionIconText?: string;
794
802
  callToActionIconName?: string;
795
803
  callToActionIconColor?: string;
@@ -876,7 +884,11 @@ export type AgentWidgetClearChatConfig = {
876
884
 
877
885
  export type AgentWidgetStatusIndicatorConfig = {
878
886
  visible?: boolean;
887
+ /** Text alignment. Default: 'right'. */
888
+ align?: 'left' | 'center' | 'right';
879
889
  idleText?: string;
890
+ /** URL to open in a new tab when the idle text is clicked. */
891
+ idleLink?: string;
880
892
  connectingText?: string;
881
893
  connectedText?: string;
882
894
  errorText?: string;
@@ -0,0 +1,62 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+
5
+ import { createAgentExperience } from "./ui";
6
+
7
+ const originalInnerWidth = window.innerWidth;
8
+
9
+ const setInnerWidth = (value: number) => {
10
+ Object.defineProperty(window, "innerWidth", {
11
+ configurable: true,
12
+ writable: true,
13
+ value,
14
+ });
15
+ };
16
+
17
+ const createMount = () => {
18
+ const mount = document.createElement("div");
19
+ document.body.appendChild(mount);
20
+ return mount;
21
+ };
22
+
23
+ describe("createAgentExperience overlay z-index", () => {
24
+ afterEach(() => {
25
+ document.body.innerHTML = "";
26
+ setInnerWidth(originalInnerWidth);
27
+ });
28
+
29
+ it("defaults sidebar mode to the overlay z-index", () => {
30
+ const mount = createMount();
31
+ const controller = createAgentExperience(mount, {
32
+ apiUrl: "https://api.example.com/chat",
33
+ launcher: {
34
+ sidebarMode: true,
35
+ position: "bottom-right",
36
+ },
37
+ });
38
+
39
+ const wrapper = mount.firstElementChild as HTMLElement | null;
40
+
41
+ expect(wrapper).not.toBeNull();
42
+ expect(wrapper?.style.zIndex).toBe("9999");
43
+
44
+ controller.destroy();
45
+ });
46
+
47
+ it("defaults mobile fullscreen to the overlay z-index", () => {
48
+ setInnerWidth(480);
49
+
50
+ const mount = createMount();
51
+ const controller = createAgentExperience(mount, {
52
+ apiUrl: "https://api.example.com/chat",
53
+ });
54
+
55
+ const wrapper = mount.firstElementChild as HTMLElement | null;
56
+
57
+ expect(wrapper).not.toBeNull();
58
+ expect(wrapper?.style.zIndex).toBe("9999");
59
+
60
+ controller.destroy();
61
+ });
62
+ });
package/src/ui.ts CHANGED
@@ -604,6 +604,23 @@ export const createAgentExperience = (
604
604
  return statusCopy[status];
605
605
  };
606
606
 
607
+ /** Update statusText element, rendering a link for idle status when idleLink is configured. */
608
+ function applyStatusToElement(el: HTMLElement, text: string, statusCfg: typeof statusConfig, status: string): void {
609
+ if (status === "idle" && statusCfg.idleLink) {
610
+ el.textContent = "";
611
+ const link = document.createElement("a");
612
+ link.href = statusCfg.idleLink;
613
+ link.target = "_blank";
614
+ link.rel = "noopener noreferrer";
615
+ link.textContent = text;
616
+ link.style.color = "inherit";
617
+ link.style.textDecoration = "none";
618
+ el.appendChild(link);
619
+ } else {
620
+ el.textContent = text;
621
+ }
622
+ }
623
+
607
624
  const { wrapper, panel } = createWrapper(config);
608
625
  const panelElements = buildPanel(config, launcherEnabled);
609
626
  let {
@@ -1474,6 +1491,7 @@ export const createAgentExperience = (
1474
1491
  // Determine panel styling based on mode, with theme overrides
1475
1492
  const position = config.launcher?.position ?? 'bottom-left';
1476
1493
  const isLeftSidebar = position === 'bottom-left' || position === 'top-left';
1494
+ const overlayZIndex = config.launcher?.zIndex ?? 9999;
1477
1495
 
1478
1496
  // Default values based on mode
1479
1497
  let defaultPanelBorder = (sidebarMode || shouldGoFullscreen) ? 'none' : '1px solid var(--persona-border)';
@@ -1524,7 +1542,8 @@ export const createAgentExperience = (
1524
1542
  padding: 0 !important;
1525
1543
  display: flex !important;
1526
1544
  flex-direction: column !important;
1527
- z-index: inherit !important;
1545
+ z-index: ${overlayZIndex} !important;
1546
+ background-color: var(--persona-surface, #ffffff) !important;
1528
1547
  `;
1529
1548
 
1530
1549
  // Panel — fill wrapper, no radius/shadow
@@ -1690,6 +1709,7 @@ export const createAgentExperience = (
1690
1709
  padding: 0 !important;
1691
1710
  display: flex !important;
1692
1711
  flex-direction: column !important;
1712
+ z-index: ${overlayZIndex} !important;
1693
1713
  ${isLeftSidebar ? 'left: 0 !important; right: auto !important;' : 'left: auto !important; right: 0 !important;'}
1694
1714
  `;
1695
1715
 
@@ -1744,7 +1764,11 @@ export const createAgentExperience = (
1744
1764
  if (!isInlineEmbed && !dockedMode) {
1745
1765
  const maxHeightStyles = 'max-height: -moz-available !important; max-height: stretch !important;';
1746
1766
  const paddingStyles = sidebarMode ? '' : 'padding-top: 1.25em !important;';
1747
- wrapper.style.cssText += maxHeightStyles + paddingStyles;
1767
+ // Override z-index only when explicitly configured; otherwise the persona-z-50 class applies
1768
+ const zIndexStyles = !sidebarMode && config.launcher?.zIndex != null
1769
+ ? `z-index: ${config.launcher.zIndex} !important;`
1770
+ : '';
1771
+ wrapper.style.cssText += maxHeightStyles + paddingStyles + zIndexStyles;
1748
1772
  }
1749
1773
  };
1750
1774
  applyFullHeightStyles();
@@ -2663,14 +2687,14 @@ export const createAgentExperience = (
2663
2687
  },
2664
2688
  onStatusChanged(status) {
2665
2689
  const currentStatusConfig = config.statusIndicator ?? {};
2666
- const getCurrentStatusText = (status: AgentWidgetSessionStatus): string => {
2667
- if (status === "idle") return currentStatusConfig.idleText ?? statusCopy.idle;
2668
- if (status === "connecting") return currentStatusConfig.connectingText ?? statusCopy.connecting;
2669
- if (status === "connected") return currentStatusConfig.connectedText ?? statusCopy.connected;
2670
- if (status === "error") return currentStatusConfig.errorText ?? statusCopy.error;
2671
- return statusCopy[status];
2690
+ const getCurrentStatusText = (s: AgentWidgetSessionStatus): string => {
2691
+ if (s === "idle") return currentStatusConfig.idleText ?? statusCopy.idle;
2692
+ if (s === "connecting") return currentStatusConfig.connectingText ?? statusCopy.connecting;
2693
+ if (s === "connected") return currentStatusConfig.connectedText ?? statusCopy.connected;
2694
+ if (s === "error") return currentStatusConfig.errorText ?? statusCopy.error;
2695
+ return statusCopy[s];
2672
2696
  };
2673
- statusText.textContent = getCurrentStatusText(status);
2697
+ applyStatusToElement(statusText, getCurrentStatusText(status), currentStatusConfig, status);
2674
2698
  },
2675
2699
  onStreamingChanged(streaming) {
2676
2700
  isStreaming = streaming;
@@ -4808,14 +4832,14 @@ export const createAgentExperience = (
4808
4832
  // Update status text if status is currently set
4809
4833
  if (session) {
4810
4834
  const currentStatus = session.getStatus();
4811
- const getCurrentStatusText = (status: AgentWidgetSessionStatus): string => {
4812
- if (status === "idle") return statusIndicatorConfig.idleText ?? statusCopy.idle;
4813
- if (status === "connecting") return statusIndicatorConfig.connectingText ?? statusCopy.connecting;
4814
- if (status === "connected") return statusIndicatorConfig.connectedText ?? statusCopy.connected;
4815
- if (status === "error") return statusIndicatorConfig.errorText ?? statusCopy.error;
4816
- return statusCopy[status];
4835
+ const getCurrentStatusText = (s: AgentWidgetSessionStatus): string => {
4836
+ if (s === "idle") return statusIndicatorConfig.idleText ?? statusCopy.idle;
4837
+ if (s === "connecting") return statusIndicatorConfig.connectingText ?? statusCopy.connecting;
4838
+ if (s === "connected") return statusIndicatorConfig.connectedText ?? statusCopy.connected;
4839
+ if (s === "error") return statusIndicatorConfig.errorText ?? statusCopy.error;
4840
+ return statusCopy[s];
4817
4841
  };
4818
- statusText.textContent = getCurrentStatusText(currentStatus);
4842
+ applyStatusToElement(statusText, getCurrentStatusText(currentStatus), statusIndicatorConfig, currentStatus);
4819
4843
  }
4820
4844
  },
4821
4845
  open() {