@totalsoft/rocket-ui
Version:
A set of reusable and composable React components built on top of Material UI core for developing fast and friendly web applications interfaces.
372 lines • 16.9 kB
JavaScript
/* eslint-disable */
// @ts-nocheck
import React, { useCallback, useRef, useEffect, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { DeprecatedAutocomplete as MuiDeprecatedAutocomplete, NoOptionsText, classes } from './DeprecatedAutocompleteStyles';
import Option from './Option';
import LinearProgress from '@mui/material/LinearProgress';
import Chip from '@mui/material/Chip';
import { is, isNil, equals, isEmpty, any, prop } from 'ramda';
import { filterOptions, getSimpleValue, findFirstNotNil, isStringOrNumber, computeChangedMultiValue, computeChangedSingleValue, stopPropagation } from './utils';
import TextField from '../TextField';
import { throttle } from 'lodash';
/**
* @deprecated Use the Autocomplete component instead.
*/
const DeprecatedAutocomplete = ({ options: receivedOptions, defaultOptions = [], loadOptions, loading: receivedLoading, loadingText, noOptionsText = 'No options', getOptionLabel, onChange, onInputChange, creatable = false, onOpen, onClose, value = null, isMultiSelection = false, withCheckboxes = false, isClearable = false, disabled = false, simpleValue = false, label, valueKey = 'id', labelKey = 'name', error = false, helperText, required = false, createdLabel = 'Add', typographyContentColor = 'textSecondary', inputSelectedColor, isSearchable = true, getOptionDisabled, placeholder, inputTextFieldProps, isPaginated, ListboxProps, stopEventPropagation = false, renderGroup, ...other }) => {
const [options, setOptions] = useState(receivedOptions ?? []);
const [asyncOptions, setAsyncOptions] = useState(is(Array, defaultOptions) ? defaultOptions : []);
const [additionalPageData, setAdditionalPageData] = useState(null);
const [hasMore, setHasMore] = useState(true);
const [localLoading, setLocalLoading] = useState(false);
const loading = receivedLoading || localLoading;
const [localInput, setLocalInput] = useState();
const disabledOptions = useMemo(() => (getOptionDisabled ? options.filter(getOptionDisabled) : []), [getOptionDisabled, options]);
const disabledValues = disabledOptions.map(prop(valueKey));
const isValueDisabled = getOptionDisabled ? any(equals(value), disabledValues) : false;
const handleLoadOptionsPaginated = useCallback((input) => {
if (hasMore) {
if (asyncOptions.length === 0)
setLocalLoading(true);
loadOptions(input, options, additionalPageData).then(({ loadedOptions, more, additional }) => {
if (input != localInput)
setAsyncOptions(loadedOptions || []);
else
setAsyncOptions(prevAsyncOptions => [...prevAsyncOptions, ...loadedOptions]);
setAdditionalPageData(additional);
if (hasMore !== more && input === localInput)
setHasMore(more);
setLocalLoading(false);
});
return;
}
setLocalLoading(false);
}, [additionalPageData, asyncOptions.length, hasMore, loadOptions, localInput, options]);
const handleLoadOptions = useCallback(async (input) => {
if (!loadOptions)
return;
if (!isPaginated) {
setLocalLoading(true);
const loadedOptions = await loadOptions(input);
setAsyncOptions(loadedOptions || []);
setLocalLoading(false);
}
else
handleLoadOptionsPaginated(input);
}, [handleLoadOptionsPaginated, isPaginated, loadOptions]);
const handleMenuOpen = useCallback(async (event) => {
if (onOpen)
onOpen(event);
await handleLoadOptions(localInput);
}, [handleLoadOptions, localInput, onOpen]);
const handleMenuClose = useCallback((event, reason) => {
setLocalInput('');
setAsyncOptions([]);
if (onClose)
onClose(event, reason);
}, [onClose]);
const renderInput = useCallback((params) => {
params.inputProps.className = `${params.inputProps.className} ${classes.input}`;
if (inputSelectedColor)
params.inputProps.style = { color: inputSelectedColor };
params.inputProps.readOnly = !isSearchable;
const stopPropagationProps = stopEventPropagation ? { onClick: stopPropagation, onFocus: stopPropagation } : {};
const textFieldProps = {
label,
error,
helperText,
required,
placeholder,
...stopPropagationProps,
...inputTextFieldProps
};
return (React.createElement(TextField, { fullWidth: true, ...params, startAdornment: params.InputProps.startAdornment, endAdornment: params.InputProps.endAdornment, ...textFieldProps, InputProps: { ...params.InputProps, margin: 'none', onClick: stopPropagation }, InputLabelProps: { ...params.InputLabelProps, margin: null } }));
}, [
inputSelectedColor,
isSearchable,
stopEventPropagation,
label,
error,
helperText,
required,
placeholder,
inputTextFieldProps
]);
const handleOptionLabel = useCallback((option) => {
if (getOptionLabel)
return getOptionLabel(option);
if (isStringOrNumber(option))
return option.toString();
const label = option?._primitiveValue ? option?._primitiveValue : findFirstNotNil([labelKey, valueKey], option);
return label?.toString() ?? '';
}, [getOptionLabel, labelKey, valueKey]);
const renderOption = useCallback((props, option, { selected }) => {
const optionLabel = handleOptionLabel(option);
return (React.createElement(Option, { createdLabel: option?._createdOption ? createdLabel : '', optionLabel: optionLabel, selected: selected, withCheckboxes: withCheckboxes, option: option, ...props }));
}, [handleOptionLabel, createdLabel, withCheckboxes]);
const renderTags = useCallback((value, getTagProps) => value.map((option, index) => (React.createElement(Chip, { key: index, label: handleOptionLabel(option), ...getTagProps({ index }), disabled: option?.isDisabled || disabled || isValueDisabled }))), [handleOptionLabel, disabled, isValueDisabled]);
const isOptionEqualToValue = useCallback((option, value) => !is(Object, option) && !is(Object, value)
? equals(option, value)
: simpleValue
? equals(option[valueKey], value) || equals(option?.[valueKey], value?.[valueKey])
: equals(option?.[valueKey], value?.[valueKey]), [simpleValue, valueKey]);
const handleChange = useCallback((event, inputValue, reason) => {
if (stopEventPropagation)
event.stopPropagation();
// when multi-value and clearable, doesn't clear disabled options that have already been selected
if (reason === 'clear' && getOptionDisabled && isMultiSelection) {
return onChange(computeChangedMultiValue(disabledOptions, simpleValue, valueKey, labelKey), event, reason);
}
setLocalInput(handleOptionLabel(inputValue));
// for multi-value DeprecatedAutocomplete, options dialog remains open after selection and we do not want to display a loading state
if (loadOptions && !isMultiSelection)
setLocalLoading(true);
if (isNil(inputValue) || isStringOrNumber(inputValue))
return onChange(inputValue, event, reason);
if (isMultiSelection) {
return onChange(computeChangedMultiValue(inputValue, simpleValue, valueKey, labelKey), event, reason);
}
return onChange(computeChangedSingleValue(inputValue, simpleValue, valueKey, labelKey), event, reason);
}, [
disabledOptions,
getOptionDisabled,
handleOptionLabel,
isMultiSelection,
labelKey,
loadOptions,
onChange,
simpleValue,
stopEventPropagation,
valueKey
]);
const handleInputChange = useCallback(async (event, value, reason) => {
setLocalInput(value ? value : '');
if (onInputChange)
onInputChange(event, value, reason);
// this prevents the component from calling loadOptions again when the user clicks outside it and the menu closes
if (event?.nativeEvent?.type === 'focusout') {
return;
}
await handleLoadOptions(value);
}, [handleLoadOptions, onInputChange]);
useEffect(() => {
// when simpleValue is false, loadOptions has already been called at this point by handleInputChange
if (!simpleValue || !loadOptions)
return;
if (is(Array, defaultOptions) && !isEmpty(defaultOptions))
return;
if (defaultOptions === false)
return;
const hasInitialValue = is(Array, value) ? !isEmpty(value) : value;
// when simpleValue is true, we need to previously load a set of options in order to match the value with one of them
if (defaultOptions === true || hasInitialValue) {
handleLoadOptions();
}
// this effect should run only at component mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
setOptions(receivedOptions || []);
}, [receivedOptions]);
const localNoOptionsText = useMemo(() => {
if (!noOptionsText || typeof noOptionsText !== 'function')
return noOptionsText;
return noOptionsText(localInput, localLoading);
}, [noOptionsText, localInput, localLoading]);
const listBoxProps = useMemo(() => isPaginated
? {
...ListboxProps,
onScroll: (event) => {
const listboxNode = event.currentTarget;
if (listboxNode.scrollTop + listboxNode.clientHeight >= listboxNode.scrollHeight - 1 && hasMore) {
handleLoadOptions(localInput);
}
}
}
: ListboxProps, [ListboxProps, handleLoadOptions, hasMore, isPaginated, localInput]);
const localValue = useMemo(() => {
return simpleValue ? getSimpleValue(loadOptions ? asyncOptions : options, value, valueKey, isMultiSelection) : value;
}, [simpleValue, loadOptions, asyncOptions, options, value, valueKey, isMultiSelection]);
const inputChangeRef = useRef(throttle(handleInputChange, 500));
const throttledOnInputChange = inputChangeRef.current;
useEffect(() => {
inputChangeRef.current = throttle(handleInputChange, 500);
}, [handleInputChange]);
return (React.createElement(MuiDeprecatedAutocomplete, { noOptionsText: React.createElement(NoOptionsText, { color: typographyContentColor }, localNoOptionsText), typographyContentColor: typographyContentColor, forcePopupIcon: true, label: label, disabled: disabled || isValueDisabled, loading: loading, loadingText: loadingText ?? React.createElement(LinearProgress, null), onOpen: handleMenuOpen, onClose: handleMenuClose, clearOnBlur: true, freeSolo: creatable, options: loading ? [] : loadOptions ? asyncOptions : options || [], autoHighlight: true, handleHomeEndKeys: true, selectOnFocus: true, disableCloseOnSelect: isMultiSelection, filterSelectedOptions: simpleValue && isMultiSelection && !withCheckboxes, filterOptions: filterOptions(labelKey, valueKey, creatable), getOptionLabel: handleOptionLabel, isOptionEqualToValue: isOptionEqualToValue, getOptionDisabled: getOptionDisabled, value: localValue, multiple: isMultiSelection, onChange: handleChange, onInputChange: throttledOnInputChange, disableClearable: !isClearable, renderInput: renderInput, renderTags: renderTags, ListboxProps: listBoxProps, ...other, ...(renderGroup ? { renderGroup } : { renderOption }) }));
};
DeprecatedAutocomplete.propTypes = {
/**
* @default []
* The array of options from which the client can select a value.
*/
options: PropTypes.array,
/**
* Function that returns a promise, which resolves to the set of options to be used once the promise resolves.
*/
loadOptions: PropTypes.func,
/**
* If true, the component is in a loading state.
* By default, this shows a linear progress instead of options.
* This can be changed by sending the loadingText prop to DeprecatedAutocomplete.
*/
loading: PropTypes.bool,
/**
* @default '<LinearProgress />'
* Text/component to display when in a loading state.
*/
loadingText: PropTypes.node,
/**
* @default 'No options'
* Text to display when there are no options.
*/
noOptionsText: PropTypes.node,
/**
* Used to determine the string value for a given option.
*/
getOptionLabel: PropTypes.func,
/**
* @default null
* The selected value from list of options.
*/
value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.number, PropTypes.string, PropTypes.bool]),
/**
* Handle change events on the autocomplete.
*/
onChange: PropTypes.func.isRequired,
/**
* Callback fired when the input value changes.
*/
onInputChange: PropTypes.func,
/**
* Handle the menu opening.
*/
onOpen: PropTypes.func,
/**
* Handle the menu closing.
*/
onClose: PropTypes.func,
/**
* @default false
* If true, the user can select multiple values from list.
*/
isMultiSelection: PropTypes.bool,
/**
* @default false
* If true, the options list will have checkboxes.
*/
withCheckboxes: PropTypes.bool,
/**
* @default false
* If true, the user can clear the selected value.
*/
isClearable: PropTypes.bool,
/**
* @default true
* If false, the user cannot type in DeprecatedAutocomplete, filter options or create new ones.
*/
isSearchable: PropTypes.bool,
/**
* @default false
* If true, the DeprecatedAutocomplete is free solo, meaning that the user input is not bound to provided options and can add
* his own values.
*/
creatable: PropTypes.bool,
/**
* @default false
* If true, the DeprecatedAutocomplete is disabled.
*/
disabled: PropTypes.bool,
/**
* Used to determine the disabled state for a given option.
*/
getOptionDisabled: PropTypes.func,
/**
* @default false
* If true, options will be an array of simple values, instead of objects.
*/
simpleValue: PropTypes.bool,
/**
* Label to be displayed in the heading component.
*/
label: PropTypes.string,
/**
* @default 'id'
* The key of values from options.
*/
valueKey: PropTypes.string,
/**
* @default 'name'
* The key of the displayed label for each option.
*/
labelKey: PropTypes.string,
/**
* The content of the helper under the input.
*/
helperText: PropTypes.node,
/**
* @default false
* If true, the helper text is displayed when an error pops up.
*/
error: PropTypes.bool,
/**
* Text to be displayed as a placeholder in the text field.
*/
placeholder: PropTypes.string,
/**
* @default false
* Marks the input field as required (with an *).
*/
required: PropTypes.bool,
/**
* The value of label when a new option is added.
*/
createdLabel: PropTypes.string,
/**
* @default []
* The default set of options to show before the user starts searching. When set to true, the results for loadOptions('') will be autoloaded.
*/
defaultOptions: PropTypes.oneOfType([PropTypes.bool, PropTypes.array]),
/**
* @default 'textSecondary'
* The color of both the text displayed when there are no options and placeholder. It supports those theme colors that make sense for this component.
*/
typographyContentColor: PropTypes.oneOf([
'initial',
'inherit',
'primary',
'secondary',
'textPrimary',
'textSecondary',
'error'
]),
/**
* The color of selected input.
*/
inputSelectedColor: PropTypes.oneOfType([
PropTypes.string,
PropTypes.oneOf(['primary', 'secondary', 'error', 'info', 'success', 'warning'])
]),
/**
* Properties that will be passed to the rendered input. This is a TextField.
*/
inputTextFieldProps: PropTypes.object,
/**
* @default false
* If true, the options list will be loaded incrementally using the paginated loadOptions callback
*/
isPaginated: PropTypes.bool,
/**
* @default false
* Stops click and change event propagation.
*/
stopEventPropagation: PropTypes.bool,
/**
* Render the group.
*
* @param {DeprecatedAutocompleteRenderGroupParams} params The group to render.
* @returns {ReactNode}
*/
renderGroup: PropTypes.func
};
export default DeprecatedAutocomplete;
//# sourceMappingURL=DeprecatedAutocomplete.js.map