diff --git a/app/javascript/components/ComboBox.tsx b/app/javascript/components/ComboBox.tsx index 63abf1a88..3bee420c9 100644 --- a/app/javascript/components/ComboBox.tsx +++ b/app/javascript/components/ComboBox.tsx @@ -22,6 +22,7 @@ import { useMultiList, useSingleList, useRemoteList, + useOnFormReset, createLoader, type ComboBoxProps } from './react-aria/hooks'; @@ -102,7 +103,7 @@ export function SingleComboBox({ const labelledby = useLabelledBy(props.id, ariaLabelledby); const { ref, dispatch } = useDispatchChangeEvent(); - const { selectedItem, ...comboBoxProps } = useSingleList({ + const { selectedItem, onReset, ...comboBoxProps } = useSingleList({ defaultItems, defaultSelectedKey, emptyFilterKey, @@ -122,6 +123,7 @@ export function SingleComboBox({ field={formValue == 'text' ? 'label' : 'value'} name={name} form={form} + onReset={onReset} data={data} /> ) : null} @@ -150,18 +152,24 @@ export function MultiComboBox(maybeProps: MultiComboBoxProps) { const { ref, dispatch } = useDispatchChangeEvent(); const inputRef = useRef(null); - const { selectedItems, hiddenInputValues, onRemove, ...comboBoxProps } = - useMultiList({ - defaultItems, - defaultSelectedKeys, - onChange: dispatch, - formValue, - allowsCustomValue, - valueSeparator, - focusInput: () => { - inputRef.current?.focus(); - } - }); + const { + selectedItems, + hiddenInputValues, + onRemove, + onReset, + ...comboBoxProps + } = useMultiList({ + defaultItems, + defaultSelectedKeys, + onChange: dispatch, + formValue, + allowsCustomValue, + valueSeparator, + focusInput: () => { + inputRef.current?.focus(); + } + }); + const formResetRef = useOnFormReset(onReset); return (
@@ -193,12 +201,13 @@ export function MultiComboBox(maybeProps: MultiComboBoxProps) { {name ? ( - {hiddenInputValues.map((value) => ( + {hiddenInputValues.map((value, i) => ( ))} @@ -238,7 +247,7 @@ export function RemoteComboBox({ : loader, [loader, minimumInputLength, limit] ); - const { selectedItem, ...comboBoxProps } = useRemoteList({ + const { selectedItem, onReset, ...comboBoxProps } = useRemoteList({ allowsCustomValue, defaultItems, defaultSelectedKey, @@ -270,6 +279,7 @@ export function RemoteComboBox({ } name={name} form={form} + onReset={onReset} data={data} /> ) : null} @@ -285,11 +295,13 @@ export function ComboBoxValueSlot({ field, name, form, + onReset, data }: { field: 'label' | 'value' | 'data'; name: string; form?: string; + onReset?: () => void; data?: Record; }) { const selectedItem = useContext(SelectedItemContext); @@ -300,8 +312,16 @@ export function ComboBoxValueSlot({ value ]) ); + const ref = useOnFormReset(onReset); return ( - + ); } diff --git a/app/javascript/components/react-aria/hooks.ts b/app/javascript/components/react-aria/hooks.ts index 7974e304e..2c75e8540 100644 --- a/app/javascript/components/react-aria/hooks.ts +++ b/app/javascript/components/react-aria/hooks.ts @@ -107,6 +107,10 @@ export function useSingleList({ } } ); + const onReset = useEvent(() => { + setSelectedKey(null); + setInputValue(''); + }); // reset default selected key when props change useEffect(() => { @@ -122,7 +126,8 @@ export function useSingleList({ onSelectionChange, inputValue, onInputChange, - items: filteredItems + items: filteredItems, + onReset }; } @@ -272,6 +277,11 @@ export function useMultiList({ } ); + const onReset = useEvent(() => { + setSelectedKeys(new Set()); + setInputValue(''); + }); + return { onRemove, onSelectionChange, @@ -279,7 +289,8 @@ export function useMultiList({ selectedItems, items: filteredItems, hiddenInputValues, - inputValue + inputValue, + onReset }; } @@ -357,6 +368,11 @@ export function useRemoteList({ } ); + const onReset = useEvent(() => { + setSelectedItem(null); + setInputValue(''); + }); + // add to items list current selected item if it's not in the list const items = selectedItem && !list.getItem(selectedItem.value) @@ -369,7 +385,8 @@ export function useRemoteList({ onSelectionChange, inputValue, onInputChange, - items + items, + onReset }; } @@ -436,3 +453,22 @@ function findLabelledbyId(id?: string) { } return label.id; } + +export function useOnFormReset(onReset?: () => void) { + const ref = useRef(null); + const onResetListener = useEvent((event) => { + if (event.target == ref.current?.form) { + onReset?.(); + } + }); + useEffect(() => { + if (onReset) { + addEventListener('reset', onResetListener); + return () => { + removeEventListener('reset', onResetListener); + }; + } + }, [onReset, onResetListener]); + + return ref; +}