Merge pull request #6266 from tchak/fix-autocomplete

Correction des bugs sur les select avec autocomplétion
This commit is contained in:
Paul Chavard 2021-06-15 10:37:32 +02:00 committed by GitHub
commit 352b92b35c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 99 additions and 41 deletions

View file

@ -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}>

View file

@ -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;
} }
@ -45,17 +47,23 @@ function ComboSearch({
const [debouncedSearchTerm] = useDebounce(searchTerm, 300); const [debouncedSearchTerm] = useDebounce(searchTerm, 300);
const [value, setValue] = useState(initialValue); const [value, setValue] = useState(initialValue);
const resultsMap = useRef({}); const resultsMap = useRef({});
const setExternalValue = useCallback((value) => { const setExternalValue = useCallback(
if (hiddenValueField) { (value) => {
hiddenValueField.setAttribute('value', value); if (hiddenValueField) {
fire(hiddenValueField, 'autosave:trigger'); hiddenValueField.setAttribute('value', value);
} fire(hiddenValueField, 'autosave:trigger');
}); }
const setExternalId = useCallback((key) => { },
if (hiddenIdField) { [hiddenValueField]
hiddenIdField.setAttribute('value', key); );
} const setExternalId = useCallback(
}); (key) => {
if (hiddenIdField) {
hiddenIdField.setAttribute('value', key);
}
},
[hiddenIdField]
);
const setExternalValueAndId = useCallback((value) => { const setExternalValueAndId = useCallback((value) => {
const [key, result] = resultsMap.current[value]; const [key, result] = resultsMap.current[value];
setExternalId(key); setExternalId(key);
@ -63,7 +71,8 @@ function ComboSearch({
if (onChange) { if (onChange) {
onChange(value, result); onChange(value, result);
} }
}); }, []);
const awaitFormSubmit = useDeferredSubmit(hiddenValueField);
const handleOnChange = useCallback( const handleOnChange = useCallback(
({ target: { value } }) => { ({ target: { value } }) => {
@ -82,7 +91,9 @@ 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], {
enabled: !!debouncedSearchTerm, enabled: !!debouncedSearchTerm,
@ -91,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}
/> />

View 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;
}