Jake Vanderwerf
2026-09-06 3e4e3db64b366028845cfd610c5b17f26bd062ee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import { useState } from '@wordpress/element';
import { Button, Panel, PanelBody } from '@wordpress/components';
import Fields from '../Fields';
 
const templateLabel = (template, row, fields) => {
    if (!template?.includes('{{')) return template;
    return template.replace(/{{(.*?)}}/g, (_, name) => {
        const val = row[name];
        if (val === undefined) return '';
        return Array.isArray(val) ? val.join(', ') : String(val);
    });
};
 
const emptyRow = (fields) =>
    Object.fromEntries(Object.entries(fields || {}).map(([name, f]) => [name, f.default ?? '']));
 
const RepeaterField = ({ field, value, onChange, meta }) => {
    const rows = Array.isArray(value) ? value : [];
    const subFields = field.fields || {};
    const [openIndex, setOpenIndex] = useState(null);
 
    const updateRow = (index, name, val) => {
        const next = rows.map((row, i) => (i === index ? { ...row, [name]: val } : row));
        onChange(next);
    };
 
    const addRow = () => onChange([...rows, emptyRow(subFields)]);
    const removeRow = (index) => onChange(rows.filter((_, i) => i !== index));
 
    return (
        <div className="jvb-repeater-field" id={field.name}>
            <p className="jvb-field-label">{field.label}</p>
 
            <Panel>
                {rows.map((row, index) => (
                    <PanelBody
                        key={index}
                        title={templateLabel(field.row_label, row, subFields) || `Row ${index + 1}`}
                        initialOpen={openIndex === index}
                        onToggle={() => setOpenIndex(openIndex === index ? null : index)}
                    >
                        {Object.entries(subFields).map(([name, subField]) => (
                            <Fields
                                key={name}
                                field={{ ...subField, name }}
                                value={row[name]}
                                onChange={(val) => updateRow(index, name, val)}
                                meta={ meta }
                            />
                        ))}
                        <Button variant="link" isDestructive onClick={() => removeRow(index)}>
                            Remove
                        </Button>
                    </PanelBody>
                ))}
            </Panel>
 
            <Button variant="secondary" onClick={addRow}>
                {field.add_label || 'Add Row'}
            </Button>
        </div>
    );
};
 
export default RepeaterField;