mui-fast-start 0.3.1 → 0.3.3

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 (64) hide show
  1. package/README.md +403 -403
  2. package/README_KR.md +403 -403
  3. package/dist/components/Object/Select/ObjSelectRecord.d.ts +2 -1
  4. package/dist/components/Object/Select/ObjSelectRecord.d.ts.map +1 -1
  5. package/dist/components/Single/Select/SingleSelectRecord.d.ts +2 -1
  6. package/dist/components/Single/Select/SingleSelectRecord.d.ts.map +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/styles/FastStartProps.d.ts +2 -1
  9. package/dist/styles/FastStartProps.d.ts.map +1 -1
  10. package/dist/types/props.d.ts +1 -1
  11. package/dist/types/props.d.ts.map +1 -1
  12. package/dist/types/props.internal.d.ts +1 -1
  13. package/dist/types/props.internal.d.ts.map +1 -1
  14. package/dist/types/provider.d.ts +2 -1
  15. package/dist/types/provider.d.ts.map +1 -1
  16. package/examples/basic/README.md +73 -73
  17. package/examples/basic/eslint.config.js +23 -23
  18. package/examples/basic/index.html +13 -13
  19. package/examples/basic/package.json +37 -37
  20. package/examples/basic/src/App.css +4 -4
  21. package/examples/basic/src/App.tsx +28 -28
  22. package/examples/basic/src/index.css +29 -29
  23. package/examples/basic/src/main.tsx +50 -50
  24. package/examples/basic/src/pages/ObjPage.tsx +175 -175
  25. package/examples/basic/src/pages/SinglePage.tsx +137 -137
  26. package/examples/basic/tsconfig.app.json +43 -43
  27. package/examples/basic/tsconfig.json +7 -7
  28. package/examples/basic/tsconfig.node.json +40 -40
  29. package/examples/basic/vite.config.ts +28 -28
  30. package/mui-fast-start-0.1.4.tgz +0 -0
  31. package/package.json +67 -67
  32. package/src/components/Object/Checkbox/ObjCheckIcon.tsx +29 -29
  33. package/src/components/Object/Checkbox/ObjCheckbox.tsx +31 -31
  34. package/src/components/Object/Select/ObjSelectOne.tsx +33 -33
  35. package/src/components/Object/Select/ObjSelectRecord.tsx +33 -33
  36. package/src/components/Object/Textfield/ObjNumber.tsx +51 -51
  37. package/src/components/Object/Textfield/ObjText.tsx +29 -29
  38. package/src/components/Single/Checkbox/SingleCheckIcon.tsx +27 -27
  39. package/src/components/Single/Checkbox/SingleCheckbox.tsx +33 -33
  40. package/src/components/Single/Select/BaseSingleSelect.tsx +45 -45
  41. package/src/components/Single/Select/SingleSelectOne.tsx +56 -56
  42. package/src/components/Single/Select/SingleSelectRecord.tsx +51 -51
  43. package/src/components/Single/TextField/SingleNumber.tsx +18 -18
  44. package/src/components/Single/TextField/SingleText.tsx +13 -13
  45. package/src/components/index.ts +15 -15
  46. package/src/hooks/index.ts +3 -3
  47. package/src/hooks/splits/useSplitNumberProps.ts +161 -161
  48. package/src/hooks/splits/useSplitTextProps.ts +36 -36
  49. package/src/hooks/state/useObjToSingle.ts +24 -24
  50. package/src/index.ts +7 -7
  51. package/src/styles/FastStartProps.ts +82 -81
  52. package/src/styles/FastStartProvider.tsx +25 -25
  53. package/src/types/index.ts +3 -3
  54. package/src/types/props.internal.ts +21 -21
  55. package/src/types/props.ts +81 -81
  56. package/src/types/provider.ts +72 -71
  57. package/src/types/types.ts +9 -9
  58. package/src/utils/index.ts +2 -2
  59. package/src/utils/number/calculate.ts +102 -102
  60. package/src/utils/object/error.ts +15 -15
  61. package/src/utils/object/merge.ts +47 -47
  62. package/tsconfig.json +34 -34
  63. package/tsconfig.lib.json +9 -9
  64. package/vite.config.ts +35 -35
