Repository: medly/medly-components (ts)

Issue:
bug: SingleSelect onBlur event not removing the focus
### Describe the bug
SingleSelect onBlur event not removing the focus, still able to open the drop-down after onBlur event using arrow keys 

### To Reproduce
1. create a ref object and pass it to the SingleSelect component
2. perform blur event using ref, based on some event, or perform it in useEffect.
3. now open the dropdown using the down arrow key
4. You'll see the drop-down opening 

### Expected behavior
blur event should remove the focus entirely form the field. It should not open drop down.

### Package versions
 - @medly-components/core : 6.26.2



Pull request:
fix(core): on blur event not removing focus properly able to open drop down using arrow key
affects: @medly-components/core

ISSUES CLOSED: #728

# PR Checklist

## Description
triggering blur event not removing the Single Select focus, still able to open the drop-down using down arrow key.

### Type of change
<!-- Please check the one that applies to this PR using "x". -->

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes and API changes)
- [ ] Build changes
- [ ] CI changes
- [ ] Document content changes
- [ ] Performance improvement
- [ ] Add missing tests
- [ ] Others (please describe)


## How has this been tested?
(Replace This Text: Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.)

[ ] Test A

[ ] Test B


## Fixes #728 


## What is the current behaviour?
able open drop-down using the down arrow key after the blur event triggered using ref


## What is the new behaviour?
will remove the focus on the blur event, and will not open dropdown.


## Does this PR introduce a breaking change?

- [ ] Yes
- [x] No

## Checklist
<!-- Please check the one that applies to this PR using "x". -->

- [x] My code follows the style guidelines of this project

- [x] I have performed a self-review of my own code

- [ ] I have commented my code, particularly in hard-to-understand areas

- [ ] I have made corresponding changes to the documentation

- [ ] My changes generate no new warnings

- [x] I have added tests that prove my fix is effective or that my feature works

- [x] New and existing unit tests pass locally with my changes

- [ ] Any dependent changes have been merged and published in downstream modules


Patch:
diff --git a/packages/core/src/components/DateRangePicker/DateRangePicker.tsx b/packages/core/src/components/DateRangePicker/DateRangePicker.tsx
index 08da724b..64c9ca7f 100644
--- a/packages/core/src/components/DateRangePicker/DateRangePicker.tsx
+++ b/packages/core/src/components/DateRangePicker/DateRangePicker.tsx
@@ -33,6 +33,7 @@ export const DateRangePicker: FC<DateRangeProps> = memo(props => {
         showTooltipForHelperAndErrorText,
         customDateRangeOptions,
         onPopupClose,
+        autoComplete,
         ...restProps
     } = props;
     const startDateRef = useRef<HTMLInputElement>(null),
@@ -131,6 +132,7 @@ export const DateRangePicker: FC<DateRangeProps> = memo(props => {
                 onCustomRangeIconClick={onCustomRangeIconClick}
                 showChevronIcon={customDateRangeOptions!.length > 0}
                 outerClickValidator={outerClickValidator}
+                autoComplete={autoComplete}
             />
             {isActive &&
                 (activePopover === PopoverTypes.CALENDAR ? (
diff --git a/packages/core/src/components/DateRangePicker/DateRangeTextFields/DateRangeTextFields.tsx b/packages/core/src/components/DateRangePicker/DateRangeTextFields/DateRangeTextFields.tsx
index bdd0c611..0b501d08 100644
--- a/packages/core/src/components/DateRangePicker/DateRangeTextFields/DateRangeTextFields.tsx
+++ b/packages/core/src/components/DateRangePicker/DateRangeTextFields/DateRangeTextFields.tsx
@@ -1,4 +1,5 @@
 import { ChevronDownIcon, DateRangeIcon } from '@medly-components/icons';
+import type { FC } from 'react';
 import { memo, useEffect } from 'react';
 import { DateIconWrapper as IconWrapper } from '../../DatePicker/DatePicker.styled';
 import datePickerPattern from '../../DatePicker/datePickerPattern';
@@ -10,7 +11,6 @@ import { Wrapper } from './DateRangeTextFields.styled';
 import InputSeparator from './InputSeparator';
 import { Props } from './types';
 import { useDateRangeTextFieldsHandlers } from './useDateRangeTextFieldsHandlers';
-import type { FC } from 'react';
 
 export const DateRangeTextFields: FC<Props> = memo(props => {
     const {
@@ -32,7 +32,8 @@ export const DateRangeTextFields: FC<Props> = memo(props => {
             showChevronIcon,
             activePopover,
             onCustomRangeIconClick,
-            outerClickValidator
+            outerClickValidator,
+            autoComplete
         } = props,
         {
             mask,
@@ -61,7 +62,8 @@ export const DateRangeTextFields: FC<Props> = memo(props => {
             placeholder: mask,
             onChange: handleTextChange,
             errorText: errorText || builtInErrorMessage,
-            pattern: datePickerPattern[displayFormat]
+            pattern: datePickerPattern[displayFormat],
+            autoComplete
         },
         iconProps = {
             variant,
diff --git a/packages/core/src/components/DateRangePicker/DateRangeTextFields/types.ts b/packages/core/src/components/DateRangePicker/DateRangeTextFields/types.ts
index 89ab18e6..b1f8cbe1 100644
--- a/packages/core/src/components/DateRangePicker/DateRangeTextFields/types.ts
+++ b/packages/core/src/components/DateRangePicker/DateRangeTextFields/types.ts
@@ -26,4 +26,5 @@ export type Props = {
     showTooltipForHelperAndErrorText?: boolean;
     onCustomRangeIconClick: () => void;
     outerClickValidator: RefObject<(e: MouseEvent) => void>;
+    autoComplete?: string;
 };
diff --git a/packages/core/src/components/SearchBox/SearchBox.tsx b/packages/core/src/components/SearchBox/SearchBox.tsx
index 48b4d2af..838b9fb0 100644
--- a/packages/core/src/components/SearchBox/SearchBox.tsx
+++ b/packages/core/src/components/SearchBox/SearchBox.tsx
@@ -1,6 +1,6 @@
 import { CloseIcon, ExpandIcon, SearchIcon } from '@medly-components/icons';
 import { CircleLoader } from '@medly-components/loaders';
-import { useCombinedRefs, useKeyPress, useOuterClickNotifier, WithStyle } from '@medly-components/utils';
+import { WithStyle, useCombinedRefs, useKeyPress, useOuterClickNotifier } from '@medly-components/utils';
 import type { FC } from 'react';
 import { forwardRef, memo, useCallback, useEffect, useRef, useState } from 'react';
 import Options from '../SingleSelect/Options';
@@ -68,13 +68,13 @@ const Component: FC<SearchBoxProps> = memo(
                 setOptionsVisibilityState(false);
                 setShowCloseIcon(false);
                 updateIsTyping(false);
-                onClear && onClear();
+                onClear?.();
             }, []),
             handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
                 const { value } = event.target;
                 updateIsTyping(value.length !== 0);
                 setShowCloseIcon(value.length !== 0);
-                onInputChange && onInputChange(value);
+                onInputChange?.(value);
             }, []),
             handleOptionClick = useCallback(
                 (option: Option) => {
@@ -83,7 +83,7 @@ const Component: FC<SearchBoxProps> = memo(
                         inputRef.current.focus();
                     }
                     setOptionsVisibilityState(false);
-                    onOptionSelected && onOptionSelected(option);
+                    onOptionSelected?.(option);
                 },
                 [onOptionSelected]
             ),
@@ -98,7 +98,7 @@ const Component: FC<SearchBoxProps> = memo(
             handleBlur = useCallback(() => {
                 isFocused.current = false;
             }, []),
-            handleSearchIconClick = useCallback(() => onSearch && onSearch(inputRef.current?.value || ''), [onSearch]);
+            handleSearchIconClick = useCallback(() => onSearch?.(inputRef.current?.value || ''), [onSearch]);
 
         useKeyboardNavigation({
             isFocused,
@@ -119,10 +119,10 @@ const Component: FC<SearchBoxProps> = memo(
                 const currentSelectedOption = options.find(option => option.hovered === true);
                 if (inputRef.current && currentSelectedOption) {
                     inputRef.current.value = currentSelectedOption.label;
-                    onOptionSelected && onOptionSelected(currentSelectedOption);
+                    onOptionSelected?.(currentSelectedOption);
                     inputRef.current.blur();
                 }
-                onSearch && onSearch(inputRef.current?.value || '');
+                !currentSelectedOption && onSearch?.(inputRef.current?.value || '');
                 setOptionsVisibilityState(false);
                 setShowCloseIcon(inputRef.current?.value.length !== 0);
                 updateIsTyping(false);
diff --git a/packages/core/src/components/SingleSelect/SingleSelect.tsx b/packages/core/src/components/SingleSelect/SingleSelect.tsx
index 51eb70cc..d3332837 100644
--- a/packages/core/src/components/SingleSelect/SingleSelect.tsx
+++ b/packages/core/src/components/SingleSelect/SingleSelect.tsx
@@ -116,6 +116,13 @@ const Component: FC<SingleSelectProps> = memo(
                 },
                 [onFocus]
             ),
+            handleBlur = useCallback(
+                (event: React.FocusEvent<HTMLInputElement>) => {
+                    isFocused.current = false;
+                    onBlur?.(event);
+                },
+                [onBlur]
+            ),
             inputValidator = useCallback(() => '', []),
             handleKeyPress = useCallback((event: React.KeyboardEvent) => !isSearchable && event.preventDefault(), [isSearchable]);
 
@@ -151,7 +158,7 @@ const Component: FC<SingleSelectProps> = memo(
             helperText: inputProps.helperText,
             errorText: inputProps.errorText || builtInErrorMessage,
             onFocus: handleFocus,
-            onBlur,
+            onBlur: handleBlur,
             onKeyPress: handleKeyPress,
             disabled,
             showDecorators,


Test patch:
diff --git a/packages/core/src/components/DateRangePicker/DateRangePicker.test.tsx b/packages/core/src/components/DateRangePicker/DateRangePicker.test.tsx
index 96b6f5f6..e2f3c085 100644
--- a/packages/core/src/components/DateRangePicker/DateRangePicker.test.tsx
+++ b/packages/core/src/components/DateRangePicker/DateRangePicker.test.tsx
@@ -566,4 +566,18 @@ describe('DateRangePicker', () => {
             await waitFor(() => expect(container.querySelector(customDateRangeOptionsPopoverSelector)).toHaveStyle(`top: ${position}`));
         });
     });
+
+    describe('input element', () => {
+        afterEach(cleanup);
+
+        it('should render properly with default input attributes being passed to input elements', () => {
+            renderComponent({
+                autoComplete: 'off'
+            });
+            const inputs = screen.getAllByPlaceholderText('MM / DD / YYYY');
+            inputs.forEach(input => {
+                expect(input).toHaveAttribute('autoComplete', 'off');
+            });
+        });
+    });
 });
diff --git a/packages/core/src/components/SearchBox/SearchBox.test.tsx b/packages/core/src/components/SearchBox/SearchBox.test.tsx
index 6d030011..56a69599 100644
--- a/packages/core/src/components/SearchBox/SearchBox.test.tsx
+++ b/packages/core/src/components/SearchBox/SearchBox.test.tsx
@@ -216,8 +216,9 @@ describe('SearchBox', () => {
         });
 
         describe('with option selected', () => {
-            const onOptionSelectedMock = jest.fn();
-            const withOptionCB = { ...props, onOptionSelected: onOptionSelectedMock };
+            const onOptionSelectedMock = jest.fn(),
+                onSearch = jest.fn(),
+                withOptionCB = { ...props, onOptionSelected: onOptionSelectedMock, onSearch };
 
             it('should call on OptionSelected when option is clicked', () => {
                 const { inputEl } = renderComponent(withOptionCB);
@@ -233,6 +234,15 @@ describe('SearchBox', () => {
                 fireEvent.keyDown(container, { key: 'Enter', code: 13 });
                 expect(inputEl).toHaveValue('Dummy 1');
             });
+
+            it('should not call onSearch on option selection using enter', () => {
+                const { container, inputEl } = renderComponent(withOptionCB);
+                fireEvent.focus(inputEl);
+                fireEvent.keyDown(container, { key: 'ArrowDown', code: 40 });
+                fireEvent.keyDown(container, { key: 'Enter', code: 13 });
+                expect(onSearch).not.toBeCalled();
+                expect(inputEl).toHaveValue('Dummy 1');
+            });
         });
     });
 });
diff --git a/packages/core/src/components/SingleSelect/SingleSelect.test.tsx b/packages/core/src/components/SingleSelect/SingleSelect.test.tsx
index 6145e1d7..708d9cb8 100644
--- a/packages/core/src/components/SingleSelect/SingleSelect.test.tsx
+++ b/packages/core/src/components/SingleSelect/SingleSelect.test.tsx
@@ -411,6 +411,15 @@ describe('SingleSelect component', () => {
                 fireEvent.keyDown(inputEl, { key: 'ArrowDown', code: 40 }); // will not shows the options
                 expect(screen.queryByRole('list')).not.toBeInTheDocument();
             });
+
+            it('should not show options on blur event', async () => {
+                render(<SingleSelect options={options} />);
+                const inputEl = screen.getByRole('textbox') as HTMLInputElement;
+                fireEvent.focus(inputEl);
+                fireEvent.blur(inputEl);
+                fireEvent.keyDown(inputEl, { key: 'ArrowDown', code: 40 }); // will not shows the options
+                expect(screen.queryByRole('list')).not.toBeInTheDocument();
+            });
         });
 
         describe('up arrow', () => {