UNPKG

@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
/* 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