diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 68cfb5b4e46aa..b4f6e5253469d 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -16,6 +16,7 @@
- `Button`: Fix RTL alignment for buttons containing an icon and text ([#44787](https://github.com/WordPress/gutenberg/pull/44787)).
- `TabPanel`: Call `onSelect()` on every tab selection, regardless of whether it was triggered by user interaction ([#44028](https://github.com/WordPress/gutenberg/pull/44028)).
- `FontSizePicker`: Fallback to font size `slug` if `name` is undefined ([#45041](https://github.com/WordPress/gutenberg/pull/45041)).
+- `AutocompleterUI`: fix issue where autocompleter UI would appear on top of other UI elements ([#44795](https://github.com/WordPress/gutenberg/pull/44795/))
### Internal
diff --git a/packages/components/src/autocomplete/autocompleter-ui.js b/packages/components/src/autocomplete/autocompleter-ui.js
index f8a9fd08eaf95..13da75123c417 100644
--- a/packages/components/src/autocomplete/autocompleter-ui.js
+++ b/packages/components/src/autocomplete/autocompleter-ui.js
@@ -7,7 +7,7 @@ import { map } from 'lodash';
/**
* WordPress dependencies
*/
-import { useLayoutEffect } from '@wordpress/element';
+import { useLayoutEffect, useRef, useEffect } from '@wordpress/element';
import { useAnchor } from '@wordpress/rich-text';
/**
@@ -31,6 +31,7 @@ export function getAutoCompleterUI( autocompleter ) {
onChangeOptions,
onSelect,
onReset,
+ reset,
value,
contentRef,
} ) {
@@ -40,6 +41,10 @@ export function getAutoCompleterUI( autocompleter ) {
value,
} );
+ const popoverRef = useRef();
+
+ useOnClickOutside( popoverRef, reset );
+
useLayoutEffect( () => {
onChangeOptions( items );
// Temporarily disabling exhaustive-deps to avoid introducing unexpected side effecst.
@@ -58,6 +63,7 @@ export function getAutoCompleterUI( autocompleter ) {
position="top right"
className="components-autocomplete__popover"
anchor={ popoverAnchor }
+ ref={ popoverRef }
>
{
+ const listener = ( event ) => {
+ // Do nothing if clicking ref's element or descendent elements, or if the ref is not referencing an element
+ if ( ! ref.current || ref.current.contains( event.target ) ) {
+ return;
+ }
+ handler( event );
+ };
+ document.addEventListener( 'mousedown', listener );
+ document.addEventListener( 'touchstart', listener );
+ return () => {
+ document.removeEventListener( 'mousedown', listener );
+ document.removeEventListener( 'touchstart', listener );
+ };
+ // Disable reason: `ref` is a ref object and should not be included in a
+ // hook's dependency list.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [ handler ] );
+}
diff --git a/packages/components/src/autocomplete/test/index.js b/packages/components/src/autocomplete/test/index.js
new file mode 100644
index 0000000000000..b33f585ea9d5f
--- /dev/null
+++ b/packages/components/src/autocomplete/test/index.js
@@ -0,0 +1,89 @@
+/**
+ * External dependencies
+ */
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+/**
+ * WordPress dependencies
+ */
+import { useRef } from '@wordpress/element';
+
+/**
+ * Internal dependencies
+ */
+import { getAutoCompleterUI } from '../autocompleter-ui';
+
+describe( 'AutocompleterUI', () => {
+ describe( 'click outside behavior', () => {
+ it( 'should call reset function when a click on another element occurs', async () => {
+ const user = userEvent.setup( {
+ advanceTimers: jest.advanceTimersByTime,
+ } );
+
+ const resetSpy = jest.fn();
+
+ const autocompleter = {
+ name: 'fruit',
+ // The prefix that triggers this completer
+ triggerPrefix: '~',
+ // Mock useItems function to return a autocomplete item.
+ useItems: () => {
+ return [
+ [
+ {
+ isDisabled: false,
+ key: 'Apple',
+ value: 'Apple',
+ label: (
+
+ 🍎
+ { 'Apple' }
+
+ ),
+ },
+ ],
+ ];
+ },
+ };
+
+ const AutocompleterUI = getAutoCompleterUI( autocompleter );
+
+ const OtherElement =
Other Element
;
+
+ const Container = () => {
+ const contentRef = useRef();
+
+ return (
+
+
{} }
+ onSelect={ () => {} }
+ value={ { visual: '🍎', name: 'Apple', id: 1 } }
+ contentRef={ contentRef }
+ reset={ resetSpy }
+ />
+ { OtherElement }
+
+ );
+ };
+
+ render(
);
+
+ // Click on autocompleter.
+ await user.click( screen.getByText( 'Apple' ) );
+
+ expect( resetSpy ).toHaveBeenCalledTimes( 0 );
+
+ // Click on other element out side of the tree.
+ await user.click( screen.getByText( 'Other Element' ) );
+
+ expect( resetSpy ).toHaveBeenCalledTimes( 1 );
+ } );
+ } );
+} );