From a852febf7e500bfe6ec9c840201962a21cee2258 Mon Sep 17 00:00:00 2001
From: mateusz-winiarczyk <mateusz.winiarczyk@appunite.com>
Date: Wed, 27 Mar 2024 09:56:23 +0100
Subject: [PATCH] feat(map): click far away from element still select this
 element (MIN-207)

---
 .../ClearAnchorsButton.component.test.tsx     |  83 +++++
 .../ClearAnchorsButton.component.tsx          |  54 ++++
 .../TopBar/ClearAnchorsButton/index.ts        |   1 +
 .../TopBar/TopBar.component.tsx               |   4 +-
 .../mapRightClick/onMapRightClick.ts          |   2 +-
 .../findClosestBioEntityPoint.test.ts         |  48 +++
 .../findClosestBioEntityPoint.ts              |  29 ++
 .../findClosestReactionPoint.test.ts          |  43 +++
 .../findClosestReactionPoint.ts               |  42 +++
 .../getMaxClickDistance.test.ts               |  25 ++
 .../mapSingleClick/getMaxClickDistance.ts     |  20 ++
 .../mapSingleClick/getSearchResults.test.ts   |  15 +-
 .../mapSingleClick/getSearchResults.ts        |  13 +-
 .../mapSingleClick/handleAliasResults.test.ts | 254 ++++++++++++---
 .../mapSingleClick/handleAliasResults.ts      |  56 +++-
 .../handleReactionResults.test.ts             | 305 ++++++++++++++----
 .../mapSingleClick/handleReactionResults.ts   |  38 ++-
 .../handleReactionSearchClickFailure.ts       |  15 +
 .../handleSearchResultAction.test.ts          |  25 +-
 .../handleSearchResultAction.ts               |  22 +-
 .../mapSingleClick/onMapSingleClick.test.ts   |  65 +++-
 .../mapSingleClick/onMapSingleClick.ts        |   6 +-
 .../utils/listeners/useOlMapListeners.test.ts |  13 +-
 .../utils/listeners/useOlMapListeners.ts      |  24 +-
 src/constants/common.ts                       |   2 +
 src/models/mocks/configurationOptionMock.ts   |  10 +
 .../configuration/configuration.constants.ts  |   1 +
 .../configuration/configuration.selectors.ts  |   6 +
 src/redux/drawer/drawer.constants.ts          |   2 +
 src/redux/drawer/drawer.selectors.ts          |   5 +
 src/redux/map/map.selectors.ts                |   2 +
 .../map/triggerSearch/searchByCoordinates.ts  |  22 +-
 .../map/triggerSearch/triggerSearch.test.ts   |  43 ++-
 src/shared/Icon/Icon.component.tsx            |   2 +
 src/shared/Icon/Icons/ClearIcon.tsx           |  32 ++
 src/types/iconTypes.ts                        |   3 +-
 36 files changed, 1164 insertions(+), 168 deletions(-)
 create mode 100644 src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.test.tsx
 create mode 100644 src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.tsx
 create mode 100644 src/components/FunctionalArea/TopBar/ClearAnchorsButton/index.ts
 create mode 100644 src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.test.ts
 create mode 100644 src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.ts
 create mode 100644 src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.test.ts
 create mode 100644 src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.ts
 create mode 100644 src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.test.ts
 create mode 100644 src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.ts
 create mode 100644 src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionSearchClickFailure.ts
 create mode 100644 src/shared/Icon/Icons/ClearIcon.tsx

diff --git a/src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.test.tsx b/src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.test.tsx
new file mode 100644
index 00000000..0180c8a6
--- /dev/null
+++ b/src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.test.tsx
@@ -0,0 +1,83 @@
+import { AppDispatch, RootState } from '@/redux/store';
+import {
+  InitialStoreState,
+  getReduxStoreWithActionsListener,
+} from '@/utils/testing/getReduxStoreActionsListener';
+import { fireEvent, render, screen } from '@testing-library/react';
+import { act } from 'react-dom/test-utils';
+import { MockStoreEnhanced } from 'redux-mock-store';
+import { DRAWER_INITIAL_STATE } from '@/redux/drawer/drawer.constants';
+import { ClearAnchorsButton } from './ClearAnchorsButton.component';
+
+const renderComponent = (
+  initialStore: InitialStoreState = {},
+): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
+  const { Wrapper, store } = getReduxStoreWithActionsListener(initialStore);
+
+  return (
+    render(
+      <Wrapper>
+        <ClearAnchorsButton />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+describe('ClearAnchorsButton - component', () => {
+  it('should trigger clear actions on clear button click and close result drawer if it is open', () => {
+    const { store } = renderComponent({
+      drawer: {
+        ...DRAWER_INITIAL_STATE,
+        drawerName: 'bio-entity',
+        isOpen: true,
+      },
+    });
+
+    const button = screen.getByTitle('Clear');
+
+    act(() => {
+      fireEvent.click(button);
+    });
+
+    const actions = store.getActions();
+
+    expect(actions).toEqual([
+      { payload: undefined, type: 'drawer/closeDrawer' },
+      { payload: undefined, type: 'contextMenu/closeContextMenu' },
+      { payload: undefined, type: 'reactions/resetReactionsData' },
+      { payload: undefined, type: 'search/clearSearchData' },
+      { payload: undefined, type: 'bioEntityContents/clearBioEntitiesData' },
+      { payload: undefined, type: 'drugs/clearDrugsData' },
+      { payload: undefined, type: 'chemicals/clearChemicalsData' },
+    ]);
+  });
+  it('should trigger clear actions on clear button click and not close result drawer if it is not open', () => {
+    const { store } = renderComponent({
+      drawer: {
+        ...DRAWER_INITIAL_STATE,
+        drawerName: 'bio-entity',
+        isOpen: false,
+      },
+    });
+
+    const button = screen.getByTitle('Clear');
+
+    act(() => {
+      fireEvent.click(button);
+    });
+
+    const actions = store.getActions();
+
+    expect(actions).toEqual([
+      { payload: undefined, type: 'contextMenu/closeContextMenu' },
+      { payload: undefined, type: 'reactions/resetReactionsData' },
+      { payload: undefined, type: 'search/clearSearchData' },
+      { payload: undefined, type: 'bioEntityContents/clearBioEntitiesData' },
+      { payload: undefined, type: 'drugs/clearDrugsData' },
+      { payload: undefined, type: 'chemicals/clearChemicalsData' },
+    ]);
+  });
+});
diff --git a/src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.tsx b/src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.tsx
new file mode 100644
index 00000000..3be4a770
--- /dev/null
+++ b/src/components/FunctionalArea/TopBar/ClearAnchorsButton/ClearAnchorsButton.component.tsx
@@ -0,0 +1,54 @@
+import { clearBioEntitiesData } from '@/redux/bioEntity/bioEntity.slice';
+import { clearChemicalsData } from '@/redux/chemicals/chemicals.slice';
+import { closeContextMenu } from '@/redux/contextMenu/contextMenu.slice';
+import { resultDrawerOpen } from '@/redux/drawer/drawer.selectors';
+import { closeDrawer } from '@/redux/drawer/drawer.slice';
+import { clearDrugsData } from '@/redux/drugs/drugs.slice';
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { resetReactionsData } from '@/redux/reactions/reactions.slice';
+import { clearSearchData } from '@/redux/search/search.slice';
+import { Button } from '@/shared/Button';
+import { Icon } from '@/shared/Icon';
+import React from 'react';
+import { useSelector } from 'react-redux';
+
+export const ClearAnchorsButton = (): React.ReactNode => {
+  const dispatch = useAppDispatch();
+  const isResultDrawerOpen = useSelector(resultDrawerOpen);
+
+  const closeInterfaceElements = (): void => {
+    if (isResultDrawerOpen) {
+      dispatch(closeDrawer());
+    }
+    dispatch(closeContextMenu());
+  };
+
+  const resetData = (): void => {
+    // Reset reactions list to prevent keeping the old selected reaction rendered
+    dispatch(resetReactionsData());
+
+    // Reset search data to prevent invalid filtering of the click-search ()
+    dispatch(clearSearchData());
+
+    // Reset old pins data
+    dispatch(clearBioEntitiesData());
+    dispatch(clearDrugsData());
+    dispatch(clearChemicalsData());
+  };
+
+  const onClearAnchorsClick = (): void => {
+    closeInterfaceElements();
+    resetData();
+  };
+
+  return (
+    <Button
+      className="ml-2 hover:bg-transparent active:bg-transparent"
+      onClick={onClearAnchorsClick}
+      title="Clear"
+      variantStyles="quiet"
+    >
+      <Icon name="clear" />
+    </Button>
+  );
+};
diff --git a/src/components/FunctionalArea/TopBar/ClearAnchorsButton/index.ts b/src/components/FunctionalArea/TopBar/ClearAnchorsButton/index.ts
new file mode 100644
index 00000000..4b04f2cc
--- /dev/null
+++ b/src/components/FunctionalArea/TopBar/ClearAnchorsButton/index.ts
@@ -0,0 +1 @@
+export { ClearAnchorsButton } from './ClearAnchorsButton.component';
diff --git a/src/components/FunctionalArea/TopBar/TopBar.component.tsx b/src/components/FunctionalArea/TopBar/TopBar.component.tsx
index 243ededa..0f94e63c 100644
--- a/src/components/FunctionalArea/TopBar/TopBar.component.tsx
+++ b/src/components/FunctionalArea/TopBar/TopBar.component.tsx
@@ -3,6 +3,7 @@ import { UserAvatar } from '@/components/FunctionalArea/TopBar/UserAvatar';
 import { openOverlaysDrawer, openSubmapsDrawer } from '@/redux/drawer/drawer.slice';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { Button } from '@/shared/Button';
+import { ClearAnchorsButton } from './ClearAnchorsButton';
 
 export const TopBar = (): JSX.Element => {
   const dispatch = useAppDispatch();
@@ -20,11 +21,12 @@ export const TopBar = (): JSX.Element => {
       <div className="flex flex-row items-center">
         <UserAvatar />
         <SearchBar />
+        <ClearAnchorsButton />
         <Button
           icon="plus"
           isIcon
           isFrontIcon
-          className="ml-8 mr-4"
+          className="ml-5 mr-4"
           onClick={onSubmapsClick}
           title="Submaps"
         >
diff --git a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts b/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts
index a957771e..2288c0c6 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts
@@ -14,7 +14,7 @@ export const onMapRightClick =
     dispatch(handleDataReset);
     dispatch(openContextMenu(pixel));
 
-    const searchResults = await getSearchResults({ coordinate, mapSize, modelId });
+    const { searchResults } = await getSearchResults({ coordinate, mapSize, modelId });
     if (!searchResults || searchResults.length === SIZE_OF_EMPTY_ARRAY) {
       return;
     }
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.test.ts
new file mode 100644
index 00000000..7dcf389a
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.test.ts
@@ -0,0 +1,48 @@
+/* eslint-disable no-magic-numbers */
+import { bioEntityContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
+import { findClosestBioEntityPoint } from './findClosestBioEntityPoint';
+
+describe('findClosestBioEntityPoint', () => {
+  const bioEntityContents = [
+    {
+      ...bioEntityContentFixture,
+      bioEntity: { ...bioEntityContentFixture.bioEntity, x: 10, y: 10, width: 20, height: 20 },
+    },
+    {
+      ...bioEntityContentFixture,
+      bioEntity: { ...bioEntityContentFixture.bioEntity, x: 50, y: 50, width: 30, height: 30 },
+    },
+  ];
+
+  const validPoint = { x: 15, y: 15 };
+  const invalidPoint = {
+    x: 500,
+    y: 300,
+  };
+
+  const searchDistance = '10';
+  const maxZoom = 5;
+  const zoom = 2;
+
+  it('should return the closest bioEntity within the search distance', () => {
+    const result = findClosestBioEntityPoint(
+      bioEntityContents,
+      searchDistance,
+      maxZoom,
+      zoom,
+      validPoint,
+    );
+    expect(result).toEqual(bioEntityContents[0]);
+  });
+
+  it('should return undefined if no matching bioEntity is found within the search distance', () => {
+    const result = findClosestBioEntityPoint(
+      bioEntityContents,
+      searchDistance,
+      maxZoom,
+      zoom,
+      invalidPoint,
+    );
+    expect(result).toBeUndefined();
+  });
+});
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.ts
new file mode 100644
index 00000000..c7150b50
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestBioEntityPoint.ts
@@ -0,0 +1,29 @@
+import { Point as PointType } from '@/types/map';
+import { BioEntityContent } from '@/types/models';
+import { getMaxClickDistance } from './getMaxClickDistance';
+
+export const findClosestBioEntityPoint = (
+  bioEntityContents: BioEntityContent[],
+  searchDistance: string,
+  maxZoom: number,
+  zoom: number,
+  point: PointType,
+): BioEntityContent | undefined => {
+  const maxDistance = getMaxClickDistance(maxZoom, zoom, searchDistance);
+
+  const matchingBioEntityFound = bioEntityContents.find(bio => {
+    const { x, y, width, height } = bio.bioEntity;
+
+    const minX = x - maxDistance;
+    const maxX = x + width + maxDistance;
+    const minY = y - maxDistance;
+    const maxY = y + height + maxDistance;
+
+    const withinXRange = point.x >= minX && point.x <= maxX;
+    const withinYRange = point.y >= minY && point.y <= maxY;
+
+    return withinXRange && withinYRange;
+  });
+
+  return matchingBioEntityFound;
+};
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.test.ts
new file mode 100644
index 00000000..b318ffc1
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.test.ts
@@ -0,0 +1,43 @@
+/* eslint-disable no-magic-numbers */
+import { reactionsFixture } from '@/models/fixtures/reactionFixture';
+import { findClosestReactionPoint } from './findClosestReactionPoint';
+
+describe('findClosestReactionPoint', () => {
+  const reaction = {
+    ...reactionsFixture[0],
+    lines: [{ start: { x: 0, y: 0 }, end: { x: 3, y: 4 }, type: 'START' }],
+  };
+
+  const validPoint = { x: 1, y: 1 };
+  const invalidPoint = {
+    x: 1115,
+    y: 2225,
+  };
+  const searchDistance = '10';
+  const maxZoom = 10;
+  const zoom = 5;
+
+  it('should return the matching line segment if a point within the search distance is found', () => {
+    const result = findClosestReactionPoint({
+      reaction,
+      searchDistance,
+      maxZoom,
+      zoom,
+      point: validPoint,
+    });
+
+    expect(result).toEqual(reaction.lines[0]);
+  });
+
+  it('should return undefined if no point within the search distance is found', () => {
+    const result = findClosestReactionPoint({
+      reaction,
+      searchDistance,
+      maxZoom,
+      zoom,
+      point: invalidPoint,
+    });
+
+    expect(result).toBeUndefined();
+  });
+});
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.ts
new file mode 100644
index 00000000..c29fc89a
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/findClosestReactionPoint.ts
@@ -0,0 +1,42 @@
+/* eslint-disable no-magic-numbers */
+import { Point as PointType } from '@/types/map';
+import { Reaction } from '@/types/models';
+import { distance } from 'ol/coordinate';
+import { LineString, Point } from 'ol/geom';
+import { getMaxClickDistance } from './getMaxClickDistance';
+
+type ReactionLine = Reaction['lines'][0];
+
+type FindClosestReactionArgs = {
+  reaction: Reaction;
+  searchDistance: string;
+  maxZoom: number;
+  zoom: number;
+  point: PointType;
+};
+
+export const findClosestReactionPoint = ({
+  reaction,
+  searchDistance,
+  maxZoom,
+  zoom,
+  point,
+}: FindClosestReactionArgs): ReactionLine | undefined => {
+  const maxDistance = getMaxClickDistance(maxZoom, zoom, searchDistance);
+
+  const clickedPoint = new Point([point.x, point.y]);
+
+  const closestLine = reaction.lines.find(line => {
+    const lineString = new LineString([
+      [line.start.x, line.start.y],
+      [line.end.x, line.end.y],
+    ]);
+
+    const closestPointOnLine = lineString.getClosestPoint(clickedPoint.getCoordinates());
+    const distanceToLine = distance(closestPointOnLine, clickedPoint.getCoordinates());
+
+    return distanceToLine <= maxDistance;
+  });
+
+  return closestLine;
+};
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.test.ts
new file mode 100644
index 00000000..35711a82
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.test.ts
@@ -0,0 +1,25 @@
+/* eslint-disable no-magic-numbers */
+import { getMaxClickDistance } from './getMaxClickDistance';
+
+describe('getMaxClickDistance', () => {
+  it.each([
+    [10, 5, '10', 320],
+    [10, 5, '20', 640],
+    [10, 2, '10', 2560],
+    [10, 3, '18', 2304],
+  ])(
+    'should calculate the maximum click distance correctly',
+    (maxZoom, zoom, searchDistance, expected) => {
+      expect(getMaxClickDistance(maxZoom, zoom, searchDistance)).toBe(expected);
+    },
+  );
+
+  it.each([['invalid'], [''], [' ']])(
+    'should throw an error if the search distance "%s" is not a valid number',
+    invalidDistance => {
+      expect(() => getMaxClickDistance(10, 5, invalidDistance)).toThrow(
+        'Invalid search distance. Please provide a valid number.',
+      );
+    },
+  );
+});
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.ts
new file mode 100644
index 00000000..bc02c450
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getMaxClickDistance.ts
@@ -0,0 +1,20 @@
+/* eslint-disable no-magic-numbers */
+
+import { ZOOM_FACTOR } from '@/constants/common';
+
+export const getMaxClickDistance = (
+  maxZoom: number,
+  zoom: number,
+  searchDistance: string,
+): number => {
+  const distance = parseFloat(searchDistance);
+
+  if (typeof distance !== 'number' || Number.isNaN(distance)) {
+    throw new Error('Invalid search distance. Please provide a valid number.');
+  }
+
+  const zoomDiff = maxZoom - zoom;
+  const maxDistance = distance * ZOOM_FACTOR ** zoomDiff;
+
+  return maxDistance;
+};
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts
index 5533b62d..996d2d1c 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts
@@ -36,7 +36,10 @@ describe('getSearchResults - util', () => {
         modelId,
       });
 
-      expect(result).toEqual([ELEMENT_SEARCH_RESULT_MOCK_ALIAS]);
+      expect(result).toEqual({
+        point,
+        searchResults: [ELEMENT_SEARCH_RESULT_MOCK_ALIAS],
+      });
     });
   });
 
@@ -65,7 +68,10 @@ describe('getSearchResults - util', () => {
         modelId,
       });
 
-      expect(result).toEqual([ELEMENT_SEARCH_RESULT_MOCK_REACTION]);
+      expect(result).toEqual({
+        point,
+        searchResults: [ELEMENT_SEARCH_RESULT_MOCK_REACTION],
+      });
     });
   });
 
@@ -96,7 +102,10 @@ describe('getSearchResults - util', () => {
         modelId,
       });
 
-      expect(result).toEqual(undefined);
+      expect(result).toEqual({
+        point,
+        searchResults: undefined,
+      });
     });
   });
 });
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts
index df376d4a..0fedae9f 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts
@@ -1,4 +1,5 @@
 import { MapSize } from '@/redux/map/map.types';
+import { Point } from '@/types/map';
 import { ElementSearchResult } from '@/types/models';
 import { latLngToPoint } from '@/utils/map/latLngToPoint';
 import { getElementsByPoint } from '@/utils/search/getElementsByCoordinates';
@@ -15,8 +16,16 @@ export const getSearchResults = async ({
   coordinate,
   mapSize,
   modelId,
-}: GetSearchResultsInput): Promise<ElementSearchResult[] | undefined> => {
+}: GetSearchResultsInput): Promise<{
+  searchResults: ElementSearchResult[] | undefined;
+  point: Point;
+}> => {
   const [lng, lat] = toLonLat(coordinate);
   const point = latLngToPoint([lat, lng], mapSize);
-  return getElementsByPoint({ point, currentModelId: modelId });
+  const searchResults = await getElementsByPoint({ point, currentModelId: modelId });
+
+  return {
+    searchResults,
+    point,
+  };
 };
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts
index 088aa80f..54ead44c 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.test.ts
@@ -1,61 +1,235 @@
-import {
-  FIRST_ARRAY_ELEMENT,
-  SECOND_ARRAY_ELEMENT,
-  SIZE_OF_EMPTY_ARRAY,
-  THIRD_ARRAY_ELEMENT,
-} from '@/constants/common';
+/* eslint-disable no-magic-numbers */
+import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
 import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture';
 import { ELEMENT_SEARCH_RESULT_MOCK_ALIAS } from '@/models/mocks/elementSearchResultMock';
 import { apiPath } from '@/redux/apiPath';
-import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
+import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
 import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
 import { waitFor } from '@testing-library/react';
 import { HttpStatusCode } from 'axios';
+import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
 import { handleAliasResults } from './handleAliasResults';
 
-const mockedAxiosOldClient = mockNetworkResponse();
+jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds');
+
+const mockedAxiosClient = mockNetworkNewAPIResponse();
+
+const SEARCH_CONFIG_MOCK = {
+  point: {
+    x: 500,
+    y: 700,
+  },
+  maxZoom: 9,
+  zoom: 5,
+  isResultDrawerOpen: false,
+};
 
 describe('handleAliasResults - util', () => {
-  const { store } = getReduxStoreWithActionsListener();
-  const { dispatch } = store;
-
-  mockedAxiosOldClient
-    .onGet(
-      apiPath.getBioEntityContentsStringWithQuery({
-        searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
-        isPerfectMatch: true,
-      }),
-    )
-    .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
-
-  beforeAll(async () => {
-    handleAliasResults(
-      dispatch,
-      ELEMENT_SEARCH_RESULT_MOCK_ALIAS,
-    )(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+  beforeEach(() => {
+    jest.clearAllMocks();
   });
+  describe('when matching bioEntity not found', () => {
+    it('should clear bio entities and do not close drawer if result drawer is not open', async () => {
+      mockedAxiosClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+            isPerfectMatch: true,
+          }),
+        )
+        .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+      const { store } = getReduxStoreWithActionsListener();
+      const { dispatch } = store;
+
+      await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, {
+        ...SEARCH_CONFIG_MOCK,
+        searchDistance: '10',
+      })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+
+      await waitFor(() => {
+        const actions = store.getActions();
+        expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+
+        const actionTypes = actions.map(action => action.type);
+
+        expect(actionTypes).toEqual([
+          'project/getMultiBioEntity/pending',
+          'project/getBioEntityContents/pending',
+          'project/getBioEntityContents/fulfilled',
+          'entityNumber/addNumbersToEntityNumberData',
+          'project/getMultiBioEntity/fulfilled',
+          'bioEntityContents/clearBioEntitiesData',
+        ]);
+      });
+    });
+    it('should clear bio entities and close drawer if result drawer is already open', async () => {
+      mockedAxiosClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+            isPerfectMatch: true,
+          }),
+        )
+        .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+      const { store } = getReduxStoreWithActionsListener();
+      const { dispatch } = store;
+
+      await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, {
+        ...SEARCH_CONFIG_MOCK,
+        searchDistance: '10',
+        isResultDrawerOpen: true,
+      })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+
+      await waitFor(() => {
+        const actions = store.getActions();
+        expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
 
-  it('should run selectTab as first action', async () => {
-    await waitFor(() => {
-      const actions = store.getActions();
-      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-      expect(actions[FIRST_ARRAY_ELEMENT].type).toEqual('drawer/selectTab');
+        const actionTypes = actions.map(action => action.type);
+
+        expect(actionTypes).toEqual([
+          'project/getMultiBioEntity/pending',
+          'project/getBioEntityContents/pending',
+          'project/getBioEntityContents/fulfilled',
+          'entityNumber/addNumbersToEntityNumberData',
+          'project/getMultiBioEntity/fulfilled',
+          'drawer/closeDrawer',
+          'bioEntityContents/clearBioEntitiesData',
+        ]);
+      });
     });
   });
+  describe('when matching bioEntity found', () => {
+    it('should select tab and open bio entity drawer', async () => {
+      mockedAxiosClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+            isPerfectMatch: true,
+          }),
+        )
+        .reply(HttpStatusCode.Ok, {
+          ...bioEntityResponseFixture,
+          content: [
+            {
+              ...bioEntityResponseFixture.content[0],
+              bioEntity: {
+                ...bioEntityResponseFixture.content[0].bioEntity,
+                x: 500,
+                y: 700,
+                width: 50,
+                height: 50,
+              },
+            },
+          ],
+        });
+      const { store } = getReduxStoreWithActionsListener();
+      const { dispatch } = store;
+
+      await handleAliasResults(
+        dispatch,
+        ELEMENT_SEARCH_RESULT_MOCK_ALIAS,
+        SEARCH_CONFIG_MOCK,
+      )(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+
+      await waitFor(() => {
+        const actions = store.getActions();
+        expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
 
-  it('should run openBioEntityDrawerById as second action', async () => {
-    await waitFor(() => {
-      const actions = store.getActions();
-      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-      expect(actions[SECOND_ARRAY_ELEMENT].type).toEqual('drawer/openBioEntityDrawerById');
+        const actionTypes = actions.map(action => action.type);
+
+        expect(actionTypes).toEqual([
+          'project/getMultiBioEntity/pending',
+          'project/getBioEntityContents/pending',
+          'project/getBioEntityContents/fulfilled',
+          'entityNumber/addNumbersToEntityNumberData',
+          'project/getMultiBioEntity/fulfilled',
+          'drawer/selectTab',
+          'drawer/openBioEntityDrawerById',
+        ]);
+      });
     });
   });
 
-  it('should run getMultiBioEntity as third action', async () => {
-    await waitFor(() => {
-      const actions = store.getActions();
-      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-      expect(actions[THIRD_ARRAY_ELEMENT].type).toEqual('project/getMultiBioEntity/pending');
+  describe('when searchDistance is not provided', () => {
+    it('should select tab and open drawer without clearing bio entities', async () => {
+      mockedAxiosClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+            isPerfectMatch: true,
+          }),
+        )
+        .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+      const { store } = getReduxStoreWithActionsListener();
+      const { dispatch } = store;
+
+      await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, {
+        ...SEARCH_CONFIG_MOCK,
+        isResultDrawerOpen: true,
+      })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+
+      await waitFor(() => {
+        const actions = store.getActions();
+        expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+
+        const actionTypes = actions.map(action => action.type);
+
+        expect(actionTypes).toEqual([
+          'project/getMultiBioEntity/pending',
+          'project/getBioEntityContents/pending',
+          'project/getBioEntityContents/fulfilled',
+          'entityNumber/addNumbersToEntityNumberData',
+          'project/getMultiBioEntity/fulfilled',
+          'drawer/selectTab',
+          'drawer/openBioEntityDrawerById',
+        ]);
+      });
+    });
+
+    describe('fitBounds after search', () => {
+      it('should fit bounds after search when hasFitBounds is true', async () => {
+        mockedAxiosClient
+          .onGet(
+            apiPath.getBioEntityContentsStringWithQuery({
+              searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+              isPerfectMatch: true,
+            }),
+          )
+          .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+        const { store } = getReduxStoreWithActionsListener();
+        const { dispatch } = store;
+
+        await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, {
+          ...SEARCH_CONFIG_MOCK,
+          hasFitBounds: true,
+        })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+
+        await waitFor(() => {
+          expect(searchFitBounds).toHaveBeenCalled();
+        });
+      });
+
+      it('should not fit bounds after search when hasFitBounds is false', async () => {
+        mockedAxiosClient
+          .onGet(
+            apiPath.getBioEntityContentsStringWithQuery({
+              searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+              isPerfectMatch: true,
+            }),
+          )
+          .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+        const { store } = getReduxStoreWithActionsListener();
+        const { dispatch } = store;
+
+        await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, {
+          ...SEARCH_CONFIG_MOCK,
+          hasFitBounds: false,
+        })(ELEMENT_SEARCH_RESULT_MOCK_ALIAS);
+
+        await waitFor(() => {
+          expect(searchFitBounds).not.toHaveBeenCalled();
+        });
+      });
     });
   });
 });
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts
index bab0bd62..cd8fc849 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleAliasResults.ts
@@ -1,33 +1,57 @@
 import { getMultiBioEntity } from '@/redux/bioEntity/bioEntity.thunks';