@@ -1,56 +1,56 @@
1
- import {MenuItem, SelectProps} from "@mui/material";
2
- import {MfsSingleSelectOneProps} from "../../../types";
3
- import React, {useContext, useMemo} from "react";
4
- import {FastStartContext} from "../../../styles/FastStartProvider.tsx";
5
- import BaseSingleSelect from "./BaseSingleSelect.tsx";
6
-
7
- export type SingleSelectOneProps<Item> = SelectProps & MfsSingleSelectOneProps<Item>;
8
-
9
- export const SingleSelectOne = <Item,>(customProps: SingleSelectOneProps<Item>) => {
10
- const defaultProps = useContext(FastStartContext)?.Single?.MfsSelectOne;
11
- const {
12
- get, set, err, label,
13
- items, renderMenuItem,
14
- emptyItem, emptyValue,
15
- getKey, ...props
16
- } = defaultProps == null
17
- ? customProps
18
- : Object.assign({...defaultProps}, customProps);
19
-
20
- const getKeyOrValue = useMemo(() => (
21
- getKey ?? ((item: Item) => item as string | number)
22
- ), [getKey]);
23
-
24
- const onChange: SelectProps['onChange'] = (event) => {
25
- const value = event.target.value;
26
- if (getKey == null) {
27
- set((value == "" ? emptyValue : value) as Item);
28
- } else {
29
- const item: Item | undefined = items.find((item: Item) => getKeyOrValue(item) === value);
30
- set(item as Item);
31
- }
32
- }
33
-
34
- const MenuItems = useMemo(() => {
35
- if (renderMenuItem != null) {
36
- return items.map(renderMenuItem);
37
- } else {
38
- return items.map((item) => {
39
- const key = getKeyOrValue(item);
40
- return <MenuItem key={key} value={key}>{key}</MenuItem>;
41
- })
42
- }
43
- }, [getKeyOrValue, items, renderMenuItem]);
44
-
45
- return (
46
- <BaseSingleSelect
47
- label={label}
48
- items={MenuItems}
49
- emptyItem={emptyItem}
50
- get={get}
51
- err={err}
52
- onChange={onChange}
53
- selectProps={props}
54
- />
55
- );
56
- }
1
+ import {MenuItem, SelectProps} from "@mui/material";
2
+ import {MfsSingleSelectOneProps} from "../../../types";
3
+ import React, {useContext, useMemo} from "react";
4
+ import {FastStartContext} from "../../../styles/FastStartProvider.tsx";
5
+ import BaseSingleSelect from "./BaseSingleSelect.tsx";
6
+
7
+ export type SingleSelectOneProps<Item> = SelectProps & MfsSingleSelectOneProps<Item>;
8
+
9
+ export const SingleSelectOne = <Item,>(customProps: SingleSelectOneProps<Item>) => {
10
+ const defaultProps = useContext(FastStartContext)?.Single?.MfsSelectOne;
11
+ const {
12
+ get, set, err, label,
13
+ items, renderMenuItem,
14
+ emptyItem, emptyValue,
15
+ getKey, ...props
16
+ } = defaultProps == null
17
+ ? customProps
18
+ : Object.assign({...defaultProps}, customProps);
19
+
20
+ const getKeyOrValue = useMemo(() => (
21
+ getKey ?? ((item: Item) => item as string | number)
22
+ ), [getKey]);
23
+
24
+ const onChange: SelectProps['onChange'] = (event) => {
25
+ const value = event.target.value;
26
+ if (getKey == null) {
27
+ set((value == "" ? emptyValue : value) as Item);
28
+ } else {
29
+ const item: Item | undefined = items.find((item: Item) => getKeyOrValue(item) === value);
30
+ set(item as Item);
31
+ }
32
+ }
33
+
34
+ const MenuItems = useMemo(() => {
35
+ if (renderMenuItem != null) {
36
+ return items.map(renderMenuItem);
37
+ } else {
38
+ return items.map((item) => {
39
+ const key = getKeyOrValue(item);
40
+ return <MenuItem key={key} value={key}>{key}</MenuItem>;
41
+ })
42
+ }
43
+ }, [getKeyOrValue, items, renderMenuItem]);
44
+
45
+ return (
46
+ <BaseSingleSelect
47
+ label={label}
48
+ items={MenuItems}
49
+ emptyItem={emptyItem}
50
+ get={get}
51
+ err={err}
52
+ onChange={onChange}
53
+ selectProps={props}
54
+ />
55
+ );
56
+ }
@@ -1,52 +1,52 @@
1
- import {MenuItem, SelectProps} from "@mui/material";
2
- import {MfsSingleSelectRecordProps} from "../../../types";
3
- import React, {useContext, useMemo} from "react";
4
- import {FastStartContext} from "../../../styles/FastStartProvider.tsx";
5
- import BaseSingleSelect from "./BaseSingleSelect.tsx";
6
-
7
-
8
- export const SingleSelectRecord = <
9
- T extends Record<string, unknown>,
10
- Value = keyof T | undefined | null
11
- >(customProps: SelectProps & MfsSingleSelectRecordProps<T, Value>) => {
12
- const defaultProps = useContext(FastStartContext)?.Single?.MfsSelectRecord;
13
- const {
14
- get, set, err, label,
15
- items, renderMenuItem,
16
- emptyItem, emptyValue,
17
- ...props
18
- } = defaultProps == null
19
- ? customProps
20
- : Object.assign({...defaultProps}, customProps);
21
-
22
- const onChange: SelectProps['onChange'] = (event) => {
23
- const value = event.target.value;
24
- set((value == "" ? emptyValue : value) as Value);
25
- }
26
-
27
- const MenuItems = useMemo(() => {
28
- if (renderMenuItem != null) {
29
- return Object.entries(items).map(([key, value], i) => (
30
- renderMenuItem(key, value as T[keyof T], i)
31
- ));
32
- } else {
33
- return Object.entries(items).map(([key, value]) => (
34
- <MenuItem key={key} value={key}>
35
- {value?.toString()}
36
- </MenuItem>
37
- ));
38
- }
39
- }, [items, renderMenuItem]);
40
-
41
- return (
42
- <BaseSingleSelect
43
- label={label}
44
- items={MenuItems}
45
- emptyItem={emptyItem}
46
- get={get}
47
- err={err}
48
- onChange={onChange}
49
- selectProps={props}
50
- />
51
- )
1
+ import {MenuItem, SelectProps} from "@mui/material";
2
+ import {MfsSingleSelectRecordProps} from "../../../types";
3
+ import React, {useContext, useMemo} from "react";
4
+ import {FastStartContext} from "../../../styles/FastStartProvider.tsx";
5
+ import BaseSingleSelect from "./BaseSingleSelect.tsx";
6
+
7
+
8
+ export const SingleSelectRecord = <
9
+ T extends Record<string, React.ReactNode>,
10
+ Value = keyof T | undefined | null
11
+ >(customProps: SelectProps & MfsSingleSelectRecordProps<T, Value>) => {
12
+ const defaultProps = useContext(FastStartContext)?.Single?.MfsSelectRecord;
13
+ const {
14
+ get, set, err, label,
15
+ items, renderMenuItem,
16
+ emptyItem, emptyValue,
17
+ ...props
18
+ } = defaultProps == null
19
+ ? customProps
20
+ : Object.assign({...defaultProps}, customProps);
21
+
22
+ const onChange: SelectProps['onChange'] = (event) => {
23
+ const value = event.target.value;
24
+ set((value == "" ? emptyValue : value) as Value);
25
+ }
26
+
27
+ const MenuItems = useMemo(() => {
28
+ if (renderMenuItem != null) {
29
+ return Object.entries(items).map(([key, value], i) => (
30
+ renderMenuItem(key, value as T[keyof T], i)
31
+ ));
32
+ } else {
33
+ return Object.entries(items).map(([key, value]) => (
34
+ <MenuItem key={key} value={key}>
35
+ {value}
36
+ </MenuItem>
37
+ ));
38
+ }
39
+ }, [items, renderMenuItem]);
40
+
41
+ return (
42
+ <BaseSingleSelect
43
+ label={label}
44
+ items={MenuItems}
45
+ emptyItem={emptyItem}
46
+ get={get}
47
+ err={err}
48
+ onChange={onChange}
49
+ selectProps={props}
50
+ />
51
+ )
52
52
  }
@@ -1,19 +1,19 @@
1
- import {useContext} from "react";
2
- import {TextField, TextFieldProps} from "@mui/material";
3
- import {FastStartContext} from "../../../styles/FastStartProvider.tsx";
4
- import {MfsSingleNumberProps} from "../../../types";
5
- import {useSplitSingleFloatProps, useSplitSingleIntegerProps} from "../../../hooks";
6
-
7
- export type SingleNumberProps = TextFieldProps & MfsSingleNumberProps;
8
-
9
- export const SingleFloat = (customProps: SingleNumberProps) => {
10
- const defaultProps = useContext(FastStartContext)?.Single?.MfsFloat;
11
- const props = useSplitSingleFloatProps(defaultProps, customProps);
12
- return <TextField {...props}/>;
13
- }
14
-
15
- export const SingleInteger = (customProps: SingleNumberProps) => {
16
- const defaultProps = useContext(FastStartContext)?.Single?.MfsInteger;
17
- const props = useSplitSingleIntegerProps(defaultProps, customProps);
18
- return <TextField {...props}/>;
1
+ import {useContext} from "react";
2
+ import {TextField, TextFieldProps} from "@mui/material";
3
+ import {FastStartContext} from "../../../styles/FastStartProvider.tsx";
4
+ import {MfsSingleNumberProps} from "../../../types";
5
+ import {useSplitSingleFloatProps, useSplitSingleIntegerProps} from "../../../hooks";
6
+
7
+ export type SingleNumberProps = TextFieldProps & MfsSingleNumberProps;
8
+
9
+ export const SingleFloat = (customProps: SingleNumberProps) => {
10
+ const defaultProps = useContext(FastStartContext)?.Single?.MfsFloat;
11
+ const props = useSplitSingleFloatProps(defaultProps, customProps);
12
+ return <TextField {...props}/>;
13
+ }
14
+
15
+ export const SingleInteger = (customProps: SingleNumberProps) => {
16
+ const defaultProps = useContext(FastStartContext)?.Single?.MfsInteger;
17
+ const props = useSplitSingleIntegerProps(defaultProps, customProps);
18
+ return <TextField {...props}/>;
19
19
  }
@@ -1,13 +1,13 @@
1
- import {useContext} from "react";
2
- import useSplitTextProps from '../../../hooks/splits/useSplitTextProps.ts';
3
- import {TextField, TextFieldProps} from "@mui/material";
4
- import {FastStartContext} from "../../../styles/FastStartProvider.tsx";
5
- import {MfsSingleTextProps} from "../../../types";
6
-
7
- export type SingleTextProps = TextFieldProps & MfsSingleTextProps;
8
-
9
- export const SingleText = (customProps: SingleTextProps) => {
10
- const defaultProps = useContext(FastStartContext)?.Single?.MfsText;
11
- const props = useSplitTextProps(defaultProps, customProps);
12
- return <TextField {...props}/>;
13
- }
1
+ import {useContext} from "react";
2
+ import useSplitTextProps from '../../../hooks/splits/useSplitTextProps.ts';
3
+ import {TextField, TextFieldProps} from "@mui/material";
4
+ import {FastStartContext} from "../../../styles/FastStartProvider.tsx";
5
+ import {MfsSingleTextProps} from "../../../types";
6
+
7
+ export type SingleTextProps = TextFieldProps & MfsSingleTextProps;
8
+
9
+ export const SingleText = (customProps: SingleTextProps) => {
10
+ const defaultProps = useContext(FastStartContext)?.Single?.MfsText;
11
+ const props = useSplitTextProps(defaultProps, customProps);
12
+ return <TextField {...props}/>;
13
+ }
@@ -1,15 +1,15 @@
1
- // Single components
2
- export { SingleCheckbox } from './Single/Checkbox/SingleCheckbox.tsx';
3
- export { SingleCheckIcon } from './Single/Checkbox/SingleCheckIcon.tsx';
4
- export { SingleFloat, SingleInteger } from './Single/TextField/SingleNumber.tsx';
5
- export { SingleText } from './Single/TextField/SingleText.tsx';
6
- export { SingleSelectOne } from './Single/Select/SingleSelectOne.tsx';
7
- export { SingleSelectRecord } from './Single/Select/SingleSelectRecord.tsx';
8
-
9
- // Object components
10
- export { ObjCheckbox } from './Object/Checkbox/ObjCheckbox.tsx';
11
- export { ObjCheckIcon } from './Object/Checkbox/ObjCheckIcon.tsx';
12
- export { ObjFloat, ObjInteger } from './Object/Textfield/ObjNumber.tsx';
13
- export { ObjText } from './Object/Textfield/ObjText.tsx';
14
- export { ObjSelectOne } from './Object/Select/ObjSelectOne.tsx';
15
- export { ObjSelectRecord } from './Object/Select/ObjSelectRecord.tsx';
1
+ // Single components
2
+ export { SingleCheckbox } from './Single/Checkbox/SingleCheckbox.tsx';
3
+ export { SingleCheckIcon } from './Single/Checkbox/SingleCheckIcon.tsx';
4
+ export { SingleFloat, SingleInteger } from './Single/TextField/SingleNumber.tsx';
5
+ export { SingleText } from './Single/TextField/SingleText.tsx';
6
+ export { SingleSelectOne } from './Single/Select/SingleSelectOne.tsx';
7
+ export { SingleSelectRecord } from './Single/Select/SingleSelectRecord.tsx';
8
+
9
+ // Object components
10
+ export { ObjCheckbox } from './Object/Checkbox/ObjCheckbox.tsx';
11
+ export { ObjCheckIcon } from './Object/Checkbox/ObjCheckIcon.tsx';
12
+ export { ObjFloat, ObjInteger } from './Object/Textfield/ObjNumber.tsx';
13
+ export { ObjText } from './Object/Textfield/ObjText.tsx';
14
+ export { ObjSelectOne } from './Object/Select/ObjSelectOne.tsx';
15
+ export { ObjSelectRecord } from './Object/Select/ObjSelectRecord.tsx';
@@ -1,3 +1,3 @@
1
- export * from './splits/useSplitNumberProps.ts';
2
- export * from './splits/useSplitTextProps.ts';
3
- export * from './state/useObjToSingle';
1
+ export * from './splits/useSplitNumberProps.ts';
2
+ export * from './splits/useSplitTextProps.ts';
3
+ export * from './state/useObjToSingle';
@@ -1,161 +1,161 @@
1
- import type {TextFieldProps} from "@mui/material";
2
- import {fastDeepMerge, floatCalculate, integerCalculate, processFloat, processInteger} from "../../utils";
3
- import React, {useCallback, useState} from "react";
4
- import {MfsSingleNumberProps} from "../../types";
5
- import {SingleNumberProps} from "../../components/Single/TextField/SingleNumber.tsx";
6
-
7
- type CalculateNumber = number | null | undefined;
8
- type CalculateFunction = (
9
- value: string | null,
10
- min: CalculateNumber,
11
- max: CalculateNumber,
12
- def: CalculateNumber
13
- ) => CalculateNumber;
14
-
15
- const useSplitNumberProps = (
16
- defaultProps: Partial<MfsSingleNumberProps> | undefined,
17
- customProps: SingleNumberProps,
18
- process: (value: string) => string,
19
- calculate: CalculateFunction,
20
- lockKeys: string[] = []
21
- ): TextFieldProps => {
22
- const [draft, setDraft] = useState<string | null>(null);
23
- const {
24
- get, set, err,
25
- minLength, maxLength,
26
- startAdornment, endAdornment,
27
- def, min, max, step,
28
- ...props
29
- } = (defaultProps == null)
30
- ? customProps
31
- : Object.assign({...defaultProps}, customProps);
32
-
33
- const getCalculate = (
34
- value: string | null
35
- ) => calculate(value, min, max, def);
36
-
37
- const getKeyboardValue = (
38
- event: React.KeyboardEvent<HTMLInputElement>
39
- ) => {
40
- const {value, valueAsNumber} = event.currentTarget;
41
- return isNaN(valueAsNumber) ? getCalculate(value) : valueAsNumber;
42
- }
43
-
44
- const getProcess = useCallback(
45
- (event: React.ChangeEvent<HTMLInputElement>): string => {
46
- const target = event.currentTarget;
47
- const value: string = process(target.value);
48
- if (value != target.value) {
49
- target.value = value;
50
- }
51
- return value;
52
- },
53
- [process]
54
- );
55
-
56
- const onSelect = () => {
57
- if (draft == null) setDraft(get.toString());
58
- };
59
-
60
- const onChange = (
61
- event: React.ChangeEvent<HTMLInputElement>
62
- ) => {
63
- const result: string = getProcess(event);
64
- const num: CalculateNumber = getCalculate(result);
65
- if (num != null && !isNaN(num) && get != num) {
66
- set(num);
67
- }
68
-
69
- setDraft(event.currentTarget.value);
70
- }
71
-
72
- const onBlur = (
73
- event: React.FocusEvent<HTMLInputElement>
74
- ) => {
75
- const {value} = event.currentTarget;
76
-
77
- setDraft(null);
78
- const num: CalculateNumber = getCalculate(value);
79
- if (get != num) set(num as number);
80
- };
81
-
82
- const onKeyDown = (
83
- event: React.KeyboardEvent<HTMLInputElement>
84
- ) => {
85
- if (lockKeys.includes(event.key) || step == null) {
86
- event.preventDefault();
87
- return;
88
- }
89
-
90
- const input = event.currentTarget;
91
- if (event.key === "ArrowUp") {
92
- event.preventDefault();
93
- const calNum: CalculateNumber = getKeyboardValue(event);
94
- if (calNum == null) return;
95
-
96
- const num: number = calNum + step;
97
- if (max != null && num > max) {
98
- input.value = max.toString();
99
- } else {
100
- input.value = digitRound(num, step).toString();
101
- }
102
- } else if (event.key === "ArrowDown") {
103
- event.preventDefault();
104
- const calNum: CalculateNumber = getKeyboardValue(event);
105
- if (calNum == null) return;
106
-
107
- const num: number = calNum - step;
108
- if (min != null && num < min) {
109
- input.value = min.toString();
110
- } else {
111
- input.value = digitRound(num, step).toString();
112
- }
113
- }
114
- };
115
-
116
- return fastDeepMerge<TextFieldProps>({
117
- error: !!err,
118
- helperText: err,
119
- value: (draft == null ? get : draft),
120
- onChange,
121
- onSelect,
122
- onBlur,
123
- slotProps: {
124
- htmlInput: {step, min, max, minLength, maxLength, onKeyDown},
125
- inputLabel: (draft == null && (!get || isNaN(get))) ? {} : { shrink: true },
126
- input: {startAdornment, endAdornment}
127
- }
128
- }, (props as TextFieldProps));
129
- }
130
-
131
- const digitRound = (num: number, step: number) => {
132
- step = Math.abs(step);
133
- if (step === 0) return num;
134
- const decimals: number = -Math.floor(Math.log10(step));
135
- if (decimals > 0) {
136
- return Number(Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals));
137
- }
138
- return num;
139
- }
140
-
141
- const useSplitSingleFloatProps = (
142
- defaultProps: Partial<MfsSingleNumberProps> | undefined,
143
- customProps: SingleNumberProps,
144
- ): TextFieldProps => useSplitNumberProps(
145
- defaultProps, customProps,
146
- processFloat, floatCalculate
147
- );
148
-
149
- const useSplitSingleIntegerProps = (
150
- defaultProps: Partial<MfsSingleNumberProps> | undefined,
151
- customProps: SingleNumberProps,
152
- ): TextFieldProps => useSplitNumberProps(
153
- defaultProps, customProps,
154
- processInteger, integerCalculate,
155
- [".", "e", "E"]
156
- )
157
-
158
- export {
159
- useSplitSingleFloatProps,
160
- useSplitSingleIntegerProps
161
- };
1
+ import type {TextFieldProps} from "@mui/material";
2
+ import {fastDeepMerge, floatCalculate, integerCalculate, processFloat, processInteger} from "../../utils";
3
+ import React, {useCallback, useState} from "react";
4
+ import {MfsSingleNumberProps} from "../../types";
5
+ import {SingleNumberProps} from "../../components/Single/TextField/SingleNumber.tsx";
6
+
7
+ type CalculateNumber = number | null | undefined;
8
+ type CalculateFunction = (
9
+ value: string | null,
10
+ min: CalculateNumber,
11
+ max: CalculateNumber,
12
+ def: CalculateNumber
13
+ ) => CalculateNumber;
14
+
15
+ const useSplitNumberProps = (
16
+ defaultProps: Partial<MfsSingleNumberProps> | undefined,
17
+ customProps: SingleNumberProps,
18
+ process: (value: string) => string,
19
+ calculate: CalculateFunction,
20
+ lockKeys: string[] = []
21
+ ): TextFieldProps => {
22
+ const [draft, setDraft] = useState<string | null>(null);
23
+ const {
24
+ get, set, err,
25
+ minLength, maxLength,
26
+ startAdornment, endAdornment,
27
+ def, min, max, step,
28
+ ...props
29
+ } = (defaultProps == null)
30
+ ? customProps
31
+ : Object.assign({...defaultProps}, customProps);
32
+
33
+ const getCalculate = (
34
+ value: string | null
35
+ ) => calculate(value, min, max, def);
36
+
37
+ const getKeyboardValue = (
38
+ event: React.KeyboardEvent<HTMLInputElement>
39
+ ) => {
40
+ const {value, valueAsNumber} = event.currentTarget;
41
+ return isNaN(valueAsNumber) ? getCalculate(value) : valueAsNumber;
42
+ }
43
+
44
+ const getProcess = useCallback(
45
+ (event: React.ChangeEvent<HTMLInputElement>): string => {
46
+ const target = event.currentTarget;
47
+ const value: string = process(target.value);
48
+ if (value != target.value) {
49
+ target.value = value;
50
+ }
51
+ return value;
52
+ },
53
+ [process]
54
+ );
55
+
56
+ const onSelect = () => {
57
+ if (draft == null) setDraft(get.toString());
58
+ };
59
+
60
+ const onChange = (
61
+ event: React.ChangeEvent<HTMLInputElement>
62
+ ) => {
63
+ const result: string = getProcess(event);
64
+ const num: CalculateNumber = getCalculate(result);
65
+ if (num != null && !isNaN(num) && get != num) {
66
+ set(num);
67
+ }
68
+
69
+ setDraft(event.currentTarget.value);
70
+ }
71
+
72
+ const onBlur = (
73
+ event: React.FocusEvent<HTMLInputElement>
74
+ ) => {
75
+ const {value} = event.currentTarget;
76
+
77
+ setDraft(null);
78
+ const num: CalculateNumber = getCalculate(value);
79
+ if (get != num) set(num as number);
80
+ };
81
+
82
+ const onKeyDown = (
83
+ event: React.KeyboardEvent<HTMLInputElement>
84
+ ) => {
85
+ if (lockKeys.includes(event.key) || step == null) {
86
+ event.preventDefault();
87
+ return;
88
+ }
89
+
90
+ const input = event.currentTarget;
91
+ if (event.key === "ArrowUp") {
92
+ event.preventDefault();
93
+ const calNum: CalculateNumber = getKeyboardValue(event);
94
+ if (calNum == null) return;
95
+
96
+ const num: number = calNum + step;
97
+ if (max != null && num > max) {
98
+ input.value = max.toString();
99
+ } else {
100
+ input.value = digitRound(num, step).toString();
101
+ }
102
+ } else if (event.key === "ArrowDown") {
103
+ event.preventDefault();
104
+ const calNum: CalculateNumber = getKeyboardValue(event);
105
+ if (calNum == null) return;
106
+
107
+ const num: number = calNum - step;
108
+ if (min != null && num < min) {
109
+ input.value = min.toString();
110
+ } else {
111
+ input.value = digitRound(num, step).toString();
112
+ }
113
+ }
114
+ };
115
+
116
+ return fastDeepMerge<TextFieldProps>({
117
+ error: !!err,
118
+ helperText: err,
119
+ value: (draft == null ? get : draft),
120
+ onChange,
121
+ onSelect,
122
+ onBlur,
123
+ slotProps: {
124
+ htmlInput: {step, min, max, minLength, maxLength, onKeyDown},
125
+ inputLabel: (draft == null && (!get || isNaN(get))) ? {} : { shrink: true },
126
+ input: {startAdornment, endAdornment}
127
+ }
128
+ }, (props as TextFieldProps));
129
+ }
130
+
131
+ const digitRound = (num: number, step: number) => {
132
+ step = Math.abs(step);
133
+ if (step === 0) return num;
134
+ const decimals: number = -Math.floor(Math.log10(step));
135
+ if (decimals > 0) {
136
+ return Number(Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals));
137
+ }
138
+ return num;
139
+ }
140
+
141
+ const useSplitSingleFloatProps = (
142
+ defaultProps: Partial<MfsSingleNumberProps> | undefined,
143
+ customProps: SingleNumberProps,
144
+ ): TextFieldProps => useSplitNumberProps(
145
+ defaultProps, customProps,
146
+ processFloat, floatCalculate
147
+ );
148
+
149
+ const useSplitSingleIntegerProps = (
150
+ defaultProps: Partial<MfsSingleNumberProps> | undefined,
151
+ customProps: SingleNumberProps,
152
+ ): TextFieldProps => useSplitNumberProps(
153
+ defaultProps, customProps,
154
+ processInteger, integerCalculate,
155
+ [".", "e", "E"]
156
+ )
157
+
158
+ export {
159
+ useSplitSingleFloatProps,
160
+ useSplitSingleIntegerProps
161
+ };