Skip to content

Commit

Permalink
Autocomplete: Only open menu on click (#4771)
Browse files Browse the repository at this point in the history
* Only open menu on click instead of just focus

* Update tests

* Add changeset

* Fix typo

* Set `openOnFocus` to `true`

* Add test, move `onFocus` function

* Update docs

* Adjust changeset

* Remove `useCallback`

* Add deprecated notice
  • Loading branch information
TylerJDev committed Jul 31, 2024
1 parent bec8a2d commit 068a4dc
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 34 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-shoes-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Set `openOnFocus` default to `false`, making the menu closed initially rather than opening on focus of input
5 changes: 3 additions & 2 deletions packages/react/src/Autocomplete/Autocomplete.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
{
"name": "openOnFocus",
"type": "boolean",
"defaultValue": "true",
"description": "Whether the associated autocomplete menu should open on an input focus event"
"defaultValue": "false",
"description": "Whether the associated autocomplete menu should open on an input focus event",
"deprecated": true
}
],
"passthrough": {
Expand Down
42 changes: 19 additions & 23 deletions packages/react/src/Autocomplete/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import useSafeTimeout from '../hooks/useSafeTimeout'
type InternalAutocompleteInputProps = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
as?: React.ComponentType<React.PropsWithChildren<any>>
// When false, the autocomplete menu will not render either on mouse click or
// keyboard focus.

/**
* @deprecated `openOnFocus` is deprecated and will be removed in v38.
* When `true`, autocomplete menu will show on focus or click.
*/
openOnFocus?: boolean
}

Expand All @@ -28,7 +31,7 @@ const AutocompleteInput = React.forwardRef(
onKeyUp,
onKeyPress,
value,
openOnFocus = true,
openOnFocus = false,
...props
},
forwardedRef,
Expand All @@ -52,15 +55,12 @@ const AutocompleteInput = React.forwardRef(
const [highlightRemainingText, setHighlightRemainingText] = useState<boolean>(true)
const {safeSetTimeout} = useSafeTimeout()

const handleInputFocus: FocusEventHandler<HTMLInputElement> = useCallback(
event => {
if (openOnFocus) {
onFocus?.(event)
setShowMenu(true)
}
},
[onFocus, setShowMenu, openOnFocus],
)
const handleInputFocus: FocusEventHandler<HTMLInputElement> = event => {
onFocus?.(event)
if (openOnFocus) {
setShowMenu(true)
}
}

const handleInputBlur: FocusEventHandler<HTMLInputElement> = useCallback(
event => {
Expand All @@ -78,16 +78,13 @@ const AutocompleteInput = React.forwardRef(
[onBlur, setShowMenu, inputRef, safeSetTimeout],
)

const handleInputChange: ChangeEventHandler<HTMLInputElement> = useCallback(
event => {
onChange && onChange(event)
setInputValue(event.currentTarget.value)
if (!showMenu) {
setShowMenu(true)
}
},
[onChange, setInputValue, setShowMenu, showMenu],
)
const handleInputChange: ChangeEventHandler<HTMLInputElement> = event => {
onChange && onChange(event)
setInputValue(event.currentTarget.value)
if (!showMenu) {
setShowMenu(true)
}
}

const handleInputKeyDown: KeyboardEventHandler<HTMLInputElement> = useCallback(
event => {
Expand Down Expand Up @@ -122,7 +119,6 @@ const AutocompleteInput = React.forwardRef(
const onInputKeyPress: KeyboardEventHandler<HTMLInputElement> = useCallback(
event => {
onKeyPress && onKeyPress(event)

if (showMenu && event.key === 'Enter' && activeDescendantRef.current) {
event.preventDefault()
event.nativeEvent.stopImmediatePropagation()
Expand Down
23 changes: 14 additions & 9 deletions packages/react/src/__tests__/Autocomplete.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {render as HTMLRender, fireEvent, waitFor, screen} from '@testing-library/react'
import {render as HTMLRender, fireEvent, screen, waitFor} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
import type {AutocompleteInputProps} from '../Autocomplete'
Expand Down Expand Up @@ -130,14 +130,15 @@ describe('Autocomplete', () => {
expect(onKeyPressMock).toHaveBeenCalled()
})

it('opens the menu when the input is focused', () => {
it('opens the menu when the input is focused and arrow key is pressed', () => {
const {getByLabelText} = HTMLRender(
<LabelledAutocomplete menuProps={{items: [], selectedItemIds: [], ['aria-labelledby']: 'autocompleteLabel'}} />,
)
const inputNode = getByLabelText(AUTOCOMPLETE_LABEL)

expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
fireEvent.focus(inputNode)
fireEvent.click(inputNode)
fireEvent.keyDown(inputNode, {key: 'ArrowDown'})
expect(inputNode.getAttribute('aria-expanded')).toBe('true')
})

Expand All @@ -148,13 +149,14 @@ describe('Autocomplete', () => {
const inputNode = getByLabelText(AUTOCOMPLETE_LABEL)

expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
fireEvent.focus(inputNode)
fireEvent.click(inputNode)
fireEvent.keyDown(inputNode, {key: 'ArrowDown'})

expect(inputNode.getAttribute('aria-expanded')).toBe('true')
// eslint-disable-next-line github/no-blur
fireEvent.blur(inputNode)

// wait a tick for blur to finish
await waitFor(() => expect(inputNode.getAttribute('aria-expanded')).not.toBe('true'))
await userEvent.tab()

expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
})

it('sets the input value to the suggested item text and highlights the untyped part of the word', async () => {
Expand Down Expand Up @@ -306,7 +308,7 @@ describe('Autocomplete', () => {
expect(onSelectedChangeMock).not.toHaveBeenCalled()
if (inputNode) {
fireEvent.focus(inputNode)
await user.type(inputNode, '{enter}')
await user.type(inputNode, '{arrowdown}{enter}')
}

expect(onSelectedChangeMock).toHaveBeenCalledWith([mockItems[0]])
Expand All @@ -329,6 +331,8 @@ describe('Autocomplete', () => {
if (inputNode) {
expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
await user.click(inputNode)

fireEvent.keyDown(inputNode, {key: 'ArrowDown'})
expect(inputNode.getAttribute('aria-expanded')).toBe('true')
await user.click(getByText(mockItems[1].text))
expect(inputNode.getAttribute('aria-expanded')).toBe('true')
Expand All @@ -352,6 +356,7 @@ describe('Autocomplete', () => {
if (inputNode) {
expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
await user.click(inputNode)
fireEvent.keyDown(inputNode, {key: 'ArrowDown'})
expect(inputNode.getAttribute('aria-expanded')).toBe('true')
await user.click(getByText(mockItems[1].text))
expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
Expand Down

0 comments on commit 068a4dc

Please sign in to comment.