import { type JSX, splitProps, createSignal, createContext, useContext } from 'solid-js'; import { cn } from '../utils/cn'; import { useChatConfig, textClass } from '../primitives/chat-config'; import { Composer, type TriggerDef, type ComposerChange } from './composer'; import type { ComposerDoc } from '../primitives/composer-model'; // --- Context --- interface PromptInputContextType { isLoading: boolean; // A string (controlled text) or a ComposerDoc (a seed that pre-populates pills). value: () => string | ComposerDoc; setValue: (value: string) => void; maxHeight: number | string; onSubmit?: () => void; disabled?: boolean; textareaRef: HTMLElement | undefined; setTextareaRef: (el: HTMLElement) => void; } const PromptInputContext = createContext(); function usePromptInput() { const ctx = useContext(PromptInputContext); if (!ctx) throw new Error('PromptInput subcomponents must be used within PromptInput'); return ctx; } // --- PromptInput (Root) --- export interface PromptInputProps extends JSX.HTMLAttributes { isLoading?: boolean; /** String = controlled text; ComposerDoc = a seed that pre-populates pills. */ value?: string | ComposerDoc; onValueChange?: (value: string) => void; maxHeight?: number | string; onSubmit?: () => void; children: JSX.Element; disabled?: boolean; } function PromptInput(props: PromptInputProps) { const [local, rest] = splitProps(props, [ 'isLoading', 'value', 'onValueChange', 'maxHeight', 'onSubmit', 'children', 'disabled', 'class', 'onClick', ]); const [internalValue, setInternalValue] = createSignal(local.value ?? ''); let textareaRef: HTMLElement | undefined; const handleChange = (newValue: string) => { setInternalValue(newValue); local.onValueChange?.(newValue); }; const handleClick: JSX.EventHandler = (e) => { if (!local.disabled) textareaRef?.focus(); if (typeof local.onClick === 'function') { (local.onClick as (e: MouseEvent & { currentTarget: HTMLDivElement }) => void)(e); } }; return ( local.value ?? internalValue(), setValue: local.onValueChange ?? handleChange, maxHeight: local.maxHeight ?? 240, onSubmit: local.onSubmit, get disabled() { return local.disabled; }, get textareaRef() { return textareaRef; }, setTextareaRef: (el) => { textareaRef = el; }, }} >
{local.children}
); } // --- PromptInputTextarea --- export interface PromptInputTextareaProps extends JSX.TextareaHTMLAttributes { disableAutosize?: boolean; /** Rich entity triggers (`/`, `@`) forwarded to the composer. */ triggers?: TriggerDef[]; /** Default icon per entity kind (kind → image src), forwarded to the composer. */ kindIcons?: Record; /** Structured change (doc + entities) from the composer, on every edit. */ onComposerChange?: (change: ComposerChange) => void; } function PromptInputTextarea(props: PromptInputTextareaProps) { const [local] = splitProps(props, ['class', 'placeholder', 'aria-label', 'triggers', 'kindIcons', 'onComposerChange']); const ctx = usePromptInput(); const config = useChatConfig(); // The editable mirrors the original