import { useEffect, useId, useRef, useState } from 'react'; const SEARCH_DEBOUNCE_MS = 320; export function AsyncCombobox({ allowClear = true, ariaLabel = 'Поисковый список', emptyLabel = 'Ничего не найдено', load, onSelect, optionDescription = () => '', optionLabel = item => item.name, optionValue = item => item.id, placeholder = 'Начните вводить название', selectedLabel = '', selectedValue = '' }) { const rootRef = useRef(null); const inputRef = useRef(null); const debounceTimer = useRef(null); const requestNumber = useRef(0); const [open, setOpen] = useState(false); const [query, setQuery] = useState(''); const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const listId = useId(); useEffect(() => () => { window.clearTimeout(debounceTimer.current); requestNumber.current += 1; }, []); useEffect(() => { if (!open) return undefined; const handlePointerDown = event => { if (rootRef.current && !rootRef.current.contains(event.target)) { close(); } }; document.addEventListener('mousedown', handlePointerDown); return () => document.removeEventListener('mousedown', handlePointerDown); }); async function search(nextQuery) { const currentRequest = requestNumber.current + 1; requestNumber.current = currentRequest; setOpen(true); setLoading(true); setError(''); try { const result = await load(String(nextQuery || '').trim(), selectedValue || ''); if (requestNumber.current !== currentRequest) return; setItems(Array.isArray(result) ? result : []); setLoading(false); } catch { if (requestNumber.current !== currentRequest) return; setError('Не удалось выполнить поиск'); setLoading(false); } } function close() { window.clearTimeout(debounceTimer.current); requestNumber.current += 1; setOpen(false); setQuery(''); setItems([]); setError(''); setLoading(false); } function choose(item) { onSelect(item); close(); } function clear() { onSelect(null); setQuery(''); inputRef.current?.focus(); void search(''); } function handleFocus() { setQuery(selectedLabel); void search(selectedLabel); } function handleInput(event) { const value = event.target.value; setQuery(value); window.clearTimeout(debounceTimer.current); debounceTimer.current = window.setTimeout(() => { void search(value); }, SEARCH_DEBOUNCE_MS); } function handleInputKeydown(event) { if (event.key === 'Escape') { close(); } else if (event.key === 'ArrowDown') { event.preventDefault(); rootRef.current?.querySelector('[role="option"]')?.focus(); } } function handleMenuKeydown(event) { const options = Array.from(rootRef.current?.querySelectorAll('[role="option"]') || []); const current = options.indexOf(document.activeElement); if (event.key === 'Escape') { event.preventDefault(); close(); inputRef.current?.focus(); } else if (event.key === 'ArrowDown' && current >= 0) { event.preventDefault(); options[Math.min(current + 1, options.length - 1)]?.focus(); } else if (event.key === 'ArrowUp' && current >= 0) { event.preventDefault(); (current === 0 ? inputRef.current : options[current - 1])?.focus(); } } return (