Merge pull request #6266 from tchak/fix-autocomplete
Correction des bugs sur les select avec autocomplétion
This commit is contained in:
commit
352b92b35c
3 changed files with 99 additions and 41 deletions
|
@ -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 (
|
||||
<Combobox openOnFocus={true} onSelect={onSelect} aria-label={label}>
|
||||
<ComboboxTokenLabel onRemove={onRemove}>
|
||||
|
|
|
@ -12,6 +12,8 @@ import {
|
|||
import '@reach/combobox/styles.css';
|
||||
import { fire } from '@utils';
|
||||
|
||||
import { useDeferredSubmit } from './shared/hooks';
|
||||
|
||||
function defaultTransformResults(_, results) {
|
||||
return results;
|
||||
}
|
||||
|
@ -45,17 +47,23 @@ function ComboSearch({
|
|||
const [debouncedSearchTerm] = useDebounce(searchTerm, 300);
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const resultsMap = useRef({});
|
||||
const setExternalValue = useCallback((value) => {
|
||||
if (hiddenValueField) {
|
||||
hiddenValueField.setAttribute('value', value);
|
||||
fire(hiddenValueField, 'autosave:trigger');
|
||||
}
|
||||
});
|
||||
const setExternalId = useCallback((key) => {
|
||||
if (hiddenIdField) {
|
||||
hiddenIdField.setAttribute('value', key);
|
||||
}
|
||||
});
|
||||
const setExternalValue = useCallback(
|
||||
(value) => {
|
||||
if (hiddenValueField) {
|
||||
hiddenValueField.setAttribute('value', value);
|
||||
fire(hiddenValueField, 'autosave:trigger');
|
||||
}
|
||||
},
|
||||
[hiddenValueField]
|
||||
);
|
||||
const setExternalId = useCallback(
|
||||
(key) => {
|
||||
if (hiddenIdField) {
|
||||
hiddenIdField.setAttribute('value', key);
|
||||
}
|
||||
},
|
||||
[hiddenIdField]
|
||||
);
|
||||
const setExternalValueAndId = useCallback((value) => {
|
||||
const [key, result] = resultsMap.current[value];
|
||||
setExternalId(key);
|
||||
|
@ -63,7 +71,8 @@ function ComboSearch({
|
|||
if (onChange) {
|
||||
onChange(value, result);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
const awaitFormSubmit = useDeferredSubmit(hiddenValueField);
|
||||
|
||||
const handleOnChange = useCallback(
|
||||
({ target: { value } }) => {
|
||||
|
@ -82,7 +91,9 @@ function ComboSearch({
|
|||
const handleOnSelect = useCallback((value) => {
|
||||
setExternalValueAndId(value);
|
||||
setValue(value);
|
||||
});
|
||||
setSearchTerm('');
|
||||
awaitFormSubmit.done();
|
||||
}, []);
|
||||
|
||||
const { isSuccess, data } = useQuery([scope, debouncedSearchTerm], {
|
||||
enabled: !!debouncedSearchTerm,
|
||||
|
@ -91,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 (
|
||||
<Combobox aria-label={label} onSelect={handleOnSelect}>
|
||||
<ComboboxInput
|
||||
className={className}
|
||||
placeholder={placeholder}
|
||||
onChange={handleOnChange}
|
||||
onBlur={onBlur}
|
||||
value={value}
|
||||
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…
Reference in a new issue