@chris-c-brine/rhf-mui-kit 0.1.4 → 0.4.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/README.md +4 -4
- package/dist/components/AutocompleteElementDisplay.d.ts +2 -4
- package/dist/components/AutocompleteElementDisplay.js +7 -15
- package/dist/components/{ObjectDisplayElement.d.ts → ObjectElementDisplay.d.ts} +24 -8
- package/dist/components/ObjectElementDisplay.js +234 -0
- package/dist/components/TextElementDisplay.d.ts +18 -0
- package/dist/components/TextElementDisplay.js +17 -0
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.js +2 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +196 -159
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +5 -3
- package/dist/utils.js +25 -2
- package/package.json +1 -1
- package/dist/components/ObjectDisplayElement.js +0 -191
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createElement as _createElement } from "react";
|
|
3
|
-
import { useMemo, useState } from "react";
|
|
4
|
-
import { Checkbox, Chip, Typography } from "@mui/material";
|
|
5
|
-
import { AutocompleteElementDisplay, } from "./AutocompleteElementDisplay";
|
|
6
|
-
import { getAutocompleteRenderValue } from "../utils";
|
|
7
|
-
import { useController } from "react-hook-form-mui";
|
|
8
|
-
import { useOnMount } from "../hooks";
|
|
9
|
-
import lodash from "lodash";
|
|
10
|
-
const { omit } = lodash;
|
|
11
|
-
/**
|
|
12
|
-
* A form component that displays a searchable dropdown for selecting object values.
|
|
13
|
-
* Extends AutocompleteDisplayElement with object-specific functionality.
|
|
14
|
-
*
|
|
15
|
-
* Features:
|
|
16
|
-
* - Works with complex object values instead of just primitive types
|
|
17
|
-
* - Supports both single and multiple selection modes
|
|
18
|
-
* - Supports free-solo mode for creating new items from text input
|
|
19
|
-
* - Handles initial values that aren't in the default options
|
|
20
|
-
* - Deduplicates options based on item keys
|
|
21
|
-
*
|
|
22
|
-
* @template TValue - The type of the option values
|
|
23
|
-
* @template Multiple - Boolean flag indicating if multiple selections are allowed
|
|
24
|
-
* @template DisableClearable - Boolean flag indicating if clearing the selection is disabled
|
|
25
|
-
* @template FreeSolo - Boolean flag indicating if free text input is allowed
|
|
26
|
-
* @template ChipComponent - The component type used for rendering chips in multiple selection mode
|
|
27
|
-
* @template TFieldValues - The type of the form values
|
|
28
|
-
* @template TName - The type of the field name
|
|
29
|
-
*
|
|
30
|
-
* @returns A React component for selecting object values
|
|
31
|
-
*/
|
|
32
|
-
export const ObjectDisplayElement = ({ options, autocompleteProps, getItemKey, getItemLabel, getChipProps, stringToNewItem, name, freeSolo, control, ...props }) => {
|
|
33
|
-
/**
|
|
34
|
-
* Access to the form field controller
|
|
35
|
-
*/
|
|
36
|
-
const { field } = useController({ name, control });
|
|
37
|
-
/**
|
|
38
|
-
* State for the current text input in free-solo mode
|
|
39
|
-
*/
|
|
40
|
-
const [freeSoloValue, setFreeSoloValue] = useState("");
|
|
41
|
-
/**
|
|
42
|
-
* State for storing dynamically added options that aren't in the original options list
|
|
43
|
-
*/
|
|
44
|
-
const [newOptions, setNewOptions] = useState([]);
|
|
45
|
-
/**
|
|
46
|
-
* On component mount, handle initial field values that aren't in the options
|
|
47
|
-
* This is important for loading saved data that might reference items not in the current options
|
|
48
|
-
*/
|
|
49
|
-
useOnMount(() => {
|
|
50
|
-
if (!freeSolo || !field.value)
|
|
51
|
-
return;
|
|
52
|
-
// Handle both single and multiple selection modes
|
|
53
|
-
const fieldValues = Array.isArray(field.value) ? field.value : [field.value];
|
|
54
|
-
// Filter out values that are already in options
|
|
55
|
-
const newFieldOptions = fieldValues.filter((value) => {
|
|
56
|
-
// Skip string values as they're handled differently
|
|
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);
|
|
65
|
-
});
|
|
66
|
-
/**
|
|
67
|
-
* Creates a new item from the current free-solo text input
|
|
68
|
-
* Returns undefined if:
|
|
69
|
-
* - Free-solo mode is disabled
|
|
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
|
|
75
|
-
*/
|
|
76
|
-
const freeSoloItem = useMemo(() => {
|
|
77
|
-
if (!freeSolo || !stringToNewItem || !freeSoloValue.length)
|
|
78
|
-
return undefined;
|
|
79
|
-
const item = stringToNewItem(freeSoloValue);
|
|
80
|
-
const itemKey = getItemKey(item);
|
|
81
|
-
if (options.some((option) => getItemKey(option) === itemKey))
|
|
82
|
-
return undefined;
|
|
83
|
-
if (newOptions.some((option) => getItemKey(option) === itemKey))
|
|
84
|
-
return undefined;
|
|
85
|
-
return item;
|
|
86
|
-
}, [stringToNewItem, freeSoloValue, newOptions, freeSolo, options, getItemKey]);
|
|
87
|
-
/**
|
|
88
|
-
* Creates a combined and deduplicated list of all available options
|
|
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
|
|
95
|
-
*/
|
|
96
|
-
const allOptions = useMemo(() => {
|
|
97
|
-
if (!freeSolo)
|
|
98
|
-
return options;
|
|
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) => {
|
|
106
|
-
const key = getItemKey(option);
|
|
107
|
-
if (uniqueKeys.has(key))
|
|
108
|
-
return false;
|
|
109
|
-
uniqueKeys.add(key);
|
|
110
|
-
return true;
|
|
111
|
-
});
|
|
112
|
-
}, [options, newOptions, freeSolo, freeSoloItem, getItemKey]);
|
|
113
|
-
// console.log({ allOptions });
|
|
114
|
-
return (_jsx(AutocompleteElementDisplay, { name: name, control: control, options: allOptions, ...props, autocompleteProps: {
|
|
115
|
-
/**
|
|
116
|
-
* Determines if two options should be considered equal
|
|
117
|
-
* Uses the getItemKey function to compare option values
|
|
118
|
-
*/
|
|
119
|
-
isOptionEqualToValue: (o, v) => getItemKey(o) === getItemKey(v),
|
|
120
|
-
/**
|
|
121
|
-
* Filters options based on the input value
|
|
122
|
-
* Checks if the option key contains the input value (case-insensitive)
|
|
123
|
-
*/
|
|
124
|
-
filterOptions: (options, { inputValue }) => options.filter((option) => getItemKey(option).toLowerCase().includes(inputValue.toLowerCase())),
|
|
125
|
-
freeSolo, // Allowed to enter own string value
|
|
126
|
-
autoComplete: true,
|
|
127
|
-
autoHighlight: true, // The first option is highlighted by default
|
|
128
|
-
openOnFocus: true, // Opens the menu when tabbed into
|
|
129
|
-
/**
|
|
130
|
-
* Custom rendering for each option in the dropdown list
|
|
131
|
-
* Displays a checkbox for multiple selection if showCheckbox is true
|
|
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
|
|
141
|
-
*/
|
|
142
|
-
onChange: (event, value, reason, details) => {
|
|
143
|
-
if (freeSolo && freeSoloItem) {
|
|
144
|
-
if (stringToNewItem == undefined) {
|
|
145
|
-
throw new Error("Must implement stringToNewItem with freeSolo!");
|
|
146
|
-
}
|
|
147
|
-
setNewOptions((prev) => [...prev, freeSoloItem]);
|
|
148
|
-
setFreeSoloValue("");
|
|
149
|
-
}
|
|
150
|
-
autocompleteProps?.onChange?.(event, value, reason, details);
|
|
151
|
-
},
|
|
152
|
-
/**
|
|
153
|
-
* Handles changes to the input text
|
|
154
|
-
* Updates freeSoloValue state with the current input
|
|
155
|
-
* Delegates to the original onInputChange handler if provided
|
|
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);
|
|
162
|
-
},
|
|
163
|
-
/**
|
|
164
|
-
* Custom rendering for the selected value(s)
|
|
165
|
-
* For multiple selection, renders a Chip for each selected value
|
|
166
|
-
* For single selection, renders the value as text
|
|
167
|
-
* Uses getItemLabel to render the value labels
|
|
168
|
-
*/
|
|
169
|
-
renderValue: (value, getItemProps, ownerState) => {
|
|
170
|
-
const typedValue = getAutocompleteRenderValue(value, ownerState);
|
|
171
|
-
if (Array.isArray(typedValue)) {
|
|
172
|
-
return typedValue.map((v, index) => {
|
|
173
|
-
// @ts-expect-error a key is returned, and the linter doesn't pick this up
|
|
174
|
-
const { key, ...chipProps } = getItemProps({ index });
|
|
175
|
-
// const { key, ...rawChipProps } = getItemProps({ index });
|
|
176
|
-
// const chipProps = omit(rawChipProps, "onDelete");
|
|
177
|
-
const label = typeof v === "string" ? v : getItemLabel(v);
|
|
178
|
-
// Get additional chip props based on the value if the function is provided
|
|
179
|
-
const valueSpecificProps = typeof v !== "string" && getChipProps ? getChipProps({ value: v, index }) : {};
|
|
180
|
-
return (_jsx(Chip, { label: label, ...valueSpecificProps, ...chipProps }, `${name}-chip-${key}`));
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
// @ts-expect-error a key is returned, and the linter doesn't pick this up
|
|
184
|
-
// const { key, ...rawChipProps } = getItemProps({ index: 0 });
|
|
185
|
-
const { key, ...rawChipProps } = getItemProps({ index: 0 });
|
|
186
|
-
const itemProps = omit(rawChipProps, "onDelete");
|
|
187
|
-
return (_jsx(Typography, { component: "span", color: "text.primary", ...(props?.viewOnly ? omit(itemProps, "disabled") : itemProps), children: (typeof typedValue === "string") ? typedValue : getItemLabel(typedValue) }, `${name}-value-${key}`));
|
|
188
|
-
},
|
|
189
|
-
...autocompleteProps,
|
|
190
|
-
} }));
|
|
191
|
-
};
|