UNPKG

@ozen-ui/kit

Version:

React component library

215 lines (214 loc) 14.5 kB
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';