@etsoo/react 1.3.96 → 1.4.0

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 (39) hide show
  1. package/lib/components/ScrollerGrid.d.ts +2 -2
  2. package/lib/mu/AutocompleteExtendedProps.d.ts +2 -2
  3. package/lib/mu/ComboBox.d.ts +1 -1
  4. package/lib/mu/ComboBox.js +1 -3
  5. package/lib/mu/DataGridEx.js +0 -1
  6. package/lib/mu/DnDList.d.ts +4 -3
  7. package/lib/mu/DnDList.js +40 -9
  8. package/lib/mu/ItemList.d.ts +6 -12
  9. package/lib/mu/ItemList.js +8 -2
  10. package/lib/mu/OptionGroup.d.ts +3 -3
  11. package/lib/mu/OptionGroup.js +14 -4
  12. package/lib/mu/ScrollerListEx.d.ts +2 -2
  13. package/lib/mu/ScrollerListEx.js +2 -2
  14. package/lib/mu/SelectEx.d.ts +2 -2
  15. package/lib/mu/TableEx.d.ts +2 -2
  16. package/lib/mu/TableEx.js +5 -2
  17. package/lib/mu/Tiplist.js +3 -3
  18. package/lib/mu/pages/EditPage.js +2 -2
  19. package/lib/mu/pages/FixedListPage.d.ts +1 -1
  20. package/lib/mu/pages/ListPage.d.ts +1 -1
  21. package/lib/mu/pages/ResponsivePage.d.ts +1 -1
  22. package/lib/mu/pages/TablePage.d.ts +1 -1
  23. package/package.json +2 -2
  24. package/src/components/ScrollerGrid.tsx +2 -2
  25. package/src/mu/AutocompleteExtendedProps.ts +2 -2
  26. package/src/mu/ComboBox.tsx +2 -4
  27. package/src/mu/DataGridEx.tsx +0 -1
  28. package/src/mu/DnDList.tsx +61 -12
  29. package/src/mu/ItemList.tsx +15 -17
  30. package/src/mu/OptionGroup.tsx +16 -10
  31. package/src/mu/ScrollerListEx.tsx +4 -4
  32. package/src/mu/SelectEx.tsx +2 -2
  33. package/src/mu/TableEx.tsx +14 -5
  34. package/src/mu/Tiplist.tsx +1 -4
  35. package/src/mu/pages/EditPage.tsx +2 -2
  36. package/src/mu/pages/FixedListPage.tsx +1 -1
  37. package/src/mu/pages/ListPage.tsx +1 -1
  38. package/src/mu/pages/ResponsivePage.tsx +1 -1
  39. package/src/mu/pages/TablePage.tsx +1 -1
@@ -33,7 +33,7 @@ export interface ScrollerGridProps<T> extends GridLoader<T>, Omit<VariableSizeGr
33
33
  * Id field
34
34
  * @default id
35
35
  */
36
- idField?: string;
36
+ idField?: keyof T;
37
37
  /**
38
38
  * Item renderer
39
39
  */
