@etsoo/materialui 1.5.70 → 1.5.72

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 (95) hide show
  1. package/__tests__/ReactAppTests.tsx +12 -7
  2. package/__tests__/SelectEx.tsx +1 -1
  3. package/__tests__/tsconfig.json +1 -1
  4. package/lib/cjs/ButtonPopupCheckbox.js +1 -1
  5. package/lib/cjs/ButtonPopupRadio.js +1 -1
  6. package/lib/cjs/DataGridEx.d.ts +8 -1
  7. package/lib/cjs/DataGridEx.js +71 -56
  8. package/lib/cjs/DataGridRenderers.d.ts +1 -1
  9. package/lib/cjs/DataGridRenderers.js +1 -1
  10. package/lib/cjs/DnDList.js +1 -1
  11. package/lib/cjs/MUUtils.d.ts +0 -9
  12. package/lib/cjs/MUUtils.js +0 -26
  13. package/lib/cjs/MobileListItemRenderer.d.ts +2 -2
  14. package/lib/cjs/ResponsibleContainer.d.ts +2 -7
  15. package/lib/cjs/ResponsibleContainer.js +8 -57
  16. package/lib/cjs/ScrollerListEx.d.ts +24 -9
  17. package/lib/cjs/ScrollerListEx.js +36 -39
  18. package/lib/cjs/SelectEx.js +2 -2
  19. package/lib/cjs/TableEx.d.ts +7 -0
  20. package/lib/cjs/TableEx.js +6 -12
  21. package/lib/cjs/app/ReactApp.d.ts +1 -3
  22. package/lib/cjs/custom/FieldDateInput.js +1 -1
  23. package/lib/cjs/custom/FieldInput.js +1 -1
  24. package/lib/cjs/custom/FieldJson.js +1 -1
  25. package/lib/cjs/custom/FieldNumberInput.js +1 -1
  26. package/lib/cjs/custom/FieldTexarea.js +1 -1
  27. package/lib/cjs/html/HtmlDiv.d.ts +24 -7
  28. package/lib/cjs/html/HtmlDiv.js +5 -1
  29. package/lib/cjs/pages/DataGridPage.js +3 -32
  30. package/lib/cjs/pages/FixedListPage.js +5 -34
  31. package/lib/cjs/pages/ListPage.js +1 -29
  32. package/lib/cjs/pages/ResponsivePage.d.ts +2 -7
  33. package/lib/cjs/uses/useGridCacheInitLoad.d.ts +2 -0
  34. package/lib/cjs/uses/useGridCacheInitLoad.js +41 -0
  35. package/lib/cjs/uses/useListCacheInitLoad.d.ts +2 -0
  36. package/lib/cjs/uses/useListCacheInitLoad.js +38 -0
  37. package/lib/mjs/ButtonPopupCheckbox.js +1 -1
  38. package/lib/mjs/ButtonPopupRadio.js +1 -1
  39. package/lib/mjs/DataGridEx.d.ts +8 -1
  40. package/lib/mjs/DataGridEx.js +71 -56
  41. package/lib/mjs/DataGridRenderers.d.ts +1 -1
  42. package/lib/mjs/DataGridRenderers.js +1 -1
  43. package/lib/mjs/DnDList.js +1 -1
  44. package/lib/mjs/MUUtils.d.ts +0 -9
  45. package/lib/mjs/MUUtils.js +0 -26
  46. package/lib/mjs/MobileListItemRenderer.d.ts +2 -2
  47. package/lib/mjs/ResponsibleContainer.d.ts +2 -7
  48. package/lib/mjs/ResponsibleContainer.js +8 -57
  49. package/lib/mjs/ScrollerListEx.d.ts +24 -9
  50. package/lib/mjs/ScrollerListEx.js +36 -39
  51. package/lib/mjs/SelectEx.js +2 -2
  52. package/lib/mjs/TableEx.d.ts +7 -0
  53. package/lib/mjs/TableEx.js +6 -12
  54. package/lib/mjs/app/ReactApp.d.ts +1 -3
  55. package/lib/mjs/custom/FieldDateInput.js +1 -1
  56. package/lib/mjs/custom/FieldInput.js +1 -1
  57. package/lib/mjs/custom/FieldJson.js +1 -1
  58. package/lib/mjs/custom/FieldNumberInput.js +1 -1
  59. package/lib/mjs/custom/FieldTexarea.js +1 -1
  60. package/lib/mjs/html/HtmlDiv.d.ts +24 -7
  61. package/lib/mjs/html/HtmlDiv.js +2 -1
  62. package/lib/mjs/pages/DataGridPage.js +3 -32
  63. package/lib/mjs/pages/FixedListPage.js +5 -34
  64. package/lib/mjs/pages/ListPage.js +1 -29
  65. package/lib/mjs/pages/ResponsivePage.d.ts +2 -7
  66. package/lib/mjs/uses/useGridCacheInitLoad.d.ts +2 -0
  67. package/lib/mjs/uses/useGridCacheInitLoad.js +35 -0
  68. package/lib/mjs/uses/useListCacheInitLoad.d.ts +2 -0
  69. package/lib/mjs/uses/useListCacheInitLoad.js +32 -0
  70. package/package.json +18 -19
  71. package/setupTests.ts +2 -0
  72. package/src/ButtonPopupCheckbox.tsx +1 -1
  73. package/src/ButtonPopupRadio.tsx +1 -1
  74. package/src/DataGridEx.tsx +151 -108
  75. package/src/DataGridRenderers.tsx +2 -1
  76. package/src/DnDList.tsx +1 -1
  77. package/src/MUUtils.ts +0 -33
  78. package/src/MobileListItemRenderer.tsx +2 -2
  79. package/src/ResponsibleContainer.tsx +21 -94
  80. package/src/ScrollerListEx.tsx +110 -122
  81. package/src/SelectEx.tsx +2 -2
  82. package/src/TableEx.tsx +20 -12
  83. package/src/custom/CustomFieldUtils.tsx +1 -1
  84. package/src/custom/FieldDateInput.tsx +1 -1
  85. package/src/custom/FieldInput.tsx +1 -1
  86. package/src/custom/FieldJson.tsx +1 -1
  87. package/src/custom/FieldNumberInput.tsx +1 -1
  88. package/src/custom/FieldTexarea.tsx +1 -1
  89. package/src/html/HtmlDiv.tsx +13 -9
  90. package/src/pages/DataGridPage.tsx +3 -49
  91. package/src/pages/FixedListPage.tsx +5 -49
  92. package/src/pages/ListPage.tsx +0 -43
  93. package/src/pages/ResponsivePage.tsx +3 -11
  94. package/src/uses/useGridCacheInitLoad.ts +55 -0
  95. package/src/uses/useListCacheInitLoad.ts +51 -0
