diff --git a/packages/vkui/src/components/CustomSelect/CustomSelect.test.tsx b/packages/vkui/src/components/CustomSelect/CustomSelect.test.tsx index 16684acf294..1c6b9154242 100644 --- a/packages/vkui/src/components/CustomSelect/CustomSelect.test.tsx +++ b/packages/vkui/src/components/CustomSelect/CustomSelect.test.tsx @@ -1986,4 +1986,108 @@ describe('CustomSelect', () => { // дропдаун открыт вниз и класс для границ выставлен верно expect(document.querySelector(`.${styles.popDown}`)).not.toBeNull(); }); + + describe('searchable + allowClearButton', () => { + it('shows clear button when typing in search and hides when search is empty', async () => { + render( + , + ); + + // дропдаун закрыт — кнопки очистки нет + expect(screen.queryByTestId('clearButtonTestId')).toBeFalsy(); + + // открываем дропдаун и вводим текст + fireEvent.click(screen.getByTestId('labelTextTestId')); + await waitFor(() => expect(screen.getByTestId(INPUT_TEST_ID)).toHaveFocus()); + expect(screen.queryByTestId('clearButtonTestId')).toBeFalsy(); + + fireEvent.change(screen.getByTestId(INPUT_TEST_ID), { target: { value: 'Mi' } }); + expect(getInputValue()).toBe('Mi'); + expect(screen.getByTestId('clearButtonTestId')).toBeTruthy(); + + // очищаем поле поиска — кнопка снова пропадает + fireEvent.change(screen.getByTestId(INPUT_TEST_ID), { target: { value: '' } }); + expect(screen.queryByTestId('clearButtonTestId')).toBeFalsy(); + }); + + it('clicking clear button clears only search text and keeps selected value', async () => { + const onChange = vi.fn(); + + render( + , + ); + + expect(getCustomSelectValue()).toEqual('Mike'); + + // открываем дропдаун и вводим текст + fireEvent.click(screen.getByTestId('labelTextTestId')); + await waitFor(() => expect(screen.getByTestId(INPUT_TEST_ID)).toHaveFocus()); + fireEvent.change(screen.getByTestId(INPUT_TEST_ID), { target: { value: 'Jo' } }); + expect(getInputValue()).toBe('Jo'); + + // кликаем по кнопке очистки + fireEvent.click(screen.getByTestId('clearButtonTestId')); + + // текст поиска сброшен + expect(getInputValue()).toBe(''); + // выбранное значение сохраняется + expect(getCustomSelectValue()).toEqual('Mike'); + // onChange не вызывается — выбранное значение не менялось + expect(onChange).not.toHaveBeenCalled(); + }); + + it('does not show clear button without allowClearButton when typing in search', async () => { + render( + , + ); + + fireEvent.click(screen.getByTestId('labelTextTestId')); + await waitFor(() => expect(screen.getByTestId(INPUT_TEST_ID)).toHaveFocus()); + fireEvent.change(screen.getByTestId(INPUT_TEST_ID), { target: { value: 'Mi' } }); + + expect(screen.queryByRole('button', { hidden: true })).toBeFalsy(); + }); + }); }); diff --git a/packages/vkui/src/components/CustomSelect/CustomSelect.tsx b/packages/vkui/src/components/CustomSelect/CustomSelect.tsx index 93e6615df18..a6adad906da 100644 --- a/packages/vkui/src/components/CustomSelect/CustomSelect.tsx +++ b/packages/vkui/src/components/CustomSelect/CustomSelect.tsx @@ -677,6 +677,12 @@ export function CustomSelect { + resetInputValue(); + selectInputRef.current && selectInputRef.current.focus(); + }, }); const passClickAndFocusToInputOnClick = React.useCallback( diff --git a/packages/vkui/src/components/CustomSelect/hooks/useAfterItems.tsx b/packages/vkui/src/components/CustomSelect/hooks/useAfterItems.tsx index 62e50d1a02b..fff3aed48a8 100644 --- a/packages/vkui/src/components/CustomSelect/hooks/useAfterItems.tsx +++ b/packages/vkui/src/components/CustomSelect/hooks/useAfterItems.tsx @@ -24,6 +24,9 @@ interface UseAfterItemsProps nativeSelectValue: NativeSelectValue; opened: boolean; onClearButtonClick: () => void; + searchable: boolean; + inputValue: string; + onInputClear: () => void; } /* eslint-enable jsdoc/require-jsdoc */ @@ -39,28 +42,43 @@ export function useAfterItems({ disabled, readOnly, icon: iconProp, + searchable, + inputValue, + onInputClear, }: UseAfterItemsProps) { const onClearButtonClickCb = useStableCallback(onClearButtonClick); + const onInputClearCb = useStableCallback(onInputClear); const controlledValueSet = isControlledOutside && value !== NOT_SELECTED.CUSTOM; const uncontrolledValueSet = !isControlledOutside && nativeSelectValue !== NOT_SELECTED.NATIVE; - const clearButtonShown = - allowClearButton && !opened && (controlledValueSet || uncontrolledValueSet); + const valueSet = controlledValueSet || uncontrolledValueSet; + const hasInputValue = searchable && opened && inputValue.length > 0; + const clearButtonShown = allowClearButton && !opened && valueSet; + const inputClearButtonShown = allowClearButton && hasInputValue; const clearButton = React.useMemo(() => { - if (!clearButtonShown) { + if (!clearButtonShown && !inputClearButtonShown) { return null; } return ( ); - }, [clearButtonShown, ClearButton, iconProp, onClearButtonClickCb, disabled, clearButtonTestId]); + }, [ + clearButtonShown, + inputClearButtonShown, + ClearButton, + iconProp, + onInputClearCb, + onClearButtonClickCb, + disabled, + clearButtonTestId, + ]); const icon = React.useMemo(() => { if (iconProp !== undefined) { @@ -69,21 +87,21 @@ export function useAfterItems({ return ( ); - }, [clearButtonShown, iconProp, opened]); + }, [clearButtonShown, inputClearButtonShown, iconProp, opened]); return React.useMemo( () => !readOnly && - (icon || clearButtonShown) && ( + (icon || clearButtonShown || inputClearButtonShown) && ( {clearButton} {icon} ), - [clearButton, clearButtonShown, icon, readOnly], + [clearButton, clearButtonShown, inputClearButtonShown, icon, readOnly], ); }