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],
);
}