/** * FormRenderer — renders a FormSchema as a small evidence-backed form. * * Each field registers itself with the rect registry under * `kind="field"` and the field's `id`, so the SVG visual guide (T07) can * draw curves from the active field to its linked evidence card and on * to the source highlight. * * CE-WP-0007-T10/T11: add-field and edit-field flows use FieldDefinitionForm. */ import { useRef, useState, type ChangeEvent, type CSSProperties } from "react"; import type { EvidenceTarget } from "@citation-evidence/engine/shared"; import { FieldDefinitionForm, type FieldType } from "./FieldDefinitionForm"; import { useActiveState, type ActiveState } from "./state/active"; import { useRegisterRect } from "./visual-guide/react-hooks"; function isFieldActive(state: ActiveState, fieldId: string): boolean { return ( state.activeTarget?.targetType === "form-field" && state.activeTarget?.targetId === fieldId ); } export interface FormFieldSchema { readonly type: "text" | "textarea" | "date"; readonly id: string; readonly label: string; } export interface FormSchema { readonly id: string; readonly title: string; readonly fields: readonly FormFieldSchema[]; } export interface FieldDefinitionPatch { readonly label: string; readonly type: FieldType; } export interface FormRendererProps { readonly schema: FormSchema; readonly values?: Readonly>; readonly onValueChange?: (fieldId: string, value: string) => void; readonly linkCounts?: Readonly>; readonly linkHints?: Readonly>; readonly showAddFieldForm?: boolean; readonly onRequestAddField?: () => void; readonly onConfirmAddField?: (patch: FieldDefinitionPatch) => void; readonly onCancelAddField?: () => void; readonly editingFieldId?: string | null; readonly onBeginEditField?: (fieldId: string) => void; readonly onSaveFieldEdit?: (fieldId: string, patch: FieldDefinitionPatch) => void; readonly onCancelFieldEdit?: () => void; } const iconButtonStyle: CSSProperties = { fontSize: 11, padding: "2px 6px", background: "white", border: "1px solid #888", borderRadius: 3, cursor: "pointer", lineHeight: 1, }; function FieldRow({ field, value, linkCount, linkHint, isActive, isEditing, editLabel, editType, onChange, onFocus, onBeginEdit, onChangeEditLabel, onChangeEditType, onSaveEdit, onCancelEdit, }: { field: FormFieldSchema; value: string; linkCount: number; linkHint?: string; isActive: boolean; isEditing: boolean; editLabel: string; editType: FieldType; onChange: (next: string) => void; onFocus: () => void; onBeginEdit: () => void; onChangeEditLabel: (next: string) => void; onChangeEditType: (next: FieldType) => void; onSaveEdit: () => void; onCancelEdit: () => void; }) { const ref = useRef(null); useRegisterRect("field", field.id, ref); if (isEditing) { return (
); } const sharedProps = { id: `field-${field.id}`, value, onFocus, onChange: (e: ChangeEvent) => onChange(e.target.value), style: { width: "100%", boxSizing: "border-box" as const, fontSize: 13, padding: 4 }, }; return (
{field.type === "textarea" ? (