@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.
Files changed (60) hide show
  1. package/dist/components/DynamicDataList/DynamicDataList.d.ts +5 -14
  2. package/dist/components/DynamicDataList/DynamicDataList.d.ts.map +1 -1
  3. package/dist/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.d.ts +3 -1
  4. package/dist/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.d.ts.map +1 -1
  5. package/dist/components/DynamicDataList/DynamicListHeader/DynamicListHeader.d.ts +5 -1
  6. package/dist/components/DynamicDataList/DynamicListHeader/DynamicListHeader.d.ts.map +1 -1
  7. package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts +9 -10
  8. package/dist/components/DynamicDataList/DynamicListRow/DynamicListRow.d.ts.map +1 -1
  9. package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.actions.d.ts +5 -0
  10. package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.actions.d.ts.map +1 -0
  11. package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.d.ts +4 -0
  12. package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.d.ts.map +1 -0
  13. package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.init.d.ts +4 -0
  14. package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.init.d.ts.map +1 -0
  15. package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.types.d.ts +38 -0
  16. package/dist/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.types.d.ts.map +1 -0
  17. package/dist/components/DynamicDataList/helpers/DynamicListReducer/index.d.ts +4 -0
  18. package/dist/components/DynamicDataList/helpers/DynamicListReducer/index.d.ts.map +1 -0
  19. package/dist/components/DynamicDataList/helpers/generateId.d.ts +6 -0
  20. package/dist/components/DynamicDataList/helpers/generateId.d.ts.map +1 -0
  21. package/dist/components/DynamicDataList/helpers/useColumnDefs.d.ts +14 -0
  22. package/dist/components/DynamicDataList/helpers/useColumnDefs.d.ts.map +1 -0
  23. package/dist/components/DynamicDataList/helpers/useDataHandler.d.ts +9 -0
  24. package/dist/components/DynamicDataList/helpers/useDataHandler.d.ts.map +1 -0
  25. package/dist/components/DynamicDataList/helpers/useRowAnimation.d.ts +12 -0
  26. package/dist/components/DynamicDataList/helpers/useRowAnimation.d.ts.map +1 -0
  27. package/dist/components/DynamicDataList/index.d.ts +1 -1
  28. package/dist/components/DynamicDataList/index.d.ts.map +1 -1
  29. package/dist/index.es.js +11 -3
  30. package/dist/index.es.js.map +1 -1
  31. package/dist/index.js +11 -3
  32. package/dist/index.js.map +1 -1
  33. package/package.json +5 -3
  34. package/src/components/DynamicDataList/DynamicDataList.scss +0 -61
  35. package/src/components/DynamicDataList/DynamicDataList.spec.tsx +126 -393
  36. package/src/components/DynamicDataList/DynamicDataList.stories.tsx +0 -5
  37. package/src/components/DynamicDataList/DynamicDataList.tsx +133 -600
  38. package/src/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.scss +4 -2
  39. package/src/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.spec.tsx +17 -44
  40. package/src/components/DynamicDataList/DynamicListDataEntry/DynamicListDataEntry.tsx +15 -22
  41. package/src/components/DynamicDataList/DynamicListHeader/DynamicListHeader.scss +15 -10
  42. package/src/components/DynamicDataList/DynamicListHeader/DynamicListHeader.spec.tsx +4 -1
  43. package/src/components/DynamicDataList/DynamicListHeader/DynamicListHeader.tsx +16 -14
  44. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.scss +16 -24
  45. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.spec.tsx +26 -253
  46. package/src/components/DynamicDataList/DynamicListRow/DynamicListRow.tsx +45 -139
  47. package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.actions.spec.ts +276 -0
  48. package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.actions.ts +86 -0
  49. package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.init.spec.ts +118 -0
  50. package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.init.ts +40 -0
  51. package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.spec.ts +89 -0
  52. package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.ts +42 -0
  53. package/src/components/DynamicDataList/helpers/DynamicListReducer/DynamicListReducer.types.ts +46 -0
  54. package/src/components/DynamicDataList/helpers/DynamicListReducer/index.ts +3 -0
  55. package/src/components/DynamicDataList/helpers/generateId.ts +10 -0
  56. package/src/components/DynamicDataList/helpers/useColumnDefs.ts +56 -0
  57. package/src/components/DynamicDataList/helpers/useDataHandler.ts +77 -0
  58. package/src/components/DynamicDataList/helpers/useRowAnimation.tsx +38 -0
  59. package/src/components/DynamicDataList/index.ts +2 -2
  60. package/src/components/DynamicDataList/DynamicDataList.reposition.spec.tsx +0 -816
@@ -1,15 +1,18 @@
1
1
  import clsx from 'clsx';
2
- import React, { PropsWithChildren, useEffect, useRef, useState } from 'react';
3
- import { CSSTransition, TransitionGroup } from 'react-transition-group';
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 positionKey = useRef<keyof T | undefined>(positionPropertyName);
127
-
128
- const { columnSizes } = useColumnDefs<T>(
129
- columns,
130
- allowReordering,
131
- allowAddAndRemove,
132
- allowRowDragging,
133
- positionKey.current,
134
- !!inlineMenuActions,
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 } = useCanAddItems(
166
- allowNewData,
167
- allowAddAndRemove,
168
- list.length,
169
- maxItemLimit,
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
- /** Resources used for dragging:
178
- * https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
179
- * https://www.youtube.com/watch?v=jfYWwQrtzzY
180
- * https://www.youtube.com/watch?v=Q1PYQPK9TaM
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
- onChange(newList);
239
- return newList;
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
- * Removes an item from the list array using the item's index
245
- * @param data Row Data
246
- * @param idx index in list array
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
- onChange(newList);
258
- return newList;
176
+ const nextPosition = items[result.destination.index].position;
259
177
 
260
- // if there is no positioning, just return the list with the item removed
261
- } else {
262
- const newList = prevState.filter(
263
- (item: T, index: number) => index !== idx,
264
- );
178
+ const newItem = {
179
+ ...oldItem,
180
+ [positionPropertyName]: nextPosition,
181
+ };
265
182
 
266
- onChange(newList);
267
- return newList;
183
+ removeItem(oldItem);
184
+ addItem(newItem);
268
185
  }
269
- });
270
- };
186
+ },
187
+ [addItem, items, positionPropertyName, removeItem],
188
+ );
271
189
 
272
- /**
273
- * Adds an item to the list array. All new entries are placed at the end.
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
- onChange(newList);
282
- return newList;
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: positionKey.current,
239
+ positionKey: positionPropertyName,
291
240
  rowHeight: listRowHeight,
292
241
  actionSize: listRowActionSize,
293
242
  horizontalTextAlign,
294
243
  verticalTextAlign,
295
- allowAdd: allowAddAndRemove,
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={Boolean(
327
- allowReordering && positionKey.current !== undefined,
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
- <TransitionGroup component={null}>
341
- {list.map((item: T, idx: number) => (
342
- <CSSTransition
343
- key={idx}
344
- timeout={{ enter: 1000, exit: 10 }}
345
- classNames={{
346
- enter: clsx(shouldAnimate && classes.rowEnter),
347
- enterActive: clsx(shouldAnimate && classes.rowEnterActive),
348
- exit: classes.rowExit,
349
- exitActive: classes.rowExitActive,
350
- }}
351
- onEnter={(node: HTMLElement, _isAppearing: boolean) => {
352
- shouldAnimate &&
353
- node.scrollIntoView({ behavior: 'smooth', block: 'start' });
354
- }}
355
- onEntered={() => {
356
- setShouldAnimate(false);
357
- }}
358
- >
359
- <DynamicListRow<T>
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
- };