Skip to content

Commit 8e374f1

Browse files
committed
Key value array fixes
1 parent 18b6ea2 commit 8e374f1

15 files changed

+762
-361
lines changed

lib/src/form/components/ArrayContainer.tsx lib/src/core/components/ArrayContainer.tsx

+65-142
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import React, {
55
useRef,
66
useState
77
} from "react";
8-
import { FieldArray } from "formik";
98

109
import {
1110
Box,
@@ -35,30 +34,32 @@ import useMeasure from "react-use-measure";
3534
import { MoreVert } from "@mui/icons-material";
3635

3736
interface ArrayContainerProps<T> {
37+
droppableId: string;
3838
value: T[];
39-
name: string;
4039
addLabel: string;
4140
buildEntry: (index: number, internalId: number) => React.ReactNode;
42-
disabled: boolean;
41+
disabled?: boolean;
4342
small?: boolean;
4443
onInternalIdAdded?: (id: number) => void;
4544
includeAddButton?: boolean;
46-
newDefaultEntry?: T | null;
45+
newDefaultEntry: T;
46+
onValueChange: (value: T[]) => void
4747
}
4848

4949
/**
5050
* @category Form custom fields
5151
*/
5252
export function ArrayContainer<T>({
53-
name,
53+
droppableId,
5454
addLabel,
5555
value,
56-
disabled,
56+
disabled = false,
5757
buildEntry,
5858
small,
5959
onInternalIdAdded,
6060
includeAddButton,
61-
newDefaultEntry = null
61+
newDefaultEntry,
62+
onValueChange
6263
}: ArrayContainerProps<T>) {
6364

6465
const hasValue = value && Array.isArray(value) && value.length > 0;
@@ -74,7 +75,6 @@ export function ArrayContainer<T>({
7475
[value, hasValue]);
7576

7677
// Used to track the ids that have displayed the initial show animation
77-
const animatedIds = useRef<Set<number>>(new Set(Object.values(internalIdsMap)));
7878
const internalIdsRef = useRef<Record<string, number>>(internalIdsMap);
7979

8080
const [internalIds, setInternalIds] = useState<number[]>(
@@ -98,83 +98,25 @@ export function ArrayContainer<T>({
9898
}
9999
}, [hasValue, internalIds.length, value]);
100100

101-
return <FieldArray
102-
name={name}
103-
validateOnChange={true}
104-
render={arrayHelpers => {
105-
return <ArrayContainerInternal
106-
disabled={disabled}
107-
internalIds={internalIds}
108-
onInternalIdAdded={onInternalIdAdded}
109-
setInternalIds={setInternalIds}
110-
arrayHelpers={arrayHelpers}
111-
newDefaultEntry={newDefaultEntry}
112-
value={value}
113-
name={name}
114-
small={small}
115-
buildEntry={buildEntry}
116-
hasValue={hasValue}
117-
includeAddButton={includeAddButton}
118-
addLabel={addLabel}
119-
animatedIds={animatedIds}
120-
/>;
121-
}}
122-
/>;
123-
}
124101

125-
function ArrayContainerInternal<T>({
126-
disabled,
127-
internalIds,
128-
onInternalIdAdded,
129-
setInternalIds,
130-
arrayHelpers,
131-
newDefaultEntry,
132-
value,
133-
name,
134-
small,
135-
buildEntry,
136-
hasValue,
137-
includeAddButton,
138-
addLabel,
139-
animatedIds
140-
}: {
141-
disabled: boolean,
142-
internalIds: any,
143-
onInternalIdAdded: ((id: number) => void) | undefined,
144-
setInternalIds: any,
145-
arrayHelpers: any,
146-
newDefaultEntry: T | null | undefined,
147-
value: T[],
148-
name: string,
149-
small: boolean | undefined,
150-
buildEntry: (index: number, internalId: number) => React.ReactNode,
151-
hasValue: boolean,
152-
includeAddButton: boolean | undefined,
153-
addLabel: string,
154-
animatedIds: React.MutableRefObject<Set<number>>,
155-
}) {
156-
// eslint-disable-next-line react-hooks/rules-of-hooks
157-
const insertInEnd = useCallback(() => {
102+
const insertInEnd = () => {
158103
if (disabled) return;
159104
const id = getRandomId();
160105
const newIds: number[] = [...internalIds, id];
161106
if (onInternalIdAdded)
162107
onInternalIdAdded(id);
163108
setInternalIds(newIds);
164-
arrayHelpers.push(newDefaultEntry);
165-
}, [arrayHelpers, disabled, internalIds, newDefaultEntry, onInternalIdAdded, setInternalIds]);
109+
onValueChange([...(value ?? []), newDefaultEntry]);
110+
};
166111

167-
// eslint-disable-next-line react-hooks/rules-of-hooks
168-
const remove = useCallback((index: number) => {
112+
const remove = (index: number) => {
169113
const newIds = [...internalIds];
170114
newIds.splice(index, 1);
171115
setInternalIds(newIds);
172-
animatedIds.current.delete(internalIds[index]);
173-
arrayHelpers.remove(index);
174-
}, [arrayHelpers, internalIds, setInternalIds]);
116+
onValueChange(value.filter((_, i) => i !== index));
117+
};
175118

176-
// eslint-disable-next-line react-hooks/rules-of-hooks
177-
const copy = useCallback((index: number) => {
119+
const copy = (index: number) => {
178120
const id = getRandomId();
179121
const copyingItem = value[index];
180122
const newIds: number[] = [
@@ -184,11 +126,11 @@ function ArrayContainerInternal<T>({
184126
if (onInternalIdAdded)
185127
onInternalIdAdded(id);
186128
setInternalIds(newIds);
187-
arrayHelpers.insert(index + 1, copyingItem);
188-
}, [arrayHelpers, internalIds, onInternalIdAdded, setInternalIds, value]);
129+
// insert value in index + 1
130+
onValueChange([...value.slice(0, index + 1), copyingItem, ...value.slice(index + 1)]);
131+
};
189132

190-
// eslint-disable-next-line react-hooks/rules-of-hooks
191-
const onDragEnd = useCallback((result: any) => {
133+
const onDragEnd = (result: any) => {
192134
// dropped outside the list
193135
if (!result.destination) {
194136
return;
@@ -202,12 +144,12 @@ function ArrayContainerInternal<T>({
202144
newIds[destinationIndex] = temp;
203145
setInternalIds(newIds);
204146

205-
arrayHelpers.move(sourceIndex, destinationIndex);
206-
}, [arrayHelpers, internalIds, setInternalIds]);
147+
onValueChange(arrayMove(value, sourceIndex, destinationIndex));
148+
};
207149

208150
return (
209151
<DragDropContext onDragEnd={onDragEnd}>
210-
<Droppable droppableId={`droppable_${name}`}
152+
<Droppable droppableId={droppableId}
211153
renderClone={(provided, snapshot, rubric) => {
212154
const index = rubric.source.index;
213155
const internalId = internalIds[index];
@@ -216,13 +158,11 @@ function ArrayContainerInternal<T>({
216158
provided={provided}
217159
internalId={internalId}
218160
index={index}
219-
name={name}
220161
small={small}
221162
disabled={disabled}
222163
buildEntry={buildEntry}
223164
remove={remove}
224165
copy={copy}
225-
animatedIds={animatedIds}
226166
/>
227167
);
228168
}}
@@ -234,22 +174,20 @@ function ArrayContainerInternal<T>({
234174
{hasValue && internalIds.map((internalId: number, index: number) => {
235175
return (
236176
<Draggable
237-
key={`array_field_${name}_${internalId}`}
238-
draggableId={`array_field_${name}_${internalId}`}
177+
key={`array_field_${internalId}`}
178+
draggableId={`array_field_${internalId}`}
239179
isDragDisabled={disabled}
240180
index={index}>
241181
{(provided, snapshot) => (
242182
<ArrayContainerItem
243183
provided={provided}
244184
internalId={internalId}
245185
index={index}
246-
name={name}
247186
small={small}
248187
disabled={disabled}
249188
buildEntry={buildEntry}
250189
remove={remove}
251190
copy={copy}
252-
animatedIds={animatedIds}
253191
/>
254192
)}
255193
</Draggable>
@@ -261,12 +199,13 @@ function ArrayContainerInternal<T>({
261199
{includeAddButton && <Box p={1}
262200
justifyContent="center"
263201
textAlign={"left"}>
264-
<Button variant="outlined"
265-
size={"large"}
266-
color="primary"
267-
disabled={disabled}
268-
startIcon={<AddIcon/>}
269-
onClick={insertInEnd}>
202+
<Button
203+
variant={small ? "text" : "outlined"}
204+
size={small ? "small" : "large"}
205+
color="primary"
206+
disabled={disabled}
207+
startIcon={<AddIcon/>}
208+
onClick={insertInEnd}>
270209
{addLabel ?? "Add"}
271210
</Button>
272211
</Box>}
@@ -280,65 +219,43 @@ function ArrayContainerInternal<T>({
280219
type ArrayContainerItemProps = {
281220
provided: DraggableProvided,
282221
index: number,
283-
name: string,
284222
internalId: number,
285223
small?: boolean,
286224
disabled: boolean,
287225
buildEntry: (index: number, internalId: number) => React.ReactNode,
288226
remove: (index: number) => void,
289227
copy: (index: number) => void,
290-
animatedIds: React.MutableRefObject<Set<number>>,
291228
};
292229

293-
function ArrayContainerItem({
294-
provided,
295-
index,
296-
name,
297-
internalId,
298-
small,
299-
disabled,
300-
buildEntry,
301-
remove,
302-
copy,
303-
animatedIds
304-
}: ArrayContainerItemProps) {
230+
export function ArrayContainerItem({
231+
provided,
232+
index,
233+
internalId,
234+
small,
235+
disabled,
236+
buildEntry,
237+
remove,
238+
copy,
239+
}: ArrayContainerItemProps) {
305240

306241
const [measureRef, bounds] = useMeasure();
307-
const smallContent = bounds.height < 100;
308-
309-
// WIP of animation
310-
// const initiallyDisplayed = animatedIds.current.has(internalId);
311-
// const [displayed, setDisplayed] = useState(initiallyDisplayed);
312-
// useEffect(() => {
313-
// setDisplayed(true);
314-
// animatedIds.current.add(internalId);
315-
// }, []);
316-
//
317-
// console.log(animatedIds.current);
242+
const contentOverflow = !small && bounds.height < 100;
318243

319244
return <Box
320245
ref={provided.innerRef}
321246
{...provided.draggableProps}
322-
style={
323-
provided.draggableProps.style
324-
}
247+
style={provided.draggableProps.style}
325248
sx={theme => ({
326249
marginBottom: 1,
327250
borderRadius: theme.shape.borderRadius,
328251
opacity: 1
329252
})}
330253
>
331-
{/*<Collapse*/}
332-
{/* in={displayed}*/}
333-
{/* appear={false}*/}
334-
{/* enter={initiallyDisplayed}*/}
335-
{/* timeout={500}>*/}
336254
<Box
337255
display="flex">
338256
<Box ref={measureRef}
339257
flexGrow={1}
340258
width={"calc(100% - 48px)"}
341-
// key={`field_${name}_entryValue`}
342259
>
343260
{buildEntry(index, internalId)}
344261
</Box>
@@ -347,24 +264,23 @@ function ArrayContainerItem({
347264
remove={remove}
348265
index={index}
349266
provided={provided}
350-
smallContent={smallContent}
267+
contentOverflow={contentOverflow}
351268
copy={copy}/>
352269
</Box>
353-
{/*</Collapse>*/}
354270
</Box>;
355271
}
356272

357-
function ArrayItemOptions({
358-
direction,
359-
disabled,
360-
remove,
361-
index,
362-
provided,
363-
copy,
364-
smallContent
365-
}: {
273+
export function ArrayItemOptions({
274+
direction,
275+
disabled,
276+
remove,
277+
index,
278+
provided,
279+
copy,
280+
contentOverflow
281+
}: {
366282
direction?: "row" | "column",
367-
smallContent: boolean,
283+
contentOverflow: boolean,
368284
disabled: boolean,
369285
remove: (index: number) => void,
370286
index: number,
@@ -384,7 +300,7 @@ function ArrayItemOptions({
384300
}, [setAnchorEl]);
385301

386302
return <Box display="flex"
387-
flexDirection={direction ?? "column"}
303+
flexDirection={direction === "row" ? "row-reverse" : "column"}
388304
sx={{
389305
pl: 1
390306
}}
@@ -405,7 +321,7 @@ function ArrayItemOptions({
405321
</Tooltip>
406322
</div>
407323

408-
{!smallContent && <>
324+
{!contentOverflow && <>
409325
<Tooltip
410326
title="Remove"
411327
placement={direction === "column" ? "left" : undefined}>
@@ -433,7 +349,7 @@ function ArrayItemOptions({
433349
</Tooltip>
434350
</>}
435351

436-
{smallContent && <>
352+
{contentOverflow && <>
437353
<IconButton onClick={openMenu}
438354
size={"small"}>
439355
<MoreVert/>
@@ -462,6 +378,13 @@ function ArrayItemOptions({
462378
</Box>;
463379
}
464380

465-
function getRandomId() {
381+
function arrayMove(value: any[], sourceIndex: number, destinationIndex: number) {
382+
const result = Array.from(value);
383+
const [removed] = result.splice(sourceIndex, 1);
384+
result.splice(destinationIndex, 0, removed);
385+
return result;
386+
}
387+
388+
export function getRandomId() {
466389
return Math.floor(Math.random() * Math.floor(Number.MAX_SAFE_INTEGER));
467390
}

0 commit comments

Comments
 (0)