UNPKG

@etsoo/materialui

Version:

TypeScript Material-UI Implementation

214 lines (213 loc) 9.88 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Tiplist = Tiplist; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = require("@etsoo/react"); const shared_1 = require("@etsoo/shared"); const react_2 = __importDefault(require("react")); const SearchField_1 = require("./SearchField"); const InputField_1 = require("./InputField"); const ReactApp_1 = require("./app/ReactApp"); const Autocomplete_1 = __importDefault(require("@mui/material/Autocomplete")); /** * Tiplist * @param props Props * @returns Component */ function Tiplist(props) { // Global app const app = (0, ReactApp_1.useAppContext)(); // Labels const { noOptions, loading, more1 = "More", open: openDefault } = app?.getLabels("noOptions", "loading", "more1", "open") ?? {}; // Destruct const { search = false, idField = "id", idValue, idIsString = false, inputAutoComplete = "off", inputError, inputHelperText, inputMargin, inputOnChange, inputRequired, inputReset, inputVariant, label, loadData, defaultValue, value, maxItems = 16, width = search ? 160 : undefined, name, readOnly, onChange, onValueChange, openOnFocus = true, noOptionsText = noOptions, loadingText = loading, openText = openDefault, getOptionLabel, getOptionDisabled, sx = {}, minChars, ...rest } = props; if (width && sx) Object.assign(sx, { width: `${width}px` }); // Value input ref const inputRef = react_2.default.createRef(); // Local value let localValue = value ?? defaultValue; // One time calculation for input's default value (uncontrolled) const localIdValue = idValue ?? shared_1.DataTypes.getValue(localValue, idField); // Changable states const [states, stateUpdate] = react_2.default.useReducer((currentState, newState) => { return { ...currentState, ...newState }; }, { // Loading unknown open: false, options: [], value: null }); // Input value const inputValue = react_2.default.useMemo(() => states.value && typeof states.value === "object" ? states.value[idField] : undefined, [states.value]); react_2.default.useEffect(() => { if (localValue != null) stateUpdate({ value: localValue }); }, [localValue]); // Ref const state = react_2.default.useRef({ idLoaded: false, idSet: false, isMounted: false }); // Add readOnly const addReadOnly = (params) => { if (readOnly != null) { Object.assign(params, { readOnly }); } // https://stackoverflow.com/questions/15738259/disabling-chrome-autofill // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html Object.assign(params.inputProps, { autoComplete: inputAutoComplete, "data-reset": inputReset }); return params; }; // Change handler const changeHandle = (event) => { // Stop processing with auto trigger event if (event.nativeEvent.cancelable && !event.nativeEvent.composed) { stateUpdate({ options: [] }); return; } // Stop bubble event.stopPropagation(); // Call with delay delayed.call(undefined, event.currentTarget.value); }; // Directly load data const loadDataDirect = (keyword, id) => { // Reset options // setOptions([]); if (id == null) { // Reset real value const input = inputRef.current; if (input && input.value !== "") { // Different value, trigger change event react_1.ReactUtils.triggerChange(input, "", false); } if (states.options.length > 0) { // Reset options stateUpdate({ options: [] }); } } // Loading indicator if (!states.loading) stateUpdate({ loading: true }); // Load list loadData(keyword, id, maxItems).then((options) => { if (!state.current.isMounted) return; if (options != null && options.length >= maxItems) { options.push({ [idField]: "n/a" }); } if (id && options && onValueChange) { const option = options.find((o) => o[idField] === id); if (option) onValueChange(option); } // Indicates loading completed stateUpdate({ loading: false, ...(options != null && { options }) }); }); }; const delayed = (0, react_1.useDelayedExecutor)(loadDataDirect, 480); const setInputValue = (value) => { stateUpdate({ value }); // Input value const input = inputRef.current; if (input) { // Update value const newValue = shared_1.DataTypes.getStringValue(value, idField) ?? ""; if (newValue !== input.value) { // Different value, trigger change event react_1.ReactUtils.triggerChange(input, newValue, false); } } }; react_2.default.useEffect(() => { if (localIdValue == null) { if (inputValue != null) setInputValue(null); return; } if (state.current.idLoaded) { state.current.idLoaded = false; state.current.idSet = false; } }, [localIdValue]); react_2.default.useEffect(() => { if (localIdValue != null && localIdValue !== "") { if (state.current.idLoaded) { // Set default if (!state.current.idSet && states.options.length > 0) { const item = states.options.find((o) => o[idField] === localIdValue); if (item) { stateUpdate({ value: states.options[0] }); state.current.idSet = true; } } } else { // Load id data loadDataDirect(undefined, localIdValue); state.current.idLoaded = true; } } }, [localIdValue, states.options]); react_2.default.useEffect(() => { state.current.isMounted = true; return () => { state.current.isMounted = false; delayed.clear(); }; }, []); // Layout return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("input", { ref: inputRef, "data-reset": inputReset ?? true, type: idIsString ? "text" : "number", style: { display: "none" }, name: name, value: `${inputValue ?? (state.current.idSet ? "" : localIdValue ?? "")}`, readOnly: true, onChange: inputOnChange }), (0, jsx_runtime_1.jsx)(Autocomplete_1.default, { filterOptions: (options, _state) => options, value: states.value, options: states.options, onChange: (event, value, reason, details) => { // Set value setInputValue(value); // Custom if (onChange != null) onChange(event, value, reason, details); if (onValueChange) onValueChange(value); // For clear case if (reason === "clear") { stateUpdate({ options: [], open: event.type === "click" }); loadDataDirect(); } }, open: states.open, openOnFocus: openOnFocus, onOpen: () => { // Should load const loading = states.loading ? true : states.options.length === 0; stateUpdate({ open: true, loading }); // If not loading if (loading) loadDataDirect(undefined, states.value == null ? undefined : states.value[idField]); }, onClose: () => { stateUpdate({ open: false, ...(!states.value && { options: [] }) }); }, loading: states.loading, renderInput: (params) => search ? ((0, jsx_runtime_1.jsx)(SearchField_1.SearchField, { onChange: changeHandle, ...addReadOnly(params), readOnly: readOnly, label: label, name: name + "Input", margin: inputMargin, minChars: minChars, variant: inputVariant, required: inputRequired, autoComplete: inputAutoComplete, error: inputError, helperText: inputHelperText })) : ((0, jsx_runtime_1.jsx)(InputField_1.InputField, { onChange: changeHandle, ...addReadOnly(params), label: label, name: name + "Input", margin: inputMargin, minChars: minChars, variant: inputVariant, required: inputRequired, autoComplete: inputAutoComplete, error: inputError, helperText: inputHelperText })), isOptionEqualToValue: (option, value) => option[idField] === value[idField], sx: sx, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, getOptionDisabled: (item) => { if (item[idField] === "n/a") return true; return getOptionDisabled ? getOptionDisabled(item) : false; }, getOptionLabel: (item) => { if (typeof item !== "object") return `${item}`; if (item[idField] === "n/a") return more1; return getOptionLabel ? getOptionLabel(item) : shared_1.DataTypes.getObjectItemLabel(item); }, ...rest })] })); }