diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx index e21ec8456..1ed7c4133 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx @@ -6,6 +6,7 @@ import { Key } from 'ts-key-enum'; import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader'; import { RecordBoardStickyHeaderEffect } from '@/object-record/record-board/components/RecordBoardStickyHeaderEffect'; +import { RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-board/constants/RecordBoardClickOutsideListenerId'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; @@ -16,7 +17,7 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; -import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; +import { useListenClickOutsideV2 } from '@/ui/utilities/pointer-event/hooks/useListenClickOutsideV2'; import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useScrollRestoration } from '~/hooks/useScrollRestoration'; @@ -69,9 +70,15 @@ export const RecordBoard = () => { const { resetRecordSelection, setRecordAsSelected } = useRecordBoardSelection(recordBoardId); - useListenClickOutsideByClassName({ - classNames: ['record-board-card'], - excludeClassNames: ['bottom-bar', 'action-menu-dropdown', 'command-menu'], + useListenClickOutsideV2({ + excludeClassNames: [ + 'bottom-bar', + 'action-menu-dropdown', + 'command-menu', + 'modal-backdrop', + ], + listenerId: RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID, + refs: [boardRef], callback: resetRecordSelection, }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/constants/RecordBoardClickOutsideListenerId.ts b/packages/twenty-front/src/modules/object-record/record-board/constants/RecordBoardClickOutsideListenerId.ts new file mode 100644 index 000000000..5179225aa --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/constants/RecordBoardClickOutsideListenerId.ts @@ -0,0 +1 @@ +export const RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID = 'record-board'; diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableInternalEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableInternalEffect.tsx index 19aa58b25..ce44b28de 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableInternalEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableInternalEffect.tsx @@ -33,6 +33,12 @@ export const RecordTableInternalEffect = ({ ); useListenClickOutsideV2({ + excludeClassNames: [ + 'bottom-bar', + 'action-menu-dropdown', + 'command-menu', + 'modal-backdrop', + ], listenerId: RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID, refs: [tableBodyRef], callback: () => { diff --git a/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx b/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx index f236b7884..2f9530aeb 100644 --- a/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx +++ b/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/__tests__/useListenClickOutsideArrayOfRef.test.tsx @@ -1,13 +1,12 @@ +import { fireEvent, renderHook } from '@testing-library/react'; import React from 'react'; import { act } from 'react-dom/test-utils'; -import { fireEvent, render, renderHook } from '@testing-library/react'; import { isDefined } from '~/utils/isDefined'; import { ClickOutsideMode, useListenClickOutside, - useListenClickOutsideByClassName, } from '../useListenClickOutside'; const containerRef = React.createRef(); @@ -77,59 +76,3 @@ describe('useListenClickOutside', () => { expect(callback).toHaveBeenCalled(); }); }); - -describe('useListenClickOutsideByClassName', () => { - it('should trigger the callback when clicking outside the specified class names', () => { - const callback = jest.fn(); - const { container } = render( -
-
Inside
-
Outside
-
, - ); - - renderHook(() => - useListenClickOutsideByClassName({ - classNames: ['wont-trigger'], - callback, - }), - ); - - act(() => { - const notClickableElement = container.querySelector('.will-trigger'); - if (isDefined(notClickableElement)) { - fireEvent.mouseDown(notClickableElement); - fireEvent.click(notClickableElement); - } - }); - - expect(callback).toHaveBeenCalled(); - }); - - it('should not trigger the callback when clicking inside the specified class names', () => { - const callback = jest.fn(); - const { container } = render( -
-
Inside
-
Outside
-
, - ); - - renderHook(() => - useListenClickOutsideByClassName({ - classNames: ['wont-trigger'], - callback, - }), - ); - - act(() => { - const notClickableElement = container.querySelector('.wont-trigger'); - if (isDefined(notClickableElement)) { - fireEvent.mouseDown(notClickableElement); - fireEvent.click(notClickableElement); - } - }); - - expect(callback).not.toHaveBeenCalled(); - }); -}); diff --git a/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutside.ts b/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutside.ts index b3a879216..613590ed5 100644 --- a/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutside.ts +++ b/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutside.ts @@ -138,58 +138,3 @@ export const useListenClickOutside = ({ } }, [refs, callback, mode, enabled, isMouseDownInside]); }; - -export const useListenClickOutsideByClassName = ({ - classNames, - excludeClassNames, - callback, -}: { - classNames: string[]; - excludeClassNames?: string[]; - callback: () => void; -}) => { - useEffect(() => { - const handleClickOutside = (event: MouseEvent | TouchEvent) => { - if (!(event.target instanceof Node)) return; - - const clickedElement = event.target as HTMLElement; - let isClickedInside = false; - let isClickedOnExcluded = false; - let currentElement: HTMLElement | null = clickedElement; - - while (currentElement) { - const currentClassList = currentElement.classList; - - isClickedInside = classNames.some((className) => - currentClassList.contains(className), - ); - isClickedOnExcluded = - excludeClassNames?.some((className) => - currentClassList.contains(className), - ) ?? false; - - if (isClickedInside || isClickedOnExcluded) { - break; - } - - currentElement = currentElement.parentElement; - } - - if (!isClickedInside && !isClickedOnExcluded) { - callback(); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - document.addEventListener('touchend', handleClickOutside, { - capture: true, - }); - - return () => { - document.removeEventListener('mousedown', handleClickOutside); - document.removeEventListener('touchend', handleClickOutside, { - capture: true, - }); - }; - }, [callback, classNames, excludeClassNames]); -}; diff --git a/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutsideV2.ts b/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutsideV2.ts index 287d6b0f8..82611db7e 100644 --- a/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutsideV2.ts +++ b/packages/twenty-front/src/modules/ui/utilities/pointer-event/hooks/useListenClickOutsideV2.ts @@ -10,6 +10,7 @@ export enum ClickOutsideMode { export type ClickOutsideListenerProps = { refs: Array>; + excludeClassNames?: string[]; callback: (event: MouseEvent | TouchEvent) => void; mode?: ClickOutsideMode; listenerId: string; @@ -18,6 +19,7 @@ export type ClickOutsideListenerProps = { export const useListenClickOutsideV2 = ({ refs, + excludeClassNames, callback, mode = ClickOutsideMode.compareHTMLRef, listenerId, @@ -106,11 +108,34 @@ export const useListenClickOutsideV2 = ({ .getValue(); if (mode === ClickOutsideMode.compareHTMLRef) { + const clickedElement = event.target as HTMLElement; + let isClickedOnExcluded = false; + let currentElement: HTMLElement | null = clickedElement; + + while (currentElement) { + const currentClassList = currentElement.classList; + + isClickedOnExcluded = + excludeClassNames?.some((className) => + currentClassList.contains(className), + ) ?? false; + + if (isClickedOnExcluded) { + break; + } + + currentElement = currentElement.parentElement; + } + const clickedOnAtLeastOneRef = refs .filter((ref) => !!ref.current) .some((ref) => ref.current?.contains(event.target as Node)); - if (!clickedOnAtLeastOneRef && !isMouseDownInside) { + if ( + !clickedOnAtLeastOneRef && + !isMouseDownInside && + !isClickedOnExcluded + ) { callback(event); } } @@ -151,7 +176,13 @@ export const useListenClickOutsideV2 = ({ } } }, - [mode, refs, callback, getClickOutsideListenerIsMouseDownInsideState], + [ + getClickOutsideListenerIsMouseDownInsideState, + mode, + refs, + excludeClassNames, + callback, + ], ); useEffect(() => {