diff --git a/packages/fuselage/src/components/AutoComplete/AutoComplete.spec.tsx b/packages/fuselage/src/components/AutoComplete/AutoComplete.spec.tsx index 8b331cb11c..a468a272d3 100644 --- a/packages/fuselage/src/components/AutoComplete/AutoComplete.spec.tsx +++ b/packages/fuselage/src/components/AutoComplete/AutoComplete.spec.tsx @@ -124,4 +124,23 @@ describe('[Autocomplete functionality]', () => { screen.queryByRole('button', { name: 'test1' }), ).not.toBeInTheDocument(); }); + it('should remove last selected item when pressing Backspace with empty filter (multiple)', async () => { + const onChange = jest.fn(); + render( + {}} + options={[ + { value: '1', label: 'test1' }, + { value: '2', label: 'test2' }, + ]} + onChange={onChange} + multiple + />, + ); + const input = screen.getByRole('textbox'); + await userEvent.type(input, '{Backspace}'); + expect(onChange).toHaveBeenCalledWith(['1']); + }); }); diff --git a/packages/fuselage/src/components/AutoComplete/AutoComplete.tsx b/packages/fuselage/src/components/AutoComplete/AutoComplete.tsx index 324e33a7a6..a74ad4e590 100644 --- a/packages/fuselage/src/components/AutoComplete/AutoComplete.tsx +++ b/packages/fuselage/src/components/AutoComplete/AutoComplete.tsx @@ -7,6 +7,7 @@ import type { ChangeEvent, ComponentType, FocusEvent, + KeyboardEvent, MouseEvent, ReactNode, } from 'react'; @@ -106,7 +107,6 @@ function AutoComplete({ ); useEffect(() => { - // Validates if selected items are still valid after value changes setSelected((selected) => { return !selected.every(isSelectedValid(value)) ? selected.filter(isSelectedValid(value)) @@ -163,13 +163,40 @@ function AutoComplete({ ); const firstSelectedIndex = useMemo( - () => options.findIndex((option) => selected[0]?.value === option.value), - [options, selected], - ); + () => + filter + ? options.findIndex((option) => + String(option.label) + .toLowerCase() + .includes(filter.toLowerCase()), + ) + : options.findIndex((option) => selected[0]?.value === option.value), + [options, selected, filter], +); const [cursor, handleKeyDown, , reset, [optionsAreVisible, hide, show]] = useCursor(firstSelectedIndex, memoizedOptions, handleSelect); + const handleKeyDownWrapper = useStableCallback( + (event: KeyboardEvent) => { + if (event.key === 'Backspace' && filter === '') { + if (selected.length > 0) { + const lastSelected = selected[selected.length - 1]; + const filtered = selected.slice(0, -1); + const filteredValue = + multiple && Array.isArray(value) + ? value.filter((item) => item !== lastSelected.value) + : ''; + setSelected(filtered); + onChange(filteredValue); + hide(); + return; + } + } + handleKeyDown(event); + }, + ); + const handleOnBlur = useStableCallback( (event: FocusEvent) => { hide(); @@ -177,7 +204,9 @@ function AutoComplete({ }, ); - useEffect(reset, [filter, reset]); + useEffect(() => { + reset(); + }, [filter, reset]); return ( ({ )} onBlur={handleOnBlur} onFocus={show} - onKeyDown={handleKeyDown} + onKeyDown={handleKeyDownWrapper} placeholder={ optionsAreVisible === AnimatedVisibility.HIDDEN || !value ? placeholder @@ -262,4 +291,4 @@ function AutoComplete({ ); } -export default AutoComplete; +export default AutoComplete; \ No newline at end of file diff --git a/packages/fuselage/src/components/Options/useCursor.ts b/packages/fuselage/src/components/Options/useCursor.ts index 8699321f21..b4d6edf340 100644 --- a/packages/fuselage/src/components/Options/useCursor.ts +++ b/packages/fuselage/src/components/Options/useCursor.ts @@ -1,6 +1,6 @@ import { useStableCallback } from '@rocket.chat/fuselage-hooks'; import type { KeyboardEvent } from 'react'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { AnimatedVisibility } from '../AnimatedVisibility'; @@ -83,10 +83,13 @@ export const useCursor = < reset: () => void, visibilityHandler: ReturnType, ] => { - const [cursor, setCursor] = useState(initial); + const [cursor, setCursor] = useState(initial); + useEffect(() => { + setCursor(initial); +}, [initial]); const visibilityHandler = useVisible(); const [visibility, hide, show] = visibilityHandler; - const reset = useStableCallback(() => setCursor(0)); + const reset = useStableCallback(() => setCursor(initial)); const handleKeyUp = useStableCallback((e: KeyboardEvent) => { const { keyCode } = e; if (AnimatedVisibility.HIDDEN === visibility && keyCode === keyCodes.TAB) { @@ -140,8 +143,11 @@ export const useCursor = < e.nativeEvent.stopImmediatePropagation(); // TODO e.stopPropagation(); } - hide(); - onChange(options[cursor], visibilityHandler); + const targetIndex = cursor >= 0 ? cursor : 0; + if (options[targetIndex]) { + hide(); + onChange(options[targetIndex], visibilityHandler); + } return; case keyCodes.ESC: