diff --git a/.changeset/autocomplete-blur-suggestion.md b/.changeset/autocomplete-blur-suggestion.md
new file mode 100644
index 00000000000..f05c0a4c428
--- /dev/null
+++ b/.changeset/autocomplete-blur-suggestion.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': patch
+---
+
+Autocomplete: Keep the typed text instead of restoring the full inline suggestion when the input loses focus, matching the behavior of pressing Escape
diff --git a/packages/react/src/Autocomplete/Autocomplete.test.tsx b/packages/react/src/Autocomplete/Autocomplete.test.tsx
index 68e61313b3e..ab1d46a7e6d 100644
--- a/packages/react/src/Autocomplete/Autocomplete.test.tsx
+++ b/packages/react/src/Autocomplete/Autocomplete.test.tsx
@@ -214,6 +214,30 @@ describe('Autocomplete', () => {
expect(inputNode?.getAttribute('aria-expanded')).not.toBe('true')
})
+ it('does not restore the autocomplete suggestion when the input is blurred', async () => {
+ const user = userEvent.setup()
+ const {container} = render(
+ <>
+
+
+ >,
+ )
+ const inputNode = container.querySelector('#autocompleteInput') as HTMLInputElement
+ const outsideButton = screen.getByRole('button', {name: 'outside'})
+
+ // Type 'ze' which gets the inline autocomplete suggestion 'zero'
+ await user.type(inputNode, 'ze')
+ expect(inputNode.value).toBe('zero')
+
+ // Move focus elsewhere on the page, like clicking outside the Autocomplete
+ await user.click(outsideButton)
+
+ // The input should retain the text the user typed rather than the full suggestion
+ await waitFor(() => expect(inputNode.value).toBe('ze'))
+ })
+
it('allows the value to be 0', () => {
const {getByDisplayValue} = render(
{
if (document.activeElement !== inputRef.current) {
setShowMenu(false)
+
+ // Reset the input's value to the text the user actually typed rather than leaving the
+ // inline autocomplete suggestion in place. This keeps the blur behavior consistent with
+ // pressing Escape, so deleting characters off a selection and then clicking away does not
+ // silently restore the full suggestion. See https://github.com/primer/react/issues/4275
+ if (inputRef.current && autocompleteSuggestion && inputRef.current.value !== inputValue) {
+ inputRef.current.value = inputValue
+ }
}
}, 0)
},
- [onBlur, setShowMenu, inputRef, safeSetTimeout],
+ [onBlur, setShowMenu, inputRef, safeSetTimeout, autocompleteSuggestion, inputValue],
)
const handleInputChange: ChangeEventHandler = event => {
@@ -136,7 +144,17 @@ const AutocompleteInput = React.forwardRef(
// TODO: fix bug where this function prevents `onChange` from being triggered if the highlighted item text
// is the same as what I'm typing
// e.g.: typing 'tw' highlights 'two', but when I 'two', the text input change does not get triggered
- if (highlightRemainingText && autocompleteSuggestion && (inputValue || isMenuDirectlyActivated)) {
+ // Only apply the inline autocomplete suggestion while the input is focused. Without this guard,
+ // the suggestion can be re-applied to the DOM after the input is blurred, which would restore
+ // the full suggestion the user was editing away from. See https://github.com/primer/react/issues/4275
+ const isInputFocused = document.activeElement === inputRef.current
+
+ if (
+ isInputFocused &&
+ highlightRemainingText &&
+ autocompleteSuggestion &&
+ (inputValue || isMenuDirectlyActivated)
+ ) {
inputRef.current.value = autocompleteSuggestion
if (autocompleteSuggestion.toLowerCase().indexOf(inputValue.toLowerCase()) === 0) {