Skip to content

Commit 92e05f7

Browse files
authored
Autocomplete: Only open menu on click (#4771)
* 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
1 parent 4ebacb7 commit 92e05f7

File tree

4 files changed

+41
-34
lines changed

4 files changed

+41
-34
lines changed

.changeset/four-shoes-yell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react': minor
3+
---
4+
5+
Set `openOnFocus` default to `false`, making the menu closed initially rather than opening on focus of input

packages/react/src/Autocomplete/Autocomplete.docs.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323
{
2424
"name": "openOnFocus",
2525
"type": "boolean",
26-
"defaultValue": "true",
27-
"description": "Whether the associated autocomplete menu should open on an input focus event"
26+
"defaultValue": "false",
27+
"description": "Whether the associated autocomplete menu should open on an input focus event",
28+
"deprecated": true
2829
}
2930
],
3031
"passthrough": {

packages/react/src/Autocomplete/AutocompleteInput.tsx

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ import useSafeTimeout from '../hooks/useSafeTimeout'
1010
type InternalAutocompleteInputProps = {
1111
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1212
as?: React.ComponentType<React.PropsWithChildren<any>>
13-
// When false, the autocomplete menu will not render either on mouse click or
14-
// keyboard focus.
13+
14+
/**
15+
* @deprecated `openOnFocus` is deprecated and will be removed in v38.
16+
* When `true`, autocomplete menu will show on focus or click.
17+
*/
1518
openOnFocus?: boolean
1619
}
1720

@@ -28,7 +31,7 @@ const AutocompleteInput = React.forwardRef(
2831
onKeyUp,
2932
onKeyPress,
3033
value,
31-
openOnFocus = true,
34+
openOnFocus = false,
3235
...props
3336
},
3437
forwardedRef,
@@ -52,15 +55,12 @@ const AutocompleteInput = React.forwardRef(
5255
const [highlightRemainingText, setHighlightRemainingText] = useState<boolean>(true)
5356
const {safeSetTimeout} = useSafeTimeout()
5457

55-
const handleInputFocus: FocusEventHandler<HTMLInputElement> = useCallback(
56-
event => {
57-
if (openOnFocus) {
58-
onFocus?.(event)
59-
setShowMenu(true)
60-
}
61-
},
62-
[onFocus, setShowMenu, openOnFocus],
63-
)
58+
const handleInputFocus: FocusEventHandler<HTMLInputElement> = event => {
59+
onFocus?.(event)
60+
if (openOnFocus) {
61+
setShowMenu(true)
62+
}
63+
}
6464

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

81-
const handleInputChange: ChangeEventHandler<HTMLInputElement> = useCallback(
82-
event => {
83-
onChange && onChange(event)
84-
setInputValue(event.currentTarget.value)
85-
if (!showMenu) {
86-
setShowMenu(true)
87-
}
88-
},
89-
[onChange, setInputValue, setShowMenu, showMenu],
90-
)
81+
const handleInputChange: ChangeEventHandler<HTMLInputElement> = event => {
82+
onChange && onChange(event)
83+
setInputValue(event.currentTarget.value)
84+
if (!showMenu) {
85+
setShowMenu(true)
86+
}
87+
}
9188

9289
const handleInputKeyDown: KeyboardEventHandler<HTMLInputElement> = useCallback(
9390
event => {
@@ -122,7 +119,6 @@ const AutocompleteInput = React.forwardRef(
122119
const onInputKeyPress: KeyboardEventHandler<HTMLInputElement> = useCallback(
123120
event => {
124121
onKeyPress && onKeyPress(event)
125-
126122
if (showMenu && event.key === 'Enter' && activeDescendantRef.current) {
127123
event.preventDefault()
128124
event.nativeEvent.stopImmediatePropagation()

packages/react/src/__tests__/Autocomplete.test.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {render as HTMLRender, fireEvent, waitFor, screen} from '@testing-library/react'
1+
import {render as HTMLRender, fireEvent, screen, waitFor} from '@testing-library/react'
22
import userEvent from '@testing-library/user-event'
33
import React from 'react'
44
import type {AutocompleteInputProps} from '../Autocomplete'
@@ -130,14 +130,15 @@ describe('Autocomplete', () => {
130130
expect(onKeyPressMock).toHaveBeenCalled()
131131
})
132132

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

139139
expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
140-
fireEvent.focus(inputNode)
140+
fireEvent.click(inputNode)
141+
fireEvent.keyDown(inputNode, {key: 'ArrowDown'})
141142
expect(inputNode.getAttribute('aria-expanded')).toBe('true')
142143
})
143144

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

150151
expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
151-
fireEvent.focus(inputNode)
152+
fireEvent.click(inputNode)
153+
fireEvent.keyDown(inputNode, {key: 'ArrowDown'})
154+
152155
expect(inputNode.getAttribute('aria-expanded')).toBe('true')
153-
// eslint-disable-next-line github/no-blur
154-
fireEvent.blur(inputNode)
155156

156-
// wait a tick for blur to finish
157-
await waitFor(() => expect(inputNode.getAttribute('aria-expanded')).not.toBe('true'))
157+
await userEvent.tab()
158+
159+
expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
158160
})
159161

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

312314
expect(onSelectedChangeMock).toHaveBeenCalledWith([mockItems[0]])
@@ -329,6 +331,8 @@ describe('Autocomplete', () => {
329331
if (inputNode) {
330332
expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
331333
await user.click(inputNode)
334+
335+
fireEvent.keyDown(inputNode, {key: 'ArrowDown'})
332336
expect(inputNode.getAttribute('aria-expanded')).toBe('true')
333337
await user.click(getByText(mockItems[1].text))
334338
expect(inputNode.getAttribute('aria-expanded')).toBe('true')
@@ -352,6 +356,7 @@ describe('Autocomplete', () => {
352356
if (inputNode) {
353357
expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')
354358
await user.click(inputNode)
359+
fireEvent.keyDown(inputNode, {key: 'ArrowDown'})
355360
expect(inputNode.getAttribute('aria-expanded')).toBe('true')
356361
await user.click(getByText(mockItems[1].text))
357362
expect(inputNode.getAttribute('aria-expanded')).not.toBe('true')

0 commit comments

Comments
 (0)