import { useRef, useState } from 'react' import type { JSONSchema } from '../api' import { Icon } from '../ui/Icon' import { useForm } from './context' import { JsonArea, Widget } from './Form' import { defaultFor, resolveSchema, variantsOf } from './resolve' let uid = 0 const newKey = () => `k${++uid}` const plural = (n: number, w: string) => `${n} ${w}${n === 1 ? '' : 's'}` /** Short label for a collapsed object card: x-label, else the first useful string. */ function summarise(root: JSONSchema, itemSchema: JSONSchema, item: unknown, index: number): string { if (typeof item === 'string' || typeof item === 'number') return String(item) if (item && typeof item === 'object') { const o = item as Record const label = itemSchema['x-label'] ?? itemSchema['x-graph']?.label const prefer = [label, 'title', 'name', 'label', 'text', 'heading', 'href', 'id'].filter(Boolean) as string[] for (const k of prefer) if (typeof o[k] === 'string' && o[k]) return String(o[k]).slice(0, 48) for (const v of Object.values(o)) if (typeof v === 'string' && v) return v.slice(0, 48) } return `Item ${index + 1}` } export function ArrayField({ schema: s, value, onChange, path }: { schema: JSONSchema; value: unknown; onChange(v: unknown): void; path: string }) { const { root, errors } = useForm() const items = Array.isArray(value) ? value : [] const it = resolveSchema(root, Array.isArray(s.items) ? undefined : s.items) const itemSchema = it.schema // Tuples / untyped arrays have no per-item form: raw JSON is the honest editor. if (Array.isArray(s.items) || s.prefixItems) return // uniqueItems + enum items: a checkbox set (the natural UI for "pick several"). if (Array.isArray(itemSchema.enum) && s.uniqueItems) { return (
{itemSchema.enum.map((o: unknown) => ( ))}
) } const t = Array.isArray(itemSchema.type) ? itemSchema.type[0] : itemSchema.type const variants = variantsOf(itemSchema) const compound = t === 'object' || t === 'array' || (!t && !!itemSchema.properties) || !!variants?.some((v) => resolveSchema(root, v).schema.type === 'object') return } function ArrayList({ schema: s, itemSchema, items, onChange, path, compound }: { schema: JSONSchema; itemSchema: JSONSchema; items: unknown[]; onChange(v: unknown): void; path: string; compound: boolean; err?: string }) { const { root } = useForm() // Stable keys survive reorders, so open/closed state and focus stay with the item. const keys = useRef([]) while (keys.current.length < items.length) keys.current.push(newKey()) if (keys.current.length > items.length) keys.current.length = items.length const [open, setOpen] = useState>({}) const [dragFrom, setDragFrom] = useState(null) const [dragOver, setDragOver] = useState(null) const min = s.minItems ?? 0 const max = s.maxItems ?? Infinity const isOpen = (k: string) => open[k] ?? items.length <= 2 const commit = (next: unknown[]) => onChange(next) const move = (from: number, to: number) => { if (to < 0 || to >= items.length || from === to) return const next = [...items] next.splice(to, 0, ...next.splice(from, 1)) keys.current.splice(to, 0, ...keys.current.splice(from, 1)) commit(next) } const remove = (i: number) => { keys.current.splice(i, 1) commit(items.filter((_, j) => j !== i)) } const duplicate = (i: number) => { const next = [...items] next.splice(i + 1, 0, structuredClone(items[i])) keys.current.splice(i + 1, 0, newKey()) commit(next) } const add = () => { if (items.length >= max) return const k = newKey() keys.current.push(k) setOpen((o) => ({ ...o, [k]: true })) commit([...items, defaultFor(root, itemSchema)]) } return (
    {items.map((item, i) => { const k = keys.current[i] const expanded = !compound || isOpen(k) return (
  1. { if (dragFrom === null) return e.preventDefault() setDragOver(i) }} onDrop={(e) => { e.preventDefault() if (dragFrom !== null) move(dragFrom, i) setDragFrom(null) setDragOver(null) }} >
    {compound ? ( ) : (
    commit(items.map((x, j) => (j === i ? (v === undefined ? defaultFor(root, itemSchema) : v) : x)))} path={`${path}.${i}`} required />
    )} {compound && ( )}
    {compound && expanded && (
    commit(items.map((x, j) => (j === i ? (v ?? {}) : x)))} path={`${path}.${i}`} required bare />
    )}
  2. ) })}
{plural(items.length, 'item')} {s.minItems ? ` · min ${s.minItems}` : ''} {s.maxItems ? ` · max ${s.maxItems}` : ''}
) }