@firecms/core 3.0.0-beta.12 → 3.0.0-beta.14

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.
Files changed (120) hide show
  1. package/dist/app/Scaffold.d.ts +4 -0
  2. package/dist/components/ArrayContainer.d.ts +13 -11
  3. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
  4. package/dist/components/EntityJsonPreview.d.ts +3 -0
  5. package/dist/components/EntityPreview.d.ts +4 -2
  6. package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
  7. package/dist/components/VirtualTable/VirtualTableProps.d.ts +0 -4
  8. package/dist/components/index.d.ts +1 -0
  9. package/dist/core/DefaultAppBar.d.ts +8 -2
  10. package/dist/core/EntityEditView.d.ts +8 -1
  11. package/dist/core/FireCMS.d.ts +2 -2
  12. package/dist/form/EntityForm.d.ts +5 -1
  13. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  14. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  15. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +3 -9
  16. package/dist/hooks/data/save.d.ts +1 -1
  17. package/dist/hooks/useBuildNavigationController.d.ts +2 -9
  18. package/dist/index.es.js +9251 -8721
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/index.umd.js +16686 -16160
  21. package/dist/index.umd.js.map +1 -1
  22. package/dist/internal/useBuildDataSource.d.ts +3 -2
  23. package/dist/internal/useBuildSideEntityController.d.ts +3 -3
  24. package/dist/types/collections.d.ts +26 -2
  25. package/dist/types/fields.d.ts +4 -1
  26. package/dist/types/firecms.d.ts +3 -2
  27. package/dist/types/navigation.d.ts +9 -0
  28. package/dist/types/plugins.d.ts +13 -0
  29. package/dist/types/properties.d.ts +22 -1
  30. package/dist/types/side_entity_controller.d.ts +4 -0
  31. package/dist/util/callbacks.d.ts +2 -0
  32. package/dist/util/createFormexStub.d.ts +2 -0
  33. package/dist/util/index.d.ts +1 -0
  34. package/dist/util/navigation_utils.d.ts +2 -1
  35. package/dist/util/objects.d.ts +1 -1
  36. package/dist/util/property_utils.d.ts +2 -2
  37. package/dist/util/references.d.ts +2 -2
  38. package/dist/util/resolutions.d.ts +11 -6
  39. package/package.json +9 -6
  40. package/src/app/Scaffold.tsx +13 -3
  41. package/src/components/ArrayContainer.tsx +414 -282
  42. package/src/components/ClearFilterSortButton.tsx +1 -1
  43. package/src/components/ConfirmationDialog.tsx +9 -9
  44. package/src/components/DeleteEntityDialog.tsx +4 -2
  45. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +8 -6
  46. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +1 -1
  47. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +1 -1
  48. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +13 -2
  49. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +58 -29
  50. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +9 -9
  51. package/src/components/EntityCollectionView/EntityCollectionView.tsx +26 -6
  52. package/src/components/EntityJsonPreview.tsx +66 -0
  53. package/src/components/EntityPreview.tsx +27 -16
  54. package/src/components/EntityView.tsx +4 -1
  55. package/src/components/ErrorView.tsx +1 -1
  56. package/src/components/HomePage/DefaultHomePage.tsx +2 -1
  57. package/src/components/HomePage/NavigationCardBinding.tsx +3 -1
  58. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -1
  59. package/src/components/SelectableTable/SelectableTable.tsx +140 -143
  60. package/src/components/VirtualTable/VirtualTable.tsx +37 -31
  61. package/src/components/VirtualTable/VirtualTableProps.tsx +0 -5
  62. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -1
  63. package/src/components/common/default_entity_actions.tsx +15 -4
  64. package/src/components/index.tsx +2 -0
  65. package/src/core/DefaultAppBar.tsx +17 -5
  66. package/src/core/EntityEditView.tsx +135 -47
  67. package/src/core/EntitySidePanel.tsx +15 -20
  68. package/src/core/FireCMS.tsx +19 -12
  69. package/src/form/EntityForm.tsx +32 -24
  70. package/src/form/PropertyFieldBinding.tsx +8 -6
  71. package/src/form/components/CustomIdField.tsx +3 -1
  72. package/src/form/components/LabelWithIcon.tsx +1 -1
  73. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +4 -1
  74. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -2
  75. package/src/form/field_bindings/BlockFieldBinding.tsx +1 -1
  76. package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
  77. package/src/form/field_bindings/MapFieldBinding.tsx +5 -4
  78. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +6 -2
  79. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +3 -3
  80. package/src/form/field_bindings/RepeatFieldBinding.tsx +9 -2
  81. package/src/form/field_bindings/SelectFieldBinding.tsx +3 -2
  82. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +227 -156
  83. package/src/hooks/data/save.ts +24 -35
  84. package/src/hooks/useBuildNavigationController.tsx +42 -17
  85. package/src/hooks/useFireCMSContext.tsx +0 -30
  86. package/src/internal/useBuildDataSource.ts +9 -5
  87. package/src/internal/useBuildSideEntityController.tsx +26 -20
  88. package/src/preview/PropertyPreview.tsx +4 -2
  89. package/src/preview/components/ImagePreview.tsx +2 -2
  90. package/src/preview/components/ReferencePreview.tsx +1 -1
  91. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +5 -4
  92. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
  93. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +4 -2
  94. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
  95. package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -2
  96. package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -2
  97. package/src/preview/property_previews/MapPropertyPreview.tsx +1 -1
  98. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +1 -1
  99. package/src/preview/property_previews/StringPropertyPreview.tsx +1 -1
  100. package/src/routes/FireCMSRoute.tsx +15 -4
  101. package/src/types/collections.ts +31 -2
  102. package/src/types/fields.tsx +5 -1
  103. package/src/types/firecms.tsx +3 -3
  104. package/src/types/navigation.ts +11 -0
  105. package/src/types/plugins.tsx +17 -0
  106. package/src/types/properties.ts +27 -1
  107. package/src/types/side_entity_controller.tsx +5 -0
  108. package/src/util/callbacks.ts +119 -0
  109. package/src/util/createFormexStub.tsx +62 -0
  110. package/src/util/index.ts +1 -0
  111. package/src/util/join_collections.ts +3 -1
  112. package/src/util/navigation_from_path.ts +5 -1
  113. package/src/util/navigation_utils.ts +84 -20
  114. package/src/util/objects.ts +54 -17
  115. package/src/util/property_utils.tsx +7 -3
  116. package/src/util/references.ts +8 -6
  117. package/src/util/resolutions.ts +17 -9
  118. package/src/util/useStorageUploadController.tsx +21 -2
  119. package/dist/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.d.ts +0 -5
  120. package/src/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.tsx +0 -59
