diff --git a/app/javascript/components/ComboMultipleDropdownList.jsx b/app/javascript/components/ComboMultipleDropdownList.jsx index 1d6ebf01b..93f5d4d22 100644 --- a/app/javascript/components/ComboMultipleDropdownList.jsx +++ b/app/javascript/components/ComboMultipleDropdownList.jsx @@ -21,6 +21,8 @@ import { fire } from '@utils'; import { XIcon } from '@heroicons/react/outline'; import isHotkey from 'is-hotkey'; +import { useDeferredSubmit } from './shared/hooks'; + const Context = createContext(); function ComboMultipleDropdownList({ @@ -78,39 +80,12 @@ function ComboMultipleDropdownList({ () => document.querySelector(`input[data-uuid="${hiddenFieldId}"]`), [hiddenFieldId] ); + const awaitFormSubmit = useDeferredSubmit(hiddenField); const handleChange = (event) => { 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) => { setSelections((selections) => { selections = fn(selections); @@ -138,6 +113,7 @@ function ComboMultipleDropdownList({ saveSelection((selections) => [...selections, selectedValue]); } setTerm(''); + awaitFormSubmit.done(); }; const onRemove = (label) => { @@ -153,6 +129,34 @@ function ComboMultipleDropdownList({ 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 ( diff --git a/app/javascript/components/ComboSearch.jsx b/app/javascript/components/ComboSearch.jsx index 9564e0f17..843fe8f19 100644 --- a/app/javascript/components/ComboSearch.jsx +++ b/app/javascript/components/ComboSearch.jsx @@ -12,6 +12,8 @@ import { import '@reach/combobox/styles.css'; import { fire } from '@utils'; +import { useDeferredSubmit } from './shared/hooks'; + function defaultTransformResults(_, results) { return results; } @@ -70,6 +72,7 @@ function ComboSearch({ onChange(value, result); } }, []); + const awaitFormSubmit = useDeferredSubmit(hiddenValueField); const handleOnChange = useCallback( ({ target: { value } }) => { @@ -88,6 +91,8 @@ function ComboSearch({ const handleOnSelect = useCallback((value) => { setExternalValueAndId(value); setValue(value); + setSearchTerm(''); + awaitFormSubmit.done(); }, []); const { isSuccess, data } = useQuery([scope, debouncedSearchTerm], { @@ -97,12 +102,22 @@ function ComboSearch({ }); const results = isSuccess ? transformResults(debouncedSearchTerm, data) : []; + const onBlur = useCallback(() => { + if (!allowInputValues && isSuccess && results[0]) { + const [, value] = transformResult(results[0]); + awaitFormSubmit(() => { + handleOnSelect(value); + }); + } + }, [data]); + return ( diff --git a/app/javascript/components/shared/hooks.js b/app/javascript/components/shared/hooks.js new file mode 100644 index 000000000..0111550cd --- /dev/null +++ b/app/javascript/components/shared/hooks.js @@ -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; +}