/** * @file Form Builder Node - Interactive form builder with various field types */ import * as React from "react"; import type { NodeDefinition, NodeRendererProps, InspectorRenderProps, ExternalDataReference, } from "../../../../../types/NodeDefinition"; import { getTextColor } from "./colorUtils"; export type FormData = { id: string; title: string; fields: Array<{ id: string; type: "text" | "email" | "number" | "select" | "textarea" | "checkbox"; label: string; required: boolean; options?: string[]; // for select fields }>; }; export const FormRenderer = ({ node, isSelected, isDragging, externalData }: NodeRendererProps) => { const formData = externalData as FormData | undefined; const formColor = "#f59e0b"; return (

📝 {formData?.title || "Form"}

{formData?.fields?.length || 0} fields
{formData?.fields?.slice(0, 3).map((field, _index) => (
• {field.label} ({field.type}) {field.required && "*"}
))} {formData && formData.fields.length > 3 &&
... and {formData.fields.length - 3} more
}
); }; export const FormInspectorRenderer = ({ externalData, onUpdateExternalData }: InspectorRenderProps) => { const formData = externalData as FormData | undefined; const [editedData, setEditedData] = React.useState({ id: formData?.id || "", title: formData?.title || "New Form", fields: formData?.fields || [], }); const addField = () => { const newField = { id: `field-${Date.now()}`, type: "text" as const, label: "New Field", required: false, }; setEditedData((prev) => ({ ...prev, fields: [...prev.fields, newField], })); }; const updateField = (fieldId: string, updates: Partial) => { setEditedData((prev) => ({ ...prev, fields: prev.fields.map((field) => (field.id === fieldId ? { ...field, ...updates } : field)), })); }; const removeField = (fieldId: string) => { setEditedData((prev) => ({ ...prev, fields: prev.fields.filter((field) => field.id !== fieldId), })); }; const handleSave = async () => { if (onUpdateExternalData) { await onUpdateExternalData(editedData); } }; return (

Form Builder

setEditedData({ ...editedData, title: e.target.value })} style={{ width: "100%", padding: "4px 8px" }} />
{editedData.fields.map((field) => (
updateField(field.id, { label: e.target.value })} style={{ flex: 1, padding: "2px 4px", fontSize: "11px" }} placeholder="Field Label" />
{field.type === "select" && (
updateField(field.id, { options: e.target.value .split(",") .map((o) => o.trim()) .filter(Boolean), }) } style={{ width: "100%", padding: "2px 4px", fontSize: "11px" }} placeholder="Options (comma separated)" />
)}
))}
); }; export const FormNodeDefinition: NodeDefinition = { type: "form-builder", displayName: "Form Builder", description: "Interactive form builder with various field types", category: "UI", defaultData: { title: "Form Builder", }, defaultSize: { width: 250, height: 120 }, ports: [ { id: "form-config", type: "input", label: "Config", position: "left", }, { id: "form-data", type: "output", label: "Form Data", position: "right", }, { id: "validation", type: "output", label: "Validation", position: "bottom", }, ], renderNode: FormRenderer, renderInspector: FormInspectorRenderer, loadExternalData: async (ref: ExternalDataReference) => { await new Promise((resolve) => setTimeout(resolve, 250)); return { id: ref.id, title: "Contact Form", fields: [ { id: "name", type: "text", label: "Full Name", required: true }, { id: "email", type: "email", label: "Email Address", required: true }, { id: "message", type: "textarea", label: "Message", required: false }, ], }; }, updateExternalData: async (_ref: ExternalDataReference, data: unknown) => { await new Promise((resolve) => setTimeout(resolve, 250)); console.log("Updated form data:", data); }, };