import React, { useCallback, useEffect, useRef } from "react"; import type { PaintFile, PaintKeyboardEvent, TextAreaElement } from "paintcannon"; import { Div, Span, Textarea, useApp } from "paintcannon-react"; import { useVimKeyHandler } from "./vim-mode.tsx"; import { FOREGROUND_COLOR } from "../theme.ts"; import { ImageInfo } from "../utils/image-utils.ts"; function getImageBadgeText(index: number): string { return `⟦ 📎 Image Attachment #${index + 1} ⟧`; } const LOADING_BADGE_TEXT = "⟦ ⏳ Attaching image... ⟧"; type Props = { readonly placeholder?: string; readonly focus?: boolean; readonly value: string; readonly onChange: (value: string) => void; readonly onImageFilesAttached?: (files: PaintFile[]) => unknown; readonly onSubmit?: (value: string) => void; readonly showLoadingImageBadge?: boolean; readonly vimEnabled?: boolean; readonly vimMode?: "NORMAL" | "INSERT"; readonly setVimMode?: (mode: "NORMAL" | "INSERT") => void; readonly attachedImages?: ImageInfo[]; readonly onRemoveLastImage?: () => unknown; readonly onKeyDown?: (event: PaintKeyboardEvent) => void; }; function characterIndexToStringIndex(value: string, characterIndex: number): number { return Array.from(value).slice(0, characterIndex).join("").length; } function stringIndexToCharacterIndex(value: string, stringIndex: number): number { return Array.from(value.slice(0, stringIndex)).length; } export default function TextInput({ attachedImages = [], value, showLoadingImageBadge = false, placeholder = "", focus = true, onChange, onImageFilesAttached, onRemoveLastImage, onSubmit, vimEnabled = false, vimMode = "NORMAL", setVimMode, onKeyDown, }: Props) { const { paintCannon } = useApp(); const textareaRef = useRef(null); const vimHandler = useVimKeyHandler(vimMode, setVimMode ?? (() => {})); useEffect(() => { if (focus) textareaRef.current?.focus(); else textareaRef.current?.blur(); }, [focus]); const setCursorAfterValueChange = useCallback( (nextValue: string, stringIndex: number) => { const position = stringIndexToCharacterIndex(nextValue, stringIndex); paintCannon.requestAnimationFrame(() => { if (textareaRef.current) textareaRef.current.cursorPosition = position; }); }, [paintCannon], ); return (
{(attachedImages.length > 0 || showLoadingImageBadge) && (
{attachedImages.map((_, index) => ( {getImageBadgeText(index)} ))} {showLoadingImageBadge && ( {LOADING_BADGE_TEXT} )}
)}