@@ -9,6 +9,8 @@ import Box from "@mui/material/Box";
9
9
  import TableSortLabel from "@mui/material/TableSortLabel";
10
10
  import Checkbox from "@mui/material/Checkbox";
11
11
  import Paper from "@mui/material/Paper";
12
+ import { GridUtils } from "./GridUtils";
13
+ import { useGridCacheInitLoad } from "./uses/useGridCacheInitLoad";
12
14
  // Borders
13
15
  const boldBorder = "2px solid rgba(224, 224, 224, 1)";
14
16
  const thinBorder = "1px solid rgba(224, 224, 224, 1)";
@@ -128,7 +130,7 @@ export function DataGridEx(props) {
128
130
  }) }));
129
131
  }
130
132
  // Destruct
131
- const { alternatingColors = [theme.palette.grey[100], undefined], borderRowsCount, bottomHeight = 53, checkable = false, className, columns, defaultOrderBy, height, headerHeight = 56, headerRenderer = defaultHeaderRenderer, footerRenderer = defaultFooterRenderer, footerItemRenderer = DataGridRenderers.defaultFooterItemRenderer, hideFooter = false, hoverColor = "#f6f9fb", idField = "id", mRef = React.createRef(), onClick, onDoubleClick, selectable = true, selectedColor = "#edf4fb", width, ...rest } = props;
133
+ const { alternatingColors = [theme.palette.grey[100], undefined], borderRowsCount, bottomHeight = 53, cacheKey, cacheMinutes = 15, checkable = false, className, columns, defaultOrderBy, height, headerHeight = 56, headerRenderer = defaultHeaderRenderer, footerRenderer = defaultFooterRenderer, footerItemRenderer = DataGridRenderers.defaultFooterItemRenderer, hideFooter = false, hoverColor = "#f6f9fb", idField = "id", mRef = React.createRef(), onClick, onDataChange, onDoubleClick, onUpdateRows, selectable = true, selectedColor = "#edf4fb", width, ...rest } = props;
132
134
  if (checkable) {
133
135
  const cbColumn = {
134
136
  field: "selected", // Avoid validation from data model
@@ -161,6 +163,8 @@ export function DataGridEx(props) {
161
163
  columns.unshift(cbColumn);
162
164
  }
163
165
  }
166
+ // Init handler
167
+ const initHandler = useGridCacheInitLoad(cacheKey, cacheMinutes);
164
168
  const refs = React.useRef({});
165
169
  const mRefLocal = useCombinedRefs(mRef, (ref) => {
166
170
  if (ref == null)
@@ -219,57 +223,6 @@ export function DataGridEx(props) {
219
223
  div.classList.remove("DataGridEx-Hover");
220
224
  });
221
225
  };
222
- /**
223
- * Item renderer
224
- */
225
- const itemRenderer = ({ columnIndex, rowIndex, style, data, selectedItems, setItems }) => {
226
- // Column
227
- const { align, cellRenderer = DataGridRenderers.defaultCellRenderer, cellBoxStyle, field, type, valueFormatter, renderProps } = columns[columnIndex];
228
- // Props
229
- const formatProps = {
230
- data,
231
- field,
232
- rowIndex,
233
- columnIndex
234
- };
235
- let rowClass = `DataGridEx-Cell${rowIndex % 2}`;
236
- if (borderRowsCount != null &&
237
- borderRowsCount > 0 &&
238
- (rowIndex + 1) % borderRowsCount === 0) {
239
- rowClass += ` DataGridEx-Cell-Border`;
240
- }
241
- // Selected
242
- const selected = data != null &&
243
- (selectedRowIndex.current === rowIndex ||
244
- selectedItems.some((selectedItem) => selectedItem != null && selectedItem[idField] === data[idField]));
245
- if (selected) {
246
- rowClass += ` DataGridEx-Selected`;
247
- }
248
- // Box style
249
- const boxStyle = data == null || cellBoxStyle == null
250
- ? undefined
251
- : typeof cellBoxStyle === "function"
252
- ? cellBoxStyle(data)
253
- : cellBoxStyle;
254
- const cellProps = {
255
- className: "DataGridEx-Cell",
256
- textAlign: GridAlignGet(align, type),
257
- sx: { ...boxStyle }
258
- };
259
- const child = cellRenderer({
260
- data,
261
- field,
262
- formattedValue: valueFormatter ? valueFormatter(formatProps) : undefined,
263
- selected,
264
- type,
265
- rowIndex,
266
- columnIndex,
267
- cellProps,
268
- renderProps: typeof renderProps === "function" ? renderProps(data) : renderProps,
269
- setItems
270
- });
271
- return (_jsx("div", { className: rowClass, style: style, "data-row": rowIndex, "data-column": columnIndex, onMouseDown: selectable && !checkable ? handleMouseDown : undefined, onMouseOver: selectable ? handleMouseOver : undefined, onMouseOut: selectable ? handleMouseOut : undefined, onClick: (event) => onClick && data != null && onClick(event, data), onDoubleClick: (event) => onDoubleClick && data != null && onDoubleClick(event, data), children: _jsx(Box, { ...cellProps, onMouseEnter: handleMouseEnter, children: child }) }));
272
- };
273
226
  // Column width calculator
274
227
  const widthCalculator = React.useMemo(() => DataGridExCalColumns(columns), [columns]);
275
228
  // Column width
@@ -287,12 +240,74 @@ export function DataGridEx(props) {
287
240
  const sharedWidth = leftWidth > 0 ? leftWidth / widthCalculator.unset : 0;
288
241
  return (column.minWidth || minWidth) + sharedWidth;
289
242
  }, [columns, width]);
