import { useEffect, useRef, useState } from "react"; import type { RpcCommand, RpcField } from "../commands/rpc"; const controls = (container: HTMLElement | null) => Array.from( container?.querySelectorAll("button, input, select, textarea") ?? [], ).filter((node) => !node.hasAttribute("disabled")); function valueFor(field: RpcField, value: string | boolean): unknown { if (field.kind === "boolean") return value === true; if (field.kind === "json") return value ? JSON.parse(value as string) : undefined; return value || undefined; } export function CommandFormDialog({ command, onSubmit, onCancel, pending = false, commandError, }: { command?: RpcCommand; onSubmit: (input: Record) => void; onCancel: () => void; pending?: boolean; commandError?: string; }) { const ref = useRef(null); const opener = useRef(null); const [values, setValues] = useState>({}); const [validationError, setValidationError] = useState(); useEffect(() => { if (!command) return; opener.current = document.activeElement as HTMLElement | null; setValues({}); setValidationError(undefined); const frame = requestAnimationFrame(() => controls(ref.current)[0]?.focus()); return () => { cancelAnimationFrame(frame); opener.current?.focus(); }; }, [command]); if (!command) return null; const fields = command.input ?? []; const submit = () => { if (pending) return; try { const input: Record = {}; for (const field of fields) { const value = valueFor( field, values[field.name] ?? (field.kind === "boolean" ? false : ""), ); if (field.required && (value === undefined || value === "")) throw new Error(`${field.label} is required`); if (value !== undefined) input[field.name] = value; } onSubmit(input); } catch (cause) { setValidationError(cause instanceof Error ? cause.message : "Invalid input"); } }; return (
{ if (event.key === "Escape") { event.preventDefault(); onCancel(); } if (event.key === "Tab") { const items = controls(ref.current), first = items[0], last = items[items.length - 1]; if (!first || !last) return; if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } } }} >

Pi RPC command

/{command.command}

{command.description}

{fields.map((field) => (