Fix autocomplete on blur
This commit is contained in:
parent
7f4e174871
commit
7575f50d25
3 changed files with 80 additions and 28 deletions
|
@ -21,6 +21,8 @@ import { fire } from '@utils';
|
||||||
import { XIcon } from '@heroicons/react/outline';
|
import { XIcon } from '@heroicons/react/outline';
|
||||||
import isHotkey from 'is-hotkey';
|
import isHotkey from 'is-hotkey';
|
||||||
|
|
||||||
|
import { useDeferredSubmit } from './shared/hooks';
|
||||||
|
|
||||||
const Context = createContext();
|
const Context = createContext();
|
||||||
|
|
||||||
function ComboMultipleDropdownList({
|
function ComboMultipleDropdownList({
|
||||||
|
@ -78,39 +80,12 @@ function ComboMultipleDropdownList({
|
||||||
() => document.querySelector(`input[data-uuid="${hiddenFieldId}"]`),
|
() => document.querySelector(`input[data-uuid="${hiddenFieldId}"]`),
|
||||||
[hiddenFieldId]
|
[hiddenFieldId]
|
||||||
);
|
);
|
||||||
|
const awaitFormSubmit = useDeferredSubmit(hiddenField);
|
||||||
|
|
||||||
const handleChange = (event) => {
|
const handleChange = (event) => {
|
||||||
setTerm(event.target.value);
|
setTerm(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyDown = (event) => {
|
|
||||||
if (
|
|
||||||
isHotkey('enter', event) ||
|
|
||||||
isHotkey(' ', event) ||
|
|
||||||
isHotkey(',', event) ||
|
|
||||||
isHotkey(';', event)
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
term &&
|
|
||||||
[...extraOptions, ...options].map(([label]) => label).includes(term)
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
return onSelect(term);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onBlur = (event) => {
|
|
||||||
if (
|
|
||||||
acceptNewValues &&
|
|
||||||
term &&
|
|
||||||
[...extraOptions, ...options].map(([label]) => label).includes(term)
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
return onSelect(term);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveSelection = (fn) => {
|
const saveSelection = (fn) => {
|
||||||
setSelections((selections) => {
|
setSelections((selections) => {
|
||||||
selections = fn(selections);
|
selections = fn(selections);
|
||||||
|
@ -138,6 +113,7 @@ function ComboMultipleDropdownList({
|
||||||
saveSelection((selections) => [...selections, selectedValue]);
|
saveSelection((selections) => [...selections, selectedValue]);
|
||||||
}
|
}
|
||||||
setTerm('');
|
setTerm('');
|
||||||
|
awaitFormSubmit.done();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRemove = (label) => {
|
const onRemove = (label) => {
|
||||||
|
@ -153,6 +129,34 @@ function ComboMultipleDropdownList({
|
||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onKeyDown = (event) => {
|
||||||
|
if (
|
||||||
|
isHotkey('enter', event) ||
|
||||||
|
isHotkey(' ', event) ||
|
||||||
|
isHotkey(',', event) ||
|
||||||
|
isHotkey(';', event)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
term &&
|
||||||
|
[...extraOptions, ...options].map(([label]) => label).includes(term)
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
onSelect(term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBlur = () => {
|
||||||
|
if (
|
||||||
|
term &&
|
||||||
|
[...extraOptions, ...options].map(([label]) => label).includes(term)
|
||||||
|
) {
|
||||||
|
awaitFormSubmit(() => {
|
||||||
|
onSelect(term);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Combobox openOnFocus={true} onSelect={onSelect} aria-label={label}>
|
<Combobox openOnFocus={true} onSelect={onSelect} aria-label={label}>
|
||||||
<ComboboxTokenLabel onRemove={onRemove}>
|
<ComboboxTokenLabel onRemove={onRemove}>
|
||||||
|
|
|
@ -12,6 +12,8 @@ import {
|
||||||
import '@reach/combobox/styles.css';
|
import '@reach/combobox/styles.css';
|
||||||
import { fire } from '@utils';
|
import { fire } from '@utils';
|
||||||
|
|
||||||
|
import { useDeferredSubmit } from './shared/hooks';
|
||||||
|
|
||||||
function defaultTransformResults(_, results) {
|
function defaultTransformResults(_, results) {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
@ -70,6 +72,7 @@ function ComboSearch({
|
||||||
onChange(value, result);
|
onChange(value, result);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
const awaitFormSubmit = useDeferredSubmit(hiddenValueField);
|
||||||
|
|
||||||
const handleOnChange = useCallback(
|
const handleOnChange = useCallback(
|
||||||
({ target: { value } }) => {
|
({ target: { value } }) => {
|
||||||
|
@ -88,6 +91,8 @@ function ComboSearch({
|
||||||
const handleOnSelect = useCallback((value) => {
|
const handleOnSelect = useCallback((value) => {
|
||||||
setExternalValueAndId(value);
|
setExternalValueAndId(value);
|
||||||
setValue(value);
|
setValue(value);
|
||||||
|
setSearchTerm('');
|
||||||
|
awaitFormSubmit.done();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { isSuccess, data } = useQuery([scope, debouncedSearchTerm], {
|
const { isSuccess, data } = useQuery([scope, debouncedSearchTerm], {
|
||||||
|
@ -97,12 +102,22 @@ function ComboSearch({
|
||||||
});
|
});
|
||||||
const results = isSuccess ? transformResults(debouncedSearchTerm, data) : [];
|
const results = isSuccess ? transformResults(debouncedSearchTerm, data) : [];
|
||||||
|
|
||||||
|
const onBlur = useCallback(() => {
|
||||||
|
if (!allowInputValues && isSuccess && results[0]) {
|
||||||
|
const [, value] = transformResult(results[0]);
|
||||||
|
awaitFormSubmit(() => {
|
||||||
|
handleOnSelect(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Combobox aria-label={label} onSelect={handleOnSelect}>
|
<Combobox aria-label={label} onSelect={handleOnSelect}>
|
||||||
<ComboboxInput
|
<ComboboxInput
|
||||||
className={className}
|
className={className}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onChange={handleOnChange}
|
onChange={handleOnChange}
|
||||||
|
onBlur={onBlur}
|
||||||
value={value}
|
value={value}
|
||||||
required={required}
|
required={required}
|
||||||
/>
|
/>
|
||||||
|
|
33
app/javascript/components/shared/hooks.js
Normal file
33
app/javascript/components/shared/hooks.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { useRef, useCallback } from 'react';
|
||||||
|
|
||||||
|
export function useDeferredSubmit(input) {
|
||||||
|
const calledRef = useRef(false);
|
||||||
|
const awaitFormSubmit = useCallback(
|
||||||
|
(callback) => {
|
||||||
|
const form = input.form;
|
||||||
|
if (!form) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const interceptFormSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
runCallback();
|
||||||
|
form.submit();
|
||||||
|
};
|
||||||
|
calledRef.current = false;
|
||||||
|
form.addEventListener('submit', interceptFormSubmit);
|
||||||
|
const runCallback = () => {
|
||||||
|
form.removeEventListener('submit', interceptFormSubmit);
|
||||||
|
clearTimeout(timer);
|
||||||
|
if (!calledRef.current) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const timer = setTimeout(runCallback, 400);
|
||||||
|
},
|
||||||
|
[input]
|
||||||
|
);
|
||||||
|
awaitFormSubmit.done = () => {
|
||||||
|
calledRef.current = true;
|
||||||
|
};
|
||||||
|
return awaitFormSubmit;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue