tp-react-elements-dev 1.14.6 → 1.14.8
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.
- package/dist/_virtual/index.esm12.js +2 -2
- package/dist/_virtual/index.esm13.js +2 -2
- package/dist/_virtual/index.esm14.js +2 -2
- package/dist/_virtual/index.esm4.js +2 -2
- package/dist/_virtual/index.esm5.js +2 -2
- package/dist/_virtual/index.esm6.js +2 -2
- package/dist/_virtual/index.esm7.js +2 -2
- package/dist/components/DateRangePicker/DateRangePicker.esm.js +3 -0
- package/dist/components/FormComponents/FileUpload/MultiFileWithPreview.esm.js +70 -15
- package/dist/components/FormComponents/FormTextField/FormTextField.esm.js +1 -1
- package/dist/components/FormComponents/FormTextFieldWithSelect/FormTextFieldWithSelect.esm.js +2 -2
- package/dist/components/FormComponents/Select/MultiSelectAutocomplete.esm.js +173 -13
- package/dist/node_modules/react-date-range/dist/components/Calendar/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DateInput/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DateRange/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DateRangePicker/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/DefinedRange/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/InputRangeField/index.esm.js +1 -1
- package/dist/node_modules/react-date-range/dist/components/Month/index.esm.js +1 -1
- package/dist/utils/Interface/FormInterface.d.ts +2 -2
- package/package.json +1 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var InputRangeField = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { InputRangeField as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Month = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { Month as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DateInput = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DateInput as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DateRange = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DateRange as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DefinedRange = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DefinedRange as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var DateRangePicker = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { DateRangePicker as __exports };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var Calendar = {};
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { Calendar as __exports };
|
|
@@ -89,6 +89,9 @@ const DateRangePickerComponent = ({ item, control, watch, setValue, getValues, c
|
|
|
89
89
|
if (selection.startDate &&
|
|
90
90
|
selection.endDate &&
|
|
91
91
|
selection.startDate?.getTime() !== selection.endDate?.getTime()) {
|
|
92
|
+
const dateObject = { startDate: format(selection.startDate, 'dd/MM/yyyy'), endDate: format(selection.endDate, 'dd/MM/yyyy') };
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
94
|
+
item.onChangeFn && item.onChangeFn(dateObject || null);
|
|
92
95
|
handleClose();
|
|
93
96
|
}
|
|
94
97
|
}, moveRangeOnFirstSelection: false, ranges: state, months: 2, direction: "horizontal", showDateDisplay: false, dateDisplayFormat: "dd/MM/yyyy", rangeColors: ['#4d73b1ff'] }) }), jsx(FormBottomField, { item,
|
|
@@ -21,6 +21,19 @@ const MultiFileWithPreview = ({ props, variant, }) => {
|
|
|
21
21
|
const [open, setOpen] = useState(false);
|
|
22
22
|
const [selectedFiles, setSelectedFiles] = useState([]);
|
|
23
23
|
const [currentFileIndex, setCurrentFileIndex] = useState(0);
|
|
24
|
+
// Clamp preview index when selection changes to avoid undefined access
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (selectedFiles.length === 0) {
|
|
27
|
+
setCurrentFileIndex(0);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (currentFileIndex > selectedFiles.length - 1) {
|
|
31
|
+
setCurrentFileIndex(selectedFiles.length - 1);
|
|
32
|
+
}
|
|
33
|
+
else if (currentFileIndex < 0) {
|
|
34
|
+
setCurrentFileIndex(0);
|
|
35
|
+
}
|
|
36
|
+
}, [selectedFiles, currentFileIndex]);
|
|
24
37
|
// Determine if this is single or multiple file mode
|
|
25
38
|
const isSingleFileMode = props.item.maxLength === 1;
|
|
26
39
|
useEffect(() => {
|
|
@@ -28,9 +41,35 @@ const MultiFileWithPreview = ({ props, variant, }) => {
|
|
|
28
41
|
if (inputRef.current)
|
|
29
42
|
inputRef.current.value = '';
|
|
30
43
|
}
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
44
|
+
const looksLikeUrl = (s) => /^(https?:|blob:|data:|\/)/i.test(s);
|
|
45
|
+
if (typeof watched === 'string' && watched.trim() !== '') {
|
|
46
|
+
const str = watched.replace(/\\/g, '/').trim();
|
|
47
|
+
if (looksLikeUrl(str)) {
|
|
48
|
+
setSelectedFiles([str]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (Array.isArray(watched)) {
|
|
52
|
+
if (typeof watched[0] === 'object') {
|
|
53
|
+
// ✅ extract URLs from object arrays (image/url/path keys)
|
|
54
|
+
const urls = watched
|
|
55
|
+
.map((item) => {
|
|
56
|
+
const possibleKeys = ['image', 'url', 'path', 'filePath'];
|
|
57
|
+
const key = possibleKeys.find((k) => item[k]);
|
|
58
|
+
return key ? item[key]?.replace(/\\/g, '/').trim() : undefined;
|
|
59
|
+
})
|
|
60
|
+
.filter((f) => f && looksLikeUrl(f));
|
|
61
|
+
if (urls.length) {
|
|
62
|
+
setSelectedFiles(urls);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (typeof watched[0] === 'string') {
|
|
66
|
+
const normalized = watched
|
|
67
|
+
.map((f) => (f || '').toString().replace(/\\/g, '/').trim())
|
|
68
|
+
.filter((f) => looksLikeUrl(f));
|
|
69
|
+
if (normalized.length) {
|
|
70
|
+
setSelectedFiles(normalized);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
34
73
|
}
|
|
35
74
|
}, [watched]);
|
|
36
75
|
const isError = !!props.errors?.[props.item.name];
|
|
@@ -70,16 +109,15 @@ const MultiFileWithPreview = ({ props, variant, }) => {
|
|
|
70
109
|
props.setValue(props.item.name + 'File', []);
|
|
71
110
|
return;
|
|
72
111
|
}
|
|
73
|
-
|
|
74
|
-
let combinedFiles;
|
|
112
|
+
let combinedFiles = [];
|
|
75
113
|
if (isSingleFileMode) {
|
|
76
|
-
//
|
|
114
|
+
// only 1 file allowed → replace
|
|
77
115
|
combinedFiles = fileArray;
|
|
78
116
|
}
|
|
79
117
|
else {
|
|
80
|
-
//
|
|
118
|
+
// append to existing
|
|
81
119
|
combinedFiles = [...selectedFiles, ...fileArray];
|
|
82
|
-
//
|
|
120
|
+
// limit by maxLength
|
|
83
121
|
if (props.item.maxLength && combinedFiles.length > props.item.maxLength) {
|
|
84
122
|
props.item?.handleFileError?.(`You can only upload up to ${props.item.maxLength} files`);
|
|
85
123
|
event.target.value = '';
|
|
@@ -87,11 +125,13 @@ const MultiFileWithPreview = ({ props, variant, }) => {
|
|
|
87
125
|
}
|
|
88
126
|
}
|
|
89
127
|
const combinedFileNames = combinedFiles
|
|
90
|
-
.map((f) => (f instanceof File ? f.name : f.split('/').pop()))
|
|
128
|
+
.map((f) => (f instanceof File ? f.name : (f?.toString?.() || '').split('/').pop()))
|
|
129
|
+
.filter(Boolean)
|
|
91
130
|
.join(',');
|
|
92
131
|
props.setValue(props.item.name, combinedFileNames);
|
|
93
132
|
props.setValue(props.item.name + 'File', combinedFiles);
|
|
94
133
|
setSelectedFiles(combinedFiles);
|
|
134
|
+
setCurrentFileIndex(combinedFiles.length - fileArray.length); // move preview to new
|
|
95
135
|
props.item.onChangeSetFileFn?.(combinedFiles);
|
|
96
136
|
};
|
|
97
137
|
const handlePreviewClick = () => {
|
|
@@ -132,24 +172,36 @@ const MultiFileWithPreview = ({ props, variant, }) => {
|
|
|
132
172
|
}
|
|
133
173
|
};
|
|
134
174
|
const isImageFile = (file) => {
|
|
175
|
+
if (!file)
|
|
176
|
+
return false;
|
|
135
177
|
const ext = typeof file === 'string'
|
|
136
178
|
? file.split('.').pop()?.toLowerCase()
|
|
137
|
-
: file.name
|
|
179
|
+
: file.name?.split('.').pop()?.toLowerCase();
|
|
138
180
|
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext || '');
|
|
139
181
|
};
|
|
140
182
|
const isPdfFile = (file) => {
|
|
141
|
-
|
|
183
|
+
if (!file)
|
|
184
|
+
return false;
|
|
185
|
+
const name = typeof file === 'string' ? file : (file.name || '');
|
|
142
186
|
return name.toLowerCase().endsWith('.pdf');
|
|
143
187
|
};
|
|
144
188
|
const isExcelFile = (file) => {
|
|
145
|
-
|
|
189
|
+
if (!file)
|
|
190
|
+
return false;
|
|
191
|
+
const name = typeof file === 'string' ? file : (file.name || '');
|
|
146
192
|
return /\.xlsx?$/.test(name.toLowerCase());
|
|
147
193
|
};
|
|
148
194
|
const isWordFile = (file) => {
|
|
149
|
-
|
|
195
|
+
if (!file)
|
|
196
|
+
return false;
|
|
197
|
+
const name = typeof file === 'string' ? file : (file.name || '');
|
|
150
198
|
return /\.(doc|docx)$/.test(name.toLowerCase());
|
|
151
199
|
};
|
|
152
|
-
const getObjectUrl = (file) =>
|
|
200
|
+
const getObjectUrl = (file) => {
|
|
201
|
+
if (!file)
|
|
202
|
+
return '';
|
|
203
|
+
return typeof file === 'string' ? file : URL.createObjectURL(file);
|
|
204
|
+
};
|
|
153
205
|
return (jsxs(Fragment, { children: [jsxs(Box, { paddingLeft: "4px", sx: { ...props.item.sx, position: 'relative' }, children: [props.item?.label && (jsx(Box, { sx: { fontSize: '10px' }, children: renderLabel(variant, props, true) })), jsxs(Box, { display: "flex", alignItems: "center", gap: 1, children: [jsxs(Button, { variant: "outlined", component: "label", startIcon: jsx(UploadFileIcon, {}), sx: { flex: 1, textTransform: 'none' }, color: isError ? 'error' : 'primary', children: [selectedFiles.length > 0
|
|
154
206
|
? isSingleFileMode
|
|
155
207
|
? '1 file selected'
|
|
@@ -176,9 +228,12 @@ const MultiFileWithPreview = ({ props, variant, }) => {
|
|
|
176
228
|
p: 3,
|
|
177
229
|
}, children: [jsxs(Box, { display: "flex", justifyContent: "space-between", alignItems: "center", children: [jsx(Typography, { variant: "h6", children: "File Preview" }), jsxs(Box, { display: "flex", gap: 1, children: [jsx(IconButton, { onClick: () => handleDeleteFile(currentFileIndex), color: "error", title: "Delete current file", children: jsx(DeleteIcon, {}) }), jsx(IconButton, { onClick: handleClose, children: jsx(CloseIcon, {}) })] })] }), jsx(Box, { mt: 2, children: selectedFiles.length > 0 && (jsxs(Fragment, { children: [jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "center", gap: 2, children: [selectedFiles.length > 1 && !isSingleFileMode ? (jsx(IconButton, { onClick: handlePrevious, children: jsx(NavigateBeforeIcon, {}) })) : null, jsx(Box, { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", sx: { p: 2, minHeight: '400px', width: '100%' }, children: (() => {
|
|
178
230
|
const currentFile = selectedFiles[currentFileIndex];
|
|
231
|
+
if (!currentFile) {
|
|
232
|
+
return jsx(Typography, { variant: "body2", color: "text.secondary", children: "No file selected" });
|
|
233
|
+
}
|
|
179
234
|
const fileName = typeof currentFile === 'string'
|
|
180
235
|
? currentFile.split('/').pop() || currentFile
|
|
181
|
-
: currentFile.name;
|
|
236
|
+
: (currentFile.name || '');
|
|
182
237
|
// Images
|
|
183
238
|
if (isImageFile(currentFile)) {
|
|
184
239
|
return (jsx("img", { alt: "Preview", src: getObjectUrl(currentFile), style: {
|
|
@@ -64,7 +64,7 @@ const FormTextField = ({ props, variant }) => {
|
|
|
64
64
|
}
|
|
65
65
|
if (props?.item?.allowSpecialChars === false) {
|
|
66
66
|
const value = e.target.value;
|
|
67
|
-
e.target.value = value.replace(/[^a-zA-Z0-9]/g, '');
|
|
67
|
+
e.target.value = value.replace(/[^a-zA-Z0-9\s]/g, '');
|
|
68
68
|
}
|
|
69
69
|
if (props?.item?.allowNumbers === false) {
|
|
70
70
|
const value = e.target.value;
|
package/dist/components/FormComponents/FormTextFieldWithSelect/FormTextFieldWithSelect.esm.js
CHANGED
|
@@ -58,9 +58,9 @@ const FormTextFieldWithSelect = ({ props, variant, }) => {
|
|
|
58
58
|
* - Calls custom onInputProps handler if provided
|
|
59
59
|
*/
|
|
60
60
|
const onInput = (e) => {
|
|
61
|
-
const filteredValue = e.target.value.replace(/[^a-zA-Z0-9.]/g, '');
|
|
62
|
-
e.target.value = filteredValue;
|
|
63
61
|
props.item.onInputProps && props.item.onInputProps(e);
|
|
62
|
+
const filteredValue = e.target.value.replace(/[^a-zA-Z0-9._]/g, '');
|
|
63
|
+
e.target.value = filteredValue;
|
|
64
64
|
};
|
|
65
65
|
/**
|
|
66
66
|
* Renders the text field component using react-hook-form's Controller
|
|
@@ -1,19 +1,153 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Autocomplete, TextField,
|
|
3
|
-
import { useMemo } from 'react';
|
|
1
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { Checkbox, Autocomplete, TextField, Box, Tooltip, Chip } from '@mui/material';
|
|
3
|
+
import { useState, useRef, useMemo, useCallback } from 'react';
|
|
4
4
|
import { useWatch, Controller } from 'react-hook-form';
|
|
5
5
|
import FormBottomField from '../FormBottomField/FormBottomField.esm.js';
|
|
6
6
|
import 'dayjs';
|
|
7
7
|
import { renderLabel } from '../../../utils/Constants/FormConstants.esm.js';
|
|
8
8
|
|
|
9
|
+
//region old code
|
|
10
|
+
// const MultiSelectAutocomplete = ({
|
|
11
|
+
// props,
|
|
12
|
+
// variant,
|
|
13
|
+
// }: {
|
|
14
|
+
// props: FormRenderProps;
|
|
15
|
+
// variant: VariantProps;
|
|
16
|
+
// }) => {
|
|
17
|
+
// const isError = !!props.errors?.[props.item.name];
|
|
18
|
+
// const watchedValue = useWatch({ control: props.control, name: props.item.name });
|
|
19
|
+
// const normalizeToStringArray = (value: unknown): string[] => {
|
|
20
|
+
// if (Array.isArray(value)) {
|
|
21
|
+
// return value.map((v) => String(v));
|
|
22
|
+
// }
|
|
23
|
+
// if (typeof value === 'string') {
|
|
24
|
+
// return value
|
|
25
|
+
// .split(',')
|
|
26
|
+
// .map((v) => v.trim())
|
|
27
|
+
// .filter((v) => v.length > 0);
|
|
28
|
+
// }
|
|
29
|
+
// if (value == null) return [];
|
|
30
|
+
// return [String(value)];
|
|
31
|
+
// };
|
|
32
|
+
// const normalizedSelectedValues = normalizeToStringArray(watchedValue);
|
|
33
|
+
// const isShowBorderError = isError && normalizedSelectedValues.length === 0;
|
|
34
|
+
// const options: OptionItem[] = (props.item.options || []) as OptionItem[];
|
|
35
|
+
// const selectedOptions: OptionItem[] = useMemo(() => {
|
|
36
|
+
// const valueSet = new Set(normalizedSelectedValues.map((v) => String(v)));
|
|
37
|
+
// return options.filter((opt) => valueSet.has(String(opt.value)));
|
|
38
|
+
// }, [normalizedSelectedValues, options]);
|
|
39
|
+
// return (
|
|
40
|
+
// <Controller
|
|
41
|
+
// control={props.control}
|
|
42
|
+
// name={props.item.name}
|
|
43
|
+
// key={props.item.name}
|
|
44
|
+
// render={({ field }) => (
|
|
45
|
+
// <>
|
|
46
|
+
// {renderLabel(variant, props)}
|
|
47
|
+
// <Autocomplete
|
|
48
|
+
// {...field}
|
|
49
|
+
// multiple
|
|
50
|
+
// disableCloseOnSelect
|
|
51
|
+
// id={`${props.item.name}-autocomplete-multi`}
|
|
52
|
+
// options={options}
|
|
53
|
+
// value={selectedOptions}
|
|
54
|
+
// getOptionLabel={(option) => String(option.label)}
|
|
55
|
+
// isOptionEqualToValue={(option, value) => String(option.value) === String(value.value)}
|
|
56
|
+
// onChange={(_, newValue) => {
|
|
57
|
+
// const joined = (newValue || []).map((o: OptionItem) => String(o.value)).join(',');
|
|
58
|
+
// props.setValue(props.item.name, joined);
|
|
59
|
+
// props?.item?.onChangeFn && props?.item?.onChangeFn(joined);
|
|
60
|
+
// props?.clearErrors && props?.clearErrors(props?.item?.name);
|
|
61
|
+
// }}
|
|
62
|
+
// onBlur={(e: any) => {
|
|
63
|
+
// props?.item?.onBlurFn && props?.item?.onBlurFn(e);
|
|
64
|
+
// }}
|
|
65
|
+
// size="small"
|
|
66
|
+
// renderOption={(renderProps, option, { selected }) => (
|
|
67
|
+
// <li {...renderProps} style={{ fontSize: '11px' }}>
|
|
68
|
+
// <Checkbox
|
|
69
|
+
// checked={selected}
|
|
70
|
+
// size="small"
|
|
71
|
+
// sx={{ mr: 1, p: '2px' }}
|
|
72
|
+
// />
|
|
73
|
+
// {option.label}
|
|
74
|
+
// </li>
|
|
75
|
+
// )}
|
|
76
|
+
// renderTags={(tagValue) => (
|
|
77
|
+
// tagValue.length ? <span>{tagValue.map((o: OptionItem) => String(o.label)).join(' , ')}</span> : null
|
|
78
|
+
// )}
|
|
79
|
+
// sx={{
|
|
80
|
+
// '& .MuiAutocomplete-input': {
|
|
81
|
+
// padding: '0px 0px 0px 5px !important',
|
|
82
|
+
// },
|
|
83
|
+
// '& .css-erkti9-MuiFormLabel-root-MuiInputLabel-root,.css-8edmr2-MuiFormLabel-root-MuiInputLabel-root': {
|
|
84
|
+
// top: '-3px',
|
|
85
|
+
// },
|
|
86
|
+
// '& .MuiAutocomplete-option': {
|
|
87
|
+
// fontSize: '11px',
|
|
88
|
+
// zIndex: 2000,
|
|
89
|
+
// },
|
|
90
|
+
// ...props.item.sx,
|
|
91
|
+
// }}
|
|
92
|
+
// ListboxProps={{
|
|
93
|
+
// onScroll: (event: React.SyntheticEvent) => {
|
|
94
|
+
// const listboxNode = event.currentTarget as HTMLElement;
|
|
95
|
+
// if (listboxNode.scrollTop + listboxNode.clientHeight === listboxNode.scrollHeight) {
|
|
96
|
+
// // Infinite scroll hook can be wired here
|
|
97
|
+
// }
|
|
98
|
+
// },
|
|
99
|
+
// }}
|
|
100
|
+
// disabled={props.item.disable}
|
|
101
|
+
// slotProps={{
|
|
102
|
+
// popper: {
|
|
103
|
+
// sx: {
|
|
104
|
+
// zIndex: 1401,
|
|
105
|
+
// },
|
|
106
|
+
// },
|
|
107
|
+
// listbox: {
|
|
108
|
+
// sx: {
|
|
109
|
+
// fontSize: '11px',
|
|
110
|
+
// },
|
|
111
|
+
// },
|
|
112
|
+
// }}
|
|
113
|
+
// renderInput={(params) => (
|
|
114
|
+
// <TextField
|
|
115
|
+
// {...params}
|
|
116
|
+
// placeholder={props.item.placeholder}
|
|
117
|
+
// error={isShowBorderError}
|
|
118
|
+
// label={
|
|
119
|
+
// variant !== 'standard'
|
|
120
|
+
// ? `${props.item.label}${props.item.required ? ' *' : ''}`
|
|
121
|
+
// : ''
|
|
122
|
+
// }
|
|
123
|
+
// inputProps={{
|
|
124
|
+
// ...params.inputProps,
|
|
125
|
+
// 'aria-labelledby': `${props.item.name}-label`,
|
|
126
|
+
// 'aria-describedby': `${props.item.name}-helper ${props.item.name}-error`,
|
|
127
|
+
// 'aria-required': props.item.required ? true : undefined,
|
|
128
|
+
// }}
|
|
129
|
+
// />
|
|
130
|
+
// )}
|
|
131
|
+
// />
|
|
132
|
+
// <FormBottomField {...props} />
|
|
133
|
+
// </>
|
|
134
|
+
// )}
|
|
135
|
+
// />
|
|
136
|
+
// );
|
|
137
|
+
// };
|
|
138
|
+
//endregion
|
|
9
139
|
const MultiSelectAutocomplete = ({ props, variant, }) => {
|
|
10
140
|
const isError = !!props.errors?.[props.item.name];
|
|
11
141
|
const watchedValue = useWatch({ control: props.control, name: props.item.name });
|
|
142
|
+
const [open, setOpen] = useState(false);
|
|
143
|
+
const tagsContainerRef = useRef(null);
|
|
12
144
|
const normalizeToStringArray = (value) => {
|
|
13
145
|
if (Array.isArray(value)) {
|
|
146
|
+
// already array of primitives/strings
|
|
14
147
|
return value.map((v) => String(v));
|
|
15
148
|
}
|
|
16
149
|
if (typeof value === 'string') {
|
|
150
|
+
// if backend provided CSV string, convert to array
|
|
17
151
|
return value
|
|
18
152
|
.split(',')
|
|
19
153
|
.map((v) => v.trim())
|
|
@@ -21,26 +155,52 @@ const MultiSelectAutocomplete = ({ props, variant, }) => {
|
|
|
21
155
|
}
|
|
22
156
|
if (value == null)
|
|
23
157
|
return [];
|
|
158
|
+
// for any other types, coerce to single-element array
|
|
24
159
|
return [String(value)];
|
|
25
160
|
};
|
|
26
161
|
const normalizedSelectedValues = normalizeToStringArray(watchedValue);
|
|
27
162
|
const isShowBorderError = isError && normalizedSelectedValues.length === 0;
|
|
28
|
-
const options = (props.item.options || []);
|
|
163
|
+
const options = useMemo(() => (props.item.options || []), [props.item.options]);
|
|
29
164
|
const selectedOptions = useMemo(() => {
|
|
30
165
|
const valueSet = new Set(normalizedSelectedValues.map((v) => String(v)));
|
|
31
166
|
return options.filter((opt) => valueSet.has(String(opt.value)));
|
|
32
167
|
}, [normalizedSelectedValues, options]);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
168
|
+
// Derived overflow flag without extra renders
|
|
169
|
+
const isOverflowing = (selectedOptions || []).length > 2;
|
|
170
|
+
// Stable callbacks to avoid re-renders
|
|
171
|
+
const getOptionLabelCb = useCallback((option) => String(option.label), []);
|
|
172
|
+
const isOptionEqualCb = useCallback((option, value) => String(option.value) === String(value.value), []);
|
|
173
|
+
const renderOptionCb = useCallback((renderProps, option, state) => (jsxs("li", { ...renderProps, style: { fontSize: '11px' }, children: [jsx(Checkbox, { checked: state.selected, size: "small", sx: { mr: 1, p: '2px' } }), option.label] })), []);
|
|
174
|
+
const chipSx = useMemo(() => ({ mr: 0.5, height: 20, fontSize: '10px', borderRadius: '10px', '& .MuiChip-label': { px: 0.75, py: 0 } }), []);
|
|
175
|
+
const selectedTooltip = useMemo(() => selectedOptions.map(o => String(o.label)).join(', '), [selectedOptions]);
|
|
176
|
+
const handleChange = useCallback((_, newValue) => {
|
|
177
|
+
const values = Array.isArray(newValue)
|
|
178
|
+
? newValue.map((o) => String(o.value))
|
|
179
|
+
: [];
|
|
180
|
+
// Minimize work during selection for snappier UX
|
|
181
|
+
props.setValue(props.item.name, values, { shouldValidate: false, shouldDirty: true });
|
|
182
|
+
props?.item?.onChangeFn && props?.item?.onChangeFn(values.join(','));
|
|
183
|
+
props?.clearErrors && props?.clearErrors(props?.item?.name);
|
|
184
|
+
}, [props]);
|
|
185
|
+
const handleBlur = useCallback((e) => {
|
|
186
|
+
props?.item?.onBlurFn && props?.item?.onBlurFn(e);
|
|
187
|
+
}, [props]);
|
|
188
|
+
return (jsx(Controller, { control: props.control, name: props.item.name, render: ({ field: { ref, onBlur: fieldOnBlur } }) => (jsxs(Fragment, { children: [renderLabel(variant, props), jsx(Autocomplete, { ref: ref, open: open, onOpen: () => setOpen(true), onClose: () => setOpen(false), multiple: true, disableCloseOnSelect: true, id: `${props.item.name}-autocomplete-multi`, options: options, value: selectedOptions, getOptionLabel: getOptionLabelCb, isOptionEqualToValue: isOptionEqualCb, onChange: handleChange, onBlur: (e) => {
|
|
189
|
+
fieldOnBlur();
|
|
190
|
+
handleBlur(e);
|
|
191
|
+
}, size: "small", disablePortal: false, autoHighlight: true, selectOnFocus: true, clearOnBlur: false, renderTags: (tagValue, getTagProps) => (jsx(Box, { ref: tagsContainerRef, sx: {
|
|
192
|
+
'& :hover': {
|
|
193
|
+
cursor: 'pointer',
|
|
194
|
+
},
|
|
195
|
+
display: 'flex', flexWrap: 'nowrap', overflow: 'hidden', alignItems: 'center'
|
|
196
|
+
}, children: isOverflowing ? (jsx(Tooltip, { title: selectedTooltip, placement: "top", children: jsx(Chip, { size: "small", label: `${tagValue.length} selected`, onClick: () => setOpen(true), clickable: true, sx: chipSx }) })) : (tagValue.map((option, index) => (jsx(Tooltip, { title: String(option?.label ?? option), placement: "top", children: jsx(Chip, { ...getTagProps({ index }), size: "small", label: String(option?.label ?? option), sx: chipSx }) }, String(option?.value ?? option?.label ?? index))))) })), renderOption: renderOptionCb, sx: {
|
|
41
197
|
'& .MuiAutocomplete-input': {
|
|
42
198
|
padding: '0px 0px 0px 5px !important',
|
|
43
199
|
},
|
|
200
|
+
'& .MuiAutocomplete-inputRoot': {
|
|
201
|
+
flexWrap: 'nowrap', // keep search beside chip
|
|
202
|
+
alignItems: 'center'
|
|
203
|
+
},
|
|
44
204
|
'& .css-erkti9-MuiFormLabel-root-MuiInputLabel-root,.css-8edmr2-MuiFormLabel-root-MuiInputLabel-root': {
|
|
45
205
|
top: '-3px',
|
|
46
206
|
},
|
|
@@ -57,7 +217,7 @@ const MultiSelectAutocomplete = ({ props, variant, }) => {
|
|
|
57
217
|
}, disabled: props.item.disable, slotProps: {
|
|
58
218
|
popper: {
|
|
59
219
|
sx: {
|
|
60
|
-
zIndex:
|
|
220
|
+
zIndex: 20000,
|
|
61
221
|
},
|
|
62
222
|
},
|
|
63
223
|
listbox: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as Calendar } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as Calendar } from '../../../../../_virtual/index.esm7.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireDayCell } from '../DayCell/index.esm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as DateInput } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as DateInput } from '../../../../../_virtual/index.esm14.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireClassnames } from '../../../../classnames/index.esm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as DateRange } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as DateRange } from '../../../../../_virtual/index.esm4.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireCalendar } from '../Calendar/index.esm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as DateRangePicker } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as DateRangePicker } from '../../../../../_virtual/index.esm6.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireDateRange } from '../DateRange/index.esm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as DefinedRange } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as DefinedRange } from '../../../../../_virtual/index.esm5.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireStyles } from '../../styles.esm.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as InputRangeField } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as InputRangeField } from '../../../../../_virtual/index.esm12.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { __exports as Month } from '../../../../../_virtual/index.
|
|
1
|
+
import { __exports as Month } from '../../../../../_virtual/index.esm13.js';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { __require as requirePropTypes } from '../../../../prop-types/index.esm.js';
|
|
4
4
|
import { __require as requireDayCell } from '../DayCell/index.esm.js';
|
|
@@ -16,8 +16,8 @@ export interface FormSectionPropsItem {
|
|
|
16
16
|
errorMessage?: string;
|
|
17
17
|
helperText?: string | React.ReactNode;
|
|
18
18
|
disable?: boolean;
|
|
19
|
-
onChangeFn?: (e: string | number | undefined | null | boolean) => void;
|
|
20
|
-
onBlurFn?: (e: string | number | undefined | null | boolean) => void;
|
|
19
|
+
onChangeFn?: (e: string | number | undefined | null | boolean | any) => void;
|
|
20
|
+
onBlurFn?: (e: string | number | undefined | null | boolean | any) => void;
|
|
21
21
|
onChangeSetFileFn?: (e: File | File[]) => void;
|
|
22
22
|
showFileCarousel?: boolean;
|
|
23
23
|
maxLength?: number;
|