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