@chris-c-brine/rhf-mui-kit 0.2.0 → 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.
@@ -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> & Viewable;
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, disableUnderline]);
29
- const textFieldAdjustedProps = useMemo(() => merge(viewOnly ? { variant: "standard" } : {}, textFieldProps), [viewOnly, textFieldProps]);
30
- return (_jsx(AutocompleteElement, { ...props, autocompleteProps: autocompleteAdjustedProps, textFieldProps: textFieldAdjustedProps }));
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,5 +1,5 @@
1
- import { type ElementType, ReactNode } from "react";
2
- import { type ChipProps, type ChipTypeMap } from "@mui/material";
1
+ import { type ElementType } from "react";
2
+ import { type ChipProps, type ChipTypeMap, type ListItemProps } from "@mui/material";
3
3
  import type { FieldPath, FieldValues } from "react-hook-form";
4
4
  import { type AutocompleteElementDisplayProps } from "./AutocompleteElementDisplay";
5
5
  /**
@@ -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 ReactNode to display as the option label
30
+ * @returns A string to display as the option label
31
31
  */
32
- getItemLabel: (value: TValue | null) => ReactNode;
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?: Multiple extends true ? (value: TValue[]) => TFieldValues : (value: TValue | null) => TFieldValues | null;
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, Typography } from "@mui/material";
3
+ import { useMemo, useState, useEffect } from "react";
4
+ import { Checkbox, Chip, ListItem, } from "@mui/material";
5
5
  import { AutocompleteElementDisplay, } from "./AutocompleteElementDisplay";
6
- import { getAutocompleteRenderValue } from "../utils";
6
+ import { getAutocompleteTypedValue, isNonNullableString } from "../utils";
7
7
  import { useController } from "react-hook-form-mui";
8
- import { useOnMount } from "../hooks";
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,44 @@ 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
- * 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
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
- // 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);
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
- * 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
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
- 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]);
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
- * 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
65
+ * Combined list of all available options (original + dynamically added)
95
66
  */
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 });
67
+ const allOptions = useMemo(() => [...options, ...newOptions], [options, newOptions]);
114
68
  return (_jsx(AutocompleteElementDisplay, { name: name, control: control, options: allOptions, ...props, autocompleteProps: {
115
69
  /**
116
70
  * Determines if two options should be considered equal
@@ -119,46 +73,134 @@ export const ObjectElementDisplay = ({ options, autocompleteProps, getItemKey, g
119
73
  isOptionEqualToValue: (o, v) => getItemKey(o) === getItemKey(v),
120
74
  /**
121
75
  * Filters options based on the input value
122
- * Checks if the option key contains the input value (case-insensitive)
76
+ * Checks if the option key or label contains the input value (case-insensitive)
77
+ * For freeSolo mode, adds a special "Add [value]" option when there's no exact match
123
78
  */
124
- filterOptions: (options, { inputValue }) => options.filter((option) => getItemKey(option).toLowerCase().includes(inputValue.toLowerCase())),
79
+ filterOptions: (options, { inputValue }) => {
80
+ if (!inputValue)
81
+ return options;
82
+ const searchValue = inputValue.toLowerCase();
83
+ // Filter options that match the input value (by key or label)
84
+ const filteredOptions = options.filter((option) => {
85
+ const key = getItemKey(option).toLowerCase();
86
+ const label = String(getItemLabel(option)).toLowerCase();
87
+ return key.includes(searchValue) || label.includes(searchValue);
88
+ });
89
+ // For freeSolo mode, add "Add [value]" option if no exact match exists
90
+ if (freeSolo && stringToNewItem && inputValue.length > 0) {
91
+ const hasExactMatch = filteredOptions.some((option) => String(getItemLabel(option)).toLowerCase() === searchValue);
92
+ if (!hasExactMatch) {
93
+ // Create a special option with a __isAddOption flag
94
+ const addOption = {
95
+ __isAddOption: true,
96
+ inputValue,
97
+ ...stringToNewItem(inputValue), // Include properties for type compatibility
98
+ };
99
+ return [addOption, ...filteredOptions];
100
+ }
101
+ }
102
+ return filteredOptions;
103
+ },
125
104
  freeSolo, // Allowed to enter own string value
126
105
  autoComplete: true,
127
106
  autoHighlight: true, // The first option is highlighted by default
128
107
  openOnFocus: true, // Opens the menu when tabbed into
129
108
  /**
130
109
  * 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
110
+ * Handles both regular options and special "Add" options in freeSolo mode
141
111
  */
112
+ renderOption: (liProps, option, { selected }, ownerState) => {
113
+ const itemProps = merge(liProps, getOptionProps?.(option) ?? {});
114
+ // Handles the special "Add" option in freeSolo mode
115
+ if (ownerState?.freeSolo &&
116
+ typeof option === "object" &&
117
+ option !== null &&
118
+ "__isAddOption" in option) {
119
+ const inputValue = option.inputValue;
120
+ return (_createElement(ListItem, { ...itemProps, key: `${name}-add-option-${inputValue}` },
121
+ "Add: '",
122
+ inputValue,
123
+ "'"));
124
+ }
125
+ // Handle regular option
126
+ return (_createElement(ListItem, { ...itemProps, key: `${name}-option-${getItemKey(option)}` },
127
+ (props?.showCheckbox && ownerState?.multiple) && (_jsx(Checkbox, { sx: { marginRight: 1 }, checked: selected })),
128
+ typeof option === "string" ? option : getItemLabel(option)));
129
+ },
142
130
  onChange: (event, value, reason, details) => {
143
- if (freeSolo && freeSoloItem) {
144
- if (stringToNewItem == undefined) {
145
- throw new Error("Must implement stringToNewItem with freeSolo!");
131
+ /**
132
+ * Helper function to apply transformValue if provided, otherwise return the original value
133
+ */
134
+ const applyTransform = (val) => {
135
+ return transformValue && val !== null
136
+ ? field.onChange(transformValue(val))
137
+ : field.onChange(val);
138
+ };
139
+ /**
140
+ * Helper function to add a new item to newOptions if it doesn't exist already
141
+ */
142
+ const addToNewOptions = (item) => {
143
+ const itemKey = getItemKey(item);
144
+ const itemExists = [...options, ...newOptions].some((option) => getItemKey(option) === itemKey);
145
+ if (!itemExists) {
146
+ setNewOptions((prev) => [...prev, item]);
147
+ }
148
+ };
149
+ /**
150
+ * Helper function to extract input value from string or AddOption
151
+ */
152
+ const getInputValue = (item) => {
153
+ if (typeof item === "string" && item.length > 0) {
154
+ return item;
155
+ }
156
+ if (typeof item === "object" && item !== null && "__isAddOption" in item) {
157
+ return item.inputValue;
158
+ }
159
+ return null;
160
+ };
161
+ // Handle freeSolo mode with stringToNewItem function
162
+ if (freeSolo && stringToNewItem) {
163
+ // Handle special add option selection or string input
164
+ const inputValue = getInputValue(value);
165
+ if (inputValue) {
166
+ const newItem = stringToNewItem(inputValue);
167
+ if (props.multiple) {
168
+ // For multiple selection, add the new item to the current values
169
+ const currentValues = Array.isArray(field.value) ? field.value : [];
170
+ const newValues = [...currentValues, newItem];
171
+ applyTransform(newValues);
172
+ }
173
+ else {
174
+ // For single selection, just use the new item
175
+ applyTransform(newItem);
176
+ }
177
+ addToNewOptions(newItem);
178
+ return;
179
+ }
180
+ // Handle array values (multiple selection)
181
+ if (Array.isArray(value) && props.multiple) {
182
+ // Convert any string values to objects and handle special add options
183
+ const newValues = value?.map((item) => {
184
+ const inputVal = getInputValue(item);
185
+ return inputVal ? stringToNewItem(inputVal) : item;
186
+ }) ?? [];
187
+ applyTransform(newValues);
188
+ // Add any new items to newOptions
189
+ const allOptionsKeys = [...options, ...newOptions].map((option) => getItemKey(option));
190
+ const newItems = newValues.filter((item) => typeof item !== "string" && !allOptionsKeys.includes(getItemKey(item)));
191
+ if (newItems.length > 0) {
192
+ setNewOptions((prev) => [...prev, ...newItems]);
193
+ }
194
+ return;
146
195
  }
147
- setNewOptions((prev) => [...prev, freeSoloItem]);
148
- setFreeSoloValue("");
149
196
  }
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);
197
+ // Default behavior for non-freeSolo cases
198
+ if (transformValue && value !== null) {
199
+ applyTransform(value);
200
+ }
201
+ else {
202
+ autocompleteProps?.onChange?.(event, value, reason, details);
203
+ }
162
204
  },
163
205
  /**
164
206
  * Custom rendering for the selected value(s)
@@ -167,21 +209,25 @@ export const ObjectElementDisplay = ({ options, autocompleteProps, getItemKey, g
167
209
  * Uses getItemLabel to render the value labels
168
210
  */
169
211
  renderValue: (value, getItemProps, ownerState) => {
170
- const typedValue = getAutocompleteRenderValue(value, ownerState);
212
+ const typedValue = getAutocompleteTypedValue(value, ownerState);
213
+ // Handle array values (multiple selection)
171
214
  if (Array.isArray(typedValue)) {
172
215
  return typedValue.map((v, index) => {
173
216
  // @ts-expect-error a key is returned, and the linter doesn't pick this up
174
217
  const { key, ...chipProps } = getItemProps({ index });
218
+ // Get the label - use string directly or extract from object
175
219
  const label = typeof v === "string" ? v : getItemLabel(v);
176
- // Get additional chip props based on the value if the function is provided
220
+ // Get additional chip props if available
177
221
  const valueSpecificProps = typeof v !== "string" && getChipProps ? getChipProps({ value: v, index }) : {};
178
- return (_jsx(Chip, { label: label, ...valueSpecificProps, ...chipProps }, `${name}-chip-${key}`));
222
+ return (_jsx(Chip, { ...valueSpecificProps, ...chipProps, label: label }, `${name}-chip-${key}`));
179
223
  });
180
224
  }
181
- // @ts-expect-error a key is returned, and the linter doesn't pick this up
182
- const { key, ...rawChipProps } = getItemProps({ index: 0 });
183
- const itemProps = omit(rawChipProps, "onDelete");
184
- return (_jsx(Typography, { component: "span", color: "text.primary", ...(props?.viewOnly ? omit(itemProps, "disabled") : itemProps), children: (typeof typedValue === "string") ? typedValue : getItemLabel(typedValue) }, `${name}-value-${key}`));
225
+ // Handles single value - return string or extracted label
226
+ return isNonNullableString(typedValue)
227
+ ? typedValue
228
+ : typedValue
229
+ ? getItemLabel(typedValue)
230
+ : "";
185
231
  },
186
232
  ...autocompleteProps,
187
233
  } }));
@@ -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
+ };
@@ -1,3 +1,4 @@
1
1
  export * from "./AutocompleteElementDisplay";
