UNPKG

@etsoo/materialui

Version:

TypeScript Material-UI Implementation

194 lines (193 loc) 9.01 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import React from "react"; import { MUGlobal } from "./MUGlobal"; import { ListItemRightIcon } from "./ListItemRightIcon"; import { ArrayUtils, Utils } from "@etsoo/shared"; import { ReactUtils } from "@etsoo/react"; import Select from "@mui/material/Select"; import Stack from "@mui/material/Stack"; import FormControl from "@mui/material/FormControl"; import InputLabel from "@mui/material/InputLabel"; import OutlinedInput from "@mui/material/OutlinedInput"; import MenuItem from "@mui/material/MenuItem"; import Checkbox from "@mui/material/Checkbox"; import ListItemText from "@mui/material/ListItemText"; import IconButton from "@mui/material/IconButton"; import FormHelperText from "@mui/material/FormHelperText"; import RefreshIcon from "@mui/icons-material/Refresh"; /** * Extended select component * @param props Props * @returns Component */ export function SelectEx(props) { // Destruct const { defaultValue, idField = "id", error, helperText, inputReset, itemIconRenderer, itemStyle, label, labelField = "label", loadData, mRef, onItemChange, onItemClick, onLoadData, multiple = false, name, options, refresh, search = false, autoAddBlankItem = search, value, onChange, fullWidth, required, variant = "outlined", ...rest } = props; // Options state const [localOptions, setOptions] = React.useState([]); const isMounted = React.useRef(false); const doItemChange = (options, value, userAction) => { if (onItemChange == null) return; let option; if (multiple && Array.isArray(value)) { option = options.find((option) => value.includes(option[idField])); } else if (value == null || value === "") { option = undefined; } else { option = options.find((option) => option[idField] === value); } onItemChange(option, userAction); }; // Local value const v = defaultValue ?? value; const valueSource = React.useMemo(() => (multiple ? (v ? (Array.isArray(v) ? v : [v]) : []) : v ?? ""), [multiple, v]); const setOptionsAdd = React.useCallback((options) => { const localOptions = [...options]; if (autoAddBlankItem) { Utils.addBlankItem(localOptions, idField, labelField); } setOptions(localOptions); if (valueSource != null) doItemChange(options, valueSource, false); }, [valueSource]); // When options change // [options] will cause infinite loop const propertyWay = loadData == null; React.useEffect(() => { if (options == null || !propertyWay) return; setOptionsAdd(options); }, [options, propertyWay, setOptionsAdd]); // Value state const [valueState, setValueStateBase] = React.useState(valueSource); const valueRef = React.useRef(); const setValueState = (newValue) => { valueRef.current = newValue; setValueStateBase(newValue); }; const [open, setOpen] = React.useState(false); React.useImperativeHandle(mRef, () => ({ setOpen: (isOpen) => { setOpen(isOpen); } }), []); React.useEffect(() => { if (valueSource != null) setValueState(valueSource); }, [valueSource]); // Label id const labelId = `selectex-label-${name}`; // Set item const setItemValue = (id) => { if (id !== valueRef.current) { // Difference const diff = multiple ? ArrayUtils.differences(id, valueRef.current) : id; setValueState(id); const input = divRef.current?.querySelector("input"); if (input) { // Different value, trigger change event ReactUtils.triggerChange(input, id, false); } return diff; } return undefined; }; // Get option id const getId = (option) => { return option[idField]; }; // Get option label const getLabel = (option) => { return typeof labelField === "function" ? labelField(option) : option[labelField]; }; // Refs const divRef = React.useRef(); // Refresh list data const refreshData = () => { if (loadData == null) return; loadData().then((result) => { if (result == null || !isMounted.current) return; if (onLoadData) onLoadData(result); setOptionsAdd(result); }); }; // When value change React.useEffect(() => { refreshData(); }, [valueSource]); // When layout ready React.useEffect(() => { const input = divRef.current?.querySelector("input"); const inputChange = (event) => { // Reset case if (event.cancelable) setValueState(multiple ? [] : ""); }; input?.addEventListener("change", inputChange); isMounted.current = true; return () => { isMounted.current = false; input?.removeEventListener("change", inputChange); }; }, [multiple]); // Layout return (_jsxs(Stack, { direction: "row", children: [_jsxs(FormControl, { size: search ? MUGlobal.searchFieldSize : MUGlobal.inputFieldSize, fullWidth: fullWidth, error: error, children: [_jsx(InputLabel, { id: labelId, variant: variant, shrink: search ? MUGlobal.searchFieldShrink : MUGlobal.inputFieldShrink, required: required, children: label }), _jsx(Select, { ref: divRef, value: multiple ? valueState : localOptions.some((o) => o[idField] === valueState) ? valueState : "", input: _jsx(OutlinedInput, { notched: true, label: label, required: required, inputProps: { "aria-hidden": false, "data-reset": inputReset } }), open: open, onClose: () => setOpen(false), onOpen: () => setOpen(true), labelId: labelId, name: name, multiple: multiple, onChange: (event, child) => { if (onChange) { onChange(event, child); // event.preventDefault() will block executing if (event.defaultPrevented) return; } // Set item value const value = event.target.value; if (multiple && !Array.isArray(value)) return; const diff = setItemValue(value); if (diff != null) { doItemChange(localOptions, diff, true); } }, renderValue: (selected) => { // The text shows up return localOptions .filter((option) => { const id = getId(option); return Array.isArray(selected) ? selected.indexOf(id) !== -1 : selected === id; }) .map((option) => getLabel(option)) .join(", "); }, sx: { minWidth: "150px" }, fullWidth: fullWidth, required: required, variant: variant, ...rest, children: localOptions.map((option) => { // Option id const id = getId(option); // Option label const label = getLabel(option); // Option return (_jsxs(MenuItem, { value: id, onClick: (event) => { if (onItemClick) { onItemClick(event, option); } }, style: itemStyle == null ? undefined : itemStyle(option), children: [multiple && (_jsx(Checkbox, { checked: Array.isArray(valueState) ? valueState.includes(id) : valueState === id })), _jsx(ListItemText, { primary: label }), itemIconRenderer && (_jsx(ListItemRightIcon, { children: itemIconRenderer(option[idField]) }))] }, id)); }) }), helperText != null && _jsx(FormHelperText, { children: helperText })] }), refresh != null && loadData != null && (typeof refresh === "string" ? (_jsx(IconButton, { size: "small", title: refresh, onClick: refreshData, children: _jsx(RefreshIcon, {}) })) : (refresh))] })); }