/** @import { Message, Theme, ChatState, Trigger, Effect, Device, ResolvedTheme } from './types.js' */ /** @import { Labels } from './i18n/labels.js' */ /** * The chat widget. * * One custom element, one shadow root. Everything inside is drawn by plain * functions in `render/` and driven by the reactive controllers in * `controllers/`; no nested custom elements, so `::part()` and the theme's * custom properties reach every corner without being forwarded. * * @element chit-ui * * @slot launcher - Replaces the whole closed-state button content. * @slot header - Replaces the panel header. * @slot header-title - Replaces only the title area of the header. * @slot header-actions - Extra buttons beside the close button. * @slot footer - Below the composer (disclaimers, attribution). */ export class ChitUI extends LitElement { /** @override */ static override styles: import("lit").CSSResult[]; /** @override */ static override properties: { state: { type: StringConstructor; reflect: boolean; noAccessor: boolean; }; theme: { type: ObjectConstructor; }; messages: { type: ArrayConstructor; }; typing: { type: ObjectConstructor; noAccessor: boolean; }; loading: { type: BooleanConstructor; reflect: boolean; noAccessor: boolean; }; busy: { type: BooleanConstructor; reflect: boolean; }; inputDisabled: { type: BooleanConstructor; reflect: boolean; attribute: string; }; inputHidden: { type: BooleanConstructor; reflect: boolean; attribute: string; }; placeholder: { type: StringConstructor; reflect: boolean; }; sendOnEnter: { type: BooleanConstructor; reflect: boolean; attribute: string; }; maxLength: { type: NumberConstructor; reflect: boolean; attribute: string; }; focusOnOpen: { type: StringConstructor; reflect: boolean; attribute: string; }; locale: { type: StringConstructor; reflect: boolean; }; labels: { type: ObjectConstructor; }; messageStyles: { type: StringConstructor; }; }; /** @type {Theme} Partial; unset keys fall back to the default theme. */ theme: Theme; /** @type {Message[]} Rendered as given. The library never mutates this. */ messages: Message[]; /** @type {boolean} Lock the composer while a reply is in flight. */ busy: boolean; /** @type {boolean} */ inputDisabled: boolean; /** @type {boolean} */ inputHidden: boolean; /** @type {string | undefined} */ placeholder: string | undefined; /** @type {boolean} */ sendOnEnter: boolean; /** @type {number | undefined} */ maxLength: number | undefined; /** * Focus the composer once the open transition finishes. 'auto' focuses on * PC only, so a phone keyboard never pops up unasked. * * Named focusOnOpen rather than autofocus: HTMLElement.autofocus is a * standard boolean property and cannot carry a third value. * * @type {'auto' | 'always' | 'never'} */ focusOnOpen: "auto" | "always" | "never"; /** @type {string | undefined} */ locale: string | undefined; /** @type {Partial | undefined} Overrides for the built-in UI strings. */ labels: Partial | undefined; /** @type {string} Extra CSS applied inside message content. */ messageStyles: string; /** @type {((html: string) => string) | undefined} Applied to `html` messages only. */ sanitize: ((html: string) => string) | undefined; /** @type {((time: Date) => string) | undefined} */ formatTime: ((time: Date) => string) | undefined; /** The launcher's drag, for the render function to hook up. */ get launcherDrag(): DragController; /** The panel's drag, likewise. */ get panelDrag(): DragController; set dragOffset(value: { x: number; y: number; } | null); /** * How far the reader has dragged the widget from where the theme put it, in * screen pixels, or null when it is still there. * * There is one of these for the whole widget rather than one per state: the * launcher and the panel are the same widget wearing two shapes, so moving * either moves both, and the panel opens where the launcher was left. * * @type {{ x: number, y: number } | null} */ get dragOffset(): { x: number; y: number; } | null; /** Forget the dragged position and go back to what the theme says. */ resetPosition(): void; /** * The composer calls this once a submit has gone out and no listener * cancelled it. Listening for `chat-submit` here instead would be wrong: * this element's own listener runs before the consumer's, so it cannot see * a `preventDefault()` that is still to come. * * @returns {void} */ handleSubmitted(): void; set loading(value: boolean); /** * Show the "waiting for an answer" indicator. Turning this on turns * `typing` off, for the reason given above. * * @type {boolean} */ get loading(): boolean; set typing(value: boolean | { html: string; }); /** * Show the "typing" bubble: `true`, or `{ html }` for your own markup. * * Turning this on turns `loading` off. The two describe the same pause in * the conversation — one says a person is writing, the other that a server * has not answered yet — and showing both would leave the reader to work * out the difference. * * @type {boolean | { html: string }} */ get typing(): boolean | { html: string; }; set state(value: ChatState); /** * Current state. Assigning it starts the transition, exactly as calling the * matching method would. * * @type {ChatState} */ get state(): ChatState; /** @returns {Device} Which theme bucket the viewport currently falls in. */ get device(): Device; /** * The theme in force for this device, every gap filled in. * * @returns {ResolvedTheme} */ get currentTheme(): ResolvedTheme; /** @returns {Labels} */ get currentLabels(): Labels; /** * The language in force: the `locale` property, else the document's. * Labels and timestamps both read it, so a page in Japanese does not end up * with Japanese buttons and American clock times. * * @returns {string | undefined} */ get resolvedLocale(): string | undefined; /** @returns {ChatState} What the DOM is showing, which lags `state` while animating. */ get renderedState(): ChatState; /** @returns {boolean} True when messages arrived while the reader was scrolled up. */ get hasUnseen(): boolean; set value(next: string); /** * What is currently typed in the composer. * * Not a reactive property on purpose: a keystroke would otherwise re-render * the whole shadow tree, every message included. * * @type {string} */ get value(): string; /** @returns {boolean} Whether what is typed could be sent right now. */ get canSend(): boolean; /** @returns {Promise} Resolves once the open animation has finished. */ open(): Promise; /** @returns {Promise} */ close(): Promise; /** @returns {Promise} */ hide(): Promise; /** @returns {Promise} From `hidden` back to the launcher. A no-op otherwise. */ show(): Promise; /** @returns {Promise} */ toggle(): Promise; /** * Send what is in the composer, or the given text, as `chat-submit`. * * Adding the message to `messages` stays the consumer's job; the widget only * reports that someone pressed send. * * @param {string} [text] * @returns {boolean} False when there was nothing to send. */ submit(text?: string): boolean; /** Empty the composer and put the caret back in it. */ clearInput(): void; /** * Scroll the conversation to the newest message. * * @param {{ smooth?: boolean }} [options] */ scrollToBottom(options?: { smooth?: boolean; }): void; /** * The content container of one message, once it has been drawn. * * @param {string} id * @returns {HTMLElement | null} */ getMessageElement(id: string): HTMLElement | null; /** * Put the caret in the composer. * * This obeys the caller, not `focusOnOpen`: someone who calls it has asked * for focus. `focusOnOpen` only governs what happens by itself on open. * * @returns {boolean} False when there is no composer to focus. */ focusInput(): boolean; /** @param {Event} event */ handleInput(event: Event): void; /** @param {KeyboardEvent} event */ handleKeydown(event: KeyboardEvent): void; /** @param {boolean} started */ handleComposition(started: boolean): void; /** * Report files the reader picked with the attach button. * * Nothing is uploaded, previewed or added to the conversation here: the * widget does not own `messages` and has nowhere to send bytes. * * @param {File[]} files * @returns {void} */ handleAttach(files: File[]): void; /** Open the file picker from code, as the attach button does. */ openAttach(): void; /** * Render a consumer-supplied typing bubble. * * @param {string} markup * @returns {unknown} */ renderTypingHtml(markup: string): unknown; /** * Re-publish a click inside a message as `chat-message-click`. * * `composedPath()[0]` rather than `event.target`: the target is retargeted to * the message container once the event leaves a component's shadow root, and * the consumer wants the element actually clicked. * * @param {MouseEvent} event */ handleMessageClick(event: MouseEvent): void; /** Called by the launcher; the resulting events report `trigger: 'user'`. */ toggleFromUser(): Promise; /** Called by the close button and by Esc. */ closeFromUser(): Promise; /** * Ask to start the conversation over. * * The widget only announces it: `messages` belongs to the consumer, so * clearing it, replaying a scenario or asking for confirmation first are all * theirs to do. Cancelling the event is not offered for the same reason — * there is nothing here to cancel. * * @param {Trigger} [trigger='api'] * @returns {void} */ home(trigger?: Trigger): void; /** @override */ override willUpdate(): void; /** @override */ override updated(): void; /** @override */ override render(): import("lit-html").TemplateResult<1>; #private; } import { LitElement } from 'lit'; import type { Theme } from './types.js'; import type { Message } from './types.js'; import type { Labels } from './i18n/labels.js'; import { DragController } from './controllers/drag-controller.js'; import type { ChatState } from './types.js'; import type { Device } from './types.js'; import type { ResolvedTheme } from './types.js'; import type { Trigger } from './types.js';