UNPKG

@nish1896/rhf-mui-components

Version:

A suite of 25+ production-ready react-hook-form components built with material-ui. Fully typed, tree-shakable, and optimized for enterprise-grade forms.

291 lines (290 loc) 12 kB
"use client"; import { RHFMuiConfigContext } from "../../config/ConfigProvider.js"; import { keepLabelAboveFormField, mergeRefs } from "../../utils/control.js"; import { defaultSelectAllOptionLabel, selectAllOptionValue } from "../../common/constants.js"; import FormControl from "../../common/FormControl.js"; import FormHelperText from "../../common/FormHelperText.js"; import FormLabel from "../../common/FormLabel.js"; import FormLabelText from "../../common/FormLabelText.js"; import { isKeyValueOption } from "../../utils/object.js"; import { fieldNameToLabel } from "../../utils/text-transform.js"; import { useFieldIds } from "../../utils/useFieldIds.js"; import { forwardRef, useCallback, useContext, useMemo } from "react"; import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime"; import { Controller } from "react-hook-form"; import Box from "@mui/material/Box"; import Autocomplete from "@mui/material/Autocomplete"; import TextField from "@mui/material/TextField"; import CircularProgress from "@mui/material/CircularProgress"; import FormControlLabel from "@mui/material/FormControlLabel"; import MuiCheckbox from "@mui/material/Checkbox"; //#region src/mui/multi-autocomplete-object/index.tsx const RHFMultiAutocompleteObject = forwardRef(function RHFMultiAutocompleteObject({ fieldName, control, registerOptions, options, labelKey, valueKey, disableClearable, autoHighlight = true, selectAllText = defaultSelectAllOptionLabel, hideSelectAllOption, customOnChange, onValueChange, disabled: muiDisabled, label, showLabelAboveFormField, formLabelProps, hideLabel, checkboxProps, renderOptionLabel, formControlLabelProps, required, helperText, errorMessage, hideErrorMessage, formHelperTextProps, textFieldProps, slotProps, ChipProps, onBlur, loading, customIds, getOptionDisabled, limitTags = 2, getLimitTagsText, ...otherMultiAutocompleteObjectProps }, ref) { const { allLabelsAboveFields, defaultFormControlLabelSx } = useContext(RHFMuiConfigContext); const { fieldId, labelId, helperTextId, errorId } = useFieldIds(fieldName, customIds); const isLabelAboveFormField = keepLabelAboveFormField(showLabelAboveFormField, allLabelsAboveFields); const defaultFieldLabel = fieldNameToLabel(fieldName); const fieldLabel = label ?? defaultFieldLabel; const accessibleFieldLabel = typeof fieldLabel === "string" ? fieldLabel : defaultFieldLabel; const shouldHideSelectAllOptions = hideSelectAllOption || options.length === 0 || options.length === 1; const { sx, ...otherFormControlLabelProps } = formControlLabelProps ?? {}; const appliedFormControlLabelSx = { ...defaultFormControlLabelSx, ...sx }; const autoCompleteOptions = useMemo(() => { if (shouldHideSelectAllOptions) return options; return [selectAllText, ...options]; }, [ options, selectAllText, shouldHideSelectAllOptions ]); const isSelectAllOption = useCallback((option) => option === selectAllText, [selectAllText]); const getOptionLabelOrValue = useCallback((option, key) => { if (typeof option === "string") return option; return key && isKeyValueOption(option, labelKey, valueKey) ? String(option[key]) : String(option); }, [labelKey, valueKey]); const displayOptionLabel = useCallback((option, getSelectAllValue) => isSelectAllOption(option) ? getSelectAllValue ? selectAllOptionValue : selectAllText : getOptionLabelOrValue(option, labelKey), [ isSelectAllOption, selectAllText, getOptionLabelOrValue, labelKey ]); const optionsEqual = useCallback((a, b) => { if (valueKey && isKeyValueOption(a, labelKey, valueKey) && isKeyValueOption(b, labelKey, valueKey)) return a[valueKey] === b[valueKey]; return a === b; }, [labelKey, valueKey]); return /* @__PURE__ */ jsx(Controller, { name: fieldName, control, rules: registerOptions, render: ({ field: { name: rhfFieldName, value: rhfValue, onChange: rhfOnChange, onBlur: rhfOnBlur, ref: rhfRef, disabled: rhfDisabled }, fieldState: { error: fieldStateError } }) => { const isDisabled = muiDisabled || rhfDisabled; const fieldErrorMessage = fieldStateError?.message?.toString() ?? errorMessage; const isError = !!fieldErrorMessage; const showHelperTextElement = !!(helperText || isError && !hideErrorMessage); const selectedOptions = rhfValue ?? []; const selectionContainsOption = (selected, opt) => selected.some((sel) => optionsEqual(sel, opt)); const areAllSelected = options.length > 0 && selectedOptions.length === options.length && options.every((opt) => selectionContainsOption(selectedOptions, opt)); const isIndeterminate = selectedOptions.length > 0 && !areAllSelected; const changeFieldState = (newValues, selectedOption) => { rhfOnChange(newValues); onValueChange?.({ newValue: newValues, selectedOption }); }; const handleCheckboxChange = (currentValue, rowOption, checked) => { if (isSelectAllOption(rowOption)) return checked ? [...options] : []; return checked ? selectionContainsOption(currentValue, rowOption) ? currentValue : [...currentValue, rowOption] : currentValue.filter((val) => !optionsEqual(val, rowOption)); }; return /* @__PURE__ */ jsxs(FormControl, { error: isError, disabled: isDisabled, children: [ !hideLabel && /* @__PURE__ */ jsx(FormLabel, { label: fieldLabel, isVisible: isLabelAboveFormField, required, error: isError, disabled: isDisabled, formLabelProps: { ...formLabelProps, id: labelId, htmlFor: fieldId } }), /* @__PURE__ */ jsx(Autocomplete, { ...otherMultiAutocompleteObjectProps, id: fieldId, options: autoCompleteOptions, value: selectedOptions, loading, disabled: isDisabled, onChange: (_, newSelectedOptions, reason, details) => { if (reason === "clear") { if (customOnChange) { customOnChange({ rhfOnChange, newValue: [], selectedOption: void 0 }); return; } rhfOnChange([]); onValueChange?.({ newValue: [] }); return; } if (newSelectedOptions.some(isSelectAllOption)) { const finalValue = areAllSelected ? [] : [...options]; if (customOnChange) { customOnChange({ rhfOnChange, newValue: finalValue, selectedOption: selectAllOptionValue }); return; } rhfOnChange(finalValue); onValueChange?.({ newValue: finalValue, selectedOption: selectAllOptionValue }); return; } const clickedOption = details?.option; const finalValue = newSelectedOptions.filter((option) => !isSelectAllOption(option)); const selectedOption = clickedOption && !isSelectAllOption(clickedOption) ? clickedOption : void 0; if (customOnChange) { customOnChange({ rhfOnChange, newValue: finalValue, selectedOption }); return; } rhfOnChange(finalValue); onValueChange?.({ newValue: finalValue, selectedOption }); }, onBlur: (blurEvent) => { rhfOnBlur(); onBlur?.(blurEvent); }, getOptionLabel: (option) => displayOptionLabel(option, true), isOptionEqualToValue: (option, value) => { if (isSelectAllOption(option)) return false; if (valueKey && isKeyValueOption(option, labelKey, valueKey)) return option[valueKey] === value[valueKey]; return option === value; }, renderInput: (params) => { const { InputProps, inputProps, disabled: paramsDisabled, ...otherInputParams } = params ?? {}; const { autoComplete = "off", placeholder, ...otherTextFieldProps } = textFieldProps ?? {}; const textFieldInputProps = { ...inputProps, "aria-required": required, "aria-invalid": isError, "aria-labelledby": !hideLabel && isLabelAboveFormField ? labelId : void 0, "aria-label": hideLabel ? accessibleFieldLabel : void 0, "aria-describedby": showHelperTextElement ? isError ? errorId : helperTextId : void 0, autoComplete }; return /* @__PURE__ */ jsx(TextField, { name: rhfFieldName, inputRef: mergeRefs(rhfRef, ref), disabled: paramsDisabled, ...otherTextFieldProps, placeholder: selectedOptions.length > 0 ? void 0 : placeholder, ...otherInputParams, label: !isLabelAboveFormField ? /* @__PURE__ */ jsx(FormLabelText, { label: fieldLabel, required }) : void 0, error: isError, slotProps: { ...textFieldProps?.slotProps, input: { ...InputProps, ...textFieldProps?.slotProps?.input, endAdornment: /* @__PURE__ */ jsxs(Fragment$1, { children: [loading && /* @__PURE__ */ jsx(CircularProgress, { color: "inherit", size: 20 }), InputProps.endAdornment] }) }, htmlInput: textFieldInputProps } }); }, renderOption: ({ key, ...optionProps }, option, state) => { const optionLabel = displayOptionLabel(option); if (isSelectAllOption(option)) return /* @__PURE__ */ jsx(Box, { component: "li", ...optionProps, children: /* @__PURE__ */ jsx(FormControlLabel, { ...otherFormControlLabelProps, label: optionLabel, disabled: isDisabled, control: /* @__PURE__ */ jsx(MuiCheckbox, { ...checkboxProps, id: `${fieldName}_${selectAllOptionValue}`, name: `${fieldName}_${selectAllOptionValue}`, value: selectAllOptionValue, checked: areAllSelected, indeterminate: isIndeterminate, disabled: isDisabled }), sx: { ...appliedFormControlLabelSx, width: "100%" }, onClick: (e) => { e.preventDefault(); changeFieldState(handleCheckboxChange(selectedOptions, option, !areAllSelected), selectAllOptionValue); } }) }, key); const optionValue = getOptionLabelOrValue(option, valueKey); const isOptionDisabled = getOptionDisabled?.(option) || isDisabled; return /* @__PURE__ */ jsx(Box, { component: "li", ...optionProps, children: /* @__PURE__ */ jsx(FormControlLabel, { ...otherFormControlLabelProps, label: renderOptionLabel?.(option, state) ?? optionLabel, disabled: isOptionDisabled, control: /* @__PURE__ */ jsx(MuiCheckbox, { ...checkboxProps, id: `${fieldName}_${optionValue}`, name: `${fieldName}_${optionValue}`, value: optionValue, checked: selectionContainsOption(selectedOptions, option), disabled: isOptionDisabled }), sx: { ...appliedFormControlLabelSx, width: "100%" }, onClick: (e) => { e.preventDefault(); if (isOptionDisabled) return; changeFieldState(handleCheckboxChange(selectedOptions, option, !selectionContainsOption(selectedOptions, option)), option); } }) }, key); }, limitTags, getLimitTagsText: (more) => getLimitTagsText?.(more) ?? `+${more} More`, autoHighlight, disableCloseOnSelect: true, disableClearable, blurOnSelect: false, fullWidth: true, multiple: true, freeSolo: false, slotProps: { ...slotProps, chip: ChipProps, listbox: { ...slotProps?.listbox } } }), /* @__PURE__ */ jsx(FormHelperText, { error: isError, errorMessage: fieldErrorMessage, hideErrorMessage, helperText, showHelperTextElement, formHelperTextProps: { ...formHelperTextProps, id: isError ? errorId : helperTextId } }) ] }); } }); }); //#endregion export { RHFMultiAutocompleteObject as default };