import { defaultFilterFn } from '../utils/default-filter-fn'; import React, { MouseEvent, useEffect, useMemo, useRef, useState } from 'react'; import { RtFormFieldModelChangeAndValues, RtFormFieldSelectProps, RtLabelledValue, RtOption, RtValue, } from '../rt-form-field-select.types'; import { RtFormFieldComboboxApi } from '@dpa/react-form-field-combobox'; import { useDebouncedCallback } from 'use-debounce'; import { RtText } from '@dpa/react-text'; import styles from '../styles/rt-form-field-select.module.css'; import { RtButton } from '@dpa/react-buttons'; export function useSelect(props: RtFormFieldSelectProps) { const { options, multiple, labelInValue, isClearable, disabled, value, sortFn, filterFn, listMaxItemsHeight, } = props; const inputRef = useRef(null); const apiRef = useRef(null); const selectedValuesList = useMemo(() => { if (!multiple) return []; return labelInValue ? (props.value as RtLabelledValue[]).map((v) => v.value) : (props.value as Exclude[]); }, [multiple, labelInValue, props.value]); const selectedOption = useMemo(() => { if (!multiple) { if (!labelInValue) return options.find((o) => o.value === props.value); return props.value as RtLabelledValue; } return null; }, [options, props.value, labelInValue, multiple]); const selectedLabel = useMemo(() => { if (multiple) return props.value instanceof Array && props.value.length > 0 ? `Выбрано ${props.value instanceof Array ? props.value.length : 0}` : ''; return selectedOption?.label || ''; }, [selectedOption, props.value, multiple]); const selectedValueId = useMemo(() => { if (multiple) return null; return selectedOption?.value || null; }, [selectedOption, multiple]); const selectedOptions = useMemo(() => { if (!multiple) return labelInValue ? [value as RtLabelledValue] : [(options.find((o) => o.value === value) as RtLabelledValue) || null]; if (labelInValue) return (value as RtLabelledValue[]) || []; return options.filter((o) => (value as RtValue[]).includes(o.value)); }, [value, multiple, labelInValue, options]); const [searchValue, setSearchValue] = useState(props.filterValue || ''); const onDebounceFilter = useDebouncedCallback<(value: string) => void>( (value) => { if (props.filterMode !== 'none') { setSearchValue(value); props.onFilter?.(value); } }, props.filterMode === 'search' ? 200 : 0 ); useEffect(() => { if ( typeof props.filterValue !== 'undefined' && props.filterValue !== searchValue ) { setSearchValue(props.filterValue || ''); } }, [props.filterValue, searchValue]); const filteredOptions = useMemo(() => { if (props.filterMode !== 'filter') return options; return (sortFn ? options.sort(sortFn) : options).filter((option) => filterFn ? filterFn(option, { searchValue }) : defaultFilterFn(option, { searchValue }) ); }, [sortFn, options, filterFn, searchValue, props.filterMode]); const virtualMaxItemsLength = filteredOptions.length > (listMaxItemsHeight as number) ? (listMaxItemsHeight as number) : filteredOptions.length; const handleSelect = ( eventValue: RtOption, event?: MouseEvent ) => { if (event) { if (multiple) inputRef.current?.focus(); else inputRef.current?.blur(); } setSearchValue(''); if (disabled || eventValue.disabled) return; const _value = labelInValue ? eventValue : eventValue.value; if (!multiple) { const isSameValue = selectedOption?.value === eventValue.value; if (isSameValue) props.onDeselect?.(eventValue); if (isClearable || !isSameValue) { apiRef.current?.closeDropdown(); (props.onChange as RtFormFieldModelChangeAndValues['onChange'])( isSameValue ? null : _value ); } } else { if (selectedValuesList.includes(eventValue.value)) { props.onDeselect?.(eventValue); const newValue = (props.value as RtValue[]).filter((val) => labelInValue ? (val as RtLabelledValue).value !== eventValue.value : val !== eventValue.value ); (props.onChange as RtFormFieldModelChangeAndValues['onChange'])( newValue ); } else if ( !props.maxCount || (props.value as RtValue[]).length < props.maxCount ) { props.onDeselect?.(eventValue); (props.onChange as RtFormFieldModelChangeAndValues['onChange'])([ ...(props.value as RtValue[]), _value, ]); } } }; useEffect(() => { if ( props.defaultActiveFirstOption && options.length > 0 && (multiple ? !value || (value as RtValue[]).length === 0 : !value) ) { handleSelect(options[0]); } }, [options]); function handleClear() { if (multiple) (props.onChange as RtFormFieldModelChangeAndValues['onChange'])([]); else (props.onChange as RtFormFieldModelChangeAndValues['onChange'])( null ); inputRef.current?.blur(); apiRef.current?.closeDropdown(); } const notFoundContent = props.notFoundContent || (
Ничего не найдено
); const optionRender: RtFormFieldSelectProps['optionRender'] = props.optionRender || ((option, { isActive }) => ( )); const dropdownRender: RtFormFieldSelectProps['dropdownRender'] = props.dropdownRender || ((menu) => menu); function dropdownScrollHandler(event: React.UIEvent) { props.onDropdownScroll?.(event); const scrollTop = event.currentTarget.scrollTop; const scrollHeight = event.currentTarget.scrollHeight; const clientHeight = event.currentTarget.clientHeight; if (scrollTop + clientHeight >= scrollHeight) { props.onDropdownScrollToBottomEnd?.(event); } } return { inputRef, apiRef, selectedValuesList, selectedValueId, selectedOption, selectedLabel, selectedOptions, filteredOptions, handleSelect, handleClear, onDebounceFilter, searchValue, notFoundContent, virtualMaxItemsLength, optionRender, dropdownRender, dropdownScrollHandler, }; }