@@ -109,4 +109,4 @@ export interface ScrollerGridForwardRef extends GridMethodRef {
109
109
  * @param props Props
110
110
  * @returns Component
111
111
  */
112
- export declare const ScrollerGrid: <T extends Record<string, any>>(props: ScrollerGridProps<T>) => JSX.Element;
112
+ export declare const ScrollerGrid: <T extends Record<string, unknown>>(props: ScrollerGridProps<T>) => JSX.Element;
@@ -4,11 +4,11 @@ import { ChangeEventHandler } from 'react';
4
4
  /**
5
5
  * Autocomplete extended props
6
6
  */
7
- export interface AutocompleteExtendedProps<T extends Record<string, any>> extends Omit<AutocompleteProps<T, undefined, false, false>, 'renderInput' | 'options'> {
7
+ export interface AutocompleteExtendedProps<T extends Record<string, unknown>> extends Omit<AutocompleteProps<T, undefined, false, false>, 'renderInput' | 'options'> {
8
8
  /**
9
9
  * Id field, default is id
10
10
  */
11
- idField?: string;
11
+ idField?: keyof T;
12
12
  /**
13
13
  * Id value
14
14
  */
@@ -8,7 +8,7 @@ export interface ComboBoxProps<T extends {}> extends AutocompleteExtendedProps<T
8
8
  /**
9
9
  * Label field
10
10
  */
11
- labelField?: string;
11
+ labelField?: keyof T;
12
12
  /**
13
13
  * Load data callback
14
14
  */
@@ -52,9 +52,7 @@ export function ComboBox(props) {
52
52
  const input = inputRef.current;
53
53
  if (input) {
54
54
  // Update value
55
- const newValue = value != null && idField in value
56
- ? `${Reflect.get(value, idField)}`
57
- : '';
55
+ const newValue = value != null ? `${Reflect.get(value, idField)}` : '';
58
56
  if (newValue !== input.value) {
59
57
  // Different value, trigger change event
60
58
  Utils.triggerChange(input, newValue, false);
@@ -244,7 +244,6 @@ export function DataGridEx(props) {
244
244
  }
245
245
  // Selected
246
246
  const selected = data != null &&
247
- idField in data &&
248
247
  (selectedRowIndex.current === rowIndex ||
249
248
  selectedItems.some((selectedItem) => selectedItem != null &&
250
249
  selectedItem[idField] === data[idField]));
@@ -6,7 +6,7 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
6
6
  /**
7
7
  * Item renderer
8
8
  */
9
- children: (item: D, index: number, onDelete: (index: number) => void) => React.ReactNode;
9
+ children: (item: D, index: number, deleteItem: (index: number) => void, editItem: (newItem: D, indexOrLabel: number | string) => void) => React.ReactNode;
10
10
  /**
11
11
  * List container component
12
12
  * https://javascript.plainenglish.io/building-a-polymorphic-component-in-react-and-typescript-d9f236950af4
@@ -19,7 +19,7 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
19
19
  /**
20
20
  * Get list item style callback
21
21
  */
22
- getItemStyle?: (isDragging: boolean) => CSSProperties;
22
+ getItemStyle?: (isDragging: boolean, index: number) => CSSProperties;
23
23
  /**
24
24
  * Get list style callback
25
25
  */
@@ -35,7 +35,7 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
35
35
  /**
36
36
  * Top and bottom sides renderer
37
37
  */
38
- sideRenderer?: (top: boolean, onAdd: (item: D) => boolean) => React.ReactNode;
38
+ sideRenderer?: (top: boolean, addItem: (item: D) => boolean, addItems: (items: D[]) => number) => React.ReactNode;
39
39
  /**
40
40
  * Name for hidden form input
41
41
  */
@@ -43,6 +43,7 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
43
43
  }
44
44
  /**
45
45
  * Drag and drop list
46
+ * https://github.com/atlassian/react-beautiful-dnd
46
47
  * @param props Props
47
48
  * @returns Component
48
49
  */
package/lib/mu/DnDList.js CHANGED
@@ -3,6 +3,7 @@ import React from 'react';
3
3
  import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
4
4
  /**
5
5
  * Drag and drop list
6
+ * https://github.com/atlassian/react-beautiful-dnd
6
7
  * @param props Props
7
8
  * @returns Component
8
9
  */
@@ -13,7 +14,6 @@ export function DnDList(props) {
13
14
  const [items, setItems] = React.useState([]);
14
15
  // Drag end handler
15
16
  const onDragEnd = (result) => {
16
- console.log(result);
17
17
  // Dropped outside the list
18
18
  if (!result.destination) {
19
19
  return;
@@ -27,8 +27,8 @@ export function DnDList(props) {
27
27
  // Update the state
28
28
  setItems(newItems);
29
29
  };
30
- // Add handler
31
- const onAdd = (newItem) => {
30
+ // Add item
31
+ const addItem = (newItem) => {
32
32
  // Existence check
33
33
  if (items.some((item) => item[labelField] == newItem[labelField])) {
34
34
  return false;
@@ -39,8 +39,39 @@ export function DnDList(props) {
39
39
  setItems(newItems);
40
40
  return true;
41
41
  };
42
- // Delete handler
43
- const onDelete = (index) => {
42
+ // Edit item
43
+ const editItem = (newItem, indexOrLabel) => {
44
+ const index = typeof indexOrLabel === 'number'
45
+ ? indexOrLabel
46
+ : items.findIndex((item) => DataTypes.convert(item[labelField], 'string') ==
47
+ indexOrLabel);
48
+ if (index === -1)
49
+ return;
50
+ // Clone
51
+ const newItems = [...items];
52
+ // Remove the item
53
+ newItems.splice(index, 1, newItem);
54
+ // Update the state
55
+ setItems(newItems);
56
+ };
57
+ // Add items
58
+ const addItems = (inputItems) => {
59
+ // Clone
60
+ const newItems = [...items];
61
+ // Insert items
62
+ inputItems.forEach((newItem) => {
63
+ // Existence check
64
+ if (newItems.some((item) => item[labelField] == newItem[labelField])) {
65
+ return;
66
+ }
67
+ newItems.push(newItem);
68
+ });
69
+ // Update the state
70
+ setItems(newItems);
71
+ return newItems.length - items.length;
72
+ };
73
+ // Delete item
74
+ const deleteItem = (index) => {
44
75
  // Clone
45
76
  const newItems = [...items];
46
77
  // Remove the item
@@ -53,7 +84,7 @@ export function DnDList(props) {
53
84
  }, [name]);
54
85
  // Layout
55
86
  return (React.createElement(React.Fragment, null,
56
- sideRenderer && sideRenderer(true, onAdd),
87
+ sideRenderer && sideRenderer(true, addItem, addItems),
57
88
  React.createElement(Component, { ...componentProps },
58
89
  React.createElement(DragDropContext, { onDragEnd: onDragEnd },
59
90
  React.createElement(Droppable, { droppableId: name }, (provided, snapshot) => (React.createElement("div", { ...provided.droppableProps, ref: provided.innerRef, style: getListStyle(snapshot.isDraggingOver) },
@@ -63,12 +94,12 @@ export function DnDList(props) {
63
94
  if (id == null)
64
95
  return;
65
96
  return (React.createElement(Draggable, { key: id, draggableId: id, index: index }, (provided, snapshot) => (React.createElement("div", { ref: provided.innerRef, ...provided.draggableProps, ...provided.dragHandleProps, style: {
66
- ...getItemStyle(snapshot.isDragging),
97
+ ...getItemStyle(snapshot.isDragging, index),
67
98
  ...provided
68
99
  .draggableProps
69
100
  .style
70
- } }, children(item, index, onDelete)))));
101
+ } }, children(item, index, deleteItem, editItem)))));
71
102
  }),
72
103
  provided.placeholder))))),
73
- sideRenderer && sideRenderer(false, onAdd)));
104
+ sideRenderer && sideRenderer(false, addItem, addItems)));
74
105
  }
@@ -1,14 +1,8 @@
1
1
  import React from 'react';
2
- /**
3
- * Item list label callback
4
- */
5
- export interface ItemListLabel {
6
- (item: any): string;
7
- }
8
2
  /**
9
3
  * Item list properties
10
4
  */
11
- export interface ItemListProps {
5
+ export interface ItemListProps<T extends Record<string, unknown>> {
12
6
  /**
13
7
  * Style class name
14
8
  */
@@ -16,11 +10,11 @@ export interface ItemListProps {
16
10
  /**
17
11
  * Id field name
18
12
  */
19
- idField?: string;
13
+ idField?: keyof T;
20
14
  /**
21
15
  * Label field name or callback
22
16
  */
23
- labelField?: string | ItemListLabel;
17
+ labelField?: keyof T | ((item: T) => string);
24
18
  /**
25
19
  * Button icon
26
20
  */
@@ -32,7 +26,7 @@ export interface ItemListProps {
32
26
  /**
33
27
  * Close event
34
28
  */
35
- onClose?(item: any, changed: boolean): void;
29
+ onClose?(item: T, changed: boolean): void;
36
30
  /**
37
31
  * Current selected language
38
32
  */
@@ -48,7 +42,7 @@ export interface ItemListProps {
48
42
  /**
49
43
  * Items
50
44
  */
51
- items: any[];
45
+ items: T[];
52
46
  /**
53
47
  * Button variant
54
48
  */
@@ -58,4 +52,4 @@ export interface ItemListProps {
58
52
  * Item list component
59
53
  * @param props Properties
60
54
  */
61
- export declare function ItemList(props: ItemListProps): JSX.Element;
55
+ export declare function ItemList<T extends Record<string, unknown> = Record<string, unknown>>(props: ItemListProps<T>): JSX.Element;
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Dialog, DialogTitle, List, ListItem, ListItemText, DialogContent, Button } from '@mui/material';
3
+ import { DataTypes } from '@etsoo/shared';
3
4
  /**
4
5
  * Item list component
5
6
  * @param props Properties
@@ -10,15 +11,20 @@ export function ItemList(props) {
10
11
  const { className, color = 'primary', items, idField = 'id', labelField = 'label', icon, onClose, selectedValue, size = 'medium', title, variant = 'outlined' } = props;
11
12
  // Get id
12
13
  const getId = (item) => {
13
- return item[idField];
14
+ var _a;
15
+ const id = item[idField];
16
+ if (typeof id === 'number')
17
+ return id;
18
+ return (_a = DataTypes.convert(id, 'string')) !== null && _a !== void 0 ? _a : '';
14
19
  };
15
20
  // Get label
16
21
  const getLabel = (item) => {
22
+ var _a;
17
23
  if (typeof labelField === 'function') {
18
24
  return labelField(item);
19
25
  }
20
26
  else {
21
- return item[labelField];
27
+ return (_a = DataTypes.convert(item[labelField], 'string')) !== null && _a !== void 0 ? _a : '';
22
28
  }
23
29
  };
24
30
  // Dialog open or not state
@@ -17,7 +17,7 @@ export interface OptionGroupProps<T extends Record<string, any> = IdLabelDto, D
17
17
  /**
18
18
  * Id field, default is id
19
19
  */
20
- idField?: string;
20
+ idField?: keyof T;
21
21
  /**
22
22
  * Label
23
23
  */
@@ -25,7 +25,7 @@ export interface OptionGroupProps<T extends Record<string, any> = IdLabelDto, D
25
25
  /**
26
26
  * Label field, default is label
27
27
  */
28
- labelField?: string;
28
+ labelField?: keyof T;
29
29
  /**
30
30
  * Multiple choose item
31
31
  */
@@ -56,4 +56,4 @@ export interface OptionGroupProps<T extends Record<string, any> = IdLabelDto, D
56
56
  * @param props Props
57
57
  * @returns Component
58
58
  */
59
- export declare function OptionGroup<T extends Record<string, any> = IdLabelDto, D extends DataTypes.IdType = string>(props: OptionGroupProps<T, D>): JSX.Element;
59
+ export declare function OptionGroup<T extends Record<string, unknown> = IdLabelDto, D extends DataTypes.IdType = string>(props: OptionGroupProps<T, D>): JSX.Element;
@@ -1,4 +1,4 @@
1
- import { Utils } from '@etsoo/shared';
1
+ import { DataTypes, Utils } from '@etsoo/shared';
2
2
  import { Checkbox, FormControl, FormControlLabel, FormGroup, FormLabel, Radio, RadioGroup } from '@mui/material';
3
3
  import React from 'react';
4
4
  /**
@@ -10,8 +10,12 @@ export function OptionGroup(props) {
10
10
  // Destruct
11
11
  const { getOptionLabel, defaultValue, idField = 'id', label, labelField = 'label', multiple = false, name, onValueChange, options, readOnly, row, size, ...rest } = props;
12
12
  // Get option value
13
+ // D type should be the source id type
13
14
  const getOptionValue = (option) => {
14
- return option[idField];
15
+ const value = DataTypes.getIdValue(option, idField);
16
+ if (value == null)
17
+ return null;
18
+ return value;
15
19
  };
16
20
  // Checkbox values
17
21
  const [values, setValues] = React.useState(defaultValue == null
@@ -27,11 +31,15 @@ export function OptionGroup(props) {
27
31
  return false;
28
32
  return values.includes(value);
29
33
  };
34
+ // First item value
35
+ const firstOptionValue = getOptionValue(options[0]);
30
36
  // Items
31
37
  const list = options.map((option) => {
32
38
  // Control
33
39
  const control = multiple ? (React.createElement(Checkbox, { name: name, readOnly: readOnly, size: size, checked: itemChecked(option), onChange: (event) => {
34
- const typeValue = Utils.parseString(event.target.value, options[0][idField]);
40
+ if (firstOptionValue == null)
41
+ return;
42
+ const typeValue = Utils.parseString(event.target.value, firstOptionValue);
35
43
  const changedValues = [...values];
36
44
  if (event.target.checked) {
37
45
  if (changedValues.includes(typeValue))
@@ -59,7 +67,9 @@ export function OptionGroup(props) {
59
67
  });
60
68
  // Group
61
69
  const group = multiple ? (React.createElement(FormGroup, { row: row }, list)) : (React.createElement(RadioGroup, { row: row, name: name, value: defaultValue, onChange: (_event, value) => {
62
- const typeValue = Utils.parseString(value, options[0][idField]);
70
+ if (firstOptionValue == null)
71
+ return;
72
+ const typeValue = Utils.parseString(value, firstOptionValue);
63
73
  if (onValueChange)
64
74
  onValueChange(typeValue);
65
75
  } }, list));
@@ -25,7 +25,7 @@ export interface ScrollerListExProps<T> extends Omit<ScrollerListProps<T>, 'item
25
25
  /**
26
26
  * Id field
27
27
  */
28
- idField?: string;
28
+ idField?: keyof T;
29
29
  /**
30
30
  * Inner item renderer
31
31
  */
@@ -48,4 +48,4 @@ export interface ScrollerListExProps<T> extends Omit<ScrollerListProps<T>, 'item
48
48
  * @param props Props
49
49
  * @returns Component
50
50
  */
51
- export declare function ScrollerListEx<T extends Record<string, any>>(props: ScrollerListExProps<T>): JSX.Element;
51
+ export declare function ScrollerListEx<T extends Record<string, unknown>>(props: ScrollerListExProps<T>): JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { css } from '@emotion/css';
2
- import { Utils } from '@etsoo/shared';
2
+ import { DataTypes, Utils } from '@etsoo/shared';
3
3
  import React from 'react';
4
4
  import { ScrollerList } from '../components/ScrollerList';
5
5
  // Scroll bar size
@@ -82,7 +82,7 @@ export function ScrollerListEx(props) {
82
82
  return selected;
83
83
  };
84
84
  // Destruct
85
- const { alternatingColors = [undefined, undefined], className, idField = 'id', innerItemRenderer, itemSize, itemKey = (index, data) => data != null && idField in data ? data[idField] : index, itemRenderer = (itemProps) => {
85
+ const { alternatingColors = [undefined, undefined], className, idField = 'id', innerItemRenderer, itemSize, itemKey = (index, data) => { var _a; return (_a = DataTypes.getIdValue(data, idField)) !== null && _a !== void 0 ? _a : index; }, itemRenderer = (itemProps) => {
86
86
  const itemHeight = typeof itemSize === 'function'
87
87
  ? itemSize(itemProps.index)
88
88
  : itemSize;
@@ -8,7 +8,7 @@ export interface SelectExProps<T extends {}> extends Omit<SelectProps, 'labelId'
8
8
  /**
9
9
  * Id field, default is id
10
10
  */
11
- idField?: string;
11
+ idField?: keyof T;
12
12
  /**
13
13
  * Item icon renderer
14
14
  */
@@ -16,7 +16,7 @@ export interface SelectExProps<T extends {}> extends Omit<SelectProps, 'labelId'
16
16
  /**
17
17
  * Label field, default is label
18
18
  */
19
- labelField?: ((option: T) => string) | string;
19
+ labelField?: ((option: T) => string) | keyof T;
20
20
  /**
21
21
  * Load data callback
22
22
  */
@@ -35,7 +35,7 @@ export interface TableExProps<T extends Record<string, any>> extends TableProps,
35
35
  /**
36
36
  * Id field
37
37
  */
38
- idField?: string;
38
+ idField?: keyof T;
39
39
  /**
40
40
  * Max height
41
41
  */
@@ -62,4 +62,4 @@ export interface TableExProps<T extends Record<string, any>> extends TableProps,
62
62
  * @param props Props
63
63
  * @returns Component
64
64
  */
65
- export declare function TableEx<T extends Record<string, any>>(props: TableExProps<T>): JSX.Element;
65
+ export declare function TableEx<T extends Record<string, unknown>>(props: TableExProps<T>): JSX.Element;
package/lib/mu/TableEx.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { DataTypes } from '@etsoo/shared';
1
2
  import { Checkbox, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TableSortLabel, useTheme } from '@mui/material';
2
3
  import React from 'react';
3
4
  import { GridAlignGet } from '../components/GridColumn';
@@ -220,12 +221,13 @@ export function TableEx(props) {
220
221
  backgroundColor: alternatingColors[1]
221
222
  }
222
223
  } }, [...Array(batchSize)].map((_item, rowIndex) => {
224
+ var _a;
223
225
  // Row
224
226
  const row = rowIndex < rows.length
225
227
  ? rows[rowIndex]
226
228
  : undefined;
227
229
  // Row id field value
228
- const rowId = row == null ? rowIndex : row[idField];
230
+ const rowId = (_a = DataTypes.getIdValue(row, idField)) !== null && _a !== void 0 ? _a : rowIndex;
229
231
  // Selected or not
230
232
  const isItemSelected = selectable
231
233
  ? selectedItems.some((item) => item[idField] === rowId)
@@ -255,7 +257,8 @@ export function TableEx(props) {
255
257
  columnIndex,
256
258
  cellProps
257
259
  })) : (React.createElement(React.Fragment, null, "\u00A0"));
258
- return (React.createElement(TableCell, { key: rowId + columnIndex, ...cellProps }, child));
260
+ return (React.createElement(TableCell, { key: rowId.toString() +
261
+ columnIndex, ...cellProps }, child));
259
262
  })));
260
263
  })))),
261
264
  React.createElement(TablePagination, { component: "div", showFirstButton: true, count: totalRows, rowsPerPage: batchSize, page: currentPage, onPageChange: handleChangePage, onRowsPerPageChange: handleChangeRowsPerPage, rowsPerPageOptions: [
package/lib/mu/Tiplist.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { DataTypes } from '@etsoo/shared';
1
2
  import { Autocomplete } from '@mui/material';
2
3
  import React from 'react';
3
4
  import { Utils } from '../app/Utils';
@@ -94,14 +95,13 @@ export function Tiplist(props) {
94
95
  });
95
96
  };
96
97
  const setInputValue = (value) => {
98
+ var _a;
97
99
  stateUpdate({ value });
98
100
  // Input value
99
101
  const input = inputRef.current;
100
102
  if (input) {
101
103
  // Update value
102
- const newValue = value != null && idField in value
103
- ? `${Reflect.get(value, idField)}`
104
- : '';
104
+ const newValue = (_a = DataTypes.getStringValue(value, idField)) !== null && _a !== void 0 ? _a : '';
105
105
  if (newValue !== input.value) {
106
106
  // Different value, trigger change event
107
107
  Utils.triggerChange(input, newValue, false);
@@ -3,7 +3,7 @@ import React from 'react';
3
3
  import { Labels } from '../../app/Labels';
4
4
  import { MUGlobal } from '../MUGlobal';
5
5
  import { CommonPage, CommonPageScrollContainer } from './CommonPage';
6
- import EditIcon from '@mui/icons-material/Edit';
6
+ import SaveIcon from '@mui/icons-material/Save';
7
7
  /**
8
8
  * Edit page
9
9
  * @param props Props
@@ -21,5 +21,5 @@ export function EditPage(props) {
21
21
  bottom: (theme) => MUGlobal.updateWithTheme(paddings, theme.spacing),
22
22
  paddingTop: paddings
23
23
  } },
24
- React.createElement(Button, { variant: "contained", type: "submit", fullWidth: true, startIcon: React.createElement(EditIcon, null) }, labels.save)))));
24
+ React.createElement(Button, { variant: "contained", type: "submit", fullWidth: true, startIcon: React.createElement(SaveIcon, null) }, labels.save)))));
25
25
  }
@@ -6,7 +6,7 @@ import { ListPageProps } from './ListPageProps';
6
6
  * @param props Props
7
7
  * @returns Component
8
8
  */
9
- export declare function FixedListPage<T, F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate>(props: ListPageProps<T, F> & {
9
+ export declare function FixedListPage<T extends {}, F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate>(props: ListPageProps<T, F> & {
10
10
  /**
11
11
  * Height will be deducted
12
12
  * @param height Current calcuated height
@@ -6,4 +6,4 @@ import { ListPageProps } from './ListPageProps';
6
6
  * @param props Props
7
7
  * @returns Component
8
8
  */
9
- export declare function ListPage<T, F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate>(props: ListPageProps<T, F>): JSX.Element;
9
+ export declare function ListPage<T extends {}, F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate>(props: ListPageProps<T, F>): JSX.Element;
@@ -6,4 +6,4 @@ import { ResponsePageProps } from './ResponsivePageProps';
6
6
  * @param props Props
7
7
  * @returns Component
8
8
  */
9
- export declare function ResponsivePage<T, F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate>(props: ResponsePageProps<T, F>): JSX.Element;
9
+ export declare function ResponsivePage<T extends {}, F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate>(props: ResponsePageProps<T, F>): JSX.Element;
@@ -6,4 +6,4 @@ import { TablePageProps } from './TablePageProps';
6
6
  * @param props Props
7
7
  * @returns Component
8
8
  */
9
- export declare function TablePage<T, F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate>(props: TablePageProps<T, F>): JSX.Element;
9
+ export declare function TablePage<T extends {}, F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate>(props: TablePageProps<T, F>): JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/react",
3
- "version": "1.3.96",
3
+ "version": "1.4.0",
4
4
  "description": "TypeScript ReactJs framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -52,7 +52,7 @@
52
52
  "@emotion/styled": "^11.6.0",
53
53
  "@etsoo/appscript": "^1.2.11",
54
54
  "@etsoo/notificationbase": "^1.0.95",
55
- "@etsoo/shared": "^1.0.95",
55
+ "@etsoo/shared": "^1.0.97",
56
56
  "@mui/icons-material": "^5.2.5",
57
57
  "@mui/material": "^5.2.6",
58
58
  "@reach/router": "^1.3.4",
@@ -51,7 +51,7 @@ export interface ScrollerGridProps<T>
51
51
  * Id field
52
52
  * @default id
53
53
  */
54
- idField?: string;
54
+ idField?: keyof T;
55
55
 
56
56
  /**
57
57
  * Item renderer
@@ -140,7 +140,7 @@ export interface ScrollerGridForwardRef extends GridMethodRef {
140
140
  * @param props Props
141
141
  * @returns Component
142
142
  */
143
- export const ScrollerGrid = <T extends Record<string, any>>(
143
+ export const ScrollerGrid = <T extends Record<string, unknown>>(
144
144
  props: ScrollerGridProps<T>
145
145
  ) => {
146
146
  // Destruct
@@ -5,7 +5,7 @@ import { ChangeEventHandler } from 'react';
5
5
  /**
6
6
  * Autocomplete extended props
7
7
  */
8
- export interface AutocompleteExtendedProps<T extends Record<string, any>>
8
+ export interface AutocompleteExtendedProps<T extends Record<string, unknown>>
9
9
  extends Omit<
10
10
  AutocompleteProps<T, undefined, false, false>,
11
11
  'renderInput' | 'options'
@@ -13,7 +13,7 @@ export interface AutocompleteExtendedProps<T extends Record<string, any>>
13
13
  /**
14
14
  * Id field, default is id
15
15
  */
16
- idField?: string;
16
+ idField?: keyof T;
17
17
 
18
18
  /**
19
19
  * Id value
@@ -14,7 +14,7 @@ export interface ComboBoxProps<T extends {}>
14
14
  /**
15
15
  * Label field
16
16
  */
17
- labelField?: string;
17
+ labelField?: keyof T;
18
18
 
19
19
  /**
20
20
  * Load data callback
@@ -118,9 +118,7 @@ export function ComboBox<T extends {} = IdLabelDto>(props: ComboBoxProps<T>) {
118
118
  if (input) {
119
119
  // Update value
120
120
  const newValue =
121
- value != null && idField in value
122
- ? `${Reflect.get(value, idField)}`
123
- : '';
121
+ value != null ? `${Reflect.get(value, idField)}` : '';
124
122
 
125
123
  if (newValue !== input.value) {
126
124
  // Different value, trigger change event
@@ -539,7 +539,6 @@ export function DataGridEx<T extends Record<string, any>>(
539
539
  // Selected
540
540
  const selected =
541
541
  data != null &&
542
- idField in data &&
543
542
  (selectedRowIndex.current === rowIndex ||
544
543
  selectedItems.some(
545
544
  (selectedItem) =>
@@ -17,7 +17,8 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
17
17
  children: (
18
18
  item: D,
19
19
  index: number,
20
- onDelete: (index: number) => void
20
+ deleteItem: (index: number) => void,
21
+ editItem: (newItem: D, indexOrLabel: number | string) => void
21
22
  ) => React.ReactNode;
22
23
 
23
24
  /**
@@ -34,7 +35,7 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
34
35
  /**
35
36
  * Get list item style callback
36
37
  */
37
- getItemStyle?: (isDragging: boolean) => CSSProperties;
38
+ getItemStyle?: (isDragging: boolean, index: number) => CSSProperties;
38
39
 
39
40
  /**
40
41
  * Get list style callback
@@ -56,7 +57,8 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
56
57
  */
57
58
  sideRenderer?: (
58
59
  top: boolean,
59
- onAdd: (item: D) => boolean
60
+ addItem: (item: D) => boolean,
61
+ addItems: (items: D[]) => number
60
62
  ) => React.ReactNode;
61
63
 
62
64
  /**
@@ -67,6 +69,7 @@ export interface DnDListProps<D extends {}, E extends React.ElementType> {
67
69
 
68
70
  /**
69
71
  * Drag and drop list
72
+ * https://github.com/atlassian/react-beautiful-dnd
70
73
  * @param props Props
71
74
  * @returns Component
72
75
  */
@@ -92,7 +95,6 @@ export function DnDList<
92
95
 
93
96
  // Drag end handler
94
97
  const onDragEnd = (result: DropResult) => {
95
- console.log(result);
96
98
  // Dropped outside the list
97
99
  if (!result.destination) {
98
100
  return;
@@ -111,8 +113,8 @@ export function DnDList<
111
113
  setItems(newItems);
112
114
  };
113
115
 
114
- // Add handler
115
- const onAdd = (newItem: D) => {
116
+ // Add item
117
+ const addItem = (newItem: D) => {
116
118
  // Existence check
117
119
  if (items.some((item) => item[labelField] == newItem[labelField])) {
118
120
  return false;
@@ -127,8 +129,53 @@ export function DnDList<
127
129
  return true;
128
130
  };
129
131
 
130
- // Delete handler
131
- const onDelete = (index: number) => {
132
+ // Edit item
133
+ const editItem = (newItem: D, indexOrLabel: number | string) => {
134
+ const index =
135
+ typeof indexOrLabel === 'number'
136
+ ? indexOrLabel
137
+ : items.findIndex(
138
+ (item) =>
139
+ DataTypes.convert(item[labelField], 'string') ==
140
+ indexOrLabel
141
+ );
142
+ if (index === -1) return;
143
+
144
+ // Clone
145
+ const newItems = [...items];
146
+
147
+ // Remove the item
148
+ newItems.splice(index, 1, newItem);
149
+
150
+ // Update the state
151
+ setItems(newItems);
152
+ };
153
+
154
+ // Add items
155
+ const addItems = (inputItems: D[]) => {
156
+ // Clone
157
+ const newItems = [...items];
158
+
159
+ // Insert items
160
+ inputItems.forEach((newItem) => {
161
+ // Existence check
162
+ if (
163
+ newItems.some((item) => item[labelField] == newItem[labelField])
164
+ ) {
165
+ return;
166
+ }
167
+
168
+ newItems.push(newItem);
169
+ });
170
+
171
+ // Update the state
172
+ setItems(newItems);
173
+
174
+ return newItems.length - items.length;
175
+ };
176
+
177
+ // Delete item
178
+ const deleteItem = (index: number) => {
132
179
  // Clone
133
180
  const newItems = [...items];
134
181
 
@@ -146,7 +193,7 @@ export function DnDList<
146
193
  // Layout
147
194
  return (
148
195
  <React.Fragment>
149
- {sideRenderer && sideRenderer(true, onAdd)}
196
+ {sideRenderer && sideRenderer(true, addItem, addItems)}
150
197
  <Component {...componentProps}>
151
198
  <DragDropContext onDragEnd={onDragEnd}>
152
199
  <Droppable droppableId={name}>
@@ -177,7 +224,8 @@ export function DnDList<
177
224
  {...provided.dragHandleProps}
178
225
  style={{
179
226
  ...getItemStyle(
180
- snapshot.isDragging
227
+ snapshot.isDragging,
228
+ index
181
229
  ),
182
230
  ...provided
183
231
  .draggableProps
@@ -187,7 +235,8 @@ export function DnDList<
187
235
  {children(
188
236
  item,
189
237
  index,
190
- onDelete
238
+ deleteItem,
239
+ editItem
191
240
  )}
192
241
  </div>
193
242
  )}
@@ -200,7 +249,7 @@ export function DnDList<
200
249
  </Droppable>
201
250
  </DragDropContext>
202
251
  </Component>
203
- {sideRenderer && sideRenderer(false, onAdd)}
252
+ {sideRenderer && sideRenderer(false, addItem, addItems)}
204
253
  </React.Fragment>
205
254
  );
206
255
  }
@@ -8,18 +8,12 @@ import {
8
8
  DialogContent,
9
9
  Button
10
10
  } from '@mui/material';
11
-
12
- /**
13
- * Item list label callback
14
- */
15
- export interface ItemListLabel {
16
- (item: any): string;
17
- }
11
+ import { DataTypes } from '@etsoo/shared';
18
12
 
19
13
  /**
20
14
  * Item list properties
21
15
  */
22
- export interface ItemListProps {
16
+ export interface ItemListProps<T extends Record<string, unknown>> {
23
17
  /**
24
18
  * Style class name
25
19
  */
@@ -28,12 +22,12 @@ export interface ItemListProps {
28
22
  /**
29
23
  * Id field name
30
24
  */
31
- idField?: string;
25
+ idField?: keyof T;
32
26
 
33
27
  /**
34
28
  * Label field name or callback
35
29
  */
36
- labelField?: string | ItemListLabel;
30
+ labelField?: keyof T | ((item: T) => string);
37
31
 
38
32
  /**
39
33
  * Button icon
@@ -48,7 +42,7 @@ export interface ItemListProps {
48
42
  /**
49
43
  * Close event
50
44
  */
51
- onClose?(item: any, changed: boolean): void;
45
+ onClose?(item: T, changed: boolean): void;
52
46
 
53
47
  /**
54
48
  * Current selected language
@@ -68,7 +62,7 @@ export interface ItemListProps {
68
62
  /**
69
63
  * Items
70
64
  */
71
- items: any[];
65
+ items: T[];
72
66
 
73
67
  /**
74
68
  * Button variant
@@ -80,7 +74,9 @@ export interface ItemListProps {
80
74
  * Item list component
81
75
  * @param props Properties
82
76
  */
83
- export function ItemList(props: ItemListProps) {
77
+ export function ItemList<
78
+ T extends Record<string, unknown> = Record<string, unknown>
79
+ >(props: ItemListProps<T>) {
84
80
  // properties destructure
85
81
  const {
86
82
  className,
@@ -97,16 +93,18 @@ export function ItemList(props: ItemListProps) {
97
93
  } = props;
98
94
 
99
95
  // Get id
100
- const getId = (item: any) => {
101
- return item[idField];
96
+ const getId = (item: T): string | number => {
97
+ const id = item[idField];
98
+ if (typeof id === 'number') return id;
99
+ return DataTypes.convert(id, 'string') ?? '';
102
100
  };
103
101
 
104
102
  // Get label
105
- const getLabel = (item: any) => {
103
+ const getLabel = (item: T): string => {
106
104
  if (typeof labelField === 'function') {
107
105
  return labelField(item);
108
106
  } else {
109
- return item[labelField];
107
+ return DataTypes.convert(item[labelField], 'string') ?? '';
110
108
  }
111
109
  };
112
110
 
@@ -32,7 +32,7 @@ export interface OptionGroupProps<
32
32
  /**
33
33
  * Id field, default is id
34
34
  */
35
- idField?: string;
35
+ idField?: keyof T;
36
36
 
37
37
  /**
38
38
  * Label
@@ -42,7 +42,7 @@ export interface OptionGroupProps<
42
42
  /**
43
43
  * Label field, default is label
44
44
  */
45
- labelField?: string;
45
+ labelField?: keyof T;
46
46
 
47
47
  /**
48
48
  * Multiple choose item
@@ -81,7 +81,7 @@ export interface OptionGroupProps<
81
81
  * @returns Component
82
82
  */
83
83
  export function OptionGroup<
84
- T extends Record<string, any> = IdLabelDto,
84
+ T extends Record<string, unknown> = IdLabelDto,
85
85
  D extends DataTypes.IdType = string
86
86
  >(props: OptionGroupProps<T, D>) {
87
87
  // Destruct
@@ -102,8 +102,11 @@ export function OptionGroup<
102
102
  } = props;
103
103
 
104
104
  // Get option value
105
- const getOptionValue = (option: T): D | undefined => {
106
- return option[idField];
105
+ // D type should be the source id type
106
+ const getOptionValue = (option: T): D | null => {
107
+ const value = DataTypes.getIdValue(option, idField);
108
+ if (value == null) return null;
109
+ return value as D;
107
110
  };
108
111
 
109
112
  // Checkbox values
@@ -124,6 +127,9 @@ export function OptionGroup<
124
127
  return values.includes(value);
125
128
  };
126
129
 
130
+ // First item value
131
+ const firstOptionValue = getOptionValue(options[0]);
132
+
127
133
  // Items
128
134
  const list = options.map((option) => {
129
135
  // Control
@@ -134,9 +140,11 @@ export function OptionGroup<
134
140
  size={size}
135
141
  checked={itemChecked(option)}
136
142
  onChange={(event) => {
143
+ if (firstOptionValue == null) return;
144
+
137
145
  const typeValue = Utils.parseString<D>(
138
146
  event.target.value,
139
- options[0][idField]
147
+ firstOptionValue
140
148
  );
141
149
 
142
150
  const changedValues = [...values];
@@ -188,10 +196,8 @@ export function OptionGroup<
188
196
  name={name}
189
197
  value={defaultValue}
190
198
  onChange={(_event, value) => {
191
- const typeValue = Utils.parseString<D>(
192
- value,
193
- options[0][idField]
194
- );
199
+ if (firstOptionValue == null) return;
200
+ const typeValue = Utils.parseString<D>(value, firstOptionValue);
195
201
  if (onValueChange) onValueChange(typeValue);
196
202
  }}
197
203
  >
@@ -1,5 +1,5 @@
1
1
  import { css } from '@emotion/css';
2
- import { Utils } from '@etsoo/shared';
2
+ import { DataTypes, Utils } from '@etsoo/shared';
3
3
  import React from 'react';
4
4
  import { ListChildComponentProps } from 'react-window';
5
5
  import { ScrollerList, ScrollerListProps } from '../components/ScrollerList';
@@ -73,7 +73,7 @@ export interface ScrollerListExProps<T>
73
73
  /**
74
74
  * Id field
75
75
  */
76
- idField?: string;
76
+ idField?: keyof T;
77
77
 
78
78
  /**
79
79
  * Inner item renderer
@@ -161,7 +161,7 @@ function defaultItemRenderer<T>({
161
161
  * @param props Props
162
162
  * @returns Component
163
163
  */
164
- export function ScrollerListEx<T extends Record<string, any>>(
164
+ export function ScrollerListEx<T extends Record<string, unknown>>(
165
165
  props: ScrollerListExProps<T>
166
166
  ) {
167
167
  // Selected item ref
@@ -201,7 +201,7 @@ export function ScrollerListEx<T extends Record<string, any>>(
201
201
  innerItemRenderer,
202
202
  itemSize,
203
203
  itemKey = (index: number, data: T) =>
204
- data != null && idField in data ? data[idField] : index,
204
+ DataTypes.getIdValue(data, idField) ?? index,
205
205
  itemRenderer = (itemProps) => {
206
206
  const itemHeight =
207
207
  typeof itemSize === 'function'
@@ -23,7 +23,7 @@ export interface SelectExProps<T extends {}>
23
23
  /**
24
24
  * Id field, default is id
25
25
  */
26
- idField?: string;
26
+ idField?: keyof T;
27
27
 
28
28
  /**
29
29
  * Item icon renderer
@@ -33,7 +33,7 @@ export interface SelectExProps<T extends {}>
33
33
  /**
34
34
  * Label field, default is label
35
35
  */
36
- labelField?: ((option: T) => string) | string;
36
+ labelField?: ((option: T) => string) | keyof T;
37
37
 
38
38
  /**
39
39
  * Load data callback
@@ -1,3 +1,4 @@
1
+ import { DataTypes } from '@etsoo/shared';
1
2
  import {
2
3
  Checkbox,
3
4
  Paper,
@@ -67,7 +68,7 @@ export interface TableExProps<T extends Record<string, any>>
67
68
  /**
68
69
  * Id field
69
70
  */
70
- idField?: string;
71
+ idField?: keyof T;
71
72
 
72
73
  /**
73
74
  * Max height
@@ -100,10 +101,14 @@ export interface TableExProps<T extends Record<string, any>>
100
101
  * @param props Props
101
102
  * @returns Component
102
103
  */
103
- export function TableEx<T extends Record<string, any>>(props: TableExProps<T>) {
104
+ export function TableEx<T extends Record<string, unknown>>(
105
+ props: TableExProps<T>
106
+ ) {
104
107
  // Theme
105
108
  const theme = useTheme();
106
109
 
110
+ type keyType = keyof T;
111
+
107
112
  // Destruct
108
113
  const {
109
114
  alternatingColors = [theme.palette.action.hover, undefined],
@@ -111,7 +116,7 @@ export function TableEx<T extends Record<string, any>>(props: TableExProps<T>) {
111
116
  columns,
112
117
  defaultOrderBy,
113
118
  headerColors = [undefined, undefined],
114
- idField = 'id',
119
+ idField = 'id' as keyType,
115
120
  loadBatchSize,
116
121
  loadData,
117
122
  maxHeight,
@@ -441,7 +446,8 @@ export function TableEx<T extends Record<string, any>>(props: TableExProps<T>) {
441
446
  : undefined;
442
447
 
443
448
  // Row id field value
444
- const rowId = row == null ? rowIndex : row[idField];
449
+ const rowId =
450
+ DataTypes.getIdValue(row, idField) ?? rowIndex;
445
451
 
446
452
  // Selected or not
447
453
  const isItemSelected = selectable
@@ -522,7 +528,10 @@ export function TableEx<T extends Record<string, any>>(props: TableExProps<T>) {
522
528
 
523
529
  return (
524
530
  <TableCell
525
- key={rowId + columnIndex}
531
+ key={
532
+ rowId.toString() +
533
+ columnIndex
534
+ }
526
535
  {...cellProps}
527
536
  >
528
537
  {child}
@@ -176,10 +176,7 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
176
176
  const input = inputRef.current;
177
177
  if (input) {
178
178
  // Update value
179
- const newValue =
180
- value != null && idField in value
181
- ? `${Reflect.get(value, idField)}`
182
- : '';
179
+ const newValue = DataTypes.getStringValue(value, idField) ?? '';
183
180
  if (newValue !== input.value) {
184
181
  // Different value, trigger change event
185
182
  Utils.triggerChange(input, newValue, false);
@@ -6,7 +6,7 @@ import { Labels } from '../../app/Labels';
6
6
  import { MUGlobal } from '../MUGlobal';
7
7
  import { CommonPage, CommonPageScrollContainer } from './CommonPage';
8
8
  import { CommonPageProps } from './CommonPageProps';
9
- import EditIcon from '@mui/icons-material/Edit';
9
+ import SaveIcon from '@mui/icons-material/Save';
10
10
 
11
11
  /**
12
12
  * Edit page router props, include 'id' (/:id) query parameter
@@ -70,7 +70,7 @@ export function EditPage(props: EditPageProps) {
70
70
  variant="contained"
71
71
  type="submit"
72
72
  fullWidth
73
- startIcon={<EditIcon />}
73
+ startIcon={<SaveIcon />}
74
74
  >
75
75
  {labels.save}
76
76
  </Button>
@@ -17,7 +17,7 @@ import { ListPageProps } from './ListPageProps';
17
17
  * @returns Component
18
18
  */
19
19
  export function FixedListPage<
20
- T,
20
+ T extends {},
21
21
  F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
22
22
  >(
23
23
  props: ListPageProps<T, F> & {
@@ -20,7 +20,7 @@ import { ListPageProps } from './ListPageProps';
20
20
  * @returns Component
21
21
  */
22
22
  export function ListPage<
23
- T,
23
+ T extends {},
24
24
  F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
25
25
  >(props: ListPageProps<T, F>) {
26
26
  // Destruct
@@ -25,7 +25,7 @@ interface LocalStates {
25
25
  * @returns Component
26
26
  */
27
27
  export function ResponsivePage<
28
- T,
28
+ T extends {},
29
29
  F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
30
30
  >(props: ResponsePageProps<T, F>) {
31
31
  // Destruct
@@ -20,7 +20,7 @@ import { TablePageProps } from './TablePageProps';
20
20
  * @returns Component
21
21
  */
22
22
  export function TablePage<
23
- T,
23
+ T extends {},
24
24
  F extends DataTypes.BasicTemplate = DataTypes.BasicTemplate
25
25
  >(props: TablePageProps<T, F>) {
26
26
  // Destruct