2
2
  export * from "./ObjectElementDisplay";
3
+ export * from "./TextElementDisplay";
3
4
  export * from "./ValidationElement";
@@ -1,3 +1,4 @@
1
1
  export * from "./AutocompleteElementDisplay";
2
2
  export * from "./ObjectElementDisplay";
3
+ export * from "./TextElementDisplay";
3
4
  export * from "./ValidationElement";
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("react/jsx-runtime"),$=require("react-hook-form-mui"),l=require("react"),M=require("lodash"),x=require("@mui/material"),G=require("react-hook-form"),{merge:q}=M,D=({viewOnly:e,disableUnderline:n,textFieldProps:t,autocompleteProps:a,...f})=>{const i=l.useMemo(()=>q({readOnly:e,disableClearable:e,disabled:e,slotProps:{input:{disableUnderline:n},chip:{disabled:!1}}},a,e?{sx:{".MuiAutocomplete-endAdornment":{display:"none"},".MuiAutocomplete-tag":{opacity:"1 !important"}}}:{}),[a,e,n]),c=l.useMemo(()=>q(e?{variant:"standard"}:{},t),[e,t]);return p.jsx($.AutocompleteElement,{...f,autocompleteProps:i,textFieldProps:c})};function J(e,n){return n.multiple,n?.freeSolo,e}const{get:L}=M;function P(e,n){const t=L(e,n);return{error:!!t,helperText:t?.message||""}}function S(e){const n=l.useRef(!1);l.useEffect(()=>(n.current||(e(),n.current=!0),()=>{n.current=!0}),[])}const{omit:g}=M,Q=({options:e,autocompleteProps:n,getItemKey:t,getItemLabel:a,getChipProps:f,stringToNewItem:i,name:c,freeSolo:d,control:F,...C})=>{const{field:j}=$.useController({name:c,control:F}),[E,b]=l.useState(""),[y,k]=l.useState([]);S(()=>{if(!d||!j.value)return;const r=(Array.isArray(j.value)?j.value:[j.value]).filter(s=>typeof s=="string"?!1:!e.some(u=>t(u)===t(s)));r.length>0&&k(r)});const h=l.useMemo(()=>{if(!d||!i||!E.length)return;const o=i(E),r=t(o);if(!e.some(s=>t(s)===r)&&!y.some(s=>t(s)===r))return o},[i,E,y,d,e,t]),H=l.useMemo(()=>{if(!d)return e;const o=[...e,...y];h&&o.push(h);const r=new Set;return o.filter(s=>{const u=t(s);return r.has(u)?!1:(r.add(u),!0)})},[e,y,d,h,t]);return p.jsx(D,{name:c,control:F,options:H,...C,autocompleteProps:{isOptionEqualToValue:(o,r)=>t(o)===t(r),filterOptions:(o,{inputValue:r})=>o.filter(s=>t(s).toLowerCase().includes(r.toLowerCase())),freeSolo:d,autoComplete:!0,autoHighlight:!0,openOnFocus:!0,renderOption:(o,r,{selected:s})=>l.createElement("li",{...o,key:`${c}-option-${t(r)}`},C?.showCheckbox&&p.jsx(x.Checkbox,{sx:{marginRight:1},checked:s}),typeof r=="string"?r:a(r)),onChange:(o,r,s,u)=>{if(d&&h){if(i==null)throw new Error("Must implement stringToNewItem with freeSolo!");k(A=>[...A,h]),b("")}n?.onChange?.(o,r,s,u)},onInputChange:(o,r,s)=>{b(r),n?.onInputChange?.(o,r,s)},renderValue:(o,r,s)=>{const u=J(o,s);if(Array.isArray(u))return u.map((m,V)=>{const{key:T,...w}=r({index:V}),z=typeof m=="string"?m:a(m),B=typeof m!="string"&&f?f({value:m,index:V}):{};return p.jsx(x.Chip,{label:z,...B,...w},`${c}-chip-${T}`)});const{key:A,...R}=r({index:0}),O=g(R,"onDelete");return p.jsx(x.Typography,{component:"span",color:"text.primary",...C?.viewOnly?g(O,"disabled"):O,children:typeof u=="string"?u:a(u)},`${c}-value-${A}`)},...n}})},W=({name:e,rules:n,formControlProps:t={}})=>{const{register:a,formState:{errors:f}}=G.useFormContext(),{error:i,helperText:c}=P(f,e);return p.jsxs(x.FormControl,{error:i,...t,children:[p.jsx("input",{type:"hidden",...a(e,n)}),i&&p.jsx(x.FormHelperText,{children:c})]})};exports.AutocompleteElementDisplay=D;exports.ObjectElementDisplay=Q;exports.ValidationElement=W;exports.useFormError=P;exports.useOnMount=S;
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"),V=require("lodash"),M=require("@mui/material"),B=require("react-hook-form"),{merge:R}=V;function L(e,n){return n?.multiple,n?.freeSolo,e}const $=(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 z(e){return e!=null&&typeof e=="string"}const{merge:G}=V,P=({viewOnly:e=void 0,disableUnderline:n,textFieldProps:r,autocompleteProps:i,...E})=>{const f=h.useMemo(()=>G({readOnly:e,disableClearable:i?.disableClearable||e,disabled:e},i,e?{sx:{".MuiAutocomplete-tag":{opacity:"1 !important"}}}:{}),[i,e]),A=h.useMemo(()=>$(r,e,n),[r,e,n]);return y.jsx(T.AutocompleteElement,{autocompleteProps:f,textFieldProps:A,...E})},J=({options:e,autocompleteProps:n,getItemKey:r,getItemLabel:i,getChipProps:E,stringToNewItem:f,transformValue:A,name:C,freeSolo:F,control:q,getOptionProps:_,...O})=>{const{field:c}=T.useController({name:C,control:q}),[b,k]=h.useState(()=>c.value?(Array.isArray(c.value)?c.value:[c.value]).filter(t=>typeof t!="string"&&!e.some(l=>r(l)===r(t))):[]);h.useEffect(()=>{if(!c.value)return;const t=(Array.isArray(c.value)?c.value:[c.value]).filter(l=>typeof l!="string"&&![...e,...b].some(o=>r(o)===r(l)));t.length>0&&k(l=>[...l,...t])},[c.value,e,b,r]);const H=h.useMemo(()=>[...e,...b],[e,b]);return y.jsx(P,{name:C,control:q,options:H,...O,autocompleteProps:{isOptionEqualToValue:(a,t)=>r(a)===r(t),filterOptions:(a,{inputValue:t})=>{if(!t)return a;const l=t.toLowerCase(),o=a.filter(u=>{const d=r(u).toLowerCase(),j=String(i(u)).toLowerCase();return d.includes(l)||j.includes(l)});return F&&f&&t.length>0&&!o.some(d=>String(i(d)).toLowerCase()===l)?[{__isAddOption:!0,inputValue:t,...f(t)},...o]:o},freeSolo:F,autoComplete:!0,autoHighlight:!0,openOnFocus:!0,renderOption:(a,t,{selected:l},o)=>{const u=V.merge(a,_?.(t)??{});if(o?.freeSolo&&typeof t=="object"&&t!==null&&"__isAddOption"in t){const d=t.inputValue;return h.createElement(M.ListItem,{...u,key:`${C}-add-option-${d}`},"Add: '",d,"'")}return h.createElement(M.ListItem,{...u,key:`${C}-option-${r(t)}`},O?.showCheckbox&&o?.multiple&&y.jsx(M.Checkbox,{sx:{marginRight:1},checked:l}),typeof t=="string"?t:i(t))},onChange:(a,t,l,o)=>{const u=s=>A&&s!==null?c.onChange(A(s)):c.onChange(s),d=s=>{const p=r(s);[...e,...b].some(x=>r(x)===p)||k(x=>[...x,s])},j=s=>typeof s=="string"&&s.length>0?s:typeof s=="object"&&s!==null&&"__isAddOption"in s?s.inputValue:null;if(F&&f){const s=j(t);if(s){const p=f(s);if(O.multiple){const x=[...Array.isArray(c.value)?c.value:[],p];u(x)}else u(p);d(p);return}if(Array.isArray(t)&&O.multiple){const p=t?.map(m=>{const D=j(m);return D?f(D):m})??[];u(p);const g=[...e,...b].map(m=>r(m)),x=p.filter(m=>typeof m!="string"&&!g.includes(r(m)));x.length>0&&k(m=>[...m,...x]);return}}A&&t!==null?u(t):n?.onChange?.(a,t,l,o)},renderValue:(a,t,l)=>{const o=L(a,l);return Array.isArray(o)?o.map((u,d)=>{const{key:j,...s}=t({index:d}),p=typeof u=="string"?u:i(u),g=typeof u!="string"&&E?E({value:u,index:d}):{};return y.jsx(M.Chip,{...g,...s,label:p},`${C}-chip-${j}`)}):z(o)?o:o?i(o):""},...n}})},N=({viewOnly:e,disableUnderline:n,...r})=>{const i=h.useMemo(()=>$(r,e,n),[r,e,n]);return y.jsx(T.TextFieldElement,{...i})},{get:Q}=V;function S(e,n){const r=Q(e,n);return{error:!!r,helperText:r?.message||""}}function W(e){const n=h.useRef(!1);h.useEffect(()=>(n.current||(e(),n.current=!0),()=>{n.current=!0}),[])}const X=({name:e,rules:n,formControlProps:r={}})=>{const{register:i,formState:{errors:E}}=B.useFormContext(),{error:f,helperText:A}=S(E,e);return y.jsxs(M.FormControl,{error:f,...r,children:[y.jsx("input",{type:"hidden",...i(e,n)}),f&&y.jsx(M.FormHelperText,{children:A})]})};exports.AutocompleteElementDisplay=P;exports.ObjectElementDisplay=J;exports.TextElementDisplay=N;exports.ValidationElement=X;exports.useFormError=S;exports.useOnMount=W;
2
2
  //# sourceMappingURL=index.cjs.map