@etsoo/materialui 1.4.24 → 1.4.26

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 (50) hide show
  1. package/__tests__/ComboBox.tsx +19 -25
  2. package/__tests__/CustomFields.tsx +2 -2
  3. package/__tests__/NotifierMUTests.tsx +15 -15
  4. package/__tests__/ReactAppTests.tsx +17 -0
  5. package/__tests__/ResponsePage.tsx +7 -6
  6. package/__tests__/SelectEx.tsx +4 -4
  7. package/__tests__/SwitchAnt.tsx +1 -1
  8. package/__tests__/tsconfig.json +3 -2
  9. package/lib/BackButton.js +1 -1
  10. package/lib/ButtonLink.js +1 -1
  11. package/lib/ComboBoxMultiple.js +2 -2
  12. package/lib/DataSteps.js +1 -1
  13. package/lib/EmailInput.js +1 -1
  14. package/lib/GridUtils.js +1 -1
  15. package/lib/IconButtonLink.js +1 -1
  16. package/lib/InputField.js +1 -1
  17. package/lib/ListChooser.js +1 -1
  18. package/lib/LoadingButton.js +1 -1
  19. package/lib/MUGlobal.js +37 -37
  20. package/lib/MaskInput.js +2 -2
  21. package/lib/MoreFab.js +1 -1
  22. package/lib/NotifierMU.js +31 -34
  23. package/lib/SearchField.js +1 -1
  24. package/lib/ShowDataComparison.js +3 -3
  25. package/lib/TabBox.js +1 -1
  26. package/lib/TextFieldEx.js +1 -1
  27. package/lib/app/ReactApp.d.ts +1 -1
  28. package/lib/app/ReactApp.js +33 -4
  29. package/lib/app/ServiceApp.js +15 -3
  30. package/lib/custom/CustomFieldWindow.js +1 -1
  31. package/lib/pages/DataGridPage.js +1 -1
  32. package/lib/pages/DataGridPageProps.d.ts +1 -1
  33. package/lib/pages/FixedListPage.js +1 -1
  34. package/lib/pages/ListPage.js +1 -1
  35. package/lib/pages/ResponsivePage.js +1 -1
  36. package/lib/pages/TablePage.js +1 -1
  37. package/lib/pages/UserMenu.js +1 -1
  38. package/package.json +13 -31
  39. package/src/BackButton.tsx +1 -1
  40. package/src/ButtonLink.tsx +1 -1
  41. package/src/ComboBoxMultiple.tsx +7 -9
  42. package/src/IconButtonLink.tsx +1 -1
  43. package/src/MUGlobal.ts +1 -1
  44. package/src/MoreFab.tsx +1 -1
  45. package/src/TabBox.tsx +1 -1
  46. package/src/app/ReactApp.ts +1 -1
  47. package/src/pages/DataGridPageProps.ts +1 -1
  48. package/src/pages/UserMenu.tsx +1 -1
  49. package/tsconfig.json +1 -1
  50. package/vite.config.ts +11 -0
@@ -1,30 +1,24 @@
1
- import React from 'react';
2
- import { ComboBox } from '../src';
3
- import { fireEvent, render, screen } from '@testing-library/react';
4
- //import '@testing-library/jest-dom/extend-expect';
1
+ import React from "react";
2
+ import { ComboBox } from "../src";
3
+ import { fireEvent, render, screen } from "@testing-library/react";
5
4
 
6
- it('Render ComboBox', async () => {
7
- // Arrange
8
- type T = { id: number; name: string };
9
- const options: T[] = [
10
- { id: 1, name: 'Name 1' },
11
- { id: 2, name: 'Name 2' }
12
- ];
5
+ it("Render ComboBox", async () => {
6
+ // Arrange
7
+ type T = { id: number; name: string };
8
+ const options: T[] = [
9
+ { id: 1, name: "Name 1" },
10
+ { id: 2, name: "Name 2" }
11
+ ];
13
12
 
14
- render(
15
- <ComboBox<T>
16
- name="Test"
17
- options={options}
18
- labelField="name"
19
- label="Test"
20
- />
21
- );
13
+ render(
14
+ <ComboBox<T> name="Test" options={options} labelField="name" label="Test" />
15
+ );
22
16
 
23
- // Act, click the list
24
- const clicked = fireEvent.click(screen.getByRole('button'));
25
- expect(clicked).toBeTruthy();
17
+ // Act, click the list
18
+ const clicked = fireEvent.click(screen.getByRole("button"));
19
+ expect(clicked).toBeTruthy();
26
20
 
27
- // Get list item
28
- const item = screen.getByText('Name 1');
29
- expect(item.nodeName).toBe('LI');
21
+ // Get list item
22
+ const item = screen.getByText("Name 1");
23
+ expect(item.nodeName).toBe("LI");
30
24
  });
@@ -141,9 +141,9 @@ it("Render FieldSelect", async () => {
141
141
 
142
142
  act(() => {
143
143
  // Act, click to open the dropdown list
144
- jest.useFakeTimers();
144
+ vi.useFakeTimers();
145
145
  fireEvent.mouseDown(button);
146
- jest.advanceTimersByTime(100);
146
+ vi.advanceTimersByTime(100);
147
147
  });
148
148
 
149
149
  const input = document.querySelector<HTMLInputElement>("input");
@@ -29,11 +29,11 @@ const notifier = NotifierMU.instance;
29
29
 
30
30
  // Timer mock
31
31
  // https://jestjs.io/docs/en/timer-mocks
32
- jest.useFakeTimers();
32
+ vi.useFakeTimers();
33
33
 
34
34
  test("Alert tests", async () => {
35
35
  // Click
36
- const handleClick = jest.fn();
36
+ const handleClick = vi.fn();
37
37
 
38
38
  act(() => {
39
39
  // Add the notification
@@ -53,12 +53,12 @@ test("Alert tests", async () => {
53
53
  expect(handleClick).toHaveBeenCalled();
54
54
 
55
55
  // Fast forward
56
- jest.runOnlyPendingTimers();
56
+ vi.runOnlyPendingTimers();
57
57
  });
58
58
 
59
59
  test("Confirm tests", async () => {
60
60
  // Click
61
- const handleClick = jest.fn();
61
+ const handleClick = vi.fn();
62
62
 
63
63
  act(() => {
64
64
  // Add the notification
@@ -78,12 +78,12 @@ test("Confirm tests", async () => {
78
78
  expect(handleClick).toHaveBeenCalled();
79
79
 
80
80
  // Fast forward
81
- jest.runOnlyPendingTimers();
81
+ vi.runOnlyPendingTimers();
82
82
  });
83
83
 
84
84
  test("Confirm tests without cancel button", async () => {
85
85
  // Click
86
- const handleClick = jest.fn();
86
+ const handleClick = vi.fn();
87
87
 
88
88
  act(() => {
89
89
  // Add the notification
@@ -105,12 +105,12 @@ test("Confirm tests without cancel button", async () => {
105
105
  expect(handleClick).toHaveBeenCalled();
106
106
 
107
107
  // Fast forward
108
- jest.runOnlyPendingTimers();
108
+ vi.runOnlyPendingTimers();
109
109
  });
110
110
 
111
111
  test("Prompt tests", async () => {
112
112
  // Click
113
- const handleClick = jest.fn((result: boolean) => {
113
+ const handleClick = vi.fn((result: boolean) => {
114
114
  expect(result).toBeTruthy();
115
115
  });
116
116
 
@@ -134,12 +134,12 @@ test("Prompt tests", async () => {
134
134
 
135
135
  expect(handleClick).toHaveBeenCalled();
136
136
 
137
- jest.runOnlyPendingTimers();
137
+ vi.runOnlyPendingTimers();
138
138
  });
139
139
 
140
140
  test("Prompt tests with form submit", async () => {
141
141
  // Click
142
- const handleClick = jest.fn((result: boolean) => {
142
+ const handleClick = vi.fn((result: boolean) => {
143
143
  expect(result).toBeTruthy();
144
144
  });
145
145
 
@@ -161,12 +161,12 @@ test("Prompt tests with form submit", async () => {
161
161
 
162
162
  expect(handleClick).toHaveBeenCalled();
163
163
 
164
- jest.runOnlyPendingTimers();
164
+ vi.runOnlyPendingTimers();
165
165
  });
166
166
 
167
- test("Message tests", (done) => {
167
+ test("Message tests", () => {
168
168
  // Callback
169
- const callback = jest.fn(() => done());
169
+ const callback = vi.fn();
170
170
 
171
171
  let n: INotification<React.ReactNode, any> | undefined;
172
172
  act(() => {
@@ -190,7 +190,7 @@ test("Message tests", (done) => {
190
190
  n?.dismiss();
191
191
 
192
192
  // Fast forward
193
- jest.runOnlyPendingTimers();
193
+ vi.runOnlyPendingTimers();
194
194
  });
195
195
 
196
196
  expect(root.innerHTML).not.toContain("Error Message");
@@ -209,7 +209,7 @@ test("Loading tests", () => {
209
209
  notifier.hideLoading();
210
210
 
211
211
  // Fast forward
212
- jest.runOnlyPendingTimers();
212
+ vi.runOnlyPendingTimers();
213
213
  });
214
214
 
215
215
  expect(root.innerHTML).not.toContain("Loading");
@@ -107,3 +107,20 @@ test("Test for properties", () => {
107
107
 
108
108
  expect(root.innerHTML).toContain(result.title);
109
109
  });
110
+
111
+ test("Test for alertResult", () => {
112
+ const result: IActionResult = {
113
+ ok: false,
114
+ type: "TokenExpired",
115
+ title: "您的令牌已过期",
116
+ data: {}
117
+ };
118
+
119
+ act(() => {
120
+ app.alertResult(result);
121
+ });
122
+
123
+ expect(root.innerHTML).toContain(
124
+ '<span style="font-size: 9px;">(TokenExpired)</span>'
125
+ );
126
+ });
@@ -2,17 +2,17 @@ import { act, render } from "@testing-library/react";
2
2
  import { MUGlobal, MobileListItemRenderer, ResponsivePage } from "../src";
3
3
  import React from "react";
4
4
 
5
- global.ResizeObserver = jest.fn().mockImplementation(() => ({
6
- observe: jest.fn(),
7
- unobserve: jest.fn(),
8
- disconnect: jest.fn()
5
+ globalThis.ResizeObserver = vi.fn().mockImplementation(() => ({
6
+ observe: vi.fn(),
7
+ unobserve: vi.fn(),
8
+ disconnect: vi.fn()
9
9
  }));
10
10
 
11
11
  type Data = { id: number; name: string };
12
12
 
13
13
  // Timer mock
14
14
  // https://jestjs.io/docs/en/timer-mocks
15
- jest.useFakeTimers();
15
+ vi.useFakeTimers();
16
16
 
17
17
  // TypeScript const assertions
18
18
  const fieldTemplate = {
@@ -29,6 +29,7 @@ it("Render ResponsePage", async () => {
29
29
  { field: "id", header: "ID" },
30
30
  { field: "name", header: "Name" }
31
31
  ]}
32
+ height={200}
32
33
  itemSize={[118, MUGlobal.pagePaddings]}
33
34
  fieldTemplate={fieldTemplate}
34
35
  loadData={({ id }) =>
@@ -48,7 +49,7 @@ it("Render ResponsePage", async () => {
48
49
 
49
50
  act(() => {
50
51
  // Fast forward
51
- jest.runOnlyPendingTimers();
52
+ vi.runOnlyPendingTimers();
52
53
  });
53
54
 
54
55
  // Assert
@@ -13,7 +13,7 @@ it("Render SelectEx", async () => {
13
13
 
14
14
  Utils.addBlankItem(options, "id", "name");
15
15
 
16
- const itemChangeCallback = jest.fn((option, userAction) => {
16
+ const itemChangeCallback = vi.fn((option, userAction) => {
17
17
  if (userAction) expect(option).toBeUndefined();
18
18
  else expect(option.id).toBe(1);
19
19
  });
@@ -37,11 +37,11 @@ it("Render SelectEx", async () => {
37
37
 
38
38
  // https://davidwcai.medium.com/react-testing-library-and-the-not-wrapped-in-act-errors-491a5629193b
39
39
  act(() => {
40
- jest.useFakeTimers();
40
+ vi.useFakeTimers();
41
41
 
42
42
  fireEvent.mouseDown(button); // Not click
43
43
 
44
- jest.advanceTimersByTime(100);
44
+ vi.advanceTimersByTime(100).useRealTimers();
45
45
  });
46
46
 
47
47
  // Get list item
@@ -67,7 +67,7 @@ it("Render multiple SelectEx", async () => {
67
67
  { id: "3", label: "Name 3" }
68
68
  ];
69
69
 
70
- const itemChangeCallback = jest.fn((option, userAction) => {
70
+ const itemChangeCallback = vi.fn((option, userAction) => {
71
71
  if (userAction) expect(option.id).toBe("3");
72
72
  else expect(option.id).toBe("1");
73
73
  });
@@ -3,7 +3,7 @@ import React, { act } from "react";
3
3
  import { SwitchAnt } from "../src/SwitchAnt";
4
4
 
5
5
  it("SwitchAnt Tests", () => {
6
- const onChange = jest.fn((event: React.ChangeEvent<HTMLInputElement>) =>
6
+ const onChange = vi.fn((event: React.ChangeEvent<HTMLInputElement>) =>
7
7
  expect(event.target.checked).toBeTruthy()
8
8
  );
9
9
 
@@ -2,7 +2,7 @@
2
2
  "compilerOptions": {
3
3
  "target": "ES2020",
4
4
  "module": "ESNext",
5
- "moduleResolution": "Node10",
5
+ "moduleResolution": "bundler",
6
6
  "allowJs": false,
7
7
  "skipLibCheck": true,
8
8
  "esModuleInterop": true,
@@ -13,7 +13,8 @@
13
13
  "isolatedModules": true,
14
14
  "noEmit": true,
15
15
  "jsx": "react",
16
- "declaration": true
16
+ "declaration": true,
17
+ "types": ["vitest/globals"]
17
18
  },
18
19
  "include": [".."]
19
20
  }
package/lib/BackButton.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { IconButton, useTheme } from "@mui/material";
3
3
  import ArrowBackIcon from "@mui/icons-material/ArrowBack";
4
- import { useNavigate } from "react-router-dom";
4
+ import { useNavigate } from "react-router";
5
5
  /**
6
6
  * BackButton
7
7
  * @param props Props
package/lib/ButtonLink.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Button } from "@mui/material";
3
- import { useNavigate } from "react-router-dom";
3
+ import { useNavigate } from "react-router";
4
4
  /**
5
5
  * ButtonLink
6
6
  * @param props Props
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Keyboard } from "@etsoo/shared";
3
3
  import { Autocomplete, Checkbox } from "@mui/material";
4
4
  import React from "react";
@@ -20,7 +20,7 @@ export function ComboBoxMultiple(props) {
20
20
  // Labels
21
21
  const labels = globalApp?.getLabels("noOptions", "loading");
22
22
  // Destruct
23
- const { search = false, autoAddBlankItem = search, idField = "id", idValue, idValues, inputError, inputHelperText, inputMargin, inputOnChange, inputRequired, inputReset, inputVariant, defaultValue, label, labelField = "label", loadData, onLoadData, name, inputAutoComplete = "off", options, dataReadonly = true, readOnly, onChange, openOnFocus = true, value, disableCloseOnSelect = true, renderOption = (props, option, { selected }) => (_jsx("li", { ...props, children: _jsxs(_Fragment, { children: [_jsx(Checkbox, { icon: icon, checkedIcon: checkedIcon, style: { marginRight: 8 }, checked: selected }), option[labelField]] }) })), getOptionLabel = (option) => `${option[labelField]}`, sx = { minWidth: "150px" }, noOptionsText = labels?.noOptions, loadingText = labels?.loading, ...rest } = props;
23
+ const { search = false, autoAddBlankItem = search, idField = "id", idValue, idValues, inputError, inputHelperText, inputMargin, inputOnChange, inputRequired, inputReset, inputVariant, defaultValue, label, labelField = "label", loadData, onLoadData, name, inputAutoComplete = "off", options, dataReadonly = true, readOnly, onChange, openOnFocus = true, value, disableCloseOnSelect = true, renderOption = (props, option, { selected }) => (_jsxs("li", { ...props, children: [_jsx(Checkbox, { icon: icon, checkedIcon: checkedIcon, style: { marginRight: 8 }, checked: selected }), `${option[labelField]}`] })), getOptionLabel = (option) => `${option[labelField]}`, sx = { minWidth: "150px" }, noOptionsText = labels?.noOptions, loadingText = labels?.loading, ...rest } = props;
24
24
  // Value input ref
25
25
  const inputRef = React.createRef();
26
26
  // Options state
package/lib/DataSteps.js CHANGED
@@ -25,7 +25,7 @@ export function DataSteps(props) {
25
25
  // Destruct
26
26
  const { InputLabelProps = {}, jsonValue, valueFormatter = (_data) => "...", onValueChange, steps, value = "", ...rest } = props;
27
27
  // Shrink
28
- InputLabelProps.shrink ?? (InputLabelProps.shrink = MUGlobal.searchFieldShrink);
28
+ InputLabelProps.shrink ??= MUGlobal.searchFieldShrink;
29
29
  // Current index
30
30
  const indexRef = React.useRef(-1);
31
31
  // Current Json data
package/lib/EmailInput.js CHANGED
@@ -8,7 +8,7 @@ export function EmailInput(props) {
8
8
  // Destruct
9
9
  const { inputProps = {}, ...rest } = props;
10
10
  // Default max length
11
- inputProps.maxLength ?? (inputProps.maxLength = 128);
11
+ inputProps.maxLength ??= 128;
12
12
  // Layout
13
13
  return _jsx(TextField, { type: "email", fullWidth: true, inputProps: inputProps, ...rest });
14
14
  }
package/lib/GridUtils.js CHANGED
@@ -79,7 +79,7 @@ export var GridUtils;
79
79
  function mergeSearchData(state, searchData) {
80
80
  if (searchData == null)
81
81
  return;
82
- state.data ?? (state.data = {});
82
+ state.data ??= {};
83
83
  Object.assign(state.data, searchData);
84
84
  }
85
85
  GridUtils.mergeSearchData = mergeSearchData;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { IconButton } from "@mui/material";
3
- import { useNavigate } from "react-router-dom";
3
+ import { useNavigate } from "react-router";
4
4
  /**
5
5
  * IconButtonLink
6
6
  * @param props Props
package/lib/InputField.js CHANGED
@@ -12,7 +12,7 @@ export const InputField = React.forwardRef((props, ref) => {
12
12
  // Destruct
13
13
  const { changeDelay, InputLabelProps = {}, InputProps = {}, onChange, onChangeDelay, readOnly, size = MUGlobal.inputFieldSize, variant = MUGlobal.inputFieldVariant, ...rest } = props;
14
14
  // Shrink
15
- InputLabelProps.shrink ?? (InputLabelProps.shrink = MUGlobal.searchFieldShrink);
15
+ InputLabelProps.shrink ??= MUGlobal.searchFieldShrink;
16
16
  // Read only
17
17
  if (readOnly != null)
18
18
  InputProps.readOnly = readOnly;
@@ -41,7 +41,7 @@ export function ListChooser(props) {
41
41
  return (_jsx(ListItem, { disableGutters: true, secondaryAction: sp.selected ? _jsx(CheckBoxIcon, { fontSize: "small" }) : undefined, children: _jsx(ListItemButton, { ...sp, children: _jsx(ListItemText, { primary: label }) }) }, `${id}`));
42
42
  }, idField = "id", labelField = "label", loadData, multiple = false, onItemChange, title, doubleClickEnabled = false, onDoubleClick, ...rest } = props;
43
43
  // Default minimum height
44
- rest.sx ?? (rest.sx = { minHeight: "220px" });
44
+ rest.sx ??= { minHeight: "220px" };
45
45
  // State
46
46
  const [items, setItems] = React.useState([]);
47
47
  // Query request data
@@ -9,7 +9,7 @@ export function LoadingButton(props) {
9
9
  // Destruct
10
10
  const { endIcon, loadingIconProps = {}, onClick, ...rest } = props;
11
11
  // Default size
12
- loadingIconProps.size ?? (loadingIconProps.size = 12);
12
+ loadingIconProps.size ??= 12;
13
13
  // State
14
14
  // https://stackoverflow.com/questions/55265255/react-usestate-hook-event-handler-using-initial-state
15
15
  const [loading, setLoading] = React.useState(false);
package/lib/MUGlobal.js CHANGED
@@ -1,9 +1,41 @@
1
1
  import { NumberUtils } from "@etsoo/shared";
2
- import { Link } from "react-router-dom";
2
+ import { Link } from "react-router";
3
3
  /**
4
4
  * MUGlobal for global configurations
5
5
  */
6
6
  export class MUGlobal {
7
+ /**
8
+ * Search field shrink
9
+ */
10
+ static searchFieldShrink = true;
11
+ /**
12
+ * Search field size
13
+ */
14
+ static searchFieldSize = "small";
15
+ /**
16
+ * Search field variant
17
+ */
18
+ static searchFieldVariant = "outlined";
19
+ /**
20
+ * Input field shrink
21
+ */
22
+ static inputFieldShrink = true;
23
+ /**
24
+ * Input field size
25
+ */
26
+ static inputFieldSize = "medium";
27
+ /**
28
+ * Input field variant
29
+ */
30
+ static inputFieldVariant = "outlined";
31
+ /**
32
+ * TextField variant
33
+ */
34
+ static textFieldVariant = "filled";
35
+ /**
36
+ * Page default paddings
37
+ */
38
+ static pagePaddings = { xs: 2, sm: 3 };
7
39
  /**
8
40
  * Get menu item props
9
41
  * @param path Current path
@@ -104,6 +136,10 @@ export class MUGlobal {
104
136
  });
105
137
  return newObj;
106
138
  }
139
+ /**
140
+ * Break points defined
141
+ */
142
+ static breakpoints = ["xs", "sm", "md", "lg", "xl"];
107
143
  /**
108
144
  * Get multple medias theme space
109
145
  * Responsive values and Breakpoints as an object
@@ -144,39 +180,3 @@ export class MUGlobal {
144
180
  return newObj;
145
181
  }
146
182
  }
147
- /**
148
- * Search field shrink
149
- */
150
- MUGlobal.searchFieldShrink = true;
151
- /**
152
- * Search field size
153
- */
154
- MUGlobal.searchFieldSize = "small";
155
- /**
156
- * Search field variant
157
- */
158
- MUGlobal.searchFieldVariant = "outlined";
159
- /**
160
- * Input field shrink
161
- */
162
- MUGlobal.inputFieldShrink = true;
163
- /**
164
- * Input field size
165
- */
166
- MUGlobal.inputFieldSize = "medium";
167
- /**
168
- * Input field variant
169
- */
170
- MUGlobal.inputFieldVariant = "outlined";
171
- /**
172
- * TextField variant
173
- */
174
- MUGlobal.textFieldVariant = "filled";
175
- /**
176
- * Page default paddings
177
- */
178
- MUGlobal.pagePaddings = { xs: 2, sm: 3 };
179
- /**
180
- * Break points defined
181
- */
182
- MUGlobal.breakpoints = ["xs", "sm", "md", "lg", "xl"];
package/lib/MaskInput.js CHANGED
@@ -24,9 +24,9 @@ export function MaskInput(props) {
24
24
  });
25
25
  const localValue = defaultValue ?? value;
26
26
  // Shrink
27
- InputLabelProps.shrink ?? (InputLabelProps.shrink = search
27
+ InputLabelProps.shrink ??= search
28
28
  ? MUGlobal.searchFieldShrink
29
- : MUGlobal.inputFieldShrink);
29
+ : MUGlobal.inputFieldShrink;
30
30
  // Read only
31
31
  if (readOnly != null)
32
32
  InputProps.readOnly = readOnly;
package/lib/MoreFab.js CHANGED
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
3
3
  import React from "react";
4
4
  import { Divider, Fab, IconButton, ListItemIcon, ListItemText, Menu, MenuItem } from "@mui/material";
5
- import { Link } from "react-router-dom";
5
+ import { Link } from "react-router";
6
6
  function getActions(input) {
7
7
  // Actions
8
8
  const actions = [];
package/lib/NotifierMU.js CHANGED
@@ -39,19 +39,19 @@ export class NotificationMU extends NotificationReact {
39
39
  let icon;
40
40
  if (this.type === NotificationMessageType.Success) {
41
41
  icon = _jsx(Done, { color: "primary" });
42
- title ?? (title = labels.success);
42
+ title ??= labels.success;
43
43
  }
44
44
  else if (this.type === NotificationMessageType.Info) {
45
45
  icon = _jsx(Info, {});
46
- title ?? (title = labels.info);
46
+ title ??= labels.info;
47
47
  }
48
48
  else if (this.type === NotificationMessageType.Warning) {
49
49
  icon = _jsx(Warning, { color: "secondary" });
50
- title ?? (title = labels.warning);
50
+ title ??= labels.warning;
51
51
  }
52
52
  else {
53
53
  icon = _jsx(Error, { color: "error" });
54
- title ?? (title = labels.alertTitle);
54
+ title ??= labels.alertTitle;
55
55
  }
56
56
  const setupProps = {
57
57
  color: "primary"
@@ -247,35 +247,6 @@ export class NotificationMU extends NotificationReact {
247
247
  * Antd notifier
248
248
  */
249
249
  export class NotifierMU extends NotifierReact {
250
- constructor() {
251
- super(...arguments);
252
- /**
253
- * Create align container
254
- * @param align Align
255
- * @param children Children
256
- * @param options Other options
257
- */
258
- this.createContainer = (align, children) => {
259
- // Each align group, class equal to something similar to 'align-topleft'
260
- const alignText = NotificationAlign[align].toLowerCase();
261
- let className = `align-${alignText}`;
262
- if (children.length === 0) {
263
- return _jsx("div", { className: className }, `empty-${alignText}`);
264
- }
265
- if (align === NotificationAlign.Unknown) {
266
- // div container for style control
267
- return (_jsx("div", { className: className, children: children }, `div-${alignText}`));
268
- }
269
- // Use SnackBar for layout
270
- return (_jsx(Snackbar, { anchorOrigin: NotifierMU.getOrigin(align), className: className, sx: align === NotificationAlign.Center
271
- ? { position: "fixed", top: "50%!important" }
272
- : undefined, open: true, children: _jsx(Box, { display: "flex", flexDirection: "column", flexWrap: "nowrap", sx: {
273
- "& > :not(style) + :not(style)": {
274
- marginTop: (theme) => theme.spacing(1)
275
- }
276
- }, children: children }, `box-${alignText}`) }, `layout-${alignText}`));
277
- };
278
- }
279
250
  /**
280
251
  * Create state and return provider
281
252
  * @param className Style class name
@@ -283,7 +254,7 @@ export class NotifierMU extends NotifierReact {
283
254
  * @returns Provider
284
255
  */
285
256
  static setup(className, debug = false) {
286
- className ?? (className = "notifier-mu");
257
+ className ??= "notifier-mu";
287
258
  // Create an instance
288
259
  const instance = new NotifierMU();
289
260
  instance.debug = debug;
@@ -334,6 +305,32 @@ export class NotifierMU extends NotifierReact {
334
305
  vertical: "top"
335
306
  };
336
307
  }
308
+ /**
309
+ * Create align container
310
+ * @param align Align
311
+ * @param children Children
312
+ * @param options Other options
313
+ */
314
+ createContainer = (align, children) => {
315
+ // Each align group, class equal to something similar to 'align-topleft'
316
+ const alignText = NotificationAlign[align].toLowerCase();
317
+ let className = `align-${alignText}`;
318
+ if (children.length === 0) {
319
+ return _jsx("div", { className: className }, `empty-${alignText}`);
320
+ }
321
+ if (align === NotificationAlign.Unknown) {
322
+ // div container for style control
323
+ return (_jsx("div", { className: className, children: children }, `div-${alignText}`));
324
+ }
325
+ // Use SnackBar for layout
326
+ return (_jsx(Snackbar, { anchorOrigin: NotifierMU.getOrigin(align), className: className, sx: align === NotificationAlign.Center
327
+ ? { position: "fixed", top: "50%!important" }
328
+ : undefined, open: true, children: _jsx(Box, { display: "flex", flexDirection: "column", flexWrap: "nowrap", sx: {
329
+ "& > :not(style) + :not(style)": {
330
+ marginTop: (theme) => theme.spacing(1)
331
+ }
332
+ }, children: children }, `box-${alignText}`) }, `layout-${alignText}`));
333
+ };
337
334
  /**
338
335
  * Add raw definition
339
336
  * @param data Notification data definition
@@ -12,7 +12,7 @@ export function SearchField(props) {
12
12
  // Destruct
13
13
  const { changeDelay, InputLabelProps = {}, InputProps = {}, onChange, readOnly, size = MUGlobal.searchFieldSize, variant = MUGlobal.searchFieldVariant, ...rest } = props;
14
14
  // Shrink
15
- InputLabelProps.shrink ?? (InputLabelProps.shrink = MUGlobal.searchFieldShrink);
15
+ InputLabelProps.shrink ??= MUGlobal.searchFieldShrink;
16
16
  // Read only
17
17
  if (readOnly != null)
18
18
  InputProps.readOnly = readOnly;
@@ -38,10 +38,10 @@ export const ShowDataComparison = (data, modelTitle, getLabel, equalCheck = true
38
38
  }
39
39
  // Labels
40
40
  const { dataComparison, field, newValue, oldValue } = app.getLabels("dataComparison", "field", "newValue", "oldValue");
41
- modelTitle ?? (modelTitle = dataComparison);
42
- getLabel ?? (getLabel = (key) => {
41
+ modelTitle ??= dataComparison;
42
+ getLabel ??= (key) => {
43
43
  return app.get(Utils.formatInitial(key)) ?? key;
44
- });
44
+ };
45
45
  const keys = new Set([
46
46
  ...Object.keys(data.oldData),
47
47
  ...Object.keys(data.newData)
package/lib/TabBox.js CHANGED
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Utils } from "@etsoo/shared";
3
3
  import { Box, Tab, Tabs } from "@mui/material";
4
4
  import React from "react";
5
- import { Link } from "react-router-dom";
5
+ import { Link } from "react-router";
6
6
  /**
7
7
  * Tabs with box
8
8
  * @param props Props
@@ -12,7 +12,7 @@ export const TextFieldEx = React.forwardRef((props, ref) => {
12
12
  // Destructure
13
13
  const { changeDelay, clearLabel = clearInput, error, fullWidth = true, helperText, InputLabelProps = {}, InputProps = {}, onChange, onClear, onKeyDown, onEnter, onVisibility, inputRef, readOnly, showClear, showPassword, type, variant = MUGlobal.textFieldVariant, ...rest } = props;
14
14
  // Shrink
15
- InputLabelProps.shrink ?? (InputLabelProps.shrink = MUGlobal.searchFieldShrink);
15
+ InputLabelProps.shrink ??= MUGlobal.searchFieldShrink;
16
16
  // State
17
17
  const [errorText, updateErrorText] = React.useState();
18
18
  const [empty, updateEmpty] = React.useState(true);
@@ -3,7 +3,7 @@ import { INotifier, NotificationReturn } from "@etsoo/notificationbase";
3
3
  import { DataTypes, IActionResult } from "@etsoo/shared";
4
4
  import React from "react";
5
5
  import { CultureAction, CultureState, INotificationReact, InputDialogProps, IPageData, IStateProps, NotificationReactCallProps, PageAction, PageState, UserAction, UserState } from "@etsoo/react";
6
- import { NavigateFunction, NavigateOptions } from "react-router-dom";
6
+ import { NavigateFunction, NavigateOptions } from "react-router";
7
7
  /**
8
8
  * React Application Type
9
9
  */
@@ -51,6 +51,7 @@ export function ReactAppStateDetector(props) {
51
51
  * React application
52
52
  */
53
53
  export class ReactApp extends CoreApp {
54
+ static _notifierProvider;
54
55
  /**
55
56
  * Get notifier provider
56
57
  */
@@ -62,6 +63,38 @@ export class ReactApp extends CoreApp {
62
63
  ReactApp._notifierProvider = NotifierMU.setup(undefined, debug);
63
64
  return NotifierMU.instance;
64
65
  }
66
+ /**
67
+ * Culture state
68
+ */
69
+ cultureState;
70
+ /**
71
+ * Page state
72
+ */
73
+ pageState;
74
+ /**
75
+ * User state
76
+ */
77
+ userState = new UserState();
78
+ /**
79
+ * Is screen size down 'sm'
80
+ */
81
+ smDown;
82
+ /**
83
+ * Is screen size up 'md'
84
+ */
85
+ mdUp;
86
+ /**
87
+ * Navigate function
88
+ */
89
+ navigateFunction;
90
+ /**
91
+ * Page state dispatch
92
+ */
93
+ pageStateDispatch;
94
+ /**
95
+ * User state dispatch
96
+ */
97
+ userStateDispatch;
65
98
  /**
66
99
  * Constructor
67
100
  * @param settings Settings
@@ -70,10 +103,6 @@ export class ReactApp extends CoreApp {
70
103
  */
71
104
  constructor(settings, name, debug = false) {
72
105
  super(settings, null, ReactApp.createNotifier(debug), new WindowStorage(), name, debug);
73
- /**
74
- * User state
75
- */
76
- this.userState = new UserState();
77
106
  if (BridgeUtils.host) {
78
107
  BridgeUtils.host.onUpdate((app, version) => {
79
108
  this.notifier.message(NotificationMessageType.Success, this.get("updateTip") + `(${[app, version].join(", ")})`, this.get("updateReady"));
@@ -9,6 +9,18 @@ const coreTokenKey = "core-refresh-token";
9
9
  * Use the new acess token and refresh token to login
10
10
  */
11
11
  export class ServiceApp extends ReactApp {
12
+ /**
13
+ * Core endpoint
14
+ */
15
+ coreEndpoint;
16
+ /**
17
+ * Core system API
18
+ */
19
+ coreApi;
20
+ /**
21
+ * Core system origin
22
+ */
23
+ coreOrigin;
12
24
  /**
13
25
  * Constructor
14
26
  * @param settings Settings
@@ -115,12 +127,12 @@ export class ServiceApp extends ReactApp {
115
127
  const { refreshToken } = user;
116
128
  this.userLogin(user, refreshToken, dispatch);
117
129
  // Core system login
118
- core ?? (core = {
130
+ core ??= {
119
131
  refreshToken,
120
132
  accessToken: user.token,
121
133
  tokenType: user.tokenScheme ?? "Bearer",
122
134
  expiresIn: user.seconds
123
- });
135
+ };
124
136
  // Cache the core system refresh token
125
137
  this.saveCoreToken(core.refreshToken);
126
138
  this.exchangeTokenAll(core, coreName);
@@ -138,7 +150,7 @@ export class ServiceApp extends ReactApp {
138
150
  */
139
151
  async tryLogin(params) {
140
152
  // Destruct
141
- params ?? (params = {});
153
+ params ??= {};
142
154
  let { onFailure, onSuccess, ...rest } = params;
143
155
  if (onFailure == null) {
144
156
  onFailure = params.onFailure = () => {
@@ -53,7 +53,7 @@ export function CustomFieldWindow(props) {
53
53
  return (_jsxs(React.Fragment, { children: [_jsx("input", { type: "hidden", name: inputName, ref: inputRef }), children((customFields, jsonData) => {
54
54
  const collections = {};
55
55
  let data = {};
56
- jsonData ?? (jsonData = inputRef?.current?.value);
56
+ jsonData ??= inputRef?.current?.value;
57
57
  if (jsonData) {
58
58
  const [d, pc] = parseJsonData(jsonData);
59
59
  data = d;
@@ -15,7 +15,7 @@ import { GridUtils } from "../GridUtils";
15
15
  export function DataGridPage(props) {
16
16
  // Destruct
17
17
  const { adjustHeight, fields, fieldTemplate, height, loadData, mRef, sizeReadyMiliseconds = 100, pageProps = {}, cacheKey, cacheMinutes = 15, ...rest } = props;
18
- pageProps.paddings ?? (pageProps.paddings = MUGlobal.pagePaddings);
18
+ pageProps.paddings ??= MUGlobal.pagePaddings;
19
19
  // States
20
20
  const [states, setStates] = React.useReducer((currentState, newState) => {
21
21
  return { ...currentState, ...newState };
@@ -4,7 +4,7 @@ import type { SearchPageProps } from "./SearchPageProps";
4
4
  /**
5
5
  * DataGrid page props
6
6
  */
7
- export type DataGridPageProps<T extends object, F extends DataTypes.BasicTemplate> = SearchPageProps<T, F> & DataGridExProps<T> & {
7
+ export type DataGridPageProps<T extends object, F extends DataTypes.BasicTemplate> = SearchPageProps<T, F> & Omit<DataGridExProps<T>, "loadData"> & {
8
8
  /**
9
9
  * Height will be deducted
10
10
  * @param height Current calcuated height
@@ -15,7 +15,7 @@ import { GridUtils } from "../GridUtils";
15
15
  export function FixedListPage(props) {
16
16
  // Destruct
17
17
  const { adjustHeight, fields, fieldTemplate, loadData, mRef, sizeReadyMiliseconds = 0, pageProps = {}, cacheKey, cacheMinutes = 15, ...rest } = props;
18
- pageProps.paddings ?? (pageProps.paddings = MUGlobal.pagePaddings);
18
+ pageProps.paddings ??= MUGlobal.pagePaddings;
19
19
  // States
20
20
  const [states] = React.useState({});
21
21
  const initLoadedRef = React.useRef();
@@ -15,7 +15,7 @@ import { GridUtils } from "../GridUtils";
15
15
  export function ListPage(props) {
16
16
  // Destruct
17
17
  const { fields, fieldTemplate, loadData, mRef, pageProps = {}, cacheKey, cacheMinutes = 15, ...rest } = props;
18
- pageProps.paddings ?? (pageProps.paddings = MUGlobal.pagePaddings);
18
+ pageProps.paddings ??= MUGlobal.pagePaddings;
19
19
  // States
20
20
  const [states] = React.useState({});
21
21
  const refs = useCombinedRefs(mRef, (ref) => {
@@ -12,7 +12,7 @@ import { OperationMessageContainer } from "../messages/OperationMessageContainer
12
12
  export function ResponsivePage(props) {
13
13
  // Destruct
14
14
  const { pageProps = {}, operationMessageHandler, ...rest } = props;
15
- pageProps.paddings ?? (pageProps.paddings = MUGlobal.pagePaddings);
15
+ pageProps.paddings ??= MUGlobal.pagePaddings;
16
16
  const { paddings, fabColumnDirection, ...pageRest } = pageProps;
17
17
  // State
18
18
  const [scrollContainer, setScrollContainer] = React.useState();
@@ -15,7 +15,7 @@ import { GridUtils } from "../GridUtils";
15
15
  export function TablePage(props) {
16
16
  // Destruct
17
17
  const { columns, fields, fieldTemplate, loadData, mRef, sizeReadyMiliseconds = 0, pageProps = {}, cacheKey, cacheMinutes = 15, ...rest } = props;
18
- pageProps.paddings ?? (pageProps.paddings = MUGlobal.pagePaddings);
18
+ pageProps.paddings ??= MUGlobal.pagePaddings;
19
19
  // States
20
20
  const [states] = React.useState({});
21
21
  const refs = useCombinedRefs(mRef, (ref) => {
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { EventWatcher } from "@etsoo/react";
3
3
  import { IconButton, Menu } from "@mui/material";
4
4
  import React from "react";
5
- import { useNavigate } from "react-router-dom";
5
+ import { useNavigate } from "react-router";
6
6
  import { UserAvatar } from "../UserAvatar";
7
7
  /**
8
8
  * Event watcher
package/package.json CHANGED
@@ -1,28 +1,13 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.4.24",
3
+ "version": "1.4.26",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/index.js",
6
+ "type": "module",
6
7
  "types": "lib/index.d.ts",
7
8
  "scripts": {
8
9
  "build": "tsc",
9
- "format": "prettier --write src/**/*.{ts,tsx}",
10
- "lint": "eslint --ext .ts,.tsx src/",
11
- "test": "jest",
12
- "test:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand"
13
- },
14
- "jest": {
15
- "automock": false,
16
- "testMatch": [
17
- "<rootDir>/__tests__/**/*.{ts,tsx}"
18
- ],
19
- "testEnvironment": "jsdom",
20
- "transform": {
21
- "^.+\\.[t|j]sx?$": "babel-jest"
22
- },
23
- "transformIgnorePatterns": [
24
- "/node_modules/(?!(@etsoo)/)"
25
- ]
10
+ "test": "vitest"
26
11
  },
27
12
  "repository": {
28
13
  "type": "git",
@@ -45,15 +30,15 @@
45
30
  },
46
31
  "homepage": "https://github.com/ETSOO/ReactMU#readme",
47
32
  "dependencies": {
48
- "@dnd-kit/core": "^6.1.0",
49
- "@dnd-kit/sortable": "^8.0.0",
33
+ "@dnd-kit/core": "^6.2.0",
34
+ "@dnd-kit/sortable": "^9.0.0",
50
35
  "@emotion/css": "^11.13.5",
51
36
  "@emotion/react": "^11.13.5",
52
37
  "@emotion/styled": "^11.13.5",
53
- "@etsoo/appscript": "^1.5.66",
54
- "@etsoo/notificationbase": "^1.1.53",
55
- "@etsoo/react": "^1.7.97",
56
- "@etsoo/shared": "^1.2.54",
38
+ "@etsoo/appscript": "^1.5.68",
39
+ "@etsoo/notificationbase": "^1.1.54",
40
+ "@etsoo/react": "^1.8.3",
41
+ "@etsoo/shared": "^1.2.55",
57
42
  "@mui/icons-material": "^6.1.8",
58
43
  "@mui/material": "^6.1.8",
59
44
  "@mui/x-data-grid": "^7.22.3",
@@ -77,9 +62,7 @@
77
62
  "@babel/preset-react": "^7.25.9",
78
63
  "@babel/preset-typescript": "^7.26.0",
79
64
  "@babel/runtime-corejs3": "^7.26.0",
80
- "@testing-library/jest-dom": "^6.6.3",
81
65
  "@testing-library/react": "^16.0.1",
82
- "@types/jest": "^29.5.14",
83
66
  "@types/pica": "^9.0.4",
84
67
  "@types/pulltorefreshjs": "^0.1.7",
85
68
  "@types/react": "^18.3.12",
@@ -87,10 +70,9 @@
87
70
  "@types/react-dom": "^18.3.1",
88
71
  "@types/react-input-mask": "^3.0.6",
89
72
  "@types/react-window": "^1.8.8",
90
- "@typescript-eslint/eslint-plugin": "^8.15.0",
91
- "@typescript-eslint/parser": "^8.15.0",
92
- "jest": "^29.7.0",
93
- "jest-environment-jsdom": "^29.7.0",
94
- "typescript": "^5.6.3"
73
+ "@vitejs/plugin-react": "^4.3.3",
74
+ "jsdom": "^25.0.1",
75
+ "typescript": "^5.7.2",
76
+ "vitest": "^2.1.5"
95
77
  }
96
78
  }
@@ -1,7 +1,7 @@
1
1
  import { IconButton, IconButtonProps, useTheme } from "@mui/material";
2
2
  import ArrowBackIcon from "@mui/icons-material/ArrowBack";
3
3
  import React from "react";
4
- import { useNavigate } from "react-router-dom";
4
+ import { useNavigate } from "react-router";
5
5
 
6
6
  /**
7
7
  * BackButton props
@@ -1,6 +1,6 @@
1
1
  import { Button, ButtonProps } from "@mui/material";
2
2
  import React from "react";
3
- import { useNavigate } from "react-router-dom";
3
+ import { useNavigate } from "react-router";
4
4
  /**
5
5
  * ButtonLink props
6
6
  */
@@ -111,15 +111,13 @@ export function ComboBoxMultiple<
111
111
  disableCloseOnSelect = true,
112
112
  renderOption = (props, option, { selected }) => (
113
113
  <li {...props}>
114
- <>
115
- <Checkbox
116
- icon={icon}
117
- checkedIcon={checkedIcon}
118
- style={{ marginRight: 8 }}
119
- checked={selected}
120
- />
121
- {option[labelField]}
122
- </>
114
+ <Checkbox
115
+ icon={icon}
116
+ checkedIcon={checkedIcon}
117
+ style={{ marginRight: 8 }}
118
+ checked={selected}
119
+ />
120
+ {`${option[labelField]}`}
123
121
  </li>
124
122
  ),
125
123
  getOptionLabel = (option: T) => `${option[labelField]}`,
@@ -1,6 +1,6 @@
1
1
  import { IconButton, IconButtonProps } from "@mui/material";
2
2
  import React from "react";
3
- import { useNavigate } from "react-router-dom";
3
+ import { useNavigate } from "react-router";
4
4
 
5
5
  /**
6
6
  * IconButtonLink props
package/src/MUGlobal.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { NumberUtils } from "@etsoo/shared";
2
2
  import { Breakpoint, ListItemButtonProps, Theme } from "@mui/material";
3
- import { Link } from "react-router-dom";
3
+ import { Link } from "react-router";
4
4
 
5
5
  /**
6
6
  * Mouse event handler with data
package/src/MoreFab.tsx CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  PaperProps,
13
13
  PopoverOrigin
14
14
  } from "@mui/material";
15
- import { Link } from "react-router-dom";
15
+ import { Link } from "react-router";
16
16
  import { ListItemReact } from "@etsoo/react";
17
17
 
18
18
  /**
package/src/TabBox.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Utils } from "@etsoo/shared";
2
2
  import { Box, BoxProps, Tab, TabProps, Tabs, TabsProps } from "@mui/material";
3
3
  import React from "react";
4
- import { Link } from "react-router-dom";
4
+ import { Link } from "react-router";
5
5
 
6
6
  /**
7
7
  * Tab with box panel props
@@ -35,7 +35,7 @@ import {
35
35
  UserCalls,
36
36
  UserState
37
37
  } from "@etsoo/react";
38
- import { NavigateFunction, NavigateOptions } from "react-router-dom";
38
+ import { NavigateFunction, NavigateOptions } from "react-router";
39
39
 
40
40
  /**
41
41
  * React Application Type
@@ -9,7 +9,7 @@ export type DataGridPageProps<
9
9
  T extends object,
10
10
  F extends DataTypes.BasicTemplate
11
11
  > = SearchPageProps<T, F> &
12
- DataGridExProps<T> & {
12
+ Omit<DataGridExProps<T>, "loadData"> & {
13
13
  /**
14
14
  * Height will be deducted
15
15
  * @param height Current calcuated height
@@ -1,7 +1,7 @@
1
1
  import { EventWatcher } from "@etsoo/react";
2
2
  import { IconButton, Menu } from "@mui/material";
3
3
  import React from "react";
4
- import { useNavigate } from "react-router-dom";
4
+ import { useNavigate } from "react-router";
5
5
  import { UserAvatar } from "../UserAvatar";
6
6
 
7
7
  /**
package/tsconfig.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  /* Visit https://aka.ms/tsconfig.json to read more about this file */
4
- "target": "ES2020",
4
+ "target": "ES2022",
5
5
  "module": "ESNext",
6
6
  "moduleResolution": "bundler",
7
7
  "allowJs": false,
package/vite.config.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "vitest/config";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ test: {
7
+ globals: true,
8
+ environment: "jsdom",
9
+ include: ["__tests__/**/*.ts(x)?"]
10
+ }
11
+ });