@wordpress-gcb/fields 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conditional-logic.js +83 -0
- package/{src → dist}/control-context.js +3 -2
- package/{src → dist}/controls/MediaCapabilityGate.js +12 -8
- package/dist/controls/MediaPicker.js +149 -0
- package/dist/controls/MediaTriggerBadges.js +35 -0
- package/{src → dist}/controls/PopoverOrModal.js +49 -43
- package/dist/controls/SortableItem.js +126 -0
- package/dist/controls/button-group.js +46 -0
- package/dist/controls/checkbox-group.js +65 -0
- package/dist/controls/checkbox.js +15 -0
- package/dist/controls/code.js +24 -0
- package/dist/controls/color.js +241 -0
- package/dist/controls/date.js +55 -0
- package/dist/controls/datetime.js +61 -0
- package/dist/controls/email.js +17 -0
- package/dist/controls/file.js +163 -0
- package/dist/controls/gallery.js +371 -0
- package/dist/controls/google-map.js +143 -0
- package/dist/controls/heading-level.js +93 -0
- package/dist/controls/icon.js +292 -0
- package/dist/controls/image.js +360 -0
- package/dist/controls/index.js +88 -0
- package/dist/controls/message.js +86 -0
- package/dist/controls/number.js +19 -0
- package/dist/controls/oembed.js +42 -0
- package/{src → dist}/controls/page-link.js +1 -2
- package/dist/controls/post-object.js +913 -0
- package/dist/controls/radio.js +19 -0
- package/dist/controls/range.js +108 -0
- package/{src → dist}/controls/relationship.js +12 -7
- package/dist/controls/repeater.js +277 -0
- package/dist/controls/richtext.js +494 -0
- package/dist/controls/select.js +144 -0
- package/dist/controls/size.js +59 -0
- package/dist/controls/spacing.js +141 -0
- package/dist/controls/taxonomy.js +569 -0
- package/dist/controls/text.js +16 -0
- package/dist/controls/textarea.js +17 -0
- package/dist/controls/toggle-group.js +28 -0
- package/dist/controls/toggle.js +15 -0
- package/dist/controls/url.js +235 -0
- package/dist/controls/user.js +383 -0
- package/{src → dist}/controls/wysiwyg.js +1 -1
- package/{src → dist}/hooks/useTokens.js +25 -21
- package/{src → dist}/index.js +2 -8
- package/dist/inspector.js +163 -0
- package/{src → dist}/provider.js +18 -17
- package/dist/utils/map-utils.js +54 -0
- package/dist/utils/token-helper.js +396 -0
- package/{src → dist}/validation-context.js +4 -4
- package/package.json +20 -13
- package/src/conditional-logic.js +0 -77
- package/src/controls/MediaPicker.js +0 -139
- package/src/controls/MediaTriggerBadges.js +0 -31
- package/src/controls/SortableItem.js +0 -110
- package/src/controls/button-group.js +0 -49
- package/src/controls/checkbox-group.js +0 -55
- package/src/controls/checkbox.js +0 -13
- package/src/controls/code.js +0 -21
- package/src/controls/color.js +0 -235
- package/src/controls/date.js +0 -37
- package/src/controls/datetime.js +0 -54
- package/src/controls/email.js +0 -15
- package/src/controls/file.js +0 -134
- package/src/controls/gallery.js +0 -338
- package/src/controls/google-map.js +0 -117
- package/src/controls/heading-level.js +0 -99
- package/src/controls/icon.js +0 -301
- package/src/controls/image.js +0 -334
- package/src/controls/index.js +0 -95
- package/src/controls/message.js +0 -56
- package/src/controls/number.js +0 -17
- package/src/controls/oembed.js +0 -32
- package/src/controls/post-object.js +0 -788
- package/src/controls/radio.js +0 -18
- package/src/controls/range.js +0 -110
- package/src/controls/repeater.js +0 -290
- package/src/controls/richtext.js +0 -505
- package/src/controls/select.js +0 -141
- package/src/controls/size.js +0 -49
- package/src/controls/spacing.js +0 -141
- package/src/controls/taxonomy.js +0 -488
- package/src/controls/text.js +0 -14
- package/src/controls/textarea.js +0 -15
- package/src/controls/toggle-group.js +0 -34
- package/src/controls/toggle.js +0 -13
- package/src/controls/url.js +0 -164
- package/src/controls/user.js +0 -343
- package/src/inspector.js +0 -174
- package/src/utils/map-utils.js +0 -51
- package/src/utils/token-helper.js +0 -243
package/src/controls/radio.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { RadioControl } from '@wordpress/components';
|
|
2
|
-
|
|
3
|
-
export default function RadioField({ control, value, onChange }) {
|
|
4
|
-
const options = (control.options || []).map((o) => ({
|
|
5
|
-
label: o.label,
|
|
6
|
-
value: String(o.value),
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
<RadioControl
|
|
11
|
-
label={control.label}
|
|
12
|
-
help={control.helpText}
|
|
13
|
-
selected={value != null ? String(value) : ''}
|
|
14
|
-
options={options}
|
|
15
|
-
onChange={onChange}
|
|
16
|
-
/>
|
|
17
|
-
);
|
|
18
|
-
}
|
package/src/controls/range.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RangeField — ported verbatim from the original.
|
|
3
|
-
* Supports raw numeric ranges, legacy `map` configs, and `tokenGroup` binding.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { __ } from '@wordpress/i18n';
|
|
7
|
-
import { RangeControl, Spinner } from '@wordpress/components';
|
|
8
|
-
import { parseMap, getTokenFromKey, getKeyFromToken, getMapKeys, mapToRangeMarks } from '../utils/map-utils';
|
|
9
|
-
import { useTokens, getTokensByGroup, generateMapFromTokens } from '../hooks/useTokens';
|
|
10
|
-
|
|
11
|
-
function RangeFieldImpl({
|
|
12
|
-
label,
|
|
13
|
-
value,
|
|
14
|
-
onChange,
|
|
15
|
-
min = 0,
|
|
16
|
-
max = 100,
|
|
17
|
-
step = 1,
|
|
18
|
-
help,
|
|
19
|
-
allowReset = true,
|
|
20
|
-
resetFallbackValue,
|
|
21
|
-
className = '',
|
|
22
|
-
map,
|
|
23
|
-
tokenGroup,
|
|
24
|
-
defaultOptionKey,
|
|
25
|
-
}) {
|
|
26
|
-
const { tokens, loading } = useTokens();
|
|
27
|
-
|
|
28
|
-
let normalizedMap = null;
|
|
29
|
-
if (tokenGroup && tokens) {
|
|
30
|
-
const groupTokens = getTokensByGroup(tokens, tokenGroup);
|
|
31
|
-
if (groupTokens) {
|
|
32
|
-
const generatedMap = generateMapFromTokens(groupTokens);
|
|
33
|
-
normalizedMap = generatedMap ? parseMap(generatedMap) : null;
|
|
34
|
-
}
|
|
35
|
-
} else if (map) {
|
|
36
|
-
normalizedMap = parseMap(map);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (tokenGroup && loading) {
|
|
40
|
-
return (
|
|
41
|
-
<div style={{ padding: '12px 0' }}>
|
|
42
|
-
<div style={{ fontWeight: 500, marginBottom: '8px' }}>{label}</div>
|
|
43
|
-
<Spinner />
|
|
44
|
-
</div>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const allowedKeys = normalizedMap ? getMapKeys(normalizedMap) : null;
|
|
49
|
-
const marks = normalizedMap ? mapToRangeMarks(normalizedMap) : null;
|
|
50
|
-
|
|
51
|
-
if ((value === undefined || value === null || value === '') && defaultOptionKey && normalizedMap) {
|
|
52
|
-
const defaultToken = getTokenFromKey(normalizedMap, defaultOptionKey);
|
|
53
|
-
if (defaultToken && onChange) onChange(defaultToken);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let displayValue;
|
|
57
|
-
if (normalizedMap && value) {
|
|
58
|
-
displayValue = Number(getKeyFromToken(normalizedMap, value)) || value;
|
|
59
|
-
} else if (typeof value === 'string' && !isNaN(value)) {
|
|
60
|
-
displayValue = Number(value);
|
|
61
|
-
} else {
|
|
62
|
-
displayValue = value;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const handleChange = (newSliderValue) => {
|
|
66
|
-
let finalValue = normalizedMap
|
|
67
|
-
? getTokenFromKey(normalizedMap, String(newSliderValue))
|
|
68
|
-
: newSliderValue;
|
|
69
|
-
if (onChange) onChange(finalValue);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const effectiveStep = normalizedMap && allowedKeys && allowedKeys.length > 1 ? 1 : step;
|
|
73
|
-
const effectiveMin = normalizedMap && allowedKeys?.length ? Math.min(...allowedKeys) : min;
|
|
74
|
-
const effectiveMax = normalizedMap && allowedKeys?.length ? Math.max(...allowedKeys) : max;
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<RangeControl
|
|
78
|
-
label={label}
|
|
79
|
-
value={displayValue}
|
|
80
|
-
onChange={handleChange}
|
|
81
|
-
min={effectiveMin}
|
|
82
|
-
max={effectiveMax}
|
|
83
|
-
step={effectiveStep}
|
|
84
|
-
marks={marks}
|
|
85
|
-
help={help}
|
|
86
|
-
allowReset={allowReset}
|
|
87
|
-
resetFallbackValue={resetFallbackValue}
|
|
88
|
-
className={className}
|
|
89
|
-
__nextHasNoMarginBottom
|
|
90
|
-
__next40pxDefaultSize
|
|
91
|
-
/>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export default function RangeField({ control, value, onChange }) {
|
|
96
|
-
return (
|
|
97
|
-
<RangeFieldImpl
|
|
98
|
-
label={control.label}
|
|
99
|
-
value={value}
|
|
100
|
-
onChange={onChange}
|
|
101
|
-
min={control.min ?? 0}
|
|
102
|
-
max={control.max ?? 100}
|
|
103
|
-
step={control.step ?? 1}
|
|
104
|
-
help={control.helpText}
|
|
105
|
-
map={control.map}
|
|
106
|
-
tokenGroup={control.tokenGroup}
|
|
107
|
-
defaultOptionKey={control.defaultOptionKey ?? control.default}
|
|
108
|
-
/>
|
|
109
|
-
);
|
|
110
|
-
}
|
package/src/controls/repeater.js
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Repeater Inspector control — multi-row form-of-forms.
|
|
3
|
-
*
|
|
4
|
-
* Distinct from the `gcb/repeater` block (which is an InnerBlocks-based
|
|
5
|
-
* canvas surface). This control lives in the Inspector / post-fields /
|
|
6
|
-
* options page, and stores an ARRAY OF OBJECTS — each object a "row"
|
|
7
|
-
* shaped by the `fields` sub-config.
|
|
8
|
-
*
|
|
9
|
-
* {
|
|
10
|
-
* attributeKey: 'social_links',
|
|
11
|
-
* type: 'repeater',
|
|
12
|
-
* label: 'Social links',
|
|
13
|
-
* fields: [
|
|
14
|
-
* { attributeKey: 'label', type: 'text', label: 'Label' },
|
|
15
|
-
* { attributeKey: 'url', type: 'url', label: 'URL' },
|
|
16
|
-
* ],
|
|
17
|
-
* min: 0,
|
|
18
|
-
* max: null, // null = unlimited
|
|
19
|
-
* addButtonLabel: 'Add link',
|
|
20
|
-
* collapsedTitle: 'label', // which sub-field to show in row header
|
|
21
|
-
* }
|
|
22
|
-
*
|
|
23
|
-
* Stored:
|
|
24
|
-
* [
|
|
25
|
-
* { _id: 'r1', label: 'GitHub', url: 'https://...' },
|
|
26
|
-
* { _id: 'r2', label: 'X', url: 'https://...' },
|
|
27
|
-
* ]
|
|
28
|
-
*
|
|
29
|
-
* Each row carries a stable `_id` so React keys + dnd-kit don't lose
|
|
30
|
-
* track during reorders. _id is generated on add and persisted with
|
|
31
|
-
* the row.
|
|
32
|
-
*
|
|
33
|
-
* Sub-fields can be any registered control type — the row body uses
|
|
34
|
-
* the same `controlComponents` registry as the top-level Inspector,
|
|
35
|
-
* so adding a control type automatically makes it usable inside a
|
|
36
|
-
* repeater.
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
import { __ } from '@wordpress/i18n';
|
|
40
|
-
import { useEffect, useState } from '@wordpress/element';
|
|
41
|
-
import { Button } from '@wordpress/components';
|
|
42
|
-
import {
|
|
43
|
-
DndContext,
|
|
44
|
-
closestCenter,
|
|
45
|
-
PointerSensor,
|
|
46
|
-
useSensor,
|
|
47
|
-
useSensors,
|
|
48
|
-
} from '@dnd-kit/core';
|
|
49
|
-
import {
|
|
50
|
-
SortableContext,
|
|
51
|
-
verticalListSortingStrategy,
|
|
52
|
-
useSortable,
|
|
53
|
-
arrayMove,
|
|
54
|
-
} from '@dnd-kit/sortable';
|
|
55
|
-
import { CSS } from '@dnd-kit/utilities';
|
|
56
|
-
import { controlComponents } from './index';
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* cheap-enough unique id. Crypto.randomUUID would be better but is
|
|
60
|
-
* unsupported in some Playground / older Safari builds we still want to
|
|
61
|
-
* cover. Collision risk on a single repeater is negligible.
|
|
62
|
-
*/
|
|
63
|
-
function newRowId() {
|
|
64
|
-
return 'r' + Math.random().toString(36).slice(2, 10);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Read `control.collapsedTitle` (a sub-field attributeKey) and pull the
|
|
69
|
-
* matching value off the row. Falls back to the index-based "Item N"
|
|
70
|
-
* label so empty rows are still identifiable.
|
|
71
|
-
*/
|
|
72
|
-
function rowTitle(row, control, index) {
|
|
73
|
-
const key = control.collapsedTitle;
|
|
74
|
-
if (key && typeof row?.[key] === 'string' && row[key].trim() !== '') {
|
|
75
|
-
return row[key];
|
|
76
|
-
}
|
|
77
|
-
return `${__('Item', 'gcblite')} ${index + 1}`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function SortableRow({
|
|
81
|
-
row,
|
|
82
|
-
index,
|
|
83
|
-
control,
|
|
84
|
-
isOpen,
|
|
85
|
-
onToggle,
|
|
86
|
-
onUpdate,
|
|
87
|
-
onRemove,
|
|
88
|
-
attributes: parentAttrs,
|
|
89
|
-
}) {
|
|
90
|
-
const {
|
|
91
|
-
attributes,
|
|
92
|
-
listeners,
|
|
93
|
-
setNodeRef,
|
|
94
|
-
transform,
|
|
95
|
-
transition,
|
|
96
|
-
isDragging,
|
|
97
|
-
} = useSortable({ id: row._id });
|
|
98
|
-
|
|
99
|
-
const style = {
|
|
100
|
-
transform: CSS.Transform.toString(transform),
|
|
101
|
-
transition,
|
|
102
|
-
opacity: isDragging ? 0.5 : 1,
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<div ref={setNodeRef} style={style} className="gcb-repeater-row">
|
|
107
|
-
<div className="gcb-repeater-row__header">
|
|
108
|
-
<button
|
|
109
|
-
type="button"
|
|
110
|
-
{...attributes}
|
|
111
|
-
{...listeners}
|
|
112
|
-
className="gcb-repeater-row__handle"
|
|
113
|
-
aria-label={__('Drag to reorder', 'gcblite')}
|
|
114
|
-
onClick={(e) => e.preventDefault()}
|
|
115
|
-
>
|
|
116
|
-
<svg viewBox="0 0 20 20" width="12" height="12">
|
|
117
|
-
<path d="M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z" />
|
|
118
|
-
</svg>
|
|
119
|
-
</button>
|
|
120
|
-
<button
|
|
121
|
-
type="button"
|
|
122
|
-
className="gcb-repeater-row__title"
|
|
123
|
-
onClick={onToggle}
|
|
124
|
-
aria-expanded={isOpen}
|
|
125
|
-
>
|
|
126
|
-
<span className="gcb-repeater-row__caret" aria-hidden>
|
|
127
|
-
{isOpen ? '▾' : '▸'}
|
|
128
|
-
</span>
|
|
129
|
-
<span className="gcb-repeater-row__title-text">
|
|
130
|
-
{rowTitle(row, control, index)}
|
|
131
|
-
</span>
|
|
132
|
-
</button>
|
|
133
|
-
<button
|
|
134
|
-
type="button"
|
|
135
|
-
className="gcb-repeater-row__remove"
|
|
136
|
-
onClick={onRemove}
|
|
137
|
-
aria-label={__('Remove row', 'gcblite')}
|
|
138
|
-
>
|
|
139
|
-
<svg viewBox="0 0 24 24" width="16" height="16">
|
|
140
|
-
<path d="M12 13.06l3.712 3.713 1.061-1.06L13.061 12l3.712-3.712-1.06-1.06L12 10.938 8.288 7.227l-1.061 1.06L10.939 12l-3.712 3.712 1.06 1.061L12 13.061z" />
|
|
141
|
-
</svg>
|
|
142
|
-
</button>
|
|
143
|
-
</div>
|
|
144
|
-
|
|
145
|
-
{isOpen && (
|
|
146
|
-
<div className="gcb-repeater-row__body">
|
|
147
|
-
{(control.fields || []).map((sub) => {
|
|
148
|
-
const SubComponent = controlComponents[sub.type];
|
|
149
|
-
if (!SubComponent) {
|
|
150
|
-
return (
|
|
151
|
-
<div
|
|
152
|
-
key={sub.attributeKey}
|
|
153
|
-
style={{ padding: 8, background: '#fff3cd', border: '1px solid #ffeeba', marginBottom: 8 }}
|
|
154
|
-
>
|
|
155
|
-
<strong>{sub.label}</strong>:{' '}
|
|
156
|
-
{__('unknown control type', 'gcblite')}{' '}
|
|
157
|
-
<code>{sub.type}</code>
|
|
158
|
-
</div>
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
return (
|
|
162
|
-
<SubComponent
|
|
163
|
-
key={sub.attributeKey}
|
|
164
|
-
control={sub}
|
|
165
|
-
value={row?.[sub.attributeKey]}
|
|
166
|
-
onChange={(next) =>
|
|
167
|
-
onUpdate({ ...row, [sub.attributeKey]: next })
|
|
168
|
-
}
|
|
169
|
-
attributes={parentAttrs}
|
|
170
|
-
/>
|
|
171
|
-
);
|
|
172
|
-
})}
|
|
173
|
-
</div>
|
|
174
|
-
)}
|
|
175
|
-
</div>
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export default function RepeaterField({ control, value, onChange, attributes }) {
|
|
180
|
-
const rows = Array.isArray(value) ? value : [];
|
|
181
|
-
const [openId, setOpenId] = useState(null);
|
|
182
|
-
|
|
183
|
-
const min = typeof control.min === 'number' ? control.min : 0;
|
|
184
|
-
const max =
|
|
185
|
-
typeof control.max === 'number' && control.max > 0 ? control.max : null;
|
|
186
|
-
const canAdd = max === null || rows.length < max;
|
|
187
|
-
const canRemove = (i) => rows.length > min;
|
|
188
|
-
|
|
189
|
-
const sensors = useSensors(
|
|
190
|
-
useSensor(PointerSensor, { activationConstraint: { distance: 6 } })
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
const handleDragEnd = (event) => {
|
|
194
|
-
const { active, over } = event;
|
|
195
|
-
if (!over || active.id === over.id) return;
|
|
196
|
-
const oldIndex = rows.findIndex((r) => r._id === active.id);
|
|
197
|
-
const newIndex = rows.findIndex((r) => r._id === over.id);
|
|
198
|
-
if (oldIndex < 0 || newIndex < 0) return;
|
|
199
|
-
onChange(arrayMove(rows, oldIndex, newIndex));
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const addRow = () => {
|
|
203
|
-
const fresh = { _id: newRowId() };
|
|
204
|
-
(control.fields || []).forEach((sub) => {
|
|
205
|
-
if (Object.prototype.hasOwnProperty.call(sub, 'default')) {
|
|
206
|
-
fresh[sub.attributeKey] = sub.default;
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
const next = [...rows, fresh];
|
|
210
|
-
onChange(next);
|
|
211
|
-
setOpenId(fresh._id);
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const removeRow = (rowId) => {
|
|
215
|
-
onChange(rows.filter((r) => r._id !== rowId));
|
|
216
|
-
if (openId === rowId) setOpenId(null);
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
const updateRow = (rowId, nextRow) => {
|
|
220
|
-
onChange(rows.map((r) => (r._id === rowId ? nextRow : r)));
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
// Lazy backfill: rows authored before this control existed (or
|
|
224
|
-
// imported from an export) may lack a stable _id. Patch in an
|
|
225
|
-
// effect (not during render) so reorders work without warning.
|
|
226
|
-
useEffect(() => {
|
|
227
|
-
if (rows.some((r) => !r || !r._id)) {
|
|
228
|
-
onChange(rows.map((r) => (r && r._id ? r : { ...r, _id: newRowId() })));
|
|
229
|
-
}
|
|
230
|
-
}, [rows, onChange]);
|
|
231
|
-
const normalizedRows = rows.map((r) =>
|
|
232
|
-
r && r._id ? r : { ...r, _id: 'pending-' + Math.random() }
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
return (
|
|
236
|
-
<div className="components-base-control gcb-repeater-control">
|
|
237
|
-
<div className="components-base-control__field">
|
|
238
|
-
{control.label && (
|
|
239
|
-
<label className="components-base-control__label">
|
|
240
|
-
{control.label}
|
|
241
|
-
</label>
|
|
242
|
-
)}
|
|
243
|
-
|
|
244
|
-
<DndContext
|
|
245
|
-
sensors={sensors}
|
|
246
|
-
collisionDetection={closestCenter}
|
|
247
|
-
onDragEnd={handleDragEnd}
|
|
248
|
-
>
|
|
249
|
-
<SortableContext
|
|
250
|
-
items={normalizedRows.map((r) => r._id)}
|
|
251
|
-
strategy={verticalListSortingStrategy}
|
|
252
|
-
>
|
|
253
|
-
<div className="gcb-repeater-rows">
|
|
254
|
-
{normalizedRows.map((row, index) => (
|
|
255
|
-
<SortableRow
|
|
256
|
-
key={row._id}
|
|
257
|
-
row={row}
|
|
258
|
-
index={index}
|
|
259
|
-
control={control}
|
|
260
|
-
isOpen={openId === row._id}
|
|
261
|
-
onToggle={() =>
|
|
262
|
-
setOpenId(openId === row._id ? null : row._id)
|
|
263
|
-
}
|
|
264
|
-
onUpdate={(next) => updateRow(row._id, next)}
|
|
265
|
-
onRemove={() =>
|
|
266
|
-
canRemove(index) ? removeRow(row._id) : null
|
|
267
|
-
}
|
|
268
|
-
attributes={attributes}
|
|
269
|
-
/>
|
|
270
|
-
))}
|
|
271
|
-
</div>
|
|
272
|
-
</SortableContext>
|
|
273
|
-
</DndContext>
|
|
274
|
-
|
|
275
|
-
{canAdd && (
|
|
276
|
-
<Button
|
|
277
|
-
variant="secondary"
|
|
278
|
-
onClick={addRow}
|
|
279
|
-
className="gcb-repeater__add"
|
|
280
|
-
>
|
|
281
|
-
{control.addButtonLabel || __('Add item', 'gcblite')}
|
|
282
|
-
</Button>
|
|
283
|
-
)}
|
|
284
|
-
</div>
|
|
285
|
-
{control.helpText && (
|
|
286
|
-
<p className="components-base-control__help">{control.helpText}</p>
|
|
287
|
-
)}
|
|
288
|
-
</div>
|
|
289
|
-
);
|
|
290
|
-
}
|