This commit is contained in:
Zuev
2026-10-02 04:07:56 +03:00
parent 97bf3cc50a
commit fc5801d683
42 changed files with 1201 additions and 305 deletions

View File

@@ -19,6 +19,7 @@ export function AsyncCombobox({
const inputRef = useRef(null);
const debounceTimer = useRef(null);
const requestNumber = useRef(0);
const restoringFocus = useRef(false);
const [open, setOpen] = useState(false);
const [query, setQuery] = useState('');
const [items, setItems] = useState([]);
@@ -38,8 +39,8 @@ export function AsyncCombobox({
close();
}
};
document.addEventListener('mousedown', handlePointerDown);
return () => document.removeEventListener('mousedown', handlePointerDown);
document.addEventListener('pointerdown', handlePointerDown);
return () => document.removeEventListener('pointerdown', handlePointerDown);
});
async function search(nextQuery) {
@@ -73,16 +74,24 @@ export function AsyncCombobox({
function choose(item) {
onSelect(item);
close();
restoreInputFocus();
}
function restoreInputFocus() {
restoringFocus.current = true;
inputRef.current?.focus();
restoringFocus.current = false;
}
function clear() {
onSelect(null);
setQuery('');
inputRef.current?.focus();
restoreInputFocus();
void search('');
}
function handleFocus() {
if (restoringFocus.current || open) return;
setQuery(selectedLabel);
void search(selectedLabel);
}
@@ -90,6 +99,12 @@ export function AsyncCombobox({
function handleInput(event) {
const value = event.target.value;
setQuery(value);
// Старый ответ не должен стать доступен во время задержки нового поиска.
requestNumber.current += 1;
setOpen(true);
setItems([]);
setLoading(true);
setError('');
window.clearTimeout(debounceTimer.current);
debounceTimer.current = window.setTimeout(() => {
void search(value);
@@ -98,6 +113,7 @@ export function AsyncCombobox({
function handleInputKeydown(event) {
if (event.key === 'Escape') {
event.preventDefault();
close();
} else if (event.key === 'ArrowDown') {
event.preventDefault();
@@ -111,7 +127,7 @@ export function AsyncCombobox({
if (event.key === 'Escape') {
event.preventDefault();
close();
inputRef.current?.focus();
restoreInputFocus();
} else if (event.key === 'ArrowDown' && current >= 0) {
event.preventDefault();
options[Math.min(current + 1, options.length - 1)]?.focus();
@@ -122,7 +138,13 @@ export function AsyncCombobox({
}
return (
<div className={`async-combobox${open ? ' is-open' : ''}`} ref={rootRef}>
<div
className={`async-combobox${open ? ' is-open' : ''}`}
ref={rootRef}
onBlur={event => {
if (!event.currentTarget.contains(event.relatedTarget)) close();
}}
>
<div className="async-combobox-control">
<input
type="search"