@@ -1,7 +1,17 @@
1
1
  import React, { useCallback, useEffect, useRef, useState } from "react";
2
-
3
- import { DragDropContext, Draggable, DraggableProvided, Droppable } from "@hello-pangea/dnd";
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;
@@ -38,8 +48,10 @@ export interface ArrayContainerProps<T> {
38
48
  size?: "small" | "medium";
39
49
  onInternalIdAdded?: (id: number) => void;
40
50
  includeAddButton?: boolean;
51
+ canAddElements?: boolean;
52
+ sortable?: boolean;
41
53
  newDefaultEntry: T;
42
- onValueChange: (value: T[]) => void,
54
+ onValueChange: (value: T[]) => void;
43
55
  className?: string;
44
56
  min?: number;
45
57
  max?: number;
@@ -47,17 +59,285 @@ export interface ArrayContainerProps<T> {
47
59
 
48
60
  const buildIdsMap = (value: any[]) =>
49
61
  value && Array.isArray(value) && value.length > 0
50
- ? value.map((v, index) => {
51
- if (!v) return {};
52
- return ({
53
- [getHashValue(v) + index]: getRandomId()
54
- });
55
- }).reduce((a, b) => ({ ...a, ...b }), {})
56
- : {}
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
+ }
57
340
 
58
- /**
59
- * @group Form custom fields
60
- */
61
341
  export function ArrayContainer<T>({
62
342
  droppableId,
63
343
  addLabel,
@@ -66,31 +346,38 @@ export function ArrayContainer<T>({
66
346
  buildEntry,
67
347
  size = "medium",
68
348
  onInternalIdAdded,
69
- includeAddButton,
349
+ includeAddButton: deprecatedIncludeAddButton,
350
+ canAddElements: canAddElementsProp = true,
351
+ sortable = true,
70
352
  newDefaultEntry,
71
353
  onValueChange,
72
354
  className,
73
355
  min = 0,
74
356
  max = Infinity
75
357
  }: ArrayContainerProps<T>) {
358
+ const canAddElements =
359
+ (canAddElementsProp === undefined ? true : canAddElementsProp) && // Default canAddElementsProp to true if undefined
360
+ (deprecatedIncludeAddButton === undefined || deprecatedIncludeAddButton);
76
361
 
77
362
  const hasValue = value && Array.isArray(value) && value.length > 0;
78
-
79
- // Used to track the ids that have displayed the initial show animation
80
363
  const internalIdsRef = useRef<Record<string, number>>(buildIdsMap(value));
81
-
82
364
  const [internalIds, setInternalIds] = useState<number[]>(
83
- hasValue
84
- ? Object.values(internalIdsRef.current)
85
- : []
365
+ hasValue ? Object.values(internalIdsRef.current) : []
86
366
  );
87
-
88
367
  const itemCustomPropsRef = useRef<Record<number, object>>({});
89
368
 
90
369
  const updateItemCustomProps = useCallback((internalId: number, customProps: object) => {
91
370
  itemCustomPropsRef.current[internalId] = customProps;
92
371
  }, []);
93
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
+
94
381
  useEffect(() => {
95
382
  if (hasValue && value && value.length !== internalIds.length) {
96
383
  const newInternalIds = value.map((v, index) => {
@@ -112,8 +399,7 @@ export function ArrayContainer<T>({
112
399
  if (disabled || (value ?? []).length >= max) return;
113
400
  const id = getRandomId();
114
401
  const newIds: number[] = [...internalIds, id];
115
- if (onInternalIdAdded)
116
- onInternalIdAdded(id);
402
+ if (onInternalIdAdded) onInternalIdAdded(id);
117
403
  setInternalIds(newIds);
118
404
  onValueChange([...(value ?? []), newDefaultEntry]);
119
405
  };
@@ -135,10 +421,8 @@ export function ArrayContainer<T>({
135
421
  id,
136
422
  ...internalIds.slice(index + 1)
137
423
  ];
138
- if (onInternalIdAdded)
139
- onInternalIdAdded(id);
424
+ if (onInternalIdAdded) onInternalIdAdded(id);
140
425
  setInternalIds(newIds);
141
- // insert value in index + 1
142
426
  onValueChange([...value.slice(0, index + 1), copyingItem, ...value.slice(index + 1)]);
143
427
  };
144
428
 
@@ -150,265 +434,113 @@ export function ArrayContainer<T>({
150
434
  id,
151
435
  ...internalIds.slice(index)
152
436
  ];
153
- if (onInternalIdAdded)
154
- onInternalIdAdded(id);
437
+ if (onInternalIdAdded) onInternalIdAdded(id);
155
438
  setInternalIds(newIds);
156
439
  onValueChange([...value.slice(0, index), newDefaultEntry, ...value.slice(index)]);
157
- }
158
-
159
- const onDragEnd = (result: any) => {
160
- // dropped outside the list
161
- if (!result.destination) {
162
- return;
163
- }
164
- const sourceIndex = result.source.index;
165
- const destinationIndex = result.destination.index;
440
+ };
166
441
 
167
- const newIds = [...internalIds];
168
- const temp = newIds[sourceIndex];
169
- newIds[sourceIndex] = newIds[destinationIndex];
170
- newIds[destinationIndex] = temp;
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);
171
453
  setInternalIds(newIds);
172
-
173
- onValueChange(arrayMove(value, sourceIndex, destinationIndex));
454
+ onValueChange(arrayMove(value, oldIndex, newIndex));
174
455
  };
175
456
 
176
- return (
177
- <DragDropContext onDragEnd={onDragEnd}>
178
- <Droppable droppableId={droppableId}
179
- renderClone={(provided, snapshot, rubric) => {
180
- const index = rubric.source.index;
181
- const internalId = internalIds[index];
182
- return (
183
- <ArrayContainerItem
184
- provided={provided}
185
- internalId={internalId}
186
- index={index}
187
- size={size}
188
- disabled={disabled}
189
- buildEntry={buildEntry}
190
- remove={remove}
191
- copy={copy}
192
- isDragging={snapshot.isDragging}
193
- storedProps={itemCustomPropsRef.current[internalId]}
194
- updateItemCustomProps={updateItemCustomProps}
195
- addInIndex={addInIndex}
196
- includeAddButton={includeAddButton}
197
- />
198
- );
199
- }}
200
- >
201
- {(droppableProvided, droppableSnapshot) => (
202
- <div
203
- className={cls("space-y-1", className)}
204
- {...droppableProvided.droppableProps}
205
- ref={droppableProvided.innerRef}>
206
- {hasValue && internalIds.map((internalId: number, index: number) => {
207
- return (
208
- <Draggable
209
- key={`array_field_${internalId}`}
210
- draggableId={`array_field_${internalId}`}
211
- isDragDisabled={disabled}
212
- index={index}>
213
- {(provided, snapshot) => (
214
- <ArrayContainerItem
215
- provided={provided}
216
- internalId={internalId}
217
- index={index}
218
- size={size}
219
- disabled={disabled}
220
- buildEntry={buildEntry}
221
- remove={remove}
222
- copy={copy}
223
- isDragging={snapshot.isDragging}
224
- storedProps={itemCustomPropsRef.current[internalId]}
225
- updateItemCustomProps={updateItemCustomProps}
226
- addInIndex={addInIndex}
227
- includeAddButton={includeAddButton}
228
- />
229
- )}
230
- </Draggable>
231
- );
232
- })}
233
-
234
- {droppableProvided.placeholder}
235
-
236
- {includeAddButton && (
237
- <div className="my-4 justify-center text-left">
238
- <Button
239
- variant={"text"}
240
- size={size === "small" ? "small" : "medium"}
241
- color="primary"
242
- disabled={disabled || value?.length >= max}
243
- startIcon={<AddIcon/>}
244
- onClick={insertInEnd}>
245
- {addLabel ?? "Add"}
246
- </Button>
247
- </div>
248
- )}
249
- </div>
250
- )}
251
- </Droppable>
252
- </DragDropContext>
253
- );
254
- }
255
-
256
- type ArrayContainerItemProps = {
257
- provided: DraggableProvided,
258
- index: number,
259
- internalId: number,
260
- size?: "small" | "medium",
261
- disabled: boolean,
262
- buildEntry: ArrayEntryBuilder,
263
- remove: (index: number) => void,
264
- copy: (index: number) => void,
265
- addInIndex?: (index: number) => void,
266
- includeAddButton?: boolean,
267
- isDragging: boolean,
268
- storedProps?: object,
269
- updateItemCustomProps: (internalId: number, props: object) => void
270
- };
271
-
272
- export function ArrayContainerItem({
273
- provided,
274
- index,
275
- internalId,
276
- size,
277
- disabled,
278
- buildEntry,
279
- remove,
280
- addInIndex,
281
- includeAddButton,
282
- copy,
283
- isDragging,
284
- storedProps,
285
- updateItemCustomProps
286
- }: ArrayContainerItemProps) {
287
-
288
- return <div
289
- ref={provided.innerRef}
290
- {...provided.draggableProps}
291
- style={provided.draggableProps.style}
292
- className={`relative ${
293
- !isDragging ? "hover:bg-surface-accent-50 dark:hover:bg-surface-800 dark:hover:bg-opacity-20" : ""
294
- } rounded-md opacity-100`}
295
- >
296
- <div
297
- className="flex items-start">
298
- <div
299
- className="flex-grow w-[calc(100%-48px)] text-text-primary dark:text-text-primary-dark">
300
- {buildEntry({
301
- index,
302
- internalId,
303
- isDragging,
304
- storedProps,
305
- storeProps: (props: object) => updateItemCustomProps(internalId, props)
306
- })}
307
- </div>
308
- <ArrayItemOptions direction={size === "small" ? "row" : "column"}
309
- disabled={disabled}
310
- remove={remove}
311
- index={index}
312
- provided={provided}
313
- addInIndex={addInIndex}
314
- includeAddButton={includeAddButton}
315
- 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
+ className={"ml-3.5"}
492
+ >
493
+ {addLabel ?? "Add"}
494
+ </Button>
495
+ </div>
496
+ )}
497
+ </div>
498
+ </SortableContext>
499
+ </DndContext>
500
+ ) : (
501
+ <div className={cls("space-y-1", className)} id={droppableId}>
502
+ {hasValue &&
503
+ internalIds.map((internalId: number, index: number) => (
504
+ <ArrayContainerItem
505
+ key={`array_field_${internalId}`}
506
+ nodeRef={(node: HTMLElement | null) => {
507
+ }}
508
+ style={{}}
509
+ dragHandleProps={{}}
510
+ internalId={internalId}
511
+ index={index}
512
+ size={size}
513
+ disabled={disabled}
514
+ buildEntry={buildEntry}
515
+ remove={remove}
516
+ copy={copy}
517
+ addInIndex={addInIndex}
518
+ canAddElements={canAddElements}
519
+ sortable={false}
520
+ isDragging={false}
521
+ storedProps={itemCustomPropsRef.current[internalId]}
522
+ updateItemCustomProps={updateItemCustomProps}
523
+ />
524
+ ))}
525
+ {canAddElements && (
526
+ <div className="my-4 justify-center text-left">
527
+ <Button
528
+ variant={"text"}
529
+ size={size === "small" ? "small" : "medium"}
530
+ color="primary"
531
+ disabled={disabled || (value?.length ?? 0) >= max}
532
+ startIcon={<AddIcon/>}
533
+ onClick={insertInEnd}
534
+ >
535
+ {addLabel ?? "Add"}
536
+ </Button>
537
+ </div>
538
+ )}
316
539
  </div>
317
- </div>;
318
- }
319
-
320
- export function ArrayItemOptions({
321
- direction,
322
- disabled,
323
- remove,
324
- index,
325
- provided,
326
- copy,
327
- includeAddButton,
328
- addInIndex
329
- }: {
330
- direction?: "row" | "column",
331
- disabled: boolean,
332
- remove: (index: number) => void,
333
- index: number,
334
- provided: any,
335
- copy: (index: number) => void,
336
- includeAddButton?: boolean,
337
- addInIndex?: (index: number) => void
338
- }) {
339
-
340
- const [menuOpen, setMenuOpen] = useState(false);
341
-
342
- const iconRef = React.useRef<HTMLDivElement>(null);
343
- useOutsideAlerter(iconRef, () => setMenuOpen(false));
344
-
345
- return <div className={`pl-2 pt-1 pb-1 flex ${direction === "row" ? "flex-row-reverse" : "flex-col"} items-center`}
346
- ref={iconRef}
347
- {...provided.dragHandleProps}>
348
- <Tooltip
349
- delayDuration={400}
350
- open={menuOpen ? false : undefined}
351
- side={direction === "column" ? "left" : undefined}
352
- title="Drag to move. Click for more options">
353
- <IconButton
354
- size="small"
355
- disabled={disabled}
356
- onClick={(e) => {
357
- e.preventDefault();
358
- setMenuOpen(true);
359
- }}
360
- onDragStart={(e: any) => {
361
- setMenuOpen(false);
362
- }}
363
- className={`cursor-${disabled ? "inherit" : "grab"}`}>
364
- <HandleIcon/>
365
- </IconButton>
366
-
367
- <Menu
368
- portalContainer={iconRef.current}
369
- open={menuOpen}
370
- trigger={<div tabIndex={-1}/>}>
371
-
372
- <MenuItem dense onClick={(e) => {
373
- setMenuOpen(false);
374
- remove(index);
375
- }}>
376
- <RemoveIcon size={"small"}/>
377
- Remove
378
- </MenuItem>
379
- <MenuItem dense onClick={() => {
380
- setMenuOpen(false);
381
- copy(index);
382
- }}>
383
- <ContentCopyIcon size={"small"}/>
384
- Copy
385
- </MenuItem>
386
-
387
- {includeAddButton && addInIndex && <MenuItem dense
388
- onClick={() => {
389
- setMenuOpen(false);
390
- addInIndex(index);
391
- }}>
392
- <KeyboardArrowUpIcon size={"small"}/>
393
- Add on top
394
- </MenuItem>}
395
-
396
- {includeAddButton && addInIndex && <MenuItem dense
397
- onClick={() => {
398
- setMenuOpen(false);
399
- addInIndex(index + 1);
400
- }}>
401
- <KeyboardArrowDownIcon size={"small"}/>
402
- Add below
403
- </MenuItem>}
404
-
405
- </Menu>
406
- </Tooltip>
407
-
408
- </div>;
540
+ );
409
541
  }
410
542
 
411
- function arrayMove(value: any[], sourceIndex: number, destinationIndex: number) {
543
+ function arrayMove<T>(value: T[], sourceIndex: number, destinationIndex: number): T[] {
412
544
  const result = Array.from(value);
413
545
  const [removed] = result.splice(sourceIndex, 1);
414
546
  result.splice(destinationIndex, 0, removed);