From e0488a8d3b9bc3ca8f8947cd6367bd043abdc92c Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Fri, 8 May 2026 07:42:48 -0400 Subject: [PATCH] Merge pull request #33534 from overleaf/em-fix-autocomplete-32913 Fix autocomplete dropdown closing on blur and input re-click GitOrigin-RevId: 82f45f0f1ae9e2b3846906d962a3f16e5b2963e4 --- .../shared/components/ol/ol-autocomplete.tsx | 3 ++ .../shared/ol-autocomplete.spec.tsx | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/services/web/frontend/js/shared/components/ol/ol-autocomplete.tsx b/services/web/frontend/js/shared/components/ol/ol-autocomplete.tsx index 146ac049d3..47773bcba2 100644 --- a/services/web/frontend/js/shared/components/ol/ol-autocomplete.tsx +++ b/services/web/frontend/js/shared/components/ol/ol-autocomplete.tsx @@ -33,6 +33,7 @@ export type OLAutocompleteProps = { inputRef?: React.ForwardedRef expandUp?: boolean onClose?: () => void + isOpen?: boolean } type OLAutocompleteDisplayItem = @@ -59,6 +60,7 @@ function OLAutocompleteInternal({ inputRef, expandUp = false, onClose, + isOpen: controlledIsOpen, }: OLAutocompleteProps) { const { t } = useTranslation() @@ -140,6 +142,7 @@ function OLAutocompleteInternal({ inputValue: internalInputValue, items: displayItems, defaultHighlightedIndex: 0, + ...(controlledIsOpen !== undefined && { isOpen: controlledIsOpen }), itemToString: item => { if (!item) return '' return item.type === 'create' ? item.inputValue : item.label diff --git a/services/web/test/frontend/components/shared/ol-autocomplete.spec.tsx b/services/web/test/frontend/components/shared/ol-autocomplete.spec.tsx index 35c82cb202..39d50172b1 100644 --- a/services/web/test/frontend/components/shared/ol-autocomplete.spec.tsx +++ b/services/web/test/frontend/components/shared/ol-autocomplete.spec.tsx @@ -60,6 +60,7 @@ function render(props: RenderProps) { useFuzzySearch={props.useFuzzySearch} expandUp={props.expandUp} onClose={props.onClose} + isOpen={props.isOpen} /> @@ -551,6 +552,35 @@ describe('', function () { }) }) + describe('controlled isOpen prop', function () { + it('keeps dropdown open when input is blurred', function () { + render({ items: testItems, isOpen: true }) + cy.findByRole('combobox').click() + cy.get('.dropdown-menu.show').should('exist') + + cy.findByRole('combobox').blur() + cy.get('.dropdown-menu.show').should('exist') + }) + + it('keeps dropdown open when input is clicked while already open', function () { + render({ items: testItems, isOpen: true }) + cy.findByRole('combobox').click() + cy.get('.dropdown-menu.show').should('exist') + + cy.findByRole('combobox').click() + cy.get('.dropdown-menu.show').should('exist') + }) + + it('calls onClose when Escape is pressed', function () { + const closeHandler = cy.stub().as('closeHandler') + render({ items: testItems, isOpen: true, onClose: closeHandler }) + + cy.findByRole('combobox').type('{esc}') + + cy.get('@closeHandler').should('have.been.calledOnce') + }) + }) + describe('expandUp prop', function () { it('renders search bar before results list when expandUp is false', function () { render({ items: testItems, expandUp: false })