@axinom/mosaic-ui 0.35.0-rc.1 → 0.35.0-rc.10
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/components/List/ListRow/ListRow.d.ts.map +1 -1
- package/dist/components/List/useColumnsSize.d.ts +1 -0
- package/dist/components/List/useColumnsSize.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/FormElements/BooleanView/BooleanViewField.scss +2 -2
- package/src/components/FormStation/FormStation.scss +4 -0
- package/src/components/FormStation/FormStation.tsx +2 -2
- package/src/components/List/List.tsx +1 -1
- package/src/components/List/ListRow/ListRow.tsx +56 -50
- package/src/components/List/ListRow/Renderers/BooleanDotRenderer/BooleanDotRenderer.scss +6 -9
- package/src/components/List/ListRow/Renderers/BooleanDotRenderer/BooleanDotRenderer.spec.tsx +2 -2
- package/src/components/List/ListRow/Renderers/BooleanDotRenderer/BooleanDotRenderer.tsx +1 -1
- package/src/components/List/useColumnsSize.ts +20 -3
- package/src/styles/variables.scss +0 -5
- package/src/components/DynamicDataList/DynamicDataList.reposition.spec.tsx +0 -816
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
2
|
import React, { PropsWithChildren, useEffect, useState } from 'react';
|
|
3
|
+
import { DraggableProvided } from 'react-beautiful-dnd';
|
|
3
4
|
import { noop } from '../../../helpers/utils';
|
|
4
5
|
import { Data } from '../../../types/data';
|
|
5
6
|
import { getTooltipText } from '../../../utils/ToolTipHelpers';
|
|
@@ -7,10 +8,7 @@ import { ActionData } from '../../Actions';
|
|
|
7
8
|
import { Button, ButtonContext } from '../../Buttons';
|
|
8
9
|
import { IconName, Icons } from '../../Icons';
|
|
9
10
|
import { InlineMenu } from '../../InlineMenu';
|
|
10
|
-
import {
|
|
11
|
-
DynamicListColumn,
|
|
12
|
-
DynamicListElevationOptions,
|
|
13
|
-
} from '../DynamicDataList.model';
|
|
11
|
+
import { DynamicListColumn } from '../DynamicDataList.model';
|
|
14
12
|
import classes from './DynamicListRow.scss';
|
|
15
13
|
|
|
16
14
|
// TODO: Add sizing for DragIcon and Input container. Similar to ActionButton sizing
|
|
@@ -34,8 +32,6 @@ export interface DynamicListRowProps<T extends Data> {
|
|
|
34
32
|
verticalTextAlign?: 'start' | 'center' | 'end';
|
|
35
33
|
/** If set to true, the remove action button will be rendered (default: undefined) */
|
|
36
34
|
allowRemove?: boolean;
|
|
37
|
-
/** If set to true, rows can be repositioned using the input field (default: undefined) */
|
|
38
|
-
allowReordering?: boolean;
|
|
39
35
|
/** Property name that is used to determine data position (default: undefined) */
|
|
40
36
|
positionKey?: keyof T;
|
|
41
37
|
/** If set to true, this component can be dragged (default: undefined) */
|
|
@@ -44,16 +40,10 @@ export interface DynamicListRowProps<T extends Data> {
|
|
|
44
40
|
dragging?: boolean;
|
|
45
41
|
/** Whether or not the DDL is disabled (default: false) */
|
|
46
42
|
disabled?: boolean;
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
e: React.DragEvent<HTMLDivElement>,
|
|
52
|
-
newPosition: number,
|
|
53
|
-
elevation: DynamicListElevationOptions,
|
|
54
|
-
) => void;
|
|
55
|
-
/** Emits when row dragging event ends. Event and data as supplied as parameters */
|
|
56
|
-
onDragEnd?: (e: React.DragEvent<HTMLDivElement>) => void;
|
|
43
|
+
/** If set to true, the position column will be shown (default: false) */
|
|
44
|
+
showPositionColumn?: boolean;
|
|
45
|
+
/** If set to true, the action column will be shown (default: false) */
|
|
46
|
+
showActionColumn?: boolean;
|
|
57
47
|
/** Emits when the position input has changed. Row data and the new position is supplied */
|
|
58
48
|
onPositionInputChanged?: (
|
|
59
49
|
currenPosition: number,
|
|
@@ -65,7 +55,9 @@ export interface DynamicListRowProps<T extends Data> {
|
|
|
65
55
|
className?: string;
|
|
66
56
|
/** CSS Class name provider for each row. Allows row style to be determined by data */
|
|
67
57
|
rowClassNameProvider?: (data: T) => string;
|
|
58
|
+
/** Provide inline actions which are available through '...' context menu */
|
|
68
59
|
inlineMenuActions?: (data: T) => ActionData[];
|
|
60
|
+
provided?: DraggableProvided;
|
|
69
61
|
}
|
|
70
62
|
|
|
71
63
|
export const DynamicListRow = <T extends Data>({
|
|
@@ -78,26 +70,26 @@ export const DynamicListRow = <T extends Data>({
|
|
|
78
70
|
horizontalTextAlign,
|
|
79
71
|
verticalTextAlign,
|
|
80
72
|
allowRemove,
|
|
81
|
-
allowReordering,
|
|
82
73
|
positionKey,
|
|
83
74
|
allowDragging,
|
|
84
|
-
dragging,
|
|
85
75
|
disabled,
|
|
86
|
-
|
|
87
|
-
onDrop = noop,
|
|
88
|
-
onDragEnd = noop,
|
|
76
|
+
provided,
|
|
89
77
|
onPositionInputChanged = noop,
|
|
90
78
|
onActionClicked = noop,
|
|
91
79
|
className = '',
|
|
92
80
|
rowClassNameProvider,
|
|
93
81
|
inlineMenuActions,
|
|
82
|
+
dragging = false,
|
|
83
|
+
showPositionColumn = false,
|
|
84
|
+
showActionColumn = false,
|
|
94
85
|
}: PropsWithChildren<DynamicListRowProps<T>>): JSX.Element => {
|
|
95
86
|
const customStyles = {
|
|
96
|
-
gridAutoRows: rowHeight
|
|
87
|
+
gridAutoRows: `minmax(50px, ${rowHeight})`,
|
|
97
88
|
gridTemplateColumns: columnSizes,
|
|
98
89
|
gridColumnGap: columnGap,
|
|
99
90
|
justifyItems: horizontalTextAlign,
|
|
100
91
|
alignItems: verticalTextAlign,
|
|
92
|
+
...provided?.draggableProps.style,
|
|
101
93
|
} as React.CSSProperties;
|
|
102
94
|
|
|
103
95
|
const inlineMenuData = inlineMenuActions?.(data);
|
|
@@ -110,14 +102,6 @@ export const DynamicListRow = <T extends Data>({
|
|
|
110
102
|
setPosition(positionKey ? Number(data[positionKey]) : undefined);
|
|
111
103
|
}, [data, positionKey]);
|
|
112
104
|
|
|
113
|
-
/* Resources used
|
|
114
|
-
https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
|
|
115
|
-
https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/dropEffect
|
|
116
|
-
https://www.youtube.com/watch?v=jfYWwQrtzzY
|
|
117
|
-
https://www.youtube.com/watch?v=-MfTv5VRM0A
|
|
118
|
-
*/
|
|
119
|
-
const [draggedOver, setDraggedOver] = useState<DynamicListElevationOptions>();
|
|
120
|
-
|
|
121
105
|
/** Emits current position and new position when user changes position via input */
|
|
122
106
|
const onPositionInputChangedHandler = (value: number): void => {
|
|
123
107
|
if (positionKey !== undefined && typeof value === 'number') {
|
|
@@ -133,120 +117,49 @@ export const DynamicListRow = <T extends Data>({
|
|
|
133
117
|
}
|
|
134
118
|
};
|
|
135
119
|
|
|
136
|
-
// Dragging has started(only emits if this is the element being dragged)
|
|
137
|
-
const onDragStartHandler = (e: React.DragEvent<HTMLDivElement>): void => {
|
|
138
|
-
e.stopPropagation();
|
|
139
|
-
e.dataTransfer.effectAllowed = 'move'; // enable moving effects(for visual feedback)
|
|
140
|
-
onDragStart(e, Number(position));
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// An element that is being dragged has entered the drop zone
|
|
144
|
-
const onDragEnterHandler = (e: React.DragEvent<HTMLDivElement>): void => {
|
|
145
|
-
e.preventDefault();
|
|
146
|
-
e.stopPropagation();
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// Dragged element is currently over the drop zone
|
|
150
|
-
const onDragOverHandler = (e: React.DragEvent<HTMLDivElement>): void => {
|
|
151
|
-
e.preventDefault();
|
|
152
|
-
e.stopPropagation();
|
|
153
|
-
e.dataTransfer.dropEffect = 'move'; // change cursor
|
|
154
|
-
|
|
155
|
-
const elevation = getElevation(
|
|
156
|
-
e.clientY,
|
|
157
|
-
e.currentTarget.getBoundingClientRect(),
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
setDraggedOver(elevation);
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// Element has dropped(only emits if this was the element being dragged)
|
|
164
|
-
const onDropHandler = (e: React.DragEvent<HTMLDivElement>): void => {
|
|
165
|
-
e.preventDefault();
|
|
166
|
-
e.stopPropagation();
|
|
167
|
-
|
|
168
|
-
const elevation = getElevation(
|
|
169
|
-
e.clientY,
|
|
170
|
-
e.currentTarget.getBoundingClientRect(),
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
onDrop(e, Number(position), elevation);
|
|
174
|
-
|
|
175
|
-
setDraggedOver(undefined); // remove dragged over styling
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// An element that is being dragged has left the drop zone
|
|
179
|
-
const onDragLeaveHandler = (e: React.DragEvent<HTMLDivElement>): void => {
|
|
180
|
-
e.preventDefault();
|
|
181
|
-
e.stopPropagation();
|
|
182
|
-
setDraggedOver(undefined); // remove dragged over styling
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
// Dragging has ended(only emits if this was the element being dragged)
|
|
186
|
-
const onDragEndhandler = (e: React.DragEvent<HTMLDivElement>): void => {
|
|
187
|
-
e.preventDefault();
|
|
188
|
-
e.stopPropagation();
|
|
189
|
-
onDragEnd(e);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
120
|
return (
|
|
193
121
|
<div
|
|
194
122
|
className={clsx(
|
|
195
123
|
classes.container,
|
|
196
|
-
{
|
|
197
|
-
[classes.draggedAbove]: draggedOver === 'above',
|
|
198
|
-
[classes.draggedBelow]: draggedOver === 'below',
|
|
199
|
-
[classes.dragging]: dragging,
|
|
200
|
-
[classes.disabled]: disabled,
|
|
201
|
-
},
|
|
124
|
+
{ [classes.dragging]: dragging },
|
|
202
125
|
'dynamic-list-row-container',
|
|
203
126
|
className,
|
|
204
127
|
rowClassNameProvider ? rowClassNameProvider(data) : '',
|
|
205
128
|
)}
|
|
206
|
-
style={customStyles}
|
|
207
|
-
draggable={Boolean(!disabled && allowDragging && allowReordering)}
|
|
208
|
-
onDragStart={onDragStartHandler}
|
|
209
|
-
onDragEnter={onDragEnterHandler}
|
|
210
|
-
onDragOver={onDragOverHandler}
|
|
211
|
-
onDrop={onDropHandler}
|
|
212
|
-
onDragLeave={onDragLeaveHandler}
|
|
213
|
-
onDragEnd={onDragEndhandler}
|
|
214
129
|
data-test-id="dynamic-list-row"
|
|
130
|
+
ref={provided?.innerRef}
|
|
131
|
+
{...provided?.draggableProps}
|
|
132
|
+
style={customStyles}
|
|
215
133
|
>
|
|
216
|
-
{
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
</div>
|
|
224
|
-
)}
|
|
134
|
+
{showPositionColumn && (
|
|
135
|
+
<div className={classes.position}>
|
|
136
|
+
{allowDragging && (
|
|
137
|
+
<div className={classes.draggable} {...provided?.dragHandleProps}>
|
|
138
|
+
<Icons icon={IconName.Drag} className={classes.dragIcon} />
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
225
141
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
setPosition(Number(event.target.value));
|
|
236
|
-
}
|
|
237
|
-
}}
|
|
238
|
-
value={position}
|
|
239
|
-
disabled={disabled}
|
|
240
|
-
onKeyDown={(event) =>
|
|
241
|
-
event.key === 'Enter' &&
|
|
242
|
-
onPositionInputChangedHandler(
|
|
243
|
-
Number(event.currentTarget.value),
|
|
244
|
-
)
|
|
142
|
+
<div className={classes.input}>
|
|
143
|
+
<input
|
|
144
|
+
type="text"
|
|
145
|
+
onChange={(event) => {
|
|
146
|
+
// check for valid number
|
|
147
|
+
if (Number.isNaN(Number(event.target.value))) {
|
|
148
|
+
return;
|
|
149
|
+
} else {
|
|
150
|
+
setPosition(Number(event.target.value));
|
|
245
151
|
}
|
|
246
|
-
|
|
247
|
-
|
|
152
|
+
}}
|
|
153
|
+
value={position}
|
|
154
|
+
disabled={disabled}
|
|
155
|
+
onKeyDown={(event) =>
|
|
156
|
+
event.key === 'Enter' &&
|
|
157
|
+
onPositionInputChangedHandler(Number(event.currentTarget.value))
|
|
158
|
+
}
|
|
159
|
+
/>
|
|
248
160
|
</div>
|
|
249
|
-
|
|
161
|
+
</div>
|
|
162
|
+
)}
|
|
250
163
|
{columns.map((column: DynamicListColumn<T>) => {
|
|
251
164
|
const columnData: React.ReactNode = renderData<T>(
|
|
252
165
|
column,
|
|
@@ -271,7 +184,7 @@ export const DynamicListRow = <T extends Data>({
|
|
|
271
184
|
</div>
|
|
272
185
|
);
|
|
273
186
|
})}
|
|
274
|
-
{
|
|
187
|
+
{showActionColumn && (
|
|
275
188
|
<div className={classes.actionButtonContainer}>
|
|
276
189
|
{inlineMenuData && !!inlineMenuData.length && (
|
|
277
190
|
<InlineMenu
|
|
@@ -302,13 +215,6 @@ export const DynamicListRow = <T extends Data>({
|
|
|
302
215
|
);
|
|
303
216
|
};
|
|
304
217
|
|
|
305
|
-
const getElevation = (
|
|
306
|
-
clientY: number,
|
|
307
|
-
box: DOMRect,
|
|
308
|
-
): DynamicListElevationOptions => {
|
|
309
|
-
return clientY - box.top - box.height / 2 < 0 ? 'above' : 'below';
|
|
310
|
-
};
|
|
311
|
-
|
|
312
218
|
const renderData = function <T extends Data>(
|
|
313
219
|
column: DynamicListColumn<T>,
|
|
314
220
|
data: T,
|
package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.actions.spec.ts
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { addItem, removeItem } from './DynamicListReducer.actions';
|
|
2
|
+
|
|
3
|
+
interface Data {
|
|
4
|
+
position?: number;
|
|
5
|
+
id: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const dataSet: Data[] = [
|
|
9
|
+
{ position: 1, id: 1 },
|
|
10
|
+
{ position: 2, id: 2 },
|
|
11
|
+
{ position: 5, id: 5 },
|
|
12
|
+
{ position: 6, id: 6 },
|
|
13
|
+
{ position: 7, id: 7 },
|
|
14
|
+
{ position: 8, id: 8 },
|
|
15
|
+
{ position: 20, id: 20 },
|
|
16
|
+
{ position: 100, id: 100 },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const addItemCases = [
|
|
20
|
+
{
|
|
21
|
+
title: 'item at the beginning',
|
|
22
|
+
item: { position: 1, id: 999 },
|
|
23
|
+
result: [
|
|
24
|
+
{ position: 1, id: 999 },
|
|
25
|
+
{ position: 2, id: 1 },
|
|
26
|
+
{ position: 3, id: 2 },
|
|
27
|
+
{ position: 5, id: 5 },
|
|
28
|
+
{ position: 6, id: 6 },
|
|
29
|
+
{ position: 7, id: 7 },
|
|
30
|
+
{ position: 8, id: 8 },
|
|
31
|
+
{ position: 20, id: 20 },
|
|
32
|
+
{ position: 100, id: 100 },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
title: 'item before a sequence',
|
|
37
|
+
item: { position: 4, id: 999 },
|
|
38
|
+
result: [
|
|
39
|
+
{ position: 4, id: 999 },
|
|
40
|
+
{ position: 1, id: 1 },
|
|
41
|
+
{ position: 2, id: 2 },
|
|
42
|
+
{ position: 5, id: 5 },
|
|
43
|
+
{ position: 6, id: 6 },
|
|
44
|
+
{ position: 7, id: 7 },
|
|
45
|
+
{ position: 8, id: 8 },
|
|
46
|
+
{ position: 20, id: 20 },
|
|
47
|
+
{ position: 100, id: 100 },
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
title: 'item in the beginning of a sequence',
|
|
52
|
+
item: { position: 5, id: 999 },
|
|
53
|
+
result: [
|
|
54
|
+
{ position: 5, id: 999 },
|
|
55
|
+
{ position: 1, id: 1 },
|
|
56
|
+
{ position: 2, id: 2 },
|
|
57
|
+
{ position: 6, id: 5 },
|
|
58
|
+
{ position: 7, id: 6 },
|
|
59
|
+
{ position: 8, id: 7 },
|
|
60
|
+
{ position: 9, id: 8 },
|
|
61
|
+
{ position: 20, id: 20 },
|
|
62
|
+
{ position: 100, id: 100 },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
title: 'item in the middle of a sequence',
|
|
67
|
+
item: { position: 6, id: 999 },
|
|
68
|
+
result: [
|
|
69
|
+
{ position: 6, id: 999 },
|
|
70
|
+
{ position: 1, id: 1 },
|
|
71
|
+
{ position: 2, id: 2 },
|
|
72
|
+
{ position: 5, id: 5 },
|
|
73
|
+
{ position: 7, id: 6 },
|
|
74
|
+
{ position: 8, id: 7 },
|
|
75
|
+
{ position: 9, id: 8 },
|
|
76
|
+
{ position: 20, id: 20 },
|
|
77
|
+
{ position: 100, id: 100 },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
title: 'item before the end of a sequence',
|
|
82
|
+
item: { position: 8, id: 999 },
|
|
83
|
+
result: [
|
|
84
|
+
{ position: 8, id: 999 },
|
|
85
|
+
{ position: 1, id: 1 },
|
|
86
|
+
{ position: 2, id: 2 },
|
|
87
|
+
{ position: 5, id: 5 },
|
|
88
|
+
{ position: 6, id: 6 },
|
|
89
|
+
{ position: 7, id: 7 },
|
|
90
|
+
{ position: 9, id: 8 },
|
|
91
|
+
{ position: 20, id: 20 },
|
|
92
|
+
{ position: 100, id: 100 },
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
title: 'item at the end of a sequence',
|
|
97
|
+
item: { position: 9, id: 999 },
|
|
98
|
+
result: [
|
|
99
|
+
{ position: 9, id: 999 },
|
|
100
|
+
{ position: 1, id: 1 },
|
|
101
|
+
{ position: 2, id: 2 },
|
|
102
|
+
{ position: 5, id: 5 },
|
|
103
|
+
{ position: 6, id: 6 },
|
|
104
|
+
{ position: 7, id: 7 },
|
|
105
|
+
{ position: 8, id: 8 },
|
|
106
|
+
{ position: 20, id: 20 },
|
|
107
|
+
{ position: 100, id: 100 },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
title: 'item without a sequence',
|
|
112
|
+
item: { position: 50, id: 999 },
|
|
113
|
+
result: [
|
|
114
|
+
{ position: 50, id: 999 },
|
|
115
|
+
{ position: 1, id: 1 },
|
|
116
|
+
{ position: 2, id: 2 },
|
|
117
|
+
{ position: 5, id: 5 },
|
|
118
|
+
{ position: 6, id: 6 },
|
|
119
|
+
{ position: 7, id: 7 },
|
|
120
|
+
{ position: 8, id: 8 },
|
|
121
|
+
{ position: 20, id: 20 },
|
|
122
|
+
{ position: 100, id: 100 },
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
title: 'item at the end',
|
|
127
|
+
item: { position: 150, id: 999 },
|
|
128
|
+
result: [
|
|
129
|
+
{ position: 150, id: 999 },
|
|
130
|
+
{ position: 1, id: 1 },
|
|
131
|
+
{ position: 2, id: 2 },
|
|
132
|
+
{ position: 5, id: 5 },
|
|
133
|
+
{ position: 6, id: 6 },
|
|
134
|
+
{ position: 7, id: 7 },
|
|
135
|
+
{ position: 8, id: 8 },
|
|
136
|
+
{ position: 20, id: 20 },
|
|
137
|
+
{ position: 100, id: 100 },
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
const removeItemCases = [
|
|
143
|
+
{
|
|
144
|
+
title: 'item at the beginning',
|
|
145
|
+
item: dataSet[0],
|
|
146
|
+
result: [
|
|
147
|
+
{ position: 1, id: 2 },
|
|
148
|
+
{ position: 5, id: 5 },
|
|
149
|
+
{ position: 6, id: 6 },
|
|
150
|
+
{ position: 7, id: 7 },
|
|
151
|
+
{ position: 8, id: 8 },
|
|
152
|
+
{ position: 20, id: 20 },
|
|
153
|
+
{ position: 100, id: 100 },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
title: 'item before a sequence',
|
|
158
|
+
item: dataSet[1],
|
|
159
|
+
result: [
|
|
160
|
+
{ position: 1, id: 1 },
|
|
161
|
+
{ position: 5, id: 5 },
|
|
162
|
+
{ position: 6, id: 6 },
|
|
163
|
+
{ position: 7, id: 7 },
|
|
164
|
+
{ position: 8, id: 8 },
|
|
165
|
+
{ position: 20, id: 20 },
|
|
166
|
+
{ position: 100, id: 100 },
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
title: 'item in the beginning of a sequence',
|
|
171
|
+
item: dataSet[2],
|
|
172
|
+
result: [
|
|
173
|
+
{ position: 1, id: 1 },
|
|
174
|
+
{ position: 2, id: 2 },
|
|
175
|
+
{ position: 5, id: 6 },
|
|
176
|
+
{ position: 6, id: 7 },
|
|
177
|
+
{ position: 7, id: 8 },
|
|
178
|
+
{ position: 20, id: 20 },
|
|
179
|
+
{ position: 100, id: 100 },
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
title: 'item in the middle of a sequence',
|
|
184
|
+
item: dataSet[3],
|
|
185
|
+
result: [
|
|
186
|
+
{ position: 1, id: 1 },
|
|
187
|
+
{ position: 2, id: 2 },
|
|
188
|
+
{ position: 5, id: 5 },
|
|
189
|
+
{ position: 6, id: 7 },
|
|
190
|
+
{ position: 7, id: 8 },
|
|
191
|
+
{ position: 20, id: 20 },
|
|
192
|
+
{ position: 100, id: 100 },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
title: 'item before the end of a sequence',
|
|
197
|
+
item: dataSet[4],
|
|
198
|
+
result: [
|
|
199
|
+
{ position: 1, id: 1 },
|
|
200
|
+
{ position: 2, id: 2 },
|
|
201
|
+
{ position: 5, id: 5 },
|
|
202
|
+
{ position: 6, id: 6 },
|
|
203
|
+
{ position: 7, id: 8 },
|
|
204
|
+
{ position: 20, id: 20 },
|
|
205
|
+
{ position: 100, id: 100 },
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
title: 'item at the end of a sequence',
|
|
210
|
+
item: dataSet[5],
|
|
211
|
+
result: [
|
|
212
|
+
{ position: 1, id: 1 },
|
|
213
|
+
{ position: 2, id: 2 },
|
|
214
|
+
{ position: 5, id: 5 },
|
|
215
|
+
{ position: 6, id: 6 },
|
|
216
|
+
{ position: 7, id: 7 },
|
|
217
|
+
{ position: 20, id: 20 },
|
|
218
|
+
{ position: 100, id: 100 },
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
title: 'item without a sequence',
|
|
223
|
+
item: dataSet[6],
|
|
224
|
+
result: [
|
|
225
|
+
{ position: 1, id: 1 },
|
|
226
|
+
{ position: 2, id: 2 },
|
|
227
|
+
{ position: 5, id: 5 },
|
|
228
|
+
{ position: 6, id: 6 },
|
|
229
|
+
{ position: 7, id: 7 },
|
|
230
|
+
{ position: 8, id: 8 },
|
|
231
|
+
{ position: 100, id: 100 },
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
title: 'item at the end',
|
|
236
|
+
item: dataSet[7],
|
|
237
|
+
result: [
|
|
238
|
+
{ position: 1, id: 1 },
|
|
239
|
+
{ position: 2, id: 2 },
|
|
240
|
+
{ position: 5, id: 5 },
|
|
241
|
+
{ position: 6, id: 6 },
|
|
242
|
+
{ position: 7, id: 7 },
|
|
243
|
+
{ position: 8, id: 8 },
|
|
244
|
+
{ position: 20, id: 20 },
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
describe('Dynamic List Reducer Actions', () => {
|
|
250
|
+
it('addItem adds item without position', () => {
|
|
251
|
+
expect(addItem({ id: 999 }, dataSet, undefined)).toStrictEqual([
|
|
252
|
+
...dataSet,
|
|
253
|
+
{ id: 999 },
|
|
254
|
+
]);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it.each(addItemCases)('addItem adds $title', ({ item, result }) => {
|
|
258
|
+
expect(addItem(item, dataSet, 'position')).toStrictEqual(result);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('removeItem removes item without position', () => {
|
|
262
|
+
expect(removeItem(dataSet[3], dataSet, undefined)).toStrictEqual([
|
|
263
|
+
{ position: 1, id: 1 },
|
|
264
|
+
{ position: 2, id: 2 },
|
|
265
|
+
{ position: 5, id: 5 },
|
|
266
|
+
{ position: 7, id: 7 },
|
|
267
|
+
{ position: 8, id: 8 },
|
|
268
|
+
{ position: 20, id: 20 },
|
|
269
|
+
{ position: 100, id: 100 },
|
|
270
|
+
]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it.each(removeItemCases)('removeItem removes $title', ({ item, result }) => {
|
|
274
|
+
expect(removeItem(item, dataSet, 'position')).toStrictEqual(result);
|
|
275
|
+
});
|
|
276
|
+
});
|
package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.actions.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Data } from '../../../../types';
|
|
2
|
+
import { DynamicListReducerState } from './DynamicListReducer.types';
|
|
3
|
+
|
|
4
|
+
export const addItem = <T extends Data>(
|
|
5
|
+
item: T,
|
|
6
|
+
items: DynamicListReducerState<T>['items'],
|
|
7
|
+
positionPropertyName: DynamicListReducerState<T>['positionPropertyName'],
|
|
8
|
+
): T[] => {
|
|
9
|
+
let newValue: T[] = [];
|
|
10
|
+
if (positionPropertyName) {
|
|
11
|
+
// We can safely push this item anywhere in the array as long as the reducer re-arranges the entire array based on positions
|
|
12
|
+
newValue.push(item);
|
|
13
|
+
|
|
14
|
+
items.forEach((value) => {
|
|
15
|
+
if (value[positionPropertyName] < item[positionPropertyName]) {
|
|
16
|
+
// Items before the new item
|
|
17
|
+
return newValue.push(value);
|
|
18
|
+
} else if (value[positionPropertyName] === item[positionPropertyName]) {
|
|
19
|
+
// If an item already exists in the same position, we need to increment the position of the existing item
|
|
20
|
+
newValue.push({
|
|
21
|
+
...value,
|
|
22
|
+
[positionPropertyName]: value[positionPropertyName] + 1,
|
|
23
|
+
});
|
|
24
|
+
} else if (value[positionPropertyName] > item[positionPropertyName]) {
|
|
25
|
+
// Increment the rest of the items
|
|
26
|
+
if (newValue[newValue.length - 1].position === value.position) {
|
|
27
|
+
newValue.push({
|
|
28
|
+
...value,
|
|
29
|
+
[positionPropertyName]: value[positionPropertyName] + 1,
|
|
30
|
+
});
|
|
31
|
+
} else {
|
|
32
|
+
return newValue.push(value);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
} else {
|
|
37
|
+
newValue = [...items, item];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return newValue;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const removeItem = <T extends Data>(
|
|
44
|
+
item: T,
|
|
45
|
+
items: DynamicListReducerState<T>['items'],
|
|
46
|
+
positionPropertyName: DynamicListReducerState<T>['positionPropertyName'],
|
|
47
|
+
): T[] => {
|
|
48
|
+
let newValue: T[] = [];
|
|
49
|
+
if (positionPropertyName) {
|
|
50
|
+
let hasReachedGap = false;
|
|
51
|
+
|
|
52
|
+
items.forEach((value) => {
|
|
53
|
+
if (value[positionPropertyName] < item[positionPropertyName]) {
|
|
54
|
+
// Items before the removed item
|
|
55
|
+
return newValue.push(value);
|
|
56
|
+
} else if (value[positionPropertyName] > item[positionPropertyName]) {
|
|
57
|
+
// Check if a gap was reached
|
|
58
|
+
hasReachedGap =
|
|
59
|
+
hasReachedGap || isGap(items[items.indexOf(value) - 1], value);
|
|
60
|
+
|
|
61
|
+
// Items after the removed item
|
|
62
|
+
if (!hasReachedGap) {
|
|
63
|
+
return newValue.push({
|
|
64
|
+
...value,
|
|
65
|
+
[positionPropertyName]: value[positionPropertyName] - 1,
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
return newValue.push(value);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
newValue = items.filter((x) => x !== item);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return newValue;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const isGap = <T extends Data>(previousItem: T, nextItem: T): boolean => {
|
|
80
|
+
const previousPosition = previousItem?.position;
|
|
81
|
+
const nextPosition = nextItem?.position;
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
previousPosition && nextPosition && previousPosition + 1 < nextPosition
|
|
85
|
+
);
|
|
86
|
+
};
|