@axinom/mosaic-ui 0.35.0-rc.7 → 0.35.0-rc.9
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/DynamicDataList/DynamicDataList.d.ts +5 -14
- package/dist/components/DynamicDataList/DynamicDataList.d.ts.map +1 -1
- package/dist/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.d.ts +3 -1
- package/dist/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.d.ts.map +1 -1
- package/dist/components/DynamicDataList/DynamicListHeader/DynamicListHeader.d.ts +5 -1
- package/dist/components/DynamicDataList/DynamicListHeader/DynamicListHeader.d.ts.map +1 -1
- package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts +9 -10
- package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts.map +1 -1
- package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.actions.d.ts +5 -0
- package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.actions.d.ts.map +1 -0
- package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.d.ts +4 -0
- package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.d.ts.map +1 -0
- package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.init.d.ts +4 -0
- package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.init.d.ts.map +1 -0
- package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.types.d.ts +38 -0
- package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.types.d.ts.map +1 -0
- package/dist/components/DynamicDataList/helpers/DynamicListReducer/index.d.ts +4 -0
- package/dist/components/DynamicDataList/helpers/DynamicListReducer/index.d.ts.map +1 -0
- package/dist/components/DynamicDataList/helpers/generateId.d.ts +6 -0
- package/dist/components/DynamicDataList/helpers/generateId.d.ts.map +1 -0
- package/dist/components/DynamicDataList/helpers/useColumnDefs.d.ts +14 -0
- package/dist/components/DynamicDataList/helpers/useColumnDefs.d.ts.map +1 -0
- package/dist/components/DynamicDataList/helpers/useDataHandler.d.ts +9 -0
- package/dist/components/DynamicDataList/helpers/useDataHandler.d.ts.map +1 -0
- package/dist/components/DynamicDataList/helpers/useRowAnimation.d.ts +12 -0
- package/dist/components/DynamicDataList/helpers/useRowAnimation.d.ts.map +1 -0
- package/dist/components/DynamicDataList/index.d.ts +1 -1
- package/dist/components/DynamicDataList/index.d.ts.map +1 -1
- package/dist/index.es.js +11 -3
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/components/DynamicDataList/DynamicDataList.scss +0 -61
- package/src/components/DynamicDataList/DynamicDataList.spec.tsx +126 -393
- package/src/components/DynamicDataList/DynamicDataList.stories.tsx +0 -5
- package/src/components/DynamicDataList/DynamicDataList.tsx +133 -600
- package/src/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.scss +4 -2
- package/src/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.spec.tsx +17 -44
- package/src/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.tsx +15 -22
- package/src/components/DynamicDataList/DynamicListHeader/DynamicListHeader.scss +15 -10
- package/src/components/DynamicDataList/DynamicListHeader/DynamicListHeader.spec.tsx +4 -1
- package/src/components/DynamicDataList/DynamicListHeader/DynamicListHeader.tsx +16 -14
- package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.scss +16 -24
- package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.spec.tsx +26 -253
- package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.tsx +45 -139
- package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.actions.spec.ts +276 -0
- package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.actions.ts +86 -0
- package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.init.spec.ts +118 -0
- package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.init.ts +40 -0
- package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.spec.ts +89 -0
- package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.ts +42 -0
- package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.types.ts +46 -0
- package/src/components/DynamicDataList/helpers/DynamicListReducer/index.ts +3 -0
- package/src/components/DynamicDataList/helpers/generateId.ts +10 -0
- package/src/components/DynamicDataList/helpers/useColumnDefs.ts +56 -0
- package/src/components/DynamicDataList/helpers/useDataHandler.ts +77 -0
- package/src/components/DynamicDataList/helpers/useRowAnimation.tsx +38 -0
- package/src/components/DynamicDataList/index.ts +2 -2
- package/src/components/DynamicDataList/DynamicDataList.reposition.spec.tsx +0 -816
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
-
import React, { PropsWithChildren,
|
|
3
|
-
import {
|
|
2
|
+
import React, { PropsWithChildren, useCallback } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
DragDropContext,
|
|
5
|
+
Draggable,
|
|
6
|
+
DraggableProvided,
|
|
7
|
+
DropResult,
|
|
8
|
+
Droppable,
|
|
9
|
+
} from 'react-beautiful-dnd';
|
|
4
10
|
import { OptionalObjectSchema } from 'yup/lib/object';
|
|
5
11
|
import { noop } from '../../helpers/utils';
|
|
6
12
|
import { Data } from '../../types/data';
|
|
7
13
|
import { ActionData } from '../Actions';
|
|
8
14
|
import { ObjectSchemaDefinition } from '../FormStation';
|
|
9
|
-
import {
|
|
10
|
-
DynamicListColumn,
|
|
11
|
-
DynamicListElevationOptions,
|
|
12
|
-
} from './DynamicDataList.model';
|
|
15
|
+
import { DynamicListColumn } from './DynamicDataList.model';
|
|
13
16
|
import classes from './DynamicDataList.scss';
|
|
14
17
|
import {
|
|
15
18
|
DynamicListDataEntry,
|
|
@@ -17,6 +20,10 @@ import {
|
|
|
17
20
|
} from './DynamicListDataEntry/DynamicListDataEntry';
|
|
18
21
|
import { DynamicListHeader } from './DynamicListHeader/DynamicListHeader';
|
|
19
22
|
import { DynamicListRow } from './DynamicListRow/DynamicListRow';
|
|
23
|
+
import { uuid } from './helpers/generateId';
|
|
24
|
+
import { useColumnDefs } from './helpers/useColumnDefs';
|
|
25
|
+
import { useDataHandler } from './helpers/useDataHandler';
|
|
26
|
+
import { useRowAnimation } from './helpers/useRowAnimation';
|
|
20
27
|
|
|
21
28
|
export interface DynamicDataListProps<T extends Data> {
|
|
22
29
|
/**
|
|
@@ -34,7 +41,7 @@ export interface DynamicDataListProps<T extends Data> {
|
|
|
34
41
|
minimumWidth?: string;
|
|
35
42
|
/** Header row height */
|
|
36
43
|
headerRowHeight?: string;
|
|
37
|
-
/** List row height */
|
|
44
|
+
/** List row height (minimum height is 50px) */
|
|
38
45
|
listRowHeight?: string;
|
|
39
46
|
/** List row action button and checkbox size (default: '50px') */
|
|
40
47
|
listRowActionSize?: string;
|
|
@@ -44,15 +51,13 @@ export interface DynamicDataListProps<T extends Data> {
|
|
|
44
51
|
horizontalTextAlign?: 'left' | 'right' | 'center';
|
|
45
52
|
/** Vertical alignment of text */
|
|
46
53
|
verticalTextAlign?: 'start' | 'center' | 'end';
|
|
47
|
-
/** If set, indicates the component has an error and will display this prop as the error message (not implemented yet) */
|
|
48
|
-
error?: string | undefined;
|
|
49
54
|
/** Property that contains the value used in reordering the list (default: undefined) */
|
|
50
55
|
positionPropertyName?: keyof T;
|
|
51
56
|
/** If sets, sets the label for the position column (default: 'Position') */
|
|
52
57
|
positionLabel?: string;
|
|
53
58
|
/** Whether or not rows can be repositioned (default: true) */
|
|
54
59
|
allowReordering?: boolean;
|
|
55
|
-
/** Determines if the Add and Delete action buttons are rendered (default: true) */
|
|
60
|
+
/** @deprecated Determines if the Add and Delete action buttons are rendered (default: true) */
|
|
56
61
|
allowAddAndRemove?: boolean;
|
|
57
62
|
/** Determines if data rows can be dragged for repositioning (default: true) */
|
|
58
63
|
allowRowDragging?: boolean;
|
|
@@ -80,6 +85,7 @@ export interface DynamicDataListProps<T extends Data> {
|
|
|
80
85
|
rowClassNameProvider?: (data: T) => string;
|
|
81
86
|
/** Raised when the list has changed */
|
|
82
87
|
onChange?: (list: T[]) => void;
|
|
88
|
+
/** Provide inline actions which are available through '...' context menu */
|
|
83
89
|
inlineMenuActions?: (data: T) => ActionData[];
|
|
84
90
|
}
|
|
85
91
|
|
|
@@ -95,7 +101,6 @@ export interface DynamicDataListProps<T extends Data> {
|
|
|
95
101
|
export const DynamicDataList = <T extends Data>({
|
|
96
102
|
columns = [],
|
|
97
103
|
value = [],
|
|
98
|
-
error,
|
|
99
104
|
showHeader = true,
|
|
100
105
|
minimumWidth = '500px',
|
|
101
106
|
columnGap,
|
|
@@ -108,7 +113,6 @@ export const DynamicDataList = <T extends Data>({
|
|
|
108
113
|
positionPropertyName,
|
|
109
114
|
positionLabel,
|
|
110
115
|
allowReordering = true,
|
|
111
|
-
allowAddAndRemove = true,
|
|
112
116
|
allowRowDragging = true,
|
|
113
117
|
allowNewData = false,
|
|
114
118
|
customDataEntry: CustomDataEntry,
|
|
@@ -123,184 +127,130 @@ export const DynamicDataList = <T extends Data>({
|
|
|
123
127
|
rowClassNameProvider,
|
|
124
128
|
inlineMenuActions,
|
|
125
129
|
}: PropsWithChildren<DynamicDataListProps<T>>): JSX.Element => {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
// TODO: Error handling
|
|
138
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
139
|
-
const errorMsg: string | undefined = error;
|
|
140
|
-
|
|
141
|
-
const [list, setList] = useState<T[]>(value);
|
|
142
|
-
useEffect(() => {
|
|
143
|
-
if (positionPropertyName) {
|
|
144
|
-
value.sort(
|
|
145
|
-
(a, b) =>
|
|
146
|
-
Number(a[positionPropertyName]) - Number(b[positionPropertyName]),
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
setList([...value]); // Set list to new value. Mainly used by 'undo' to reset the list
|
|
150
|
-
}, [positionPropertyName, value]);
|
|
151
|
-
|
|
152
|
-
const [isDragging, setIsDragging] = useState<boolean>(false);
|
|
153
|
-
|
|
154
|
-
const [currentRowPosition, setCurrentRowPosition] = useState<number>(); // position of the row being dragged
|
|
155
|
-
|
|
156
|
-
const [shouldAnimate, setShouldAnimate] = useState<boolean>(false);
|
|
157
|
-
|
|
158
|
-
// Position for new data entry. defaults to 1 if list is empty
|
|
159
|
-
const { newDataPosition } = usePositions<T>(
|
|
160
|
-
list,
|
|
161
|
-
allowNewData,
|
|
162
|
-
positionKey.current,
|
|
163
|
-
);
|
|
130
|
+
const { columnSizes, showActionColumn, showPositionColumn } =
|
|
131
|
+
useColumnDefs<T>(
|
|
132
|
+
columns,
|
|
133
|
+
allowReordering,
|
|
134
|
+
allowNewData,
|
|
135
|
+
allowRowDragging,
|
|
136
|
+
positionPropertyName,
|
|
137
|
+
!!inlineMenuActions,
|
|
138
|
+
);
|
|
164
139
|
|
|
165
|
-
const { canAddItems } =
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
140
|
+
const { addItem, removeItem, canAddItems, items, nextPosition } =
|
|
141
|
+
useDataHandler(
|
|
142
|
+
value,
|
|
143
|
+
positionPropertyName,
|
|
144
|
+
maxItemLimit,
|
|
145
|
+
allowNewData,
|
|
146
|
+
onChange,
|
|
147
|
+
onAddTransformData,
|
|
148
|
+
);
|
|
171
149
|
|
|
172
150
|
const customStyles = {
|
|
173
151
|
gridRowGap: rowGap,
|
|
174
152
|
minWidth: minimumWidth,
|
|
175
153
|
} as React.CSSProperties;
|
|
176
154
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const onDragStartHandler = (
|
|
184
|
-
event: React.DragEvent<HTMLDivElement>,
|
|
185
|
-
position: number,
|
|
186
|
-
): void => {
|
|
187
|
-
setIsDragging(true);
|
|
188
|
-
if (positionKey.current !== undefined) {
|
|
189
|
-
setCurrentRowPosition(position);
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const onDropHandler = (
|
|
194
|
-
event: React.DragEvent<HTMLDivElement>,
|
|
195
|
-
newPosition: number,
|
|
196
|
-
elevation: DynamicListElevationOptions,
|
|
197
|
-
): void => {
|
|
198
|
-
// Exit if currentRowPosition is not set or if currentRowPosition is newPosition
|
|
199
|
-
if (
|
|
200
|
-
currentRowPosition !== undefined &&
|
|
201
|
-
currentRowPosition !== newPosition
|
|
202
|
-
) {
|
|
203
|
-
onRepositionHandler(currentRowPosition, newPosition, 'drag', elevation);
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
208
|
-
const onDragEndHandler = (event: React.DragEvent<HTMLDivElement>): void => {
|
|
209
|
-
// clean up dragging flow
|
|
210
|
-
setIsDragging(false);
|
|
211
|
-
setCurrentRowPosition(undefined); // reset position
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
// TODO: rows should expand in height with data... discuss this with team lead
|
|
215
|
-
|
|
216
|
-
const onRepositionHandler = (
|
|
217
|
-
currentRowPosition: number,
|
|
218
|
-
newPosition: number,
|
|
219
|
-
eventType: 'drag' | 'input',
|
|
220
|
-
elevation?: DynamicListElevationOptions,
|
|
221
|
-
): void => {
|
|
222
|
-
if (positionKey.current === undefined) {
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const posKey = positionKey.current;
|
|
227
|
-
|
|
228
|
-
setList(() => {
|
|
229
|
-
const newList = calculateNewList(
|
|
230
|
-
list,
|
|
231
|
-
posKey,
|
|
232
|
-
currentRowPosition,
|
|
233
|
-
newPosition,
|
|
234
|
-
eventType,
|
|
235
|
-
elevation,
|
|
236
|
-
);
|
|
155
|
+
const onPositionInputChangedHandler = useCallback(
|
|
156
|
+
(currentPosition: number, newPosition: number) => {
|
|
157
|
+
if (currentPosition !== newPosition && positionPropertyName) {
|
|
158
|
+
const oldItem = items.find(
|
|
159
|
+
(item) => item[positionPropertyName] === currentPosition,
|
|
160
|
+
);
|
|
237
161
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
162
|
+
if (oldItem) {
|
|
163
|
+
removeItem(oldItem);
|
|
164
|
+
addItem({ ...oldItem, [positionPropertyName]: newPosition });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
[addItem, items, positionPropertyName, removeItem],
|
|
169
|
+
);
|
|
242
170
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
*/
|
|
248
|
-
const onDeleteItemHandler = (data: T, idx: number): void => {
|
|
249
|
-
setList((prevState) => {
|
|
250
|
-
// if positioning is active
|
|
251
|
-
if (
|
|
252
|
-
positionKey.current !== undefined &&
|
|
253
|
-
(allowReordering || allowRowDragging)
|
|
254
|
-
) {
|
|
255
|
-
const newList = removeItemFromList(prevState, positionKey.current, idx);
|
|
171
|
+
const onDragEndHandler = useCallback(
|
|
172
|
+
(result: DropResult) => {
|
|
173
|
+
if (result.destination && positionPropertyName) {
|
|
174
|
+
const oldItem = items[result.source.index];
|
|
256
175
|
|
|
257
|
-
|
|
258
|
-
return newList;
|
|
176
|
+
const nextPosition = items[result.destination.index].position;
|
|
259
177
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
);
|
|
178
|
+
const newItem = {
|
|
179
|
+
...oldItem,
|
|
180
|
+
[positionPropertyName]: nextPosition,
|
|
181
|
+
};
|
|
265
182
|
|
|
266
|
-
|
|
267
|
-
|
|
183
|
+
removeItem(oldItem);
|
|
184
|
+
addItem(newItem);
|
|
268
185
|
}
|
|
269
|
-
}
|
|
270
|
-
|
|
186
|
+
},
|
|
187
|
+
[addItem, items, positionPropertyName, removeItem],
|
|
188
|
+
);
|
|
271
189
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
* @param data Row Data
|
|
275
|
-
*/
|
|
276
|
-
const onAddItemHandler = (data: T): void => {
|
|
277
|
-
setShouldAnimate(true);
|
|
278
|
-
setList((prevState) => {
|
|
279
|
-
const newList = [...prevState, onAddTransformData(data)];
|
|
190
|
+
const { AnimatedRow, setLastAddedItem, setShouldAnimate } =
|
|
191
|
+
useRowAnimation<T>();
|
|
280
192
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
193
|
+
const onAddItemHandler = useCallback(
|
|
194
|
+
(data: T) => {
|
|
195
|
+
addItem(data);
|
|
196
|
+
setLastAddedItem(data);
|
|
197
|
+
setShouldAnimate(true);
|
|
198
|
+
},
|
|
199
|
+
[addItem, setLastAddedItem, setShouldAnimate],
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const getRow = (
|
|
203
|
+
data: T,
|
|
204
|
+
idx: number,
|
|
205
|
+
provided?: DraggableProvided,
|
|
206
|
+
isDragging = false,
|
|
207
|
+
): JSX.Element => (
|
|
208
|
+
<AnimatedRow data={data} key={idx}>
|
|
209
|
+
<DynamicListRow<T>
|
|
210
|
+
key={idx}
|
|
211
|
+
columns={columns}
|
|
212
|
+
data={data}
|
|
213
|
+
columnSizes={columnSizes}
|
|
214
|
+
columnGap={columnGap}
|
|
215
|
+
rowHeight={listRowHeight}
|
|
216
|
+
actionSize={listRowActionSize}
|
|
217
|
+
horizontalTextAlign={horizontalTextAlign}
|
|
218
|
+
verticalTextAlign={verticalTextAlign}
|
|
219
|
+
allowRemove={allowNewData}
|
|
220
|
+
positionKey={positionPropertyName}
|
|
221
|
+
allowDragging={allowRowDragging}
|
|
222
|
+
onActionClicked={(data) => removeItem(data)}
|
|
223
|
+
onPositionInputChanged={onPositionInputChangedHandler}
|
|
224
|
+
inlineMenuActions={inlineMenuActions}
|
|
225
|
+
rowClassNameProvider={rowClassNameProvider}
|
|
226
|
+
disabled={disabled}
|
|
227
|
+
provided={provided}
|
|
228
|
+
dragging={isDragging}
|
|
229
|
+
showActionColumn={showActionColumn}
|
|
230
|
+
showPositionColumn={showPositionColumn}
|
|
231
|
+
/>
|
|
232
|
+
</AnimatedRow>
|
|
233
|
+
);
|
|
285
234
|
|
|
286
235
|
const DynamicListDataEntryProps: DynamicListDataEntryProps<T> = {
|
|
287
236
|
columns,
|
|
288
237
|
columnSizes,
|
|
289
238
|
columnGap,
|
|
290
|
-
positionKey:
|
|
239
|
+
positionKey: positionPropertyName,
|
|
291
240
|
rowHeight: listRowHeight,
|
|
292
241
|
actionSize: listRowActionSize,
|
|
293
242
|
horizontalTextAlign,
|
|
294
243
|
verticalTextAlign,
|
|
295
|
-
allowAdd:
|
|
244
|
+
allowAdd: allowNewData,
|
|
296
245
|
allowReordering,
|
|
297
246
|
allowDragging: allowRowDragging,
|
|
298
|
-
newDataPosition,
|
|
247
|
+
newDataPosition: nextPosition,
|
|
299
248
|
rowValidationSchema,
|
|
300
249
|
defaultValuesForNewData,
|
|
301
250
|
sticky: stickyHeader,
|
|
302
251
|
showHeader,
|
|
303
252
|
disabled,
|
|
253
|
+
showPositionColumn,
|
|
304
254
|
onActionClicked: onAddItemHandler,
|
|
305
255
|
};
|
|
306
256
|
|
|
@@ -323,12 +273,12 @@ export const DynamicDataList = <T extends Data>({
|
|
|
323
273
|
rowHeight={headerRowHeight}
|
|
324
274
|
horizontalTextAlign={horizontalTextAlign}
|
|
325
275
|
verticalTextAlign={verticalTextAlign}
|
|
326
|
-
allowReordering={
|
|
327
|
-
|
|
328
|
-
)}
|
|
329
|
-
positionKey={positionKey.current}
|
|
276
|
+
allowReordering={allowReordering}
|
|
277
|
+
positionKey={positionPropertyName}
|
|
330
278
|
positionLabel={positionLabel}
|
|
331
279
|
sticky={stickyHeader}
|
|
280
|
+
allowDragging={allowRowDragging}
|
|
281
|
+
showPositionColumn={showPositionColumn}
|
|
332
282
|
/>
|
|
333
283
|
)}
|
|
334
284
|
{canAddItems &&
|
|
@@ -337,443 +287,26 @@ export const DynamicDataList = <T extends Data>({
|
|
|
337
287
|
) : (
|
|
338
288
|
<DynamicListDataEntry<T> {...DynamicListDataEntryProps} />
|
|
339
289
|
))}
|
|
340
|
-
|
|
341
|
-
{
|
|
342
|
-
<
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
key={idx}
|
|
361
|
-
columns={columns}
|
|
362
|
-
data={item}
|
|
363
|
-
columnSizes={columnSizes}
|
|
364
|
-
columnGap={columnGap}
|
|
365
|
-
rowHeight={listRowHeight}
|
|
366
|
-
actionSize={listRowActionSize}
|
|
367
|
-
horizontalTextAlign={horizontalTextAlign}
|
|
368
|
-
verticalTextAlign={verticalTextAlign}
|
|
369
|
-
allowRemove={allowAddAndRemove}
|
|
370
|
-
allowReordering={allowReordering}
|
|
371
|
-
positionKey={positionKey.current}
|
|
372
|
-
allowDragging={allowRowDragging}
|
|
373
|
-
dragging={isDragging}
|
|
374
|
-
onDragStart={onDragStartHandler}
|
|
375
|
-
onDrop={onDropHandler}
|
|
376
|
-
onDragEnd={onDragEndHandler}
|
|
377
|
-
onActionClicked={(data) => onDeleteItemHandler(data, idx)}
|
|
378
|
-
onPositionInputChanged={(currentPosition, newPosition) =>
|
|
379
|
-
onRepositionHandler(currentPosition, newPosition, 'input')
|
|
380
|
-
}
|
|
381
|
-
inlineMenuActions={inlineMenuActions}
|
|
382
|
-
rowClassNameProvider={rowClassNameProvider}
|
|
383
|
-
disabled={disabled}
|
|
384
|
-
/>
|
|
385
|
-
</CSSTransition>
|
|
386
|
-
))}
|
|
387
|
-
</TransitionGroup>
|
|
290
|
+
{allowReordering ? (
|
|
291
|
+
<DragDropContext onDragEnd={onDragEndHandler}>
|
|
292
|
+
<Droppable droppableId={uuid()}>
|
|
293
|
+
{(provided) => (
|
|
294
|
+
<div ref={provided.innerRef} {...provided.droppableProps}>
|
|
295
|
+
{items.map((item: T, idx: number) => (
|
|
296
|
+
<Draggable draggableId={String(idx)} index={idx} key={idx}>
|
|
297
|
+
{(provided, snapshot) =>
|
|
298
|
+
getRow(item, idx, provided, snapshot.isDragging)
|
|
299
|
+
}
|
|
300
|
+
</Draggable>
|
|
301
|
+
))}
|
|
302
|
+
{provided.placeholder}
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
</Droppable>
|
|
306
|
+
</DragDropContext>
|
|
307
|
+
) : (
|
|
308
|
+
items.map((item: T, idx: number) => getRow(item, idx))
|
|
309
|
+
)}
|
|
388
310
|
</div>
|
|
389
311
|
);
|
|
390
312
|
};
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* Generates a combined string of all columns.columnSize values, to be used as CSS value
|
|
394
|
-
* Also produces property key which is used for positioning
|
|
395
|
-
* @param columns The list of columns that should be used
|
|
396
|
-
* @returns a string that consists of defined column sizes and a position key that maps to a data property
|
|
397
|
-
*/
|
|
398
|
-
const useColumnDefs = function <T extends Data>(
|
|
399
|
-
columns: DynamicListColumn<T>[],
|
|
400
|
-
allowReordering: boolean,
|
|
401
|
-
allowAddAndRemove: boolean,
|
|
402
|
-
allowRowDragging: boolean,
|
|
403
|
-
positionKey?: keyof T,
|
|
404
|
-
showInlineMenu?: boolean,
|
|
405
|
-
): {
|
|
406
|
-
readonly columnSizes: string;
|
|
407
|
-
} {
|
|
408
|
-
let positionColumn = '';
|
|
409
|
-
let actionColumn = '';
|
|
410
|
-
const columnDefinition = columns
|
|
411
|
-
.filter((columns) => columns.propertyName !== positionKey) // do not allow a column to be created for 'position'
|
|
412
|
-
.reduce((prev, current) => `${prev} ${current.size ?? '1fr'}`, '')
|
|
413
|
-
.trim();
|
|
414
|
-
|
|
415
|
-
if (allowReordering) {
|
|
416
|
-
if (allowRowDragging === true) {
|
|
417
|
-
positionColumn = '100px ';
|
|
418
|
-
} else {
|
|
419
|
-
positionColumn = '60px ';
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (allowAddAndRemove) {
|
|
424
|
-
actionColumn = ' 50px';
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
if (showInlineMenu) {
|
|
428
|
-
actionColumn = ' 50px';
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const columnSizes = `${positionColumn}${columnDefinition}${actionColumn}`;
|
|
432
|
-
|
|
433
|
-
return {
|
|
434
|
-
columnSizes,
|
|
435
|
-
} as const;
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Determines whether the the Data Entry row is shown
|
|
440
|
-
* @param allowNewData is new data is allowed
|
|
441
|
-
* @param allowAddAndRemove is the action button shown
|
|
442
|
-
* @param currentItemAmt current amout of items in the list
|
|
443
|
-
* @param maxItemAmtLimit maximum amount of items that be added
|
|
444
|
-
*/
|
|
445
|
-
const useCanAddItems = (
|
|
446
|
-
allowNewData: boolean,
|
|
447
|
-
allowAddAndRemove: boolean,
|
|
448
|
-
currentItemAmt: number,
|
|
449
|
-
maxItemAmtLimit?: number,
|
|
450
|
-
): {
|
|
451
|
-
readonly canAddItems: boolean;
|
|
452
|
-
} => {
|
|
453
|
-
const [canAddItems, setCanAddItems] = useState<boolean>(
|
|
454
|
-
allowNewData === true &&
|
|
455
|
-
allowAddAndRemove === true &&
|
|
456
|
-
maxItemAmtLimit === undefined
|
|
457
|
-
? true
|
|
458
|
-
: maxItemAmtLimit !== undefined && currentItemAmt < maxItemAmtLimit
|
|
459
|
-
? true
|
|
460
|
-
: false,
|
|
461
|
-
);
|
|
462
|
-
|
|
463
|
-
useEffect(() => {
|
|
464
|
-
if (
|
|
465
|
-
allowNewData === true &&
|
|
466
|
-
allowAddAndRemove === true &&
|
|
467
|
-
maxItemAmtLimit === undefined
|
|
468
|
-
) {
|
|
469
|
-
setCanAddItems(true);
|
|
470
|
-
} else if (
|
|
471
|
-
maxItemAmtLimit !== undefined &&
|
|
472
|
-
currentItemAmt < maxItemAmtLimit
|
|
473
|
-
) {
|
|
474
|
-
setCanAddItems(true);
|
|
475
|
-
} else {
|
|
476
|
-
setCanAddItems(false);
|
|
477
|
-
}
|
|
478
|
-
}, [allowNewData, allowAddAndRemove, currentItemAmt, maxItemAmtLimit]);
|
|
479
|
-
|
|
480
|
-
return {
|
|
481
|
-
canAddItems,
|
|
482
|
-
} as const;
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Returns position info from the list array
|
|
487
|
-
* @param list data array
|
|
488
|
-
* @param allowNewData whether or not new data can be entered
|
|
489
|
-
* @param positionKey key from data object that holds the position value
|
|
490
|
-
*/
|
|
491
|
-
const usePositions = function <T extends Data>(
|
|
492
|
-
list: T[],
|
|
493
|
-
allowNewData: boolean,
|
|
494
|
-
positionKey: keyof T | undefined,
|
|
495
|
-
): {
|
|
496
|
-
readonly firstPosition: number;
|
|
497
|
-
readonly lastPosition: number;
|
|
498
|
-
readonly newDataPosition: number;
|
|
499
|
-
} {
|
|
500
|
-
const [firstPosition, setFirstPosition] = useState<number>(
|
|
501
|
-
// find the first position in the list array or fall back to 1
|
|
502
|
-
allowNewData && list.length > 0 && positionKey !== undefined
|
|
503
|
-
? list[0][positionKey]
|
|
504
|
-
: 1,
|
|
505
|
-
);
|
|
506
|
-
const [lastPosition, setLastPostion] = useState<number>(
|
|
507
|
-
// find the last position in the list array or fall back to 1
|
|
508
|
-
allowNewData && list.length > 0 && positionKey !== undefined
|
|
509
|
-
? list.slice(-1)[0][positionKey]
|
|
510
|
-
: 1,
|
|
511
|
-
);
|
|
512
|
-
|
|
513
|
-
const [newDataPosition, setNewDataPosition] = useState<number>(
|
|
514
|
-
// set initial position to last item position + 1 or fall back to 1
|
|
515
|
-
allowNewData && list.length > 0 && positionKey !== undefined
|
|
516
|
-
? list.slice(-1)[0][positionKey] + 1
|
|
517
|
-
: 1,
|
|
518
|
-
);
|
|
519
|
-
|
|
520
|
-
useEffect(() => {
|
|
521
|
-
if (allowNewData && positionKey !== undefined) {
|
|
522
|
-
// if the list is empty, set all positions to 1
|
|
523
|
-
if (list.length === 0) {
|
|
524
|
-
setFirstPosition(1);
|
|
525
|
-
setLastPostion(1);
|
|
526
|
-
setNewDataPosition(1);
|
|
527
|
-
|
|
528
|
-
// list is not empty, find and set positions
|
|
529
|
-
} else {
|
|
530
|
-
const firstDataRow: T = list[0];
|
|
531
|
-
setFirstPosition(firstDataRow[positionKey]);
|
|
532
|
-
|
|
533
|
-
const lastDataRow: T = list.slice(-1)[0];
|
|
534
|
-
setLastPostion(lastDataRow[positionKey]);
|
|
535
|
-
|
|
536
|
-
setNewDataPosition(lastDataRow[positionKey] + 1);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}, [allowNewData, list, positionKey]);
|
|
540
|
-
|
|
541
|
-
return {
|
|
542
|
-
firstPosition,
|
|
543
|
-
lastPosition,
|
|
544
|
-
newDataPosition,
|
|
545
|
-
} as const;
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
export const calculateNewList = <T,>(
|
|
549
|
-
list: T[],
|
|
550
|
-
posKey: keyof T,
|
|
551
|
-
currentRowPosition: number,
|
|
552
|
-
newPosition: number,
|
|
553
|
-
eventType: 'drag' | 'input',
|
|
554
|
-
elevation?: DynamicListElevationOptions,
|
|
555
|
-
): T[] => {
|
|
556
|
-
let calculatedPos: number;
|
|
557
|
-
const allPositions: number[] = list.map((data) => Number(data[posKey]));
|
|
558
|
-
|
|
559
|
-
function findDragPosition(
|
|
560
|
-
newPos: number,
|
|
561
|
-
currentPos: number,
|
|
562
|
-
elevation?: DynamicListElevationOptions,
|
|
563
|
-
): number {
|
|
564
|
-
let moveDir: 'up' | 'down';
|
|
565
|
-
|
|
566
|
-
if (newPos < currentPos) {
|
|
567
|
-
moveDir = 'up';
|
|
568
|
-
} else {
|
|
569
|
-
moveDir = 'down';
|
|
570
|
-
}
|
|
571
|
-
const newPosIndex = allPositions.indexOf(newPos);
|
|
572
|
-
const prevPos = allPositions[newPosIndex - 1];
|
|
573
|
-
const nextPos = allPositions[newPosIndex + 1];
|
|
574
|
-
|
|
575
|
-
if (moveDir === 'up') {
|
|
576
|
-
// below the first row
|
|
577
|
-
if (prevPos === undefined && elevation === 'below') {
|
|
578
|
-
return allPositions[0] + 1;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// top of the list
|
|
582
|
-
if (prevPos === undefined) {
|
|
583
|
-
return 1;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// place it below the previous position
|
|
587
|
-
if (elevation === 'above') {
|
|
588
|
-
return prevPos + 1;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// place it below the new pos
|
|
592
|
-
if (elevation === 'below') {
|
|
593
|
-
// same position
|
|
594
|
-
if (currentPos === nextPos) {
|
|
595
|
-
return currentPos;
|
|
596
|
-
} else {
|
|
597
|
-
// move underneath new position
|
|
598
|
-
return allPositions[newPosIndex] + 1;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
} else {
|
|
602
|
-
// item placed in same position
|
|
603
|
-
if (prevPos === currentPos && elevation === 'above') {
|
|
604
|
-
return currentPos;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// place row in new position
|
|
608
|
-
if (elevation === 'above') {
|
|
609
|
-
return prevPos;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// place row in new position
|
|
613
|
-
if (elevation === 'below') {
|
|
614
|
-
return newPos;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// Fallback to the bottom of the list. This fallback is never meant to be reached.
|
|
619
|
-
return allPositions[allPositions.length - 1] ?? 1;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// item was moved via input
|
|
623
|
-
if (eventType === 'input') {
|
|
624
|
-
if (newPosition <= 0) {
|
|
625
|
-
calculatedPos = 1;
|
|
626
|
-
} else {
|
|
627
|
-
calculatedPos = newPosition;
|
|
628
|
-
}
|
|
629
|
-
// item was moved via drag
|
|
630
|
-
} else {
|
|
631
|
-
calculatedPos = findDragPosition(
|
|
632
|
-
newPosition,
|
|
633
|
-
currentRowPosition,
|
|
634
|
-
elevation,
|
|
635
|
-
);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// exit if the calculatedPos is also the current position
|
|
639
|
-
if (calculatedPos === currentRowPosition) {
|
|
640
|
-
return list;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
const tempItems: T[] = [];
|
|
644
|
-
|
|
645
|
-
// item was moved up
|
|
646
|
-
if (calculatedPos < currentRowPosition) {
|
|
647
|
-
let isAfterNewPosGap = false;
|
|
648
|
-
for (const item of list) {
|
|
649
|
-
// all items before the new row position
|
|
650
|
-
if (Number(item[posKey]) < calculatedPos) {
|
|
651
|
-
tempItems.push(item);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// increment the existing row in the new row position
|
|
655
|
-
else if (Number(item[posKey]) === calculatedPos) {
|
|
656
|
-
tempItems.push({ ...item, [posKey]: Number(item[posKey]) + 1 });
|
|
657
|
-
|
|
658
|
-
// all items between new position and current position
|
|
659
|
-
} else if (
|
|
660
|
-
Number(item[posKey]) > calculatedPos &&
|
|
661
|
-
Number(item[posKey]) < currentRowPosition
|
|
662
|
-
) {
|
|
663
|
-
// if the previous position is empty, don't increment anymore rows
|
|
664
|
-
if (!allPositions.includes(Number(item[posKey]) - 1)) {
|
|
665
|
-
isAfterNewPosGap = true;
|
|
666
|
-
}
|
|
667
|
-
if (!isAfterNewPosGap) {
|
|
668
|
-
tempItems.push({ ...item, [posKey]: Number(item[posKey]) + 1 });
|
|
669
|
-
} else {
|
|
670
|
-
tempItems.push(item);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// update current row being moved
|
|
674
|
-
} else if (Number(item[posKey]) === currentRowPosition) {
|
|
675
|
-
tempItems.push({ ...item, [posKey]: calculatedPos });
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// all items after currentRowPosition
|
|
679
|
-
else {
|
|
680
|
-
tempItems.push(item);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// item was moved down
|
|
685
|
-
} else {
|
|
686
|
-
let isGapBeforeNewPos = false;
|
|
687
|
-
const reverseList = list.slice().reverse();
|
|
688
|
-
|
|
689
|
-
// loop through the reversed list
|
|
690
|
-
for (const item of reverseList) {
|
|
691
|
-
// push all items after new position without mutations
|
|
692
|
-
if (Number(item[posKey]) > calculatedPos) {
|
|
693
|
-
tempItems.push(item);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// all items between current position and new position
|
|
697
|
-
else if (
|
|
698
|
-
Number(item[posKey]) <= calculatedPos &&
|
|
699
|
-
Number(item[posKey]) > currentRowPosition
|
|
700
|
-
) {
|
|
701
|
-
// if no gap is dectected, decrement item
|
|
702
|
-
if (!isGapBeforeNewPos) {
|
|
703
|
-
tempItems.push({ ...item, [posKey]: Number(item[posKey]) - 1 });
|
|
704
|
-
|
|
705
|
-
// if a gap was dectected, push the item without mutating
|
|
706
|
-
} else {
|
|
707
|
-
tempItems.push(item);
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
// check if the previous position is open
|
|
711
|
-
if (!allPositions.includes(Number(item[posKey]) - 1)) {
|
|
712
|
-
isGapBeforeNewPos = true;
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// update current row being moved
|
|
717
|
-
else if (Number(item[posKey]) === currentRowPosition) {
|
|
718
|
-
tempItems.push({ ...item, [posKey]: calculatedPos });
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// push all items before current row without mutations
|
|
722
|
-
else {
|
|
723
|
-
tempItems.push(item);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// IMPORTANT always sort the array by position key. All other logic expects the list to be sorted by position key if reordering is allowed.
|
|
729
|
-
return tempItems.sort((a, b) => Number(a[posKey]) - Number(b[posKey]));
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
/**
|
|
733
|
-
* Responsible for removing an item from the list and updating all subsequent item's positions
|
|
734
|
-
* @param list list array
|
|
735
|
-
* @param posKey position
|
|
736
|
-
* @param idx index of the item being removed
|
|
737
|
-
*/
|
|
738
|
-
export const removeItemFromList = <T,>(
|
|
739
|
-
list: T[],
|
|
740
|
-
posKey: keyof T,
|
|
741
|
-
idx: number,
|
|
742
|
-
): T[] => {
|
|
743
|
-
const allPositions: number[] = list.map((data) => Number(data[posKey]));
|
|
744
|
-
|
|
745
|
-
// position of the item removed
|
|
746
|
-
const removedPos = Number(list[idx][posKey]);
|
|
747
|
-
|
|
748
|
-
const listWithoutRemovedItem = list
|
|
749
|
-
.filter((item: T, index: number) => index !== idx)
|
|
750
|
-
.map((item) => item);
|
|
751
|
-
|
|
752
|
-
const tempItems: T[] = [];
|
|
753
|
-
|
|
754
|
-
let isGapReached = false;
|
|
755
|
-
for (const item of listWithoutRemovedItem) {
|
|
756
|
-
// push all items before the deleted row
|
|
757
|
-
if (Number(item[posKey]) < removedPos) {
|
|
758
|
-
tempItems.push(item);
|
|
759
|
-
|
|
760
|
-
// items after the deleted row
|
|
761
|
-
} else {
|
|
762
|
-
// if the position before is empty, a gap was reached
|
|
763
|
-
if (!allPositions.includes(Number(item[posKey]) - 1)) {
|
|
764
|
-
isGapReached = true;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// only continue decrementing subsequent items as long as no gap was reached
|
|
768
|
-
if (!isGapReached) {
|
|
769
|
-
tempItems.push({ ...item, [posKey]: Number(item[posKey]) - 1 });
|
|
770
|
-
// if a gap was reached, just push the rest of the items
|
|
771
|
-
} else {
|
|
772
|
-
tempItems.push(item);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// IMPORTANT always sort the array by position key. All other logic expects the list to be sorted by position key if reordering is allowed.
|
|
778
|
-
return tempItems.sort((a, b) => Number(a[posKey]) - Number(b[posKey]));
|
|
779
|
-
};
|