@chris-c-brine/rhf-mui-kit 0.2.0 → 0.5.0
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/components/AutocompleteElementDisplay.d.ts +2 -4
- package/dist/components/AutocompleteElementDisplay.js +7 -15
- package/dist/components/ObjectElementDisplay.d.ts +22 -6
- package/dist/components/ObjectElementDisplay.js +161 -106
- package/dist/components/TextElementDisplay.d.ts +18 -0
- package/dist/components/TextElementDisplay.js +17 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +197 -154
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +5 -3
- package/dist/utils.js +25 -2
- package/package.json +2 -2
|
@@ -2,10 +2,8 @@ import { type AutocompleteElementProps } from "react-hook-form-mui";
|
|
|
2
2
|
import { type ChipTypeMap } from "@mui/material";
|
|
3
3
|
import type { FieldPath, FieldValues } from "react-hook-form";
|
|
4
4
|
import { type ElementType } from "react";
|
|
5
|
-
export type AutocompleteElementDisplayProps<TValue, Multiple extends boolean | undefined, DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, ChipComponent extends ElementType = ChipTypeMap["defaultComponent"], TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>> = AutocompleteElementProps<TValue, Multiple, DisableClearable, FreeSolo, ChipComponent, TFieldValues, TName> &
|
|
6
|
-
export declare const AutocompleteElementDisplay: <TValue, Multiple extends boolean | undefined, DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, ChipComponent extends ElementType = ChipTypeMap["defaultComponent"], TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ viewOnly, disableUnderline, textFieldProps, autocompleteProps, ...props }: AutocompleteElementDisplayProps<TValue, Multiple, DisableClearable, FreeSolo, ChipComponent, TFieldValues, TName>) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
-
type Viewable = {
|
|
5
|
+
export type AutocompleteElementDisplayProps<TValue, Multiple extends boolean | undefined, DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, ChipComponent extends ElementType = ChipTypeMap["defaultComponent"], TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>> = AutocompleteElementProps<TValue, Multiple, DisableClearable, FreeSolo, ChipComponent, TFieldValues, TName> & {
|
|
8
6
|
viewOnly?: boolean;
|
|
9
7
|
disableUnderline?: boolean;
|
|
10
8
|
};
|
|
11
|
-
export {};
|
|
9
|
+
export declare const AutocompleteElementDisplay: <TValue, Multiple extends boolean | undefined, DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, ChipComponent extends ElementType = ChipTypeMap["defaultComponent"], TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ viewOnly, disableUnderline, textFieldProps, autocompleteProps, ...props }: AutocompleteElementDisplayProps<TValue, Multiple, DisableClearable, FreeSolo, ChipComponent, TFieldValues, TName>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,30 +2,22 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { AutocompleteElement } from "react-hook-form-mui";
|
|
3
3
|
import { useMemo } from "react";
|
|
4
4
|
import lodash from "lodash";
|
|
5
|
+
import { getTextElementDisplayProps } from "../utils";
|
|
5
6
|
const { merge } = lodash;
|
|
6
|
-
export const AutocompleteElementDisplay = ({ viewOnly, disableUnderline, textFieldProps, autocompleteProps, ...props }) => {
|
|
7
|
+
export const AutocompleteElementDisplay = ({ viewOnly = undefined, disableUnderline, textFieldProps, autocompleteProps, ...props }) => {
|
|
7
8
|
const autocompleteAdjustedProps = useMemo(() => merge({
|
|
8
9
|
readOnly: viewOnly,
|
|
9
|
-
disableClearable: viewOnly,
|
|
10
|
+
disableClearable: autocompleteProps?.disableClearable || viewOnly,
|
|
10
11
|
disabled: viewOnly,
|
|
11
|
-
slotProps: {
|
|
12
|
-
input: { disableUnderline },
|
|
13
|
-
chip: {
|
|
14
|
-
disabled: false,
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
12
|
}, autocompleteProps, viewOnly
|
|
18
13
|
? {
|
|
19
14
|
sx: {
|
|
20
|
-
".MuiAutocomplete-endAdornment": {
|
|
21
|
-
display: "none",
|
|
22
|
-
},
|
|
23
15
|
".MuiAutocomplete-tag": {
|
|
24
|
-
opacity: "1 !important"
|
|
16
|
+
opacity: "1 !important",
|
|
25
17
|
},
|
|
26
18
|
},
|
|
27
19
|
}
|
|
28
|
-
: {}), [autocompleteProps, viewOnly
|
|
29
|
-
const textFieldAdjustedProps = useMemo(() =>
|
|
30
|
-
return (_jsx(AutocompleteElement, {
|
|
20
|
+
: {}), [autocompleteProps, viewOnly]);
|
|
21
|
+
const textFieldAdjustedProps = useMemo(() => getTextElementDisplayProps(textFieldProps, viewOnly, disableUnderline), [textFieldProps, viewOnly, disableUnderline]);
|
|
22
|
+
return (_jsx(AutocompleteElement, { autocompleteProps: autocompleteAdjustedProps, textFieldProps: textFieldAdjustedProps, ...props }));
|
|
31
23
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type ElementType
|
|
2
|
-
import { type ChipProps, type ChipTypeMap } from "@mui/material";
|
|
3
|
-
import type { FieldPath, FieldValues } from "react-hook-form";
|
|
1
|
+
import { type ElementType } from "react";
|
|
2
|
+
import { type ChipProps, type ChipTypeMap, type ListItemProps } from "@mui/material";
|
|
3
|
+
import type { FieldPath, FieldValue, FieldValues } from "react-hook-form";
|
|
4
4
|
import { type AutocompleteElementDisplayProps } from "./AutocompleteElementDisplay";
|
|
5
5
|
/**
|
|
6
6
|
* Extends AutocompleteElementDisplayProps with additional properties for handling object values.
|
|
@@ -27,9 +27,17 @@ export type ObjectElementDisplayProps<TValue, Multiple extends boolean | undefin
|
|
|
27
27
|
* Can return any ReactNode for custom rendering.
|
|
28
28
|
*
|
|
29
29
|
* @param value - The option value or null
|
|
30
|
-
* @returns A
|
|
30
|
+
* @returns A string to display as the option label
|
|
31
31
|
*/
|
|
32
|
-
getItemLabel: (value: TValue | null) =>
|
|
32
|
+
getItemLabel: (value: TValue | null) => string;
|
|
33
|
+
/**
|
|
34
|
+
* Function to get additional props for an option list item.
|
|
35
|
+
* Used for customizing the rendering of the option.
|
|
36
|
+
*
|
|
37
|
+
* @param value - The option value or null
|
|
38
|
+
* @returns Additional props to apply to the list item
|
|
39
|
+
*/
|
|
40
|
+
getOptionProps?: (value: TValue | null) => ListItemProps;
|
|
33
41
|
/**
|
|
34
42
|
* Function to convert a free text input string to a TValue object.
|
|
35
43
|
* Required when freeSolo is true to create new items from text input.
|
|
@@ -54,6 +62,14 @@ export type ObjectElementDisplayProps<TValue, Multiple extends boolean | undefin
|
|
|
54
62
|
value: TValue;
|
|
55
63
|
index: number;
|
|
56
64
|
}) => Partial<ChipProps> | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Optional function to transform the value before it's updated in the form.
|
|
67
|
+
* This allows for custom processing or enrichment of the selected value.
|
|
68
|
+
*
|
|
69
|
+
* @param value - The value that would normally be sent to the form
|
|
70
|
+
* @returns The transformed value to be stored in the form
|
|
71
|
+
*/
|
|
72
|
+
transformValue?: (value: Multiple extends true ? TValue[] : TValue | null) => FieldValue<FieldValues>;
|
|
57
73
|
};
|
|
58
74
|
/**
|
|
59
75
|
* A form component that displays a searchable dropdown for selecting object values.
|
|
@@ -76,4 +92,4 @@ export type ObjectElementDisplayProps<TValue, Multiple extends boolean | undefin
|
|
|
76
92
|
*
|
|
77
93
|
* @returns A React component for selecting object values
|
|
78
94
|
*/
|
|
79
|
-
export declare const ObjectElementDisplay: <TValue, Multiple extends boolean | undefined, DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, ChipComponent extends ElementType = ChipTypeMap["defaultComponent"], TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ options, autocompleteProps, getItemKey, getItemLabel, getChipProps, stringToNewItem, name, freeSolo, control, ...props }: ObjectElementDisplayProps<TValue, Multiple, DisableClearable, FreeSolo, ChipComponent, TFieldValues, TName>) => import("react/jsx-runtime").JSX.Element;
|
|
95
|
+
export declare const ObjectElementDisplay: <TValue, Multiple extends boolean | undefined, DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, ChipComponent extends ElementType = ChipTypeMap["defaultComponent"], TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ options, autocompleteProps, getItemKey, getItemLabel, getChipProps, stringToNewItem, transformValue, name, freeSolo, control, getOptionProps, ...props }: ObjectElementDisplayProps<TValue, Multiple, DisableClearable, FreeSolo, ChipComponent, TFieldValues, TName>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createElement as _createElement } from "react";
|
|
3
|
-
import { useMemo, useState } from "react";
|
|
4
|
-
import { Checkbox, Chip,
|
|
3
|
+
import { useMemo, useState, useEffect } from "react";
|
|
4
|
+
import { Checkbox, Chip, ListItem, } from "@mui/material";
|
|
5
5
|
import { AutocompleteElementDisplay, } from "./AutocompleteElementDisplay";
|
|
6
|
-
import {
|
|
6
|
+
import { getAutocompleteTypedValue, isNonNullableString } from "../utils";
|
|
7
7
|
import { useController } from "react-hook-form-mui";
|
|
8
|
-
import {
|
|
9
|
-
import lodash from "lodash";
|
|
10
|
-
const { omit } = lodash;
|
|
8
|
+
import { merge } from "lodash";
|
|
11
9
|
/**
|
|
12
10
|
* A form component that displays a searchable dropdown for selecting object values.
|
|
13
11
|
* Extends AutocompleteElementDisplay with object-specific functionality.
|
|
@@ -29,88 +27,53 @@ const { omit } = lodash;
|
|
|
29
27
|
*
|
|
30
28
|
* @returns A React component for selecting object values
|
|
31
29
|
*/
|
|
32
|
-
export const ObjectElementDisplay = ({ options, autocompleteProps, getItemKey, getItemLabel, getChipProps, stringToNewItem, name, freeSolo, control, ...props }) => {
|
|
30
|
+
export const ObjectElementDisplay = ({ options, autocompleteProps, getItemKey, getItemLabel, getChipProps, stringToNewItem, transformValue, name, freeSolo, control, getOptionProps, ...props }) => {
|
|
33
31
|
/**
|
|
34
32
|
* Access to the form field controller
|
|
35
33
|
*/
|
|
36
34
|
const { field } = useController({ name, control });
|
|
37
|
-
/**
|
|
38
|
-
* State for the current text input in free-solo mode
|
|
39
|
-
*/
|
|
40
|
-
const [freeSoloValue, setFreeSoloValue] = useState("");
|
|
41
35
|
/**
|
|
42
36
|
* State for storing dynamically added options that aren't in the original options list
|
|
37
|
+
* Includes both default values and values added during freeSolo mode
|
|
43
38
|
*/
|
|
44
|
-
const [newOptions, setNewOptions] = useState(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*/
|
|
49
|
-
useOnMount(() => {
|
|
50
|
-
if (!freeSolo || !field.value)
|
|
51
|
-
return;
|
|
52
|
-
// Handle both single and multiple selection modes
|
|
39
|
+
const [newOptions, setNewOptions] = useState(() => {
|
|
40
|
+
if (!field.value)
|
|
41
|
+
return [];
|
|
42
|
+
// Convert field value to array for consistent handling
|
|
53
43
|
const fieldValues = Array.isArray(field.value) ? field.value : [field.value];
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (typeof value === "string")
|
|
58
|
-
return false;
|
|
59
|
-
// Check if this value exists in options
|
|
60
|
-
return !options.some((option) => getItemKey(option) === getItemKey(value));
|
|
61
|
-
});
|
|
62
|
-
// Add new values to newOptions if any were found
|
|
63
|
-
if (newFieldOptions.length > 0)
|
|
64
|
-
setNewOptions(newFieldOptions);
|
|
44
|
+
// Keep only object values that don't exist in the options array
|
|
45
|
+
return fieldValues.filter((value) => typeof value !== "string" &&
|
|
46
|
+
!options.some((option) => getItemKey(option) === getItemKey(value)));
|
|
65
47
|
});
|
|
66
48
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* - No stringToNewItem converter is provided
|
|
71
|
-
* - Input is empty
|
|
72
|
-
* - An item with the same key already exists in options or newOptions
|
|
73
|
-
*
|
|
74
|
-
* @returns The new item created from the free-solo input, or undefined
|
|
49
|
+
* Update newOptions when field.value changes
|
|
50
|
+
* This ensures that any new values added to the field after rendering
|
|
51
|
+
* are properly included in newOptions and displayed
|
|
75
52
|
*/
|
|
76
|
-
|
|
77
|
-
if (!
|
|
78
|
-
return
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}, [
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!field.value)
|
|
55
|
+
return;
|
|
56
|
+
const fieldValues = Array.isArray(field.value) ? field.value : [field.value];
|
|
57
|
+
const newFieldOptions = fieldValues.filter((value) => typeof value !== "string" &&
|
|
58
|
+
![...options, ...newOptions].some((option) => getItemKey(option) === getItemKey(value)));
|
|
59
|
+
// Only update newOptions if there are new values to add
|
|
60
|
+
if (newFieldOptions.length > 0) {
|
|
61
|
+
setNewOptions((prevOptions) => [...prevOptions, ...newFieldOptions]);
|
|
62
|
+
}
|
|
63
|
+
}, [field.value, options, newOptions, getItemKey]);
|
|
87
64
|
/**
|
|
88
|
-
*
|
|
89
|
-
* Includes:
|
|
90
|
-
* - Original options from props
|
|
91
|
-
* - Dynamically added options from newOptions
|
|
92
|
-
* - Current free-solo item if it exists
|
|
93
|
-
*
|
|
94
|
-
* @returns Array of all available options with duplicates removed
|
|
65
|
+
* Combined list of all available options (original + dynamically added)
|
|
95
66
|
*/
|
|
96
67
|
const allOptions = useMemo(() => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// Combine all options and deduplicate by key
|
|
100
|
-
const combinedOptions = [...options, ...newOptions];
|
|
101
|
-
if (freeSoloItem)
|
|
102
|
-
combinedOptions.push(freeSoloItem);
|
|
103
|
-
// Deduplicate using getItemKey
|
|
104
|
-
const uniqueKeys = new Set();
|
|
105
|
-
return combinedOptions.filter((option) => {
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
return [...options, ...newOptions].filter((option) => {
|
|
106
70
|
const key = getItemKey(option);
|
|
107
|
-
if (
|
|
71
|
+
if (seen.has(key))
|
|
108
72
|
return false;
|
|
109
|
-
|
|
73
|
+
seen.add(key);
|
|
110
74
|
return true;
|
|
111
75
|
});
|
|
112
|
-
}, [options, newOptions
|
|
113
|
-
// console.log({ allOptions });
|
|
76
|
+
}, [options, newOptions]);
|
|
114
77
|
return (_jsx(AutocompleteElementDisplay, { name: name, control: control, options: allOptions, ...props, autocompleteProps: {
|
|
115
78
|
/**
|
|
116
79
|
* Determines if two options should be considered equal
|
|
@@ -119,46 +82,134 @@ export const ObjectElementDisplay = ({ options, autocompleteProps, getItemKey, g
|
|
|
119
82
|
isOptionEqualToValue: (o, v) => getItemKey(o) === getItemKey(v),
|
|
120
83
|
/**
|
|
121
84
|
* Filters options based on the input value
|
|
122
|
-
* Checks if the option key contains the input value (case-insensitive)
|
|
85
|
+
* Checks if the option key or label contains the input value (case-insensitive)
|
|
86
|
+
* For freeSolo mode, adds a special "Add [value]" option when there's no exact match
|
|
123
87
|
*/
|
|
124
|
-
filterOptions: (options, { inputValue }) =>
|
|
88
|
+
filterOptions: (options, { inputValue }) => {
|
|
89
|
+
if (!inputValue)
|
|
90
|
+
return options;
|
|
91
|
+
const searchValue = inputValue.toLowerCase();
|
|
92
|
+
// Filter options that match the input value (by key or label)
|
|
93
|
+
const filteredOptions = options.filter((option) => {
|
|
94
|
+
const key = getItemKey(option).toLowerCase();
|
|
95
|
+
const label = String(getItemLabel(option)).toLowerCase();
|
|
96
|
+
return key.includes(searchValue) || label.includes(searchValue);
|
|
97
|
+
});
|
|
98
|
+
// For freeSolo mode, add "Add [value]" option if no exact match exists
|
|
99
|
+
if (freeSolo && stringToNewItem && inputValue.length > 0) {
|
|
100
|
+
const hasExactMatch = filteredOptions.some((option) => String(getItemLabel(option)).toLowerCase() === searchValue);
|
|
101
|
+
if (!hasExactMatch) {
|
|
102
|
+
// Create a special option with a __isAddOption flag
|
|
103
|
+
const addOption = {
|
|
104
|
+
__isAddOption: true,
|
|
105
|
+
inputValue,
|
|
106
|
+
...stringToNewItem(inputValue), // Include properties for type compatibility
|
|
107
|
+
};
|
|
108
|
+
return [addOption, ...filteredOptions];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return filteredOptions;
|
|
112
|
+
},
|
|
125
113
|
freeSolo, // Allowed to enter own string value
|
|
126
114
|
autoComplete: true,
|
|
127
115
|
autoHighlight: true, // The first option is highlighted by default
|
|
128
116
|
openOnFocus: true, // Opens the menu when tabbed into
|
|
129
117
|
/**
|
|
130
118
|
* Custom rendering for each option in the dropdown list
|
|
131
|
-
*
|
|
132
|
-
* Uses getItemLabel to render the option label
|
|
133
|
-
*/
|
|
134
|
-
renderOption: (liProps, option, { selected }) => (_createElement("li", { ...liProps, key: `${name}-option-${getItemKey(option)}` },
|
|
135
|
-
props?.showCheckbox && _jsx(Checkbox, { sx: { marginRight: 1 }, checked: selected }),
|
|
136
|
-
typeof option === "string" ? option : getItemLabel(option))),
|
|
137
|
-
/**
|
|
138
|
-
* Handles changes to the selected value(s)
|
|
139
|
-
* In free-solo mode, adds the new item to newOptions when selected
|
|
140
|
-
* Delegates to the original onChange handler if provided
|
|
119
|
+
* Handles both regular options and special "Add" options in freeSolo mode
|
|
141
120
|
*/
|
|
121
|
+
renderOption: (liProps, option, { selected }, ownerState) => {
|
|
122
|
+
const itemProps = merge(liProps, getOptionProps?.(option) ?? {});
|
|
123
|
+
// Handles the special "Add" option in freeSolo mode
|
|
124
|
+
if (ownerState?.freeSolo &&
|
|
125
|
+
typeof option === "object" &&
|
|
126
|
+
option !== null &&
|
|
127
|
+
"__isAddOption" in option) {
|
|
128
|
+
const inputValue = option.inputValue;
|
|
129
|
+
return (_createElement(ListItem, { ...itemProps, key: `${name}-add-option-${inputValue}` },
|
|
130
|
+
"Add: '",
|
|
131
|
+
inputValue,
|
|
132
|
+
"'"));
|
|
133
|
+
}
|
|
134
|
+
// Handle regular option
|
|
135
|
+
return (_createElement(ListItem, { ...itemProps, key: `${name}-option-${getItemKey(option)}` },
|
|
136
|
+
(props?.showCheckbox && ownerState?.multiple) && (_jsx(Checkbox, { sx: { marginRight: 1 }, checked: selected })),
|
|
137
|
+
typeof option === "string" ? option : getItemLabel(option)));
|
|
138
|
+
},
|
|
142
139
|
onChange: (event, value, reason, details) => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Helper function to apply transformValue if provided, otherwise return the original value
|
|
142
|
+
*/
|
|
143
|
+
const applyTransform = (val) => {
|
|
144
|
+
return transformValue && val !== null
|
|
145
|
+
? field.onChange(transformValue(val))
|
|
146
|
+
: field.onChange(val);
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Helper function to add a new item to newOptions if it doesn't exist already
|
|
150
|
+
*/
|
|
151
|
+
const addToNewOptions = (item) => {
|
|
152
|
+
const itemKey = getItemKey(item);
|
|
153
|
+
const itemExists = [...options, ...newOptions].some((option) => getItemKey(option) === itemKey);
|
|
154
|
+
if (!itemExists) {
|
|
155
|
+
setNewOptions((prev) => [...prev, item]);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Helper function to extract input value from string or AddOption
|
|
160
|
+
*/
|
|
161
|
+
const getInputValue = (item) => {
|
|
162
|
+
if (typeof item === "string" && item.length > 0) {
|
|
163
|
+
return item;
|
|
164
|
+
}
|
|
165
|
+
if (typeof item === "object" && item !== null && "__isAddOption" in item) {
|
|
166
|
+
return item.inputValue;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
};
|
|
170
|
+
// Handle freeSolo mode with stringToNewItem function
|
|
171
|
+
if (freeSolo && stringToNewItem) {
|
|
172
|
+
// Handle special add option selection or string input
|
|
173
|
+
const inputValue = getInputValue(value);
|
|
174
|
+
if (inputValue) {
|
|
175
|
+
const newItem = stringToNewItem(inputValue);
|
|
176
|
+
if (props.multiple) {
|
|
177
|
+
// For multiple selection, add the new item to the current values
|
|
178
|
+
const currentValues = Array.isArray(field.value) ? field.value : [];
|
|
179
|
+
const newValues = [...currentValues, newItem];
|
|
180
|
+
applyTransform(newValues);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
// For single selection, just use the new item
|
|
184
|
+
applyTransform(newItem);
|
|
185
|
+
}
|
|
186
|
+
addToNewOptions(newItem);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// Handle array values (multiple selection)
|
|
190
|
+
if (Array.isArray(value) && props.multiple) {
|
|
191
|
+
// Convert any string values to objects and handle special add options
|
|
192
|
+
const newValues = value?.map((item) => {
|
|
193
|
+
const inputVal = getInputValue(item);
|
|
194
|
+
return inputVal ? stringToNewItem(inputVal) : item;
|
|
195
|
+
}) ?? [];
|
|
196
|
+
applyTransform(newValues);
|
|
197
|
+
// Add any new items to newOptions
|
|
198
|
+
const allOptionsKeys = [...options, ...newOptions].map((option) => getItemKey(option));
|
|
199
|
+
const newItems = newValues.filter((item) => typeof item !== "string" && !allOptionsKeys.includes(getItemKey(item)));
|
|
200
|
+
if (newItems.length > 0) {
|
|
201
|
+
setNewOptions((prev) => [...prev, ...newItems]);
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
146
204
|
}
|
|
147
|
-
setNewOptions((prev) => [...prev, freeSoloItem]);
|
|
148
|
-
setFreeSoloValue("");
|
|
149
205
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
onInputChange: (event, value, reason) => {
|
|
158
|
-
// event.preventDefault();
|
|
159
|
-
setFreeSoloValue(value);
|
|
160
|
-
// Call the original onInputChange if it exists
|
|
161
|
-
autocompleteProps?.onInputChange?.(event, value, reason);
|
|
206
|
+
// Default behavior for non-freeSolo cases
|
|
207
|
+
if (transformValue && value !== null) {
|
|
208
|
+
applyTransform(value);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
autocompleteProps?.onChange?.(event, value, reason, details);
|
|
212
|
+
}
|
|
162
213
|
},
|
|
163
214
|
/**
|
|
164
215
|
* Custom rendering for the selected value(s)
|
|
@@ -167,21 +218,25 @@ export const ObjectElementDisplay = ({ options, autocompleteProps, getItemKey, g
|
|
|
167
218
|
* Uses getItemLabel to render the value labels
|
|
168
219
|
*/
|
|
169
220
|
renderValue: (value, getItemProps, ownerState) => {
|
|
170
|
-
const typedValue =
|
|
221
|
+
const typedValue = getAutocompleteTypedValue(value, ownerState);
|
|
222
|
+
// Handle array values (multiple selection)
|
|
171
223
|
if (Array.isArray(typedValue)) {
|
|
172
224
|
return typedValue.map((v, index) => {
|
|
173
225
|
// @ts-expect-error a key is returned, and the linter doesn't pick this up
|
|
174
226
|
const { key, ...chipProps } = getItemProps({ index });
|
|
227
|
+
// Get the label - use string directly or extract from object
|
|
175
228
|
const label = typeof v === "string" ? v : getItemLabel(v);
|
|
176
|
-
// Get additional chip props
|
|
229
|
+
// Get additional chip props if available
|
|
177
230
|
const valueSpecificProps = typeof v !== "string" && getChipProps ? getChipProps({ value: v, index }) : {};
|
|
178
|
-
return (_jsx(Chip, {
|
|
231
|
+
return (_jsx(Chip, { ...valueSpecificProps, ...chipProps, label: label }, `${name}-chip-${key}`));
|
|
179
232
|
});
|
|
180
233
|
}
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
234
|
+
// Handles single value - return string or extracted label
|
|
235
|
+
return isNonNullableString(typedValue)
|
|
236
|
+
? typedValue
|
|
237
|
+
: typedValue
|
|
238
|
+
? getItemLabel(typedValue)
|
|
239
|
+
: "";
|
|
185
240
|
},
|
|
186
241
|
...autocompleteProps,
|
|
187
242
|
} }));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type TextFieldElementProps } from "react-hook-form-mui";
|
|
2
|
+
import { type FieldPath, type FieldValues } from "react-hook-form";
|
|
3
|
+
export type TextElementDisplayProps<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>> = TextFieldElementProps<TFieldValues, TName> & Viewable;
|
|
4
|
+
/**
|
|
5
|
+
* A form component that displays a text field with view-only capabilities.
|
|
6
|
+
* Extends TextFieldElement with view-only functionality.
|
|
7
|
+
*
|
|
8
|
+
* @template TFieldValues - The type of the form values
|
|
9
|
+
* @template TName - The type of the field name
|
|
10
|
+
*
|
|
11
|
+
* @returns A React component for text input with view-only support
|
|
12
|
+
*/
|
|
13
|
+
export declare const TextElementDisplay: <TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ viewOnly, disableUnderline, ...props }: TextElementDisplayProps<TFieldValues, TName>) => import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
type Viewable = {
|
|
15
|
+
viewOnly?: boolean;
|
|
16
|
+
disableUnderline?: boolean;
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { TextFieldElement } from "react-hook-form-mui";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { getTextElementDisplayProps } from "../utils";
|
|
5
|
+
/**
|
|
6
|
+
* A form component that displays a text field with view-only capabilities.
|
|
7
|
+
* Extends TextFieldElement with view-only functionality.
|
|
8
|
+
*
|
|
9
|
+
* @template TFieldValues - The type of the form values
|
|
10
|
+
* @template TName - The type of the field name
|
|
11
|
+
*
|
|
12
|
+
* @returns A React component for text input with view-only support
|
|
13
|
+
*/
|
|
14
|
+
export const TextElementDisplay = ({ viewOnly, disableUnderline, ...props }) => {
|
|
15
|
+
const adjustedProps = useMemo(() => getTextElementDisplayProps(props, viewOnly, disableUnderline), [props, viewOnly, disableUnderline]);
|
|
16
|
+
return _jsx(TextFieldElement, { ...adjustedProps });
|
|
17
|
+
};
|
package/dist/components/index.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("react/jsx-runtime"),T=require("react-hook-form-mui"),h=require("react"),g=require("lodash"),M=require("@mui/material"),B=require("react-hook-form"),{merge:R}=g;function L(e,n){return n?.multiple,n?.freeSolo,e}const S=(e,n=!1,r=!1)=>R(e,n?{disabled:!0,variant:"standard",sx:{"& .MuiAutocomplete-endAdornment":{display:"none"},...r&&{"& .MuiInput-underline:before":{borderBottom:"none"},"& .MuiInput-underline:after":{borderBottom:"none"},"& .MuiInput-underline:hover:not(.Mui-disabled):before":{borderBottom:"none"}}}}:{});function w(e){return e!=null&&typeof e=="string"}const{merge:z}=g,$=({viewOnly:e=void 0,disableUnderline:n,textFieldProps:r,autocompleteProps:c,...E})=>{const f=h.useMemo(()=>z({readOnly:e,disableClearable:c?.disableClearable||e,disabled:e},c,e?{sx:{".MuiAutocomplete-tag":{opacity:"1 !important"}}}:{}),[c,e]),A=h.useMemo(()=>S(r,e,n),[r,e,n]);return y.jsx(T.AutocompleteElement,{autocompleteProps:f,textFieldProps:A,...E})},G=({options:e,autocompleteProps:n,getItemKey:r,getItemLabel:c,getChipProps:E,stringToNewItem:f,transformValue:A,name:C,freeSolo:F,control:q,getOptionProps:_,...V})=>{const{field:a}=T.useController({name:C,control:q}),[b,k]=h.useState(()=>a.value?(Array.isArray(a.value)?a.value:[a.value]).filter(t=>typeof t!="string"&&!e.some(s=>r(s)===r(t))):[]);h.useEffect(()=>{if(!a.value)return;const t=(Array.isArray(a.value)?a.value:[a.value]).filter(s=>typeof s!="string"&&![...e,...b].some(u=>r(u)===r(s)));t.length>0&&k(s=>[...s,...t])},[a.value,e,b,r]);const H=h.useMemo(()=>{const i=new Set;return[...e,...b].filter(t=>{const s=r(t);return i.has(s)?!1:(i.add(s),!0)})},[e,b]);return y.jsx($,{name:C,control:q,options:H,...V,autocompleteProps:{isOptionEqualToValue:(i,t)=>r(i)===r(t),filterOptions:(i,{inputValue:t})=>{if(!t)return i;const s=t.toLowerCase(),u=i.filter(l=>{const d=r(l).toLowerCase(),j=String(c(l)).toLowerCase();return d.includes(s)||j.includes(s)});return F&&f&&t.length>0&&!u.some(d=>String(c(d)).toLowerCase()===s)?[{__isAddOption:!0,inputValue:t,...f(t)},...u]:u},freeSolo:F,autoComplete:!0,autoHighlight:!0,openOnFocus:!0,renderOption:(i,t,{selected:s},u)=>{const l=g.merge(i,_?.(t)??{});if(u?.freeSolo&&typeof t=="object"&&t!==null&&"__isAddOption"in t){const d=t.inputValue;return h.createElement(M.ListItem,{...l,key:`${C}-add-option-${d}`},"Add: '",d,"'")}return h.createElement(M.ListItem,{...l,key:`${C}-option-${r(t)}`},V?.showCheckbox&&u?.multiple&&y.jsx(M.Checkbox,{sx:{marginRight:1},checked:s}),typeof t=="string"?t:c(t))},onChange:(i,t,s,u)=>{const l=o=>A&&o!==null?a.onChange(A(o)):a.onChange(o),d=o=>{const p=r(o);[...e,...b].some(x=>r(x)===p)||k(x=>[...x,o])},j=o=>typeof o=="string"&&o.length>0?o:typeof o=="object"&&o!==null&&"__isAddOption"in o?o.inputValue:null;if(F&&f){const o=j(t);if(o){const p=f(o);if(V.multiple){const x=[...Array.isArray(a.value)?a.value:[],p];l(x)}else l(p);d(p);return}if(Array.isArray(t)&&V.multiple){const p=t?.map(m=>{const D=j(m);return D?f(D):m})??[];l(p);const O=[...e,...b].map(m=>r(m)),x=p.filter(m=>typeof m!="string"&&!O.includes(r(m)));x.length>0&&k(m=>[...m,...x]);return}}A&&t!==null?l(t):n?.onChange?.(i,t,s,u)},renderValue:(i,t,s)=>{const u=L(i,s);return Array.isArray(u)?u.map((l,d)=>{const{key:j,...o}=t({index:d}),p=typeof l=="string"?l:c(l),O=typeof l!="string"&&E?E({value:l,index:d}):{};return y.jsx(M.Chip,{...O,...o,label:p},`${C}-chip-${j}`)}):w(u)?u:u?c(u):""},...n}})},J=({viewOnly:e,disableUnderline:n,...r})=>{const c=h.useMemo(()=>S(r,e,n),[r,e,n]);return y.jsx(T.TextFieldElement,{...c})},{get:N}=g;function P(e,n){const r=N(e,n);return{error:!!r,helperText:r?.message||""}}function Q(e){const n=h.useRef(!1);h.useEffect(()=>(n.current||(e(),n.current=!0),()=>{n.current=!0}),[])}const W=({name:e,rules:n,formControlProps:r={}})=>{const{register:c,formState:{errors:E}}=B.useFormContext(),{error:f,helperText:A}=P(E,e);return y.jsxs(M.FormControl,{error:f,...r,children:[y.jsx("input",{type:"hidden",...c(e,n)}),f&&y.jsx(M.FormHelperText,{children:A})]})};exports.AutocompleteElementDisplay=$;exports.ObjectElementDisplay=G;exports.TextElementDisplay=J;exports.ValidationElement=W;exports.useFormError=P;exports.useOnMount=Q;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|