diff --git a/src/components/forms/ComboBox/ComboBox.test.tsx b/src/components/forms/ComboBox/ComboBox.test.tsx index d709a6da40..c5b69e0a60 100644 --- a/src/components/forms/ComboBox/ComboBox.test.tsx +++ b/src/components/forms/ComboBox/ComboBox.test.tsx @@ -482,6 +482,25 @@ describe('ComboBox component', () => { fireEvent.blur(getByTestId('combo-box-clear-button')) expect(getByTestId('combo-box-input')).toHaveFocus() }) + + it('focuses the input after clearing when the when FocusMode is None', () => { + const { getByTestId } = render( + <> +
+ + + ) + + userEvent.type(getByTestId('combo-box-input'), 'b') + userEvent.click(getByTestId('outside')) + userEvent.click(getByTestId('combo-box-clear-button')) + expect(getByTestId('combo-box-input')).toHaveFocus() + }) }) it('clears input value and closes list when an incomplete item is remaining on blur', () => { @@ -1058,6 +1077,24 @@ describe('ComboBox component', () => { 'usa-combo-box__list-option--focused' ) }) + + it('clears focus when clicking outside of the component', () => { + const { getByTestId } = render( + <> +
+ + + ) + + userEvent.click(getByTestId('combo-box-toggle')) + userEvent.click(getByTestId('outside')) + expect(getByTestId('combo-box-input')).not.toHaveFocus() + }) }) describe('accessibility and internationalization', () => { diff --git a/src/components/forms/ComboBox/ComboBox.tsx b/src/components/forms/ComboBox/ComboBox.tsx index d3b7c4e1b7..a40eac39e5 100644 --- a/src/components/forms/ComboBox/ComboBox.tsx +++ b/src/components/forms/ComboBox/ComboBox.tsx @@ -145,16 +145,13 @@ export const ComboBox = (props: ComboBoxProps): React.ReactElement => { } const handleInputBlur = (event: FocusEvent): void => { - const { target: elementLosingFocus, relatedTarget: newTarget } = event + const { relatedTarget: newTarget } = event const newTargetIsOutside = !newTarget || - (newTarget instanceof Node && - !containerRef.current?.contains(elementLosingFocus)) + (newTarget instanceof Node && !containerRef.current?.contains(newTarget)) - if (state.selectedOption?.value) { - if (newTargetIsOutside) dispatch({ type: ActionTypes.CLOSE_LIST }) - } else if (newTargetIsOutside) { - dispatch({ type: ActionTypes.CLEAR }) + if (newTargetIsOutside) { + dispatch({ type: ActionTypes.BLUR }) } } @@ -203,7 +200,7 @@ export const ComboBox = (props: ComboBoxProps): React.ReactElement => { !newTarget || (newTarget instanceof Node && !containerRef.current?.contains(newTarget)) ) { - dispatch({ type: ActionTypes.CLOSE_LIST }) + dispatch({ type: ActionTypes.BLUR }) } } diff --git a/src/components/forms/ComboBox/useCombobox.ts b/src/components/forms/ComboBox/useCombobox.ts index 743ab9a6f3..c436e0ed19 100644 --- a/src/components/forms/ComboBox/useCombobox.ts +++ b/src/components/forms/ComboBox/useCombobox.ts @@ -9,6 +9,7 @@ export enum ActionTypes { CLOSE_LIST, FOCUS_OPTION, UPDATE_FILTER, + BLUR, } export type Action = @@ -33,7 +34,9 @@ export type Action = type: ActionTypes.UPDATE_FILTER value: string } - + | { + type: ActionTypes.BLUR + } export interface State { isOpen: boolean selectedOption?: ComboBoxOption @@ -124,11 +127,29 @@ export const useCombobox = ( ...state, inputValue: '', isOpen: false, + focusMode: FocusMode.Input, selectedOption: undefined, filter: undefined, filteredOptions: optionsList.filter(isPartialMatch('')), } + case ActionTypes.BLUR: { + const newState = { + ...state, + isOpen: false, + focusMode: FocusMode.None, + focusedOption: undefined, + } + if (state.filteredOptions.length === 0) { + newState.filteredOptions = optionsList.filter(isPartialMatch('')) + } + + if (!state.selectedOption) { + newState.inputValue = '' + } + + return newState + } default: throw new Error() }