/** * The array field renderer. Each item is a nested {@link FieldNode}; edits map * the list to a new array (add appends a seeded item, remove filters by index). * The whole list re-renders as one controlled value, so index keys are correct. */ import type { ReactNode } from 'react'; import { CloseIcon } from '../components/icons'; import { Button } from '../components/primitives'; import { classifySchema } from './classify'; import { defaultValueForSchema } from './default-value'; import { FieldGroup } from './field-group'; import { FieldNode } from './field-node'; import { useKeyedCallbacks } from './keyed-callbacks'; import type { JsonSchema, SchemaFormErrors } from './types'; export function ArrayField({ heading, description, error, items, root, value, onChange, path, errors, idPrefix, }: { heading: string; description: string | undefined; error: string | undefined; items: JsonSchema; root: JsonSchema; value: unknown; onChange: (value: unknown) => void; path: string; errors: SchemaFormErrors | undefined; idPrefix: string; }): ReactNode { const list = Array.isArray(value) ? (value as unknown[]) : []; const addItem = (): void => { onChange([...list, newItemValue(items, root)]); }; const removeItem = (index: number): void => { onChange(list.filter((_, position) => position !== index)); }; const setItem = (index: number, next: unknown): void => { onChange(list.map((item, position) => (position === index ? next : item))); }; const changeFor = useKeyedCallbacks(setItem, list.keys()); return ( {list.length === 0 ? No items : null} {list.map((item, index) => (
{/* The item takes the row's spare width; the Remove button keeps its own. */}
))}
); } function newItemValue(schema: JsonSchema, root: JsonSchema): unknown { const seeded = defaultValueForSchema(schema, root); if (seeded !== undefined) return seeded; const classified = classifySchema(schema, root); switch (classified.model.kind) { case 'string': return ''; case 'boolean': return false; default: return undefined; } }