@ozen-ui/kit
Version:
React component library
215 lines (214 loc) • 14.5 kB
JavaScript
import { __assign, __read, __rest, __spreadArray } from "tslib";
import './Autocomplete.css';
import React, { useEffect, forwardRef, useRef, useState } from 'react';
import { useControlled } from '../../hooks/useControlled';
import { useElementSize } from '../../hooks/useElementSize';
import { useMultiRef } from '../../hooks/useMultiRef';
import { useMutableRef } from '../../hooks/useMutableRef';
import { useThemeProps } from '../../hooks/useThemeProps';
import { cn } from '../../utils/classname';
import { isKeys } from '../../utils/isKeys';
import { AutocompleteDropdown, AutocompleteInput } from './components';
import { AUTOCOMPLETE_DEFAULT_SIZE, AUTOCOMPLETE_DEFAULT_DISABLED, AUTOCOMPLETE_DEFAULT_REQUIRED, AUTOCOMPLETE_DEFAULT_AUTOFOCUS, AUTOCOMPLETE_DEFAULT_FULLWIDTH, AUTOCOMPLETE_DEFAULT_ALLOW_CUSTOM_VALUE, AUTOCOMPLETE_DEFAULT_DISABLE_CLEAR_BUTTON, AUTOCOMPLETE_DEFAULT_DISABLE_SHOW_CHEVRON, AUTOCOMPLETE_DEFAULT_DISABLE_CLOSE_ON_SELECT, AUTOCOMPLETE_DEFAULT_DISABLE_SHOW_EMPTY_OPTIONS_LIST, AUTOCOMPLETE_DEFAULT_RENDER_RIGHT_POSITION, } from './constants';
import { withDefaultGetters, isMultipleParams, isNotMultipleParams, } from './helpers';
export var cnAutocomplete = cn('AutocompleteNext');
function AutocompleteRender(inProps, ref) {
var props = useThemeProps({
props: inProps,
name: 'AutocompleteNext',
});
var _a = withDefaultGetters(props), _b = _a.disabled, disabled = _b === void 0 ? AUTOCOMPLETE_DEFAULT_DISABLED : _b, _c = _a.required, required = _c === void 0 ? AUTOCOMPLETE_DEFAULT_REQUIRED : _c, _d = _a.autoFocus, autoFocus = _d === void 0 ? AUTOCOMPLETE_DEFAULT_AUTOFOCUS : _d, _e = _a.fullWidth, fullWidth = _e === void 0 ? AUTOCOMPLETE_DEFAULT_FULLWIDTH : _e, _f = _a.size, size = _f === void 0 ? AUTOCOMPLETE_DEFAULT_SIZE : _f, _g = _a.allowCustomValue, allowCustomValue = _g === void 0 ? AUTOCOMPLETE_DEFAULT_ALLOW_CUSTOM_VALUE : _g, _h = _a.disableShowChevron, disableShowChevron = _h === void 0 ? AUTOCOMPLETE_DEFAULT_DISABLE_SHOW_CHEVRON : _h, _j = _a.disableClearButton, disableClearButton = _j === void 0 ? AUTOCOMPLETE_DEFAULT_DISABLE_CLEAR_BUTTON : _j, _k = _a.disableCloseOnSelect, disableCloseOnSelect = _k === void 0 ? AUTOCOMPLETE_DEFAULT_DISABLE_CLOSE_ON_SELECT : _k, _l = _a.disableShowEmptyOptionsList, disableShowEmptyOptionsList = _l === void 0 ? AUTOCOMPLETE_DEFAULT_DISABLE_SHOW_EMPTY_OPTIONS_LIST : _l, _m = _a.renderRightPosition, renderRightPosition = _m === void 0 ? AUTOCOMPLETE_DEFAULT_RENDER_RIGHT_POSITION : _m, limitTags = _a.limitTags, renderInput = _a.renderInput, multiple = _a.multiple, searchFunctionProp = _a.searchFunction, inputValueProp = _a.inputValue, valueProp = _a.value, options = _a.options, defaultValue = _a.defaultValue, onInputChange = _a.onInputChange, getOptionKey = _a.getOptionKey, getOptionLabel = _a.getOptionLabel, getOptionDisabled = _a.getOptionDisabled, onCloseProp = _a.onClose, onOpenProp = _a.onOpen, openProp = _a.open, defaultOpen = _a.defaultOpen, loading = _a.loading, inputProps = _a.inputProps, bodyProps = _a.bodyProps, onKeyDown = _a.onKeyDown, noOptionsText = _a.noOptionsText, clearText = _a.clearText, openText = _a.openText, renderOption = _a.renderOption, loadingText = _a.loadingText, closeText = _a.closeText, listProps = _a.listProps, popoverProps = _a.popoverProps, onChangeProp = _a.onChange, other = __rest(_a, ["disabled", "required", "autoFocus", "fullWidth", "size", "allowCustomValue", "disableShowChevron", "disableClearButton", "disableCloseOnSelect", "disableShowEmptyOptionsList", "renderRightPosition", "limitTags", "renderInput", "multiple", "searchFunction", "inputValue", "value", "options", "defaultValue", "onInputChange", "getOptionKey", "getOptionLabel", "getOptionDisabled", "onClose", "onOpen", "open", "defaultOpen", "loading", "inputProps", "bodyProps", "onKeyDown", "noOptionsText", "clearText", "openText", "renderOption", "loadingText", "closeText", "listProps", "popoverProps", "onChange"]);
var _o = useElementSize(), inputRef = _o.ref, height = _o.height;
var lastInputValue = useRef('');
var anchorRef = useRef(null);
var inputBodyRef = useMultiRef([anchorRef, bodyProps === null || bodyProps === void 0 ? void 0 : bodyProps.ref]);
var rootRef = useMultiRef([ref, inputRef]);
var _p = __read(useControlled({
value: inputValueProp,
defaultValue: '',
name: 'AutocompleteNext',
state: 'inputValue',
}), 2), inputValue = _p[0], setInputValue = _p[1];
var _q = __read(useControlled({
value: valueProp,
defaultValue: defaultValue,
name: 'AutocompleteNext',
state: 'value',
}), 2), valueState = _q[0], setValueState = _q[1];
var _r = __read(useControlled({
value: openProp,
defaultValue: defaultOpen,
name: 'AutocompleteNext',
state: 'open',
}), 2), open = _r[0], setOpen = _r[1];
var searchFunctionDefault = function (options, searchValue) {
return options === null || options === void 0 ? void 0 : options.filter(function (option) {
return getOptionLabel(option).toLowerCase().includes(searchValue.toLowerCase());
});
};
var savedOnInputChange = useMutableRef(onInputChange);
var savedGetOptionLabel = useMutableRef(getOptionLabel);
var _s = __read(useState(__spreadArray([], __read(options), false)), 2), filteredOptions = _s[0], setFilteredOptions = _s[1];
var searchFunction = searchFunctionProp || searchFunctionDefault;
var hasOptions = !!(filteredOptions === null || filteredOptions === void 0 ? void 0 : filteredOptions.length);
var _t = __read(useState(undefined), 2), search = _t[0], setSearch = _t[1];
var changeInputValue = function (e, value) {
var _a;
setInputValue(value);
(_a = savedOnInputChange.current) === null || _a === void 0 ? void 0 : _a.call(savedOnInputChange, e, value);
};
var callOnChange = function (e, option) {
var _a, _b, _c;
var inputValue = '';
var onChange = function (e, option) {
setValueState(option);
onChangeProp === null || onChangeProp === void 0 ? void 0 : onChangeProp(e, option);
};
var params = __assign(__assign({}, inProps), { onChange: onChange, value: valueState });
if (isNotMultipleParams(params)) {
inputValue = option ? getOptionLabel(option) : '';
(_a = params.onChange) === null || _a === void 0 ? void 0 : _a.call(params, e, option);
}
if (isMultipleParams(params)) {
if (option === null) {
(_b = params.onChange) === null || _b === void 0 ? void 0 : _b.call(params, e, null);
}
else {
var value = params.value || [];
var optionKey_1 = option && getOptionKey(option);
var optionIsSelected = value.find(function (item) { return getOptionKey(item) === optionKey_1; });
// eslint-disable-next-line no-nested-ternary
var res = optionIsSelected
? value.filter(function (option) { return getOptionKey(option) !== optionKey_1; })
: option
? __spreadArray(__spreadArray([], __read(value), false), [option], false) : value;
(_c = params.onChange) === null || _c === void 0 ? void 0 : _c.call(params, e, res);
}
setSearch(undefined);
}
changeInputValue(null, inputValue);
};
/** Эффект — фильтрация списка при изменении опций */
useEffect(function () {
if (!open)
return;
setFilteredOptions(search ? search(options) : __spreadArray([], __read(options), false));
}, [search, options, open, searchFunctionProp]);
/** Эффект — синхронизируем значение текстового поля со значением в списке */
useEffect(function () {
var _a;
if (allowCustomValue || multiple)
return;
var value = valueState || '';
var params = __assign(__assign({}, inProps), { value: value });
if (isNotMultipleParams(params)) {
lastInputValue.current = (_a = savedGetOptionLabel.current) === null || _a === void 0 ? void 0 : _a.call(savedGetOptionLabel, params.value);
if (lastInputValue.current !== inputValue)
changeInputValue(null, lastInputValue.current);
}
}, [valueState, allowCustomValue]);
/** Эффект — автофокусировка в текстовом поле */
useEffect(function () {
var _a;
if (autoFocus)
(_a = anchorRef.current) === null || _a === void 0 ? void 0 : _a.focus();
}, [autoFocus]);
/** Ждём выполнения анимации на закрытие и делаем сброс функции фильтрации */
var handleExited = function () {
var _a;
setSearch(undefined);
(_a = popoverProps === null || popoverProps === void 0 ? void 0 : popoverProps.onExited) === null || _a === void 0 ? void 0 : _a.call(popoverProps);
};
/** Закрытие */
var handleClose = function () {
setOpen(false);
onCloseProp === null || onCloseProp === void 0 ? void 0 : onCloseProp();
};
/** Открытие */
var handleOpen = function () {
setOpen(true);
onOpenProp === null || onOpenProp === void 0 ? void 0 : onOpenProp();
};
/** Переключатель открытия и закрытия */
var handleToggle = function () {
if (disabled)
return;
if (open)
handleClose();
else
handleOpen();
};
/** Очистка поля */
var handleClear = function (e) {
callOnChange(e, null);
setSearch(undefined);
};
/** Актуализация значения текстового поля после его покидания */
var handleBlur = function (e) {
var _a;
(_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onBlur) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
if (allowCustomValue)
return;
if (inputValue !== lastInputValue.current) {
changeInputValue(null, lastInputValue.current);
}
};
/** Открытие списка по клику на текстовом поле */
var handleClickOnInput = function (e) {
var _a;
handleToggle();
(_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onClick) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
};
/** Управление элементом контроля через клавиатуру */
var handleKeyDown = function (e) {
if (isKeys(e, ['ArrowDown', 'ArrowUp']) && !open) {
e.preventDefault();
handleToggle();
}
var params = __assign(__assign({}, inProps), { value: valueState });
if (isMultipleParams(params) && isKeys(e, ['Backspace']) && !inputValue) {
var value = params.value;
if (value)
callOnChange(e, value[value.length - 1] || null);
}
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(e);
};
/** Событие ввода значения в текстовом поле */
var handleChangeInput = function (e) {
var value = e.target.value;
// Открываем список при вводе первого символа (при условии, что список еще не открыт)
if (value.length && !open)
handleOpen();
if (multiple)
changeInputValue(e, value);
if (value && !multiple)
changeInputValue(e, value);
if (!value && !multiple)
callOnChange(e, null);
// Задаем функцию фильтрации
setSearch(function () { return function (options) { return searchFunction(__spreadArray([], __read(options), false), value); }; });
};
/** Событие выбора значения из раскрывающегося списка */
var handleChangeDropdown = function (e, option) {
callOnChange(e, option);
// Закрываем список после выбора
if (!disableCloseOnSelect)
handleClose();
};
var autocompleteInputProps = __assign({ open: open, size: size, limitTags: limitTags, disabled: disabled, multiple: multiple, required: required, fullWidth: fullWidth, clearText: clearText, closeText: closeText, openText: openText, inputProps: __assign(__assign({}, inputProps), { onBlur: handleBlur, onClick: handleClickOnInput }), bodyProps: __assign(__assign({}, bodyProps), { ref: inputBodyRef }), renderInput: renderInput, renderRightPosition: renderRightPosition, getOptionKey: getOptionKey, getOptionLabel: getOptionLabel, getOptionDisabled: getOptionDisabled, value: inputValue, disableShowChevron: disableShowChevron, disableClearButton: disableClearButton, onClear: handleClear, onOpen: handleToggle, onRemoveTag: callOnChange, onKeyDown: handleKeyDown, onChange: handleChangeInput, selectedOptions: valueState }, other);
var update = useRef();
var setUpdate = function (func) {
if (func) {
update.current = func;
}
};
useEffect(function () {
var _a;
(_a = update.current) === null || _a === void 0 ? void 0 : _a.call(update);
}, [height]);
return (React.createElement(React.Fragment, null,
React.createElement(AutocompleteInput, __assign({}, autocompleteInputProps, { ref: rootRef })),
React.createElement(AutocompleteDropdown, { open: open, size: size, loading: loading, setUpdate: setUpdate, value: valueState, multiple: multiple, onClose: handleClose, listProps: listProps, anchorRef: anchorRef, hasOptions: hasOptions, options: filteredOptions, loadingText: loadingText, renderOption: renderOption, getOptionKey: getOptionKey, noOptionsText: noOptionsText, onChange: handleChangeDropdown, getOptionLabel: getOptionLabel, getOptionDisabled: getOptionDisabled, disableShowEmptyOptionsList: disableShowEmptyOptionsList, popoverProps: __assign(__assign({}, popoverProps), { onExited: handleExited }) })));
}
export var Autocomplete = forwardRef(AutocompleteRender);
Autocomplete.displayName = 'Autocomplete';