@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.
@@ -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
- };