-import { openBioEntityDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
+import { closeDrawer, openBioEntityDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
 import { AppDispatch } from '@/redux/store';
 import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
 import { ElementSearchResult } from '@/types/models';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { clearBioEntitiesData } from '@/redux/bioEntity/bioEntity.slice';
+import { Point } from '@/types/map';
+import { findClosestBioEntityPoint } from './findClosestBioEntityPoint';
 
+type SearchConfig = {
+  point: Point;
+  searchDistance?: string;
+  maxZoom: number;
+  zoom: number;
+  hasFitBounds?: boolean;
+  isResultDrawerOpen?: boolean;
+};
 /* prettier-ignore */
 export const handleAliasResults =
-  (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, hasFitBounds?: boolean, fitBoundsZoom?: number) =>
+  (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, { hasFitBounds, maxZoom, point, searchDistance, zoom, isResultDrawerOpen }: SearchConfig) =>
     async ({ id }: ElementSearchResult): Promise<void> => {
-
-      dispatch(selectTab(`${id}`));
-      dispatch(openBioEntityDrawerById(id));
-      dispatch(
+      const bioEntityContents = await dispatch(
         getMultiBioEntity({
           searchQueries: [id.toString()],
           isPerfectMatch: true
         }),
-      )
-        .unwrap().then((bioEntityContents) => {
+      ).unwrap();
 
-          PluginsEventBus.dispatchEvent('onSearch', {
-            type: 'bioEntity',
-            searchValues: [closestSearchResult],
-            results: [bioEntityContents],
-          });
+      if (searchDistance) {
 
-          if (hasFitBounds) {
-            searchFitBounds(fitBoundsZoom);
+        const matchingBioEntityFound = findClosestBioEntityPoint(bioEntityContents, searchDistance, maxZoom, zoom, point);
+
+        if (!matchingBioEntityFound) {
+          if (isResultDrawerOpen) {
+            dispatch(closeDrawer());
           }
-        });
+
+          dispatch(clearBioEntitiesData());
+          return;
+        }
+      }
+
+      dispatch(selectTab(`${id}`));
+      dispatch(openBioEntityDrawerById(id));
+
+
+      PluginsEventBus.dispatchEvent('onSearch', {
+        type: 'bioEntity',
+        searchValues: [closestSearchResult],
+        results: [bioEntityContents],
+      });
+
+      if (hasFitBounds) {
+        searchFitBounds();
+      }
     };
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts
index 28974241..00513517 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.test.ts
@@ -12,89 +12,258 @@ import { mockNetworkNewAPIResponse, mockNetworkResponse } from '@/utils/mockNetw
 import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
 import { HttpStatusCode } from 'axios';
 import { handleReactionResults } from './handleReactionResults';
+import * as findClosestReactionPoint from './findClosestReactionPoint';
 
 const mockedAxiosOldClient = mockNetworkResponse();
 const mockedAxiosNewClient = mockNetworkNewAPIResponse();
 
+jest.mock('./findClosestReactionPoint', () => ({
+  __esModule: true,
+  ...jest.requireActual('./findClosestReactionPoint'),
+}));
+
+const findClosestReactionPointSpy = jest.spyOn(
+  findClosestReactionPoint,
+  'findClosestReactionPoint',
+);
+
+const SEARCH_CONFIG_MOCK = {
+  point: {
+    x: 200,
+    y: 3012,
+  },
+  maxZoom: 9,
+  zoom: 3,
+  isResultDrawerOpen: false,
+};
+
 describe('handleReactionResults - util', () => {
-  const { store } = getReduxStoreWithActionsListener({
-    ...INITIAL_STORE_STATE_MOCK,
-  });
-  const { dispatch } = store;
-
-  mockedAxiosNewClient
-    .onGet(
-      apiPath.getBioEntityContentsStringWithQuery({
-        searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
-        isPerfectMatch: true,
-      }),
-    )
-    .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
-
-  mockedAxiosOldClient
-    .onGet(apiPath.getReactionsWithIds([ELEMENT_SEARCH_RESULT_MOCK_REACTION.id]))
-    .reply(HttpStatusCode.Ok, [
-      {
-        ...reactionsFixture[0],
-        reactants: [],
-        products: [],
-        modifiers: [
-          {
-            ...reactionsFixture[0].modifiers[0],
-            aliasId: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id,
-          },
-        ],
-      },
-    ]);
-
-  beforeEach(async () => {
-    await handleReactionResults(
-      dispatch,
-      ELEMENT_SEARCH_RESULT_MOCK_REACTION,
-    )(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
-  });
+  const searchDistance = '10';
 
-  it('should run getReactionsByIds as first action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[0].type).toEqual('reactions/getByIds/pending');
-    expect(actions[1].type).toEqual('reactions/getByIds/fulfilled');
+  beforeEach(() => {
+    jest.clearAllMocks();
   });
 
-  it('should run openReactionDrawerById to empty array as third action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[2].type).toEqual('drawer/openReactionDrawerById');
-    expect(actions[2].payload).toEqual(reactionsFixture[FIRST_ARRAY_ELEMENT].id);
-  });
+  describe('actions', () => {
+    const { store } = getReduxStoreWithActionsListener({
+      ...INITIAL_STORE_STATE_MOCK,
+    });
+    const { dispatch } = store;
 
-  it('should run select tab as fourth action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[3].type).toEqual('drawer/selectTab');
-  });
+    mockedAxiosNewClient
+      .onGet(
+        apiPath.getBioEntityContentsStringWithQuery({
+          searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+          isPerfectMatch: true,
+        }),
+      )
+      .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+
+    mockedAxiosOldClient
+      .onGet(apiPath.getReactionsWithIds([ELEMENT_SEARCH_RESULT_MOCK_REACTION.id]))
+      .reply(HttpStatusCode.Ok, [
+        {
+          ...reactionsFixture[0],
+          reactants: [],
+          products: [],
+          modifiers: [
+            {
+              ...reactionsFixture[0].modifiers[0],
+              aliasId: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id,
+            },
+          ],
+        },
+      ]);
 
-  it('should run getMultiBioEntity to empty array as fifth action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[4].type).toEqual('project/getMultiBioEntity/pending');
+    beforeEach(async () => {
+      await handleReactionResults(
+        dispatch,
+        ELEMENT_SEARCH_RESULT_MOCK_REACTION,
+      )(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
+    });
+
+    it('should run getReactionsByIds as first action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[0].type).toEqual('reactions/getByIds/pending');
+      expect(actions[1].type).toEqual('reactions/getByIds/fulfilled');
+    });
+
+    it('should run openReactionDrawerById to empty array as third action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[2].type).toEqual('drawer/openReactionDrawerById');
+      expect(actions[2].payload).toEqual(reactionsFixture[FIRST_ARRAY_ELEMENT].id);
+    });
+
+    it('should run select tab as fourth action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[3].type).toEqual('drawer/selectTab');
+    });
+
+    it('should run getMultiBioEntity to empty array as fifth action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[4].type).toEqual('project/getMultiBioEntity/pending');
+    });
+
+    it('should run getBioEntityContents as sixth action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[5].type).toEqual('project/getBioEntityContents/pending');
+    });
+
+    it('should run getBioEntityContents fullfilled as seventh action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[6].type).toEqual('project/getBioEntityContents/fulfilled');
+    });
+
+    it('should run addNumbersToEntityNumberData as eighth action', () => {
+      const actions = store.getActions();
+      expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
+      expect(actions[7].type).toEqual('entityNumber/addNumbersToEntityNumberData');
+    });
   });
+  describe('when search config provided but search distance is not provided', () => {
+    const { store } = getReduxStoreWithActionsListener();
+    const { dispatch } = store;
+
+    it('should not find closest reaction', async () => {
+      await handleReactionResults(
+        dispatch,
+        ELEMENT_SEARCH_RESULT_MOCK_REACTION,
+        SEARCH_CONFIG_MOCK,
+      )(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
 
-  it('should run getBioEntityContents as sixth action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[5].type).toEqual('project/getBioEntityContents/pending');
+      expect(findClosestReactionPointSpy).not.toHaveBeenCalled();
+    });
   });
 
-  it('should run getBioEntityContents fullfilled as seventh action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[6].type).toEqual('project/getBioEntityContents/fulfilled');
+  describe('when search config provided and matching reaction not found', () => {
+    mockedAxiosNewClient
+      .onGet(
+        apiPath.getBioEntityContentsStringWithQuery({
+          searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+          isPerfectMatch: true,
+        }),
+      )
+      .reply(HttpStatusCode.Ok, bioEntityResponseFixture);
+
+    mockedAxiosOldClient
+      .onGet(apiPath.getReactionsWithIds([ELEMENT_SEARCH_RESULT_MOCK_REACTION.id]))
+      .reply(HttpStatusCode.Ok, [
+        {
+          ...reactionsFixture[0],
+          reactants: [],
+          products: [],
+          modifiers: [
+            {
+              ...reactionsFixture[0].modifiers[0],
+              aliasId: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id,
+            },
+          ],
+        },
+      ]);
+
+    const invalidPoint = {
+      x: 991,
+      y: 612,
+    };
+    it('should close drawer and reset data if result drawer open', async () => {
+      const { store } = getReduxStoreWithActionsListener();
+
+      await handleReactionResults(store.dispatch, ELEMENT_SEARCH_RESULT_MOCK_REACTION, {
+        ...SEARCH_CONFIG_MOCK,
+        searchDistance,
+        point: invalidPoint,
+        isResultDrawerOpen: true,
+      })(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
+
+      const actions = store.getActions();
+      const acionTypes = actions.map(action => action.type);
+
+      expect(acionTypes).toStrictEqual([
+        'reactions/getByIds/pending',
+        'reactions/getByIds/fulfilled',
+        'drawer/closeDrawer',
+        'reactions/resetReactionsData',
+        'bioEntityContents/clearBioEntitiesData',
+      ]);
+    });
+
+    it('should only reset data if result drawer is closed', async () => {
+      const { store } = getReduxStoreWithActionsListener();
+
+      const dispatchSpy = jest.spyOn(store, 'dispatch');
+
+      await handleReactionResults(store.dispatch, ELEMENT_SEARCH_RESULT_MOCK_REACTION, {
+        ...SEARCH_CONFIG_MOCK,
+        searchDistance,
+        point: invalidPoint,
+        isResultDrawerOpen: false,
+      })(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
+
+      expect(dispatchSpy).toHaveBeenCalledWith({
+        payload: undefined,
+        type: 'reactions/resetReactionsData',
+      });
+
+      expect(dispatchSpy).toHaveBeenCalledWith({
+        payload: undefined,
+        type: 'bioEntityContents/clearBioEntitiesData',
+      });
+    });
   });
+  describe('when search config provided and matching reaction found', () => {
+    const reaction = {
+      ...reactionsFixture[0],
+      products: [],
+      reactants: [],
+      modifiers: [],
+      lines: [{ start: { x: 0, y: 0 }, end: { x: 3, y: 4 }, type: 'START' }],
+    };
+
+    const point = { x: 1, y: 1 };
+    const maxZoom = 10;
+    const zoom = 5;
+
+    it('should open reaction drawer and fetch bio entities', async () => {
+      const { store } = getReduxStoreWithActionsListener();
+      mockedAxiosNewClient
+        .onGet(
+          apiPath.getBioEntityContentsStringWithQuery({
+            searchQuery: ELEMENT_SEARCH_RESULT_MOCK_ALIAS.id.toString(),
+            isPerfectMatch: true,
+          }),
+        )
+        .reply(HttpStatusCode.Ok, []);
+
+      mockedAxiosOldClient
+        .onGet(apiPath.getReactionsWithIds([ELEMENT_SEARCH_RESULT_MOCK_REACTION.id]))
+        .reply(HttpStatusCode.Ok, [reaction]);
+
+      await handleReactionResults(store.dispatch, ELEMENT_SEARCH_RESULT_MOCK_REACTION, {
+        searchDistance,
+        maxZoom,
+        zoom,
+        point,
+        isResultDrawerOpen: false,
+      })(ELEMENT_SEARCH_RESULT_MOCK_REACTION);
+
+      const actions = store.getActions();
+      const acionTypes = actions.map(action => action.type);
 
-  it('should run addNumbersToEntityNumberData as eighth action', () => {
-    const actions = store.getActions();
-    expect(actions.length).toBeGreaterThan(SIZE_OF_EMPTY_ARRAY);
-    expect(actions[7].type).toEqual('entityNumber/addNumbersToEntityNumberData');
+      expect(acionTypes).toStrictEqual([
+        'reactions/getByIds/pending',
+        'reactions/getByIds/fulfilled',
+        'drawer/openReactionDrawerById',
+        'drawer/selectTab',
+        'project/getMultiBioEntity/pending',
+        'entityNumber/addNumbersToEntityNumberData',
+        'project/getMultiBioEntity/fulfilled',
+      ]);
+    });
   });
 });
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts
index bee6326b..c9875710 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionResults.ts
@@ -3,14 +3,26 @@ import { getMultiBioEntity } from '@/redux/bioEntity/bioEntity.thunks';
 import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
 import { getReactionsByIds } from '@/redux/reactions/reactions.thunks';
 import { AppDispatch } from '@/redux/store';
-import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
-import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
 import { ElementSearchResult, Reaction } from '@/types/models';
 import { PayloadAction } from '@reduxjs/toolkit';
+import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { Point } from '@/types/map';
+import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
+import { findClosestReactionPoint } from './findClosestReactionPoint';
+import { handleReactionSearchClickFailure } from './handleReactionSearchClickFailure';
+
+type SearchConfig = {
+  point: Point;
+  searchDistance?: string;
+  maxZoom: number;
+  zoom: number;
+  hasFitBounds?: boolean;
+  isResultDrawerOpen: boolean;
+};
 
 /* prettier-ignore */
 export const handleReactionResults =
-  (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, hasFitBounds?: boolean, fitBoundsZoom?: number) =>
+  (dispatch: AppDispatch, closestSearchResult: ElementSearchResult, searchConfig?: SearchConfig) =>
     async ({ id }: ElementSearchResult): Promise<void> => {
       const data = await dispatch(getReactionsByIds([id])) as PayloadAction<Reaction[] | undefined>;
       const payload = data?.payload;
@@ -19,6 +31,20 @@ export const handleReactionResults =
       }
 
       const reaction = payload[FIRST_ARRAY_ELEMENT];
+
+      if (searchConfig && searchConfig.searchDistance) {
+        const { maxZoom, point, searchDistance, zoom, isResultDrawerOpen } = searchConfig;
+        const matchingReactionFound = findClosestReactionPoint({
+          reaction, searchDistance, maxZoom, zoom, point
+        });
+
+        if (!matchingReactionFound) {
+          handleReactionSearchClickFailure(dispatch, isResultDrawerOpen);
+
+          return;
+        }
+      }
+
       const { products, reactants, modifiers } = reaction;
       const productsIds = products.map(p => p.aliasId);
       const reactantsIds = reactants.map(r => r.aliasId);
@@ -26,6 +52,7 @@ export const handleReactionResults =
       const bioEntitiesIds = [...productsIds, ...reactantsIds, ...modifiersIds].map(identifier => String(identifier));
 
       dispatch(openReactionDrawerById(reaction.id));
+
       dispatch(selectTab(''));
       await dispatch(
         getMultiBioEntity({
@@ -39,8 +66,9 @@ export const handleReactionResults =
           results: [bioEntityContents],
         });
 
-        if (hasFitBounds) {
-          searchFitBounds(fitBoundsZoom);
+        if (searchConfig && searchConfig.hasFitBounds) {
+          searchFitBounds();
         }
       });
+
     };
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionSearchClickFailure.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionSearchClickFailure.ts
new file mode 100644
index 00000000..7368bb18
--- /dev/null
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleReactionSearchClickFailure.ts
@@ -0,0 +1,15 @@
+import { AppDispatch } from '@/redux/store';
+import { closeDrawer } from '@/redux/drawer/drawer.slice';
+import { resetReactionsData } from '@/redux/reactions/reactions.slice';
+import { clearBioEntitiesData } from '@/redux/bioEntity/bioEntity.slice';
+
+export const handleReactionSearchClickFailure = (
+  dispatch: AppDispatch,
+  isResultDrawerOpen: boolean,
+): void => {
+  if (isResultDrawerOpen) {
+    dispatch(closeDrawer());
+  }
+  dispatch(resetReactionsData());
+  dispatch(clearBioEntitiesData());
+};
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.test.ts
index 84925396..d07fe1be 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.test.ts
@@ -19,6 +19,13 @@ jest.mock('./handleReactionResults', () => ({
 const handleAliasResultsSpy = jest.spyOn(handleAliasResults, 'handleAliasResults');
 const handleReactionResultsSpy = jest.spyOn(handleReactionResults, 'handleReactionResults');
 
+const POINT_MOCK = {
+  x: 1323,
+  y: 2000,
+};
+const ZOOM_MOCK = 3;
+const MAX_ZOOM_MOCK = 9;
+
 describe('handleSearchResultAction - util', () => {
   const dispatch = jest.fn();
 
@@ -30,7 +37,14 @@ describe('handleSearchResultAction - util', () => {
     const searchResults = [ELEMENT_SEARCH_RESULT_MOCK_ALIAS];
 
     it('should fire handleAliasResults', async () => {
-      await handleSearchResultAction({ searchResults, dispatch });
+      await handleSearchResultAction({
+        searchResults,
+        dispatch,
+        maxZoom: MAX_ZOOM_MOCK,
+        isResultDrawerOpen: false,
+        point: POINT_MOCK,
+        zoom: ZOOM_MOCK,
+      });
       expect(handleAliasResultsSpy).toBeCalled();
     });
   });
@@ -39,7 +53,14 @@ describe('handleSearchResultAction - util', () => {
     const searchResults = [ELEMENT_SEARCH_RESULT_MOCK_REACTION];
 
     it('should fire handleReactionResults', async () => {
-      await handleSearchResultAction({ searchResults, dispatch });
+      await handleSearchResultAction({
+        searchResults,
+        dispatch,
+        maxZoom: MAX_ZOOM_MOCK,
+        isResultDrawerOpen: false,
+        point: POINT_MOCK,
+        zoom: ZOOM_MOCK,
+      });
       expect(handleReactionResultsSpy).toBeCalled();
     });
   });
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts
index 39dea100..f85813b1 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction.ts
@@ -2,21 +2,30 @@ import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
 import { AppDispatch } from '@/redux/store';
 import { ElementSearchResult } from '@/types/models';
 import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
+import { Point } from '@/types/map';
 import { handleAliasResults } from './handleAliasResults';
 import { handleReactionResults } from './handleReactionResults';
 
 interface HandleSearchResultActionInput {
   searchResults: ElementSearchResult[];
   dispatch: AppDispatch;
+  point: Point;
+  searchDistance?: string;
+  maxZoom: number;
+  zoom: number;
   hasFitBounds?: boolean;
-  fitBoundsZoom?: number;
+  isResultDrawerOpen: boolean;
 }
 
 export const handleSearchResultAction = async ({
   searchResults,
   dispatch,
+  point,
+  searchDistance,
+  maxZoom,
+  zoom,
   hasFitBounds,
-  fitBoundsZoom,
+  isResultDrawerOpen,
 }: HandleSearchResultActionInput): Promise<void> => {
   const closestSearchResult = searchResults[FIRST_ARRAY_ELEMENT];
   const { type } = closestSearchResult;
@@ -25,7 +34,14 @@ export const handleSearchResultAction = async ({
     REACTION: handleReactionResults,
   }[type];
 
-  await action(dispatch, closestSearchResult, hasFitBounds, fitBoundsZoom)(closestSearchResult);
+  await action(dispatch, closestSearchResult, {
+    point,
+    searchDistance,
+    maxZoom,
+    zoom,
+    hasFitBounds,
+    isResultDrawerOpen,
+  })(closestSearchResult);
 
   if (type === 'ALIAS') {
     PluginsEventBus.dispatchEvent('onBioEntityClick', closestSearchResult);
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts
index 4270a324..ae02e8c1 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts
@@ -37,6 +37,11 @@ const getEvent = (coordinate: MapBrowserEvent<UIEvent>['coordinate']): MapBrowse
     coordinate,
   }) as unknown as MapBrowserEvent<UIEvent>;
 
+const MAX_ZOOM_MOCK_MOCK = 9;
+const ZOOM_MOCK = 3;
+const SEARCH_DISTANCE_MOCK = '10';
+const IS_RESULT_DRAWER_OPEN_MOCK = true;
+
 describe('onMapSingleClick - util', () => {
   beforeEach(() => {
     jest.clearAllMocks();
@@ -54,7 +59,15 @@ describe('onMapSingleClick - util', () => {
       minZoom: 2,
       maxZoom: 9,
     };
-    const handler = onMapSingleClick(mapSize, modelId, dispatch);
+    const handler = onMapSingleClick(
+      mapSize,
+      modelId,
+      dispatch,
+      SEARCH_DISTANCE_MOCK,
+      MAX_ZOOM_MOCK_MOCK,
+      ZOOM_MOCK,
+      IS_RESULT_DRAWER_OPEN_MOCK,
+    );
     const coordinate = [90, 90];
     const event = getEvent(coordinate);
 
@@ -79,7 +92,15 @@ describe('onMapSingleClick - util', () => {
       minZoom: 2,
       maxZoom: 9,
     };
-    const handler = onMapSingleClick(mapSize, modelId, dispatch);
+    const handler = onMapSingleClick(
+      mapSize,
+      modelId,
+      dispatch,
+      SEARCH_DISTANCE_MOCK,
+      MAX_ZOOM_MOCK_MOCK,
+      ZOOM_MOCK,
+      IS_RESULT_DRAWER_OPEN_MOCK,
+    );
     const coordinate = [90, 90];
     const point = { x: 180.0008084837557, y: 179.99919151624428 };
     const event = getEvent(coordinate);
@@ -111,7 +132,15 @@ describe('onMapSingleClick - util', () => {
       maxZoom: 9,
     };
 
-    const handler = onMapSingleClick(mapSize, modelId, dispatch);
+    const handler = onMapSingleClick(
+      mapSize,
+      modelId,
+      dispatch,
+      SEARCH_DISTANCE_MOCK,
+      MAX_ZOOM_MOCK_MOCK,
+      ZOOM_MOCK,
+      IS_RESULT_DRAWER_OPEN_MOCK,
+    );
     const coordinate = [180, 180];
     const point = { x: 360.0032339350228, y: 359.9967660649771 };
     const event = getEvent(coordinate);
@@ -161,7 +190,15 @@ describe('onMapSingleClick - util', () => {
     } as unknown as Map;
 
     it('does NOT fire search result action handler', async () => {
-      const handler = onMapSingleClick(mapSize, modelId, dispatch);
+      const handler = onMapSingleClick(
+        mapSize,
+        modelId,
+        dispatch,
+        SEARCH_DISTANCE_MOCK,
+        MAX_ZOOM_MOCK_MOCK,
+        ZOOM_MOCK,
+        IS_RESULT_DRAWER_OPEN_MOCK,
+      );
       await handler(event, mapInstanceMock);
       await waitFor(() => expect(handleSearchResultActionSpy).not.toBeCalled());
     });
@@ -192,7 +229,15 @@ describe('onMapSingleClick - util', () => {
       } as unknown as Map;
 
       it('does fire search result action handler', async () => {
-        const handler = onMapSingleClick(mapSize, modelId, dispatch);
+        const handler = onMapSingleClick(
+          mapSize,
+          modelId,
+          dispatch,
+          SEARCH_DISTANCE_MOCK,
+          MAX_ZOOM_MOCK_MOCK,
+          ZOOM_MOCK,
+          IS_RESULT_DRAWER_OPEN_MOCK,
+        );
         await handler(event, mapInstanceMock);
         await waitFor(() => expect(handleSearchResultActionSpy).toBeCalled());
       });
@@ -225,7 +270,15 @@ describe('onMapSingleClick - util', () => {
       } as unknown as Map;
 
       it('does fire search result action - handle reaction', async () => {
-        const handler = onMapSingleClick(mapSize, modelId, dispatch);
+        const handler = onMapSingleClick(
+          mapSize,
+          modelId,
+          dispatch,
+          SEARCH_DISTANCE_MOCK,
+          MAX_ZOOM_MOCK_MOCK,
+          ZOOM_MOCK,
+          IS_RESULT_DRAWER_OPEN_MOCK,
+        );
         await handler(event, mapInstanceMock);
         await waitFor(() => expect(handleSearchResultActionSpy).toBeCalled());
       });
diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts
index 3d1e425c..31dabd9a 100644
--- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts
+++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts
@@ -10,7 +10,7 @@ import { handleSearchResultAction } from './handleSearchResultAction';
 
 /* prettier-ignore */
 export const onMapSingleClick =
-  (mapSize: MapSize, modelId: number, dispatch: AppDispatch) =>
+  (mapSize: MapSize, modelId: number, dispatch: AppDispatch, searchDistance: string | undefined, maxZoom: number, zoom: number, isResultDrawerOpen: boolean) =>
     async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => {
       const featuresAtPixel: FeatureLike[] = [];
       mapInstance.forEachFeatureAtPixel(pixel, (feature) => featuresAtPixel.push(feature));
@@ -24,10 +24,10 @@ export const onMapSingleClick =
       // so we need to reset all the data before updating
       dispatch(handleDataReset);
 
-      const searchResults = await getSearchResults({ coordinate, mapSize, modelId });
+      const {searchResults, point} = await getSearchResults({ coordinate, mapSize, modelId });
       if (!searchResults || searchResults.length === SIZE_OF_EMPTY_ARRAY) {
         return;
       }
 
-      handleSearchResultAction({ searchResults, dispatch });
+      handleSearchResultAction({ searchResults, dispatch, point, searchDistance, maxZoom, zoom, isResultDrawerOpen });
     };
diff --git a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.test.ts b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.test.ts
index 68b1c28b..340b61de 100644
--- a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.test.ts
+++ b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.test.ts
@@ -1,8 +1,8 @@
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
-import mapSlice from '@/redux/map/map.slice';
-import { getReduxWrapperUsingSliceReducer } from '@/utils/testing/getReduxWrapperUsingSliceReducer';
 import { renderHook } from '@testing-library/react';
 import { View } from 'ol';
+import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
+import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
 import * as singleClickListener from './mapSingleClick/onMapSingleClick';
 import * as positionListener from './onMapPositionChange';
 import { useOlMapListeners } from './useOlMapListeners';
@@ -25,7 +25,14 @@ jest.mock('use-debounce', () => {
 });
 
 describe('useOlMapListeners - util', () => {
-  const { Wrapper } = getReduxWrapperUsingSliceReducer('map', mapSlice);
+  const { Wrapper } = getReduxWrapperWithStore({
+    map: {
+      data: { ...initialMapDataFixture },
+      loading: 'succeeded',
+      error: { message: '', name: '' },
+      openedMaps: openedMapsThreeSubmapsFixture,
+    },
+  });
 
   beforeEach(() => {
     jest.clearAllMocks();
diff --git a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
index 72016ec3..86e88297 100644
--- a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
+++ b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts
@@ -1,6 +1,10 @@
-import { OPTIONS } from '@/constants/map';
+import { DEFAULT_ZOOM, OPTIONS } from '@/constants/map';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
-import { mapDataSizeSelector } from '@/redux/map/map.selectors';
+import {
+  mapDataLastZoomValue,
+  mapDataMaxZoomValue,
+  mapDataSizeSelector,
+} from '@/redux/map/map.selectors';
 import { currentModelIdSelector } from '@/redux/models/models.selectors';
 import { MapInstance } from '@/types/map';
 import { View } from 'ol';
@@ -10,6 +14,8 @@ import { Pixel } from 'ol/pixel';
 import { useEffect, useRef } from 'react';
 import { useSelector } from 'react-redux';
 import { useDebouncedCallback } from 'use-debounce';
+import { searchDistanceValSelector } from '@/redux/configuration/configuration.selectors';
+import { resultDrawerOpen } from '@/redux/drawer/drawer.selectors';
 import { onMapRightClick } from './mapRightClick/onMapRightClick';
 import { onMapSingleClick } from './mapSingleClick/onMapSingleClick';
 import { onMapPositionChange } from './onMapPositionChange';
@@ -23,6 +29,10 @@ interface UseOlMapListenersInput {
 export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput): void => {
   const mapSize = useSelector(mapDataSizeSelector);
   const modelId = useSelector(currentModelIdSelector);
+  const searchDistance = useSelector(searchDistanceValSelector);
+  const maxZoom = useSelector(mapDataMaxZoomValue);
+  const lastZoom = useSelector(mapDataLastZoomValue);
+  const isResultDrawerOpen = useSelector(resultDrawerOpen);
   const coordinate = useRef<Coordinate>([]);
   const pixel = useRef<Pixel>([]);
   const dispatch = useAppDispatch();
@@ -42,7 +52,15 @@ export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput)
   );
 
   const handleMapSingleClick = useDebouncedCallback(
-    onMapSingleClick(mapSize, modelId, dispatch),
+    onMapSingleClick(
+      mapSize,
+      modelId,
+      dispatch,
+      searchDistance,
+      maxZoom,
+      lastZoom || DEFAULT_ZOOM,
+      isResultDrawerOpen,
+    ),
     OPTIONS.clickPersistTime,
     { leading: false },
   );
diff --git a/src/constants/common.ts b/src/constants/common.ts
index 5f326139..092b9fc1 100644
--- a/src/constants/common.ts
+++ b/src/constants/common.ts
@@ -16,3 +16,5 @@ export const NOOP = (): void => {};
 
 export const ONE_DECIMAL = 0.1;
 export const ONE_HUNDRED = 100;
+
+export const ZOOM_FACTOR = 2.0; // Zoom factor indicating doubling the distance for each zoom level
diff --git a/src/models/mocks/configurationOptionMock.ts b/src/models/mocks/configurationOptionMock.ts
index c1e6a1e5..f7a293f7 100644
--- a/src/models/mocks/configurationOptionMock.ts
+++ b/src/models/mocks/configurationOptionMock.ts
@@ -6,6 +6,7 @@ export const CONFIGURATION_OPTIONS_TYPES_MOCK: string[] = [
   'SIMPLE_COLOR_VAL',
   'NEUTRAL_COLOR_VAL',
   'OVERLAY_OPACITY',
+  'SEARCH_DISTANCE',
 ];
 
 export const CONFIGURATION_OPTIONS_COLOURS_MOCK: ConfigurationOption[] = [
@@ -54,4 +55,13 @@ export const CONFIGURATION_OPTIONS_COLOURS_MOCK: ConfigurationOption[] = [
     value: '0.8',
     group: 'Overlays',
   },
+  {
+    idObject: 19,
+    type: 'SEARCH_DISTANCE',
+    valueType: 'DOUBLE',
+    commonName: 'Max distance for clicking on element (px)',
+    isServerSide: false,
+    value: '10',
+    group: 'Point and click',
+  },
 ];
diff --git a/src/redux/configuration/configuration.constants.ts b/src/redux/configuration/configuration.constants.ts
index 9a2762d9..a03edc92 100644
--- a/src/redux/configuration/configuration.constants.ts
+++ b/src/redux/configuration/configuration.constants.ts
@@ -3,6 +3,7 @@ export const MAX_COLOR_VAL_NAME_ID = 'MAX_COLOR_VAL';
 export const SIMPLE_COLOR_VAL_NAME_ID = 'SIMPLE_COLOR_VAL';
 export const NEUTRAL_COLOR_VAL_NAME_ID = 'NEUTRAL_COLOR_VAL';
 export const OVERLAY_OPACITY_NAME_ID = 'OVERLAY_OPACITY';
+export const SEARCH_DISTANCE_NAME_ID = 'SEARCH_DISTANCE';
 
 export const LEGEND_FILE_NAMES_IDS = [
   'LEGEND_FILE_1',
diff --git a/src/redux/configuration/configuration.selectors.ts b/src/redux/configuration/configuration.selectors.ts
index 25cbfbfa..00683ccb 100644
--- a/src/redux/configuration/configuration.selectors.ts
+++ b/src/redux/configuration/configuration.selectors.ts
@@ -16,6 +16,7 @@ import {
   SBML_HANDLER_NAME_ID,
   SIMPLE_COLOR_VAL_NAME_ID,
   SVG_IMAGE_HANDLER_NAME_ID,
+  SEARCH_DISTANCE_NAME_ID,
 } from './configuration.constants';
 import { ConfigurationHandlersIds, ConfigurationImageHandlersIds } from './configuration.types';
 
@@ -50,6 +51,11 @@ export const simpleColorValSelector = createSelector(
   state => configurationAdapterSelectors.selectById(state, SIMPLE_COLOR_VAL_NAME_ID)?.value,
 );
 
+export const searchDistanceValSelector = createSelector(
+  configurationOptionsSelector,
+  state => configurationAdapterSelectors.selectById(state, SEARCH_DISTANCE_NAME_ID)?.value,
+);
+
 export const defaultLegendImagesSelector = createSelector(configurationOptionsSelector, state =>
   LEGEND_FILE_NAMES_IDS.map(
     legendNameId => configurationAdapterSelectors.selectById(state, legendNameId)?.value,
diff --git a/src/redux/drawer/drawer.constants.ts b/src/redux/drawer/drawer.constants.ts
index bef3fd3d..84246ac3 100644
--- a/src/redux/drawer/drawer.constants.ts
+++ b/src/redux/drawer/drawer.constants.ts
@@ -20,6 +20,8 @@ export const DRAWER_INITIAL_STATE: DrawerState = {
   },
 };
 
+export const RESULT_DRAWERS = ['search', 'reaction', 'bio-entity'];
+
 export const DRUGS_FOR_BIO_ENTITY_FETCHING_ERROR_PREFIX = 'Failed to fetch drugs for bio entity';
 export const CHEMICALS_FOR_BIO_ENTITY_FETCHING_ERROR_PREFIX =
   'Failed to fetch chemicals for bio entity';
diff --git a/src/redux/drawer/drawer.selectors.ts b/src/redux/drawer/drawer.selectors.ts
index 30d9831c..af943c46 100644
--- a/src/redux/drawer/drawer.selectors.ts
+++ b/src/redux/drawer/drawer.selectors.ts
@@ -2,6 +2,7 @@ import { DEFAULT_FETCH_DATA } from '@/constants/fetchData';
 import { rootSelector } from '@/redux/root/root.selectors';
 import { assertNever } from '@/utils/assertNever';
 import { createSelector } from '@reduxjs/toolkit';
+import { RESULT_DRAWERS } from './drawer.constants';
 
 export const drawerSelector = createSelector(rootSelector, state => state.drawer);
 
@@ -127,3 +128,7 @@ export const currentStepOverlayDrawerStateSelector = createSelector(
   overlayDrawerStateSelector,
   state => state.currentStep,
 );
+
+export const resultDrawerOpen = createSelector(drawerSelector, drawer => {
+  return drawer.isOpen && RESULT_DRAWERS.includes(drawer.drawerName);
+});
diff --git a/src/redux/map/map.selectors.ts b/src/redux/map/map.selectors.ts
index ac398f90..257f99fd 100644
--- a/src/redux/map/map.selectors.ts
+++ b/src/redux/map/map.selectors.ts
@@ -32,3 +32,5 @@ export const mapDataLastZoomValue = createSelector(
   mapDataLastPositionSelector,
   position => position.z,
 );
+
+export const mapDataMaxZoomValue = createSelector(mapDataSizeSelector, model => model.maxZoom);
diff --git a/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts b/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts
index c04dfaac..207c91c0 100644
--- a/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts
+++ b/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts
@@ -3,6 +3,10 @@ import { handleSearchResultAction } from '@/components/Map/MapViewer/utils/liste
 import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common';
 import { store } from '@/redux/store';
 import { getElementsByPoint } from '@/utils/search/getElementsByCoordinates';
+import { mapDataLastZoomValue, mapDataMaxZoomValue } from '@/redux/map/map.selectors';
+import { searchDistanceValSelector } from '@/redux/configuration/configuration.selectors';
+import { DEFAULT_ZOOM } from '@/constants/map';
+import { resultDrawerOpen } from '@/redux/drawer/drawer.selectors';
 import { Coordinates } from './triggerSearch.types';
 
 export const searchByCoordinates = async (
@@ -11,11 +15,16 @@ export const searchByCoordinates = async (
   hasFitBounds?: boolean,
   fitBoundsZoom?: number,
 ): Promise<void> => {
-  const { dispatch } = store;
+  const { dispatch, getState } = store;
   // side-effect below is to prevent complications with data update - old data may conflict with new data
   // so we need to reset all the data before updating
   dispatch(handleDataReset);
 
+  const maxZoom = mapDataMaxZoomValue(getState());
+  const lastZoom = mapDataLastZoomValue(getState());
+  const searchDistance = searchDistanceValSelector(getState());
+  const isResultDrawerOpen = resultDrawerOpen(getState());
+
   const searchResults = await getElementsByPoint({
     point: coordinates,
     currentModelId: modelId,
@@ -25,5 +34,14 @@ export const searchByCoordinates = async (
     return;
   }
 
-  handleSearchResultAction({ searchResults, dispatch, hasFitBounds, fitBoundsZoom });
+  handleSearchResultAction({
+    searchResults,
+    dispatch,
+    hasFitBounds,
+    zoom: fitBoundsZoom || lastZoom || DEFAULT_ZOOM,
+    maxZoom,
+    point: coordinates,
+    searchDistance,
+    isResultDrawerOpen,
+  });
 };
diff --git a/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts b/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts
index 707fb162..bdec8559 100644
--- a/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts
+++ b/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts
@@ -1,5 +1,7 @@
 /* eslint-disable no-magic-numbers */
 import { handleSearchResultAction } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction';
+import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
+import { CONFIGURATION_INITIAL_STORE_MOCKS } from '@/redux/configuration/configuration.mock';
 import { bioEntityResponseFixture } from '@/models/fixtures/bioEntityContentsFixture';
 import { chemicalsFixture } from '@/models/fixtures/chemicalsFixture';
 import { drugsFixture } from '@/models/fixtures/drugFixtures';
@@ -9,8 +11,8 @@ import { RootState, store } from '@/redux/store';
 import { mockNetworkNewAPIResponse, mockNetworkResponse } from '@/utils/mockNetworkResponse';
 import { waitFor } from '@testing-library/react';
 import { HttpStatusCode } from 'axios';
-import { ERROR_INVALID_MODEL_ID_TYPE } from '../../errorMessages';
 import { triggerSearch } from './triggerSearch';
+import { ERROR_INVALID_MODEL_ID_TYPE } from '../../errorMessages';
 
 const mockedAxiosClient = mockNetworkNewAPIResponse();
 const mockedAxiosOldClient = mockNetworkResponse();
@@ -18,6 +20,19 @@ const SEARCH_QUERY = 'park7';
 const point = { x: 545.8013, y: 500.9926 };
 const modelId = 1000;
 
+const MOCK_STATE = {
+  drawer: {
+    isOpen: false,
+  },
+  map: {
+    data: { ...initialMapDataFixture },
+    loading: 'succeeded',
+    error: { message: '', name: '' },
+    openedMaps: openedMapsThreeSubmapsFixture,
+  },
+  configuration: CONFIGURATION_INITIAL_STORE_MOCKS,
+};
+
 jest.mock('../../../../redux/store');
 jest.mock(
   '../../../../components/Map/MapViewer/utils/listeners/mapSingleClick/handleSearchResultAction',
@@ -29,6 +44,7 @@ describe('triggerSearch', () => {
   });
   describe('search by query', () => {
     it('should throw error if query param is wrong type', async () => {
+      jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState);
       const invalidParams = {
         // eslint-disable-next-line @typescript-eslint/no-explicit-any
         query: 123 as any,
@@ -39,14 +55,9 @@ describe('triggerSearch', () => {
       );
     });
     it('should search for provided query and open drawer when it is not open', async () => {
-      const getState = jest.spyOn(store, 'getState').mockImplementation(
-        () =>
-          ({
-            drawer: {
-              isOpen: false,
-            },
-          }) as RootState,
-      );
+      const getState = jest
+        .spyOn(store, 'getState')
+        .mockImplementation(() => MOCK_STATE as RootState);
       mockedAxiosClient
         .onGet(
           apiPath.getBioEntityContentsStringWithQuery({
@@ -87,6 +98,7 @@ describe('triggerSearch', () => {
       const getState = jest.spyOn(store, 'getState').mockImplementation(
         () =>
           ({
+            ...MOCK_STATE,
             drawer: {
               isOpen: true,
             },
@@ -132,6 +144,7 @@ describe('triggerSearch', () => {
   });
   describe('search by coordinations', () => {
     it('should throw error if coordinates param is wrong type', async () => {
+      jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState);
       const invalidParams = {
         // eslint-disable-next-line @typescript-eslint/no-explicit-any
         coordinates: {} as any,
@@ -143,6 +156,7 @@ describe('triggerSearch', () => {
       );
     });
     it('should throw error if model id param is wrong type', async () => {
+      jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState);
       const invalidParams = {
         coordinates: { x: 992, y: 993 },
         // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -152,6 +166,7 @@ describe('triggerSearch', () => {
       await expect(triggerSearch(invalidParams)).rejects.toThrowError(ERROR_INVALID_MODEL_ID_TYPE);
     });
     it('should search result with proper data', async () => {
+      jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState);
       mockedAxiosOldClient
         .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId))
         .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_ALIAS]);
@@ -175,10 +190,20 @@ describe('triggerSearch', () => {
         expect(handleSearchResultAction).toHaveBeenCalledWith({
           searchResults: [ELEMENT_SEARCH_RESULT_MOCK_ALIAS],
           dispatch: store.dispatch,
+          hasFitBounds: undefined,
+          isResultDrawerOpen: false,
+          maxZoom: 9,
+          point: {
+            x: 545.8013,
+            y: 500.9926,
+          },
+          searchDistance: undefined,
+          zoom: 5,
         });
       });
     });
     it('should not search result if there is no bio entity with specific coordinates', async () => {
+      jest.spyOn(store, 'getState').mockImplementation(() => MOCK_STATE as RootState);
       mockedAxiosOldClient
         .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, modelId))
         .reply(HttpStatusCode.Ok, []);
diff --git a/src/shared/Icon/Icon.component.tsx b/src/shared/Icon/Icon.component.tsx
index 258f7a09..172101ad 100644
--- a/src/shared/Icon/Icon.component.tsx
+++ b/src/shared/Icon/Icon.component.tsx
@@ -20,6 +20,7 @@ import { MaginfierZoomInIcon } from './Icons/MagnifierZoomIn';
 import { MaginfierZoomOutIcon } from './Icons/MagnifierZoomOut';
 import { ReloadIcon } from './Icons/ReloadIcon';
 import { ThreeDotsIcon } from './Icons/ThreeDotsIcon';
+import { ClearIcon } from './Icons/ClearIcon';
 
 export interface IconProps {
   className?: string;
@@ -47,6 +48,7 @@ const icons: Record<IconTypes, IconComponentType> = {
   'magnifier-zoom-out': MaginfierZoomOutIcon,
   'three-dots': ThreeDotsIcon,
   reload: ReloadIcon,
+  clear: ClearIcon,
 } as const;
 
 export const Icon = ({ name, className = '', ...rest }: IconProps): JSX.Element => {
diff --git a/src/shared/Icon/Icons/ClearIcon.tsx b/src/shared/Icon/Icons/ClearIcon.tsx
new file mode 100644
index 00000000..5b446bc5
--- /dev/null
+++ b/src/shared/Icon/Icons/ClearIcon.tsx
@@ -0,0 +1,32 @@
+interface ClearIconProps {
+  className?: string;
+}
+
+export const ClearIcon = ({ className, ...rest }: ClearIconProps): JSX.Element => (
+  <svg
+    width="24"
+    height="24"
+    viewBox="0 0 24 24"
+    fill="none"
+    className={className}
+    xmlns="http://www.w3.org/2000/svg"
+    {...rest}
+  >
+    <g clipPath="url(#clip0_4431_10860)">
+      <path
+        d="M12 23.7279L5.63604 17.364C2.12132 13.8492 2.12132 8.15076 5.63604 4.63604C9.15076 1.12132 14.8492 1.12132 18.364 4.63604C21.8787 8.15076 21.8787 13.8492 18.364 17.364L12 23.7279ZM16.9497 15.9497C19.6834 13.2161 19.6834 8.78392 16.9497 6.05025C14.2161 3.31658 9.78392 3.31658 7.05025 6.05025C4.31658 8.78392 4.31658 13.2161 7.05025 15.9497L12 20.8995L16.9497 15.9497ZM12 13C10.8954 13 10 12.1046 10 11C10 9.89543 10.8954 9 12 9C13.1046 9 14 9.89543 14 11C14 12.1046 13.1046 13 12 13Z"
+        fill="currentColor"
+      />
+      <rect x="13" y="-2" width="12" height="12" rx="6" fill="white" />
+      <path
+        d="M18.9999 3.2952L21.4748 0.820312L22.1819 1.52742L19.707 4.0023L22.1819 6.47715L21.4748 7.18425L18.9999 4.7094L16.525 7.18425L15.8179 6.47715L18.2928 4.0023L15.8179 1.52742L16.525 0.820312L18.9999 3.2952Z"
+        fill="currentColor"
+      />
+    </g>
+    <defs>
+      <clipPath id="clip0_4431_10860">
+        <rect width="24" height="24" fill="white" />
+      </clipPath>
+    </defs>
+  </svg>
+);
diff --git a/src/types/iconTypes.ts b/src/types/iconTypes.ts
index 1a7ec425..b33c7f6a 100644
--- a/src/types/iconTypes.ts
+++ b/src/types/iconTypes.ts
@@ -18,6 +18,7 @@ export type IconTypes =
   | 'magnifier-zoom-out'
   | 'pin'
   | 'three-dots'
-  | 'reload';
+  | 'reload'
+  | 'clear';
 
 export type IconComponentType = ({ className }: { className: string }) => JSX.Element;
-- 
GitLab