diff --git a/widget/README.md b/widget/README.md index 2e3c3b4580..2a5c640191 100644 --- a/widget/README.md +++ b/widget/README.md @@ -123,6 +123,7 @@ That's it! The widget will appear as a floating button in the bottom-right corne | `text-color` | string | `#000000bf` | Text color (75% opacity black) | | `mode` | string | `"launcher"` | Display mode: `"launcher"` or `"inline"` | | `include-citations`| boolean | `false` | Include citation markers in responses | +| `start-expanded` | boolean | `false` | Inline mode: show the full chat panel before the first message, instead of the compact input bar | **Note**: These attributes must be provided as HTML attributes. Only `backend-url` and `api-key` can optionally be set via environment variables for self-hosted builds. @@ -232,7 +233,21 @@ The widget is embedded directly in your page layout. Perfect for dedicated suppo ``` -**CSS Tip**: The widget will fill its container's dimensions in inline mode. +**CSS Tip**: The widget will fill its container's dimensions in inline mode. Give the container an explicit height; otherwise the panel grows with the conversation. + +Before the first message, inline mode shows a compact input bar. Add `start-expanded` to show the full chat panel at the container's size from the start: + +```html +
+ +
+``` + +## Keyboard Events + +Key presses typed into the widget do not propagate to the host page (except Escape). The widget uses Shadow DOM, so a page-level handler sees `event.target` as ``, not the text field. Without this, handlers that block Backspace outside text fields would also block it in the widget. + +Handlers registered in the capture phase still run first. If such a handler blocks keys outside text fields, check `event.composedPath()[0]` instead of `event.target`. ## Development diff --git a/widget/src/config/config.ts b/widget/src/config/config.ts index ee2fa5278e..c098afa0b3 100644 --- a/widget/src/config/config.ts +++ b/widget/src/config/config.ts @@ -23,6 +23,7 @@ export function resolveConfig(attributes: Partial): WidgetConfig { logo: attributes.logo, mode: attributes.mode || "launcher", includeCitations: attributes.includeCitations ?? false, + startExpanded: attributes.startExpanded ?? false, }; if (!config.backendUrl) { diff --git a/widget/src/styles/widget-styles.ts b/widget/src/styles/widget-styles.ts index 6f94118c25..c6483b1272 100644 --- a/widget/src/styles/widget-styles.ts +++ b/widget/src/styles/widget-styles.ts @@ -10,6 +10,12 @@ export const widgetStyles = css` font-family: var(--onyx-font-family); } + /* Without a host height, the inline container's height: 100% resolves to + auto and the panel grows with its content instead of filling the parent. */ + :host([mode="inline"]) { + height: 100%; + } + .launcher { position: fixed; background: var(--background-neutral-00); diff --git a/widget/src/types/widget-types.ts b/widget/src/types/widget-types.ts index 1ee62b2b70..615973bde5 100644 --- a/widget/src/types/widget-types.ts +++ b/widget/src/types/widget-types.ts @@ -29,6 +29,8 @@ export interface WidgetConfig { // Optional - Display mode?: "launcher" | "inline"; + // Inline mode: show the full chat panel before the first message. + startExpanded?: boolean; // Optional - Citations includeCitations?: boolean; diff --git a/widget/src/widget.ts b/widget/src/widget.ts index d0ace958a5..d5dfd619b8 100644 --- a/widget/src/widget.ts +++ b/widget/src/widget.ts @@ -35,9 +35,12 @@ export class OnyxChatWidget extends LitElement { @property({ attribute: "text-color" }) textColor?: string; @property({ attribute: "agent-name" }) agentName?: string; @property({ attribute: "logo" }) logo?: string; - @property() mode?: "launcher" | "inline"; + // Reflected so `:host([mode="inline"])` also matches when set as a property. + @property({ reflect: true }) mode?: "launcher" | "inline"; @property({ attribute: "include-citations", type: Boolean }) includeCitations?: boolean; + @property({ attribute: "start-expanded", type: Boolean }) + startExpanded?: boolean; // Assigned as a JS property, since a function cannot ride an HTML attribute. // Takes precedence over `api-key` and keeps the credential out of the markup. @@ -113,6 +116,7 @@ export class OnyxChatWidget extends LitElement { logo: this.logo, mode: this.mode, includeCitations: this.includeCitations, + startExpanded: this.startExpanded, }); // Apply custom colors @@ -358,6 +362,13 @@ export class OnyxChatWidget extends LitElement { } private handleKeyDown(e: KeyboardEvent) { + // Shadow DOM retargets `e.target` to the host element, so page-level + // handlers that let keys through only for text fields (e.g. ones that + // block Backspace navigation) would cancel typing here. Escape still + // propagates so the host page can close its overlays. + if (e.key !== "Escape") { + e.stopPropagation(); + } if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); this.sendMessage(); @@ -538,7 +549,10 @@ export class OnyxChatWidget extends LitElement { render() { const showContainer = this.config.mode === "inline" || this.isOpen; const hasMessages = this.messages.length > 0 || this.isStreaming; - const isCompactInline = this.config.mode === "inline" && !hasMessages; + const isCompactInline = + this.config.mode === "inline" && + !this.config.startExpanded && + !hasMessages; return html` ${this.config.mode === "launcher"