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 { 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;
|
||||||
}
|
}
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
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
Reference in a new issue