tp-react-elements-dev 1.13.2 → 1.13.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.
|
@@ -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: {
|