243
+ const onUpdateRowsHandler = React.useCallback((rows, state) => {
244
+ GridUtils.getUpdateRowsHandler(cacheKey)?.(rows, state);
245
+ onUpdateRows?.(rows, state);
246
+ }, [onUpdateRows, cacheKey]);
290
247
  // Table
291
248
  const table = React.useMemo(() => {
292
- return (_jsx(ScrollerGrid, { className: Utils.mergeClasses("DataGridEx-Body", "DataGridEx-CustomBar", className, createGridStyle(alternatingColors, selectedColor, hoverColor)), columnCount: columns.length, columnWidth: columnWidth, defaultOrderBy: defaultOrderBy, height: height -
293
- headerHeight -
294
- (hideFooter ? 0 : bottomHeight + 1) -
295
- scrollbarSize, headerRenderer: headerRenderer, idField: idField, itemRenderer: itemRenderer, footerRenderer: hideFooter ? undefined : footerRenderer, width: Math.max(width ?? 0, widthCalculator.total), mRef: mRefLocal, ...rest }));
249
+ return (_jsx(ScrollerGrid, { className: Utils.mergeClasses("DataGridEx-Body", "DataGridEx-CustomBar", className, createGridStyle(alternatingColors, selectedColor, hoverColor)), onCellsRendered: cacheKey
250
+ ? (visibleCells) => sessionStorage.setItem(`${cacheKey}-scroll`, JSON.stringify(visibleCells))
251
+ : undefined, onInitLoad: initHandler, onUpdateRows: onUpdateRowsHandler, cellComponent: ({ rowIndex, columnIndex, style, rows, states }) => {
252
+ // Column
253
+ const { align, cellRenderer = DataGridRenderers.defaultCellRenderer, cellBoxStyle, field, type, valueFormatter, renderProps } = columns[columnIndex];
254
+ // Data
255
+ const data = rows[rowIndex];
256
+ // Props
257
+ const formatProps = {
258
+ data,
259
+ field,
260
+ rowIndex,
261
+ columnIndex
262
+ };
263
+ let rowClass = `DataGridEx-Cell${rowIndex % 2}`;
264
+ if (borderRowsCount != null &&
265
+ borderRowsCount > 0 &&
266
+ (rowIndex + 1) % borderRowsCount === 0) {
267
+ rowClass += ` DataGridEx-Cell-Border`;
268
+ }
269
+ // Selected
270
+ const selected = data != null &&
271
+ (selectedRowIndex.current === rowIndex ||
272
+ states.selectedItems.some((selectedItem) => selectedItem != null &&
273
+ selectedItem[idField] === data[idField]));
274
+ if (selected) {
275
+ rowClass += ` DataGridEx-Selected`;
276
+ }
277
+ // Box style
278
+ const boxStyle = data == null || cellBoxStyle == null
279
+ ? undefined
280
+ : typeof cellBoxStyle === "function"
281
+ ? cellBoxStyle(data)
282
+ : cellBoxStyle;
283
+ const cellProps = {
284
+ className: "DataGridEx-Cell",
285
+ textAlign: GridAlignGet(align, type),
286
+ sx: { ...boxStyle }
287
+ };
288
+ const child = cellRenderer({
289
+ data,
290
+ field,
291
+ formattedValue: valueFormatter
292
+ ? valueFormatter(formatProps)
293
+ : undefined,
294
+ selected,
295
+ type,
296
+ rowIndex,
297
+ columnIndex,
298
+ cellProps,
299
+ renderProps: typeof renderProps === "function"
300
+ ? renderProps(data)
301
+ : renderProps,
302
+ triggerChange: () => onDataChange?.(rows, rowIndex, columnIndex)
303
+ });
304
+ return (_jsx("div", { className: rowClass, style: style, "data-row": rowIndex, "data-column": columnIndex, onMouseDown: selectable && !checkable ? handleMouseDown : undefined, onMouseOver: selectable ? handleMouseOver : undefined, onMouseOut: selectable ? handleMouseOut : undefined, onClick: (event) => onClick && data != null && onClick(event, data), onDoubleClick: (event) => onDoubleClick && data != null && onDoubleClick(event, data), children: _jsx(Box, { ...cellProps, onMouseEnter: handleMouseEnter, children: child }) }));
305
+ }, columnCount: columns.length, columnWidth: columnWidth, defaultOrderBy: defaultOrderBy, height: typeof height === "number"
306
+ ? height -
307
+ headerHeight -
308
+ (hideFooter ? 0 : bottomHeight + 1) -
309
+ scrollbarSize
310
+ : height, headerRenderer: headerRenderer, idField: idField, footerRenderer: hideFooter ? undefined : footerRenderer, width: Math.max(width ?? 0, widthCalculator.total), mRef: mRefLocal, ...rest }));
296
311
  }, [width]);
297
312
  return (_jsx(Paper, { sx: {
298
313
  fontSize: "0.875rem",
@@ -10,7 +10,7 @@ export declare namespace DataGridRenderers {
10
10
  * @param param Props
11
11
  * @returns Component
12
12
  */
13
- function defaultCellRenderer<T extends Record<string, any>>({ cellProps, data, field, formattedValue, columnIndex, type, renderProps }: GridCellRendererProps<T>): React.ReactNode;
13
+ function defaultCellRenderer<T extends Record<string, any>>({ cellProps, data, field, formattedValue, columnIndex, type, renderProps, triggerChange }: GridCellRendererProps<T>): React.ReactNode;
14
14
  /**
15
15
  * Default footer item renderer
16
16
  * @param rows Rows
@@ -15,7 +15,7 @@ export var DataGridRenderers;
15
15
  * @param param Props
16
16
  * @returns Component
17
17
  */
18
- function defaultCellRenderer({ cellProps, data, field, formattedValue, columnIndex, type, renderProps }) {
18
+ function defaultCellRenderer({ cellProps, data, field, formattedValue, columnIndex, type, renderProps, triggerChange }) {
19
19
  // Is loading
20
20
  if (data == null) {
21
21
  // First column, show loading indicator
@@ -167,7 +167,7 @@ export function DnDList(props) {
167
167
  ? input.removeEventListener("change", doFormChange)
168
168
  : input.addEventListener("change", doFormChange));
169
169
  };
170
- const divRef = React.useRef();
170
+ const divRef = React.useRef(null);
171
171
  if (dnd == null) {
172
172
  return _jsx(Skeleton, { variant: "rectangular", width: "100%", height: height });
173
173
  }
@@ -1,5 +1,3 @@
1
- import { QueryRQ } from "@etsoo/appscript";
2
- import { IdType } from "@etsoo/shared";
3
1
  import { GridApiCommunity } from "@mui/x-data-grid/internals";
4
2
  /**
5
3
  * MU utilities
@@ -12,11 +10,4 @@ export declare namespace MUUtils {
12
10
  * @returns Results
13
11
  */
14
12
  function getGridData<T>(grid: GridApiCommunity, checkField: keyof T | ((item: T) => boolean)): T[];
15
- /**
16
- * Setup paging keysets
17
- * @param data Paging data
18
- * @param lastItem Last item of the query
19
- * @param idField Id field
20
- */
21
- function setupPagingKeysets<T, K extends IdType = number>(data: QueryRQ<K>, lastItem: T | undefined, idField: keyof T & string): QueryRQ<K>;
22
13
  }
@@ -26,30 +26,4 @@ export var MUUtils;
26
26
  return items;
27
27
  }
28
28
  MUUtils.getGridData = getGridData;
29
- /**
30
- * Setup paging keysets
31
- * @param data Paging data
32
- * @param lastItem Last item of the query
33
- * @param idField Id field
34
- */
35
- function setupPagingKeysets(data, lastItem, idField) {
36
- // If the id field is not set for ordering, add it with descending
37
- if (typeof data.queryPaging === "object") {
38
- const orderBy = (data.queryPaging.orderBy ??= []);
39
- const idUpper = idField.toUpperCase();
40
- if (!orderBy.find((o) => o.field.toUpperCase() === idUpper)) {
41
- orderBy.push({ field: idField, desc: true, unique: true });
42
- }
43
- // Set the paging keysets
44
- if (lastItem) {
45
- const keysets = orderBy.map((o) => Reflect.get(lastItem, o.field));
46
- data.queryPaging.keysets = keysets;
47
- }
48
- else {
49
- data.queryPaging.keysets = undefined;
50
- }
51
- }
52
- return data;
53
- }
54
- MUUtils.setupPagingKeysets = setupPagingKeysets;
55
29
  })(MUUtils || (MUUtils = {}));
@@ -1,6 +1,6 @@
1
1
  import { ListItemReact } from "@etsoo/react";
2
2
  import React from "react";
3
- import { ScrollerListExInnerItemRendererProps } from "./ScrollerListEx";
3
+ import { ScrollerListExItemRendererProps } from "./ScrollerListEx";
4
4
  /**
5
5
  * Default mobile list item renderer
6
6
  * @param param0 List renderer props
@@ -8,7 +8,7 @@ import { ScrollerListExInnerItemRendererProps } from "./ScrollerListEx";
8
8
  * @param renderer Renderer for card content
9
9
  * @returns Component
10
10
  */
11
- export declare function MobileListItemRenderer<T>({ data, itemHeight, margins }: ScrollerListExInnerItemRendererProps<T>, renderer: (data: T) => [
11
+ export declare function MobileListItemRenderer<T>({ data, itemHeight, margins }: ScrollerListExItemRendererProps<T>, renderer: (data: T) => [
12
12
  string,
13
13
  string | undefined,
14
14
  React.ReactNode | (ListItemReact | boolean)[],
@@ -1,8 +1,7 @@
1
1
  import React from "react";
2
- import { ListChildComponentProps } from "react-window";
3
2
  import { GridColumn, GridJsonData, GridMethodRef, GridTemplateType } from "@etsoo/react";
4
3
  import { DataGridExProps } from "./DataGridEx";
5
- import { ScrollerListExInnerItemRendererProps, ScrollerListExItemSize } from "./ScrollerListEx";
4
+ import { ScrollerListExItemSize, ScrollerListExProps } from "./ScrollerListEx";
6
5
  import { SxProps, Theme } from "@mui/material/styles";
7
6
  /**
8
7
  * ResponsibleContainer props
@@ -41,14 +40,10 @@ export type ResponsibleContainerProps<T extends object, F> = Omit<DataGridExProp
41
40
  * Grid height
42
41
  */
43
42
  height?: number;
44
- /**
45
- * Inner item renderer
46
- */
47
- innerItemRenderer: (props: ScrollerListExInnerItemRendererProps<T>) => React.ReactNode;
48
43
  /**
49
44
  * Item renderer
50
45
  */
51
- itemRenderer?: (props: ListChildComponentProps<T>) => React.ReactElement;
46
+ itemRenderer?: ScrollerListExProps<T>["itemRenderer"];
52
47
  /**
53
48
  * Item size, a function indicates its a variable size list
54
49
  */
@@ -36,6 +36,8 @@ export function ResponsibleContainer(props) {
36
36
  if (ref == null)
37
37
  return;
38
38
  state.ref = ref;
39
+ if (ref.element && elementReady)
40
+ elementReady(ref.element, true);
39
41
  });
40
42
  // Screen size detection
41
43
  const showDataGrid = useMediaQuery("(min-width:600px)");
@@ -77,44 +79,6 @@ export function ResponsibleContainer(props) {
77
79
  state.rect = rect;
78
80
  return false;
79
81
  });
80
- const onInitLoad = (ref) => {
81
- // Avoid repeatedly load from cache
82
- if (refs.current.initLoaded || !cacheKey)
83
- return undefined;
84
- // Cache data
85
- const cacheData = GridUtils.getCacheData(cacheKey, cacheMinutes);
86
- if (cacheData) {
87
- const { rows, state } = cacheData;
88
- GridUtils.mergeSearchData(state, searchData);
89
- // Scroll position
90
- const scrollData = sessionStorage.getItem(`${cacheKey}-scroll`);
91
- if (scrollData) {
92
- if ("resetAfterColumnIndex" in ref) {
93
- const { scrollLeft, scrollTop } = JSON.parse(scrollData);
94
- globalThis.setTimeout(() => ref.scrollTo({ scrollLeft, scrollTop }), 0);
95
- }
96
- else {
97
- const { scrollOffset } = JSON.parse(scrollData);
98
- globalThis.setTimeout(() => ref.scrollTo(scrollOffset), 0);
99
- }
100
- }
101
- // Update flag value
102
- refs.current.initLoaded = true;
103
- // Return cached rows and state
104
- return [rows, state];
105
- }
106
- return undefined;
107
- };
108
- const onListScroll = (props) => {
109
- if (!cacheKey || !refs.current.initLoaded)
110
- return;
111
- sessionStorage.setItem(`${cacheKey}-scroll`, JSON.stringify(props));
112
- };
113
- const onGridScroll = (props) => {
114
- if (!cacheKey || !refs.current.initLoaded)
115
- return;
116
- sessionStorage.setItem(`${cacheKey}-scroll`, JSON.stringify(props));
117
- };
118
82
  // Rect
119
83
  const rect = dimensions[0][2];
120
84
  // Create list
@@ -144,26 +108,13 @@ export function ResponsibleContainer(props) {
144
108
  if (adjustFabHeight)
145
109
  heightLocal = adjustFabHeight(heightLocal, showDataGrid);
146
110
  if (showDataGrid) {
147
- // Delete
148
- delete rest.itemRenderer;
149
- return (_jsx(Box, { className: "DataGridBox", children: _jsx(DataGridEx, { autoLoad: !hasFields, height: heightLocal, width: rect.width, loadData: localLoadData, mRef: mRefs, onDoubleClick: (_, data) => quickAction && quickAction(data), outerRef: (element) => {
150
- if (element != null && elementReady)
151
- elementReady(element, true);
152
- }, onScroll: onGridScroll, columns: columns, onUpdateRows: GridUtils.getUpdateRowsHandler(cacheKey), onInitLoad: onInitLoad, ...rest }) }));
111
+ // Remove useless props
112
+ const { itemRenderer, ...gridProps } = rest;
113
+ return (_jsx(Box, { className: "DataGridBox", children: _jsx(DataGridEx, { autoLoad: !hasFields, height: heightLocal, width: rect.width, loadData: localLoadData, mRef: mRefs, onDoubleClick: (_, data) => quickAction && quickAction(data), columns: columns, ...gridProps }) }));
153
114
  }
154
- // Delete
155
- delete rest.checkable;
156
- delete rest.borderRowsCount;
157
- delete rest.bottomHeight;
158
- delete rest.footerItemRenderer;
159
- delete rest.headerHeight;
160
- delete rest.hideFooter;
161
- delete rest.hoverColor;
162
- delete rest.selectable;
163
- return (_jsx(Box, { className: "ListBox", sx: { height: heightLocal }, children: _jsx(ScrollerListEx, { autoLoad: !hasFields, height: heightLocal, loadData: localLoadData, onUpdateRows: GridUtils.getUpdateRowsHandler(cacheKey), onInitLoad: onInitLoad, mRef: mRefs, onClick: (event, data) => quickAction && ReactUtils.isSafeClick(event) && quickAction(data), oRef: (element) => {
164
- if (element != null && elementReady)
165
- elementReady(element, false);
166
- }, onScroll: onListScroll, ...rest }) }));
115
+ // Remove useless props
116
+ const { checkable, borderRowsCount, bottomHeight, footerItemRenderer, headerHeight, hideFooter, hoverColor, selectable, ...listProps } = rest;
117
+ return (_jsx(Box, { className: "ListBox", sx: { height: heightLocal }, children: _jsx(ScrollerListEx, { autoLoad: !hasFields, height: heightLocal, loadData: localLoadData, mRef: mRefs, onClick: (event, data) => quickAction && ReactUtils.isSafeClick(event) && quickAction(data), ...listProps }) }));
167
118
  })();
168
119
  const searchBar = React.useMemo(() => {
169
120
  if (!hasFields ||
@@ -1,15 +1,22 @@
1
1
  import { ScrollerListProps } from "@etsoo/react";
2
2
  import React from "react";
3
- import { ListChildComponentProps } from "react-window";
4
3
  import { MouseEventWithDataHandler } from "./MUGlobal";
5
4
  /**
6
5
  * Extended ScrollerList inner item renderer props
7
6
  */
8
- export interface ScrollerListExInnerItemRendererProps<T> extends ListChildComponentProps<T> {
7
+ export type ScrollerListExItemRendererProps<T> = {
9
8
  /**
10
- * Item selected
9
+ * Row index
11
10
  */
12
- selected: boolean;
11
+ index: number;
12
+ /**
13
+ * Row data
14
+ */
15
+ data: T;
16
+ /**
17
+ * Style
18
+ */
19
+ style: React.CSSProperties;
13
20
  /**
14
21
  * Item height
15
22
  */
@@ -22,7 +29,11 @@ export interface ScrollerListExInnerItemRendererProps<T> extends ListChildCompon
22
29
  * Default margins
23
30
  */
24
31
  margins: object;
25
- }
32
+ /**
33
+ * Item selected
34
+ */
35
+ selected: boolean;
36
+ };
26
37
  /**
27
38
  * Extended ScrollerList ItemSize type
28
39
  * 1. Callback function
@@ -33,19 +44,23 @@ export type ScrollerListExItemSize = ((index: number) => [number, number] | [num
33
44
  /**
34
45
  * Extended ScrollerList Props
35
46
  */
36
- export type ScrollerListExProps<T extends object> = Omit<ScrollerListProps<T>, "itemRenderer" | "itemSize"> & {
47
+ export type ScrollerListExProps<T extends object> = Omit<ScrollerListProps<T>, "rowComponent" | "rowHeight" | "onClick" | "onDoubleClick" | "onInitLoad"> & {
37
48
  /**
38
49
  * Alternating colors for odd/even rows
39
50
  */
40
51
  alternatingColors?: [string?, string?];
41
52
  /**
42
- * Inner item renderer
53
+ * Cache key
54
+ */
55
+ cacheKey?: string;
56
+ /**
57
+ * Cache minutes
43
58
  */
44
- innerItemRenderer: (props: ScrollerListExInnerItemRendererProps<T>) => React.ReactNode;
59
+ cacheMinutes?: number;
45
60
  /**
46
61
  * Item renderer
47
62
  */
48
- itemRenderer?: (props: ListChildComponentProps<T>) => React.ReactElement;
63
+ itemRenderer?: (props: ScrollerListExItemRendererProps<T>) => React.ReactNode;
49
64
  /**
50
65
  * Item size, a function indicates its a variable size list
51
66
  */
@@ -5,6 +5,9 @@ import { Utils } from "@etsoo/shared";
5
5
  import React from "react";
6
6
  import { MUGlobal } from "./MUGlobal";
7
7
  import { useTheme } from "@mui/material/styles";
8
+ import { GridUtils } from "./GridUtils";
9
+ import { useListCacheInitLoad } from "./uses/useListCacheInitLoad";
10
+ import Box from "@mui/material/Box";
8
11
  // Scroll bar size
9
12
  const scrollbarSize = 16;
10
13
  // Selected class name
@@ -68,24 +71,6 @@ const defaultMargin = (margin, horizon) => {
68
71
  marginBottom: half
69
72
  };
70
73
  };
71
- // Default itemRenderer
72
- function defaultItemRenderer({ index, innerItemRenderer, data, onMouseDown, selected, style, itemHeight, onClick, onDoubleClick, space, margins }) {
73
- // Child
74
- const child = innerItemRenderer({
75
- index,
76
- data,
77
- style,
78
- selected,
79
- itemHeight,
80
- space,
81
- margins
82
- });
83
- let rowClass = `ScrollerListEx-Row${index % 2}`;
84
- if (selected)
85
- rowClass += ` ${selectedClassName}`;
86
- // Layout
87
- return (_jsx("div", { className: rowClass, style: style, onMouseDown: (event) => onMouseDown(event.currentTarget, data), onClick: (event) => onClick && onClick(event, data), onDoubleClick: (event) => onDoubleClick && onDoubleClick(event, data), children: child }));
88
- }
89
74
  /**
90
75
  * Extended ScrollerList
91
76
  * @param props Props
@@ -93,7 +78,7 @@ function defaultItemRenderer({ index, innerItemRenderer, data, onMouseDown, sele
93
78
  */
94
79
  export function ScrollerListEx(props) {
95
80
  // Selected item ref
96
- const selectedItem = React.useRef();
81
+ const selectedItem = React.useRef(null);
97
82
  const onMouseDown = (div, data) => {
98
83
  // Destruct
99
84
  const [selectedDiv, selectedData] = selectedItem.current ?? [];
@@ -113,20 +98,12 @@ export function ScrollerListEx(props) {
113
98
  return selected;
114
99
  };
115
100
  // Destruct
116
- const { alternatingColors = [undefined, undefined], className, idField = "id", innerItemRenderer, itemSize, itemRenderer = (itemProps) => {
117
- const [itemHeight, space, margins] = calculateItemSize(itemProps.index);
118
- return defaultItemRenderer({
119
- itemHeight,
120
- innerItemRenderer,
121
- onMouseDown,
122
- onClick,
123
- onDoubleClick,
124
- space,
125
- margins,
126
- selected: isSelected(itemProps.data),
127
- ...itemProps
128
- });
129
- }, onClick, onDoubleClick, onSelectChange, selectedColor = "#edf4fb", ...rest } = props;
101
+ const { alternatingColors = [undefined, undefined], className, cacheKey, cacheMinutes = 15, idField = "id", itemSize, itemRenderer = ({ data, itemHeight, margins }) => (_jsx(Box, { component: "pre", sx: {
102
+ height: itemHeight,
103
+ ...margins
104
+ }, children: JSON.stringify(data) })), onClick, onDoubleClick, onUpdateRows, onSelectChange, selectedColor = "#edf4fb", ...rest } = props;
105
+ // Init handler
106
+ const initHandler = useListCacheInitLoad(cacheKey, cacheMinutes);
130
107
  // Theme
131
108
  const theme = useTheme();
132
109
  // Cache calculation
@@ -150,11 +127,31 @@ export function ScrollerListEx(props) {
150
127
  // Calculation
151
128
  return itemSizeResult;
152
129
  };
153
- // Local item size
154
- const itemSizeLocal = (index) => {
155
- const [size, space] = calculateItemSize(index);
156
- return size + space;
157
- };
130
+ const onUpdateRowsHandler = React.useCallback((rows, state) => {
131
+ GridUtils.getUpdateRowsHandler(cacheKey)?.(rows, state);
132
+ onUpdateRows?.(rows, state);
133
+ }, [onUpdateRows, cacheKey]);
158
134
  // Layout
159
- return (_jsx(ScrollerList, { className: Utils.mergeClasses("ScrollerListEx-Body", className, createGridStyle(alternatingColors, selectedColor)), idField: idField, itemRenderer: itemRenderer, itemSize: itemSizeLocal, ...rest }));
135
+ return (_jsx(ScrollerList, { className: Utils.mergeClasses("ScrollerListEx-Body", className, createGridStyle(alternatingColors, selectedColor)), idField: idField, onRowsRendered: cacheKey
136
+ ? (visibleRows) => sessionStorage.setItem(`${cacheKey}-scroll`, JSON.stringify(visibleRows))
137
+ : undefined, onInitLoad: initHandler, onUpdateRows: onUpdateRowsHandler, rowComponent: ({ index, items, style }) => {
138
+ const data = items[index];
139
+ const selected = isSelected(data);
140
+ const rowClass = `ScrollerListEx-Row${index % 2}${selected ? ` ${selectedClassName}` : ""}`;
141
+ const [itemHeight, space, margins] = calculateItemSize(index);
142
+ // Child
143
+ const child = itemRenderer({
144
+ index,
145
+ data,
146
+ style,
147
+ selected,
148
+ itemHeight,
149
+ space,
150
+ margins
151
+ });
152
+ return (_jsx("div", { className: rowClass, style: style, onMouseDown: (event) => onMouseDown(event.currentTarget, data), onClick: (event) => onClick && onClick(event, data), onDoubleClick: (event) => onDoubleClick && onDoubleClick(event, data), children: child }));
153
+ }, rowHeight: (index) => {
154
+ const [size, space] = calculateItemSize(index);
155
+ return size + space;
156
+ }, ...rest }));
160
157
  }
@@ -63,7 +63,7 @@ export function SelectEx(props) {
63
63
  }, [options, propertyWay, setOptionsAdd]);
64
64
  // Value state
65
65
  const [valueState, setValueStateBase] = React.useState(valueSource);
66
- const valueRef = React.useRef();
66
+ const valueRef = React.useRef(null);
67
67
  const setValueState = (newValue) => {
68
68
  valueRef.current = newValue;
69
69
  setValueStateBase(newValue);
@@ -108,7 +108,7 @@ export function SelectEx(props) {
108
108
  : option[labelField];
109
109
  };
110
110
  // Refs
111
- const divRef = React.useRef();
111
+ const divRef = React.useRef(null);
112
112
  // Refresh list data
113
113
  const refreshData = () => {
114
114
  if (loadData == null)
@@ -43,6 +43,13 @@ export type TableExProps<T extends object, D extends DataTypes.Keys<T>> = TableP
43
43
  * Methods
44
44
  */
45
45
  mRef?: React.Ref<TableExMethodRef<T>>;
46
+ /**
47
+ * Data change handler
48
+ * @param rows Rows
49
+ * @param rowIndex Row index
50
+ * @param columnIndex Column index
51
+ */
52
+ onDataChange?: (rows: T[], rowIndex: number, columnIndex: number) => void;
46
53
  /**
47
54
  * On items select change
48
55
  */
@@ -27,7 +27,7 @@ export function TableEx(props) {
27
27
  // Theme
28
28
  const theme = useTheme();
29
29
  // Destruct
30
- const { alternatingColors = [theme.palette.action.hover, undefined], autoLoad = true, columns, defaultOrderBy, headerColors = [undefined, undefined], idField = "id", loadBatchSize, loadData, maxHeight, mRef, onSelectChange, rowHeight = 53, otherHeight = 110, threshold, ...rest } = props;
30
+ const { alternatingColors = [theme.palette.action.hover, undefined], autoLoad = true, columns, defaultOrderBy, headerColors = [undefined, undefined], idField = "id", loadBatchSize, loadData, maxHeight, mRef, onDataChange, onSelectChange, rowHeight = 53, otherHeight = 110, threshold, ...rest } = props;
31
31
  const selectable = onSelectChange != null;
32
32
  // Rows per page
33
33
  let rowsPerPageLocal;
@@ -83,6 +83,9 @@ export function TableEx(props) {
83
83
  });
84
84
  };
85
85
  React.useImperativeHandle(mRef, () => ({
86
+ get element() {
87
+ return null;
88
+ },
86
89
  delete(index) {
87
90
  const item = rows.at(index);
88
91
  if (item) {
@@ -97,20 +100,11 @@ export function TableEx(props) {
97
100
  newRows.splice(start, 0, item);
98
101
  setRows(newRows);
99
102
  },
100
- /**
101
- * Refresh data
102
- */
103
103
  refresh() {
104
104
  loadDataLocal();
105
105
  },
106
- /**
107
- * Reset
108
- */
109
106
  reset,
110
- scrollToRef(scrollOffset) {
111
- // Not implemented
112
- },
113
- scrollToItemRef(index) {
107
+ scrollToRow(param) {
114
108
  // Not implemented
115
109
  }
116
110
  }), []);
@@ -284,7 +278,7 @@ export function TableEx(props) {
284
278
  rowIndex,
285
279
  columnIndex,
286
280
  cellProps,
287
- setItems
281
+ triggerChange: () => onDataChange?.(rows, rowIndex, columnIndex)
288
282
  })) : (_jsx(React.Fragment, { children: "\u00A0" }));
289
283
  return (_jsx(TableCell, { ...cellProps, children: child }, `${rowId}${columnIndex}`));
290
284
  })] }, rowId));
@@ -173,9 +173,7 @@ export declare class ReactApp<S extends IAppSettings, D extends IUser> extends C
173
173
  * @param props Props
174
174
  */
175
175
  showInputDialog({ title, message, callback, ...rest }: InputDialogProps): INotificationReact;
176
- stateDetector(props: IStateProps): React.FunctionComponentElement<{
177
- children?: React.ReactNode | undefined;
178
- }>;
176
+ stateDetector(props: IStateProps): React.FunctionComponentElement<React.FragmentProps>;
179
177
  /**
180
178
  * User login extended
181
179
  * @param user New user
@@ -10,7 +10,7 @@ import Typography from "@mui/material/Typography";
10
10
  */
11
11
  export const FieldDateInput = ({ field, mref, onChange, defaultValue }) => {
12
12
  // Ref
13
- const inputRef = React.useRef();
13
+ const inputRef = React.useRef(null);
14
14
  const getValue = () => inputRef.current == null
15
15
  ? undefined
16
16
  : DateUtils.parse(inputRef.current.value);