@etsoo/materialui
Version:
TypeScript Material-UI Implementation
194 lines (193 loc) • 9.01 kB
JavaScript
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))] }));
}