@firecms/core 3.0.0-canary.242 → 3.0.0-canary.244
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/components/ArrayContainer.d.ts +7 -12
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +0 -4
- package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +3 -9
- package/dist/index.es.js +705 -370
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +713 -381
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collections.d.ts +4 -0
- package/package.json +8 -6
- package/src/components/ArrayContainer.tsx +408 -294
- package/src/components/HomePage/DefaultHomePage.tsx +1 -0
- package/src/components/VirtualTable/VirtualTable.tsx +0 -26
- package/src/components/VirtualTable/VirtualTableProps.tsx +0 -5
- package/src/core/EntityEditView.tsx +1 -0
- package/src/core/EntitySidePanel.tsx +1 -13
- package/src/form/components/CustomIdField.tsx +3 -1
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +222 -154
- package/src/types/collections.ts +5 -0
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import {
|
|
3
|
+
closestCenter,
|
|
4
|
+
DndContext,
|
|
5
|
+
DragEndEvent,
|
|
6
|
+
KeyboardSensor,
|
|
7
|
+
PointerSensor,
|
|
8
|
+
useSensor,
|
|
9
|
+
useSensors
|
|
10
|
+
} from "@dnd-kit/core";
|
|
11
|
+
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
|
|
12
|
+
|
|
13
|
+
import { SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
14
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
5
15
|
import { getHashValue } from "../util";
|
|
6
16
|
import {
|
|
7
17
|
AddIcon,
|
|
@@ -20,11 +30,11 @@ import {
|
|
|
20
30
|
} from "@firecms/ui";
|
|
21
31
|
|
|
22
32
|
export type ArrayEntryParams = {
|
|
23
|
-
index: number
|
|
24
|
-
internalId: number
|
|
25
|
-
isDragging: boolean
|
|
26
|
-
storedProps?: object
|
|
27
|
-
storeProps: (props: object) => void
|
|
33
|
+
index: number;
|
|
34
|
+
internalId: number;
|
|
35
|
+
isDragging: boolean;
|
|
36
|
+
storedProps?: object;
|
|
37
|
+
storeProps: (props: object) => void;
|
|
28
38
|
};
|
|
29
39
|
|
|
30
40
|
export type ArrayEntryBuilder = (params: ArrayEntryParams) => React.ReactNode;
|
|
@@ -37,14 +47,11 @@ export interface ArrayContainerProps<T> {
|
|
|
37
47
|
disabled?: boolean;
|
|
38
48
|
size?: "small" | "medium";
|
|
39
49
|
onInternalIdAdded?: (id: number) => void;
|
|
40
|
-
/**
|
|
41
|
-
* @deprecated Use `canAddElements` instead
|
|
42
|
-
*/
|
|
43
50
|
includeAddButton?: boolean;
|
|
44
51
|
canAddElements?: boolean;
|
|
45
52
|
sortable?: boolean;
|
|
46
53
|
newDefaultEntry: T;
|
|
47
|
-
onValueChange: (value: T[]) => void
|
|
54
|
+
onValueChange: (value: T[]) => void;
|
|
48
55
|
className?: string;
|
|
49
56
|
min?: number;
|
|
50
57
|
max?: number;
|
|
@@ -52,17 +59,285 @@ export interface ArrayContainerProps<T> {
|
|
|
52
59
|
|
|
53
60
|
const buildIdsMap = (value: any[]) =>
|
|
54
61
|
value && Array.isArray(value) && value.length > 0
|
|
55
|
-
? value
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
? value
|
|
63
|
+
.map((v, index) => {
|
|
64
|
+
if (!v) return {};
|
|
65
|
+
return {
|
|
66
|
+
[getHashValue(v) + index]: getRandomId()
|
|
67
|
+
};
|
|
68
|
+
})
|
|
69
|
+
.reduce((a, b) => ({ ...a, ...b }), {})
|
|
70
|
+
: {};
|
|
71
|
+
|
|
72
|
+
type SortableItemProps = {
|
|
73
|
+
id: number;
|
|
74
|
+
index: number;
|
|
75
|
+
size?: "small" | "medium";
|
|
76
|
+
disabled: boolean;
|
|
77
|
+
buildEntry: ArrayEntryBuilder;
|
|
78
|
+
remove: (index: number) => void;
|
|
79
|
+
copy: (index: number) => void;
|
|
80
|
+
addInIndex?: (index: number) => void;
|
|
81
|
+
canAddElements?: boolean;
|
|
82
|
+
sortable: boolean;
|
|
83
|
+
storedProps?: object;
|
|
84
|
+
updateItemCustomProps: (internalId: number, props: object) => void;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
function SortableItem({
|
|
88
|
+
id,
|
|
89
|
+
index,
|
|
90
|
+
size,
|
|
91
|
+
disabled,
|
|
92
|
+
buildEntry,
|
|
93
|
+
remove,
|
|
94
|
+
copy,
|
|
95
|
+
addInIndex,
|
|
96
|
+
canAddElements,
|
|
97
|
+
sortable,
|
|
98
|
+
storedProps,
|
|
99
|
+
updateItemCustomProps
|
|
100
|
+
}: SortableItemProps) {
|
|
101
|
+
const {
|
|
102
|
+
attributes,
|
|
103
|
+
listeners,
|
|
104
|
+
setNodeRef,
|
|
105
|
+
transform,
|
|
106
|
+
transition,
|
|
107
|
+
isDragging
|
|
108
|
+
} = sortable
|
|
109
|
+
? useSortable({ id })
|
|
110
|
+
: {
|
|
111
|
+
attributes: {},
|
|
112
|
+
listeners: {},
|
|
113
|
+
setNodeRef: (node: HTMLElement | null) => {
|
|
114
|
+
},
|
|
115
|
+
transform: null,
|
|
116
|
+
transition: undefined,
|
|
117
|
+
isDragging: false
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const style = transform ? {
|
|
121
|
+
transform: CSS.Transform.toString(transform),
|
|
122
|
+
transition
|
|
123
|
+
} : {};
|
|
124
|
+
const dragHandleProps = sortable ? { ...listeners, ...attributes } : {};
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<ArrayContainerItem
|
|
128
|
+
nodeRef={setNodeRef}
|
|
129
|
+
style={style}
|
|
130
|
+
dragHandleProps={dragHandleProps}
|
|
131
|
+
internalId={id}
|
|
132
|
+
index={index}
|
|
133
|
+
size={size}
|
|
134
|
+
disabled={disabled}
|
|
135
|
+
buildEntry={buildEntry}
|
|
136
|
+
remove={remove}
|
|
137
|
+
copy={copy}
|
|
138
|
+
addInIndex={addInIndex}
|
|
139
|
+
canAddElements={canAddElements}
|
|
140
|
+
sortable={sortable}
|
|
141
|
+
isDragging={isDragging}
|
|
142
|
+
storedProps={storedProps}
|
|
143
|
+
updateItemCustomProps={updateItemCustomProps}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
type ArrayContainerItemProps = {
|
|
149
|
+
nodeRef: (node: HTMLElement | null) => void;
|
|
150
|
+
style: React.CSSProperties;
|
|
151
|
+
dragHandleProps: any;
|
|
152
|
+
index: number;
|
|
153
|
+
internalId: number;
|
|
154
|
+
size?: "small" | "medium";
|
|
155
|
+
disabled: boolean;
|
|
156
|
+
buildEntry: ArrayEntryBuilder;
|
|
157
|
+
remove: (index: number) => void;
|
|
158
|
+
copy: (index: number) => void;
|
|
159
|
+
addInIndex?: (index: number) => void;
|
|
160
|
+
canAddElements?: boolean;
|
|
161
|
+
sortable: boolean;
|
|
162
|
+
isDragging: boolean;
|
|
163
|
+
storedProps?: object;
|
|
164
|
+
updateItemCustomProps: (internalId: number, props: object) => void;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export function ArrayContainerItem({
|
|
168
|
+
nodeRef,
|
|
169
|
+
style,
|
|
170
|
+
dragHandleProps,
|
|
171
|
+
index,
|
|
172
|
+
internalId,
|
|
173
|
+
size,
|
|
174
|
+
disabled,
|
|
175
|
+
buildEntry,
|
|
176
|
+
remove,
|
|
177
|
+
copy,
|
|
178
|
+
addInIndex,
|
|
179
|
+
canAddElements,
|
|
180
|
+
sortable,
|
|
181
|
+
isDragging,
|
|
182
|
+
storedProps,
|
|
183
|
+
updateItemCustomProps
|
|
184
|
+
}: ArrayContainerItemProps) {
|
|
185
|
+
return (
|
|
186
|
+
<div
|
|
187
|
+
ref={nodeRef}
|
|
188
|
+
style={style}
|
|
189
|
+
className={`relative ${!isDragging ? "hover\\:bg-surface-accent-50 dark\\:hover\\:bg-surface-800 dark\\:hover\\:bg-opacity-20" : ""} rounded-md opacity-100`}
|
|
190
|
+
>
|
|
191
|
+
<div className="flex items-start">
|
|
192
|
+
<div className="flex-grow w-[calc(100%-48px)] text-text-primary dark:text-text-primary-dark">
|
|
193
|
+
{buildEntry({
|
|
194
|
+
index,
|
|
195
|
+
internalId,
|
|
196
|
+
isDragging,
|
|
197
|
+
storedProps,
|
|
198
|
+
storeProps: (props: object) => updateItemCustomProps(internalId, props)
|
|
199
|
+
})}
|
|
200
|
+
</div>
|
|
201
|
+
<ArrayItemOptions
|
|
202
|
+
dragHandleProps={dragHandleProps}
|
|
203
|
+
direction={size === "small" ? "row" : "column"}
|
|
204
|
+
disabled={disabled}
|
|
205
|
+
remove={remove}
|
|
206
|
+
index={index}
|
|
207
|
+
copy={copy}
|
|
208
|
+
canAddElements={canAddElements}
|
|
209
|
+
sortable={sortable}
|
|
210
|
+
addInIndex={addInIndex}
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function ArrayItemOptions({
|
|
218
|
+
dragHandleProps,
|
|
219
|
+
direction,
|
|
220
|
+
disabled,
|
|
221
|
+
remove,
|
|
222
|
+
index,
|
|
223
|
+
copy,
|
|
224
|
+
canAddElements,
|
|
225
|
+
sortable,
|
|
226
|
+
addInIndex,
|
|
227
|
+
}: {
|
|
228
|
+
dragHandleProps: any;
|
|
229
|
+
direction?: "row" | "column";
|
|
230
|
+
disabled: boolean;
|
|
231
|
+
remove: (index: number) => void;
|
|
232
|
+
index: number;
|
|
233
|
+
copy: (index: number) => void;
|
|
234
|
+
sortable: boolean;
|
|
235
|
+
canAddElements?: boolean;
|
|
236
|
+
addInIndex?: (index: number) => void;
|
|
237
|
+
}) {
|
|
238
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
239
|
+
const iconRef = useRef<HTMLDivElement>(null);
|
|
240
|
+
useOutsideAlerter(iconRef, () => {
|
|
241
|
+
if (menuOpen) setMenuOpen(false);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const showMenu = canAddElements ?? false;
|
|
245
|
+
|
|
246
|
+
const handleIconButtonClick = (e: React.MouseEvent) => {
|
|
247
|
+
if (showMenu) {
|
|
248
|
+
e.stopPropagation();
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
setMenuOpen(o => !o);
|
|
251
|
+
} else if (sortable) {
|
|
252
|
+
// Allow drag to propagate if menu is not shown
|
|
253
|
+
} else {
|
|
254
|
+
e.stopPropagation();
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const title = !disabled && sortable && showMenu ? "Drag to move. Click for options" :
|
|
260
|
+
!disabled && showMenu ? "Click for options" :
|
|
261
|
+
!disabled && sortable ? "Drag to move" : undefined;
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<div ref={iconRef}
|
|
265
|
+
className={`pl-2 pt-1 pb-1 flex ${direction === "row" ? "flex-row-reverse" : "flex-col"} items-center`}>
|
|
266
|
+
<Tooltip
|
|
267
|
+
delayDuration={400}
|
|
268
|
+
open={menuOpen ? false : undefined}
|
|
269
|
+
side={direction === "column" ? "left" : undefined}
|
|
270
|
+
title={title}
|
|
271
|
+
>
|
|
272
|
+
<IconButton
|
|
273
|
+
size="small"
|
|
274
|
+
disabled={disabled || (!showMenu && !sortable)}
|
|
275
|
+
{...(sortable ? dragHandleProps : {})}
|
|
276
|
+
onClick={handleIconButtonClick}
|
|
277
|
+
onFocus={() => {
|
|
278
|
+
if (sortable && menuOpen) setMenuOpen(false);
|
|
279
|
+
}}
|
|
280
|
+
className={cls(
|
|
281
|
+
(disabled || (!sortable && !showMenu)) ? "cursor-inherit" : "",
|
|
282
|
+
sortable && !disabled ? "cursor-grab" : "",
|
|
283
|
+
!sortable && showMenu && !disabled ? "cursor-pointer" : ""
|
|
284
|
+
)}
|
|
285
|
+
>
|
|
286
|
+
<HandleIcon/>
|
|
287
|
+
</IconButton>
|
|
288
|
+
</Tooltip>
|
|
289
|
+
{showMenu && (
|
|
290
|
+
<Menu portalContainer={iconRef.current} open={menuOpen} trigger={<div tabIndex={-1}/>}>
|
|
291
|
+
<MenuItem
|
|
292
|
+
dense
|
|
293
|
+
onClick={(e: React.MouseEvent) => {
|
|
294
|
+
setMenuOpen(false);
|
|
295
|
+
remove(index);
|
|
296
|
+
}}
|
|
297
|
+
>
|
|
298
|
+
<RemoveIcon size={"small"}/>
|
|
299
|
+
Remove
|
|
300
|
+
</MenuItem>
|
|
301
|
+
<MenuItem
|
|
302
|
+
dense
|
|
303
|
+
onClick={() => {
|
|
304
|
+
setMenuOpen(false);
|
|
305
|
+
copy(index);
|
|
306
|
+
}}
|
|
307
|
+
>
|
|
308
|
+
<ContentCopyIcon size={"small"}/>
|
|
309
|
+
Copy
|
|
310
|
+
</MenuItem>
|
|
311
|
+
{addInIndex && (
|
|
312
|
+
<MenuItem
|
|
313
|
+
dense
|
|
314
|
+
onClick={() => {
|
|
315
|
+
setMenuOpen(false);
|
|
316
|
+
addInIndex(index);
|
|
317
|
+
}}
|
|
318
|
+
>
|
|
319
|
+
<KeyboardArrowUpIcon size={"small"}/>
|
|
320
|
+
Add on top
|
|
321
|
+
</MenuItem>
|
|
322
|
+
)}
|
|
323
|
+
{addInIndex && (
|
|
324
|
+
<MenuItem
|
|
325
|
+
dense
|
|
326
|
+
onClick={() => {
|
|
327
|
+
setMenuOpen(false);
|
|
328
|
+
addInIndex(index + 1);
|
|
329
|
+
}}
|
|
330
|
+
>
|
|
331
|
+
<KeyboardArrowDownIcon size={"small"}/>
|
|
332
|
+
Add below
|
|
333
|
+
</MenuItem>
|
|
334
|
+
)}
|
|
335
|
+
</Menu>
|
|
336
|
+
)}
|
|
337
|
+
</div>
|
|
338
|
+
);
|
|
339
|
+
}
|
|
62
340
|
|
|
63
|
-
/**
|
|
64
|
-
* @group Form custom fields
|
|
65
|
-
*/
|
|
66
341
|
export function ArrayContainer<T>({
|
|
67
342
|
droppableId,
|
|
68
343
|
addLabel,
|
|
@@ -80,27 +355,29 @@ export function ArrayContainer<T>({
|
|
|
80
355
|
min = 0,
|
|
81
356
|
max = Infinity
|
|
82
357
|
}: ArrayContainerProps<T>) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
358
|
+
const canAddElements =
|
|
359
|
+
(canAddElementsProp === undefined ? true : canAddElementsProp) && // Default canAddElementsProp to true if undefined
|
|
360
|
+
(deprecatedIncludeAddButton === undefined || deprecatedIncludeAddButton);
|
|
86
361
|
|
|
87
362
|
const hasValue = value && Array.isArray(value) && value.length > 0;
|
|
88
|
-
|
|
89
|
-
// Used to track the ids that have displayed the initial show animation
|
|
90
363
|
const internalIdsRef = useRef<Record<string, number>>(buildIdsMap(value));
|
|
91
|
-
|
|
92
364
|
const [internalIds, setInternalIds] = useState<number[]>(
|
|
93
|
-
hasValue
|
|
94
|
-
? Object.values(internalIdsRef.current)
|
|
95
|
-
: []
|
|
365
|
+
hasValue ? Object.values(internalIdsRef.current) : []
|
|
96
366
|
);
|
|
97
|
-
|
|
98
367
|
const itemCustomPropsRef = useRef<Record<number, object>>({});
|
|
99
368
|
|
|
100
369
|
const updateItemCustomProps = useCallback((internalId: number, customProps: object) => {
|
|
101
370
|
itemCustomPropsRef.current[internalId] = customProps;
|
|
102
371
|
}, []);
|
|
103
372
|
|
|
373
|
+
const pointerSensor = useSensor(PointerSensor, {
|
|
374
|
+
activationConstraint: {
|
|
375
|
+
distance: 5,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
const keyboardSensor = useSensor(KeyboardSensor, {});
|
|
379
|
+
const sensors = useSensors(pointerSensor, keyboardSensor);
|
|
380
|
+
|
|
104
381
|
useEffect(() => {
|
|
105
382
|
if (hasValue && value && value.length !== internalIds.length) {
|
|
106
383
|
const newInternalIds = value.map((v, index) => {
|
|
@@ -122,8 +399,7 @@ export function ArrayContainer<T>({
|
|
|
122
399
|
if (disabled || (value ?? []).length >= max) return;
|
|
123
400
|
const id = getRandomId();
|
|
124
401
|
const newIds: number[] = [...internalIds, id];
|
|
125
|
-
if (onInternalIdAdded)
|
|
126
|
-
onInternalIdAdded(id);
|
|
402
|
+
if (onInternalIdAdded) onInternalIdAdded(id);
|
|
127
403
|
setInternalIds(newIds);
|
|
128
404
|
onValueChange([...(value ?? []), newDefaultEntry]);
|
|
129
405
|
};
|
|
@@ -145,10 +421,8 @@ export function ArrayContainer<T>({
|
|
|
145
421
|
id,
|
|
146
422
|
...internalIds.slice(index + 1)
|
|
147
423
|
];
|
|
148
|
-
if (onInternalIdAdded)
|
|
149
|
-
onInternalIdAdded(id);
|
|
424
|
+
if (onInternalIdAdded) onInternalIdAdded(id);
|
|
150
425
|
setInternalIds(newIds);
|
|
151
|
-
// insert value in index + 1
|
|
152
426
|
onValueChange([...value.slice(0, index + 1), copyingItem, ...value.slice(index + 1)]);
|
|
153
427
|
};
|
|
154
428
|
|
|
@@ -160,272 +434,112 @@ export function ArrayContainer<T>({
|
|
|
160
434
|
id,
|
|
161
435
|
...internalIds.slice(index)
|
|
162
436
|
];
|
|
163
|
-
if (onInternalIdAdded)
|
|
164
|
-
onInternalIdAdded(id);
|
|
437
|
+
if (onInternalIdAdded) onInternalIdAdded(id);
|
|
165
438
|
setInternalIds(newIds);
|
|
166
439
|
onValueChange([...value.slice(0, index), newDefaultEntry, ...value.slice(index)]);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const onDragEnd = (result: any) => {
|
|
170
|
-
// dropped outside the list
|
|
171
|
-
if (!result.destination) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const sourceIndex = result.source.index;
|
|
175
|
-
const destinationIndex = result.destination.index;
|
|
440
|
+
};
|
|
176
441
|
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
442
|
+
const onDragEnd = (event: DragEndEvent) => {
|
|
443
|
+
const {
|
|
444
|
+
active,
|
|
445
|
+
over
|
|
446
|
+
} = event;
|
|
447
|
+
if (!over || active.id === over.id) return;
|
|
448
|
+
|
|
449
|
+
const oldIndex = internalIds.indexOf(active.id as number);
|
|
450
|
+
const newIndex = internalIds.indexOf(over.id as number);
|
|
451
|
+
if (oldIndex === -1 || newIndex === -1) return;
|
|
452
|
+
const newIds = arrayMove(internalIds, oldIndex, newIndex);
|
|
181
453
|
setInternalIds(newIds);
|
|
182
|
-
|
|
183
|
-
onValueChange(arrayMove(value, sourceIndex, destinationIndex));
|
|
454
|
+
onValueChange(arrayMove(value, oldIndex, newIndex));
|
|
184
455
|
};
|
|
185
456
|
|
|
186
|
-
return (
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
{
|
|
247
|
-
|
|
248
|
-
{
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
type ArrayContainerItemProps = {
|
|
269
|
-
provided: DraggableProvided,
|
|
270
|
-
index: number,
|
|
271
|
-
internalId: number,
|
|
272
|
-
size?: "small" | "medium",
|
|
273
|
-
disabled: boolean,
|
|
274
|
-
buildEntry: ArrayEntryBuilder,
|
|
275
|
-
remove: (index: number) => void,
|
|
276
|
-
copy: (index: number) => void,
|
|
277
|
-
addInIndex?: (index: number) => void,
|
|
278
|
-
canAddElements?: boolean,
|
|
279
|
-
sortable: boolean,
|
|
280
|
-
isDragging: boolean,
|
|
281
|
-
storedProps?: object,
|
|
282
|
-
updateItemCustomProps: (internalId: number, props: object) => void
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
export function ArrayContainerItem({
|
|
286
|
-
provided,
|
|
287
|
-
index,
|
|
288
|
-
internalId,
|
|
289
|
-
size,
|
|
290
|
-
disabled,
|
|
291
|
-
buildEntry,
|
|
292
|
-
remove,
|
|
293
|
-
addInIndex,
|
|
294
|
-
canAddElements,
|
|
295
|
-
sortable,
|
|
296
|
-
copy,
|
|
297
|
-
isDragging,
|
|
298
|
-
storedProps,
|
|
299
|
-
updateItemCustomProps
|
|
300
|
-
}: ArrayContainerItemProps) {
|
|
301
|
-
|
|
302
|
-
return <div
|
|
303
|
-
ref={provided.innerRef}
|
|
304
|
-
{...provided.draggableProps}
|
|
305
|
-
style={provided.draggableProps.style}
|
|
306
|
-
className={`relative ${
|
|
307
|
-
!isDragging ? "hover:bg-surface-accent-50 dark:hover:bg-surface-800 dark:hover:bg-opacity-20" : ""
|
|
308
|
-
} rounded-md opacity-100`}
|
|
309
|
-
>
|
|
310
|
-
<div
|
|
311
|
-
className="flex items-start">
|
|
312
|
-
<div
|
|
313
|
-
className="flex-grow w-[calc(100%-48px)] text-text-primary dark:text-text-primary-dark">
|
|
314
|
-
{buildEntry({
|
|
315
|
-
index,
|
|
316
|
-
internalId,
|
|
317
|
-
isDragging,
|
|
318
|
-
storedProps,
|
|
319
|
-
storeProps: (props: object) => updateItemCustomProps(internalId, props)
|
|
320
|
-
})}
|
|
321
|
-
</div>
|
|
322
|
-
<ArrayItemOptions direction={size === "small" ? "row" : "column"}
|
|
323
|
-
disabled={disabled}
|
|
324
|
-
remove={remove}
|
|
325
|
-
index={index}
|
|
326
|
-
provided={provided}
|
|
327
|
-
addInIndex={addInIndex}
|
|
328
|
-
canAddElements={canAddElements}
|
|
329
|
-
sortable={sortable}
|
|
330
|
-
copy={copy}/>
|
|
457
|
+
return sortable ? (
|
|
458
|
+
<DndContext sensors={sensors}
|
|
459
|
+
modifiers={[restrictToVerticalAxis]}
|
|
460
|
+
collisionDetection={closestCenter}
|
|
461
|
+
onDragEnd={onDragEnd}>
|
|
462
|
+
<SortableContext items={internalIds} strategy={verticalListSortingStrategy}>
|
|
463
|
+
<div className={cls("space-y-1", className)} id={droppableId}>
|
|
464
|
+
{hasValue &&
|
|
465
|
+
internalIds.map((internalId: number, index: number) => (
|
|
466
|
+
<SortableItem
|
|
467
|
+
key={`array_field_${internalId}`}
|
|
468
|
+
id={internalId}
|
|
469
|
+
index={index}
|
|
470
|
+
size={size}
|
|
471
|
+
disabled={disabled}
|
|
472
|
+
buildEntry={buildEntry}
|
|
473
|
+
remove={remove}
|
|
474
|
+
copy={copy}
|
|
475
|
+
addInIndex={addInIndex}
|
|
476
|
+
canAddElements={canAddElements}
|
|
477
|
+
sortable={sortable}
|
|
478
|
+
storedProps={itemCustomPropsRef.current[internalId]}
|
|
479
|
+
updateItemCustomProps={updateItemCustomProps}
|
|
480
|
+
/>
|
|
481
|
+
))}
|
|
482
|
+
{canAddElements && (
|
|
483
|
+
<div className="my-4 justify-center text-left">
|
|
484
|
+
<Button
|
|
485
|
+
variant={"text"}
|
|
486
|
+
size={size === "small" ? "small" : "medium"}
|
|
487
|
+
color="primary"
|
|
488
|
+
disabled={disabled || (value?.length ?? 0) >= max}
|
|
489
|
+
startIcon={<AddIcon/>}
|
|
490
|
+
onClick={insertInEnd}
|
|
491
|
+
>
|
|
492
|
+
{addLabel ?? "Add"}
|
|
493
|
+
</Button>
|
|
494
|
+
</div>
|
|
495
|
+
)}
|
|
496
|
+
</div>
|
|
497
|
+
</SortableContext>
|
|
498
|
+
</DndContext>
|
|
499
|
+
) : (
|
|
500
|
+
<div className={cls("space-y-1", className)} id={droppableId}>
|
|
501
|
+
{hasValue &&
|
|
502
|
+
internalIds.map((internalId: number, index: number) => (
|
|
503
|
+
<ArrayContainerItem
|
|
504
|
+
key={`array_field_${internalId}`}
|
|
505
|
+
nodeRef={(node: HTMLElement | null) => {
|
|
506
|
+
}}
|
|
507
|
+
style={{}}
|
|
508
|
+
dragHandleProps={{}}
|
|
509
|
+
internalId={internalId}
|
|
510
|
+
index={index}
|
|
511
|
+
size={size}
|
|
512
|
+
disabled={disabled}
|
|
513
|
+
buildEntry={buildEntry}
|
|
514
|
+
remove={remove}
|
|
515
|
+
copy={copy}
|
|
516
|
+
addInIndex={addInIndex}
|
|
517
|
+
canAddElements={canAddElements}
|
|
518
|
+
sortable={false}
|
|
519
|
+
isDragging={false}
|
|
520
|
+
storedProps={itemCustomPropsRef.current[internalId]}
|
|
521
|
+
updateItemCustomProps={updateItemCustomProps}
|
|
522
|
+
/>
|
|
523
|
+
))}
|
|
524
|
+
{canAddElements && (
|
|
525
|
+
<div className="my-4 justify-center text-left">
|
|
526
|
+
<Button
|
|
527
|
+
variant={"text"}
|
|
528
|
+
size={size === "small" ? "small" : "medium"}
|
|
529
|
+
color="primary"
|
|
530
|
+
disabled={disabled || (value?.length ?? 0) >= max}
|
|
531
|
+
startIcon={<AddIcon/>}
|
|
532
|
+
onClick={insertInEnd}
|
|
533
|
+
>
|
|
534
|
+
{addLabel ?? "Add"}
|
|
535
|
+
</Button>
|
|
536
|
+
</div>
|
|
537
|
+
)}
|
|
331
538
|
</div>
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
export function ArrayItemOptions({
|
|
336
|
-
direction,
|
|
337
|
-
disabled,
|
|
338
|
-
remove,
|
|
339
|
-
index,
|
|
340
|
-
provided,
|
|
341
|
-
copy,
|
|
342
|
-
canAddElements,
|
|
343
|
-
sortable,
|
|
344
|
-
addInIndex
|
|
345
|
-
}: {
|
|
346
|
-
direction?: "row" | "column",
|
|
347
|
-
disabled: boolean,
|
|
348
|
-
remove: (index: number) => void,
|
|
349
|
-
index: number,
|
|
350
|
-
provided: any,
|
|
351
|
-
copy: (index: number) => void,
|
|
352
|
-
sortable: boolean,
|
|
353
|
-
canAddElements?: boolean,
|
|
354
|
-
addInIndex?: (index: number) => void
|
|
355
|
-
}) {
|
|
356
|
-
|
|
357
|
-
const [menuOpen, setMenuOpen] = useState(false);
|
|
358
|
-
|
|
359
|
-
const iconRef = React.useRef<HTMLDivElement>(null);
|
|
360
|
-
useOutsideAlerter(iconRef, () => setMenuOpen(false));
|
|
361
|
-
|
|
362
|
-
return <div className={`pl-2 pt-1 pb-1 flex ${direction === "row" ? "flex-row-reverse" : "flex-col"} items-center`}
|
|
363
|
-
ref={iconRef}
|
|
364
|
-
{...provided.dragHandleProps}>
|
|
365
|
-
<Tooltip
|
|
366
|
-
delayDuration={400}
|
|
367
|
-
open={menuOpen ? false : undefined}
|
|
368
|
-
side={direction === "column" ? "left" : undefined}
|
|
369
|
-
title={!disabled && sortable ? "Drag to move. Click for more options" : undefined}>
|
|
370
|
-
<IconButton
|
|
371
|
-
size="small"
|
|
372
|
-
disabled={disabled || !canAddElements}
|
|
373
|
-
onClick={(e) => {
|
|
374
|
-
e.preventDefault();
|
|
375
|
-
setMenuOpen(true);
|
|
376
|
-
}}
|
|
377
|
-
onDragStart={(e: any) => {
|
|
378
|
-
setMenuOpen(false);
|
|
379
|
-
}}
|
|
380
|
-
className={disabled || !sortable ? "cursor-inherit" : "cursor-grab"}>
|
|
381
|
-
<HandleIcon/>
|
|
382
|
-
</IconButton>
|
|
383
|
-
|
|
384
|
-
<Menu
|
|
385
|
-
portalContainer={iconRef.current}
|
|
386
|
-
open={menuOpen}
|
|
387
|
-
trigger={<div tabIndex={-1}/>}>
|
|
388
|
-
|
|
389
|
-
<MenuItem dense onClick={(e) => {
|
|
390
|
-
setMenuOpen(false);
|
|
391
|
-
remove(index);
|
|
392
|
-
}}>
|
|
393
|
-
<RemoveIcon size={"small"}/>
|
|
394
|
-
Remove
|
|
395
|
-
</MenuItem>
|
|
396
|
-
<MenuItem dense onClick={() => {
|
|
397
|
-
setMenuOpen(false);
|
|
398
|
-
copy(index);
|
|
399
|
-
}}>
|
|
400
|
-
<ContentCopyIcon size={"small"}/>
|
|
401
|
-
Copy
|
|
402
|
-
</MenuItem>
|
|
403
|
-
|
|
404
|
-
{addInIndex && <MenuItem dense
|
|
405
|
-
onClick={() => {
|
|
406
|
-
setMenuOpen(false);
|
|
407
|
-
addInIndex(index);
|
|
408
|
-
}}>
|
|
409
|
-
<KeyboardArrowUpIcon size={"small"}/>
|
|
410
|
-
Add on top
|
|
411
|
-
</MenuItem>}
|
|
412
|
-
|
|
413
|
-
{addInIndex && <MenuItem dense
|
|
414
|
-
onClick={() => {
|
|
415
|
-
setMenuOpen(false);
|
|
416
|
-
addInIndex(index + 1);
|
|
417
|
-
}}>
|
|
418
|
-
<KeyboardArrowDownIcon size={"small"}/>
|
|
419
|
-
Add below
|
|
420
|
-
</MenuItem>}
|
|
421
|
-
|
|
422
|
-
</Menu>
|
|
423
|
-
</Tooltip>
|
|
424
|
-
|
|
425
|
-
</div>;
|
|
539
|
+
);
|
|
426
540
|
}
|
|
427
541
|
|
|
428
|
-
function arrayMove(value:
|
|
542
|
+
function arrayMove<T>(value: T[], sourceIndex: number, destinationIndex: number): T[] {
|
|
429
543
|
const result = Array.from(value);
|
|
430
544
|
const [removed] = result.splice(sourceIndex, 1);
|
|
431
545
|
result.splice(destinationIndex, 0, removed);
|