CE-WP-0007 T10-T12: field add/edit dialog, pencil edit, integration tests
- FieldDefinitionForm shared component (label + text/textarea/date) - Add field opens inline form; per-field pencil edit with stable ids - forms-field-edit.dom.test.tsx covers add, edit, and link-to-new-field - Workplan T10-T12 and README marked done
This commit is contained in:
parent
2fd085b65e
commit
48a53df9fc
6 changed files with 540 additions and 54 deletions
|
|
@ -34,7 +34,7 @@ import {
|
|||
useScrollToAnnotation,
|
||||
} from "@work/index";
|
||||
|
||||
import { FormRenderer } from "@binder/FormRenderer";
|
||||
import { FormRenderer, type FieldDefinitionPatch } from "@binder/FormRenderer";
|
||||
|
||||
import { DEMO_SCHEMA } from "./demo-schema";
|
||||
import { HighlightRectBridge } from "./HighlightRectBridge";
|
||||
|
|
@ -64,24 +64,76 @@ export function FormsApp() {
|
|||
[schema],
|
||||
);
|
||||
|
||||
const addField = useCallback(() => {
|
||||
setSchema((prev) => {
|
||||
const n = prev.fields.length + 1;
|
||||
const field: FormFieldSchema = {
|
||||
type: "text",
|
||||
id: `field_${n}`,
|
||||
label: `New field ${n}`,
|
||||
};
|
||||
return { ...prev, fields: [...prev.fields, field] };
|
||||
});
|
||||
const [showAddFieldForm, setShowAddFieldForm] = useState(false);
|
||||
const [editingFieldId, setEditingFieldId] = useState<string | null>(null);
|
||||
|
||||
const nextFieldId = useCallback((fields: readonly FormFieldSchema[]): string => {
|
||||
let max = 0;
|
||||
for (const f of fields) {
|
||||
const m = /^field_(\d+)$/.exec(f.id);
|
||||
if (m) max = Math.max(max, Number(m[1]));
|
||||
}
|
||||
return `field_${max + 1}`;
|
||||
}, []);
|
||||
|
||||
const handleConfirmAddField = useCallback(
|
||||
(patch: FieldDefinitionPatch) => {
|
||||
setSchema((prev) => {
|
||||
const id = nextFieldId(prev.fields);
|
||||
const n = prev.fields.length + 1;
|
||||
const field: FormFieldSchema = {
|
||||
id,
|
||||
type: patch.type,
|
||||
label: patch.label.length > 0 ? patch.label : `New field ${n}`,
|
||||
};
|
||||
return { ...prev, fields: [...prev.fields, field] };
|
||||
});
|
||||
setShowAddFieldForm(false);
|
||||
},
|
||||
[nextFieldId],
|
||||
);
|
||||
|
||||
const handleSaveFieldEdit = useCallback(
|
||||
(fieldId: string, patch: FieldDefinitionPatch) => {
|
||||
setSchema((prev) => ({
|
||||
...prev,
|
||||
fields: prev.fields.map((f) =>
|
||||
f.id === fieldId
|
||||
? {
|
||||
...f,
|
||||
type: patch.type,
|
||||
label: patch.label.length > 0 ? patch.label : f.label,
|
||||
}
|
||||
: f,
|
||||
),
|
||||
}));
|
||||
setEditingFieldId(null);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
|
||||
<div style={{ display: "flex", flex: 1, minHeight: 0 }}>
|
||||
<CollectionList />
|
||||
<ViewerShell />
|
||||
<FormPane schema={schema} onAddField={addField} />
|
||||
<FormPane
|
||||
schema={schema}
|
||||
showAddFieldForm={showAddFieldForm}
|
||||
editingFieldId={editingFieldId}
|
||||
onRequestAddField={() => {
|
||||
setEditingFieldId(null);
|
||||
setShowAddFieldForm(true);
|
||||
}}
|
||||
onConfirmAddField={handleConfirmAddField}
|
||||
onCancelAddField={() => setShowAddFieldForm(false)}
|
||||
onBeginEditField={(fieldId) => {
|
||||
setShowAddFieldForm(false);
|
||||
setEditingFieldId(fieldId);
|
||||
}}
|
||||
onSaveFieldEdit={handleSaveFieldEdit}
|
||||
onCancelFieldEdit={() => setEditingFieldId(null)}
|
||||
/>
|
||||
</div>
|
||||
<EvidenceStrip fieldLabels={fieldLabels} />
|
||||
<ScrollBridge />
|
||||
|
|
@ -104,10 +156,24 @@ function ScrollBridge() {
|
|||
|
||||
function FormPane({
|
||||
schema,
|
||||
onAddField,
|
||||
showAddFieldForm,
|
||||
editingFieldId,
|
||||
onRequestAddField,
|
||||
onConfirmAddField,
|
||||
onCancelAddField,
|
||||
onBeginEditField,
|
||||
onSaveFieldEdit,
|
||||
onCancelFieldEdit,
|
||||
}: {
|
||||
schema: FormSchema;
|
||||
onAddField: () => void;
|
||||
showAddFieldForm: boolean;
|
||||
editingFieldId: string | null;
|
||||
onRequestAddField: () => void;
|
||||
onConfirmAddField: (patch: FieldDefinitionPatch) => void;
|
||||
onCancelAddField: () => void;
|
||||
onBeginEditField: (fieldId: string) => void;
|
||||
onSaveFieldEdit: (fieldId: string, patch: FieldDefinitionPatch) => void;
|
||||
onCancelFieldEdit: () => void;
|
||||
}) {
|
||||
const { document } = useActiveDocument();
|
||||
const { bindings } = useBinder();
|
||||
|
|
@ -189,23 +255,14 @@ function FormPane({
|
|||
schema={schema}
|
||||
linkCounts={linkCounts}
|
||||
linkHints={linkHints}
|
||||
headerAction={
|
||||
<button
|
||||
type="button"
|
||||
data-testid="add-field-button"
|
||||
onClick={onAddField}
|
||||
style={{
|
||||
fontSize: 11,
|
||||
padding: "4px 10px",
|
||||
border: "1px solid #888",
|
||||
borderRadius: 4,
|
||||
background: "white",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Add field
|
||||
</button>
|
||||
}
|
||||
showAddFieldForm={showAddFieldForm}
|
||||
onRequestAddField={onRequestAddField}
|
||||
onConfirmAddField={onConfirmAddField}
|
||||
onCancelAddField={onCancelAddField}
|
||||
editingFieldId={editingFieldId}
|
||||
onBeginEditField={onBeginEditField}
|
||||
onSaveFieldEdit={onSaveFieldEdit}
|
||||
onCancelFieldEdit={onCancelFieldEdit}
|
||||
/>
|
||||
) : (
|
||||
<EmptyHint />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue